File Coverage

blib/lib/Excel/Writer/XLSX/Utility.pm
Criterion Covered Total %
statement 91 128 71.0
branch 22 42 52.3
condition 14 24 58.3
subroutine 18 23 78.2
pod 16 17 94.1
total 161 234 68.8


line stmt bran cond sub pod time code
1             package Excel::Writer::XLSX::Utility;
2              
3             ###############################################################################
4             #
5             # Utility - Helper functions for Excel::Writer::XLSX.
6             #
7             #
8             # Used in conjunction with Excel::Writer::XLSX
9             #
10             # Copyright 2000-2020, John McNamara, jmcnamara@cpan.org
11             #
12             # Documentation after __END__
13             #
14              
15             # perltidy with the following options: -mbl=2 -pt=0 -nola
16              
17 1097     1097   28131 use 5.008002;
  1097         3819  
18 1097     1097   5821 use strict;
  1097         2115  
  1097         22326  
19 1097     1097   5287 use Exporter;
  1097         2224  
  1097         33959  
20 1097     1097   5685 use warnings;
  1097         2253  
  1097         40774  
21 1097     1097   459060 use autouse 'Date::Calc' => qw(Delta_DHMS Decode_Date_EU Decode_Date_US);
  1097         747061  
  1097         6024  
22 1097     1097   99034 use autouse 'Date::Manip' => qw(ParseDate Date_Init);
  1097         2199  
  1097         3816  
