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-2019, 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 1056     1056   28586 use 5.008002;
  1056         4044  
18 1056     1056   5842 use strict;
  1056         2258  
  1056         21805  
19 1056     1056   5262 use Exporter;
  1056         2115  
  1056         36816  
20 1056     1056   6143 use warnings;
  1056         2496  
  1056         40026  
21 1056     1056   464322 use autouse 'Date::Calc' => qw(Delta_DHMS Decode_Date_EU Decode_Date_US);
  1056         783451  
  1056         6366  
22 1056     1056   106789 use autouse 'Date::Manip' => qw(ParseDate Date_Init);
  1056         2375  
  1056         3986  
23              
24             our $VERSION = '1.03';
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 7275     7275 1 131243 my $row = $_[0] + 1; # Change from 0-indexed to 1 indexed.
66 7275         10847 my $col = $_[1];
67 7275 100       15166 my $row_abs = $_[2] ? '$' : '';
68 7275 100       13750 my $col_abs = $_[3] ? '$' : '';
69              
70              
71 7275         14616 my $col_str = xl_col_to_name( $col, $col_abs );
72              
73 7275         21193 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 2855     2855 1 143627 my $cell = shift;
89              
90 2855 100       6219 return ( 0, 0, 0, 0 ) unless $cell;
91              
92 2854         9971 $cell =~ /(\$?)([A-Z]{1,3})(\$?)(\d+)/;
93              
94 2854 100       8103 my $col_abs = $1 eq "" ? 0 : 1;
95 2854         5182 my $col = $2;
96 2854 100       5967 my $row_abs = $3 eq "" ? 0 : 1;
97 2854         4922 my $row = $4;
98              
99             # Convert base26 column string to number
100             # All your Base are belong to us.
101 2854         6734 my @chars = split //, $col;
102 2854         4435 my $expn = 0;
103 2854         4338 $col = 0;
104              
105 2854         6447 while ( @chars ) {
106 3900         6497 my $char = pop( @chars ); # LS char first
107 3900         7600 $col += ( ord( $char ) - ord( 'A' ) + 1 ) * ( 26**$expn );
108 3900         7886 $expn++;
109             }
110              
111             # Convert 1-index to zero-index
112 2854         5444 $row--;
113 2854         4209 $col--;
114              
115 2854         10421 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 8049     8049 1 125421 my $col = $_[0];
126 8049 100       15268 my $col_abs = $_[1] ? '$' : '';
127 8049         12583 my $col_str = '';
128              
129             # Change from 0-indexed to 1 indexed.
130 8049         11900 $col++;
131              
132 8049         16563 while ( $col ) {
133              
134             # Set remainder from 1 .. 26
135 10154   100     22559 my $remainder = $col % 26 || 26;
136              
137             # Convert the $remainder to a character. C-ishly.
138 10154         20355 my $col_letter = chr( ord( 'A' ) + $remainder - 1 );
139              
140             # Accumulate the column letters, right to left.
141 10154         18748 $col_str = $col_letter . $col_str;
142              
143             # Get the next order of magnitude.
144 10154         27515 $col = int( ( $col - 1 ) / 26 );
145             }
146              
147 8049         19423 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 5600 my ( $row_1, $row_2, $col_1, $col_2 ) = @_[ 0 .. 3 ];
158 263         672 my ( $row_abs_1, $row_abs_2, $col_abs_1, $col_abs_2 ) = @_[ 4 .. 7 ];
159              
160 263         661 my $range1 = xl_rowcol_to_cell( $row_1, $col_1, $row_abs_1, $col_abs_1 );
161 263         775 my $range2 = xl_rowcol_to_cell( $row_2, $col_2, $row_abs_2, $col_abs_2 );
162              
163 263         1094 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 3201 my ( $sheetname, $row_1, $row_2, $col_1, $col_2 ) = @_;
174              
175 40         111 $sheetname = quote_sheetname( $sheetname );
176              
177 40         103 my $range = xl_range( $row_1, $row_2, $col_1, $col_2, 1, 1, 1, 1 );
178              
179 40         138 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 279 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     659 if ( $sheetname =~ /\W/ && $sheetname !~ /^'/ ) {
197             # Double quote any single quotes.
198 26         56 $sheetname =~ s/'/''/g;
199 26         66 $sheetname = q(') . $sheetname . q(');
200             }
201              
202 148         348 return $sheetname;
203             }
204              
205              
206             ###############################################################################
207             #
208             # xl_inc_row($string)
209             #
210             sub xl_inc_row {
211              
212 4     4 1 1848 my $cell = shift;
213 4         11 my ( $row, $col, $row_abs, $col_abs ) = xl_cell_to_rowcol( $cell );
214              
215 4         11 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 1988 my $cell = shift;
231 4         11 my ( $row, $col, $row_abs, $col_abs ) = xl_cell_to_rowcol( $cell );
232              
233 4         10 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 1979 my $cell = shift;
249 4         11 my ( $row, $col, $row_abs, $col_abs ) = xl_cell_to_rowcol( $cell );
250              
251 4         11 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 2036 my $cell = shift;
262 4         13 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 7197 my $time = shift;
305              
306 14 100       119 if ( $time =~ /(\d+):(\d\d):?((?:\d\d)(?:\.\d+)?)?(?:\s+)?(am|pm)?/i ) {
307              
308 13         32 my $hours = $1;
309 13         25 my $minutes = $2;
310 13   100     45 my $seconds = $3 || 0;
311 13   100     62 my $meridian = lc( $4 || '' );
312              
313             # Normalise midnight and midday
314 13 100 100     53 $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       30 $hours += 12 if $meridian eq 'pm';
318              
319             # Calculate the time as a fraction of 24 hours in seconds
320 13         69 return ( $hours * 3600 + $minutes * 60 + $seconds ) / ( 24 * 60 * 60 );
321              
322             }
323             else {
324 1         3 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 1947 my $date = $_[0] || 0;
420              
421 4 100       13 if ( $date < 1462 ) {
422              
423             # before 1904
424 1         3 $date = 0;
425             }
426             else {
427 3         6 $date -= 1462;
428             }
429              
430 4         10 return $date;
431             }
432              
433              
434             1;
435              
436              
437             __END__