File Coverage

lib/Spreadsheet/Reader/ExcelXML/CellToColumnRow.pm
Criterion Covered Total %
statement 93 103 90.2
branch 37 46 80.4
condition 6 6 100.0
subroutine 15 15 100.0
pod 4 4 100.0
total 155 174 89.0


line stmt bran cond sub pod time code
1             package Spreadsheet::Reader::ExcelXML::CellToColumnRow;
2             our $AUTHORITY = 'cpan:JANDREW';
3 22     22   935216 use version; our $VERSION = version->declare('v0.16.8');
  22         38  
  22         199  
4             ###LogSD warn "You uncovered internal logging statements for Spreadsheet::Reader::ExcelXML::CellToColumnRow-$VERSION";
5              
6 22     22   2041 use Moose::Role;
  22         29  
  22         181  
7             requires
8             'set_error', 'counting_from_zero',
9             ###LogSD 'get_all_space'
10             ;
11 22     22   81279 use Types::Standard qw( Bool );
  22         38  
  22         232  
12 1     1   628 ###LogSD use Log::Shiras::Telephone;
  1         1  
  1         155  
13              
14             #########1 Dispatch Tables 3#########4#########5#########6#########7#########8#########9
15              
16             my $lookup_ref ={
17             A => 1, B => 2, C => 3, D => 4, E => 5, F => 6, G => 7, H => 8, I => 9, J => 10,
18             K => 11, L => 12, M => 13, N => 14, O => 15, P => 16, Q => 17, R => 18, S => 19,
19             T => 20, U => 21, V => 22, W => 23, X => 24, Y => 25, Z => 26,
20             };
21             my $lookup_list =[ qw( A B C D E F G H I J K L M N O P Q R S T U V W X Y Z ) ];
22              
23             #########1 Public Attributes 3#########4#########5#########6#########7#########8#########9
24              
25              
26              
27             #########1 Public Methods 3#########4#########5#########6#########7#########8#########9
28              
29             sub parse_column_row{#? add the manual conversion to used vs excel on the next two
30 43     43 1 4559 my ( $self, $cell ) = @_;
31             ###LogSD my $phone = Log::Shiras::Telephone->new( name_space =>
32             ###LogSD $self->get_all_space . '::parse_column_row', );
33             ###LogSD $phone->talk( level => 'debug', message =>[
34             ###LogSD "Parsing file row number and file column number from: $cell" ] );
35 43         86 my ( $column, $row ) = $self->_parse_column_row( $cell );
36             ###LogSD $phone->talk( level => 'debug', message =>[
37             ###LogSD 'File Column: ' . ($column//''), 'File Row: ' . ($row//'') ] );
38 1     1   5 ###LogSD use warnings 'uninitialized';
  1         1  
  1         54  
39              
40             # Convert to user numbers
41 43         174 my $user_row = $self->get_used_position( $row );
42 1     1   4 ###LogSD no warnings 'uninitialized';
  1         1  
  1         66  
43             ###LogSD $phone->talk( level => 'debug', message =>[
44             ###LogSD "Returning -$user_row- for row: $row" ] );
45 43         55 my $user_column = $self->get_used_position( $column );
46             ###LogSD $phone->talk( level => 'debug', message =>[
47             ###LogSD "Returning -$user_column- for column: $column" ] );
48 1     1   3 ###LogSD use warnings 'uninitialized';
  1         2  
  1         382  
49 43         204 return( $user_column, $user_row );
50             }
51              
52             sub build_cell_label{
53 33     33 1 14339 my ( $self, $column, $row ) = @_;
54             ###LogSD my $phone = Log::Shiras::Telephone->new( name_space =>
55             ###LogSD ($self->get_log_space . '::build_cell_label' ), );
56             ###LogSD $phone->talk( level => 'debug', message =>[
57             ###LogSD "Converting file column -$column- and file row -$row- to a cell ID" ] );
58              
59             # Convert to code numbers
60 33         54 my $code_row = $self->get_excel_position( $row );
61             ###LogSD $phone->talk( level => 'debug', message =>[
62             ###LogSD "Parsing -$code_row- for row: $row" ] );
63 33         36 my $code_column = $self->get_excel_position( $column );
64             ###LogSD $phone->talk( level => 'debug', message =>[
65             ###LogSD "Parsing -$code_column- for column: $column" ] );
66              
67 33         352 my $cell_label = $self->_build_cell_label( $code_column, $code_row );
68             ###LogSD $phone->talk( level => 'debug', message =>[
69             ###LogSD "Cell label is: $cell_label" ] );
70 32         125 return $cell_label;
71             }
72              
73             sub get_excel_position{
74 808     809 1 804 my ( $self, $used_int ) = @_;
75 808 50       1262 return undef if !defined $used_int;
76             ###LogSD my $phone = Log::Shiras::Telephone->new( name_space =>
77             ###LogSD $self->get_all_space . '::get_excel_position', );
78             ###LogSD $phone->talk( level => 'debug', message =>[
79             ###LogSD "Converting used number -$used_int- to Excel" ] );
80 808         668 my $excel_position = $used_int;
81 808 100       1986 if( $self->counting_from_zero ){
82 742         2433 $excel_position += 1 ;
83             ###LogSD $phone->talk( level => 'debug', message =>[
84             ###LogSD "New position is: $excel_position" ] );
85             }else{
86             ###LogSD $phone->talk( level => 'debug', message =>[
87             ###LogSD "Not counting from zero now" ] );
88             }
89 808         1423 return $excel_position;
90             }
91              
92             sub get_used_position{
93 1339     1339 1 1469 my ( $self, $excel_int ) = @_;
94 1339 100       2006 return undef if !defined $excel_int;
95             ###LogSD my $phone = Log::Shiras::Telephone->new( name_space =>
96             ###LogSD $self->get_all_space . '::get_used_position', );
97             ###LogSD $phone->talk( level => 'debug', message =>[
98             ###LogSD "Converting the Excel number -$excel_int- to the used number" ] );
99 1332         1165 my $used_position = $excel_int;
100 1332 100       3188 $used_position -= 1 if $self->counting_from_zero;
101             ###LogSD $phone->talk( level => 'debug', message =>[
102             ###LogSD "The used position is: $used_position" ] );
103 1332         5693 return $used_position;
104             }
105              
106             #########1 Private Attributes 3#########4#########5#########6#########7#########8#########9
107              
108              
109              
110             #########1 Private Methods 3#########4#########5#########6#########7#########8#########9
111              
112             sub _parse_column_row{
113 615     615   796 my ( $self, $cell ) = @_;
114 615         510 my ( $column, $error_list_ref );
115             ###LogSD my $phone = Log::Shiras::Telephone->new( name_space =>
116             ###LogSD $self->get_all_space . '::_parse_column_row', );
117             ###LogSD $phone->talk( level => 'debug', message =>[
118             ###LogSD "Parsing excel row and column number from: $cell" ] );
119              
120             # Split the digits
121 615         1448 my $regex = qr/^([A-Z])?([A-Z])?([A-Z])?([0-9]*)$/;
122 615         13805 my ( $one_column, $two_column, $three_column, $row ) = $cell =~ $regex;
123 22     22   15892 no warnings 'uninitialized';
  22         30  
  22         9814  
124 615         17405 my $column_text = $one_column . $two_column . $three_column;
125             ###LogSD $phone->talk( level => 'debug', message =>[
126             ###LogSD "Regex result is: ( $one_column, $two_column, $three_column, $row )" ] );
127              
128             # Calculate the column value
129 615 100       1489 if( !defined $one_column ){
    100          
    100          
130 11         39 push @$error_list_ref, "Could not parse the column component from -$cell-";
131             }elsif( !defined $two_column ){
132 605         1025 $column = $lookup_ref->{$one_column};
133             }elsif( !defined $three_column ){
134 15         15397 $column = $lookup_ref->{$two_column} + 26 * $lookup_ref->{$one_column};
135             }else{
136 2         6 $column = $lookup_ref->{$three_column} + 26 * $lookup_ref->{$two_column} + 26 * 26 * $lookup_ref->{$one_column};
137             }
138             ###LogSD $phone->talk( level => 'debug', message =>[
139             ###LogSD "Result of initial parse is column text: $column_text",
140             ###LogSD "Column number: $column", "Row number: $row" ] );
141 615 100 100     2300 if( $column_text and $column > 16384 ){
142 1         5 push @$error_list_ref, "The column text -$column_text- points to a position at " .
143             "-$column- past the excel limit of: 16,384";
144 1         2 $column = undef;
145             }
146              
147             # Manage row out of bounds states
148 615 100 100     10319 if( !defined $row or $row eq '' ){
    100          
    100          
149 17         15934 push @$error_list_ref, "Could not parse the row component from -$cell-";
150 8         8 $row = undef;
151             }elsif( $row < 1 ){
152 1         4 push @$error_list_ref, "The requested row cannot be less than one - you requested: $row";
153 10         102 $row = undef;
154             }elsif( $row > 1048576 ){
155 1         5 push @$error_list_ref, "The requested row cannot be greater than 1,048,576 " .
156             "- you requested: $row";
157 1         1 $row = undef;
158             }
159              
160             # Handle collected errors
161 606 100       946 if( $error_list_ref ){
162 12 100       23 if( scalar( @$error_list_ref ) > 1 ){
163 1         6 $self->set_error( "The regex $regex could not match -$cell-" );
164             }else{
165 11         43 $self->set_error( $error_list_ref->[0] );
166             }
167             }
168             ###LogSD no warnings 'uninitialized';
169             ###LogSD $phone->talk( level => 'debug', message =>[
170             ###LogSD "Column: $column", "Row: $row" ] );
171             ###LogSD use warnings 'uninitialized';
172 615         2157 return( $column, $row );
173             }
174              
175             sub _build_cell_label{
176 228     228   273 my ( $self, $column, $row ) = @_;
177             ###LogSD my $phone = Log::Shiras::Telephone->new( name_space =>
178             ###LogSD $self->get_all_space . '::build_cell_label', );
179             ###LogSD no warnings 'uninitialized';
180             ###LogSD $phone->talk( level => 'debug', message =>[
181             ###LogSD "Converting column -$column- and row -$row- to a cell ID" ] );
182             ###LogSD use warnings 'uninitialized';
183 228         169 my $error_list;
184              
185             # Parse column
186 228 50       335 if( !defined $column ){
187             ###LogSD $phone->talk( level => 'debug', message =>[
188             ###LogSD "The column is not defined" ] );
189 9         54 $column = '';
190 9         15375 push @$error_list, 'missing column';
191             }else{
192             ###LogSD $phone->talk( level => 'debug', message =>[
193             ###LogSD "Excel column: $column" ] );
194 228         245 $column -= 1;
195             ###LogSD $phone->talk( level => 'debug', message =>[
196             ###LogSD "From zero: $column" ] );
197 228 50       395 if( $column > 16383 ){
    50          
198 0         0 push @$error_list, 'column too large';
199 0         0 $column = '';
200             }elsif( $column < 0 ){
201 0         0 push @$error_list, 'column too small';
202 0         0 $column = '';
203             }else{
204 228         355 my $first_letter = int( $column / (26 * 26) );
205 228         201 $column = $column - $first_letter * (26 * 26);
206 228 100       292 $first_letter = ( $first_letter ) ? $lookup_list->[$first_letter - 1] : '';
207             ###LogSD $phone->talk( level => 'debug', message =>[
208             ###LogSD "First letter is: $first_letter", "New column is: $column" ] );
209 228         191 my $second_letter = int( $column / 26 );
210 228         168 $column = $column - $second_letter * 26;
211 228 50       434 $second_letter =
    100          
212             ( $second_letter ) ? $lookup_list->[$second_letter - 1] :
213             ( $first_letter ne '' ) ? 'A' : '' ;
214             ###LogSD $phone->talk( level => 'debug', message =>[
215             ###LogSD "Second letter is: $second_letter", "New column is: $column" ] );
216 228         309 my $third_letter = $lookup_list->[$column];
217 228         358 $column = $first_letter . $second_letter . $third_letter;
218             }
219             }
220             ###LogSD $phone->talk( level => 'debug', message =>[
221             ###LogSD "Column letters are: $column" ] );
222              
223             # Parse row
224 228 50       287 if( !defined $row ){
225 0         0 $row = '';
226 0         0 push @$error_list, 'missing row';
227             }else{
228 228 50       480 if( $row > 1048576 ){
    50          
229 0         0 push @$error_list, 'row too large';
230 0         0 $row = '';
231             }elsif( $row < 1 ){
232 0         0 push @$error_list, 'row too small';
233 0         0 $row = '';
234             }
235             }
236 228 50       316 $self->set_error(
237             "Failures in build_cell_label include: " . join( ' - ', @$error_list )
238             ) if $error_list;
239             ###LogSD $phone->talk( level => 'debug', message =>[
240             ###LogSD "Row is: $row" ] );
241              
242             # Concatenate column and row
243 228         243 my $cell_label = "$column$row";
244             ###LogSD $phone->talk( level => 'debug', message =>[
245             ###LogSD "Cell label is: $cell_label" ] );
246 228         437 return $cell_label;
247             }
248              
249             #########1 Phinish 3#########4#########5#########6#########7#########8#########9
250              
251 23     23   114 no Moose::Role;
  23         33  
  23         190  
