File Coverage

blib/lib/Excel/Table.pm
Criterion Covered Total %
statement 245 253 96.8
branch 80 102 78.4
condition 4 6 66.6
subroutine 30 30 100.0
pod 9 10 90.0
total 368 401 91.7


line stmt bran cond sub pod time code
1             package Excel::Table;
2             #
3             # Excel::Table.pm - spreadsheet table processing class.
4             #
5             =head1 NAME
6              
7             Excel::Table
8              
9             =head1 AUTHOR
10              
11             Copyright (C) 2012 Tom McMeekin Etmcmeeki@cpan.orgE
12              
13             =head1 SYNOPSIS
14              
15             use Excel::Table;
16              
17             my $xs = Excel::Table->new('dir' => '/cygdrive/c/Users/self/Desktop');
18              
19             for ($xs->list_workbooks) {
20             print "workbook [$_]\n";
21             }
22              
23             $xs->open('mybook.xls');
24              
25             my $wb1 = $xs->open_re('foo*bar*');
26              
27             for my $worksheet ($wb1->worksheets) {
28             print "worksheet: " . $worksheet->get_name() . "\n";
29             }
30              
31             $xs->null("this is a null value");
32             $xs->force_null(1);
33              
34             $xs->rowid(0);
35              
36             $xs->trim(0);
37              
38             my @data = $xs->extract('Sheet1');
39              
40             for (@data) {
41             printf "rowid [%s] title [%s] max_width [%d] value [%s]\n",
42             $_->[0],
43             $xs->titles->[0],
44             $xs->widths->[0],
45             $data{$_}->[0];
46             }
47              
48             @data = $xs->extract_hash('Sheet1');
49              
50             @data = $xs->select("column1,column2,column3", 'Sheet1');
51              
52             @data = $xs->select_hash("column1,column2,column3", 'Sheet1');
53              
54             printf "columns %d rows %d title_row %d\n",
55             $xs->columns, $xs->rows, $xs->title_row;
56              
57             printf "regexp [%s] pathname [%s] sheet_name [%s]\n",
58             $xs->regexp, $xs->pathname, $xs->sheet_name;
59              
60             printf "colid2title(0) = [%s]\n", $xs->colid2title(0);
61              
62             printf "title2colid('Foo') = %d\n", $xs->title2colid('Foo');
63              
64             =head1 DESCRIPTION
65              
66             Excel::Table.pm - spreadsheet table processing. Retrieves worksheets as
67             if they are structured tables array-format.
68              
69             =over 4
70              
71             =item 1a. OBJ->dir(EXPR)
72              
73             Override the directory location in which to look for workbooks.
74             Defaults to "." (i.e. the current working directory).
75             This location is critical to the B, B,
76             and B methods.
77              
78             =item 1b. OBJ->list_workbooks
79              
80             Returns an array of workbook files in the directory defined by the
81             B property.
82              
83             =item 2a. OBJ->open(EXPR)
84              
85             Parses the filename specified by EXPR. The B property
86             will designate the search path.
87             Once opened, via this method (or B) the
88             workbook is available for use by the B method.
89              
90             =item 2b. OBJ->open_re(EXPR)
91              
92             This will search for a file which has a filename matching the regexp EXPR.
93             A warning will be issued if multiple matches are found, only the first will
94             be opened.
95              
96             =item 3. OBJ->regexp
97              
98             Returns the regexp used to search for the workbook on the filesystem.
99              
100             =item 4. OBJ->pathname
101              
102             Returns the pathname of the opened workbook.
103              
104             =item 5a. OBJ->extract(EXPR,[TITLE_ROW])
105              
106             This will extract all data from the worksheet named EXPR. Data is extracted
107             into an array and returned. Format of data is per below:
108              
109             [ value1, value2, value3, ... ],
110             [ value1, value2, value3, ... ],
111             [ value1, value2, value3, ... ],
112             ...
113              
114             The object OBJ will be populated with various properties to assist you to
115             access the data in the array, including column titles and widths.
116              
117             A worksheet object is temporarily created in order to populate the array.
118             Once a worksheet is extracted, the associated worksheet object is destroyed.
119             This routine can be called again on any worksheet in the workbook.
120              
121             If the TITLE_ROW argument is specified, then the B property will
122             also be updated prior to extraction.
123              
124             =item 5b. OBJ->extract_hash(EXPR,[TITLE_ROW])
125              
126             Per the B method, but returns an array of hashes, with the hash
127             keys corresponding to the titles.
128              
129             =item 5c. OBJ->select(CLAUSE,EXPR,[TITLE_ROW])
130              
131             Similar to the B method, this will extract all rows from the worksheet EXPR, constraining the columns to those specified by the B argument,
132             which is a comma-separated string, e.g. "column1,column2,column3".
133              
134             As with the B method, the B and B properties will
135             be revised.
136              
137             =item 5d. OBJ->select_hash(CLAUSE,EXPR,[TITLE_ROW])
138              
139             Per the B
140              
141             =item 6. OBJ->columns or OBJ->rows
142              
143             Returns the number of columns or rows available in the sheet extracted via the
144             B method.
145              
146             =item 7a. OBJ->force_null
147              
148             Flag which determines if whitespace fields should be
149             replaced by specific text (see OBJ->null).
150              
151             =item 7b. OBJ->null
152              
153             String to replace whitespace fields with. Defaults to "(null)".
154              
155             =item 8. OBJ->rowid
156              
157             Flag which determines whether a pseudo-column "rowid" is included in each
158             tuple. The value will take the form "999999999" Defaults to FALSE.
159              
160             =item 9. OBJ->sheet_name
161              
162             Returns the sheet_name against which data was extracted via B.
163              
164             =item 10. OBJ->trim
165              
166             Flag which determines if trailing whitespace fields should be trimmed.
167              
168             =item 11a. OBJ->title_row
169              
170             Returns the title row of the worksheet (defaults to zero), following extract.
171              
172             =item 11b. OBJ->titles
173              
174             Returns an array of title fields, the title row number having been defined
175             as OBJ->title_row.
176              
177             =item 11c. OBJ->colid2title(colid)
178              
179             Converts the column number (colid) to a string column title (i.e.
180             the offset within the title_row array).
181             If no match, then returns undef.
182              
183             =item 11d. OBJ->title2colid(REGEXP)
184              
185             Returns the column number of the title identified by REGEXP.
186             If no match, then returns undef.
187              
188             =item 12. OBJ->widths
189              
190             Returns an array of maximum lengths of any (non-title) data in each column.
191              
192             =back
193              
194             =cut
195              
196 9     9   698301 use strict;
  9         17  
  9         385  
