File Coverage

lib/Spreadsheet/Reader/ExcelXML/XMLReader/FileWorksheet.pm
Criterion Covered Total %
statement 204 210 97.1
branch 112 124 90.3
condition 55 78 70.5
subroutine 13 13 100.0
pod 3 3 100.0
total 387 428 90.4


line stmt bran cond sub pod time code
1             package Spreadsheet::Reader::ExcelXML::XMLReader::FileWorksheet;
2             our $AUTHORITY = 'cpan:JANDREW';
3 18     18   44006 use version; our $VERSION = version->declare('v0.16.8');
  18         29  
  18         101  
4             ###LogSD warn "You uncovered internal logging statements for Spreadsheet::Reader::ExcelXML::XMLReader::FileWorksheet-$VERSION";
5              
6 18     18   1836 use 5.010;
  18         49  
7 18     18   71 use Moose::Role;
  18         27  
  18         117  
8             requires qw(
9             current_named_node current_node_parsed starts_at_the_edge
10             _parse_column_row advance_element_position good_load
11             start_the_file_over squash_node parse_element
12             spreading_merged_values should_skip_hidden has_shared_strings_interface
13             get_shared_string are_spaces_empty get_empty_return_type
14             get_values_only collecting_merge_data collecting_column_formats
15             );
16 18     18   63781 use Clone 'clone';
  18         28  
  18         948  
17 18     18   74 use Carp qw( confess );
  18         26  
  18         740  
18 18         127 use Types::Standard qw(
19             Bool Int is_HashRef ArrayRef
20             Maybe HashRef
21 18     18   69 );
  18         30  
22 18     18   13520 use MooseX::ShortCut::BuildInstance qw ( build_instance should_re_use_classes );
  18         25  
  18         165  
23             should_re_use_classes( 1 );
24 18     18   7363 use lib '../../../../lib';
  18         31  
  18         135  
25 3     1   2892 ###LogSD use Log::Shiras::Telephone;
  3         6  
  3         2805  
