File Coverage

blib/lib/Spreadsheet/ParseExcel/Stream/XLS.pm
Criterion Covered Total %
statement 13 15 86.6
branch n/a
condition n/a
subroutine 5 5 100.0
pod n/a
total 18 20 90.0


line stmt bran cond sub pod time code
1             package Spreadsheet::ParseExcel::Stream::XLS;
2              
3 8     8   50 use strict;
  8         16  
  8         293  
4 8     8   43 use warnings;
  8         20  
  8         287  
5              
6 8     8   12388 use Spreadsheet::ParseExcel;
  8         755424  
  8         370  
7 8     8   108 use Scalar::Util qw(weaken);
  8         17  
  8         621  
8 8     8   5929 use Coro;
  0            
  0            
9              
10             our $VERSION = '0.11';
11              
12             sub new {
13             my ($class, $file, $opts) = @_;
14              
15             $opts ||= {};
16             my @password;
17             if ( defined($opts->{Password}) && length($opts->{Password}) ) {
18             @password = ( Password => $opts->{Password} );
19             }
20              
21             my $main = Coro::State->new();
22             my ($xls,$parser);
23              
24             my ($wb, $idx, $row, $col, $cell);
25             my $tmp = my $handler = sub {
26             ($wb, $idx, $row, $col, $cell) = @_;
27             $parser->transfer($main);
28             };
29              
30             my $tmp_p = $parser = Coro::State->new(sub {
31             $xls->Parse($file);
32             # Flag the generator that we're done
33             undef $xls;
34             # If we don't transfer back when done parsing,
35             # it's an implicit program exit (oops!)
36             $parser->transfer($main)
37             });
38             weaken($parser);
39              
40             $xls = Spreadsheet::ParseExcel->new(
41             CellHandler => $handler,
42             NotSetCell => 1,
43             @password,
44             );
45              
46             # Returns the next cell of the spreadsheet
47             my $generator = sub {
48              
49             # Just in case we ask for the next cell when we're already done
50             return unless $xls;
51              
52             $main->transfer($parser);
53             return [ $wb, $idx, $row, $col, $cell ] if $xls;
54              
55             # We're done with these threads
56             $main->cancel();
57             $parser->cancel();
58             return;
59             };
60             my $nxt_cell = $generator->();
61              
62             my $self = bless {
63             # Save a reference to the parser so it doesn't disappear
64             # until the object is destroyed.
65             PARSER => $tmp_p,
66             NEXT_CELL => $nxt_cell,
67             SUB => $generator,
68             TRIM => $opts->{TrimEmpty},
69             NEW_WS => 0,
70             }, 'Spreadsheet::ParseExcel::Stream::Sheet';
71             $self->bind_columns( @{$opts->{BindColumns}} ) if $opts->{BindColumns};
72             return $self;
73             }
74              
75             package Spreadsheet::ParseExcel::Stream::Sheet;
76              
77             sub sheet {
78             my $self = shift;
79             return unless $self->{NEXT_CELL};
80              
81             # NEW_WS:
82             # undef - in the middle of a sheet.
83             # 0 - Hit end of previous sheet and fetched next row.
84             # 1 - At end of sheet but not fetched next row yet.
85             # 0 and 1 will be treated as same, just undef NEW_WS and return.
86             # If undef, advance to the next sheet.
87             if ( ! defined $self->{NEW_WS} ) {
88             # Advance to the next sheet
89             my $curr_cell = $self->{NEXT_CELL};
90             my $curr_sheet = $curr_cell->[1];
91             my $f = $self->{SUB};
92             my $nxt_cell = $f->();
93             while ( $nxt_cell && $nxt_cell->[1] == $curr_sheet ) {
94             $nxt_cell = $f->();
95             }
96             $self->{NEXT_CELL} = $nxt_cell or return;
97             }
98             $self->{NEW_WS} = undef;
99             return $self;
100             }
101              
102             sub workbook {
103             my $self = shift;
104             my $row = $self->{NEXT_CELL};
105             return $row->[0];
106             }
107              
108             sub worksheet {
109             my $self = shift;
110             my $row = $self->{NEXT_CELL};
111             my $wb = $row->[0];
112             return $wb->worksheet($row->[1]);
113             }
114              
115             sub name {
116             my $self = shift;
117             return $self->worksheet()->{Name};
118             }
119              
120             sub set_next_row {
121             my ($self, $current) = @_;
122             return $self->{CURR_ROW} if $current;
123              
124             return $self->{NEW_WS} = 0 if $self->{NEW_WS};
125              
126             # Save original cell so we can detect change in worksheet
127             my $curr_cell = $self->{NEXT_CELL};
128             my $f = $self->{SUB};
129              
130             # Initialize row with first cell
131             my @row = ();
132             my $nxt_cell = $f->();
133              
134             my $min_col = $self->{TRIM}
135             ? ( $curr_cell->[0]->worksheet( $curr_cell->[1] )->col_range)[0]
136             : 0;
137             $row[ $curr_cell->[3] - $min_col ] = $curr_cell;
138              
139             # Collect current row on current worksheet
140             my ( $curr_sheet, $curr_row ) = @$curr_cell[1,2];
141             while ( $nxt_cell && $nxt_cell->[1] == $curr_sheet && $nxt_cell->[2] == $curr_row ) {
142             $curr_cell = $nxt_cell;
143             $row[ $curr_cell->[3] - $min_col ] = $curr_cell;
144             $nxt_cell = $f->();
145             }
146             $self->{NEXT_CELL} = $nxt_cell;
147             $self->{NEW_WS}++ if !$nxt_cell || $curr_sheet != $nxt_cell->[1];
148             $self->{CURR_ROW} = \@row;
149             }
150              
151             sub next_row {
152             my ($self, $current, $f) = @_;
153             $f ||= sub {$_->[4]};
154             unless ($current) {
155             my $row = $self->set_next_row();
156             return unless $row;
157             if ( $self->{BIND} ) {
158             my @curr_row = map { defined $_ ? $f->() : $_ } @{$self->{CURR_ROW}};
159             $$_ = shift @curr_row for @{$self->{BIND}};
160             return 1;
161             }
162             }
163             return [ map { defined $_ ? $f->() : $_ } @{$self->{CURR_ROW}} ];
164             }
165              
166             sub row {
167             my ($self,$current) = @_;
168             return $self->next_row($current, sub {$_->[4]->value});
169             }
170              
171             sub unformatted {
172             my ($self, $current) = @_;
173             return $self->next_row($current, sub {$_->[4]->unformatted});
174             }
175              
176             sub bind_columns {
177             my $self = shift;
178             $self->{BIND} = [ @_ ];
179             }
180              
181             sub unbind_columns { delete $_[0]->{BIND} }
182              
183             1;
184              
185             __END__