środa, 8 lipca 2015

Konwersja Excela na CSV

Jak to z Microsoftem bywa nie jest łatwo. Są dwa formaty Excela -- stary (.xls) oraz nowy (.xlsx). Pakiety Perlowe Spreadsheet::Excel oraz Spreadsheet::ParseXLSX radzą sobie nieźle, aczkolwiek oczywiście gwarancji nie ma i być nie może skoro sam Excel czasami siebie samego nie potrafi zinterpretować.

No ale jest jeszcze trzeci format: jak plik .xlsx jest zabezpieczone hasłem (password protected). I na taką okoliczność nie ma zbyt wielu narzędzi. Można wszakże problem rozwiązać w dwóch krokach korzystając Libreoffice, który potrafi interpretować pliki Excela i można go uruchomić w trybie batch:

#!/bin/bash
XLS="$1"
TMP="${XLS%.*}.xlsx"
libreoffice --headless --convert-to xlsx "$XLS" --outdir ./xlsx-temp/
perl xslx2csv.pl ./xlsx-temp/"$TMP" "$OUTFILE"

Powyższy skrypt obsłuży wszystkie rodzaje plików Excela, zamieniając je najpierw na plik w formacie XLSX (plik password protected zostanie zmieniony na prawdziwy format XLSX, interpretowalny przez np. Spreadsheet::ParseXLSX).

Można od razu konwertować do CSV (--convert-to csv), ale konwersji będzie podlegać tylko pierwszy arkusz. Jak interesuje nas na przykład drugi, to kicha... nie da się (a przynajmniej ja nie wiem jak to osiągnąć). Inny problem to zamiana XLSX→XLSX -- nie ma w LibreOffice możliwości określenia nazwy pliku wynikowego, a próba:

libreoffice --headless --convert-to xlsx plik.xlsx

Kończy się błędem. Na szczęście jest obejście w postaci opcji --outdir. Plik wyjściowy -- o tej samej nazwie co wejściowy -- jest zapisywany w innym katalogu i problem rozwiązany.

Po zamianie Excela na ,,kanoniczny'' XLSX do konwersji na CSV można wykorzystać następujący skrypt Perla:

#!/usr/bin/perl
# Wykorzystanie perl xslx2csv.pl plik.xslx [numer-arkusza]

use Spreadsheet::ParseXLSX;
use open ":encoding(utf8)";
use open IN => ":encoding(utf8)", OUT => ":utf8";

$xslxfile = $ARGV[0]; 
$ArkuszNo = $ARGV[1] || 1; ## domyślnie arkuszu 1

my $source_excel = new Spreadsheet::ParseXLSX;
my $source_book = $source_excel->parse("$xslxfile")
  or die "Could not open source Excel file $xslxfile: $!";

# Zapisuje zawartość wybranego arkusza do hasza %csv
my %csv = ();

foreach my $sheet_number (0 .. $source_book->{SheetCount}-1) {
  my $sheet = $source_book->{Worksheet}[$sheet_number];

  print STDERR "*** SHEET:", $sheet->{Name}, "/", $sheet_number, "\n";
  if ( $ArkuszNo ==  $sheet_number + 1 ) {

    next unless defined $sheet->{MaxRow};
    next unless $sheet->{MinRow} <= $sheet->{MaxRow};
    next unless defined $sheet->{MaxCol};
    next unless $sheet->{MinCol} <= $sheet->{MaxCol};

    foreach my $row_index ($sheet->{MinRow} .. $sheet->{MaxRow}) {
       foreach my $col_index ($sheet->{MinCol} .. $sheet->{MaxCol}) {
          my $source_cell = $sheet->{Cells}[$row_index][$col_index];
	  if ($source_cell) {
	    $csv{$row_index}{$col_index} = $source_cell->Value;
	  }
       }
    }
  }
}

Arkusz jest w haszu %csv. Jak go przekształcić/wydrukować itp. pozostawiam inwencji ewentualnego czytelnika.

1 komentarz: