File Coverage

blib/lib/Excel/ValueReader/XLSX/Backend.pm
Criterion Covered Total %
statement 69 69 100.0
branch 7 12 58.3
condition 1 3 33.3
subroutine 13 13 100.0
pod 0 3 0.0
total 90 100 90.0


line stmt bran cond sub pod time code
1             package Excel::ValueReader::XLSX::Backend;
2 1     1   859 use utf8;
  1         2  
  1         5  
3 1     1   46 use 5.10.1;
  1         3  
4 1     1   6 use Moose;
  1         2  
  1         7  
5 1     1   6656 use Archive::Zip qw(AZ_OK);
  1         65073  
  1         85  
6 1     1   7 use Carp qw/croak/;
  1         2  
  1         858  
7              
8             our $VERSION = '1.09';
9              
10             #======================================================================
11             # ATTRIBUTES
12             #======================================================================
13             has 'frontend' => (is => 'ro', isa => 'Excel::ValueReader::XLSX',
14             required => 1, weak_ref => 1,
15             handles => [qw/A1_to_num formatted_date/]);
16              
17              
18              
19             my %lazy_attrs = ( zip => 'Archive::Zip',
20             date_styles => 'ArrayRef',
21             strings => 'ArrayRef',
22             workbook_data => 'HashRef',
23             table_info => 'HashRef',
24             sheet_for_table => 'ArrayRef', );
25              
26             while (my ($name, $type) = each %lazy_attrs) {
27             has $name => (is => 'ro', isa => $type, builder => "_$name", init_arg => undef, lazy => 1);
28             }
29              
30              
31              
32              
33             #======================================================================
34             # ATTRIBUTE CONSTRUCTORS
35             #======================================================================
36              
37             sub _zip {
38 12     12   23 my $self = shift;
39              
40 12         314 my $xlsx_file = $self->frontend->xlsx;
41 12         258 my $zip = Archive::Zip->new;
42 12         518 my $result = $zip->read($xlsx_file);
43 12 50       57471 $result == AZ_OK or die "cannot unzip $xlsx_file";
44              
45 12         616 return $zip;
46             }
47              
48              
49             sub _table_info {
50 2     2   6 my ($self) = @_;
51              
52 2         3 my %table_info;
53 2         59 my @table_members = $self->zip->membersMatching(qr[^xl/tables/table\d+\.xml$]);
54 2         500 foreach my $table_member (map {$_->fileName} @table_members) {
  10         50  
55 10         56 my ($table_id) = $table_member =~ /table(\d+)\.xml/;
56 10         28 my $table_xml = $self->_zip_member_contents($table_member);
57 10         35 my ($name, $ref, $table_columns, $no_headers)
58             = $self->_parse_table_xml($table_xml); # defined in subclass
59 10 50       542 my $sheet_id = $self->sheet_for_table->[$table_id]
60             or croak "could not find sheet id for table $table_id";
61 10         46 $table_info{$name} = [$sheet_id, $table_id, $ref, $table_columns, $no_headers];
62             }
63              
64 2         58 return \%table_info;
65             }
66              
67              
68             sub _sheet_for_table {
69 2     2   5 my ($self) = @_;
70              
71 2         3 my @sheet_for_table;
72 2         52 my @rel_members = $self->zip->membersMatching(qr[^xl/worksheets/_rels/sheet\d+\.xml\.rels$]);
73 2         467 foreach my $rel_member (map {$_->fileName} @rel_members) {
  10         52  
74 10         139 my ($sheet_id) = $rel_member =~ /sheet(\d+)\.xml/;
75 10         31 my $rel_xml = $self->_zip_member_contents($rel_member);
76 10         35 my @table_ids = $self->_table_targets($rel_xml); # defined in subclass
77 10         111 $sheet_for_table[$_] = $sheet_id foreach @table_ids;
78             }
79              
80 2         71 return \@sheet_for_table;
81             }
82              
83              
84             # attribute constructors for _date_styles, _strings and _workbook_data are supplied in subclasses
85              
86             #======================================================================
87             # METHODS
88             #======================================================================
89              
90              
91             sub base_year {
92 156     156 0 1290 my ($self) = @_;
93 156         5009 return $self->workbook_data->{base_year};
94             }
95              
96             sub sheets {
97 32     32 0 141 my ($self) = @_;
98 32         1010 return $self->workbook_data->{sheets};
99             }
100              
101              
102              
103             sub Excel_builtin_date_formats {
104 10     10 0 64 my @numFmt;
105              
106             # source : section 18.8.30 numFmt (Number Format) in ECMA-376-1:2016
107             # Office Open XML File Formats — Fundamentals and Markup Language Reference
108 10         25 $numFmt[14] = 'mm-dd-yy';
109 10         21 $numFmt[15] = 'd-mmm-yy';
110 10         18 $numFmt[16] = 'd-mmm';
111 10         21 $numFmt[17] = 'mmm-yy';
112 10         20 $numFmt[18] = 'h:mm AM/PM';
113 10         17 $numFmt[19] = 'h:mm:ss AM/PM';
114 10         17 $numFmt[20] = 'h:mm';
115 10         19 $numFmt[21] = 'h:mm:ss';
116 10         21 $numFmt[22] = 'm/d/yy h:mm';
117 10         20 $numFmt[45] = 'mm:ss';
118 10         13 $numFmt[46] = '[h]:mm:ss';
119 10         17 $numFmt[47] = 'mmss.0';
120              
121 10         52 return @numFmt;
122             }
123              
124             sub _zip_member_contents {
125 78     78   197 my ($self, $member) = @_;
126              
127 78 50       2303 my $contents = $self->zip->contents($member)
128             or die "no contents for member $member";
129 78         92971 utf8::decode($contents);
130              
131 78         307 return $contents;
132             }
133              
134             sub _zip_member_name_for_sheet {
135 30     30   89 my ($self, $sheet) = @_;
136              
137             # check that sheet name was given
138 30 50       141 $sheet or die "->values(): missing sheet name";
139              
140             # get sheet id
141 30         145 my $id = $self->sheets->{$sheet};
142 30 100 33     157 $id //= $sheet if $sheet =~ /^\d+$/;
143 30 50       88 $id or die "no such sheet: $sheet";
144              
145             # construct member name for that sheet
146 30         116 return "xl/worksheets/sheet$id.xml";
147             }
148              
149              
150             1;
151              
152             __END__
153              
154             =head1 NAME
155              
156             Excel::ValueReader::XLSX::Backend -- abstract class, parent for the Regex and LibXML backends
157              
158             =head1 DESCRIPTION
159              
160             L<Excel::ValueReader::XLSX> has two possible implementation backends for parsing
161             C<XLSX> files :
162             L<Excel::ValueReader::XLSX::Backend::Regex>, based on regular expressions, or
163             L<Excel::ValueReader::XLSX::Backend::LibXML>, based on the libxml2 library.
164             Both backends share some common features, so the present class implements those
165             common features. This is about internal implementation; it should be of no interest
166             to external users of the module.
167              
168             =head1 ATTRIBUTES
169              
170             A backend instance possesses the following attributes :
171              
172             =over
173              
174             =item frontend
175              
176             a weak reference to the frontend instance
177              
178             =item zip
179              
180             an L<Archive::Zip> instance for accessing the contents of the C<xlsx> file
181              
182             =item date_styles
183              
184             an array of numeric styles for presenting dates and times. Styles are either
185             Excel's builtin styles, or custom styles defined in the workbook.
186              
187             =item strings
188              
189             an array of all shared strings within the workbook
190              
191             =item workbook_data
192              
193             some metadata information about the workbook
194              
195             =back
196              
197              
198              
199             =head1 ABSTRACT METHODS
200              
201             Not defined in this abstract class, but implemented in subclasses.
202              
203             =over
204              
205             =item values
206              
207             Inspects all cells within the XSLX files and returns a bi-dimensional array of values.
208              
209              
210             =back
211              
212              
213              
214             =head1 AUTHOR
215              
216             Laurent Dami, E<lt>dami at cpan.orgE<gt>
217              
218             =head1 COPYRIGHT AND LICENSE
219              
220             Copyright 2021 by Laurent Dami.
221              
222             This library is free software; you can redistribute it and/or modify
223             it under the same terms as Perl itself.