26             #########1 Dispatch Tables & Package Variables 5#########6#########7#########8#########9
27              
28              
29              
30             #########1 Public Attributes 3#########4#########5#########6#########7#########8#########9
31              
32             has is_hidden =>(
33             isa => Bool,
34             reader => 'is_sheet_hidden',
35             );
36              
37             #########1 Public Methods 3#########4#########5#########6#########7#########8#########9
38              
39             sub advance_row_position{
40 207     207 1 301 my( $self, $increment ) = @_;
41 207   100     799 $increment //= 1;
42             ###LogSD my $phone = Log::Shiras::Telephone->new( name_space =>
43             ###LogSD $self->get_all_space . '::FileWorksheet::advance_row_position', );
44             ###LogSD $phone->talk( level => 'debug', message => [
45             ###LogSD "Moving row forward -$increment- times", ] );
46 207         787 my( $result, $node_name, $node_level ) = $self->advance_element_position( 'row', $increment );
47             ###LogSD $phone->talk( level => 'debug ', message => [
48             ###LogSD "advance result is:" . ($result//'fail') ] );
49 207 100       544 return undef if !$result;
50              
51             # Pull data about the row
52 193         632 my $row_ref = $self->current_node_parsed;
53 193         333 $row_ref = $row_ref->{row};
54 193         350 delete $row_ref->{raw_text};
55 193         5914 $self->_set_custom_row_data( $row_ref->{r} => $row_ref );# Should this be tied into cache_positions?
56             ###LogSD $phone->talk( level => 'debug ', message => [
57             ###LogSD "parse result is:", $row_ref, $self->_get_all_positions, ] );
58              
59 193         430 return $row_ref;
60             }
61              
62             sub build_row_data{
63 193     193 1 215 my( $self, ) = @_;# $row_ref, $row_position
64             ###LogSD my $phone = Log::Shiras::Telephone->new( name_space =>
65             ###LogSD $self->get_all_space . '::FileWorksheet::build_row_data', );
66             ###LogSD $phone->talk( level => 'debug', message => [
67             ###LogSD "Building out the current row node", ] );
68 193         585 my $full_row_ref = $self->squash_node( $self->parse_element );
69             ###LogSD $phone->talk( level => 'trace', message =>[
70             ###LogSD "Full row parsed to:", $full_row_ref, ] );
71              
72             # Confirm row list
73 193 100       621 if( exists $full_row_ref->{c} ){
74 47         124 my $alt_row->{list} = [ $full_row_ref->{c} ];
75 47         75 delete $full_row_ref->{c};
76 47         77 $alt_row->{attributes} = $full_row_ref;
77 47         66 $full_row_ref = $alt_row;
78             }
79             ###LogSD $phone->talk( level => 'trace', message =>[
80             ###LogSD "New full row adjusted to:", $full_row_ref, ] );
81 193         217 my $new_ref;
82             @$new_ref{qw( row_number row_formats )} = exists $full_row_ref->{attributes} ?
83             ( $full_row_ref->{attributes}->{r}, $full_row_ref->{attributes} ) :
84 193 50       882 ( $full_row_ref->{r}, $full_row_ref ) ;
85             ###LogSD $phone->talk( level => 'trace', message =>[
86             ###LogSD "updated Full row:", $full_row_ref,
87             ###LogSD "New row ref initialized as:", $new_ref ] );
88              
89             # set spans value
90 193 100       443 if( exists $full_row_ref->{attributes}->{spans} ){
91             $new_ref->{row_span} =
92 187         1202 [ $full_row_ref->{attributes}->{spans} =~ /(\d+):(\d+)/ ];
93             }else{
94 6   50     178 $new_ref->{row_span} = [($self->_min_col//1),($self->_max_col//1)];
      100        
95             }
96             ###LogSD $phone->talk( level => 'trace', message =>[
97             ###LogSD "Updated new ref:", $new_ref ] );
98              
99             # Parse the cells for position and range
100 193         312 my $column_to_cell_translations = [];
101 193         214 my $last_value_column;
102             my $alt_ref;
103 193         243 for my $cell ( @{$full_row_ref->{list}} ){
  193         374  
104             ###LogSD $phone->talk( level => 'debug', message =>[
105             ###LogSD "Processing cell:", $cell] );
106 499         1480 @$cell{qw( cell_col cell_row )} = $self->_parse_column_row( $cell->{r} );
107             ###LogSD $phone->talk( level => 'debug', message =>[
108             ###LogSD "Cell column: $cell->{cell_col}", "Cell row: $cell->{cell_row}" ] );
109              
110             # load cell_type, cell_xml_value, and rich text
111             # can't delay this because the information is required for 'empty_is_end'
112 499         681 $cell->{cell_type} = 'Text';
113 499         524 my $v_node = $cell->{v};
114             ###LogSD $phone->talk( level => 'debug', message =>[
115             ###LogSD "v node is:", $v_node ] );
116 499 100       1085 if( exists $cell->{t} ){
    100          
117 189 100       421 if( $cell->{t} eq 's' ){
    100          
    100          
    50          
    0          
    0          
118             ###LogSD $phone->talk( level => 'debug', message =>[
119             ###LogSD "Identified potentially required shared string for cell:", $cell] );
120 174 100       807 my $position = ( $self->has_shared_strings_interface ) ?
121             $self->get_shared_string( $v_node ) : $v_node;
122             ###LogSD $phone->talk( level => 'debug', message =>[
123             ###LogSD "Shared strings resolved to:", $position] );
124 174 100       481 if( is_HashRef( $position ) ){
125 79         500 @$cell{qw( cell_xml_value rich_text )} = ( $position->{raw_text}, $position->{rich_text} );
126 79 100       255 delete $cell->{rich_text} if !$cell->{rich_text};
127             }else{
128 95         415 $cell->{cell_xml_value} = $position;
129             }
130             }elsif( $cell->{t} =~ /^(e|inlineStr)$/ ){
131             ###LogSD $phone->talk( level => 'debug', message =>[
132             ###LogSD "Identified a stored string in the worksheet file: ", $v_node ] );
133 3         7 $cell->{cell_xml_value} = $v_node;
134             }elsif( $cell->{t} eq 'str' ){
135             ###LogSD $phone->talk( level => 'debug', message =>[
136             ###LogSD "Identified a potential formula stored in the core data position: ", $v_node ] );
137 11         35 $cell->{cell_xml_value} = $v_node;
138 11 50 33     59 if( !exists $cell->{f} and $v_node =~ /\=/ ){
139 0         0 $cell->{f} = $v_node;
140             }
141             }elsif( $cell->{t} eq 'b' ){
142             ###LogSD $phone->talk( level => 'debug', message =>[
143             ###LogSD "Identified a stored boolean in the worksheet file: ", $v_node ] );
144 1 50       3 $cell->{cell_xml_value} = $v_node ? 1 : 0 ;
145 1         2 $cell->{cell_type} = 'Numeric';
146             }elsif( $cell->{t} eq 'd' ){
147             ###LogSD $phone->talk( level => 'debug', message =>[
148             ###LogSD "Identified a stored date in the worksheet file: ", $v_node ] );
149 0         0 $cell->{cell_xml_value} = $v_node;
150 0         0 $cell->{cell_type} = 'Date';
151             }elsif( $cell->{t} eq 'n' ){
152             ###LogSD $phone->talk( level => 'debug', message =>[
153             ###LogSD "Identified a stored number in the worksheet file: ", $v_node ] );
154 0         0 $cell->{cell_xml_value} = $v_node;
155 0         0 $cell->{cell_type} = 'Numeric';
156             }else{
157 0         0 confess "Unknown 't' attribute set for the cell: $cell->{t}";
158             }
159 189         268 delete $cell->{t};
160 189 100 66     819 if( $self->are_spaces_empty and $cell->{cell_xml_value} and $cell->{cell_xml_value} =~ /^\s+$/ ){
      100        
161             ###LogSD $phone->talk( level => 'debug', message =>[
162             ###LogSD "Clearing spaces only xml value: " . ($v_node//'')] );
163 8         102 delete $cell->{cell_xml_value};
164             }
165             }elsif( defined $v_node ){
166             ###LogSD $phone->talk( level => 'debug', message =>[
167             ###LogSD "Setting cell_xml_value from: $v_node", ] );
168 272         398 $cell->{cell_xml_value} = $v_node;
169 272 100 66     1087 $cell->{cell_type} = 'Numeric' if $cell->{cell_xml_value} and $cell->{cell_xml_value} ne '';
170             }
171 499         1109 delete $cell->{v};
172 499 100 66     1539 if( $self->get_empty_return_type eq 'empty_string' ){
    100 66        
173 220 100 66     760 $cell->{cell_xml_value} = '' if !exists $cell->{cell_xml_value} or !defined $cell->{cell_xml_value};
174             }elsif( !defined $cell->{cell_xml_value} or
175             ($cell->{cell_xml_value} and length( $cell->{cell_xml_value} ) == 0) ){
176 41         226 delete $cell->{cell_xml_value};
177             }
178             ###LogSD $phone->talk( level => 'debug', message =>[
179             ###LogSD "Updated cell:", $cell] );
180              
181             # Handle formula, position translations, and last value
182 499 100       1948 $cell->{cell_formula} = $cell->{f} if exists $cell->{f};
183 499         464 delete $cell->{f};
184 499         448 $last_value_column = $cell->{cell_col};
185 499         652 push @$alt_ref, $cell;
186 499         870 $column_to_cell_translations->[$cell->{cell_col}] = $#$alt_ref;
187             ###LogSD $phone->talk( level => 'debug', message => [
188             ###LogSD 'Saving cell:', $cell, $alt_ref,
189             ###LogSD "..with column to cell translations:", $column_to_cell_translations,
190             ###LogSD "..and last value column: $last_value_column", ] );
191             }
192              
193             # Scrub merge cells, column formats, and then empty values
194             my $max_column = ( $last_value_column and $new_ref->{row_span}->[1] < $last_value_column) ?
195 193 100 66     1120 $last_value_column : $new_ref->{row_span}->[1];
196 193         211 my( $cell_stack, $position_stack );
197             ###LogSD $phone->talk( level => 'debug', message => [
198             ###LogSD "Asking for row merge map of:", $new_ref ] );
199 193         5412 my $merge_range = $self->_get_row_merge_map( $new_ref->{row_number} );
200             ###LogSD $phone->talk( level => 'debug', message => [
201             ###LogSD "Full row merge settings:", $merge_range ] );
202 193         490 SCRUBINGCELLSTACK: for my $col ( 1 .. $max_column ){
203             ###LogSD $phone->talk( level => 'debug', message => [
204             ###LogSD "Processing column: $col", ,
205             ###LogSD (defined $column_to_cell_translations->[$col] ? $alt_ref->[$column_to_cell_translations->[$col]] : undef) ] );
206 902         673 my $new_cell;
207              
208             # Resolve additional merge info
209 902 100 66     1348 if( $merge_range and $merge_range->[$col] ){# Do it different for named worksheets
210             ###LogSD $phone->talk( level => 'debug', message => [
211             ###LogSD "Column -$col- is part of a merge range", $new_cell->{cell_merge} ] );
212              
213             # Handle primary position sharing
214 34 100       150 if( $self->spreading_merged_values ){
215 4         44 my $not_primary = 1;
216 4 50 33     19 if( defined $column_to_cell_translations->[$col] and $alt_ref->[$column_to_cell_translations->[$col]] ){
217             ###LogSD $phone->talk( level => 'debug', message => [
218             ###LogSD "Checking column -$col- to see if it contains the primary value" ] );
219 4         10 $merge_range->[$col] =~ /([^:]+)/;
220 4 100       13 if( $alt_ref->[$column_to_cell_translations->[$col]]->{r} eq $1 ){
221 2         2 $not_primary = 0;
222             ###LogSD $phone->talk( level => 'debug', message => [
223             ###LogSD "Found a primary value at: " . $alt_ref->[$column_to_cell_translations->[$col]]->{r} ] );
224 2         3 my $primary_ref;
225 2         3 for my $key ( qw( rich_text cell_xml_value s cell_type ) ){
226 8 100 33     22 if( !exists $primary_ref->{$key} and
227             exists $alt_ref->[$column_to_cell_translations->[$col]]->{$key} ){
228 7         15 $primary_ref->{$key} = $alt_ref->[$column_to_cell_translations->[$col]]->{$key};
229             }
230             }
231             ###LogSD $phone->talk( level => 'debug', message =>[
232             ###LogSD "primary ref is:", $primary_ref ] );
233 2 100       4 delete $primary_ref->{rich_text} if !$primary_ref->{rich_text};
234             ###LogSD $phone->talk( level => 'debug', message => [
235             ###LogSD "Built primary ref: ", $primary_ref ] );
236 2         64 $self->_set_merged_value( $merge_range->[$col] => $primary_ref );
237             }
238             }
239 4 100       7 if( $not_primary ){
240 2         62 $new_cell = $self->_get_merged_value( $merge_range->[$col] );
241             ###LogSD $phone->talk( level => 'debug', message => [
242             ###LogSD "New cell now has: ", $new_cell ] );
243             }
244             }
245 34         153 $new_cell->{cell_merge} = $merge_range->[$col];
246             ###LogSD $phone->talk( level => 'debug', message => [
247             ###LogSD "updated new cell now has: ", $new_cell ] );
248             }
249              
250             # Handle formats (especially from the column) including hidden - more work needed here!
251 902 100 66     3402 if( $new_cell or (defined $column_to_cell_translations->[$col] and
      66        
252             $alt_ref->[$column_to_cell_translations->[$col]] ) ){
253             $new_cell->{cell_hidden} =
254             $self->is_sheet_hidden ? 'sheet' :
255             ( $self->get_custom_column_data( $col ) and
256             exists $self->get_custom_column_data( $col )->{hidden} and
257             $self->get_custom_column_data( $col )->{hidden} ) ? 'column' :
258 499 100 66     11274 $full_row_ref->{attributes}->{hidden} ? 'row' : undef ;
    100          
    100          
259 499 100       1135 delete $new_cell->{cell_hidden} if !$new_cell->{cell_hidden};
260             ###LogSD $phone->talk( level => 'debug', message => [
261             ###LogSD "updated new cell now has: ", $new_cell ] );
262             }
263              
264             # Load in remaining data
265             ###LogSD $phone->talk( level => 'debug', message => [
266             ###LogSD "Possibly loading additional cell data: ", defined $column_to_cell_translations->[$col] ? $alt_ref->[$column_to_cell_translations->[$col]] : $column_to_cell_translations->[$col] ] );
267 902 100 66     2477 if( defined $column_to_cell_translations->[$col] and $alt_ref->[$column_to_cell_translations->[$col]] ){
268             ###LogSD $phone->talk( level => 'debug', message => [
269             ###LogSD "Mapping file column -$col- from position: $column_to_cell_translations->[$col]", ] );
270 499 100       415 map{ $new_cell->{$_} = $alt_ref->[$column_to_cell_translations->[$col]]->{$_} if !exists $new_cell->{$_} } keys %{$alt_ref->[$column_to_cell_translations->[$col]]};
  2847         8422  
  499         1378  
271             }
272             ###LogSD $phone->talk( level => 'debug', message =>[
273             ###LogSD "Updated new cell:", $new_cell ] );
274              
275             # Skip hidden
276             ###LogSD $phone->talk( level => 'debug', message =>[
277             ###LogSD "Should skip hidden: " . $self->should_skip_hidden,
278             ###LogSD "With hidden value exists: " . (($new_cell and exists $new_cell->{ cell_hidden }) ? $new_cell->{ cell_hidden } : 'undef'), $new_cell ] );
279 902 100 100     2561 if( $self->should_skip_hidden and $new_cell and exists $new_cell->{cell_hidden} ){
      66        
280             ###LogSD $phone->talk( level => 'debug', message =>[
281             ###LogSD "Skipping a known hidden cell", $new_cell ] );
282 15         180 next SCRUBINGCELLSTACK;
283             }
284              
285             # Skip empty
286             ###LogSD $phone->talk( level => 'debug', message =>[
287             ###LogSD "Checking values only: " . $self->get_values_only,
288             ###LogSD "With xml value exists: " . exists $new_cell->{cell_xml_value}, $new_cell ] );
289 887 100 100     3899 if( $self->get_values_only and $new_cell and !exists $new_cell->{cell_xml_value} ){
      100        
290             ###LogSD $phone->talk( level => 'debug', message =>[
291             ###LogSD "Skipping the empty cell", $new_cell ] );
292 4         53 next SCRUBINGCELLSTACK;
293             }
294              
295             # Stack new data
296             ###LogSD $phone->talk( level => 'debug', message =>[
297             ###LogSD "Checking if anything was stored in new_cell:", $new_cell ] );
298 883 100 66     4308 if( $new_cell and (keys %$new_cell ) > 0 ){
299 480         571 push @$cell_stack, $new_cell;
300 480         659 $position_stack->[$new_cell->{cell_col}] = $#$cell_stack;
301             ###LogSD $phone->talk( level => 'debug', message => [
302             ###LogSD 'Updated cell stack:', $cell_stack,
303             ###LogSD '..with position stack:', $position_stack ] );
304 480         704 $last_value_column = $new_cell->{cell_col};
305             }
306             }
307 193         367 $new_ref->{column_to_cell_translations} = $position_stack;
308              
309             # Handle full empty rows here
310             ###LogSD $phone->talk( level => 'debug', message => [
311             ###LogSD 'Updated cell stack:', $cell_stack,
312             ###LogSD '..with position stack:', $position_stack,
313             ###LogSD '..and last value column:', $last_value_column ] );
314 193 100       365 if( $cell_stack ){
315 186         296 $new_ref->{row_value_cells} = $cell_stack;
316             # Update max column as needed
317 186         279 $new_ref->{row_last_value_column} = $last_value_column;
318 186 100 66     4486 if( !$self->has_max_col or $last_value_column > $self->_max_col ){
319             ###LogSD $phone->talk( level => 'debug', message => [
320             ###LogSD "Updating max column with: $last_value_column", ] );
321 6         145 $self->_set_max_col( $last_value_column );
322             }
323             # Update span end
324 186 100       485 if( $new_ref->{row_span}->[1] < $last_value_column ){
325             ###LogSD $phone->talk( level => 'debug', message => [
326             ###LogSD "Updating span-end with: $last_value_column", ] );
327 2         4 $new_ref->{row_span}->[1] = $last_value_column;
328             }
329             # Update max row
330 186 100 100     4261 if( !$self->has_max_row or $new_ref->{row_number} > $self->_max_row ){
331             ###LogSD $phone->talk( level => 'debug', message => [
332             ###LogSD "Updating max row with: $new_ref->{row_number}", ] );
333 26         632 $self->_set_max_row( $new_ref->{row_number} );
334             }
335             # Update max column
336 186 100 66     4285 if( !$self->has_max_col or $new_ref->{row_span}->[1] > $self->_max_col ){
337             ###LogSD $phone->talk( level => 'debug', message => [
338             ###LogSD "Updating max column with: $new_ref->{row_span}->[1]", ] );
339 3         72 $self->_set_max_col( $new_ref->{row_span}->[1] );
340             }
341             }else{
342             ###LogSD $phone->talk( level => 'debug', message => [
343             ###LogSD 'No data available for this row', ] );
344 7         42 return undef;
345             }
346              
347              
348             ###LogSD $phone->talk( level => 'trace', message =>[
349             ###LogSD "Updated new ref:", $new_ref,] );
350 186         1234 return $new_ref;
351             }
352              
353             sub load_unique_bits{
354 32     32 1 65 my( $self, ) = @_;
355             ###LogSD my $phone = Log::Shiras::Telephone->new( name_space =>
356             ###LogSD $self->get_all_space . '::FileWorksheet::load_unique_bits', );
357             ###LogSD $phone->talk( level => 'debug', message => [
358             ###LogSD "Setting the Worksheet unique bits", ] );
359              
360             # Read the sheet row-column dimensions
361 32         65 my $good_load = 0;
362 32         2112 my $current_named_node = $self->current_named_node;
363             ###LogSD $phone->talk( level => 'debug', message => [
364             ###LogSD "Currently at named node:", $current_named_node, ] );
365 32         3533 my $result = 1;
366 32         62 my( $node_name, $node_level, $node_ref );
367 32 50       141 if( $current_named_node->{name} eq 'dimension' ){
368             ###LogSD $phone->talk( level => 'debug', message => [
369             ###LogSD "already at the dimension node" ] );
370             }else{
371 32         3352 ( $result, $node_name ) = $self->advance_element_position( 'dimension' );
372             ###LogSD $phone->talk( level => 'debug', message => [
373             ###LogSD "attempt to get to the dimension node result: $result" ] );
374             }
375 32 100       96 if( $result ){
376 32         166 my $dimension = $self->current_node_parsed;
377             ###LogSD $phone->talk( level => 'debug', message => [
378             ###LogSD "parsed dimension value:", $dimension ] );
379 30         130 my ( $start, $end ) = split( /:/, $dimension->{dimension}->{ref} );
380             ###LogSD $phone->talk( level => 'debug', message => [
381             ###LogSD "Start position: $start",
382             ###LogSD ( $end ? "End position: $end" : '' ), ] );
383 32 100       244 my ( $start_column, $start_row ) = ( $self->starts_at_the_edge ) ?
384             ( 1, 1 ) : $self->_parse_column_row( $start );
385 32 100       295 my ( $end_column, $end_row ) = $end ?
386             $self->_parse_column_row( $end ) :
387             ( undef, undef ) ;
388             ###LogSD $phone->talk( level => 'debug', message => [
389             ###LogSD 'Start column: ' . ($start_column//'undef'), 'Start row: ' . ($start_row//'undef'),
390             ###LogSD 'End column: ' . ($end_column//'undef'), 'End row: ' . ($end_row//'undef') ] );
391 32         3688 $self->_set_min_col( $start_column );
392 32         738 $self->_set_min_row( $start_row );
393 32 100       620 $self->_set_max_col( $end_column ) if defined $end_column;
394 32 100       4181 $self->_set_max_row( $end_row ) if defined $end_row;
395 32         96 $good_load = 1;
396             }else{
397 2         2867 $self->_set_min_col( 0 );
398 2         53 $self->_set_min_row( 0 );
399 2         50 $self->set_error( "No sheet dimensions provided" );
400             }
401              
402             # Work without a net !!!!!
403 32         4911 $self->change_stack_storage_to( 0 );
404              
405             #pull column stats
406             ###LogSD $phone->talk( level => 'trace', message => [
407             ###LogSD "Checking if column data should be collected: " . $self->collecting_column_formats ] );
408 32 100       4119 if( $self->collecting_column_formats ){
409 28 100       233 if( $node_name eq 'EOF' ){
410 2         60 $self->start_the_file_over;
411             }
412 28         95 ( $result, $node_name, $node_level, $node_ref ) = $self->advance_element_position( 'cols' );
413             ###LogSD $phone->talk( level => 'debug', message => [
414             ###LogSD "Arrived at node named -$node_name- with result: $result", ] );
415 26 100       111 if( $result ){
416              
417             # Build the node and add it to the stack
418 19         62 my $node_ref = $self->initial_node_build( $node_name, $node_ref );
419             ###LogSD $phone->talk( level => 'debug', message =>[
420             ###LogSD "Returned from initial node build with node:", $node_ref ] );
421 19         527 $self->add_node_to_stack( $node_ref );
422              
423             # Pull the data
424 21         196 my $column_data = $self->parse_element;
425             ###LogSD $phone->talk( level => 'trace', message => [
426             ###LogSD "parsed column elements to:", $column_data ] );
427              
428             # Process the data
429 21         59 my $column_store = [];
430 21         4541 for my $definition ( @{$column_data->{list}} ){
  21         90  
431 64 50 66     101 next if !is_HashRef( $definition ) or !is_HashRef( $definition->{attributes} );
432             ###LogSD $phone->talk( level => 'trace', message => [
433             ###LogSD "Processing:", $definition ] );
434 66         369 my $row_ref;
435 66 100       106 map{ $row_ref->{$_} = $definition->{attributes}->{$_} if defined $definition->{attributes}->{$_} } qw( width customWidth bestFit hidden );
  258         3536  
436             ###LogSD $phone->talk( level => 'debug', message => [
437             ###LogSD "Updated row ref:", $row_ref ] );
438 66         192 for my $col ( $definition->{attributes}->{min} .. $definition->{attributes}->{max} ){
439 32827         21262 $column_store->[$col] = $row_ref;
440             ###LogSD $phone->talk( level => 'trace', message => [
441             ###LogSD "Updated column store is:", $column_store ] );
442             }
443             }
444             ###LogSD $phone->talk( level => 'trace', message => [
445             ###LogSD "Final column store is:", $column_store ] );
446 21         3814 $good_load = 1;
447 21         591 $self->_set_column_formats( $column_store );
448             }
449             }
450              
451             # Get sheet meta data merge information
452 32         93 my $merge_ref = [];
453             ###LogSD $phone->talk( level => 'trace', message => [
454             ###LogSD "Checking if merged data should be collected: " . $self->collecting_merge_data ] );
455 32 100       5895 if( $self->collecting_merge_data ){
456 28 100       196 if( $node_name eq 'EOF' ){
457 9         51 $self->start_the_file_over;
458             }
459 31         8698 ( $result, $node_name, $node_level, $node_ref ) = $self->advance_element_position( 'mergeCells' );
460             ###LogSD $phone->talk( level => 'debug', message => [
461             ###LogSD "Arrived at node named -$node_name- with result: $result", ] );
462              
463 31 100       203 if( $result ){
464              
465             # Build the node and add it to the stack
466 15         13851 my $node_ref = $self->initial_node_build( $node_name, $node_ref );
467             ###LogSD $phone->talk( level => 'debug', message =>[
468             ###LogSD "Returned from initial node build with node:", $node_ref ] );
469 15         305 $self->add_node_to_stack( $node_ref );
470              
471 30         151 my $merge_range = $self->parse_element;
472             ###LogSD $phone->talk( level => 'debug', message => [
473             ###LogSD "Processing all merge ranges:", $merge_range ] );
474 15         67 $merge_range = $self->squash_node( $merge_range );
475             ###LogSD $phone->talk( level => 'trace', message => [
476             ###LogSD "squashed merge range:", $merge_range ] );
477 15         12284 my $final_ref = [];
478 16         3464 for my $merge_ref ( @{$merge_range->{list}} ){
  16         59  
479             ###LogSD $phone->talk( level => 'debug', message => [
480             ###LogSD "parsed merge element to:", $merge_ref ] );
481 20         5356 my ( $start, $end ) = split /:/, $merge_ref->{ref};
482 20         5497 my ( $start_col, $start_row ) = $self->_parse_column_row( $start );
483 20         164 my ( $end_col, $end_row ) = $self->_parse_column_row( $end );
484             ###LogSD $phone->talk( level => 'debug', message => [
485             ###LogSD "Start column: $start_col", "Start row: $start_row",
486             ###LogSD "End column: $end_col", "End row: $end_row" ] );
487 20         27 my $min_col = $start_col;
488 20         71 while ( $start_row <= $end_row ){
489 40         4579 $final_ref->[$start_row]->[$start_col] = $merge_ref->{ref};
490 40         69 $start_col++;
491 38 100       71 if( $start_col > $end_col ){
492 22         29 $start_col = $min_col;
493 22         93 $start_row++;
494             }
495             }
496             }
497             ###LogSD $phone->talk( level => 'trace', message => [
498             ###LogSD "Final merge ref:", $final_ref ] );
499 12         3130 $good_load = 1;
500 12         292 $self->_set_merge_map( $final_ref );
501             }# exit 1;
502             }
503              
504             # Record file state
505 32 50       141 if( $good_load ){
506             ###LogSD $phone->talk( level => 'debug', message => [
507             ###LogSD "The Worksheet file has metadata" ] );
508 32         5050 $self->good_load( 1 );
509             }else{
510 2         14 $self->set_error( "No 'Worksheet' definition elements found - can't parse this as a Worksheet file" );
511 2         31 return undef;
512             }
513              
514 32         3672 $self->start_the_file_over;
515             ###LogSD $phone->talk( level => 'trace', message => [
516             ###LogSD "Finished the worksheet unique bits" ] );
517 32         200 return 1;
518             }
519              
520             #########1 Private Attributes 3#########4#########5#########6#########7#########8#########9
521              
522             has _sheet_min_col =>(
523             isa => Int,
524             writer => '_set_min_col',
525             reader => '_min_col',
526             predicate => 'has_min_col',
527             );
528              
529             has _sheet_min_row =>(
530             isa => Int,
531             writer => '_set_min_row',
532             reader => '_min_row',
533             predicate => 'has_min_row',
534             );
535              
536             has _sheet_max_col =>(
537             isa => Int,
538             writer => '_set_max_col',
539             reader => '_max_col',
540             predicate => 'has_max_col',
541             );
542              
543             has _sheet_max_row =>(
544             isa => Int,
545             writer => '_set_max_row',
546             reader => '_max_row',
547             predicate => 'has_max_row',
548             );
549              
550             has _column_formats =>(
551             isa => ArrayRef,
552             traits => ['Array'],
553             writer => '_set_column_formats',
554             reader => '_get_column_formats',
555             default => sub{ [] },
556             handles =>{
557             get_custom_column_data => 'get',
558             },
559             );
560              
561             has _row_formats =>(
562             isa => ArrayRef,
563             traits => ['Array'],
564             writer => '_set_row_formats',
565             reader => '_get_row_formats',
566             default => sub{ [] },
567             handles =>{
568             get_custom_row_data => 'get',
569             _set_custom_row_data => 'set',
570             },
571             );
572              
573             has _merge_map =>(
574             isa => ArrayRef,
575             traits => ['Array'],
576             writer => '_set_merge_map',
577             reader => 'get_merge_map',
578             default => sub{ [] },
579             handles =>{
580             _get_row_merge_map => 'get',
581             },
582             );
583              
584             has _primary_merged_values =>(# Values for the top left corner of the merge range
585             isa => HashRef,
586             traits => ['Hash'],
587             reader => '_get_all_merged_values',
588             default => sub{ {} },
589             handles =>{
590             _set_merged_value => 'set',
591             _get_merged_value => 'get',
592             },
593             );
594              
595             #########1 Private Methods 3#########4#########5#########6#########7#########8#########9
596              
597              
598              
599             #########1 Phinish 3#########4#########5#########6#########7#########8#########9
600              
601 18     18   31947 no Moose::Role;
  18         31  
  18         99  
602              
603             1;
604              
605             #########1 Documentation 3#########4#########5#########6#########7#########8#########9
606             __END__
607              
608             =head1 NAME
609              
610             Spreadsheet::Reader::ExcelXML::XMLReader::FileWorksheet - Zip file worksheet interpreter
611              
612             =head1 SYNOPSIS
613              
614             See t\Spreadsheet\Reader\ExcelXML\XMLReader\05-file_worksheet.t
615              
616             =head1 DESCRIPTION
617              
618             This documentation is written to explain ways to use this module when writing your own excel
619             parser. To use the general package for excel parsing out of the box please review the
620             documentation for L<Workbooks|Spreadsheet::Reader::ExcelXML>,
621             L<Worksheets|Spreadsheet::Reader::ExcelXML::Worksheet>, and
622             L<Cells|Spreadsheet::Reader::ExcelXML::Cell>
623              
624             This module incrementally adds functionality to the base class
625             L<Spreadsheet::Reader::ExcelXML::XMLReader>. The goal is to parse individual worksheet files
626             (not chartsheets) from the zip file format (.xlsx) into perl objects The primary purpose
627             of this role is to normalize functions used by L<Spreadsheet::Reader::ExcelXML::WorksheetToRow>
628             where other roles could be used to normalize other formats. It does not provide a way to read
629             L<chartsheets|Spreadsheet::Reader::ExcelXML::Chartsheet>.
630              
631             I<All positions (row and column places and integers) at this level are stored and returned
632             in count from one mode!>
633              
634             To replace this part in the package look in the raw code of
635             L<Spreadsheet::Reader::ExcelXML::Workbook> and adjust the 'worksheet_interface' key of the
636             $parser_modules variable.
637              
638             =head2 requires
639              
640             This module is a L<role|Moose::Manual::Roles> and as such only adds incremental methods and
641             attributes to some base class. In order to use this role some base object methods are
642             required. The requirments are listed below with links to the default provider.
643              
644             =over
645              
646             L<Spreadsheet::Reader::ExcelXML::XMLReader/current_named_node>
647              
648             L<Spreadsheet::Reader::ExcelXML::XMLReader/current_node_parsed>
649              
650             L<Spreadsheet::Reader::ExcelXML::XMLReader/advance_element_position>
651              
652             L<Spreadsheet::Reader::ExcelXML::XMLReader/good_load>
653              
654             L<Spreadsheet::Reader::ExcelXML::XMLReader/start_the_file_over>
655              
656             L<Spreadsheet::Reader::ExcelXML::XMLReader/squash_node>
657              
658             L<Spreadsheet::Reader::ExcelXML::XMLReader/parse_element>
659              
660             "_parse_column_row" in L<Spreadsheet::Reader::ExcelXML::CellToColumnRow
661             |Spreadsheet::Reader::ExcelXML::CellToColumnRow/parse_column_row( $excel_cell_id )>
662              
663             L<Spreadsheet::Reader::ExcelXML/spreading_merged_values>
664              
665             L<Spreadsheet::Reader::ExcelXML/should_skip_hidden>
666              
667             L<Spreadsheet::Reader::ExcelXML/has_shared_strings_interface>
668              
669             L<Spreadsheet::Reader::ExcelXML/are_spaces_empty>
670              
671             L<Spreadsheet::Reader::ExcelXML/get_empty_return_type>
672              
673             L<Spreadsheet::Reader::ExcelXML/get_values_only>
674              
675             L<Spreadsheet::Reader::ExcelXML/starts_at_the_edge>
676              
677             L<Spreadsheet::Reader::ExcelXML/collecting_merge_data>
678              
679             L<Spreadsheet::Reader::ExcelXML/collecting_column_formats>
680              
681             L<Spreadsheet::Reader::ExcelXML::SharedStrings/get_shared_string( $positive_intE<sol>$name )>
682              
683             =back
684              
685             =head2 Attributes
686              
687             Data passed to new when creating an instance. This list only contains public attributes
688             incrementally provided by this role. For access to the values in these attributes see
689             the listed 'attribute methods'. For general information on attributes see
690             L<Moose::Manual::Attributes>. For ways to manage the instance when opened see the
691             L<Methods|/Methods>.
692              
693             =head3 is_hidden
694              
695             =over
696              
697             B<Definition:> This data is collected at the workbook level for this file type. It indicates
698             if the sheet is human visible.
699              
700             B<Range:> (1|0)
701              
702             B<attribute methods> Methods provided to adjust this attribute
703              
704             =over
705              
706             B<is_sheet_hidden>
707              
708             =over
709              
710             B<Definition:> return the attribute value
711              
712             =back
713              
714             =back
715              
716             =back
717              
718             =head3 _sheet_min_col
719              
720             =over
721              
722             B<Definition:> This is the minimum column in the sheet with data or formatting. For this
723             module it is pulled from the xml file at worksheet/dimension:ref = "upperleft:lowerright"
724              
725             B<Range:> an integer
726              
727             B<attribute methods> Methods provided to adjust this attribute
728              
729             =over
730              
731             B<_min_col>
732              
733             =over
734              
735             B<Definition:> returns the attribute value
736              
737             =back
738              
739             B<has_min_col>
740              
741             =over
742              
743             B<Definition:> attribute predicate
744              
745             =back
746              
747             =back
748              
749             =back
750              
751             =head3 _sheet_min_row
752              
753             =over
754              
755             B<Definition:> This is the minimum row in the sheet with data or formatting. For this
756             module it is pulled from the xml file at worksheet/dimension:ref = "upperleft:lowerright"
757              
758             B<Range:> an integer
759              
760             B<attribute methods> Methods provided to adjust this attribute
761              
762             =over
763              
764             B<_set_min_row>
765              
766             =over
767              
768             B<Definition:> sets the attribute value
769              
770             =back
771              
772             B<_min_row>
773              
774             =over
775              
776             B<Definition:> returns the attribute value
777              
778             =back
779              
780             B<has_min_row>
781              
782             =over
783              
784             B<Definition:> attribute predicate
785              
786             =back
787              
788             =back
789              
790             =back
791              
792             =head3 _sheet_max_col
793              
794             =over
795              
796             B<Definition:> This is the maximum column in the sheet with data or formatting. For this
797             module it is pulled from the xml file at worksheet/dimension/ref = "upperleft:lowerright"
798              
799             B<Range:> an integer
800              
801             B<attribute methods> Methods provided to adjust this attribute
802              
803             =over
804              
805             B<_set_max_col>
806              
807             =over
808              
809             B<Definition:> sets the attribute value
810              
811             =back
812              
813             B<_max_col>
814              
815             =over
816              
817             B<Definition:> returns the attribute value
818              
819             =back
820              
821             B<has_max_col>
822              
823             =over
824              
825             B<Definition:> attribute predicate
826              
827             =back
828              
829             =back
830              
831             =back
832              
833             =head3 _sheet_max_row
834              
835             =over
836              
837             B<Definition:> This is the maximum row in the sheet with data or formatting. For this
838             module it is pulled from the xml file at worksheet/dimension:ref = "upperleft:lowerright"
839              
840             B<Range:> an integer
841              
842             B<attribute methods> Methods provided to adjust this attribute
843              
844             =over
845              
846             B<_set_max_row>
847              
848             =over
849              
850             B<Definition:> sets the attribute value
851              
852             =back
853              
854             B<_max_row>
855              
856             =over
857              
858             B<Definition:> returns the attribute value
859              
860             =back
861              
862             B<has_max_row>
863              
864             =over
865              
866             B<Definition:> attribute predicate
867              
868             =back
869              
870             =back
871              
872             =back
873              
874             =head2 Methods
875              
876             These are the methods provided by this class for use within the package but are not intended
877             to be used by the end user. Other private methods not listed here are used in the module but
878             not used by the package. If a method is listed here then replacement of this module
879             either requires replacing the method or rewriting all the associated connecting roles and classes.
880              
881             =head3 load_unique_bits
882              
883             =over
884              
885             B<Definition:> This is called by L<Spreadsheet::Reader::ExcelXML::XMLReader> when the file is
886             loaded for the first time so that file specific metadata can be collected.
887              
888             B<Accepts:> nothing
889              
890             B<Returns:> nothing
891              
892             =back
893              
894             =head3 advance_row_position( $increment )
895              
896             =over
897              
898             B<Definition:> As an XML data structure each worksheet has three levels of information. The
899             column data is stored separately in the file and just referenced. The row data encases all
900             the cell data for that row. Each cell contains modifiers to row and column settings. The
901             column data is read during the 'load_unique_bits' method. The cell specific data is not
902             completed here. This method will advance to the next recorded row position in the XML file.
903             Not to be confused with the next row number. If you want to advance to the 'next' position
904             more than one time then you can provide a value for $increment.
905              
906             B<Accepts:> a positive integer $increment (defaults to 1 if no value passed)
907              
908             B<Returns:> The attribute ref of the top row node
909              
910             =back
911              
912             =head3 build_row_data
913              
914             =over
915              
916             B<Definition:> Collects all the sub-information (XML node) for the row in order to build
917             the argument for populating a L<Spreadsheet::Reader::ExcelXML::Row> instance.
918              
919             B<Accepts:> nothing
920              
921             B<Returns:> a hash ref of inputs for L<Spreadsheet::Reader::ExcelXML::Row>
922              
923             =back
924              
925             =head3 get_custom_column_data( $column )
926              
927             =over
928              
929             B<Definition:> Returns any collected custom column information for the indicated
930             $column.
931              
932             B<Accepts:> a positive integer $column in count from 1 context
933              
934             B<Returns:> a hash ref of custom column settings
935              
936             =back
937              
938             =head3 get_custom_row_data( $row )
939              
940             =over
941              
942             B<Definition:> Returns any collected custom row information for the indicated $row.
943              
944             B<Accepts:> a positive integer $row in count from 1 context
945              
946             B<Returns:> a hash ref of custom row settings
947              
948             =back
949              
950             =head3 get_merge_map
951              
952             =over
953              
954             B<Definition:> This returns the full merge map with merge ranges stored in each
955             position for the range of known rows and columns.
956              
957             B<Accepts:> nothing
958              
959             B<Returns:> an array ref of array refs where the top level array represents
960             rows stored in count from 1 context and the second level array ref are the
961             columns stored in count from 1 context. (The first position for each will
962             therefor be dead space)
963              
964             =back
965              
966             =head1 SUPPORT
967              
968             =over
969              
970             L<github Spreadsheet::Reader::ExcelXML/issues
971             |https://github.com/jandrew/p5-spreadsheet-reader-excelxml/issues>
972              
973             =back
974              
975             =head1 TODO
976              
977             =over
978              
979             B<1.> If a the primary cell of a merge range is hidden show that value
980             in the top left unhidden cell even when the attribute
981             L<Spreadsheet::Reader::ExcelXML::Workbook/spread_merged_values> is not
982             set. (This is the way excel does it(ish))
983              
984             =back
985              
986             =head1 AUTHOR
987              
988             =over
989              
990             =item Jed Lund
991              
992             =item jandrew@cpan.org
993              
994             =back
995              
996             =head1 COPYRIGHT
997              
998             This program is free software; you can redistribute
999             it and/or modify it under the same terms as Perl itself.
1000              
1001             The full text of the license can be found in the
1002             LICENSE file included with this module.
1003              
1004             This software is copyrighted (c) 2016 by Jed Lund
1005              
1006             =head1 DEPENDENCIES
1007              
1008             =over
1009              
1010             L<Spreadsheet::Reader::ExcelXML> - the package
1011              
1012             =back
1013              
1014             =head1 SEE ALSO
1015              
1016             =over
1017              
1018             L<Spreadsheet::Read> - generic Spreadsheet reader
1019              
1020             L<Spreadsheet::ParseExcel> - Excel binary version 2003 and earlier (.xls files)
1021              
1022             L<Spreadsheet::XLSX> - Excel version 2007 and later
1023              
1024             L<Spreadsheet::ParseXLSX> - Excel version 2007 and later
1025              
1026             L<Log::Shiras|https://github.com/jandrew/Log-Shiras>
1027              
1028             =over
1029              
1030             All lines in this package that use Log::Shiras are commented out
1031              
1032             =back
1033              
1034             =back
1035              
1036             =cut
1037              
1038             #########1 Documentation End 3#########4#########5#########6#########7#########8#########9