23              
24             our $VERSION = '1.07';
25              
26             # Row and column functions
27             my @rowcol = qw(
28             xl_rowcol_to_cell
29             xl_cell_to_rowcol
30             xl_col_to_name
31             xl_range
32             xl_range_formula
33             xl_inc_row
34             xl_dec_row
35             xl_inc_col
36             xl_dec_col
37             );
38              
39             # Date and Time functions
40             my @dates = qw(
41             xl_date_list
42             xl_date_1904
43             xl_parse_time
44             xl_parse_date
45             xl_parse_date_init
46             xl_decode_date_EU
47             xl_decode_date_US
48             );
49              
50             our @ISA = qw(Exporter);
51             our @EXPORT_OK = ();
52             our @EXPORT = ( @rowcol, @dates, 'quote_sheetname' );
53             our %EXPORT_TAGS = (
54             rowcol => \@rowcol,
55             dates => \@dates
56             );
57              
58              
59             ###############################################################################
60             #
61             # xl_rowcol_to_cell($row, $col, $row_absolute, $col_absolute)
62             #
63             sub xl_rowcol_to_cell {
64              
65 7347     7347 1 131219 my $row = $_[0] + 1; # Change from 0-indexed to 1 indexed.
66 7347         9633 my $col = $_[1];
67 7347 100       13463 my $row_abs = $_[2] ? '$' : '';
68 7347 100       11919 my $col_abs = $_[3] ? '$' : '';
69              
70              
71 7347         12744 my $col_str = xl_col_to_name( $col, $col_abs );
72              
73 7347         18632 return $col_str . $row_abs . $row;
74             }
75              
76              
77             ###############################################################################
78             #
79             # xl_cell_to_rowcol($string)
80             #
81             # Returns: ($row, $col, $row_absolute, $col_absolute)
82             #
83             # The $row_absolute and $col_absolute parameters aren't documented because they
84             # mainly used internally and aren't very useful to the user.
85             #
86             sub xl_cell_to_rowcol {
87              
88 3011     3011 1 116368 my $cell = shift;
89              
90 3011 100       5991 return ( 0, 0, 0, 0 ) unless $cell;
91              
92 3010         9324 $cell =~ /(\$?)([A-Z]{1,3})(\$?)(\d+)/;
93              
94 3010 100       7966 my $col_abs = $1 eq "" ? 0 : 1;
95 3010         4908 my $col = $2;
96 3010 100       7331 my $row_abs = $3 eq "" ? 0 : 1;
97 3010         4530 my $row = $4;
98              
99             # Convert base26 column string to number
100             # All your Base are belong to us.
101 3010         6264 my @chars = split //, $col;
102 3010         4431 my $expn = 0;
103 3010         4337 $col = 0;
104              
105 3010         6128 while ( @chars ) {
106 4056         5981 my $char = pop( @chars ); # LS char first
107 4056         6905 $col += ( ord( $char ) - ord( 'A' ) + 1 ) * ( 26**$expn );
108 4056         7582 $expn++;
109             }
110              
111             # Convert 1-index to zero-index
112 3010         5370 $row--;
113 3010         3955 $col--;
114              
115 3010         9396 return $row, $col, $row_abs, $col_abs;
116             }
117              
118              
119             ###############################################################################
120             #
121             # xl_col_to_name($col, $col_absolute)
122             #
123             sub xl_col_to_name {
124              
125 8121     8121 1 103070 my $col = $_[0];
126 8121 100       13650 my $col_abs = $_[1] ? '$' : '';
127 8121         10486 my $col_str = '';
128              
129             # Change from 0-indexed to 1 indexed.
130 8121         10130 $col++;
131              
132 8121         14911 while ( $col ) {
133              
134             # Set remainder from 1 .. 26
135 10226   100     19277 my $remainder = $col % 26 || 26;
136              
137             # Convert the $remainder to a character. C-ishly.
138 10226         17979 my $col_letter = chr( ord( 'A' ) + $remainder - 1 );
139              
140             # Accumulate the column letters, right to left.
141 10226         16326 $col_str = $col_letter . $col_str;
142              
143             # Get the next order of magnitude.
144 10226         24590 $col = int( ( $col - 1 ) / 26 );
145             }
146              
147 8121         16917 return $col_abs . $col_str;
148             }
149              
150              
151             ###############################################################################
152             #
153             # xl_range($row_1, $row_2, $col_1, $col_2, $row_abs_1, $row_abs_2, $col_abs_1, $col_abs_2)
154             #
155             sub xl_range {
156              
157 263     263 1 4599 my ( $row_1, $row_2, $col_1, $col_2 ) = @_[ 0 .. 3 ];
158 263         642 my ( $row_abs_1, $row_abs_2, $col_abs_1, $col_abs_2 ) = @_[ 4 .. 7 ];
159              
160 263         646 my $range1 = xl_rowcol_to_cell( $row_1, $col_1, $row_abs_1, $col_abs_1 );
161 263         670 my $range2 = xl_rowcol_to_cell( $row_2, $col_2, $row_abs_2, $col_abs_2 );
162              
163 263         928 return $range1 . ':' . $range2;
164             }
165              
166              
167             ###############################################################################
168             #
169             # xl_range_formula($sheetname, $row_1, $row_2, $col_1, $col_2)
170             #
171             sub xl_range_formula {
172              
173 40     40 1 3185 my ( $sheetname, $row_1, $row_2, $col_1, $col_2 ) = @_;
174              
175 40         103 $sheetname = quote_sheetname( $sheetname );
176              
177 40         118 my $range = xl_range( $row_1, $row_2, $col_1, $col_2, 1, 1, 1, 1 );
178              
179 40         134 return '=' . $sheetname . '!' . $range
180             }
181              
182              
183             ###############################################################################
184             #
185             # quote_sheetname()
186             #
187             # Sheetnames used in references should be quoted if they contain any spaces,
188             # special characters or if they look like something that isn't a sheet name.
189             #
190             sub quote_sheetname {
191              
192 148     148 0 278 my $sheetname = $_[0];
193              
194             # Use Excel's conventions and quote the sheet name if it contains any
195             # non-word character or if it isn't already quoted.
196 148 100 100     645 if ( $sheetname =~ /\W/ && $sheetname !~ /^'/ ) {
197             # Double quote any single quotes.
198 26         59 $sheetname =~ s/'/''/g;
199 26         68 $sheetname = q(') . $sheetname . q(');
200             }
201              
202 148         352 return $sheetname;
203             }
204              
205              
206             ###############################################################################
207             #
208             # xl_inc_row($string)
209             #
210             sub xl_inc_row {
211              
212 4     4 1 1732 my $cell = shift;
213 4         10 my ( $row, $col, $row_abs, $col_abs ) = xl_cell_to_rowcol( $cell );
214              
215 4         9 return xl_rowcol_to_cell( ++$row, $col, $row_abs, $col_abs );
216             }
217              
218              
219             ###############################################################################
220             #
221             # xl_dec_row($string)
222             #
223             # Decrements the row number of an Excel cell reference in A1 notation.
224             # For example C4 to C3
225             #
226             # Returns: a cell reference string.
227             #
228             sub xl_dec_row {
229              
230 4     4 1 1652 my $cell = shift;
231 4         10 my ( $row, $col, $row_abs, $col_abs ) = xl_cell_to_rowcol( $cell );
232              
233 4         11 return xl_rowcol_to_cell( --$row, $col, $row_abs, $col_abs );
234             }
235              
236              
237             ###############################################################################
238             #
239             # xl_inc_col($string)
240             #
241             # Increments the column number of an Excel cell reference in A1 notation.
242             # For example C3 to D3
243             #
244             # Returns: a cell reference string.
245             #
246             sub xl_inc_col {
247              
248 4     4 1 2209 my $cell = shift;
249 4         10 my ( $row, $col, $row_abs, $col_abs ) = xl_cell_to_rowcol( $cell );
250              
251 4         10 return xl_rowcol_to_cell( $row, ++$col, $row_abs, $col_abs );
252             }
253              
254              
255             ###############################################################################
256             #
257             # xl_dec_col($string)
258             #
259             sub xl_dec_col {
260              
261 4     4 1 1640 my $cell = shift;
262 4         10 my ( $row, $col, $row_abs, $col_abs ) = xl_cell_to_rowcol( $cell );
263              
264 4         10 return xl_rowcol_to_cell( $row, --$col, $row_abs, $col_abs );
265             }
266              
267              
268             ###############################################################################
269             #
270             # xl_date_list($years, $months, $days, $hours, $minutes, $seconds)
271             #
272             sub xl_date_list {
273              
274 0 0   0 1 0 return undef unless @_;
275              
276 0         0 my $years = $_[0];
277 0   0     0 my $months = $_[1] || 1;
278 0   0     0 my $days = $_[2] || 1;
279 0   0     0 my $hours = $_[3] || 0;
280 0   0     0 my $minutes = $_[4] || 0;
281 0   0     0 my $seconds = $_[5] || 0;
282              
283 0         0 my @date = ( $years, $months, $days, $hours, $minutes, $seconds );
284 0         0 my @epoch = ( 1899, 12, 31, 0, 0, 0 );
285              
286 0         0 ( $days, $hours, $minutes, $seconds ) = Delta_DHMS( @epoch, @date );
287              
288 0         0 my $date =
289             $days + ( $hours * 3600 + $minutes * 60 + $seconds ) / ( 24 * 60 * 60 );
290              
291             # Add a day for Excel's missing leap day in 1900
292 0 0       0 $date++ if ( $date > 59 );
293              
294 0         0 return $date;
295             }
296              
297              
298             ###############################################################################
299             #
300             # xl_parse_time($string)
301             #
302             sub xl_parse_time {
303              
304 14     14 1 6109 my $time = shift;
305              
306 14 100       107 if ( $time =~ /(\d+):(\d\d):?((?:\d\d)(?:\.\d+)?)?(?:\s+)?(am|pm)?/i ) {
307              
308 13         33 my $hours = $1;
309 13         19 my $minutes = $2;
310 13   100     34 my $seconds = $3 || 0;
311 13   100     53 my $meridian = lc( $4 || '' );
312              
313             # Normalise midnight and midday
314 13 100 100     40 $hours = 0 if ( $hours == 12 && $meridian ne '' );
315              
316             # Add 12 hours to the pm times. Note: 12.00 pm has been set to 0.00.
317 13 100       27 $hours += 12 if $meridian eq 'pm';
318              
319             # Calculate the time as a fraction of 24 hours in seconds
320 13         58 return ( $hours * 3600 + $minutes * 60 + $seconds ) / ( 24 * 60 * 60 );
321              
322             }
323             else {
324 1         2 return undef; # Not a valid time string
325             }
326             }
327              
328              
329             ###############################################################################
330             #
331             # xl_parse_date($string)
332             #
333             sub xl_parse_date {
334              
335 0     0 1 0 my $date = ParseDate( $_[0] );
336              
337             # Unpack the return value from ParseDate()
338 0         0 my ( $years, $months, $days, $hours, undef, $minutes, undef, $seconds ) =
339             unpack( "A4 A2 A2 A2 C A2 C A2",
340             $date );
341              
342             # Convert to Excel date
343 0         0 return xl_date_list( $years, $months, $days, $hours, $minutes, $seconds );
344             }
345              
346              
347             ###############################################################################
348             #
349             # xl_parse_date_init("variable=value", ...)
350             #
351             sub xl_parse_date_init {
352              
353 0     0 1 0 Date_Init( @_ ); # How lazy is that.
354             }
355              
356              
357             ###############################################################################
358             #
359             # xl_decode_date_EU($string)
360             #
361             sub xl_decode_date_EU {
362              
363 0 0   0 1 0 return undef unless @_;
364              
365 0         0 my $date = shift;
366 0         0 my @date;
367 0         0 my $time = 0;
368              
369             # Remove and decode the time portion of the string
370 0 0       0 if ( $date =~ s/(\d+:\d\d:?(\d\d(\.\d+)?)?(\s+)?(am|pm)?)//i ) {
371 0         0 $time = xl_parse_time( $1 );
372             }
373              
374             # Return if the string is now blank, i.e. it contained a time only.
375 0 0       0 return $time if $date =~ /^\s*$/;
376              
377             # Decode the date portion of the string
378 0         0 @date = Decode_Date_EU( $date );
379 0 0       0 return undef unless @date;
380              
381 0         0 return xl_date_list( @date ) + $time;
382             }
383              
384              
385             ###############################################################################
386             #
387             # xl_decode_date_US($string)
388             #
389             sub xl_decode_date_US {
390              
391 0 0   0 1 0 return undef unless @_;
392              
393 0         0 my $date = shift;
394 0         0 my @date;
395 0         0 my $time = 0;
396              
397             # Remove and decode the time portion of the string
398 0 0       0 if ( $date =~ s/(\d+:\d\d:?(\d\d(\.\d+)?)?(\s+)?(am|pm)?)//i ) {
399 0         0 $time = xl_parse_time( $1 );
400             }
401              
402             # Return if the string is now blank, i.e. it contained a time only.
403 0 0       0 return $time if $date =~ /^\s*$/;
404              
405             # Decode the date portion of the string
406 0         0 @date = Decode_Date_US( $date );
407 0 0       0 return undef unless @date;
408              
409 0         0 return xl_date_list( @date ) + $time;
410             }
411              
412              
413             ###############################################################################
414             #
415             # xl_decode_date_US($string)
416             #
417             sub xl_date_1904 {
418              
419 4   100 4 1 1581 my $date = $_[0] || 0;
420              
421 4 100       10 if ( $date < 1462 ) {
422              
423             # before 1904
424 1         2 $date = 0;
425             }
426             else {
427 3         4 $date -= 1462;
428             }
429              
430 4         8 return $date;
431             }
432              
433              
434             1;
435              
436              
437             __END__