Spreadsheet-Read
view release on metacpan or search on metacpan
wrap => 0,
merged => 0,
hidden => 0,
locked => 0,
enc => "utf-8", # $ENV{LC_ALL} // $ENV{LANG} // ...
formula => undef,
);
# Helper functions
sub _dump {
my ($label, $ref) = @_;
if ($can{dmp}) {
print STDERR Data::Peek::DDumper ({ $label => $ref });
}
else {
print STDERR Data::Dumper->Dump ([$ref], [$label]);
}
my @c = caller (1);
print STDERR "<<- $c[1]:$c[2]|$c[3]\n";
} # _dump
sub _parser {
my $type = shift or return "";
if ($type =~ m/::/ and my @p = grep { $_->[1] eq $type } @parsers) {
my $format = $p[0][0];
$ENV{"SPREADSHEET_READ_\U$format"} = $type;
eval "local \$_; require $type";
$@ and croak ("Forced backend $type for $format fails to load:\n$@");
$can{$format} = $type;
$type = $format;
}
$type = lc $type;
my $ods = $can{ods} ? "ods" : "sxc";
# Aliases and fullnames
$type eq "excel" and return "xls";
$type eq "excel2007" and return "xlsx";
$type eq "xlsm" and return "xlsx";
$type eq "oo" and return $ods;
# $type eq "sxc" and return $ods;
$type eq "openoffice" and return $ods;
$type eq "libreoffice" and return $ods;
$type eq "perl" and return "prl";
$type eq "scalc" and return "sc";
$type eq "squirrelcalc" and return "sc";
return exists $can{$type} ? $type : "";
} # _parser
sub new {
my $class = shift;
my $r = ReadData (@_);
unless ($r) {
@_ and return; # new with arguments failed to open resource
$r = [{
parsers => [],
error => undef,
sheets => 0,
sheet => { },
}];
}
bless $r => $class;
} # new
sub parsers {
ref $_[0] eq __PACKAGE__ and shift;
my @c;
for (sort { $a->[0] cmp $b->[0] || $a->[1] cmp $b->[1] }
grep { $_->[0] !~ m{^(?:dmp|ios|!.*)$} }
@parsers) {
my ($typ, $mod, $min) = @$_;
eval "local \$_; require $mod";
my $vsn = $@ ? "-" : eval { $mod->VERSION };
push @c => {
ext => $typ,
mod => $mod,
min => $min,
vsn => $vsn,
def => $can{$typ} eq $mod ? "*" : "",
};
}
@c;
} # parsers
# Spreadsheet::Read::parses ("csv") or die "Cannot parse CSV"
sub parses {
ref $_[0] eq __PACKAGE__ and shift;
my $type = shift or
return sort grep { !m/^(?:dmp|ios)/ && $can{$_} !~ m{^!} }
keys %can;
$type = _parser ($type) or return 0;
if ($can{$type} =~ m/^!\s*(.*)/) {
$@ = $1;
return 0;
}
return $can{$type} || 0;
} # parses
sub sheets {
my $ctrl = shift->[0];
wantarray or return $ctrl->{sheets};
my $s = $ctrl->{sheet} or return (); # No labels defined
sort { $s->{$a} <=> $s->{$b} } keys %$s;
} # sheets
# col2label (4) => "D"
sub col2label {
ref $_[0] eq __PACKAGE__ and shift;
my $c = shift;
defined $c && $c > 0 or return "";
my $cell = "";
while ($c) {
use integer;
substr $cell, 0, 0, chr (--$c % 26 + ord "A");
$c /= 26;
}
$cell;
} # col2label
sub cr2cell {
ref $_[0] eq __PACKAGE__ and shift;
my ($c, $r) = @_;
defined $c && defined $r && $c > 0 && $r > 0 or return "";
col2label ($c) . $r;
} # cr2cell
# cell2cr ("D18") => (4, 18)
sub cell2cr {
ref $_[0] eq __PACKAGE__ and shift;
my ($cc, $r) = (uc ($_[0]||"") =~ m/^([A-Z]+)([0-9]+)$/) or return (0, 0);
my $c = 0;
while ($cc =~ s/^([A-Z])//) {
$c = 26 * $c + 1 + ord ($1) - ord ("A");
}
($c, $r);
} # cell2cr
# my @row = cellrow ($book->[1], 1);
# my @row = $book->cellrow (1, 1);
sub cellrow {
my $sheet = ref $_[0] eq __PACKAGE__ ? (shift)->[shift] : shift or return;
ref $sheet eq "HASH" && exists $sheet->{cell} or return;
exists $sheet->{maxcol} && exists $sheet->{maxrow} or return;
my $row = shift or return;
$row > 0 && $row <= $sheet->{maxrow} or return;
my $s = $sheet->{cell};
map { $s->[$_][$row] } 1..$sheet->{maxcol};
} # cellrow
# my @row = row ($book->[1], 1);
# my @row = $book->row (1, 1);
sub row {
my $sheet = ref $_[0] eq __PACKAGE__ ? (shift)->[shift] : shift or return;
ref $sheet eq "HASH" && exists $sheet->{cell} or return;
exists $sheet->{maxcol} && exists $sheet->{maxrow} or return;
my $row = shift or return;
$row > 0 && $row <= $sheet->{maxrow} or return;
map { $sheet->{cr2cell ($_, $row)} } 1..$sheet->{maxcol};
} # row
# Convert {cell}'s [column][row] to a [row][column] list
# my @rows = rows ($book->[1]);
sub rows {
my $sheet = ref $_[0] eq __PACKAGE__ ? (shift)->[shift] : shift or return;
ref $sheet eq "HASH" && exists $sheet->{cell} or return;
exists $sheet->{maxcol} && exists $sheet->{maxrow} or return;
my $s = $sheet->{cell};
map {
my $r = $_;
[ map { $s->[$_][$r] } 1..$sheet->{maxcol} ];
} 1..$sheet->{maxrow};
} # rows
sub sheet {
my ($book, $sheet) = @_;
$book && $sheet or return;
my $class = "Spreadsheet::Read::Sheet";
$sheet =~ m/^[0-9]+$/ && $sheet >= 1 && $sheet <= $book->[0]{sheets} and
return bless $book->[$sheet] => $class;
exists $book->[0]{sheet}{$sheet} and
return bless $book->[$book->[0]{sheet}{$sheet}] => $class;
foreach my $idx (1 .. $book->[0]{sheets}) {
$book->[$idx]{label} eq $sheet and
return bless $book->[$idx] => $class;
}
return;
} # sheet
# If option "clip" is set, remove the trailing rows and
# columns in each sheet that contain no visible data
sub _clipsheets {
my ($opt, $ref) = @_;
unless ($ref->[0]{sheets}) {
$ref->{sheet} ||= {};
return $ref;
}
my ($rc, $cl) = ($opt->{rc}, $opt->{cells});
my ($oc, $os, $oa) = ($opt->{clip}, $opt->{strip}, $opt->{attr});
# Strip leading/trailing spaces
if ($os || $oc) {
foreach my $sheet (1 .. $ref->[0]{sheets}) {
$ref->[$sheet]{indx} = $sheet;
my $ss = $ref->[$sheet];
$ss->{maxrow} && $ss->{maxcol} or next;
my ($mc, $mr) = (0, 0);
foreach my $row (1 .. $ss->{maxrow}) {
foreach my $col (1 .. $ss->{maxcol}) {
if ($rc) {
defined $ss->{cell}[$col][$row] or next;
$os & 2 and $ss->{cell}[$col][$row] =~ s/\s+$//;
$os & 1 and $ss->{cell}[$col][$row] =~ s/^\s+//;
if (length $ss->{cell}[$col][$row]) {
$col > $mc and $mc = $col;
$row > $mr and $mr = $row;
}
}
if ($cl) {
my $cell = cr2cell ($col, $row);
defined $ss->{$cell} or next;
$os & 2 and $ss->{$cell} =~ s/\s+$//;
$os & 1 and $ss->{$cell} =~ s/^\s+//;
if (length $ss->{$cell}) {
$col > $mc and $mc = $col;
$row > $mr and $mr = $row;
}
}
}
}
$oc && ($mc < $ss->{maxcol} || $mr < $ss->{maxrow}) or next;
# Remove trailing empty columns
foreach my $col (($mc + 1) .. $ss->{maxcol}) {
$rc and undef $ss->{cell}[$col];
$oa and undef $ss->{attr}[$col];
$cl or next;
my $c = col2label ($col);
delete $ss->{"$c$_"} for 1 .. $ss->{maxrow};
}
# Remove trailing empty rows
ref $book eq __PACKAGE__) && $book->[0]{sheets} or return $r;
my $c1 = $book->[0];
my $c2 = $r->[0];
unless ($c1->{parsers}) {
$c1->{parsers}[0]{$_} = $c1->{$_} for qw( type parser version );
$book->[$_]{parser} = 0 for 1 .. $c1->{sheets};
}
my ($pidx) = (grep { my $p = $c1->{parsers}[$_];
$p->{type} eq $c2->{type} &&
$p->{parser} eq $c2->{parser} &&
$p->{version} eq $c2->{version} } 0 .. $#{$c1->{parsers}});
unless (defined $pidx) {
$pidx = scalar @{$c1->{parsers}};
$c1->{parsers}[$pidx]{$_} = $c2->{$_} for qw( type parser version );
}
foreach my $sn (sort { $c2->{sheet}{$a} <=> $c2->{sheet}{$b} } keys %{$c2->{sheet}}) {
my $s = $sn;
my $v = 2;
while (exists $c1->{sheet}{$s}) {
$s = $sn."[".$v++."]";
}
$c1->{sheet}{$s} = $c1->{sheets} + $c2->{sheet}{$sn};
$r->[$c2->{sheet}{$sn}]{parser} = $pidx;
push @$book, $r->[$c2->{sheet}{$sn}];
}
$c1->{sheets} += $c2->{sheets};
return $book;
} # add
package Spreadsheet::Read::Attribute;
use Carp;
use vars qw( $AUTOLOAD );
sub AUTOLOAD {
my $self = shift;
(my $attr = $AUTOLOAD) =~ s/.*:://;
$self->{$attr};
} # AUTOLOAD
package Spreadsheet::Read::Sheet;
use List::Util qw( min max );
sub cell {
my ($sheet, @id) = @_;
@id == 2 && $id[0] =~ m/^[0-9]+$/ && $id[1] =~ m/^[0-9]+$/ and
return $sheet->{cell}[$id[0]][$id[1]];
@id && $id[0] && exists $sheet->{$id[0]} and
return $sheet->{$id[0]};
} # cell
sub attr {
my ($sheet, @id) = @_;
my $class = "Spreadsheet::Read::Attribute";
@id == 2 && $id[0] =~ m/^[0-9]+$/ && $id[1] =~ m/^[0-9]+$/ and
return bless $sheet->{attr}[$id[0]][$id[1]] => $class;
if (@id && $id[0] && exists $sheet->{$id[0]}) {
my ($c, $r) = $sheet->cell2cr ($id[0]);
return bless $sheet->{attr}[$c][$r] => $class;
}
undef;
} # attr
sub maxrow {
my $sheet = shift;
return $sheet->{maxrow};
} # maxrow
sub maxcol {
my $sheet = shift;
return $sheet->{maxcol};
} # maxrow
sub col2label {
$_[0] =~ m/::/ and shift; # class unused
return Spreadsheet::Read::col2label (@_);
} # col2label
sub cr2cell {
$_[0] =~ m/::/ and shift; # class unused
return Spreadsheet::Read::cr2cell (@_);
} # cr2cell
sub cell2cr {
$_[0] =~ m/::/ and shift; # class unused
return Spreadsheet::Read::cell2cr (@_);
} # cell2cr
sub label {
my ($sheet, $label) = @_;
defined $label and $sheet->{label} = $label;
return $sheet->{label};
} # label
sub active {
my $sheet = shift;
return $sheet->{active};
} # label
sub hidden {
my $sheet = shift;
return $sheet->{hidden};
} # label
# my @row = $sheet->cellrow (1);
sub cellrow {
my ($sheet, $row) = @_;
defined $row && $row > 0 && $row <= $sheet->{maxrow} or return;
my $s = $sheet->{cell};
map { $s->[$_][$row] } 1..$sheet->{maxcol};
} # cellrow
# my @row = $sheet->row (1);
sub row {
my ($sheet, $row) = @_;
defined $row && $row > 0 && $row <= $sheet->{maxrow} or return;
map { $sheet->{$sheet->cr2cell ($_, $row)} } 1..$sheet->{maxcol};
} # row
( run in 0.627 second using v1.01-cache-2.11-cpan-a5abf4f5562 )