Spreadsheet-Read
view release on metacpan or search on metacpan
scripts/xlsx2csv view on Meta::CPAN
print "usage: $cmd [-A [-N | -J c] | -o file.csv] [-s sep] [-f] [-i] file.xls\n";
print " $cmd --help | --man | --info\n";
print " --list List supported spreadsheet formats and exit\n";
print " -A --all Export all sheets (filename-sheetname.csv)\n";
print " -N --no-pfx No filename prefix on -A (sheetname.csv)\n";
print " -Z --zip Convert sheets to CSV's in ZIP\n";
print " -J s --join=s Use s to join filename-sheetname (-)\n";
print " -o f --out=f Set output filename\n";
print " -i f --in=f Set input filename\n";
print " -f --force Force overwrite output if exists\n";
print " -s s --sep=s Set CSV separator character\n";
print "Unless -A is used, all other options are passed on to xlscat\n";
@_ and print join "\n", @_, "";
exit $err;
} # usage
use Getopt::Long qw( :config bundling noignorecase passthrough );
GetOptions (
"help|?" => sub { usage 0; },
"V|version" => sub { print "$cmd [$VERSION]\n"; exit 0; },
"man" => sub { pod_nroff (); },
"info" => sub { pod_text (); },
"list" => sub { list_parsers (); },
"o|c|out=s" => \ my $csv,
"i|x|in=s" => \ my $xls,
"f|force!" => \ my $opt_f,
"s|sep=s" => \ my $opt_s,
"A|all!" => \ my $opt_A,
"N|no-pfx!" => \ my $opt_N,
"Z|zip!" => \ my $opt_Z,
"J|join=s" => \(my $opt_J = "-"),
"v|verbose:1" => \(my $opt_v = 0),
) or usage 1;
sub list_parsers {
print "Ext Parser module Req Has Def\n";
print "----- ----------------------- ----- ----- ---\n";
for (Spreadsheet::Read::parsers ()) {
printf "%-5s %-23s %5s %5s %s\n",
$_->{ext}, $_->{mod}, $_->{min}, $_->{vsn}, $_->{def} ? "<==" : "";
}
exit 0;
} # list_parsers
sub pod_text {
require Pod::Text::Color;
my $m = $ENV{NO_COLOR} ? "Pod::Text" : "Pod::Text::Color";
my $p = $m->new ();
open my $fh, ">", \my $out;
$p->parse_from_file ($0, $fh);
close $fh;
print $out;
exit 0;
} # pod_text
sub pod_nroff {
first { -x "$_/nroff" } grep { -d } split m/:+/ => $ENV{PATH} or pod_text ();
require Pod::Man;
my $p = Pod::Man->new ();
open my $fh, "|-", "nroff", "-man";
$p->parse_from_file ($0, $fh);
close $fh;
exit 0;
} # pod_nroff
unless ($xls) {
foreach my $i (reverse 0 .. $#ARGV) {
-f $ARGV[$i] or next;
$xls = splice @ARGV, $i, 1;
last;
}
}
$xls or usage 1, "No input file";
-r $xls or usage 1, "Input file unreadable";
-s $xls or usage 1, "Input file empty";
my @known_ext = Spreadsheet::Read::parses (0);
@known_ext or @known_ext = qw( csv ods sc sxc xls xlsx );
my $valid_ext = do { local $" = "|"; qr{ \. (?: @known_ext )}xi };
if ($opt_Z) {
require Archive::Zip;
$@ and die "--zip requires Archive::Zip, which could not be loaded\n";
}
my %e = (n => "\n", t => "\t", e => "\e", r => "\r");
$opt_s and $opt_s =~ s/\\+([nter])/$e{$1}/g;
if ($opt_A || $opt_Z) {
$opt_v and warn "Reading $xls ...\n";
my $ss = ReadData ($xls) or die "Cannot read/parse $xls\n";
my $az = $opt_Z ? Archive::Zip->new : undef;
$csv and $xls = $csv;
$xls =~ s/$valid_ext $//ix;
$csv = Text::CSV_XS->new ({ binary => 1, auto_diag => 1, eol => "\r\n" });
$opt_s and $csv->sep_char ($opt_s);
foreach my $si (1 .. $ss->[0]{sheets}) {
my $s = $ss->[$si] or next;
$s->{maxcol} or next;
my $mr = $s->{maxrow} or next;
my $sn = $s->{label} || "sheet-$si";
$sn =~ s/\s+$//;
$sn =~ s/^\s+//;
$sn =~ s/[^-\w.]+/_/g; # remove any special chars from worksheet name
if ($opt_Z) {
open my $fh, ">:encoding(utf-8)", \my $data;
$csv->print ($fh, [ row ($s, $_) ]) for 1 .. $mr;
close $fh;
$az->addString ($data, "$sn.csv");
next;
}
my $fn = $opt_N ? "$sn.csv" : "$xls$opt_J$sn.csv";
-f $fn && !$opt_f and die "$fn already exists\n";
warn "Saving sheet to $fn ...\n";
open my $fh, ">:encoding(utf-8)", $fn or die "$fn: $!\n";
( run in 0.402 second using v1.01-cache-2.11-cpan-5511b514fd6 )