File Coverage

blib/lib/Excel/ValueReader/XLSX/Backend.pm
Criterion Covered Total %
statement 78 79 98.7
branch 9 14 64.2
condition 1 3 33.3
subroutine 17 18 94.4
pod 0 4 0.0
total 105 118 88.9


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