File Coverage

blib/lib/Spreadsheet/XLSX.pm
Criterion Covered Total %
statement 148 154 96.1
branch 65 80 81.2
condition 20 27 74.0
subroutine 14 14 100.0
pod 0 1 0.0
total 247 276 89.4


line stmt bran cond sub pod time code
1             package Spreadsheet::XLSX;
2              
3 6     6   581292 use 5.006000;
  6         80  
4 6     6   36 use strict;
  6         12  
  6         149  
5 6     6   30 use warnings;
  6         64  
  6         275  
6              
7 6     6   40 use base 'Spreadsheet::ParseExcel::Workbook';
  6         12  
  6         3347  
8              
9             our $VERSION = '0.16';
10              
11 6     6   8304 use Archive::Zip;
  6         461387  
  6         295  
12 6     6   5260 use Spreadsheet::ParseExcel;
  6         341168  
  6         254  
13 6     6   3589 use Spreadsheet::XLSX::Fmt2007;
  6         23  
  6         12017  
14              
15             ################################################################################
16              
17             sub new {
18 5     5 0 977 my ($class, $filename, $converter) = @_;
19              
20 5         15 my %shared_info; # shared_strings, styles, style_info, rels, converter
21 5         14 $shared_info{converter} = $converter;
22            
23 5         31 my $self = bless Spreadsheet::ParseExcel::Workbook->new(), $class;
24              
25 5         71 my $zip = __load_zip($filename);
26              
27 5         23 $shared_info{shared_strings}= __load_shared_strings($zip, $shared_info{converter});
28 5         20 my ($styles, $style_info) = __load_styles($zip);
29 5         18 $shared_info{styles} = $styles;
30 5         14 $shared_info{style_info} = $style_info;
31 5         20 $shared_info{rels} = __load_rels($zip);
32              
33 5         29 $self->_load_workbook($zip, \%shared_info);
34              
35 5         4170 return $self;
36             }
37              
38             sub _load_workbook {
39 5     5   17 my ($self, $zip, $shared_info) = @_;
40              
41 5 50       22 my $member_workbook = $zip->memberNamed('xl/workbook.xml') or die("xl/workbook.xml not found in this zip\n");
42 5         282 $self->{SheetCount} = 0;
43 5         40 $self->{FmtClass} = Spreadsheet::XLSX::Fmt2007->new;
44 5         15 $self->{Flg1904} = 0;
45 5 50       17 if ($member_workbook->contents =~ /date1904="1"/) {
46 0         0 $self->{Flg1904} = 1;
47             }
48              
49 5         5848 foreach ($member_workbook->contents =~ /\<(.*?)\/?\>/g) {
50              
51 74         5567 /^(\w+)\s+/;
52              
53 74         193 my ($tag, $other) = ($1, $');
54              
55 74         169 my @pairs = split /\" /, $other;
56              
57 74 100       176 $tag eq 'sheet' or next;
58              
59 14         50 my $sheet = {
60             MaxRow => 0,
61             MaxCol => 0,
62             MinRow => 1000000,
63             MinCol => 1000000,
64             };
65              
66 14         95 foreach ($other =~ /(\S+=".*?")/gsm) {
67              
68 45         178 my ($k, $v) = split /=?"/; #"
69              
70 45 100       134 if ($k eq 'name') {
    100          
71 14         29 $sheet->{Name} = $v;
72 14 50       49 $sheet->{Name} = $shared_info->{converter}->convert($sheet->{Name}) if defined $shared_info->{converter};
73             } elsif ($k eq 'r:id') {
74              
75 14         44 $sheet->{path} = $shared_info->{rels}->{$v};
76              
77             }
78              
79             }
80              
81 14         105 my $wsheet = Spreadsheet::ParseExcel::Worksheet->new(%$sheet);
82 14         258 $self->{Worksheet}[$self->{SheetCount}] = $wsheet;
83 14         50 $self->{SheetCount} += 1;
84              
85             }
86              
87              
88 5         19 foreach my $sheet (@{$self->{Worksheet}}) {
  5         16  
89              
90 14 50       123 my $member_sheet = $zip->memberNamed("xl/$sheet->{path}") or next;
91              
92 14         986 my ($row, $col);
93              
94 14         29 my $parsing_v_tag = 0;
95 14         24 my $s = 0;
96 14         22 my $s2 = 0;
97 14         23 my $sty = 0;
98 14         56 foreach ($member_sheet->contents =~ /(\<.*?\/?\>|.*?(?=\<))/g) {
99 9954 100 66     66746 if (/^\
    100          
    100          
    100          
100              
101 1624         3775 ($row, $col) = __decode_cell_name($1, $2, $3);
102              
103 1624 100       4137 $s = m/t=\"s\"/ ? 1 : 0;
104 1624 100       3223 $s2 = m/t=\"str\"/ ? 1 : 0;
105 1624 100       6024 $sty = m/s="([0-9]+)"/ ? $1 : 0;
106              
107             } elsif (/^/) {
108 1622         2590 $parsing_v_tag = 1;
109             } elsif (/^<\/v>/) {
110 1622         2733 $parsing_v_tag = 0;
111             } elsif (length($_) && $parsing_v_tag) {
112 1622 100       4053 my $v = $s ? $shared_info->{shared_strings}->[$_] : $_;
113              
114 1622 50       3171 if ($v eq "") {
115 0         0 $v = "";
116             }
117 1622         2274 my $type = "Text";
118 1622         2261 my $thisstyle = "";
119              
120 1622 50 66     3708 if (not($s) && not($s2)) {
121 786         1086 $type = "Numeric";
122              
123 786 100 66     2814 if (defined $sty && defined $shared_info->{styles}->[$sty]) {
124 783         1779 $thisstyle = $shared_info->{style_info}->{$shared_info->{styles}->[$sty]};
125 783 100       2165 if ($thisstyle =~ /\b(mmm|m|d|yy|h|hh|mm|ss)\b/) {
126 2         3 $type = "Date";
127             }
128             }
129             }
130              
131              
132 1622 100       3453 $sheet->{MaxRow} = $row if $sheet->{MaxRow} < $row;
133 1622 100       3047 $sheet->{MaxCol} = $col if $sheet->{MaxCol} < $col;
134 1622 100       2981 $sheet->{MinRow} = $row if $sheet->{MinRow} > $row;
135 1622 100       2843 $sheet->{MinCol} = $col if $sheet->{MinCol} > $col;
136              
137 1622 100 66     4905 if ($v =~ /(.*)E\-(.*)/gsm && $type eq "Numeric") {
138 97         591 $v = $1 / (10**$2); # this handles scientific notation for very small numbers
139             }
140              
141 1622         5433 my $cell = Spreadsheet::ParseExcel::Cell->new(
142             Val => $v,
143             Format => $thisstyle,
144             Type => $type
145             );
146              
147 1622         18027 $cell->{_Value} = $self->{FmtClass}->ValFmt($cell, $self);
148 1622 100       3836 if ($type eq "Date") {
149 2 100       7 if ($v < 1) { #then this is Excel time field
150 1         2 $cell->{Type} = "Text";
151             }
152 2         5 $cell->{Val} = $cell->{_Value};
153             }
154 1622         4669 $sheet->{Cells}[$row][$col] = $cell;
155             }
156             }
157              
158 14 100       628 $sheet->{MinRow} = 0 if $sheet->{MinRow} > $sheet->{MaxRow};
159 14 100       57 $sheet->{MinCol} = 0 if $sheet->{MinCol} > $sheet->{MaxCol};
160              
161             }
162              
163 5         19 return $self;
164             }
165              
166             # Convert cell name in the format AA1 to a row and column number.
167              
168             sub __decode_cell_name {
169 1624     1624   6702 my ($letter1, $letter2, $digits) = @_;
170              
171 1624         3215 my $col = ord($letter1) - 65;
172              
173 1624 50       3034 if ($letter2) {
174 0         0 $col++;
175 0         0 $col *= 26;
176 0         0 $col += (ord($letter2) - 65);
177             }
178              
179 1624         3202 my $row = $digits - 1;
180              
181 1624         3147 return ($row, $col);
182             }
183              
184              
185             sub __load_shared_strings {
186 5     5   16 my ($zip, $converter) = @_;
187              
188 5         29 my $member_shared_strings = $zip->memberNamed('xl/sharedStrings.xml');
189              
190 5         396 my @shared_strings = ();
191              
192 5 100       80 if ($member_shared_strings) {
193              
194 4         54 my $mstr = $member_shared_strings->contents;
195 4         6389 $mstr =~ s//<\/t>/gsm; # this handles an empty t tag in the xml
196 4         934 foreach my $si ($mstr =~ /(.*?)<\/si/gsm) {
197 830         1002 my $str;
198 830         2779 foreach my $t ($si =~ /(.*?)<\/t/gsm) {
199 830 50       1592 $t = $converter->convert($t) if defined $converter;
200 830         1561 $str .= $t;
201             }
202 830         1636 push @shared_strings, $str;
203             }
204             }
205              
206 5         74 return \@shared_strings;
207             }
208              
209              
210             sub __load_styles {
211 5     5   16 my ($zip) = @_;
212              
213 5         27 my $member_styles = $zip->memberNamed('xl/styles.xml');
214              
215 5         435 my @styles = ();
216 5         13 my %style_info = ();
217              
218 5 50       19 if ($member_styles) {
219 5         51 my $formatter = Spreadsheet::XLSX::Fmt2007->new();
220              
221 5         25 foreach my $t ($member_styles->contents =~ /xf\ numFmtId="([^"]*)"(?!.*\/cellStyleXfs)/gsm) { #"
222 57         12175 push @styles, $t;
223             }
224              
225 5   100     1235 my $default = $1 || '';
226            
227 5         22 foreach my $t1 (@styles) {
228 57         162 $member_styles->contents =~ /numFmtId="$t1" formatCode="([^"]*)/;
229 57   100     63629 my $formatCode = $1 || '';
230 57 100 100     222 if ($formatCode eq $default || not($formatCode)) {
231 41 100 66     238 if ($t1 == 9 || $t1 == 10) {
    100          
232 1         4 $formatCode = '0.00000%';
233             } elsif ($t1 == 14) {
234 1         3 $formatCode = 'yyyy-mm-dd';
235             } else {
236 39         82 $formatCode = '';
237             }
238             # $formatCode = $formatter->FmtStringDef($t1);
239             }
240 57         155 $style_info{$t1} = $formatCode;
241 57   100     275 $default = $1 || '';
242             }
243              
244             }
245 5         26 return (\@styles, \%style_info);
246             }
247              
248              
249             sub __load_rels {
250 5     5   12 my ($zip) = @_;
251              
252 5 50       33 my $member_rels = $zip->memberNamed('xl/_rels/workbook.xml.rels') or die("xl/_rels/workbook.xml.rels not found in this zip\n");
253              
254 5         212 my %rels = ();
255              
256 5         21 foreach ($member_rels->contents =~ /\/g) {
257              
258 28         5655 my ($id, $target);
259 28         98 ($id) = /Id="(.*?)"/;
260 28         89 ($target) = /Target="(.*?)"/;
261            
262 28 50 33     109 if (defined $id and defined $target) {
263 28         96 $rels{$id} = $target;
264             }
265              
266             }
267              
268 5         24 return \%rels;
269             }
270              
271             sub __load_zip {
272 5     5   11 my ($filename) = @_;
273              
274 5         62 my $zip = Archive::Zip->new();
275              
276 5 50       279 if (ref $filename) {
277 0 0       0 $zip->readFromFileHandle($filename) == Archive::Zip::AZ_OK or die("Cannot open data as Zip archive");
278             } else {
279 5 50       32 $zip->read($filename) == Archive::Zip::AZ_OK or die("Cannot open $filename as Zip archive");
280             }
281            
282 5         18654 return $zip;
283             }
284              
285              
286             1;
287             __END__