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