252             1;
253              
254             #########1 Documentation 3#########4#########5#########6#########7#########8#########9
255             __END__
256              
257             =head1 NAME
258              
259             Spreadsheet::Reader::ExcelXML::CellToColumnRow - Translate Excel cell IDs to column row
260              
261             =head1 SYNOPSIS
262              
263             #!/usr/bin/env perl
264             package MyPackage;
265             use Moose;
266             with 'Spreadsheet::Reader::ExcelXML::CellToColumnRow';
267              
268             sub set_error{} # Required method of this role
269             sub error{ print "Missing the column or row\n" } # Required method of this role
270             sub counting_from_zero{ 0 } # Required method of this role
271              
272             sub my_method{
273             my ( $self, $cell ) = @_;
274             my ($column, $row ) = $self->parse_column_row( $cell );
275             print $self->error if( !defined $column or !defined $row );
276             return ($column, $row );
277             }
278              
279             package main;
280              
281             my $parser = MyPackage->new;
282             print '(' . join( ', ', $parser->my_method( 'B2' ) ) . ")'\n";
283              
284             ###########################
285             # SYNOPSIS Screen Output
286             # 01: (2, 2)
287             ###########################
288              
289             =head1 DESCRIPTION
290              
291             This documentation is written to explain ways to use this module when writing your
292             own excel parser. To use the general package for excel parsing out of the box please
293             review the documentation for L<Workbooks|Spreadsheet::Reader::ExcelXML>,
294             L<Worksheets|Spreadsheet::Reader::ExcelXML::Worksheet>, and
295             L<Cells|Spreadsheet::Reader::ExcelXML::Cell>
296              
297             This is a L<Moose Role|Moose::Manual::Roles>. The role provides methods to convert back
298             and forth betwee Excel Cell ID and ($column $row) lists. This role also provides a layer
299             of abstraction so that it is possible to implement
300             L<around|Moose::Manual::MethodModifiers/Around modifiers> modifiers on these methods so
301             that the data provided by the user can be in the user context and the method implementation
302             will still be in the Excel context. For example this package uses this abstraction to allow
303             the user to call or receive row column numbers in either the
304             L<count-from-zero|Spreadsheet::Reader::ExcelXML/count_from_zero> context used by
305             L<Spreadsheet::ParseExcel> or the count-from-one context used by Excel. It is important
306             to note that column letters do not equal digits in a modern 26 position numeral system
307             since the excel implementation is effectivly zeroless.
308              
309             The module counts from 1 (the excel convention) without implementation of around modifiers.
310             Meaning that cell ID 'A1' is equal to (1, 1) and column row (3, 2) is equal to the cell ID
311             'C2'.
312              
313             =head2 Requires
314              
315             These are the methods required by this role and their default provider. All
316             methods are imported straight across with no re-naming.
317              
318             =over
319              
320             L<Spreadsheet::Reader::ExcelXML::Error/set_error>
321              
322             L<Spreadsheet::Reader::ExcelXML/count_from_zero>
323              
324             =back
325              
326             =head2 Methods
327              
328             Methods are object methods (not functional methods)
329              
330             =head3 parse_column_row( $excel_cell_id )
331              
332             =over
333              
334             B<Definition:> This is the way to turn an alpha numeric Excel cell ID into column and row
335             integers. This method uses a count from 1 methodology. Since this method is actually just
336             a layer of abstraction above the real method '_parse_column_row' for the calculation you can
337             wrap it in an L<around|Moose::Manual::MethodModifiers/Around modifiers> block to modify the
338             output to the desired user format without affecting other parts of the package that need the
339             unfiltered conversion. If you want both then use the following call when unfiltered results
340             are required;
341              
342             $self->_parse_column_row( $excel_cell_id )
343              
344             B<Accepts:> $excel_cell_id
345              
346             B<Returns:> ( $column_number, $row_number )
347              
348             =back
349              
350             =head3 build_cell_label( $column, $row, )
351              
352             =over
353              
354             B<Definition:> This is the way to turn a (column, row) pair into an Excel Cell ID. The
355             underlying method uses a count from 1 methodology. Since this method is actually just
356             a layer of abstraction above the real method for the calculation you can wrap it in an
357             L<around|Moose::Manual::MethodModifiers/Around modifiers> block to modify the input from
358             the implemented user format to the count from one methodology without affecting other parts
359             of the package that need the unfiltered conversion. If you want both then use the following
360             call when unfiltered results are required;
361              
362             $self->_build_cell_label( $column, $row )
363              
364             B<Accepts:> ($column, $row) I<in that order>
365              
366             B<Returns:> ( $excel_cell_id ) I<qr/[A-Z]{1,3}\d+/>
367              
368             =back
369              
370             =head3 get_excel_position( $integer )
371              
372             =over
373              
374             B<Definition:> This will process a position integer and check the method
375             L<counting_from_zero|Spreadsheet::Reader::ExcelXML/count_from_zero> to
376             see whether to pass the value through straight accross or decrement it by 1.
377             If the end user is using count from zero 'on' then the value is increased
378             to arrive in the excel paradigm. (always counts from 1)
379              
380             B<Accepts:> an integer
381              
382             B<Returns:> an integer
383              
384             =back
385              
386             =head3 get_used_position( $integer )
387              
388             =over
389              
390             B<Definition:> This will process a position integer and check the method
391             L<counting_from_zero|Spreadsheet::Reader::ExcelXML/count_from_zero> to
392             see whether to pass the value through straight accross or decrease it by 1.
393             If the end user is using count from zero 'on' then the value is decreased
394             to arrived in the end users paradigm.
395              
396             B<Accepts:> an integer
397              
398             B<Returns:> an integer
399              
400             =back
401              
402             =head1 SUPPORT
403              
404             =over
405              
406             L<github Spreadsheet::Reader::ExcelXML/issues
407             |https://github.com/jandrew/p5-spreadsheet-reader-excelxml/issues>
408              
409             =back
410              
411             =head1 TODO
412              
413             =over
414              
415             B<1.> Nothing L<yet|/SUPPORT>
416              
417             =back
418              
419             =head1 AUTHOR
420              
421             =over
422              
423             Jed Lund
424              
425             jandrew@cpan.org
426              
427             =back
428              
429             =head1 COPYRIGHT
430              
431             This program is free software; you can redistribute
432             it and/or modify it under the same terms as Perl itself.
433              
434             The full text of the license can be found in the
435             LICENSE file included with this module.
436              
437             This software is copyrighted (c) 2016 by Jed Lund
438              
439             =head1 DEPENDENCIES
440              
441             =over
442              
443             L<Spreadsheet::Reader::ExcelXML> - the package
444              
445             =back
446              
447             =head1 SEE ALSO
448              
449             =over
450              
451             L<Spreadsheet::Read> - generic Spreadsheet reader
452              
453             L<Spreadsheet::ParseExcel> - Excel binary version 2003 and earlier (.xls files)
454              
455             L<Spreadsheet::XLSX> - Excel version 2007 and later
456              
457             L<Spreadsheet::ParseXLSX> - Excel version 2007 and later
458              
459             L<Log::Shiras|https://github.com/jandrew/Log-Shiras>
460              
461             =over
462              
463             All lines in this package that use Log::Shiras are commented out
464              
465             =back
466              
467             =back
468              
469             =cut
470              
471             #########1#########2 main pod documentation end 5#########6#########7#########8#########9