197 9     9   44 use warnings;
  9         17  
  9         275  
198              
199 9     9   901 use Data::Dumper;
  9         11672  
  9         653  
200 9     9   8372 use Spreadsheet::ParseExcel 0.57;
  9         540692  
  9         313  
201 9     9   4992 use Spreadsheet::XLSX;
  9         565063  
  9         325  
202 9     9   92 use File::Basename;
  9         13  
  9         669  
203              
204 9     9   51 use Carp qw(cluck confess); # only use stack backtrace within class
  9         12  
  9         510  
205 9     9   47 use Log::Log4perl qw/ get_logger /;
  9         11  
  9         91  
206              
207 9     9   542 use vars qw/ @EXPORT $VERSION /;
  9         16  
  9         578  
208              
209             $VERSION = "1.021"; # update this on new release
210              
211             #@ISA = qw(Exporter);
212             #@EXPORT = qw();
213              
214             # package constants
215 9     9   122 use constant S_RID => "rowid";
  9         13  
  9         564  
216 9     9   41 use constant S_NULL => "(null)";
  9         10  
  9         421  
217 9         518 use constant EXT_EXCEL => qw/
218             \.xls \.xla \.xlb \.xlc \.xld \.xlk \.xll \.xlm \.xlt
219             \.xlv \.xlw \.xls \.xlt
220 9     9   39 /; # known extensions for EXCEL file
  9         11  
221              
222             # need the Spreadsheet::XLSX module for the following:
223 9         23338 use constant EXT_EXCEL_2007 => qw/
224             \.xlsx \.xlsm \.xlsb \.xltm \.xlam
225 9     9   43 /; # known extensions for EXCEL 2007 file
  9         11  
226              
227              
228             # package globals
229              
230             our $AUTOLOAD;
231              
232              
233             # package locals
234             my $n_Objects = 0; # counter of objects created.
235              
236             my %attribute = (
237             _n_objects => \$n_Objects,
238             _xl_vers => undef,
239             columns => undef,
240             dir => ".",
241             _log => get_logger("Excel::Table"),
242             null => S_NULL,
243             pathname => undef,
244             regexp => undef,
245             force_null => 0,
246             rows => undef,
247             rowid => 0,
248             sheet_name => undef,
249             title_row => 0, # if title row is zero, first data row is 1
250             titles => undef,
251             trim => 0,
252             widths => undef,
253             workbook => undef,
254             );
255              
256              
257             #INIT { };
258              
259              
260             sub AUTOLOAD {
261 18985     18985   577870 my $self = shift;
262 18985 50       29607 my $type = ref($self) or croak("self is not an object");
263              
264 18985         16937 my $name = $AUTOLOAD;
265 18985         42574 $name =~ s/.*://; # strip fully−qualified portion
266              
267 18985 50       35565 unless (exists $self->{_permitted}->{$name} ) {
268 0         0 confess "no attribute [$name] in class [$type]";
269             }
270              
271 18985 100       23411 if (@_) {
272 328         848 return $self->{$name} = shift;
273             } else {
274 18657         52818 return $self->{$name};
275             }
276             }
277              
278              
279             sub new {
280 24     24 0 33013 my ($class) = shift;
281             #my $self = $class->SUPER::new(@_);
282 24         346 my $self = { _permitted => \%attribute, %attribute };
283              
284 24         81 ++ ${ $self->{_n_objects} };
  24         62  
285              
286 24         57 bless ($self, $class);
287              
288 24         63 my %args = @_; # start processing any parameters passed
289 24         36 my ($method,$value); # start processing any parameters passed
290 24         137 while (($method, $value) = each %args) {
291              
292 31 50       75 confess "SYNTAX new(method => value, ...) value not specified"
293             unless (defined $value);
294              
295 31         145 $self->_log->debug("method [self->$method($value)]");
296              
297 31         269 $self->$method($value);
298             }
299            
300 24         74 return $self;
301             }
302              
303              
304             sub _determine_xl_vers {
305 290     290   402 my ($self,$pn)=@_;
306 290 50       512 $self->_log->logcroak("SYNTAX: _determine_xl_vers(path)")
307             unless defined ($pn);
308             # return version string or undef for given pathname
309 290         264 my $extension;
310             my @extensions;
311 290         253 my $retval = undef;
312              
313 290         881 $self->_log->debug("pn [$pn]");
314              
315 290         2328 @extensions = EXT_EXCEL;
316 290         26154 (undef,undef,$extension) = fileparse($pn,@extensions);
317             #$self->_log->debug(sprintf " extension [%s] \@extensions [%s]", $extension, Dumper(\@extensions));
318              
319 290 100       774 $retval = 'xl2003' if ($extension ne "");
320              
321 290         810 @extensions = EXT_EXCEL_2007;
322 290         12506 (undef,undef,$extension) = fileparse($pn,@extensions);
323              
324 290 100       755 $retval = 'xl2007' if ($extension ne "");
325              
326 290 100       484 if (defined $retval) {
327 56         240 $self->_log->debug("pn [$pn] returning [$retval]");
328             }
329              
330 290         1676 return $retval;
331             }
332              
333              
334             sub list_workbooks {
335 13     13 1 1065 my $self = shift;
336              
337 13         57 my $dn = $self->dir;
338 13         17 my ($dh,$fn);
339 0         0 my @workbooks;
340              
341 13         45 $self->_log->debug("dn [$dn]");
342              
343 13 50       699 opendir($dh, $dn) || $self->_log->logcroak("opendir($dn)");
344              
345 13         209 while ($fn = readdir($dh)) {
346 260         2042 my $pn = File::Spec->catfile($dn, $fn);
347              
348             # need to remember just the filename here, not the path because
349             # open will use the self->dir property to make the path
350 260 100       596 push @workbooks, $fn
351             if (defined($self->_determine_xl_vers($pn)));
352             }
353              
354 13         132 closedir $dh;
355              
356 13         73 $self->_log->debug(sprintf '@workbooks [%s]', Dumper(\@workbooks));
357              
358 13         1267 return @workbooks;
359             }
360              
361              
362             sub open {
363 30     30 1 9525 my ($self,$fn)=@_;
364 30 50       108 $self->_log->logcroak("SYNTAX: open(file)") unless defined ($fn);
365 30         196 my $pn = File::Spec->catfile($self->dir, $fn);
366              
367             # to look for the file in the cwd, is not good behaviour,
368             # so dir must be explicit, thus the default to "."
369              
370 30 50       604 if (-f $pn) {
371 30         158 $self->pathname($pn);
372             } else {
373 0         0 $self->_log->logcroak("no such path [$pn]");
374             }
375              
376 30         141 $self->_log->debug("parsing [$pn]");
377              
378 30         318 $self->{'_xl_vers'} = $self->_determine_xl_vers($pn);
379              
380 30         47 my $parser;
381              
382 30 100       157 if ($self->_xl_vers eq 'xl2007') {
383 13         185 $parser = Spreadsheet::XLSX->new($pn);
384              
385 13 50       409905 $self->_log->logcroak("Spreadsheet::XLSX->new($pn) failed")
386             unless defined $parser;
387              
388 13         127 $self->workbook($parser);
389             } else {
390 17         150 $parser = Spreadsheet::ParseExcel->new();
391 17         28926 $self->workbook($parser->Parse($pn));
392              
393 17 50       835 $self->_log->logcroak("Parse() failed, error: " . $self->workbook->error())
394             unless defined $self->workbook;
395             }
396              
397              
398 30         146 return $self->workbook;
399             }
400              
401              
402             sub open_re {
403 7     7 1 3583 my $self = shift;
404 7 50       20 if (@_) { $self->regexp(shift); } else { $self->_log->logcroak("SYNTAX: open_re(regexp)"); }
  7         39  
  0         0  
405 7         45 my $re = $self->regexp;
406 7         12 my $matches = 0;
407 7         8 my $wb = undef;
408              
409 7         24 $self->_log->debug(sprintf "regexp [%s]", $self->regexp);
410 7         60 for ( $self->list_workbooks ) {
411 14         53 $self->_log->debug(" file [$_]");
412 14 100       160 if ($_ =~ /$re/) {
413 10         34 $self->_log->debug(" FOUND [$_]");
414 10 100       77 $wb = $_ unless ($matches++); # remember first occurence
415             }
416             }
417              
418 7 100       22 unless (defined $wb) {
419 1         3 $self->_log->logcarp("could not find file matching [$re]");
420 1         529 return undef;
421             }
422              
423 6 100       28 $self->_log->logwarn("non-unique match on [$re]")
424             if ($matches > 1);
425              
426 6         1554 return $self->open($wb);
427             }
428              
429              
430             sub _prepend_rowid {
431 110     110   122 my ($self, $ra_columns, $id)=@_;
432              
433 110 100       260 my $rowid = ($id == $self->title_row) ? S_RID : sprintf "%09d", $id;
434              
435 110         156 push @$ra_columns, $rowid;
436              
437 110         120 return $rowid;
438             }
439              
440              
441             sub extract {
442 36     36 1 5114 my $self = shift;
443 36 50       125 if (@_) { $self->sheet_name(shift); }
  36         210  
444 36 50       118 if (@_) { $self->title_row(shift); }
  0         0  
445              
446 36 50 33     138 $self->_log->logcroak("SYNTAX: extract(sheet_name,title_row)")
447             unless (defined $self->sheet_name && defined $self->title_row);
448              
449 36         154 $self->_log->debug(sprintf "opening [%s]", $self->sheet_name);
450              
451 36         400 my $ws = $self->workbook->worksheet($self->sheet_name);
452              
453 36         573 my ($minr, $maxr) = $ws->row_range();
454 36         372 my ($minc, $maxc) = $ws->col_range();
455              
456 36         380 $self->rows($maxr);
457 36         204 $self->columns($maxc + 1);
458              
459 36 100       116 $self->title_row($minr) # fix minimum row
460             if ($self->title_row < $minr);
461              
462 36         133 $self->_log->debug(sprintf "sheet_name [%s] minr [%d] maxr [%d] minc [%d] maxc [%d]",
463             $self->sheet_name, $minr, $maxr, $minc, $maxc);
464              
465 36         209 my ($subr,$subc,$value);
466 0         0 my @data;
467 0         0 my (@columns,@widths);
468              
469 36         119 for ($subr = $self->title_row; $subr <= $maxr; $subr++) {
470              
471 368 100       968 $self->_prepend_rowid(\@columns, $subr)
472             if ($self->rowid);
473              
474 368         769 for ($subc = $minc; $subc <= $maxc; $subc++) {
475              
476 3584         7355 my $cell = $ws->get_cell($subr, $subc);
477              
478 3584 100       27844 if (defined $cell) {
479 3552 100       8820 $value = ($self->trim) ? $self->_trim_whitespace($cell->value) : $cell->value;
480             } else {
481 32         42 $value = undef;
482             }
483              
484 3584 100       13211 $value = $self->_resolve_null($value, $self->null)
485             if ($self->force_null);
486              
487 3584         7803 push @columns, $value;
488             }
489              
490             # adjust widths, including rowid column
491 368         343 $subc = 0;
492 368         474 for $value (@columns) {
493              
494             # calculate width, ignoring title_row
495              
496 3694 100       8891 if ($subr == $self->title_row) {
497 346         371 $widths[$subc] = 0;
498             } else {
499 3348 100 100     11149 $widths[$subc] = length($value)
500             if (defined($value) &&
501             length($value) > $widths[$subc]);
502             }
503              
504 3694         3946 $subc++;
505             }
506              
507 368         966 $self->_log->debug(sprintf '@columns [%s]', Dumper(\@columns));
508 368         26592 $self->_log->debug(sprintf '@widths [%s]', Dumper(\@widths));
509            
510 368 100       22789 if ($subr == $self->title_row) {
511 36         195 $self->titles([ @columns ]);
512             } else {
513 332         936 push @data, [ @columns ];
514             }
515 368         1079 @columns = ();
516             }
517 36         216 $self->widths([ @widths ]);
518              
519 36         85 @widths = $ws = ();
520              
521 36         292 return @data;
522             }
523              
524              
525             sub colid2title {
526 4     4 1 9 my ($self,$colid)=@_;
527              
528 4 50       16 $self->_log->logcroak("SYNTAX: colid2title2(colid)")
529             unless (defined $colid);
530              
531 4         23 $self->_log->debug("colid [$colid]");
532              
533             return undef
534 4 50       38 if ($colid < 0);
535              
536             return undef
537 4 100       5 unless ($colid < scalar @{ $self->titles });
  4         18  
538              
539 2         10 return $self->titles->[$colid];
540             }
541              
542              
543             sub title2colid {
544 44     44 1 69 my ($self,$title)=@_;
545              
546 44 50       93 $self->_log->logcroak("SYNTAX: title2colid(title)")
547             unless (defined $title);
548              
549 44         124 $self->_log->debug("title [$title] ");
550              
551 44         189 my $tmax = scalar @{ $self->titles };
  44         118  
552              
553 44         139 for (my $tsub = 0; $tsub < $tmax; $tsub++) {
554 294 100       831 if ($self->titles->[$tsub] =~ /$title/) {
555 30         95 $self->_log->debug("match at colid $tsub");
556 30         186 return $tsub;
557             }
558             }
559 14         54 $self->_log->debug("NO MATCH");
560              
561 14         90 return undef;
562             }
563              
564              
565             sub _trim_whitespace {
566 1526     1526   5288 my ($self,$s_value)=@_;
567              
568 1526 50       2207 if (defined $s_value) {
569 1526         3360 $self->_log->debug("s_value [$s_value]");
570              
571 1526         7909 $s_value =~ s/^[[:cntrl:][:space:]]+//; # trim leading
572 1526         2194 $s_value =~ s/[[:cntrl:][:space:]]+$//; # trim trailing
573              
574 1526         3538 $self->_log->debug("after s_value [$s_value]");
575             }
576              
577 1526         7464 return $s_value;
578             }
579              
580              
581             sub _resolve_null {
582 440     440   434 my ($self, $s_value, $s_null)=@_;
583              
584 440 100       787 $self->_log->debug(sprintf "s_value [%s] s_null [%s]",
    50          
585             (defined $s_value) ? $s_value : "not defined",
586             (defined $s_null) ? $s_null : "not defined",
587             );
588              
589 440 100       1871 if (defined $s_value) {
590 436 100       656 $s_value = $s_null
591             if ($s_value eq "");
592             } else {
593 4         5 $s_value = $s_null;
594             }
595              
596 440         470 return $s_value;
597             }
598              
599              
600             sub _array_to_hash {
601 4     4   6 my $self = shift;
602 4         6 my @data;
603              
604 4         12 for my $row (@_) {
605 40         136 $self->_log->debug(sprintf '$row [%s]', Dumper($row));
606              
607 40         2353 my %data;
608 40         68 my $unique = 0;
609 40         59 my $m_value = scalar(@$row);
610              
611 40         99 for (my $ss_value = 0; $ss_value < $m_value; $ss_value++) {
612              
613 260         632 my $column = $self->titles->[$ss_value];
614 260         289 my $value = $row->[$ss_value];
615              
616 260 100       367 my $key = (exists $data{$column}) ? $column . $unique++ : $column;
617 260         565 $data{$key} = $value;
618             }
619              
620 40         109 $self->_log->debug(sprintf 'data [%s]', Dumper(\%data));
621              
622 40         2687 push @data, { %data };
623              
624 40         127 %data = ();
625             }
626              
627 4         26 return @data;
628             }
629              
630              
631             sub extract_hash {
632 2     2 1 5 my $self = shift;
633              
634 2 50       8 $self->_log->logcroak("SYNTAX: extract_hash(sheet_name,[title_row])")
635             unless (@_ > 0);
636              
637 2         7 return $self->_array_to_hash($self->extract(@_));
638             }
639              
640              
641             sub select_hash {
642 2     2 1 9344 my $self = shift;
643 2         5 my $clause = shift;
644              
645 2 50       11 $self->_log->logcroak("SYNTAX: select_hash(clause,[sheet_name,title_row])")
646             unless (@_ > 0);
647              
648 2         10 return $self->_array_to_hash($self->select($clause, @_));
649             }
650              
651              
652             sub select {
653 14     14 1 9667 my $self = shift;
654 14         28 my $clause = shift;
655              
656 14 50       50 $self->_log->logcroak("SYNTAX: select(clause,[sheet_name,title_row])")
657             unless (defined $clause);
658              
659 14         61 my @pre = $self->extract(@_);
660 14         29 my (@post, @id);
661 0         0 my (@columns, @widths);
662              
663 14 100       46 $clause = join(',', S_RID, $clause)
664             if ($self->rowid);
665              
666 14         71 for my $column (split(/,/, $clause)) {
667 40         3079 $self->_log->debug("column [$column]");
668              
669 40         241 my $id = $self->title2colid($column);
670              
671 40 100       72 if (defined $id) {
672 28         43 push @id, $id;
673 28         33 push @columns, $column;
674 28         69 push @widths, $self->widths->[$id];
675             } else {
676 12         33 $self->_log->logwarn("invalid column [$column]");
677             }
678             }
679              
680 14         905 $self->_log->debug(sprintf '@id [%s]', Dumper(\@id));
681              
682 14 100       857 my $f_no_columns = ($self->rowid) ? 1 : 0;
683              
684 14 100       47 unless (scalar(@columns) == $f_no_columns) { # no columns, thus no rows
685 10         24 for my $row (@pre) {
686 100         422 $self->_log->debug(sprintf 'row [%s]', Dumper($row));
687              
688 100         6455 my @wanted = ();
689              
690 100         158 for my $id (@id) {
691 260         365 push @wanted, $row->[$id];
692             }
693              
694 100         379 $self->_log->debug(sprintf '@wanted [%s]', Dumper(\@wanted));
695              
696 100 50       5001 push @post, [ @wanted ]
697             if (scalar(@wanted)); # account for null case
698             }
699             }
700              
701 14         59 $self->_log->debug(sprintf '@columns [%s]', Dumper(\@columns));
702 14         664 $self->_log->debug(sprintf '@widths [%s]', Dumper(\@widths));
703              
704 14         703 $self->titles([ @columns ]);
705 14         65 $self->widths([ @widths ]);
706              
707 14         217 return @post;
708             }
709              
710              
711             DESTROY {
712 24     24   68830 my $self = shift;
713 24         31 -- ${ $self->{_n_objects} };
  24         3078  
714             };
715              
716             #END { }
717              
718             1;
719              
720             __END__