File Coverage

blib/lib/Excel/Writer/XLSX/Worksheet.pm
Criterion Covered Total %
statement 3456 3678 93.9
branch 1459 1748 83.4
condition 579 699 82.8
subroutine 226 234 96.5
pod 0 84 0.0
total 5720 6443 88.7


sub elements. element.
line stmt bran cond sub pod time code
1             package Excel::Writer::XLSX::Worksheet;
2              
3             ###############################################################################
4             #
5             # Worksheet - A class for writing Excel Worksheets.
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 1041     1041   28424 use 5.008002;
  1041         4232  
18 1041     1041   6044 use strict;
  1041         2434  
  1041         21036  
19 1041     1041   5135 use warnings;
  1041         2305  
  1041         29704  
20 1041     1041   5552 use Carp;
  1041         2359  
  1041         75534  
21 1041     1041   8538 use File::Temp 'tempfile';
  1041         23463  
  1041         56562  
22 1041     1041   6871 use List::Util qw(max min);
  1041         2595  
  1041         114440  
23 1041     1041   525706 use Excel::Writer::XLSX::Format;
  1041         2819  
  1041         52191  
24 1041     1041   551843 use Excel::Writer::XLSX::Drawing;
  1041         3241  
  1041         52980  
25 1041     1041   8015 use Excel::Writer::XLSX::Package::XMLwriter;
  1041         2330  
  1041         42544  
26 1041         1719779 use Excel::Writer::XLSX::Utility qw(xl_cell_to_rowcol
27             xl_rowcol_to_cell
28             xl_col_to_name
29             xl_range
30 1041     1041   559112 quote_sheetname);
  1041         3092  
31              
32             our @ISA = qw(Excel::Writer::XLSX::Package::XMLwriter);
33             our $VERSION = '1.03';
34              
35              
36             ###############################################################################
37             #
38             # Public and private API methods.
39             #
40             ###############################################################################
41              
42              
43             ###############################################################################
44             #
45             # new()
46             #
47             # Constructor.
48             #
49             sub new {
50              
51 1332     1332 0 258235 my $class = shift;
52 1332         3164 my $fh = shift;
53 1332         6941 my $self = Excel::Writer::XLSX::Package::XMLwriter->new( $fh );
54 1332         3366 my $rowmax = 1_048_576;
55 1332         3051 my $colmax = 16_384;
56 1332         2800 my $strmax = 32767;
57              
58 1332         4566 $self->{_name} = $_[0];
59 1332         3347 $self->{_index} = $_[1];
60 1332         3268 $self->{_activesheet} = $_[2];
61 1332         2976 $self->{_firstsheet} = $_[3];
62 1332         3020 $self->{_str_total} = $_[4];
63 1332         2915 $self->{_str_unique} = $_[5];
64 1332         4160 $self->{_str_table} = $_[6];
65 1332         3402 $self->{_date_1904} = $_[7];
66 1332         3113 $self->{_palette} = $_[8];
67 1332   100     7334 $self->{_optimization} = $_[9] || 0;
68 1332         3344 $self->{_tempdir} = $_[10];
69 1332         3088 $self->{_excel2003_style} = $_[11];
70 1332         2996 $self->{_default_url_format} = $_[12];
71 1332   100     4891 $self->{_max_url_length} = $_[13] || 2079;
72              
73 1332         5123 $self->{_ext_sheets} = [];
74 1332         3310 $self->{_fileclosed} = 0;
75 1332         3341 $self->{_excel_version} = 2007;
76              
77 1332         3127 $self->{_xls_rowmax} = $rowmax;
78 1332         3098 $self->{_xls_colmax} = $colmax;
79 1332         3121 $self->{_xls_strmax} = $strmax;
80 1332         3195 $self->{_dim_rowmin} = undef;
81 1332         3058 $self->{_dim_rowmax} = undef;
82 1332         3228 $self->{_dim_colmin} = undef;
83 1332         3193 $self->{_dim_colmax} = undef;
84              
85 1332         3467 $self->{_colinfo} = {};
86 1332         3521 $self->{_selections} = [];
87 1332         3291 $self->{_hidden} = 0;
88 1332         3169 $self->{_active} = 0;
89 1332         3032 $self->{_tab_color} = 0;
90              
91 1332         3156 $self->{_panes} = [];
92 1332         5251 $self->{_active_pane} = 3;
93 1332         3032 $self->{_selected} = 0;
94 1332         2807 $self->{_hide_row_col_headers} = 0;
95              
96 1332         2986 $self->{_page_setup_changed} = 0;
97 1332         2934 $self->{_paper_size} = 0;
98 1332         3394 $self->{_orientation} = 1;
99              
100 1332         3049 $self->{_print_options_changed} = 0;
101 1332         2994 $self->{_hcenter} = 0;
102 1332         2933 $self->{_vcenter} = 0;
103 1332         2888 $self->{_print_gridlines} = 0;
104 1332         2956 $self->{_screen_gridlines} = 1;
105 1332         2901 $self->{_print_headers} = 0;
106              
107 1332         2987 $self->{_header_footer_changed} = 0;
108 1332         3245 $self->{_header} = '';
109 1332         3136 $self->{_footer} = '';
110 1332         2941 $self->{_header_footer_aligns} = 1;
111 1332         3062 $self->{_header_footer_scales} = 1;
112 1332         3176 $self->{_header_images} = [];
113 1332         3187 $self->{_footer_images} = [];
114              
115 1332         3308 $self->{_margin_left} = 0.7;
116 1332         3078 $self->{_margin_right} = 0.7;
117 1332         3230 $self->{_margin_top} = 0.75;
118 1332         3163 $self->{_margin_bottom} = 0.75;
119 1332         3429 $self->{_margin_header} = 0.3;
120 1332         2974 $self->{_margin_footer} = 0.3;
121              
122 1332         3099 $self->{_repeat_rows} = '';
123 1332         3107 $self->{_repeat_cols} = '';
124 1332         3232 $self->{_print_area} = '';
125              
126 1332         3030 $self->{_page_order} = 0;
127 1332         3090 $self->{_black_white} = 0;
128 1332         2976 $self->{_draft_quality} = 0;
129 1332         3094 $self->{_print_comments} = 0;
130 1332         8134 $self->{_page_start} = 0;
131              
132 1332         3270 $self->{_fit_page} = 0;
133 1332         2906 $self->{_fit_width} = 0;
134 1332         2964 $self->{_fit_height} = 0;
135              
136 1332         3018 $self->{_hbreaks} = [];
137 1332         3277 $self->{_vbreaks} = [];
138              
139 1332         3076 $self->{_protect} = 0;
140 1332         3113 $self->{_password} = undef;
141              
142 1332         3052 $self->{_set_cols} = {};
143 1332         3293 $self->{_set_rows} = {};
144              
145 1332         3046 $self->{_zoom} = 100;
146 1332         2999 $self->{_zoom_scale_normal} = 1;
147 1332         2986 $self->{_print_scale} = 100;
148 1332         2881 $self->{_right_to_left} = 0;
149 1332         2868 $self->{_show_zeros} = 1;
150 1332         2808 $self->{_leading_zeros} = 0;
151              
152 1332         2798 $self->{_outline_row_level} = 0;
153 1332         2891 $self->{_outline_col_level} = 0;
154 1332         2936 $self->{_outline_style} = 0;
155 1332         2870 $self->{_outline_below} = 1;
156 1332         2926 $self->{_outline_right} = 1;
157 1332         2961 $self->{_outline_on} = 1;
158 1332         2972 $self->{_outline_changed} = 0;
159              
160 1332         2945 $self->{_original_row_height} = 15;
161 1332         2868 $self->{_default_row_height} = 15;
162 1332         2862 $self->{_default_row_pixels} = 20;
163 1332         3007 $self->{_default_col_width} = 8.43;
164 1332         2843 $self->{_default_col_pixels} = 64;
165 1332         2939 $self->{_default_row_zeroed} = 0;
166              
167 1332         3177 $self->{_names} = {};
168              
169 1332         3581 $self->{_write_match} = [];
170              
171              
172 1332         3276 $self->{_table} = {};
173 1332         3286 $self->{_merge} = [];
174              
175 1332         3123 $self->{_has_vml} = 0;
176 1332         2930 $self->{_has_header_vml} = 0;
177 1332         2829 $self->{_has_comments} = 0;
178 1332         3071 $self->{_comments} = {};
179 1332         3060 $self->{_comments_array} = [];
180 1332         3461 $self->{_comments_author} = '';
181 1332         2925 $self->{_comments_visible} = 0;
182 1332         2911 $self->{_vml_shape_id} = 1024;
183 1332         3030 $self->{_buttons_array} = [];
184 1332         3217 $self->{_header_images_array} = [];
185              
186 1332         3177 $self->{_autofilter} = '';
187 1332         2909 $self->{_filter_on} = 0;
188 1332         2974 $self->{_filter_range} = [];
189 1332         3246 $self->{_filter_cols} = {};
190              
191 1332         3152 $self->{_col_sizes} = {};
192 1332         3165 $self->{_row_sizes} = {};
193 1332         3077 $self->{_col_formats} = {};
194 1332         3005 $self->{_col_size_changed} = 0;
195 1332         2951 $self->{_row_size_changed} = 0;
196              
197 1332         2907 $self->{_last_shape_id} = 1;
198 1332         2937 $self->{_rel_count} = 0;
199 1332         2909 $self->{_hlink_count} = 0;
200 1332         3261 $self->{_hlink_refs} = [];
201 1332         3259 $self->{_external_hyper_links} = [];
202 1332         3219 $self->{_external_drawing_links} = [];
203 1332         3235 $self->{_external_comment_links} = [];
204 1332         3067 $self->{_external_vml_links} = [];
205 1332         3230 $self->{_external_table_links} = [];
206 1332         3131 $self->{_drawing_links} = [];
207 1332         3260 $self->{_vml_drawing_links} = [];
208 1332         3091 $self->{_charts} = [];
209 1332         11997 $self->{_images} = [];
210 1332         4165 $self->{_tables} = [];
211 1332         3164 $self->{_sparklines} = [];
212 1332         3203 $self->{_shapes} = [];
213 1332         3026 $self->{_shape_hash} = {};
214 1332         2830 $self->{_has_shapes} = 0;
215 1332         2797 $self->{_drawing} = 0;
216 1332         2896 $self->{_drawing_rels} = {};
217 1332         2842 $self->{_drawing_rels_id} = 0;
218 1332         3004 $self->{_vml_drawing_rels} = {};
219 1332         3094 $self->{_vml_drawing_rels_id} = 0;
220              
221 1332         2979 $self->{_horizontal_dpi} = 0;
222 1332         2988 $self->{_vertical_dpi} = 0;
223              
224 1332         3219 $self->{_rstring} = '';
225 1332         2994 $self->{_previous_row} = 0;
226              
227 1332 100       5357 if ( $self->{_optimization} == 1 ) {
228 8         53 my $fh = tempfile( DIR => $self->{_tempdir} );
229 8         5996 binmode $fh, ':utf8';
230              
231 8         26 $self->{_cell_data_fh} = $fh;
232 8         30 $self->{_fh} = $fh;
233             }
234              
235 1332         3501 $self->{_validations} = [];
236 1332         3489 $self->{_cond_formats} = {};
237 1332         3426 $self->{_data_bars_2010} = [];
238 1332         3084 $self->{_use_data_bars_2010} = 0;
239 1332         3112 $self->{_dxf_priority} = 1;
240              
241 1332 100       4379 if ( $self->{_excel2003_style} ) {
242 8         21 $self->{_original_row_height} = 12.75;
243 8         21 $self->{_default_row_height} = 12.75;
244 8         17 $self->{_default_row_pixels} = 17;
245 8         15 $self->{_margin_left} = 0.75;
246 8         23 $self->{_margin_right} = 0.75;
247 8         21 $self->{_margin_top} = 1;
248 8         18 $self->{_margin_bottom} = 1;
249 8         19 $self->{_margin_header} = 0.5;
250 8         18 $self->{_margin_footer} = 0.5;
251 8         17 $self->{_header_footer_aligns} = 0;
252             }
253              
254 1332         3648 bless $self, $class;
255 1332         5928 return $self;
256             }
257              
258             ###############################################################################
259             #
260             # _set_xml_writer()
261             #
262             # Over-ridden to ensure that write_single_row() is called for the final row
263             # when optimisation mode is on.
264             #
265             sub _set_xml_writer {
266              
267 956     956   13881 my $self = shift;
268 956         2593 my $filename = shift;
269              
270 956 100       4404 if ( $self->{_optimization} == 1 ) {
271 8         26 $self->_write_single_row();
272             }
273              
274 956         8553 $self->SUPER::_set_xml_writer( $filename );
275             }
276              
277              
278             ###############################################################################
279             #
280             # _assemble_xml_file()
281             #
282             # Assemble and write the XML file.
283             #
284             sub _assemble_xml_file {
285              
286 993     993   2970 my $self = shift;
287              
288 993         10059 $self->xml_declaration();
289              
290             # Write the root worksheet element.
291 993         6323 $self->_write_worksheet();
292              
293             # Write the worksheet properties.
294 993         4834 $self->_write_sheet_pr();
295              
296             # Write the worksheet dimensions.
297 993         4670 $self->_write_dimension();
298              
299             # Write the sheet view properties.
300 993         4733 $self->_write_sheet_views();
301              
302             # Write the sheet format properties.
303 993         5129 $self->_write_sheet_format_pr();
304              
305             # Write the sheet column info.
306 993         4996 $self->_write_cols();
307              
308             # Write the worksheet data such as rows columns and cells.
309 993 100       4601 if ( $self->{_optimization} == 0 ) {
310 985         4545 $self->_write_sheet_data();
311             }
312             else {
313 8         33 $self->_write_optimized_sheet_data();
314             }
315              
316             # Write the sheetProtection element.
317 993         6137 $self->_write_sheet_protection();
318              
319             # Write the worksheet calculation properties.
320             #$self->_write_sheet_calc_pr();
321              
322             # Write the worksheet phonetic properties.
323 993 100       5937 if ($self->{_excel2003_style}) {
324 8         32 $self->_write_phonetic_pr();
325             }
326              
327             # Write the autoFilter element.
328 993         5472 $self->_write_auto_filter();
329              
330             # Write the mergeCells element.
331 993         4929 $self->_write_merge_cells();
332              
333             # Write the conditional formats.
334 993         4807 $self->_write_conditional_formats();
335              
336             # Write the dataValidations element.
337 993         5009 $self->_write_data_validations();
338              
339             # Write the hyperlink element.
340 993         5015 $self->_write_hyperlinks();
341              
342             # Write the printOptions element.
343 993         5099 $self->_write_print_options();
344              
345             # Write the worksheet page_margins.
346 993         4704 $self->_write_page_margins();
347              
348             # Write the worksheet page setup.
349 993         4899 $self->_write_page_setup();
350              
351             # Write the headerFooter element.
352 993         4632 $self->_write_header_footer();
353              
354             # Write the rowBreaks element.
355 993         4667 $self->_write_row_breaks();
356              
357             # Write the colBreaks element.
358 993         5006 $self->_write_col_breaks();
359              
360             # Write the drawing element.
361 993         4722 $self->_write_drawings();
362              
363             # Write the legacyDrawing element.
364 993         5094 $self->_write_legacy_drawing();
365              
366             # Write the legacyDrawingHF element.
367 993         4739 $self->_write_legacy_drawing_hf();
368              
369             # Write the tableParts element.
370 993         5020 $self->_write_table_parts();
371              
372             # Write the extLst elements.
373 993         4745 $self->_write_ext_list();
374              
375             # Close the worksheet tag.
376 993         5059 $self->xml_end_tag( 'worksheet' );
377              
378             # Close the XML writer filehandle.
379 993         8440 $self->xml_get_fh()->close();
380             }
381              
382              
383             ###############################################################################
384             #
385             # _close()
386             #
387             # Write the worksheet elements.
388             #
389             sub _close {
390              
391             # TODO. Unused. Remove after refactoring.
392 0     0   0 my $self = shift;
393 0         0 my $sheetnames = shift;
394 0         0 my $num_sheets = scalar @$sheetnames;
395             }
396              
397              
398             ###############################################################################
399             #
400             # get_name().
401             #
402             # Retrieve the worksheet name.
403             #
404             sub get_name {
405              
406 956     956 0 2673 my $self = shift;
407              
408 956         4722 return $self->{_name};
409             }
410              
411              
412             ###############################################################################
413             #
414             # select()
415             #
416             # Set this worksheet as a selected worksheet, i.e. the worksheet has its tab
417             # highlighted.
418             #
419             sub select {
420              
421 118     118 0 1944 my $self = shift;
422              
423 118         483 $self->{_hidden} = 0; # Selected worksheet can't be hidden.
424 118         330 $self->{_selected} = 1;
425             }
426              
427              
428             ###############################################################################
429             #
430             # activate()
431             #
432             # Set this worksheet as the active worksheet, i.e. the worksheet that is
433             # displayed when the workbook is opened. Also set it as selected.
434             #
435             sub activate {
436              
437 8     8 0 62 my $self = shift;
438              
439 8         21 $self->{_hidden} = 0; # Active worksheet can't be hidden.
440 8         25 $self->{_selected} = 1;
441 8         16 ${ $self->{_activesheet} } = $self->{_index};
  8         23  
442             }
443              
444              
445             ###############################################################################
446             #
447             # hide()
448             #
449             # Hide this worksheet.
450             #
451             sub hide {
452              
453 2     2 0 19 my $self = shift;
454              
455 2         5 $self->{_hidden} = 1;
456              
457             # A hidden worksheet shouldn't be active or selected.
458 2         5 $self->{_selected} = 0;
459 2         12 ${ $self->{_activesheet} } = 0;
  2         8  
460 2         5 ${ $self->{_firstsheet} } = 0;
  2         5  
461             }
462              
463              
464             ###############################################################################
465             #
466             # set_first_sheet()
467             #
468             # Set this worksheet as the first visible sheet. This is necessary
469             # when there are a large number of worksheets and the activated
470             # worksheet is not visible on the screen.
471             #
472             sub set_first_sheet {
473              
474 1     1 0 7 my $self = shift;
475              
476 1         3 $self->{_hidden} = 0; # Active worksheet can't be hidden.
477 1         2 ${ $self->{_firstsheet} } = $self->{_index};
  1         3  
478             }
479              
480              
481             ###############################################################################
482             #
483             # protect( $password )
484             #
485             # Set the worksheet protection flags to prevent modification of worksheet
486             # objects.
487             #
488             sub protect {
489              
490 27     27 0 485 my $self = shift;
491 27   100     93 my $password = shift || '';
492 27   100     68 my $options = shift || {};
493              
494 27 100       70 if ( $password ne '' ) {
495 6         20 $password = $self->_encode_password( $password );
496             }
497              
498             # Default values for objects that can be protected.
499 27         239 my %defaults = (
500             sheet => 1,
501             content => 0,
502             objects => 0,
503             scenarios => 0,
504             format_cells => 0,
505             format_columns => 0,
506             format_rows => 0,
507             insert_columns => 0,
508             insert_rows => 0,
509             insert_hyperlinks => 0,
510             delete_columns => 0,
511             delete_rows => 0,
512             select_locked_cells => 1,
513             sort => 0,
514             autofilter => 0,
515             pivot_tables => 0,
516             select_unlocked_cells => 1,
517             );
518              
519              
520             # Overwrite the defaults with user specified values.
521 27         43 for my $key ( keys %{$options} ) {
  27         92  
522              
523 60 50       117 if ( exists $defaults{$key} ) {
524 60         161 $defaults{$key} = $options->{$key};
525             }
526             else {
527 0         0 carp "Unknown protection object: $key\n";
528             }
529             }
530              
531             # Set the password after the user defined values.
532 27         65 $defaults{password} = $password;
533              
534 27         122 $self->{_protect} = \%defaults;
535             }
536              
537              
538             ###############################################################################
539             #
540             # _encode_password($password)
541             #
542             # Based on the algorithm provided by Daniel Rentz of OpenOffice.
543             #
544             sub _encode_password {
545              
546 1041     1041   10347 use integer;
  1041         2840  
  1041         8486  
547              
548 6     6   20 my $self = shift;
549 6         14 my $plaintext = $_[0];
550 6         15 my $password;
551             my $count;
552 6         0 my @chars;
553 6         10 my $i = 0;
554              
555 6         31 $count = @chars = split //, $plaintext;
556              
557 6         16 foreach my $char ( @chars ) {
558 48         72 my $low_15;
559             my $high_15;
560 48         74 $char = ord( $char ) << ++$i;
561 48         67 $low_15 = $char & 0x7fff;
562 48         61 $high_15 = $char & 0x7fff << 15;
563 48         63 $high_15 = $high_15 >> 15;
564 48         75 $char = $low_15 | $high_15;
565             }
566              
567 6         15 $password = 0x0000;
568 6         21 $password ^= $_ for @chars;
569 6         11 $password ^= $count;
570 6         11 $password ^= 0xCE4B;
571              
572 6         36 return sprintf "%X", $password;
573             }
574              
575              
576             ###############################################################################
577             #
578             # set_column($firstcol, $lastcol, $width, $format, $hidden, $level)
579             #
580             # Set the width of a single column or a range of columns.
581             # See also: _write_col_info
582             #
583             sub set_column {
584              
585 189     189 0 1319 my $self = shift;
586 189         572 my @data = @_;
587 189         408 my $cell = $data[0];
588              
589             # Check for a cell reference in A1 notation and substitute row and column
590 189 100       972 if ( $cell =~ /^\D/ ) {
591 188         842 @data = $self->_substitute_cellref( @_ );
592              
593             # Returned values $row1 and $row2 aren't required here. Remove them.
594 188         426 shift @data; # $row1
595 188         446 splice @data, 1, 1; # $row2
596             }
597              
598 189 50       667 return if @data < 3; # Ensure at least $firstcol, $lastcol and $width
599 189 50       555 return if not defined $data[0]; # Columns must be defined.
600 189 50       505 return if not defined $data[1];
601              
602             # Assume second column is the same as first if 0. Avoids KB918419 bug.
603 189 100       604 $data[1] = $data[0] if $data[1] == 0;
604              
605             # Ensure 2nd col is larger than first. Also for KB918419 bug.
606 189 50       584 ( $data[0], $data[1] ) = ( $data[1], $data[0] ) if $data[0] > $data[1];
607              
608              
609             # Check that cols are valid and store max and min values with default row.
610             # NOTE: The check shouldn't modify the row dimensions and should only modify
611             # the column dimensions in certain cases.
612 189         390 my $ignore_row = 1;
613 189         345 my $ignore_col = 1;
614 189 100       646 $ignore_col = 0 if ref $data[3]; # Column has a format.
615 189 100 100     1014 $ignore_col = 0 if $data[2] && $data[4]; # Column has a width but is hidden
616              
617 189 50       790 return -2
618             if $self->_check_dimensions( 0, $data[0], $ignore_row, $ignore_col );
619 189 50       668 return -2
620             if $self->_check_dimensions( 0, $data[1], $ignore_row, $ignore_col );
621              
622             # Set the limits for the outline levels (0 <= x <= 7).
623 189 100       729 $data[5] = 0 unless defined $data[5];
624 189 50       762 $data[5] = 0 if $data[5] < 0;
625 189 50       830 $data[5] = 7 if $data[5] > 7;
626              
627 189 100       919 if ( $data[5] > $self->{_outline_col_level} ) {
628 1         3 $self->{_outline_col_level} = $data[5];
629             }
630              
631             # Store the column data based on the first column. Padded for sorting.
632 189         1458 $self->{_colinfo}->{ sprintf "%05d", $data[0] } = [@data];
633              
634             # Store the column change to allow optimisations.
635 189         1072 $self->{_col_size_changed} = 1;
636              
637             # Store the col sizes for use when calculating image vertices taking
638             # hidden columns into account. Also store the column formats.
639 189         681 my $width = $data[2];
640 189         421 my $format = $data[3];
641 189   100     762 my $hidden = $data[4] || 0;
642              
643 189 100       511 $width = $self->{_default_col_width} if !defined $width;
644              
645 189         523 my ( $firstcol, $lastcol ) = @data;
646              
647 189         643 foreach my $col ( $firstcol .. $lastcol ) {
648 370         1047 $self->{_col_sizes}->{$col} = [$width, $hidden];
649 370 100       1468 $self->{_col_formats}->{$col} = $format if $format;
650             }
651             }
652              
653              
654             ###############################################################################
655             #
656             # set_selection()
657             #
658             # Set which cell or cells are selected in a worksheet.
659             #
660             sub set_selection {
661              
662 36     36 0 162 my $self = shift;
663 36         103 my $pane;
664             my $active_cell;
665 36         0 my $sqref;
666              
667 36 50       98 return unless @_;
668              
669             # Check for a cell reference in A1 notation and substitute row and column.
670 36 100       174 if ( $_[0] =~ /^\D/ ) {
671 33         112 @_ = $self->_substitute_cellref( @_ );
672             }
673              
674              
675             # There should be either 2 or 4 arguments.
676 36 100       116 if ( @_ == 2 ) {
    50          
677              
678             # Single cell selection.
679 28         114 $active_cell = xl_rowcol_to_cell( $_[0], $_[1] );
680 28         72 $sqref = $active_cell;
681             }
682             elsif ( @_ == 4 ) {
683              
684             # Range selection.
685 8         25 $active_cell = xl_rowcol_to_cell( $_[0], $_[1] );
686              
687 8         56 my ( $row_first, $col_first, $row_last, $col_last ) = @_;
688              
689             # Swap last row/col for first row/col as necessary
690 8 100       21 if ( $row_first > $row_last ) {
691 3         10 ( $row_first, $row_last ) = ( $row_last, $row_first );
692             }
693              
694 8 100       26 if ( $col_first > $col_last ) {
695 3         7 ( $col_first, $col_last ) = ( $col_last, $col_first );
696             }
697              
698             # If the first and last cell are the same write a single cell.
699 8 100 66     28 if ( ( $row_first == $row_last ) && ( $col_first == $col_last ) ) {
700 1         2 $sqref = $active_cell;
701             }
702             else {
703 7         21 $sqref = xl_range( $row_first, $row_last, $col_first, $col_last );
704             }
705              
706             }
707             else {
708              
709             # User supplied wrong number or arguments.
710 0         0 return;
711             }
712              
713             # Selection isn't set for cell A1.
714 36 100       105 return if $sqref eq 'A1';
715              
716 32         149 $self->{_selections} = [ [ $pane, $active_cell, $sqref ] ];
717             }
718              
719              
720             ###############################################################################
721             #
722             # freeze_panes( $row, $col, $top_row, $left_col )
723             #
724             # Set panes and mark them as frozen.
725             #
726             sub freeze_panes {
727              
728 66     66 0 370 my $self = shift;
729              
730 66 50       215 return unless @_;
731              
732             # Check for a cell reference in A1 notation and substitute row and column.
733 66 100       260 if ( $_[0] =~ /^\D/ ) {
734 10         33 @_ = $self->_substitute_cellref( @_ );
735             }
736              
737 66         140 my $row = shift;
738 66   100     179 my $col = shift || 0;
739 66   100     197 my $top_row = shift || $row;
740 66   100     166 my $left_col = shift || $col;
741 66   100     163 my $type = shift || 0;
742              
743 66         258 $self->{_panes} = [ $row, $col, $top_row, $left_col, $type ];
744             }
745              
746              
747             ###############################################################################
748             #
749             # split_panes( $y, $x, $top_row, $left_col )
750             #
751             # Set panes and mark them as split.
752             #
753             # Implementers note. The API for this method doesn't map well from the XLS
754             # file format and isn't sufficient to describe all cases of split panes.
755             # It should probably be something like:
756             #
757             # split_panes( $y, $x, $top_row, $left_col, $offset_row, $offset_col )
758             #
759             # I'll look at changing this if it becomes an issue.
760             #
761             sub split_panes {
762              
763 38     38 0 262 my $self = shift;
764              
765             # Call freeze panes but add the type flag for split panes.
766 38         125 $self->freeze_panes( @_[ 0 .. 3 ], 2 );
767             }
768              
769             # Older method name for backwards compatibility.
770             *thaw_panes = *split_panes;
771              
772              
773             ###############################################################################
774             #
775             # set_portrait()
776             #
777             # Set the page orientation as portrait.
778             #
779             sub set_portrait {
780              
781 2     2 0 26 my $self = shift;
782              
783 2         5 $self->{_orientation} = 1;
784 2         14 $self->{_page_setup_changed} = 1;
785             }
786              
787              
788             ###############################################################################
789             #
790             # set_landscape()
791             #
792             # Set the page orientation as landscape.
793             #
794             sub set_landscape {
795              
796 2     2 0 33 my $self = shift;
797              
798 2         5 $self->{_orientation} = 0;
799 2         5 $self->{_page_setup_changed} = 1;
800             }
801              
802              
803             ###############################################################################
804             #
805             # set_page_view()
806             #
807             # Set the page view mode for Mac Excel.
808             #
809             sub set_page_view {
810              
811 2     2 0 12 my $self = shift;
812              
813 2 50       14 $self->{_page_view} = defined $_[0] ? $_[0] : 1;
814             }
815              
816              
817             ###############################################################################
818             #
819             # set_tab_color()
820             #
821             # Set the colour of the worksheet tab.
822             #
823             sub set_tab_color {
824              
825 4     4 0 96 my $self = shift;
826 4         18 my $color = &Excel::Writer::XLSX::Format::_get_color( $_[0] );
827              
828 4         14 $self->{_tab_color} = $color;
829             }
830              
831              
832             ###############################################################################
833             #
834             # set_paper()
835             #
836             # Set the paper type. Ex. 1 = US Letter, 9 = A4
837             #
838             sub set_paper {
839              
840 19     19 0 146 my $self = shift;
841 19         41 my $paper_size = shift;
842              
843 19 50       70 if ( $paper_size ) {
844 19         72 $self->{_paper_size} = $paper_size;
845 19         55 $self->{_page_setup_changed} = 1;
846             }
847             }
848              
849              
850             ###############################################################################
851             #
852             # set_header()
853             #
854             # Set the page header caption and optional margin.
855             #
856             sub set_header {
857              
858 31     31 0 383 my $self = shift;
859 31   100     131 my $string = $_[0] || '';
860 31   100     171 my $margin = $_[1] || 0.3;
861 31   100     230 my $options = $_[2] || {};
862              
863              
864             # Replace the Excel placeholder &[Picture] with the internal &G.
865 31         197 $string =~ s/&\[Picture\]/&G/g;
866              
867 31 50       119 if ( length $string >= 255 ) {
868 0         0 carp 'Header string must be less than 255 characters';
869 0         0 return;
870             }
871              
872 31 100       127 if ( defined $options->{align_with_margins} ) {
873 1         9 $self->{_header_footer_aligns} = $options->{align_with_margins};
874             }
875              
876 31 100       102 if ( defined $options->{scale_with_doc} ) {
877 1         7 $self->{_header_footer_scales} = $options->{scale_with_doc};
878             }
879              
880             # Reset the array in case the function is called more than once.
881 31         157 $self->{_header_images} = [];
882              
883 31 100       111 if ( $options->{image_left} ) {
884 21         62 push @{ $self->{_header_images} }, [ $options->{image_left}, 'LH' ];
  21         106  
885             }
886              
887 31 100       123 if ( $options->{image_center} ) {
888 6         20 push @{ $self->{_header_images} }, [ $options->{image_center}, 'CH' ];
  6         24  
889             }
890              
891 31 100       133 if ( $options->{image_right} ) {
892 5         9 push @{ $self->{_header_images} }, [ $options->{image_right}, 'RH' ];
  5         16  
893             }
894              
895 31         187 my $placeholder_count = () = $string =~ /&G/g;
896 31         68 my $image_count = @{ $self->{_header_images} };
  31         102  
897              
898 31 50       126 if ( $image_count != $placeholder_count ) {
899 0         0 warn "Number of header images ($image_count) doesn't match placeholder "
900             . "count ($placeholder_count) in string: $string\n";
901 0         0 $self->{_header_images} = [];
902 0         0 return;
903             }
904              
905 31 100       105 if ( $image_count ) {
906 21         95 $self->{_has_header_vml} = 1;
907             }
908              
909 31         98 $self->{_header} = $string;
910 31         70 $self->{_margin_header} = $margin;
911 31         110 $self->{_header_footer_changed} = 1;
912             }
913              
914              
915             ###############################################################################
916             #
917             # set_footer()
918             #
919             # Set the page footer caption and optional margin.
920             #
921             sub set_footer {
922              
923 15     15 0 192 my $self = shift;
924 15   100     56 my $string = $_[0] || '';
925 15   100     82 my $margin = $_[1] || 0.3;
926 15   100     73 my $options = $_[2] || {};
927              
928              
929             # Replace the Excel placeholder &[Picture] with the internal &G.
930 15         43 $string =~ s/&\[Picture\]/&G/g;
931              
932 15 50       52 if ( length $string >= 255 ) {
933 0         0 carp 'Footer string must be less than 255 characters';
934 0         0 return;
935             }
936              
937 15 100       68 if ( defined $options->{align_with_margins} ) {
938 1         6 $self->{_header_footer_aligns} = $options->{align_with_margins};
939             }
940              
941 15 100       60 if ( defined $options->{scale_with_doc} ) {
942 1         4 $self->{_header_footer_scales} = $options->{scale_with_doc};
943             }
944              
945             # Reset the array in case the function is called more than once.
946 15         45 $self->{_footer_images} = [];
947              
948 15 100       49 if ( $options->{image_left} ) {
949 4         9 push @{ $self->{_footer_images} }, [ $options->{image_left}, 'LF' ];
  4         18  
950             }
951              
952 15 100       58 if ( $options->{image_center} ) {
953 3         7 push @{ $self->{_footer_images} }, [ $options->{image_center}, 'CF' ];
  3         101  
954             }
955              
956 15 100       59 if ( $options->{image_right} ) {
957 5         9 push @{ $self->{_footer_images} }, [ $options->{image_right}, 'RF' ];
  5         19  
958             }
959              
960 15         64 my $placeholder_count = () = $string =~ /&G/g;
961 15         39 my $image_count = @{ $self->{_footer_images} };
  15         120  
962              
963 15 50       59 if ( $image_count != $placeholder_count ) {
964 0         0 warn "Number of footer images ($image_count) doesn't match placeholder "
965             . "count ($placeholder_count) in string: $string\n";
966 0         0 $self->{_footer_images} = [];
967 0         0 return;
968             }
969              
970 15 100       47 if ( $image_count ) {
971 6         14 $self->{_has_header_vml} = 1;
972             }
973              
974 15         36 $self->{_footer} = $string;
975 15         30 $self->{_margin_footer} = $margin;
976 15         71 $self->{_header_footer_changed} = 1;
977             }
978              
979              
980             ###############################################################################
981             #
982             # center_horizontally()
983             #
984             # Center the page horizontally.
985             #
986             sub center_horizontally {
987              
988 4     4 0 53 my $self = shift;
989              
990 4         12 $self->{_print_options_changed} = 1;
991 4         9 $self->{_hcenter} = 1;
992             }
993              
994              
995             ###############################################################################
996             #
997             # center_vertically()
998             #
999             # Center the page horizontally.
1000             #
1001             sub center_vertically {
1002              
1003 4     4 0 34 my $self = shift;
1004              
1005 4         19 $self->{_print_options_changed} = 1;
1006 4         11 $self->{_vcenter} = 1;
1007             }
1008              
1009              
1010             ###############################################################################
1011             #
1012             # set_margins()
1013             #
1014             # Set all the page margins to the same value in inches.
1015             #
1016             sub set_margins {
1017              
1018 2     2 0 50 my $self = shift;
1019              
1020 2         8 $self->set_margin_left( $_[0] );
1021 2         8 $self->set_margin_right( $_[0] );
1022 2         5 $self->set_margin_top( $_[0] );
1023 2         6 $self->set_margin_bottom( $_[0] );
1024             }
1025              
1026              
1027             ###############################################################################
1028             #
1029             # set_margins_LR()
1030             #
1031             # Set the left and right margins to the same value in inches.
1032             #
1033             sub set_margins_LR {
1034              
1035 1     1 0 24 my $self = shift;
1036              
1037 1         5 $self->set_margin_left( $_[0] );
1038 1         3 $self->set_margin_right( $_[0] );
1039             }
1040              
1041              
1042             ###############################################################################
1043             #
1044             # set_margins_TB()
1045             #
1046             # Set the top and bottom margins to the same value in inches.
1047             #
1048             sub set_margins_TB {
1049              
1050 1     1 0 28 my $self = shift;
1051              
1052 1         5 $self->set_margin_top( $_[0] );
1053 1         4 $self->set_margin_bottom( $_[0] );
1054             }
1055              
1056              
1057             ###############################################################################
1058             #
1059             # set_margin_left()
1060             #
1061             # Set the left margin in inches.
1062             #
1063             sub set_margin_left {
1064              
1065 5     5 0 87 my $self = shift;
1066 5         11 my $margin = shift;
1067 5         17 my $default = 0.7;
1068              
1069             # Add 0 to ensure the argument is numeric.
1070 5 50       18 if ( defined $margin ) { $margin = 0 + $margin }
  5         16  
1071 0         0 else { $margin = $default }
1072              
1073 5         14 $self->{_margin_left} = $margin;
1074             }
1075              
1076              
1077             ###############################################################################
1078             #
1079             # set_margin_right()
1080             #
1081             # Set the right margin in inches.
1082             #
1083             sub set_margin_right {
1084              
1085 5     5 0 33 my $self = shift;
1086 5         8 my $margin = shift;
1087 5         8 my $default = 0.7;
1088              
1089             # Add 0 to ensure the argument is numeric.
1090 5 50       12 if ( defined $margin ) { $margin = 0 + $margin }
  5         10  
1091 0         0 else { $margin = $default }
1092              
1093 5         13 $self->{_margin_right} = $margin;
1094             }
1095              
1096              
1097             ###############################################################################
1098             #
1099             # set_margin_top()
1100             #
1101             # Set the top margin in inches.
1102             #
1103             sub set_margin_top {
1104              
1105 5     5 0 35 my $self = shift;
1106 5         8 my $margin = shift;
1107 5         10 my $default = 0.75;
1108              
1109             # Add 0 to ensure the argument is numeric.
1110 5 50       13 if ( defined $margin ) { $margin = 0 + $margin }
  5         12  
1111 0         0 else { $margin = $default }
1112              
1113 5         12 $self->{_margin_top} = $margin;
1114             }
1115              
1116              
1117             ###############################################################################
1118             #
1119             # set_margin_bottom()
1120             #
1121             # Set the bottom margin in inches.
1122             #
1123             sub set_margin_bottom {
1124              
1125              
1126 5     5 0 32 my $self = shift;
1127 5         12 my $margin = shift;
1128 5         7 my $default = 0.75;
1129              
1130             # Add 0 to ensure the argument is numeric.
1131 5 50       13 if ( defined $margin ) { $margin = 0 + $margin }
  5         9  
1132 0         0 else { $margin = $default }
1133              
1134 5         14 $self->{_margin_bottom} = $margin;
1135             }
1136              
1137              
1138             ###############################################################################
1139             #
1140             # repeat_rows($first_row, $last_row)
1141             #
1142             # Set the rows to repeat at the top of each printed page.
1143             #
1144             sub repeat_rows {
1145              
1146 6     6 0 37 my $self = shift;
1147              
1148 6         12 my $row_min = $_[0];
1149 6   66     33 my $row_max = $_[1] || $_[0]; # Second row is optional
1150              
1151              
1152             # Convert to 1 based.
1153 6         12 $row_min++;
1154 6         20 $row_max++;
1155              
1156 6         34 my $area = '$' . $row_min . ':' . '$' . $row_max;
1157              
1158             # Build up the print titles "Sheet1!$1:$2"
1159 6         38 my $sheetname = quote_sheetname( $self->{_name} );
1160 6         20 $area = $sheetname . "!" . $area;
1161              
1162 6         20 $self->{_repeat_rows} = $area;
1163             }
1164              
1165              
1166             ###############################################################################
1167             #
1168             # repeat_columns($first_col, $last_col)
1169             #
1170             # Set the columns to repeat at the left hand side of each printed page. This is
1171             # stored as a element.
1172             #
1173             sub repeat_columns {
1174              
1175 3     3 0 16 my $self = shift;
1176              
1177             # Check for a cell reference in A1 notation and substitute row and column
1178 3 50       36 if ( $_[0] =~ /^\D/ ) {
1179 3         23 @_ = $self->_substitute_cellref( @_ );
1180              
1181             # Returned values $row1 and $row2 aren't required here. Remove them.
1182 3         6 shift @_; # $row1
1183 3         6 splice @_, 1, 1; # $row2
1184             }
1185              
1186 3         7 my $col_min = $_[0];
1187 3   66     31 my $col_max = $_[1] || $_[0]; # Second col is optional
1188              
1189             # Convert to A notation.
1190 3         16 $col_min = xl_col_to_name( $_[0], 1 );
1191 3         9 $col_max = xl_col_to_name( $_[1], 1 );
1192              
1193 3         9 my $area = $col_min . ':' . $col_max;
1194              
1195             # Build up the print area range "=Sheet2!C1:C2"
1196 3         11 my $sheetname = quote_sheetname( $self->{_name} );
1197 3         9 $area = $sheetname . "!" . $area;
1198              
1199 3         12 $self->{_repeat_cols} = $area;
1200             }
1201              
1202              
1203             ###############################################################################
1204             #
1205             # print_area($first_row, $first_col, $last_row, $last_col)
1206             #
1207             # Set the print area in the current worksheet. This is stored as a
1208             # element.
1209             #
1210             sub print_area {
1211              
1212 9     9 0 59 my $self = shift;
1213              
1214             # Check for a cell reference in A1 notation and substitute row and column
1215 9 50       55 if ( $_[0] =~ /^\D/ ) {
1216 9         50 @_ = $self->_substitute_cellref( @_ );
1217             }
1218              
1219 9 50       38 return if @_ != 4; # Require 4 parameters
1220              
1221 9         40 my ( $row1, $col1, $row2, $col2 ) = @_;
1222              
1223             # Ignore max print area since this is the same as no print area for Excel.
1224 9 100 33     145 if ( $row1 == 0
      66        
      100        
1225             and $col1 == 0
1226             and $row2 == $self->{_xls_rowmax} - 1
1227             and $col2 == $self->{_xls_colmax} - 1 )
1228             {
1229 1         4 return;
1230             }
1231              
1232             # Build up the print area range "=Sheet2!R1C1:R2C1"
1233 8         38 my $area = $self->_convert_name_area( $row1, $col1, $row2, $col2 );
1234              
1235 8         39 $self->{_print_area} = $area;
1236             }
1237              
1238              
1239             ###############################################################################
1240             #
1241             # autofilter($first_row, $first_col, $last_row, $last_col)
1242             #
1243             # Set the autofilter area in the worksheet.
1244             #
1245             sub autofilter {
1246              
1247 32     32 0 696 my $self = shift;
1248              
1249             # Check for a cell reference in A1 notation and substitute row and column
1250 32 100       172 if ( $_[0] =~ /^\D/ ) {
1251 30         105 @_ = $self->_substitute_cellref( @_ );
1252             }
1253              
1254 32 50       105 return if @_ != 4; # Require 4 parameters
1255              
1256 32         89 my ( $row1, $col1, $row2, $col2 ) = @_;
1257              
1258             # Reverse max and min values if necessary.
1259 32 50       86 ( $row1, $row2 ) = ( $row2, $row1 ) if $row2 < $row1;
1260 32 50       77 ( $col1, $col2 ) = ( $col2, $col1 ) if $col2 < $col1;
1261              
1262             # Build up the print area range "Sheet1!$A$1:$C$13".
1263 32         106 my $area = $self->_convert_name_area( $row1, $col1, $row2, $col2 );
1264 32         101 my $ref = xl_range( $row1, $row2, $col1, $col2 );
1265              
1266 32         78 $self->{_autofilter} = $area;
1267 32         67 $self->{_autofilter_ref} = $ref;
1268 32         124 $self->{_filter_range} = [ $col1, $col2 ];
1269             }
1270              
1271              
1272             ###############################################################################
1273             #
1274             # filter_column($column, $criteria, ...)
1275             #
1276             # Set the column filter criteria.
1277             #
1278             sub filter_column {
1279              
1280 25     25 0 129 my $self = shift;
1281 25         47 my $col = $_[0];
1282 25         41 my $expression = $_[1];
1283              
1284             croak "Must call autofilter() before filter_column()"
1285 25 50       65 unless $self->{_autofilter};
1286 25 50       72 croak "Incorrect number of arguments to filter_column()"
1287             unless @_ == 2;
1288              
1289              
1290             # Check for a column reference in A1 notation and substitute.
1291 25 100       106 if ( $col =~ /^\D/ ) {
1292 24         64 my $col_letter = $col;
1293              
1294             # Convert col ref to a cell ref and then to a col number.
1295 24         139 ( undef, $col ) = $self->_substitute_cellref( $col . '1' );
1296              
1297 24 50       91 croak "Invalid column '$col_letter'" if $col >= $self->{_xls_colmax};
1298             }
1299              
1300 25         39 my ( $col_first, $col_last ) = @{ $self->{_filter_range} };
  25         73  
1301              
1302             # Reject column if it is outside filter range.
1303 25 50 33     138 if ( $col < $col_first or $col > $col_last ) {
1304 0         0 croak "Column '$col' outside autofilter() column range "
1305             . "($col_first .. $col_last)";
1306             }
1307              
1308              
1309 25         83 my @tokens = $self->_extract_filter_tokens( $expression );
1310              
1311 25 50 66     99 croak "Incorrect number of tokens in expression '$expression'"
1312             unless ( @tokens == 3 or @tokens == 7 );
1313              
1314              
1315 25         83 @tokens = $self->_parse_filter_expression( $expression, @tokens );
1316              
1317             # Excel handles single or double custom filters as default filters. We need
1318             # to check for them and handle them accordingly.
1319 25 100 100     210 if ( @tokens == 2 && $tokens[0] == 2 ) {
    100 100        
      100        
      66        
1320              
1321             # Single equality.
1322 6         34 $self->filter_column_list( $col, $tokens[1] );
1323             }
1324             elsif (@tokens == 5
1325             && $tokens[0] == 2
1326             && $tokens[2] == 1
1327             && $tokens[3] == 2 )
1328             {
1329              
1330             # Double equality with "or" operator.
1331 3         15 $self->filter_column_list( $col, $tokens[1], $tokens[4] );
1332             }
1333             else {
1334              
1335             # Non default custom filter.
1336 16         54 $self->{_filter_cols}->{$col} = [@tokens];
1337 16         43 $self->{_filter_type}->{$col} = 0;
1338              
1339             }
1340              
1341 25         87 $self->{_filter_on} = 1;
1342             }
1343              
1344              
1345             ###############################################################################
1346             #
1347             # filter_column_list($column, @matches )
1348             #
1349             # Set the column filter criteria in Excel 2007 list style.
1350             #
1351             sub filter_column_list {
1352              
1353 14     14 0 51 my $self = shift;
1354 14         27 my $col = shift;
1355 14         42 my @tokens = @_;
1356              
1357             croak "Must call autofilter() before filter_column_list()"
1358 14 50       44 unless $self->{_autofilter};
1359 14 50       43 croak "Incorrect number of arguments to filter_column_list()"
1360             unless @tokens;
1361              
1362             # Check for a column reference in A1 notation and substitute.
1363 14 100       69 if ( $col =~ /^\D/ ) {
1364 5         12 my $col_letter = $col;
1365              
1366             # Convert col ref to a cell ref and then to a col number.
1367 5         28 ( undef, $col ) = $self->_substitute_cellref( $col . '1' );
1368              
1369 5 50       26 croak "Invalid column '$col_letter'" if $col >= $self->{_xls_colmax};
1370             }
1371              
1372 14         30 my ( $col_first, $col_last ) = @{ $self->{_filter_range} };
  14         47  
1373              
1374             # Reject column if it is outside filter range.
1375 14 50 33     92 if ( $col < $col_first or $col > $col_last ) {
1376 0         0 croak "Column '$col' outside autofilter() column range "
1377             . "($col_first .. $col_last)";
1378             }
1379              
1380 14         55 $self->{_filter_cols}->{$col} = [@tokens];
1381 14         45 $self->{_filter_type}->{$col} = 1; # Default style.
1382 14         41 $self->{_filter_on} = 1;
1383             }
1384              
1385              
1386             ###############################################################################
1387             #
1388             # _extract_filter_tokens($expression)
1389             #
1390             # Extract the tokens from the filter expression. The tokens are mainly non-
1391             # whitespace groups. The only tricky part is to extract string tokens that
1392             # contain whitespace and/or quoted double quotes (Excel's escaped quotes).
1393             #
1394             # Examples: 'x < 2000'
1395             # 'x > 2000 and x < 5000'
1396             # 'x = "foo"'
1397             # 'x = "foo bar"'
1398             # 'x = "foo "" bar"'
1399             #
1400             sub _extract_filter_tokens {
1401              
1402 67     67   25174 my $self = shift;
1403 67         122 my $expression = $_[0];
1404              
1405 67 100       178 return unless $expression;
1406              
1407 65         584 my @tokens = ( $expression =~ /"(?:[^"]|"")*"|\S+/g ); #"
1408              
1409             # Remove leading and trailing quotes and unescape other quotes
1410 65         168 for ( @tokens ) {
1411 247         436 s/^"//; #"
1412 247         377 s/"$//; #"
1413 247         414 s/""/"/g; #"
1414             }
1415              
1416 65         242 return @tokens;
1417             }
1418              
1419              
1420             ###############################################################################
1421             #
1422             # _parse_filter_expression(@token)
1423             #
1424             # Converts the tokens of a possibly conditional expression into 1 or 2
1425             # sub expressions for further parsing.
1426             #
1427             # Examples:
1428             # ('x', '==', 2000) -> exp1
1429             # ('x', '>', 2000, 'and', 'x', '<', 5000) -> exp1 and exp2
1430             #
1431             sub _parse_filter_expression {
1432              
1433 49     49   147 my $self = shift;
1434 49         71 my $expression = shift;
1435 49         131 my @tokens = @_;
1436              
1437             # The number of tokens will be either 3 (for 1 expression)
1438             # or 7 (for 2 expressions).
1439             #
1440 49 100       129 if ( @tokens == 7 ) {
1441              
1442 10         64 my $conditional = $tokens[3];
1443              
1444 10 100       84 if ( $conditional =~ /^(and|&&)$/ ) {
    50          
1445 5         15 $conditional = 0;
1446             }
1447             elsif ( $conditional =~ /^(or|\|\|)$/ ) {
1448 5         23 $conditional = 1;
1449             }
1450             else {
1451 0         0 croak "Token '$conditional' is not a valid conditional "
1452             . "in filter expression '$expression'";
1453             }
1454              
1455 10         44 my @expression_1 =
1456             $self->_parse_filter_tokens( $expression, @tokens[ 0, 1, 2 ] );
1457 10         45 my @expression_2 =
1458             $self->_parse_filter_tokens( $expression, @tokens[ 4, 5, 6 ] );
1459              
1460 10         50 return ( @expression_1, $conditional, @expression_2 );
1461             }
1462             else {
1463 39         106 return $self->_parse_filter_tokens( $expression, @tokens );
1464             }
1465             }
1466              
1467              
1468             ###############################################################################
1469             #
1470             # _parse_filter_tokens(@token)
1471             #
1472             # Parse the 3 tokens of a filter expression and return the operator and token.
1473             #
1474             sub _parse_filter_tokens {
1475              
1476 59     59   99 my $self = shift;
1477 59         90 my $expression = shift;
1478 59         135 my @tokens = @_;
1479              
1480 59         355 my %operators = (
1481             '==' => 2,
1482             '=' => 2,
1483             '=~' => 2,
1484             'eq' => 2,
1485              
1486             '!=' => 5,
1487             '!~' => 5,
1488             'ne' => 5,
1489             '<>' => 5,
1490              
1491             '<' => 1,
1492             '<=' => 3,
1493             '>' => 4,
1494             '>=' => 6,
1495             );
1496              
1497 59         119 my $operator = $operators{ $tokens[1] };
1498 59         110 my $token = $tokens[2];
1499              
1500              
1501             # Special handling of "Top" filter expressions.
1502 59 100       161 if ( $tokens[0] =~ /^top|bottom$/i ) {
1503              
1504 4         8 my $value = $tokens[1];
1505              
1506 4 50 33     33 if ( $value =~ /\D/
      33        
1507             or $value < 1
1508             or $value > 500 )
1509             {
1510 0         0 croak "The value '$value' in expression '$expression' "
1511             . "must be in the range 1 to 500";
1512             }
1513              
1514 4         10 $token = lc $token;
1515              
1516 4 50 66     22 if ( $token ne 'items' and $token ne '%' ) {
1517 0         0 croak "The type '$token' in expression '$expression' "
1518             . "must be either 'items' or '%'";
1519             }
1520              
1521 4 100       16 if ( $tokens[0] =~ /^top$/i ) {
1522 2         5 $operator = 30;
1523             }
1524             else {
1525 2         3 $operator = 32;
1526             }
1527              
1528 4 100       10 if ( $tokens[2] eq '%' ) {
1529 2         4 $operator++;
1530             }
1531              
1532 4         9 $token = $value;
1533             }
1534              
1535              
1536 59 0 33     146 if ( not $operator and $tokens[0] ) {
1537 0         0 croak "Token '$tokens[1]' is not a valid operator "
1538             . "in filter expression '$expression'";
1539             }
1540              
1541              
1542             # Special handling for Blanks/NonBlanks.
1543 59 100       153 if ( $token =~ /^blanks|nonblanks$/i ) {
1544              
1545             # Only allow Equals or NotEqual in this context.
1546 7 50 66     35 if ( $operator != 2 and $operator != 5 ) {
1547 0         0 croak "The operator '$tokens[1]' in expression '$expression' "
1548             . "is not valid in relation to Blanks/NonBlanks'";
1549             }
1550              
1551 7         20 $token = lc $token;
1552              
1553             # The operator should always be 2 (=) to flag a "simple" equality in
1554             # the binary record. Therefore we convert <> to =.
1555 7 100       21 if ( $token eq 'blanks' ) {
1556 4 100       14 if ( $operator == 5 ) {
1557 1         3 $token = ' ';
1558             }
1559             }
1560             else {
1561 3 100       11 if ( $operator == 5 ) {
1562 1         2 $operator = 2;
1563 1         6 $token = 'blanks';
1564             }
1565             else {
1566 2         4 $operator = 5;
1567 2         5 $token = ' ';
1568             }
1569             }
1570             }
1571              
1572              
1573             # if the string token contains an Excel match character then change the
1574             # operator type to indicate a non "simple" equality.
1575 59 100 100     202 if ( $operator == 2 and $token =~ /[*?]/ ) {
1576 3         6 $operator = 22;
1577             }
1578              
1579              
1580 59         344 return ( $operator, $token );
1581             }
1582              
1583              
1584             ###############################################################################
1585             #
1586             # _convert_name_area($first_row, $first_col, $last_row, $last_col)
1587             #
1588             # Convert zero indexed rows and columns to the format required by worksheet
1589             # named ranges, eg, "Sheet1!$A$1:$C$13".
1590             #
1591             sub _convert_name_area {
1592              
1593 40     40   75 my $self = shift;
1594              
1595 40         79 my $row_num_1 = $_[0];
1596 40         82 my $col_num_1 = $_[1];
1597 40         72 my $row_num_2 = $_[2];
1598 40         89 my $col_num_2 = $_[3];
1599              
1600 40         88 my $range1 = '';
1601 40         67 my $range2 = '';
1602 40         65 my $row_col_only = 0;
1603 40         74 my $area;
1604              
1605             # Convert to A1 notation.
1606 40         183 my $col_char_1 = xl_col_to_name( $col_num_1, 1 );
1607 40         121 my $col_char_2 = xl_col_to_name( $col_num_2, 1 );
1608 40         136 my $row_char_1 = '$' . ( $row_num_1 + 1 );
1609 40         100 my $row_char_2 = '$' . ( $row_num_2 + 1 );
1610              
1611             # We need to handle some special cases that refer to rows or columns only.
1612 40 100 100     396 if ( $row_num_1 == 0 and $row_num_2 == $self->{_xls_rowmax} - 1 ) {
    100 100        
1613 1         2 $range1 = $col_char_1;
1614 1         4 $range2 = $col_char_2;
1615 1         2 $row_col_only = 1;
1616             }
1617             elsif ( $col_num_1 == 0 and $col_num_2 == $self->{_xls_colmax} - 1 ) {
1618 1         2 $range1 = $row_char_1;
1619 1         2 $range2 = $row_char_2;
1620 1         2 $row_col_only = 1;
1621             }
1622             else {
1623 38         86 $range1 = $col_char_1 . $row_char_1;
1624 38         75 $range2 = $col_char_2 . $row_char_2;
1625             }
1626              
1627             # A repeated range is only written once (if it isn't a special case).
1628 40 100 100     143 if ( $range1 eq $range2 && !$row_col_only ) {
1629 1         2 $area = $range1;
1630             }
1631             else {
1632 39         95 $area = $range1 . ':' . $range2;
1633             }
1634              
1635             # Build up the print area range "Sheet1!$A$1:$C$13".
1636 40         157 my $sheetname = quote_sheetname( $self->{_name} );
1637 40         131 $area = $sheetname . "!" . $area;
1638              
1639 40         97 return $area;
1640             }
1641              
1642              
1643             ###############################################################################
1644             #
1645             # hide_gridlines()
1646             #
1647             # Set the option to hide gridlines on the screen and the printed page.
1648             #
1649             # This was mainly useful for Excel 5 where printed gridlines were on by
1650             # default.
1651             #
1652             sub hide_gridlines {
1653              
1654 12     12 0 116 my $self = shift;
1655 12 100       43 my $option =
1656             defined $_[0] ? $_[0] : 1; # Default to hiding printed gridlines
1657              
1658 12 100       52 if ( $option == 0 ) {
    100          
1659 5         22 $self->{_print_gridlines} = 1; # 1 = display, 0 = hide
1660 5         9 $self->{_screen_gridlines} = 1;
1661 5         14 $self->{_print_options_changed} = 1;
1662             }
1663             elsif ( $option == 1 ) {
1664 4         7 $self->{_print_gridlines} = 0;
1665 4         9 $self->{_screen_gridlines} = 1;
1666             }
1667             else {
1668 3         19 $self->{_print_gridlines} = 0;
1669 3         9 $self->{_screen_gridlines} = 0;
1670             }
1671             }
1672              
1673              
1674             ###############################################################################
1675             #
1676             # print_row_col_headers()
1677             #
1678             # Set the option to print the row and column headers on the printed page.
1679             # See also the _store_print_headers() method below.
1680             #
1681             sub print_row_col_headers {
1682              
1683 2     2 0 13 my $self = shift;
1684 2 50       8 my $headers = defined $_[0] ? $_[0] : 1;
1685              
1686 2 50       7 if ( $headers ) {
1687 2         10 $self->{_print_headers} = 1;
1688 2         6 $self->{_print_options_changed} = 1;
1689             }
1690             else {
1691 0         0 $self->{_print_headers} = 0;
1692             }
1693             }
1694              
1695              
1696             ###############################################################################
1697             #
1698             # hide_row_col_headers()
1699             #
1700             # Set the option to hide the row and column headers in Excel.
1701             #
1702             sub hide_row_col_headers {
1703              
1704 1     1 0 8 my $self = shift;
1705 1         5 $self->{_hide_row_col_headers} = 1;
1706             }
1707              
1708              
1709             ###############################################################################
1710             #
1711             # fit_to_pages($width, $height)
1712             #
1713             # Store the vertical and horizontal number of pages that will define the
1714             # maximum area printed.
1715             #
1716             sub fit_to_pages {
1717              
1718 6     6 0 59 my $self = shift;
1719              
1720 6         48 $self->{_fit_page} = 1;
1721 6 100       25 $self->{_fit_width} = defined $_[0] ? $_[0] : 1;
1722 6 100       26 $self->{_fit_height} = defined $_[1] ? $_[1] : 1;
1723 6         18 $self->{_page_setup_changed} = 1;
1724             }
1725              
1726              
1727             ###############################################################################
1728             #
1729             # set_h_pagebreaks(@breaks)
1730             #
1731             # Store the horizontal page breaks on a worksheet.
1732             #
1733             sub set_h_pagebreaks {
1734              
1735 4     4 0 73 my $self = shift;
1736              
1737 4         9 push @{ $self->{_hbreaks} }, @_;
  4         81  
1738             }
1739              
1740              
1741             ###############################################################################
1742             #
1743             # set_v_pagebreaks(@breaks)
1744             #
1745             # Store the vertical page breaks on a worksheet.
1746             #
1747             sub set_v_pagebreaks {
1748              
1749 3     3 0 36 my $self = shift;
1750              
1751 3         13 push @{ $self->{_vbreaks} }, @_;
  3         24  
1752             }
1753              
1754              
1755             ###############################################################################
1756             #
1757             # set_zoom( $scale )
1758             #
1759             # Set the worksheet zoom factor.
1760             #
1761             sub set_zoom {
1762              
1763 3     3 0 19 my $self = shift;
1764 3   50     12 my $scale = $_[0] || 100;
1765              
1766             # Confine the scale to Excel's range
1767 3 50 33     22 if ( $scale < 10 or $scale > 400 ) {
1768 0         0 carp "Zoom factor $scale outside range: 10 <= zoom <= 400";
1769 0         0 $scale = 100;
1770             }
1771              
1772 3         12 $self->{_zoom} = int $scale;
1773             }
1774              
1775              
1776             ###############################################################################
1777             #
1778             # set_print_scale($scale)
1779             #
1780             # Set the scale factor for the printed page.
1781             #
1782             sub set_print_scale {
1783              
1784 3     3 0 20 my $self = shift;
1785 3   50     12 my $scale = $_[0] || 100;
1786              
1787             # Confine the scale to Excel's range
1788 3 50 33     33 if ( $scale < 10 or $scale > 400 ) {
1789 0         0 carp "Print scale $scale outside range: 10 <= zoom <= 400";
1790 0         0 $scale = 100;
1791             }
1792              
1793             # Turn off "fit to page" option.
1794 3         24 $self->{_fit_page} = 0;
1795              
1796 3         9 $self->{_print_scale} = int $scale;
1797 3         9 $self->{_page_setup_changed} = 1;
1798             }
1799              
1800              
1801             ###############################################################################
1802             #
1803             # print_black_and_white()
1804             #
1805             # Set the option to print the worksheet in black and white.
1806             #
1807             sub print_black_and_white {
1808              
1809 1     1 0 6 my $self = shift;
1810              
1811 1         4 $self->{_black_white} = 1;
1812             }
1813              
1814              
1815             ###############################################################################
1816             #
1817             # keep_leading_zeros()
1818             #
1819             # Causes the write() method to treat integers with a leading zero as a string.
1820             # This ensures that any leading zeros such, as in zip codes, are maintained.
1821             #
1822             sub keep_leading_zeros {
1823              
1824 0     0 0 0 my $self = shift;
1825              
1826 0 0       0 if ( defined $_[0] ) {
1827 0         0 $self->{_leading_zeros} = $_[0];
1828             }
1829             else {
1830 0         0 $self->{_leading_zeros} = 1;
1831             }
1832             }
1833              
1834              
1835             ###############################################################################
1836             #
1837             # show_comments()
1838             #
1839             # Make any comments in the worksheet visible.
1840             #
1841             sub show_comments {
1842              
1843 2     2 0 9 my $self = shift;
1844              
1845 2 50       10 $self->{_comments_visible} = defined $_[0] ? $_[0] : 1;
1846             }
1847              
1848              
1849             ###############################################################################
1850             #
1851             # set_comments_author()
1852             #
1853             # Set the default author of the cell comments.
1854             #
1855             sub set_comments_author {
1856              
1857 39     39 0 227 my $self = shift;
1858              
1859 39 50       204 $self->{_comments_author} = $_[0] if defined $_[0];
1860             }
1861              
1862              
1863             ###############################################################################
1864             #
1865             # right_to_left()
1866             #
1867             # Display the worksheet right to left for some eastern versions of Excel.
1868             #
1869             sub right_to_left {
1870              
1871 1     1 0 6 my $self = shift;
1872              
1873 1 50       5 $self->{_right_to_left} = defined $_[0] ? $_[0] : 1;
1874             }
1875              
1876              
1877             ###############################################################################
1878             #
1879             # hide_zero()
1880             #
1881             # Hide cell zero values.
1882             #
1883             sub hide_zero {
1884              
1885 1     1 0 6 my $self = shift;
1886              
1887 1 50       5 $self->{_show_zeros} = defined $_[0] ? not $_[0] : 0;
1888             }
1889              
1890              
1891             ###############################################################################
1892             #
1893             # print_across()
1894             #
1895             # Set the order in which pages are printed.
1896             #
1897             sub print_across {
1898              
1899 2     2 0 24 my $self = shift;
1900 2 50       9 my $page_order = defined $_[0] ? $_[0] : 1;
1901              
1902 2 50       6 if ( $page_order ) {
1903 2         10 $self->{_page_order} = 1;
1904 2         5 $self->{_page_setup_changed} = 1;
1905             }
1906             else {
1907 0         0 $self->{_page_order} = 0;
1908             }
1909             }
1910              
1911              
1912             ###############################################################################
1913             #
1914             # set_start_page()
1915             #
1916             # Set the start page number.
1917             #
1918             sub set_start_page {
1919              
1920 3     3 0 26 my $self = shift;
1921 3 50       19 return unless defined $_[0];
1922              
1923 3         25 $self->{_page_start} = $_[0];
1924             }
1925              
1926              
1927             ###############################################################################
1928             #
1929             # set_first_row_column()
1930             #
1931             # Set the topmost and leftmost visible row and column.
1932             # TODO: Document this when tested fully for interaction with panes.
1933             #
1934             sub set_first_row_column {
1935              
1936 0     0 0 0 my $self = shift;
1937              
1938 0   0     0 my $row = $_[0] || 0;
1939 0   0     0 my $col = $_[1] || 0;
1940              
1941 0 0       0 $row = $self->{_xls_rowmax} if $row > $self->{_xls_rowmax};
1942 0 0       0 $col = $self->{_xls_colmax} if $col > $self->{_xls_colmax};
1943              
1944 0         0 $self->{_first_row} = $row;
1945 0         0 $self->{_first_col} = $col;
1946             }
1947              
1948              
1949             ###############################################################################
1950             #
1951             # add_write_handler($re, $code_ref)
1952             #
1953             # Allow the user to add their own matches and handlers to the write() method.
1954             #
1955             sub add_write_handler {
1956              
1957 0     0 0 0 my $self = shift;
1958              
1959 0 0       0 return unless @_ == 2;
1960 0 0       0 return unless ref $_[1] eq 'CODE';
1961              
1962 0         0 push @{ $self->{_write_match} }, [@_];
  0         0  
1963             }
1964              
1965              
1966             ###############################################################################
1967             #
1968             # write($row, $col, $token, $format)
1969             #
1970             # Parse $token and call appropriate write method. $row and $column are zero
1971             # indexed. $format is optional.
1972             #
1973             # Returns: return value of called subroutine
1974             #
1975             sub write {
1976              
1977 10398     10398 0 26204 my $self = shift;
1978              
1979             # Check for a cell reference in A1 notation and substitute row and column
1980 10398 100       30482 if ( $_[0] =~ /^\D/ ) {
1981 1054         4661 @_ = $self->_substitute_cellref( @_ );
1982             }
1983              
1984 10398         16286 my $token = $_[2];
1985              
1986             # Handle undefs as blanks
1987 10398 100       19379 $token = '' unless defined $token;
1988              
1989              
1990             # First try user defined matches.
1991 10398         14534 for my $aref ( @{ $self->{_write_match} } ) {
  10398         23242  
1992 0         0 my $re = $aref->[0];
1993 0         0 my $sub = $aref->[1];
1994              
1995 0 0       0 if ( $token =~ /$re/ ) {
1996 0         0 my $match = &$sub( $self, @_ );
1997 0 0       0 return $match if defined $match;
1998             }
1999             }
2000              
2001              
2002             # Match an array ref.
2003 10398 100 33     65041 if ( ref $token eq "ARRAY" ) {
    50          
    100          
    100          
    50          
    50          
    100          
    100          
    100          
2004 1012         3432 return $self->write_row( @_ );
2005             }
2006              
2007             # Match integer with leading zero(s)
2008             elsif ( $self->{_leading_zeros} and $token =~ /^0\d+$/ ) {
2009 0         0 return $self->write_string( @_ );
2010             }
2011              
2012             # Match number
2013             elsif ( $token =~ /^([+-]?)(?=[0-9]|\.[0-9])[0-9]*(\.[0-9]*)?([Ee]([+-]?[0-9]+))?$/ ) {
2014 7196         17133 return $self->write_number( @_ );
2015             }
2016              
2017             # Match http, https or ftp URL
2018             elsif ( $token =~ m|^[fh]tt?ps?://| ) {
2019 14         179 return $self->write_url( @_ );
2020             }
2021              
2022             # Match mailto:
2023             elsif ( $token =~ m/^mailto:/ ) {
2024 0         0 return $self->write_url( @_ );
2025             }
2026              
2027             # Match internal or external sheet link
2028             elsif ( $token =~ m[^(?:in|ex)ternal:] ) {
2029 0         0 return $self->write_url( @_ );
2030             }
2031              
2032             # Match formula
2033             elsif ( $token =~ /^=/ ) {
2034 21         77 return $self->write_formula( @_ );
2035             }
2036              
2037             # Match array formula
2038             elsif ( $token =~ /^{=.*}$/ ) {
2039 2         15 return $self->write_formula( @_ );
2040             }
2041              
2042             # Match blank
2043             elsif ( $token eq '' ) {
2044 29         95 splice @_, 2, 1; # remove the empty string from the parameter list
2045 29         95 return $self->write_blank( @_ );
2046             }
2047              
2048             # Default: match string
2049             else {
2050 2124         5406 return $self->write_string( @_ );
2051             }
2052             }
2053              
2054              
2055             ###############################################################################
2056             #
2057             # write_row($row, $col, $array_ref, $format)
2058             #
2059             # Write a row of data starting from ($row, $col). Call write_col() if any of
2060             # the elements of the array ref are in turn array refs. This allows the writing
2061             # of 1D or 2D arrays of data in one go.
2062             #
2063             # Returns: the first encountered error value or zero for no errors
2064             #
2065             sub write_row {
2066              
2067 1019     1019 0 2036 my $self = shift;
2068              
2069              
2070             # Check for a cell reference in A1 notation and substitute row and column
2071 1019 100       3906 if ( $_[0] =~ /^\D/ ) {
2072 7         26 @_ = $self->_substitute_cellref( @_ );
2073             }
2074              
2075             # Catch non array refs passed by user.
2076 1019 50       3022 if ( ref $_[2] ne 'ARRAY' ) {
2077 0         0 croak "Not an array ref in call to write_row()$!";
2078             }
2079              
2080 1019         2031 my $row = shift;
2081 1019         1723 my $col = shift;
2082 1019         1615 my $tokens = shift;
2083 1019         2128 my @options = @_;
2084 1019         1673 my $error = 0;
2085 1019         1645 my $ret;
2086              
2087 1019         2212 for my $token ( @$tokens ) {
2088              
2089             # Check for nested arrays
2090 3869 100       8208 if ( ref $token eq "ARRAY" ) {
2091 1091         3347 $ret = $self->write_col( $row, $col, $token, @options );
2092             }
2093             else {
2094 2778         5353 $ret = $self->write( $row, $col, $token, @options );
2095             }
2096              
2097             # Return only the first error encountered, if any.
2098 3869   33     14067 $error ||= $ret;
2099 3869         6401 $col++;
2100             }
2101              
2102 1019         3434 return $error;
2103             }
2104              
2105              
2106             ###############################################################################
2107             #
2108             # write_col($row, $col, $array_ref, $format)
2109             #
2110             # Write a column of data starting from ($row, $col). Call write_row() if any of
2111             # the elements of the array ref are in turn array refs. This allows the writing
2112             # of 1D or 2D arrays of data in one go.
2113             #
2114             # Returns: the first encountered error value or zero for no errors
2115             #
2116             sub write_col {
2117              
2118 1112     1112 0 2110 my $self = shift;
2119              
2120              
2121             # Check for a cell reference in A1 notation and substitute row and column
2122 1112 100       3757 if ( $_[0] =~ /^\D/ ) {
2123 19         83 @_ = $self->_substitute_cellref( @_ );
2124             }
2125              
2126             # Catch non array refs passed by user.
2127 1112 50       3222 if ( ref $_[2] ne 'ARRAY' ) {
2128 0         0 croak "Not an array ref in call to write_col()$!";
2129             }
2130              
2131 1112         2110 my $row = shift;
2132 1112         1844 my $col = shift;
2133 1112         1759 my $tokens = shift;
2134 1112         2267 my @options = @_;
2135 1112         1820 my $error = 0;
2136 1112         1765 my $ret;
2137              
2138 1112         2357 for my $token ( @$tokens ) {
2139              
2140             # write() will deal with any nested arrays
2141 5423         12442 $ret = $self->write( $row, $col, $token, @options );
2142              
2143             # Return only the first error encountered, if any.
2144 5423   33     18791 $error ||= $ret;
2145 5423         9184 $row++;
2146             }
2147              
2148 1112         2556 return $error;
2149             }
2150              
2151              
2152             ###############################################################################
2153             #
2154             # write_comment($row, $col, $comment)
2155             #
2156             # Write a comment to the specified row and column (zero indexed).
2157             #
2158             # Returns 0 : normal termination
2159             # -1 : insufficient number of arguments
2160             # -2 : row or column out of range
2161             #
2162             sub write_comment {
2163              
2164 4153     4153 0 14244 my $self = shift;
2165              
2166             # Check for a cell reference in A1 notation and substitute row and column
2167 4153 100       10114 if ( $_[0] =~ /^\D/ ) {
2168 57         230 @_ = $self->_substitute_cellref( @_ );
2169             }
2170              
2171 4153 50       8578 if ( @_ < 3 ) { return -1 } # Check the number of args
  0         0  
2172              
2173 4153         6803 my $row = $_[0];
2174 4153         5450 my $col = $_[1];
2175              
2176             # Check for pairs of optional arguments, i.e. an odd number of args.
2177 4153 50       7719 croak "Uneven number of additional arguments" unless @_ % 2;
2178              
2179             # Check that row and col are valid and store max and min values
2180 4153 50       7451 return -2 if $self->_check_dimensions( $row, $col );
2181              
2182 4153         6742 $self->{_has_vml} = 1;
2183 4153         5815 $self->{_has_comments} = 1;
2184              
2185             # Process the properties of the cell comment.
2186 4153         16226 $self->{_comments}->{$row}->{$col} = [ @_ ];
2187             }
2188              
2189              
2190             ###############################################################################
2191             #
2192             # write_number($row, $col, $num, $format)
2193             #
2194             # Write a double to the specified row and column (zero indexed).
2195             # An integer can be written as a double. Excel will display an
2196             # integer. $format is optional.
2197             #
2198             # Returns 0 : normal termination
2199             # -1 : insufficient number of arguments
2200             # -2 : row or column out of range
2201             #
2202             sub write_number {
2203              
2204 7207     7207 0 10869 my $self = shift;
2205              
2206             # Check for a cell reference in A1 notation and substitute row and column
2207 7207 50       16806 if ( $_[0] =~ /^\D/ ) {
2208 0         0 @_ = $self->_substitute_cellref( @_ );
2209             }
2210              
2211 7207 50       14368 if ( @_ < 3 ) { return -1 } # Check the number of args
  0         0  
2212              
2213              
2214 7207         11440 my $row = $_[0]; # Zero indexed row
2215 7207         10074 my $col = $_[1]; # Zero indexed column
2216 7207         11200 my $num = $_[2] + 0;
2217 7207         10165 my $xf = $_[3]; # The cell format
2218 7207         10363 my $type = 'n'; # The data type
2219              
2220             # Check that row and col are valid and store max and min values
2221 7207 50       13732 return -2 if $self->_check_dimensions( $row, $col );
2222              
2223             # Write previous row if in in-line string optimization mode.
2224 7207 100 100     16554 if ( $self->{_optimization} == 1 && $row > $self->{_previous_row} ) {
2225 2         9 $self->_write_single_row( $row );
2226             }
2227              
2228 7207         21650 $self->{_table}->{$row}->{$col} = [ $type, $num, $xf ];
2229              
2230 7207         16075 return 0;
2231             }
2232              
2233              
2234             ###############################################################################
2235             #
2236             # write_string ($row, $col, $string, $format)
2237             #
2238             # Write a string to the specified row and column (zero indexed).
2239             # $format is optional.
2240             # Returns 0 : normal termination
2241             # -1 : insufficient number of arguments
2242             # -2 : row or column out of range
2243             # -3 : long string truncated to 32767 chars
2244             # -4 : Ignore undef strings
2245             #
2246             sub write_string {
2247              
2248 2985     2985 0 6522 my $self = shift;
2249              
2250             # Check for a cell reference in A1 notation and substitute row and column
2251 2985 100       8068 if ( $_[0] =~ /^\D/ ) {
2252 45         101 @_ = $self->_substitute_cellref( @_ );
2253             }
2254              
2255 2985 50       6445 if ( @_ < 3 ) { return -1 } # Check the number of args
  0         0  
2256              
2257 2985         4923 my $row = $_[0]; # Zero indexed row
2258 2985         4527 my $col = $_[1]; # Zero indexed column
2259 2985         4469 my $str = $_[2];
2260 2985         4319 my $xf = $_[3]; # The cell format
2261 2985         4369 my $type = 's'; # The data type
2262 2985         4503 my $index;
2263 2985         4651 my $str_error = 0;
2264              
2265             # Ignore undef strings.
2266 2985 50       6165 return -4 if !defined $str;
2267              
2268             # Check that row and col are valid and store max and min values
2269 2985 100       6251 return -2 if $self->_check_dimensions( $row, $col );
2270              
2271             # Check that the string is < 32767 chars
2272 2984 50       7093 if ( length $str > $self->{_xls_strmax} ) {
2273 0         0 $str = substr( $str, 0, $self->{_xls_strmax} );
2274 0         0 $str_error = -3;
2275             }
2276              
2277             # Write a shared string or an in-line string based on optimisation level.
2278 2984 100       6202 if ( $self->{_optimization} == 0 ) {
2279 2694         5789 $index = $self->_get_shared_string_index( $str );
2280             }
2281             else {
2282 290         459 $index = $str;
2283             }
2284              
2285             # Write previous row if in in-line string optimization mode.
2286 2984 100 100     8460 if ( $self->{_optimization} == 1 && $row > $self->{_previous_row} ) {
2287 278         773 $self->_write_single_row( $row );
2288             }
2289              
2290 2984         11196 $self->{_table}->{$row}->{$col} = [ $type, $index, $xf ];
2291              
2292 2984         7539 return $str_error;
2293             }
2294              
2295              
2296             ###############################################################################
2297             #
2298             # write_rich_string( $row, $column, $format, $string, ..., $cell_format )
2299             #
2300             # The write_rich_string() method is used to write strings with multiple formats.
2301             # The method receives string fragments prefixed by format objects. The final
2302             # format object is used as the cell format.
2303             #
2304             # Returns 0 : normal termination.
2305             # -1 : insufficient number of arguments.
2306             # -2 : row or column out of range.
2307             # -3 : long string truncated to 32767 chars.
2308             # -4 : 2 consecutive formats used.
2309             #
2310             sub write_rich_string {
2311              
2312 29     29 0 1112 my $self = shift;
2313              
2314             # Check for a cell reference in A1 notation and substitute row and column
2315 29 100       823 if ( $_[0] =~ /^\D/ ) {
2316 28         144 @_ = $self->_substitute_cellref( @_ );
2317             }
2318              
2319 29 50       110 if ( @_ < 3 ) { return -1 } # Check the number of args
  0         0  
2320              
2321 29         76 my $row = shift; # Zero indexed row.
2322 29         61 my $col = shift; # Zero indexed column.
2323 29         81 my $str = '';
2324 29         59 my $xf = undef;
2325 29         54 my $type = 's'; # The data type.
2326 29         54 my $length = 0; # String length.
2327 29         49 my $index;
2328 29         52 my $str_error = 0;
2329              
2330             # Check that row and col are valid and store max and min values
2331 29 50       100 return -2 if $self->_check_dimensions( $row, $col );
2332              
2333              
2334             # If the last arg is a format we use it as the cell format.
2335 29 100       108 if ( ref $_[-1] ) {
2336 3         8 $xf = pop @_;
2337             }
2338              
2339              
2340             # Create a temp XML::Writer object and use it to write the rich string
2341             # XML to a string.
2342 29 50   17   864 open my $str_fh, '>', \$str or die "Failed to open filehandle: $!";
  17         125  
  17         33  
  17         122  
2343 29         12711 binmode $str_fh, ':utf8';
2344              
2345 29         226 my $writer = Excel::Writer::XLSX::Package::XMLwriter->new( $str_fh );
2346              
2347 29         169 $self->{_rstring} = $writer;
2348              
2349             # Create a temp format with the default font for unformatted fragments.
2350 29         217 my $default = Excel::Writer::XLSX::Format->new();
2351              
2352             # Convert the list of $format, $string tokens to pairs of ($format, $string)
2353             # except for the first $string fragment which doesn't require a default
2354             # formatting run. Use the default for strings without a leading format.
2355 29         169 my @fragments;
2356 29         63 my $last = 'format';
2357 29         53 my $pos = 0;
2358              
2359 29         144 for my $token ( @_ ) {
2360 114 100       258 if ( !ref $token ) {
2361              
2362             # Token is a string.
2363 81 100       183 if ( $last ne 'format' ) {
2364              
2365             # If previous token wasn't a format add one before the string.
2366 25         66 push @fragments, ( $default, $token );
2367             }
2368             else {
2369              
2370             # If previous token was a format just add the string.
2371 56         114 push @fragments, $token;
2372             }
2373              
2374 81         129 $length += length $token; # Keep track of actual string length.
2375 81         127 $last = 'string';
2376             }
2377             else {
2378              
2379             # Can't allow 2 formats in a row.
2380 33 100 100     142 if ( $last eq 'format' && $pos > 0 ) {
2381 1         8 return -4;
2382             }
2383              
2384             # Token is a format object. Add it to the fragment list.
2385 32         62 push @fragments, $token;
2386 32         65 $last = 'format';
2387             }
2388              
2389 113         194 $pos++;
2390             }
2391              
2392              
2393             # If the first token is a string start the element.
2394 28 100       91 if ( !ref $fragments[0] ) {
2395 24         120 $self->{_rstring}->xml_start_tag( 'r' );
2396             }
2397              
2398             # Write the XML elements for the $format $string fragments.
2399 28         84 for my $token ( @fragments ) {
2400 136 100       329 if ( ref $token ) {
2401              
2402             # Write the font run.
2403 56         188 $self->{_rstring}->xml_start_tag( 'r' );
2404 56         177 $self->_write_font( $token );
2405             }
2406             else {
2407              
2408             # Write the string fragment part, with whitespace handling.
2409 80         142 my @attributes = ();
2410              
2411 80 100 100     480 if ( $token =~ /^\s/ || $token =~ /\s$/ ) {
2412 10         26 push @attributes, ( 'xml:space' => 'preserve' );
2413             }
2414              
2415 80         308 $self->{_rstring}->xml_data_element( 't', $token, @attributes );
2416 80         251 $self->{_rstring}->xml_end_tag( 'r' );
2417             }
2418             }
2419              
2420             # Check that the string is < 32767 chars.
2421 28 50       120 if ( $length > $self->{_xls_strmax} ) {
2422 0         0 return -3;
2423             }
2424              
2425              
2426             # Write a shared string or an in-line string based on optimisation level.
2427 28 100       118 if ( $self->{_optimization} == 0 ) {
2428 20         80 $index = $self->_get_shared_string_index( $str );
2429             }
2430             else {
2431 8         16 $index = $str;
2432             }
2433              
2434             # Write previous row if in in-line string optimization mode.
2435 28 100 66     141 if ( $self->{_optimization} == 1 && $row > $self->{_previous_row} ) {
2436 8         24 $self->_write_single_row( $row );
2437             }
2438              
2439 28         171 $self->{_table}->{$row}->{$col} = [ $type, $index, $xf ];
2440              
2441 28         254 return 0;
2442             }
2443              
2444              
2445             ###############################################################################
2446             #
2447             # write_blank($row, $col, $format)
2448             #
2449             # Write a blank cell to the specified row and column (zero indexed).
2450             # A blank cell is used to specify formatting without adding a string
2451             # or a number.
2452             #
2453             # A blank cell without a format serves no purpose. Therefore, we don't write
2454             # a BLANK record unless a format is specified. This is mainly an optimisation
2455             # for the write_row() and write_col() methods.
2456             #
2457             # Returns 0 : normal termination (including no format)
2458             # -1 : insufficient number of arguments
2459             # -2 : row or column out of range
2460             #
2461             sub write_blank {
2462              
2463 85     85 0 212 my $self = shift;
2464              
2465             # Check for a cell reference in A1 notation and substitute row and column
2466 85 50       290 if ( $_[0] =~ /^\D/ ) {
2467 0         0 @_ = $self->_substitute_cellref( @_ );
2468             }
2469              
2470             # Check the number of args
2471 85 50       216 return -1 if @_ < 2;
2472              
2473             # Don't write a blank cell unless it has a format
2474 85 100       291 return 0 if not defined $_[2];
2475              
2476 61         107 my $row = $_[0]; # Zero indexed row
2477 61         97 my $col = $_[1]; # Zero indexed column
2478 61         90 my $xf = $_[2]; # The cell format
2479 61         98 my $type = 'b'; # The data type
2480              
2481             # Check that row and col are valid and store max and min values
2482 61 50       134 return -2 if $self->_check_dimensions( $row, $col );
2483              
2484             # Write previous row if in in-line string optimization mode.
2485 61 50 66     188 if ( $self->{_optimization} == 1 && $row > $self->{_previous_row} ) {
2486 0         0 $self->_write_single_row( $row );
2487             }
2488              
2489 61         189 $self->{_table}->{$row}->{$col} = [ $type, undef, $xf ];
2490              
2491 61         192 return 0;
2492             }
2493              
2494              
2495             ###############################################################################
2496             #
2497             # write_formula($row, $col, $formula, $format)
2498             #
2499             # Write a formula to the specified row and column (zero indexed).
2500             #
2501             # $format is optional.
2502             #
2503             # Returns 0 : normal termination
2504             # -1 : insufficient number of arguments
2505             # -2 : row or column out of range
2506             #
2507             sub write_formula {
2508              
2509 114     114 0 346 my $self = shift;
2510              
2511             # Check for a cell reference in A1 notation and substitute row and column
2512 114 100       391 if ( $_[0] =~ /^\D/ ) {
2513 21         60 @_ = $self->_substitute_cellref( @_ );
2514             }
2515              
2516 114 50       305 if ( @_ < 3 ) { return -1 } # Check the number of args
  0         0  
2517              
2518 114         214 my $row = $_[0]; # Zero indexed row
2519 114         196 my $col = $_[1]; # Zero indexed column
2520 114         177 my $formula = $_[2]; # The formula text string
2521 114         293 my $xf = $_[3]; # The format object.
2522 114         292 my $value = $_[4]; # Optional formula value.
2523 114         189 my $type = 'f'; # The data type
2524              
2525             # Hand off array formulas.
2526 114 100       327 if ( $formula =~ /^{=.*}$/ ) {
2527 3         26 return $self->write_array_formula( $row, $col, $row, $col, $formula,
2528             $xf, $value );
2529             }
2530              
2531             # Check that row and col are valid and store max and min values
2532 111 50       262 return -2 if $self->_check_dimensions( $row, $col );
2533              
2534             # Remove the = sign if it exists.
2535 111         310 $formula =~ s/^=//;
2536              
2537             # Write previous row if in in-line string optimization mode.
2538 111 50 66     328 if ( $self->{_optimization} == 1 && $row > $self->{_previous_row} ) {
2539 0         0 $self->_write_single_row( $row );
2540             }
2541              
2542 111         416 $self->{_table}->{$row}->{$col} = [ $type, $formula, $xf, $value ];
2543              
2544 111         393 return 0;
2545             }
2546              
2547              
2548             ###############################################################################
2549             #
2550             # write_array_formula($row1, $col1, $row2, $col2, $formula, $format)
2551             #
2552             # Write an array formula to the specified row and column (zero indexed).
2553             #
2554             # $format is optional.
2555             #
2556             # Returns 0 : normal termination
2557             # -1 : insufficient number of arguments
2558             # -2 : row or column out of range
2559             #
2560             sub write_array_formula {
2561              
2562 7     7 0 31 my $self = shift;
2563              
2564             # Check for a cell reference in A1 notation and substitute row and column
2565 7 100       41 if ( $_[0] =~ /^\D/ ) {
2566 4         18 @_ = $self->_substitute_cellref( @_ );
2567             }
2568              
2569 7 50       40 if ( @_ < 5 ) { return -1 } # Check the number of args
  0         0  
2570              
2571 7         100 my $row1 = $_[0]; # First row
2572 7         80 my $col1 = $_[1]; # First column
2573 7         20 my $row2 = $_[2]; # Last row
2574 7         21 my $col2 = $_[3]; # Last column
2575 7         16 my $formula = $_[4]; # The formula text string
2576 7         13 my $xf = $_[5]; # The format object.
2577 7         9 my $value = $_[6]; # Optional formula value.
2578 7         15 my $type = 'a'; # The data type
2579              
2580             # Swap last row/col with first row/col as necessary
2581 7 50       31 ( $row1, $row2 ) = ( $row2, $row1 ) if $row1 > $row2;
2582 7 50       24 ( $col1, $col2 ) = ( $col1, $col2 ) if $col1 > $col2;
2583              
2584              
2585             # Check that row and col are valid and store max and min values
2586 7 50       22 return -2 if $self->_check_dimensions( $row2, $col2 );
2587              
2588              
2589             # Define array range
2590 7         14 my $range;
2591              
2592 7 100 66     33 if ( $row1 == $row2 and $col1 == $col2 ) {
2593 4         26 $range = xl_rowcol_to_cell( $row1, $col1 );
2594              
2595             }
2596             else {
2597 3         20 $range =
2598             xl_rowcol_to_cell( $row1, $col1 ) . ':'
2599             . xl_rowcol_to_cell( $row2, $col2 );
2600             }
2601              
2602             # Remove array formula braces and the leading =.
2603 7         65 $formula =~ s/^{(.*)}$/$1/;
2604 7         29 $formula =~ s/^=//;
2605              
2606             # Write previous row if in in-line string optimization mode.
2607 7         24 my $row = $row1;
2608 7 50 33     32 if ( $self->{_optimization} == 1 && $row > $self->{_previous_row} ) {
2609 0         0 $self->_write_single_row( $row );
2610             }
2611              
2612 7         30 $self->{_table}->{$row1}->{$col1} =
2613             [ $type, $formula, $xf, $range, $value ];
2614              
2615              
2616             # Pad out the rest of the area with formatted zeroes.
2617 7 50       25 if ( !$self->{_optimization} ) {
2618 7         22 for my $row ( $row1 .. $row2 ) {
2619 13         37 for my $col ( $col1 .. $col2 ) {
2620 13 100 66     61 next if $row == $row1 and $col == $col1;
2621 6         19 $self->write_number( $row, $col, 0, $xf );
2622             }
2623             }
2624             }
2625              
2626 7         26 return 0;
2627             }
2628              
2629              
2630             ###############################################################################
2631             #
2632             # write_blank($row, $col, $format)
2633             #
2634             # Write a boolean value to the specified row and column (zero indexed).
2635             #
2636             # Returns 0 : normal termination (including no format)
2637             # -2 : row or column out of range
2638             #
2639             sub write_boolean {
2640              
2641 4     4 0 24 my $self = shift;
2642              
2643             # Check for a cell reference in A1 notation and substitute row and column
2644 4 50       19 if ( $_[0] =~ /^\D/ ) {
2645 4         16 @_ = $self->_substitute_cellref( @_ );
2646             }
2647              
2648 4         8 my $row = $_[0]; # Zero indexed row
2649 4         7 my $col = $_[1]; # Zero indexed column
2650 4 100       11 my $val = $_[2] ? 1 : 0; # Boolean value.
2651 4         7 my $xf = $_[3]; # The cell format
2652 4         8 my $type = 'l'; # The data type
2653              
2654             # Check that row and col are valid and store max and min values
2655 4 50       13 return -2 if $self->_check_dimensions( $row, $col );
2656              
2657             # Write previous row if in in-line string optimization mode.
2658 4 50 33     29 if ( $self->{_optimization} == 1 && $row > $self->{_previous_row} ) {
2659 0         0 $self->_write_single_row( $row );
2660             }
2661              
2662 4         17 $self->{_table}->{$row}->{$col} = [ $type, $val, $xf ];
2663              
2664 4         13 return 0;
2665             }
2666              
2667              
2668             ###############################################################################
2669             #
2670             # outline_settings($visible, $symbols_below, $symbols_right, $auto_style)
2671             #
2672             # This method sets the properties for outlining and grouping. The defaults
2673             # correspond to Excel's defaults.
2674             #
2675             sub outline_settings {
2676              
2677 1     1 0 7 my $self = shift;
2678              
2679 1 50       8 $self->{_outline_on} = defined $_[0] ? $_[0] : 1;
2680 1 50       4 $self->{_outline_below} = defined $_[1] ? $_[1] : 1;
2681 1 50       18 $self->{_outline_right} = defined $_[2] ? $_[2] : 1;
2682 1   50     4 $self->{_outline_style} = $_[3] || 0;
2683              
2684 1         3 $self->{_outline_changed} = 1;
2685             }
2686              
2687              
2688             ###############################################################################
2689             #
2690             # Escape urls like Excel.
2691             #
2692             sub _escape_url {
2693              
2694 98     98   235 my $url = shift;
2695              
2696             # Don't escape URL if it looks already escaped.
2697 98 100       363 return $url if $url =~ /%[0-9a-fA-F]{2}/;
2698              
2699             # Escape the URL escape symbol.
2700 97         253 $url =~ s/%/%25/g;
2701              
2702             # Escape whitespace in URL.
2703 97         492 $url =~ s/[\s\x00]/%20/g;
2704              
2705             # Escape other special characters in URL.
2706 97         363 $url =~ s/(["<>[\]`^{}])/sprintf '%%%x', ord $1/eg;
  11         46  
2707              
2708 97         596 return $url;
2709             }
2710              
2711              
2712             ###############################################################################
2713             #
2714             # write_url($row, $col, $url, format, $string)
2715             #
2716             # Write a hyperlink. This is comprised of two elements: the visible label and
2717             # the invisible link. The visible label is the same as the link unless an
2718             # alternative string is specified. The label is written using the
2719             # write_string() method. Therefore the max characters string limit applies.
2720             # $string and $format are optional and their order is interchangeable.
2721             #
2722             # The hyperlink can be to a http, ftp, mail, internal sheet, or external
2723             # directory url.
2724             #
2725             # Returns 0 : normal termination
2726             # -1 : insufficient number of arguments
2727             # -2 : row or column out of range
2728             # -3 : long string truncated to 32767 chars
2729             # -4 : URL longer than 255 characters
2730             # -5 : Exceeds limit of 65_530 urls per worksheet
2731             #
2732             sub write_url {
2733              
2734 83     83 0 678 my $self = shift;
2735              
2736             # Check for a cell reference in A1 notation and substitute row and column
2737 83 100       507 if ( $_[0] =~ /^\D/ ) {
2738 68         373 @_ = $self->_substitute_cellref( @_ );
2739             }
2740              
2741 83 50       305 if ( @_ < 3 ) { return -1 } # Check the number of args
  0         0  
2742              
2743              
2744             # Reverse the order of $string and $format if necessary, for backward
2745             # compatibility. We work on a copy in order to protect the callers
2746             # args. We don't use "local @_" in case of perl50005 threads.
2747 83         274 my @args = @_;
2748 83 100 100     390 if (defined $args[3] and !ref $args[3]) {
2749 13         43 ( $args[3], $args[4] ) = ( $args[4], $args[3] );
2750             }
2751              
2752 83         186 my $row = $args[0]; # Zero indexed row
2753 83         166 my $col = $args[1]; # Zero indexed column
2754 83         164 my $url = $args[2]; # URL string
2755 83         153 my $xf = $args[3]; # Cell format
2756 83         159 my $str = $args[4]; # Alternative label
2757 83         138 my $tip = $args[5]; # Tool tip
2758 83         179 my $type = 'l'; # XML data type
2759 83         163 my $link_type = 1;
2760 83         149 my $external = 0;
2761              
2762             # The displayed string defaults to the url string.
2763 83 100       260 $str = $url unless defined $str;
2764              
2765             # Remove the URI scheme from internal links.
2766 83 100       304 if ( $url =~ s/^internal:// ) {
2767 8         17 $str =~ s/^internal://;
2768 8         15 $link_type = 2;
2769             }
2770              
2771             # Remove the URI scheme from external links and change the directory
2772             # separator from Unix to Dos.
2773 83 100       302 if ( $url =~ s/^external:// ) {
2774 15         60 $str =~ s/^external://;
2775 15         46 $url =~ s[/][\\]g;
2776 15         34 $str =~ s[/][\\]g;
2777 15         23 $external = 1;
2778             }
2779              
2780             # Strip the mailto header.
2781 83         182 $str =~ s/^mailto://;
2782              
2783             # Check that row and col are valid and store max and min values
2784 83 50       313 return -2 if $self->_check_dimensions( $row, $col );
2785              
2786             # Check that the string is < 32767 chars
2787 83         179 my $str_error = 0;
2788 83 50       295 if ( length $str > $self->{_xls_strmax} ) {
2789 0         0 $str = substr( $str, 0, $self->{_xls_strmax} );
2790 0         0 $str_error = -3;
2791             }
2792              
2793             # Copy string for use in hyperlink elements.
2794 83         192 my $url_str = $str;
2795              
2796             # External links to URLs and to other Excel workbooks have slightly
2797             # different characteristics that we have to account for.
2798 83 100       261 if ( $link_type == 1 ) {
2799              
2800             # Split url into the link and optional anchor/location.
2801 75         323 ( $url, $url_str ) = split /#/, $url, 2;
2802              
2803 75         276 $url = _escape_url( $url );
2804              
2805             # Escape the anchor for hyperlink style urls only.
2806 75 100 100     571 if ( $url_str && !$external ) {
2807 4         18 $url_str = _escape_url( $url_str );
2808             }
2809              
2810             # Add the file:/// URI to the url for Windows style "C:/" link and
2811             # Network shares.
2812 75 100 100     486 if ( $url =~ m{^\w:} || $url =~ m{^\\\\} ) {
2813 9         30 $url = 'file:///' . $url;
2814             }
2815              
2816             # Convert a ./dir/file.xlsx link to dir/file.xlsx.
2817 75         178 $url =~ s{^.\\}{};
2818             }
2819              
2820             # Excel limits the escaped URL and location/anchor to 255 characters.
2821 83   100     380 my $tmp_url_str = $url_str || '';
2822 83         376 my $max_url = $self->{_max_url_length};
2823              
2824 83 100 66     932 if ( length $url > $max_url || length $tmp_url_str > $max_url ) {
2825 1         281 carp "Ignoring URL '$url' where link or anchor > $max_url characters "
2826             . "since it exceeds Excel's limit for URLS. See LIMITATIONS "
2827             . "section of the Excel::Writer::XLSX documentation.";
2828 1         9 return -4;
2829             }
2830              
2831             # Check the limit of URLS per worksheet.
2832 82         723 $self->{_hlink_count}++;
2833              
2834 82 50       594 if ( $self->{_hlink_count} > 65_530 ) {
2835 0         0 carp "Ignoring URL '$url' since it exceeds Excel's limit of 65,530 "
2836             . "URLs per worksheet. See LIMITATIONS section of the "
2837             . "Excel::Writer::XLSX documentation.";
2838 0         0 return -5;
2839             }
2840              
2841             # Write previous row if in in-line string optimization mode.
2842 82 50 66     499 if ( $self->{_optimization} == 1 && $row > $self->{_previous_row} ) {
2843 0         0 $self->_write_single_row( $row );
2844             }
2845              
2846             # Add the default URL format.
2847 82 100       609 if ( !defined $xf ) {
2848 65         1082 $xf = $self->{_default_url_format};
2849             }
2850              
2851             # Write the hyperlink string.
2852 82         866 $self->write_string( $row, $col, $str, $xf );
2853              
2854             # Store the hyperlink data in a separate structure.
2855 82         448 $self->{_hyperlinks}->{$row}->{$col} = {
2856             _link_type => $link_type,
2857             _url => $url,
2858             _str => $url_str,
2859             _tip => $tip
2860             };
2861              
2862 82         301 return $str_error;
2863             }
2864              
2865              
2866             ###############################################################################
2867             #
2868             # write_date_time ($row, $col, $string, $format)
2869             #
2870             # Write a datetime string in ISO8601 "yyyy-mm-ddThh:mm:ss.ss" format as a
2871             # number representing an Excel date. $format is optional.
2872             #
2873             # Returns 0 : normal termination
2874             # -1 : insufficient number of arguments
2875             # -2 : row or column out of range
2876             # -3 : Invalid date_time, written as string
2877             #
2878             sub write_date_time {
2879              
2880 129     129 0 814 my $self = shift;
2881              
2882             # Check for a cell reference in A1 notation and substitute row and column
2883 129 100       460 if ( $_[0] =~ /^\D/ ) {
2884 12         35 @_ = $self->_substitute_cellref( @_ );
2885             }
2886              
2887 129 50       316 if ( @_ < 3 ) { return -1 } # Check the number of args
  0         0  
2888              
2889 129         227 my $row = $_[0]; # Zero indexed row
2890 129         205 my $col = $_[1]; # Zero indexed column
2891 129         202 my $str = $_[2];
2892 129         212 my $xf = $_[3]; # The cell format
2893 129         213 my $type = 'n'; # The data type
2894              
2895              
2896             # Check that row and col are valid and store max and min values
2897 129 50       287 return -2 if $self->_check_dimensions( $row, $col );
2898              
2899 129         228 my $str_error = 0;
2900 129         316 my $date_time = $self->convert_date_time( $str );
2901              
2902             # If the date isn't valid then write it as a string.
2903 129 50       400 if ( !defined $date_time ) {
2904 0         0 return $self->write_string( @_ );
2905             }
2906              
2907             # Write previous row if in in-line string optimization mode.
2908 129 50 33     346 if ( $self->{_optimization} == 1 && $row > $self->{_previous_row} ) {
2909 0         0 $self->_write_single_row( $row );
2910             }
2911              
2912 129         540 $self->{_table}->{$row}->{$col} = [ $type, $date_time, $xf ];
2913              
2914 129         357 return $str_error;
2915             }
2916              
2917              
2918             ###############################################################################
2919             #
2920             # convert_date_time($date_time_string)
2921             #
2922             # The function takes a date and time in ISO8601 "yyyy-mm-ddThh:mm:ss.ss" format
2923             # and converts it to a decimal number representing a valid Excel date.
2924             #
2925             # Dates and times in Excel are represented by real numbers. The integer part of
2926             # the number stores the number of days since the epoch and the fractional part
2927             # stores the percentage of the day in seconds. The epoch can be either 1900 or
2928             # 1904.
2929             #
2930             # Parameter: Date and time string in one of the following formats:
2931             # yyyy-mm-ddThh:mm:ss.ss # Standard
2932             # yyyy-mm-ddT # Date only
2933             # Thh:mm:ss.ss # Time only
2934             #
2935             # Returns:
2936             # A decimal number representing a valid Excel date, or
2937             # undef if the date is invalid.
2938             #
2939             sub convert_date_time {
2940              
2941 768     768 0 302707 my $self = shift;
2942 768         1376 my $date_time = $_[0];
2943              
2944 768         1161 my $days = 0; # Number of days since epoch
2945 768         1147 my $seconds = 0; # Time expressed as fraction of 24h hours in seconds
2946              
2947 768         1878 my ( $year, $month, $day );
2948 768         0 my ( $hour, $min, $sec );
2949              
2950              
2951             # Strip leading and trailing whitespace.
2952 768         1952 $date_time =~ s/^\s+//;
2953 768         1620 $date_time =~ s/\s+$//;
2954              
2955             # Check for invalid date char.
2956 768 100       2209 return if $date_time =~ /[^0-9T:\-\.Z]/;
2957              
2958             # Check for "T" after date or before time.
2959 767 100       3149 return unless $date_time =~ /\dT|T\d/;
2960              
2961             # Strip trailing Z in ISO8601 date.
2962 765         1279 $date_time =~ s/Z$//;
2963              
2964              
2965             # Split into date and time.
2966 765         2392 my ( $date, $time ) = split /T/, $date_time;
2967              
2968              
2969             # We allow the time portion of the input DateTime to be optional.
2970 765 100       1973 if ( $time ne '' ) {
2971              
2972             # Match hh:mm:ss.sss+ where the seconds are optional
2973 206 50       904 if ( $time =~ /^(\d\d):(\d\d)(:(\d\d(\.\d+)?))?/ ) {
2974 206         427 $hour = $1;
2975 206         377 $min = $2;
2976 206   100     553 $sec = $4 || 0;
2977             }
2978             else {
2979 0         0 return undef; # Not a valid time format.
2980             }
2981              
2982             # Some boundary checks
2983 206 100       515 return if $hour >= 24;
2984 205 100       401 return if $min >= 60;
2985 204 100       527 return if $sec >= 60;
2986              
2987             # Excel expresses seconds as a fraction of the number in 24 hours.
2988 202         482 $seconds = ( $hour * 60 * 60 + $min * 60 + $sec ) / ( 24 * 60 * 60 );
2989             }
2990              
2991              
2992             # We allow the date portion of the input DateTime to be optional.
2993 761 100       1664 return $seconds if $date eq '';
2994              
2995              
2996             # Match date as yyyy-mm-dd.
2997 759 100       2796 if ( $date =~ /^(\d\d\d\d)-(\d\d)-(\d\d)$/ ) {
2998 757         1731 $year = $1;
2999 757         1384 $month = $2;
3000 757         1291 $day = $3;
3001             }
3002             else {
3003 2         9 return undef; # Not a valid date format.
3004             }
3005              
3006             # Set the epoch as 1900 or 1904. Defaults to 1900.
3007 757         1345 my $date_1904 = $self->{_date_1904};
3008              
3009              
3010             # Special cases for Excel.
3011 757 100       1643 if ( not $date_1904 ) {
3012 542 100       1314 return $seconds if $date eq '1899-12-31'; # Excel 1900 epoch
3013 438 100       820 return $seconds if $date eq '1900-01-00'; # Excel 1900 epoch
3014 437 100       892 return 60 + $seconds if $date eq '1900-02-29'; # Excel false leapday
3015             }
3016              
3017              
3018             # We calculate the date by calculating the number of days since the epoch
3019             # and adjust for the number of leap days. We calculate the number of leap
3020             # days by normalising the year in relation to the epoch. Thus the year 2000
3021             # becomes 100 for 4 and 100 year leapdays and 400 for 400 year leapdays.
3022             #
3023 651 100       1308 my $epoch = $date_1904 ? 1904 : 1900;
3024 651 100       1208 my $offset = $date_1904 ? 4 : 0;
3025 651         919 my $norm = 300;
3026 651         1572 my $range = $year - $epoch;
3027              
3028              
3029             # Set month days and check for leap year.
3030 651         1564 my @mdays = ( 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 );
3031 651         937 my $leap = 0;
3032 651 100 100     3298 $leap = 1 if $year % 4 == 0 and $year % 100 or $year % 400 == 0;
      100        
3033 651 100       1410 $mdays[1] = 29 if $leap;
3034              
3035              
3036             # Some boundary checks
3037 651 100 66     2181 return if $year < $epoch or $year > 9999;
3038 645 100 100     2074 return if $month < 1 or $month > 12;
3039 639 100 100     2138 return if $day < 1 or $day > $mdays[ $month - 1 ];
3040              
3041             # Accumulate the number of days since the epoch.
3042 633         1137 $days = $day; # Add days for current month
3043 633         2286 $days += $mdays[$_] for 0 .. $month - 2; # Add days for past months
3044 633         1168 $days += $range * 365; # Add days for past years
3045 633         1226 $days += int( ( $range ) / 4 ); # Add leapdays
3046 633         1109 $days -= int( ( $range + $offset ) / 100 ); # Subtract 100 year leapdays
3047 633         1058 $days += int( ( $range + $offset + $norm ) / 400 ); # Add 400 year leapdays
3048 633         916 $days -= $leap; # Already counted above
3049              
3050              
3051             # Adjust for Excel erroneously treating 1900 as a leap year.
3052 633 100 100     1987 $days++ if $date_1904 == 0 and $days > 59;
3053              
3054 633         1963 return $days + $seconds;
3055             }
3056              
3057              
3058             ###############################################################################
3059             #
3060             # set_row($row, $height, $XF, $hidden, $level, $collapsed)
3061             #
3062             # This method is used to set the height and XF format for a row.
3063             #
3064             sub set_row {
3065              
3066 382     382 0 2366 my $self = shift;
3067 382         659 my $row = shift; # Row Number.
3068 382         561 my $height = shift; # Row height.
3069 382         585 my $xf = shift; # Format object.
3070 382   100     980 my $hidden = shift || 0; # Hidden flag.
3071 382   100     1063 my $level = shift || 0; # Outline level.
3072 382   100     1042 my $collapsed = shift || 0; # Collapsed row.
3073 382         573 my $min_col = 0;
3074              
3075 382 50       782 return unless defined $row; # Ensure at least $row is specified.
3076              
3077             # Get the default row height.
3078 382         761 my $default_height = $self->{_default_row_height};
3079              
3080             # Use min col in _check_dimensions(). Default to 0 if undefined.
3081 382 100       848 if ( defined $self->{_dim_colmin} ) {
3082 349         542 $min_col = $self->{_dim_colmin};
3083             }
3084              
3085             # Check that row is valid.
3086 382 50       927 return -2 if $self->_check_dimensions( $row, $min_col );
3087              
3088 382 100       1231 $height = $default_height if !defined $height;
3089              
3090             # If the height is 0 the row is hidden and the height is the default.
3091 382 100       833 if ( $height == 0 ) {
3092 1         2 $hidden = 1;
3093 1         3 $height = $default_height;
3094             }
3095              
3096             # Set the limits for the outline levels (0 <= x <= 7).
3097 382 50       803 $level = 0 if $level < 0;
3098 382 50       798 $level = 7 if $level > 7;
3099              
3100 382 100       901 if ( $level > $self->{_outline_row_level} ) {
3101 11         24 $self->{_outline_row_level} = $level;
3102             }
3103              
3104             # Store the row properties.
3105 382         1324 $self->{_set_rows}->{$row} = [ $height, $xf, $hidden, $level, $collapsed ];
3106              
3107             # Store the row change to allow optimisations.
3108 382         724 $self->{_row_size_changed} = 1;
3109              
3110             # Store the row sizes for use when calculating image vertices.
3111 382         1216 $self->{_row_sizes}->{$row} = [$height, $hidden];
3112             }
3113              
3114              
3115             ###############################################################################
3116             #
3117             # set_default_row()
3118             #
3119             # Set the default row properties
3120             #
3121             sub set_default_row {
3122              
3123 6     6 0 43 my $self = shift;
3124 6   66     38 my $height = shift || $self->{_original_row_height};
3125 6   100     30 my $zero_height = shift || 0;
3126              
3127 6 100       52 if ( $height != $self->{_original_row_height} ) {
3128 5         16 $self->{_default_row_height} = $height;
3129              
3130             # Store the row change to allow optimisations.
3131 5         13 $self->{_row_size_changed} = 1;
3132             }
3133              
3134 6 100       25 if ( $zero_height ) {
3135 3         8 $self->{_default_row_zeroed} = 1;
3136             }
3137             }
3138              
3139              
3140             ###############################################################################
3141             #
3142             # merge_range($first_row, $first_col, $last_row, $last_col, $string, $format)
3143             #
3144             # Merge a range of cells. The first cell should contain the data and the others
3145             # should be blank. All cells should contain the same format.
3146             #
3147             sub merge_range {
3148              
3149 20     20 0 187 my $self = shift;
3150              
3151             # Check for a cell reference in A1 notation and substitute row and column
3152 20 100       97 if ( $_[0] =~ /^\D/ ) {
3153 12         48 @_ = $self->_substitute_cellref( @_ );
3154             }
3155 20 50       96 croak "Incorrect number of arguments" if @_ < 6;
3156 20 50       72 croak "Fifth parameter must be a format object" unless ref $_[5];
3157              
3158 20         41 my $row_first = shift;
3159 20         39 my $col_first = shift;
3160 20         45 my $row_last = shift;
3161 20         128 my $col_last = shift;
3162 20         99 my $string = shift;
3163 20         108 my $format = shift;
3164 20         54 my @extra_args = @_; # For write_url().
3165              
3166             # Excel doesn't allow a single cell to be merged
3167 20 50 66     113 if ( $row_first == $row_last and $col_first == $col_last ) {
3168 0         0 croak "Can't merge single cell";
3169             }
3170              
3171             # Swap last row/col with first row/col as necessary
3172 20 50       60 ( $row_first, $row_last ) = ( $row_last, $row_first )
3173             if $row_first > $row_last;
3174 20 50       54 ( $col_first, $col_last ) = ( $col_last, $col_first )
3175             if $col_first > $col_last;
3176              
3177             # Check that column number is valid and store the max value
3178 20 50       75 return if $self->_check_dimensions( $row_last, $col_last );
3179              
3180             # Store the merge range.
3181 20         53 push @{ $self->{_merge} }, [ $row_first, $col_first, $row_last, $col_last ];
  20         243  
3182              
3183             # Write the first cell
3184 20         247 $self->write( $row_first, $col_first, $string, $format, @extra_args );
3185              
3186             # Pad out the rest of the area with formatted blank cells.
3187 20         76 for my $row ( $row_first .. $row_last ) {
3188 30         62 for my $col ( $col_first .. $col_last ) {
3189 68 100 100     222 next if $row == $row_first and $col == $col_first;
3190 48         231 $self->write_blank( $row, $col, $format );
3191             }
3192             }
3193             }
3194              
3195              
3196             ###############################################################################
3197             #
3198             # merge_range_type()
3199             #
3200             # Same as merge_range() above except the type of write() is specified.
3201             #
3202             sub merge_range_type {
3203              
3204 7     7 0 37 my $self = shift;
3205 7         12 my $type = shift;
3206              
3207             # Check for a cell reference in A1 notation and substitute row and column
3208 7 50       25 if ( $_[0] =~ /^\D/ ) {
3209 7         21 @_ = $self->_substitute_cellref( @_ );
3210             }
3211              
3212 7         15 my $row_first = shift;
3213 7         12 my $col_first = shift;
3214 7         12 my $row_last = shift;
3215 7         13 my $col_last = shift;
3216 7         10 my $format;
3217              
3218             # Get the format. It can be in different positions for the different types.
3219 7 100 66     37 if ( $type eq 'array_formula'
      100        
3220             || $type eq 'blank'
3221             || $type eq 'rich_string' )
3222             {
3223              
3224             # The format is the last element.
3225 2         17 $format = $_[-1];
3226             }
3227             else {
3228              
3229             # Or else it is after the token.
3230 5         11 $format = $_[1];
3231             }
3232              
3233             # Check that there is a format object.
3234 7 50       19 croak "Format object missing or in an incorrect position"
3235             unless ref $format;
3236              
3237             # Excel doesn't allow a single cell to be merged
3238 7 50 33     25 if ( $row_first == $row_last and $col_first == $col_last ) {
3239 0         0 croak "Can't merge single cell";
3240             }
3241              
3242             # Swap last row/col with first row/col as necessary
3243 7 50       16 ( $row_first, $row_last ) = ( $row_last, $row_first )
3244             if $row_first > $row_last;
3245 7 50       23 ( $col_first, $col_last ) = ( $col_last, $col_first )
3246             if $col_first > $col_last;
3247              
3248             # Check that column number is valid and store the max value
3249 7 50       18 return if $self->_check_dimensions( $row_last, $col_last );
3250              
3251             # Store the merge range.
3252 7         14 push @{ $self->{_merge} }, [ $row_first, $col_first, $row_last, $col_last ];
  7         26  
3253              
3254             # Write the first cell
3255 7 100       38 if ( $type eq 'string' ) {
    100          
    100          
    100          
    100          
    100          
    50          
    0          
3256 1         9 $self->write_string( $row_first, $col_first, @_ );
3257             }
3258             elsif ( $type eq 'number' ) {
3259 1         5 $self->write_number( $row_first, $col_first, @_ );
3260             }
3261             elsif ( $type eq 'blank' ) {
3262 1         6 $self->write_blank( $row_first, $col_first, @_ );
3263             }
3264             elsif ( $type eq 'date_time' ) {
3265 1         5 $self->write_date_time( $row_first, $col_first, @_ );
3266             }
3267             elsif ( $type eq 'rich_string' ) {
3268 1         9 $self->write_rich_string( $row_first, $col_first, @_ );
3269             }
3270             elsif ( $type eq 'url' ) {
3271 1         4 $self->write_url( $row_first, $col_first, @_ );
3272             }
3273             elsif ( $type eq 'formula' ) {
3274 1         7 $self->write_formula( $row_first, $col_first, @_ );
3275             }
3276             elsif ( $type eq 'array_formula' ) {
3277 0         0 $self->write_formula_array( $row_first, $col_first, @_ );
3278             }
3279             else {
3280 0         0 croak "Unknown type '$type'";
3281             }
3282              
3283             # Pad out the rest of the area with formatted blank cells.
3284 7         19 for my $row ( $row_first .. $row_last ) {
3285 7         15 for my $col ( $col_first .. $col_last ) {
3286 14 100 66     46 next if $row == $row_first and $col == $col_first;
3287 7         22 $self->write_blank( $row, $col, $format );
3288             }
3289             }
3290             }
3291              
3292              
3293             ###############################################################################
3294             #
3295             # data_validation($row, $col, {...})
3296             #
3297             # This method handles the interface to Excel data validation.
3298             # Somewhat ironically this requires a lot of validation code since the
3299             # interface is flexible and covers a several types of data validation.
3300             #
3301             # We allow data validation to be called on one cell or a range of cells. The
3302             # hashref contains the validation parameters and must be the last param:
3303             # data_validation($row, $col, {...})
3304             # data_validation($first_row, $first_col, $last_row, $last_col, {...})
3305             #
3306             # Returns 0 : normal termination
3307             # -1 : insufficient number of arguments
3308             # -2 : row or column out of range
3309             # -3 : incorrect parameter.
3310             #
3311             sub data_validation {
3312              
3313 68     68 0 1763 my $self = shift;
3314              
3315             # Check for a cell reference in A1 notation and substitute row and column
3316 68 100       339 if ( $_[0] =~ /^\D/ ) {
3317 63         205 @_ = $self->_substitute_cellref( @_ );
3318             }
3319              
3320             # Check for a valid number of args.
3321 68 50 66     315 if ( @_ != 5 && @_ != 3 ) { return -1 }
  0         0  
3322              
3323             # The final hashref contains the validation parameters.
3324 68         124 my $param = pop;
3325              
3326             # Make the last row/col the same as the first if not defined.
3327 68         158 my ( $row1, $col1, $row2, $col2 ) = @_;
3328 68 100       162 if ( !defined $row2 ) {
3329 63         98 $row2 = $row1;
3330 63         107 $col2 = $col1;
3331             }
3332              
3333             # Check that row and col are valid without storing the values.
3334 68 50       188 return -2 if $self->_check_dimensions( $row1, $col1, 1, 1 );
3335 68 50       167 return -2 if $self->_check_dimensions( $row2, $col2, 1, 1 );
3336              
3337              
3338             # Check that the last parameter is a hash list.
3339 68 50       202 if ( ref $param ne 'HASH' ) {
3340 0         0 carp "Last parameter '$param' in data_validation() must be a hash ref";
3341 0         0 return -3;
3342             }
3343              
3344             # List of valid input parameters.
3345 68         451 my %valid_parameter = (
3346             validate => 1,
3347             criteria => 1,
3348             value => 1,
3349             source => 1,
3350             minimum => 1,
3351             maximum => 1,
3352             ignore_blank => 1,
3353             dropdown => 1,
3354             show_input => 1,
3355             input_title => 1,
3356             input_message => 1,
3357             show_error => 1,
3358             error_title => 1,
3359             error_message => 1,
3360             error_type => 1,
3361             other_cells => 1,
3362             );
3363              
3364             # Check for valid input parameters.
3365 68         275 for my $param_key ( keys %$param ) {
3366 262 50       546 if ( not exists $valid_parameter{$param_key} ) {
3367 0         0 carp "Unknown parameter '$param_key' in data_validation()";
3368 0         0 return -3;
3369             }
3370             }
3371              
3372             # Map alternative parameter names 'source' or 'minimum' to 'value'.
3373 68 100       202 $param->{value} = $param->{source} if defined $param->{source};
3374 68 100       176 $param->{value} = $param->{minimum} if defined $param->{minimum};
3375              
3376             # 'validate' is a required parameter.
3377 68 50       181 if ( not exists $param->{validate} ) {
3378 0         0 carp "Parameter 'validate' is required in data_validation()";
3379 0         0 return -3;
3380             }
3381              
3382              
3383             # List of valid validation types.
3384 68         490 my %valid_type = (
3385             'any' => 'none',
3386             'any value' => 'none',
3387             'whole number' => 'whole',
3388             'whole' => 'whole',
3389             'integer' => 'whole',
3390             'decimal' => 'decimal',
3391             'list' => 'list',
3392             'date' => 'date',
3393             'time' => 'time',
3394             'text length' => 'textLength',
3395             'length' => 'textLength',
3396             'custom' => 'custom',
3397             );
3398              
3399              
3400             # Check for valid validation types.
3401 68 50       222 if ( not exists $valid_type{ lc( $param->{validate} ) } ) {
3402 0         0 carp "Unknown validation type '$param->{validate}' for parameter "
3403             . "'validate' in data_validation()";
3404 0         0 return -3;
3405             }
3406             else {
3407 68         162 $param->{validate} = $valid_type{ lc( $param->{validate} ) };
3408             }
3409              
3410             # No action is required for validation type 'any'
3411             # unless there are input messages.
3412 68 100 100     192 if ( $param->{validate} eq 'none'
      66        
3413             && !defined $param->{input_message}
3414             && !defined $param->{input_title} )
3415             {
3416 1         7 return 0;
3417             }
3418              
3419             # The any, list and custom validations don't have a criteria
3420             # so we use a default of 'between'.
3421 67 100 100     356 if ( $param->{validate} eq 'none'
      100        
3422             || $param->{validate} eq 'list'
3423             || $param->{validate} eq 'custom' )
3424             {
3425 18         54 $param->{criteria} = 'between';
3426 18         37 $param->{maximum} = undef;
3427             }
3428              
3429             # 'criteria' is a required parameter.
3430 67 50       156 if ( not exists $param->{criteria} ) {
3431 0         0 carp "Parameter 'criteria' is required in data_validation()";
3432 0         0 return -3;
3433             }
3434              
3435              
3436             # List of valid criteria types.
3437 67         580 my %criteria_type = (
3438             'between' => 'between',
3439             'not between' => 'notBetween',
3440             'equal to' => 'equal',
3441             '=' => 'equal',
3442             '==' => 'equal',
3443             'not equal to' => 'notEqual',
3444             '!=' => 'notEqual',
3445             '<>' => 'notEqual',
3446             'greater than' => 'greaterThan',
3447             '>' => 'greaterThan',
3448             'less than' => 'lessThan',
3449             '<' => 'lessThan',
3450             'greater than or equal to' => 'greaterThanOrEqual',
3451             '>=' => 'greaterThanOrEqual',
3452             'less than or equal to' => 'lessThanOrEqual',
3453             '<=' => 'lessThanOrEqual',
3454             );
3455              
3456             # Check for valid criteria types.
3457 67 50       190 if ( not exists $criteria_type{ lc( $param->{criteria} ) } ) {
3458 0         0 carp "Unknown criteria type '$param->{criteria}' for parameter "
3459             . "'criteria' in data_validation()";
3460 0         0 return -3;
3461             }
3462             else {
3463 67         149 $param->{criteria} = $criteria_type{ lc( $param->{criteria} ) };
3464             }
3465              
3466              
3467             # 'Between' and 'Not between' criteria require 2 values.
3468 67 100 100     209 if ( $param->{criteria} eq 'between' || $param->{criteria} eq 'notBetween' )
3469             {
3470 42 50       100 if ( not exists $param->{maximum} ) {
3471 0         0 carp "Parameter 'maximum' is required in data_validation() "
3472             . "when using 'between' or 'not between' criteria";
3473 0         0 return -3;
3474             }
3475             }
3476             else {
3477 25         48 $param->{maximum} = undef;
3478             }
3479              
3480              
3481             # List of valid error dialog types.
3482 67         204 my %error_type = (
3483             'stop' => 0,
3484             'warning' => 1,
3485             'information' => 2,
3486             );
3487              
3488             # Check for valid error dialog types.
3489 67 100       158 if ( not exists $param->{error_type} ) {
    50          
3490 65         118 $param->{error_type} = 0;
3491             }
3492             elsif ( not exists $error_type{ lc( $param->{error_type} ) } ) {
3493 0         0 carp "Unknown criteria type '$param->{error_type}' for parameter "
3494             . "'error_type' in data_validation()";
3495 0         0 return -3;
3496             }
3497             else {
3498 2         6 $param->{error_type} = $error_type{ lc( $param->{error_type} ) };
3499             }
3500              
3501              
3502             # Convert date/times value if required.
3503 67 100 100     268 if ( $param->{validate} eq 'date' || $param->{validate} eq 'time' ) {
3504 7         31 my $date_time = $self->convert_date_time( $param->{value} );
3505              
3506 7 100       19 if ( defined $date_time ) {
3507 5         11 $param->{value} = $date_time;
3508             }
3509              
3510 7 100       76 if ( defined $param->{maximum} ) {
3511 3         15 my $date_time = $self->convert_date_time( $param->{maximum} );
3512              
3513 3 100       13 if ( defined $date_time ) {
3514 2         5 $param->{maximum} = $date_time;
3515             }
3516             }
3517             }
3518              
3519             # Check that the input title doesn't exceed the maximum length.
3520 67 100 100     206 if ( $param->{input_title} and length $param->{input_title} > 32 ) {
3521 1         279 carp "Length of input title '$param->{input_title}'"
3522             . " exceeds Excel's limit of 32";
3523 1         15 return -3;
3524             }
3525              
3526             # Check that the error title don't exceed the maximum length.
3527 66 50 66     170 if ( $param->{error_title} and length $param->{error_title} > 32 ) {
3528 0         0 carp "Length of error title '$param->{error_title}'"
3529             . " exceeds Excel's limit of 32";
3530 0         0 return -3;
3531             }
3532              
3533             # Check that the input message don't exceed the maximum length.
3534 66 100 100     198 if ( $param->{input_message} and length $param->{input_message} > 255 ) {
3535 1         239 carp "Length of input message '$param->{input_message}'"
3536             . " exceeds Excel's limit of 255";
3537 1         15 return -3;
3538             }
3539              
3540             # Check that the error message don't exceed the maximum length.
3541 65 50 66     160 if ( $param->{error_message} and length $param->{error_message} > 255 ) {
3542 0         0 carp "Length of error message '$param->{error_message}'"
3543             . " exceeds Excel's limit of 255";
3544 0         0 return -3;
3545             }
3546              
3547             # Check that the input list don't exceed the maximum length.
3548 65 100       157 if ( $param->{validate} eq 'list' ) {
3549              
3550 13 100       61 if ( ref $param->{value} eq 'ARRAY' ) {
3551              
3552 11         27 my $formula = join ',', @{ $param->{value} };
  11         52  
3553 11 100       45 if ( length $formula > 255 ) {
3554 1         235 carp "Length of list items '$formula' exceeds Excel's "
3555             . "limit of 255, use a formula range instead";
3556 1         14 return -3;
3557             }
3558             }
3559             }
3560              
3561             # Set some defaults if they haven't been defined by the user.
3562 64 100       188 $param->{ignore_blank} = 1 if !defined $param->{ignore_blank};
3563 64 100       193 $param->{dropdown} = 1 if !defined $param->{dropdown};
3564 64 100       189 $param->{show_input} = 1 if !defined $param->{show_input};
3565 64 100       152 $param->{show_error} = 1 if !defined $param->{show_error};
3566              
3567              
3568             # These are the cells to which the validation is applied.
3569 64         209 $param->{cells} = [ [ $row1, $col1, $row2, $col2 ] ];
3570              
3571             # A (for now) undocumented parameter to pass additional cell ranges.
3572 64 100       183 if ( exists $param->{other_cells} ) {
3573              
3574 3         5 push @{ $param->{cells} }, @{ $param->{other_cells} };
  3         7  
  3         16  
3575             }
3576              
3577             # Store the validation information until we close the worksheet.
3578 64         582 push @{ $self->{_validations} }, $param;
  64         980  
3579             }
3580              
3581              
3582             ###############################################################################
3583             #
3584             # conditional_formatting($row, $col, {...})
3585             #
3586             # This method handles the interface to Excel conditional formatting.
3587             #
3588             # We allow the format to be called on one cell or a range of cells. The
3589             # hashref contains the formatting parameters and must be the last param:
3590             # conditional_formatting($row, $col, {...})
3591             # conditional_formatting($first_row, $first_col, $last_row, $last_col, {...})
3592             #
3593             # Returns 0 : normal termination
3594             # -1 : insufficient number of arguments
3595             # -2 : row or column out of range
3596             # -3 : incorrect parameter.
3597             #
3598             sub conditional_formatting {
3599              
3600 149     149 0 1545 my $self = shift;
3601 149         307 my $user_range = '';
3602              
3603             # Check for a cell reference in A1 notation and substitute row and column
3604 149 50       684 if ( $_[0] =~ /^\D/ ) {
3605              
3606             # Check for a user defined multiple range like B3:K6,B8:K11.
3607 149 100       564 if ( $_[0] =~ /,/ ) {
3608 1         4 $user_range = $_[0];
3609 1         5 $user_range =~ s/^=//;
3610 1         10 $user_range =~ s/\s*,\s*/ /g;
3611 1         6 $user_range =~ s/\$//g;
3612             }
3613              
3614 149         512 @_ = $self->_substitute_cellref( @_ );
3615             }
3616              
3617             # The final hashref contains the validation parameters.
3618 149         340 my $options = pop;
3619              
3620             # Make the last row/col the same as the first if not defined.
3621 149         408 my ( $row1, $col1, $row2, $col2 ) = @_;
3622 149 100       424 if ( !defined $row2 ) {
3623 74         150 $row2 = $row1;
3624 74         131 $col2 = $col1;
3625             }
3626              
3627             # Check that row and col are valid without storing the values.
3628 149 50       509 return -2 if $self->_check_dimensions( $row1, $col1, 1, 1 );
3629 149 50       610 return -2 if $self->_check_dimensions( $row2, $col2, 1, 1 );
3630              
3631              
3632             # Check that the last parameter is a hash list.
3633 149 50       705 if ( ref $options ne 'HASH' ) {
3634 0         0 carp "Last parameter in conditional_formatting() "
3635             . "must be a hash ref";
3636 0         0 return -3;
3637             }
3638              
3639             # Copy the user params.
3640 149         762 my $param = {%$options};
3641              
3642             # List of valid input parameters.
3643 149         2846 my %valid_parameter = (
3644             type => 1,
3645             format => 1,
3646             criteria => 1,
3647             value => 1,
3648             minimum => 1,
3649             maximum => 1,
3650             stop_if_true => 1,
3651             min_type => 1,
3652             mid_type => 1,
3653             max_type => 1,
3654             min_value => 1,
3655             mid_value => 1,
3656             max_value => 1,
3657             min_color => 1,
3658             mid_color => 1,
3659             max_color => 1,
3660             bar_color => 1,
3661             bar_negative_color => 1,
3662             bar_negative_color_same => 1,
3663             bar_solid => 1,
3664             bar_border_color => 1,
3665             bar_negative_border_color => 1,
3666             bar_negative_border_color_same => 1,
3667             bar_no_border => 1,
3668             bar_direction => 1,
3669             bar_axis_position => 1,
3670             bar_axis_color => 1,
3671             bar_only => 1,
3672             icon_style => 1,
3673             reverse_icons => 1,
3674             icons_only => 1,
3675             icons => 1,
3676             data_bar_2010 => 1,
3677             );
3678              
3679             # Check for valid input parameters.
3680 149         1143 for my $param_key ( keys %$param ) {
3681 492 50       1248 if ( not exists $valid_parameter{$param_key} ) {
3682 0         0 carp "Unknown parameter '$param_key' in conditional_formatting()";
3683 0         0 return -3;
3684             }
3685             }
3686              
3687             # 'type' is a required parameter.
3688 149 50       759 if ( not exists $param->{type} ) {
3689 0         0 carp "Parameter 'type' is required in conditional_formatting()";
3690 0         0 return -3;
3691             }
3692              
3693             # List of valid validation types.
3694 149         1916 my %valid_type = (
3695             'cell' => 'cellIs',
3696             'date' => 'date',
3697             'time' => 'time',
3698             'average' => 'aboveAverage',
3699             'duplicate' => 'duplicateValues',
3700             'unique' => 'uniqueValues',
3701             'top' => 'top10',
3702             'bottom' => 'top10',
3703             'text' => 'text',
3704             'time_period' => 'timePeriod',
3705             'blanks' => 'containsBlanks',
3706             'no_blanks' => 'notContainsBlanks',
3707             'errors' => 'containsErrors',
3708             'no_errors' => 'notContainsErrors',
3709             '2_color_scale' => '2_color_scale',
3710             '3_color_scale' => '3_color_scale',
3711             'data_bar' => 'dataBar',
3712             'formula' => 'expression',
3713             'icon_set' => 'iconSet',
3714             );
3715              
3716              
3717             # Check for valid validation types.
3718 149 50       627 if ( not exists $valid_type{ lc( $param->{type} ) } ) {
3719 0         0 carp "Unknown validation type '$param->{type}' for parameter "
3720             . "'type' in conditional_formatting()";
3721 0         0 return -3;
3722             }
3723             else {
3724 149 100       630 $param->{direction} = 'bottom' if $param->{type} eq 'bottom';
3725 149         764 $param->{type} = $valid_type{ lc( $param->{type} ) };
3726             }
3727              
3728              
3729             # List of valid criteria types.
3730 149         2399 my %criteria_type = (
3731             'between' => 'between',
3732             'not between' => 'notBetween',
3733             'equal to' => 'equal',
3734             '=' => 'equal',
3735             '==' => 'equal',
3736             'not equal to' => 'notEqual',
3737             '!=' => 'notEqual',
3738             '<>' => 'notEqual',
3739             'greater than' => 'greaterThan',
3740             '>' => 'greaterThan',
3741             'less than' => 'lessThan',
3742             '<' => 'lessThan',
3743             'greater than or equal to' => 'greaterThanOrEqual',
3744             '>=' => 'greaterThanOrEqual',
3745             'less than or equal to' => 'lessThanOrEqual',
3746             '<=' => 'lessThanOrEqual',
3747             'containing' => 'containsText',
3748             'not containing' => 'notContains',
3749             'begins with' => 'beginsWith',
3750             'ends with' => 'endsWith',
3751             'yesterday' => 'yesterday',
3752             'today' => 'today',
3753             'last 7 days' => 'last7Days',
3754             'last week' => 'lastWeek',
3755             'this week' => 'thisWeek',
3756             'next week' => 'nextWeek',
3757             'last month' => 'lastMonth',
3758             'this month' => 'thisMonth',
3759             'next month' => 'nextMonth',
3760             );
3761              
3762             # Check for valid criteria types.
3763 149 100 100     727 if ( defined $param->{criteria}
3764             && exists $criteria_type{ lc( $param->{criteria} ) } )
3765             {
3766 53         159 $param->{criteria} = $criteria_type{ lc( $param->{criteria} ) };
3767             }
3768              
3769             # Convert date/times value if required.
3770 149 100 66     813 if ( $param->{type} eq 'date' || $param->{type} eq 'time' ) {
3771 2         5 $param->{type} = 'cellIs';
3772              
3773 2 100 66     12 if ( defined $param->{value} && $param->{value} =~ /T/ ) {
3774 1         5 my $date_time = $self->convert_date_time( $param->{value} );
3775              
3776 1 50       3 if ( !defined $date_time ) {
3777 0         0 carp "Invalid date/time value '$param->{value}' "
3778             . "in conditional_formatting()";
3779 0         0 return -3;
3780             }
3781             else {
3782 1         3 $param->{value} = $date_time;
3783             }
3784             }
3785              
3786 2 100 66     12 if ( defined $param->{minimum} && $param->{minimum} =~ /T/ ) {
3787 1         6 my $date_time = $self->convert_date_time( $param->{minimum} );
3788              
3789 1 50       4 if ( !defined $date_time ) {
3790 0         0 carp "Invalid date/time value '$param->{minimum}' "
3791             . "in conditional_formatting()";
3792 0         0 return -3;
3793             }
3794             else {
3795 1         9 $param->{minimum} = $date_time;
3796             }
3797             }
3798              
3799 2 100 66     13 if ( defined $param->{maximum} && $param->{maximum} =~ /T/ ) {
3800 1         3 my $date_time = $self->convert_date_time( $param->{maximum} );
3801              
3802 1 50       3 if ( !defined $date_time ) {
3803 0         0 carp "Invalid date/time value '$param->{maximum}' "
3804             . "in conditional_formatting()";
3805 0         0 return -3;
3806             }
3807             else {
3808 1         3 $param->{maximum} = $date_time;
3809             }
3810             }
3811             }
3812              
3813              
3814             # List of valid icon styles.
3815 149         1521 my %icon_set_styles = (
3816             "3_arrows" => "3Arrows", # 1
3817             "3_flags" => "3Flags", # 2
3818             "3_traffic_lights_rimmed" => "3TrafficLights2", # 3
3819             "3_symbols_circled" => "3Symbols", # 4
3820             "4_arrows" => "4Arrows", # 5
3821             "4_red_to_black" => "4RedToBlack", # 6
3822             "4_traffic_lights" => "4TrafficLights", # 7
3823             "5_arrows_gray" => "5ArrowsGray", # 8
3824             "5_quarters" => "5Quarters", # 9
3825             "3_arrows_gray" => "3ArrowsGray", # 10
3826             "3_traffic_lights" => "3TrafficLights", # 11
3827             "3_signs" => "3Signs", # 12
3828             "3_symbols" => "3Symbols2", # 13
3829             "4_arrows_gray" => "4ArrowsGray", # 14
3830             "4_ratings" => "4Rating", # 15
3831             "5_arrows" => "5Arrows", # 16
3832             "5_ratings" => "5Rating", # 17
3833             );
3834              
3835              
3836             # Set properties for icon sets.
3837 149 100       439 if ( $param->{type} eq 'iconSet' ) {
3838              
3839 37 50       90 if ( !defined $param->{icon_style} ) {
3840 0         0 carp "The 'icon_style' parameter must be specified when "
3841             . "'type' == 'icon_set' in conditional_formatting()";
3842 0         0 return -3;
3843             }
3844              
3845             # Check for valid icon styles.
3846 37 50       88 if ( not exists $icon_set_styles{ $param->{icon_style} } ) {
3847 0         0 carp "Unknown icon style '$param->{icon_style}' for parameter "
3848             . "'icon_style' in conditional_formatting()";
3849 0         0 return -3;
3850             }
3851             else {
3852 37         74 $param->{icon_style} = $icon_set_styles{ $param->{icon_style} };
3853             }
3854              
3855             # Set the number of icons for the icon style.
3856 37         86 $param->{total_icons} = 3;
3857 37 100       145 if ( $param->{icon_style} =~ /^4/ ) {
    100          
3858 11         20 $param->{total_icons} = 4;
3859             }
3860             elsif ( $param->{icon_style} =~ /^5/ ) {
3861 8         18 $param->{total_icons} = 5;
3862             }
3863              
3864             $param->{icons} =
3865 37         120 $self->_set_icon_properties( $param->{total_icons}, $param->{icons} );
3866             }
3867              
3868              
3869             # Set the formatting range.
3870 149         328 my $range = '';
3871 149         270 my $start_cell = ''; # Use for formulas.
3872              
3873             # Swap last row/col for first row/col as necessary
3874 149 50       387 if ( $row1 > $row2 ) {
3875 0         0 ( $row1, $row2 ) = ( $row2, $row1 );
3876             }
3877              
3878 149 50       400 if ( $col1 > $col2 ) {
3879 0         0 ( $col1, $col2 ) = ( $col2, $col1 );
3880             }
3881              
3882             # If the first and last cell are the same write a single cell.
3883 149 100 100     635 if ( ( $row1 == $row2 ) && ( $col1 == $col2 ) ) {
3884 74         347 $range = xl_rowcol_to_cell( $row1, $col1 );
3885 74         155 $start_cell = $range;
3886             }
3887             else {
3888 75         328 $range = xl_range( $row1, $row2, $col1, $col2 );
3889 75         228 $start_cell = xl_rowcol_to_cell( $row1, $col1 );
3890             }
3891              
3892             # Override with user defined multiple range if provided.
3893 149 100       422 if ( $user_range ) {
3894 1         3 $range = $user_range;
3895             }
3896              
3897             # Get the dxf format index.
3898 149 100 66     771 if ( defined $param->{format} && ref $param->{format} ) {
3899 27         133 $param->{format} = $param->{format}->get_dxf_index();
3900             }
3901              
3902             # Set the priority based on the order of adding.
3903 149         436 $param->{priority} = $self->{_dxf_priority}++;
3904              
3905             # Check for 2010 style data_bar parameters.
3906 149 100 100     2776 if ( $self->{_use_data_bars_2010}
      100        
      100        
      100        
      100        
      100        
      100        
      100        
      100        
      100        
      100        
3907             || $param->{data_bar_2010}
3908             || $param->{bar_solid}
3909             || $param->{bar_border_color}
3910             || $param->{bar_negative_color}
3911             || $param->{bar_negative_color_same}
3912             || $param->{bar_negative_border_color}
3913             || $param->{bar_negative_border_color_same}
3914             || $param->{bar_no_border}
3915             || $param->{bar_axis_position}
3916             || $param->{bar_axis_color}
3917             || $param->{bar_direction} )
3918             {
3919 25         59 $param->{_is_data_bar_2010} = 1;
3920             }
3921              
3922             # Special handling of text criteria.
3923 149 100       492 if ( $param->{type} eq 'text' ) {
3924              
3925 8 100       34 if ( $param->{criteria} eq 'containsText' ) {
    100          
    100          
    50          
3926 1         12 $param->{type} = 'containsText';
3927             $param->{formula} = sprintf 'NOT(ISERROR(SEARCH("%s",%s)))',
3928 1         8 $param->{value}, $start_cell;
3929             }
3930             elsif ( $param->{criteria} eq 'notContains' ) {
3931 1         3 $param->{type} = 'notContainsText';
3932             $param->{formula} = sprintf 'ISERROR(SEARCH("%s",%s))',
3933 1         6 $param->{value}, $start_cell;
3934             }
3935             elsif ( $param->{criteria} eq 'beginsWith' ) {
3936 3         8 $param->{type} = 'beginsWith';
3937             $param->{formula} = sprintf 'LEFT(%s,%d)="%s"',
3938 3         15 $start_cell, length( $param->{value} ), $param->{value};
3939             }
3940             elsif ( $param->{criteria} eq 'endsWith' ) {
3941 3         6 $param->{type} = 'endsWith';
3942             $param->{formula} = sprintf 'RIGHT(%s,%d)="%s"',
3943 3         14 $start_cell, length( $param->{value} ), $param->{value};
3944             }
3945             else {
3946 0         0 carp "Invalid text criteria '$param->{criteria}' "
3947             . "in conditional_formatting()";
3948             }
3949             }
3950              
3951             # Special handling of time time_period criteria.
3952 149 100       404 if ( $param->{type} eq 'timePeriod' ) {
3953              
3954 10 100       71 if ( $param->{criteria} eq 'yesterday' ) {
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    50          
3955 1         7 $param->{formula} = sprintf 'FLOOR(%s,1)=TODAY()-1', $start_cell;
3956             }
3957             elsif ( $param->{criteria} eq 'today' ) {
3958 1         6 $param->{formula} = sprintf 'FLOOR(%s,1)=TODAY()', $start_cell;
3959             }
3960             elsif ( $param->{criteria} eq 'tomorrow' ) {
3961 1         7 $param->{formula} = sprintf 'FLOOR(%s,1)=TODAY()+1', $start_cell;
3962             }
3963             elsif ( $param->{criteria} eq 'last7Days' ) {
3964             $param->{formula} =
3965 1         6 sprintf 'AND(TODAY()-FLOOR(%s,1)<=6,FLOOR(%s,1)<=TODAY())',
3966             $start_cell, $start_cell;
3967             }
3968             elsif ( $param->{criteria} eq 'lastWeek' ) {
3969             $param->{formula} =
3970 1         6 sprintf 'AND(TODAY()-ROUNDDOWN(%s,0)>=(WEEKDAY(TODAY())),'
3971             . 'TODAY()-ROUNDDOWN(%s,0)<(WEEKDAY(TODAY())+7))',
3972             $start_cell, $start_cell;
3973             }
3974             elsif ( $param->{criteria} eq 'thisWeek' ) {
3975             $param->{formula} =
3976 1         6 sprintf 'AND(TODAY()-ROUNDDOWN(%s,0)<=WEEKDAY(TODAY())-1,'
3977             . 'ROUNDDOWN(%s,0)-TODAY()<=7-WEEKDAY(TODAY()))',
3978             $start_cell, $start_cell;
3979             }
3980             elsif ( $param->{criteria} eq 'nextWeek' ) {
3981             $param->{formula} =
3982 1         6 sprintf 'AND(ROUNDDOWN(%s,0)-TODAY()>(7-WEEKDAY(TODAY())),'
3983             . 'ROUNDDOWN(%s,0)-TODAY()<(15-WEEKDAY(TODAY())))',
3984             $start_cell, $start_cell;
3985             }
3986             elsif ( $param->{criteria} eq 'lastMonth' ) {
3987             $param->{formula} =
3988 1         8 sprintf
3989             'AND(MONTH(%s)=MONTH(TODAY())-1,OR(YEAR(%s)=YEAR(TODAY()),'
3990             . 'AND(MONTH(%s)=1,YEAR(A1)=YEAR(TODAY())-1)))',
3991             $start_cell, $start_cell, $start_cell;
3992             }
3993             elsif ( $param->{criteria} eq 'thisMonth' ) {
3994             $param->{formula} =
3995 1         6 sprintf 'AND(MONTH(%s)=MONTH(TODAY()),YEAR(%s)=YEAR(TODAY()))',
3996             $start_cell, $start_cell;
3997             }
3998             elsif ( $param->{criteria} eq 'nextMonth' ) {
3999             $param->{formula} =
4000 1         7 sprintf
4001             'AND(MONTH(%s)=MONTH(TODAY())+1,OR(YEAR(%s)=YEAR(TODAY()),'
4002             . 'AND(MONTH(%s)=12,YEAR(%s)=YEAR(TODAY())+1)))',
4003             $start_cell, $start_cell, $start_cell, $start_cell;
4004             }
4005             else {
4006 0         0 carp "Invalid time_period criteria '$param->{criteria}' "
4007             . "in conditional_formatting()";
4008             }
4009             }
4010              
4011              
4012             # Special handling of blanks/error types.
4013 149 100       412 if ( $param->{type} eq 'containsBlanks' ) {
4014 1         7 $param->{formula} = sprintf 'LEN(TRIM(%s))=0', $start_cell;
4015             }
4016              
4017 149 100       408 if ( $param->{type} eq 'notContainsBlanks' ) {
4018 1         6 $param->{formula} = sprintf 'LEN(TRIM(%s))>0', $start_cell;
4019             }
4020              
4021 149 100       376 if ( $param->{type} eq 'containsErrors' ) {
4022 1         5 $param->{formula} = sprintf 'ISERROR(%s)', $start_cell;
4023             }
4024              
4025 149 100       413 if ( $param->{type} eq 'notContainsErrors' ) {
4026 1         5 $param->{formula} = sprintf 'NOT(ISERROR(%s))', $start_cell;
4027             }
4028              
4029              
4030             # Special handling for 2 color scale.
4031 149 100       411 if ( $param->{type} eq '2_color_scale' ) {
4032 1         2 $param->{type} = 'colorScale';
4033              
4034             # Color scales don't use any additional formatting.
4035 1         2 $param->{format} = undef;
4036              
4037             # Turn off 3 color parameters.
4038 1         2 $param->{mid_type} = undef;
4039 1         2 $param->{mid_color} = undef;
4040              
4041 1   50     4 $param->{min_type} ||= 'min';
4042 1   50     5 $param->{max_type} ||= 'max';
4043 1   50     5 $param->{min_value} ||= 0;
4044 1   50     4 $param->{max_value} ||= 0;
4045 1   50     5 $param->{min_color} ||= '#FF7128';
4046 1   50     5 $param->{max_color} ||= '#FFEF9C';
4047              
4048 1         4 $param->{max_color} = $self->_get_palette_color( $param->{max_color} );
4049 1         3 $param->{min_color} = $self->_get_palette_color( $param->{min_color} );
4050             }
4051              
4052              
4053             # Special handling for 3 color scale.
4054 149 100       406 if ( $param->{type} eq '3_color_scale' ) {
4055 4         13 $param->{type} = 'colorScale';
4056              
4057             # Color scales don't use any additional formatting.
4058 4         11 $param->{format} = undef;
4059              
4060 4   100     21 $param->{min_type} ||= 'min';
4061 4   100     24 $param->{mid_type} ||= 'percentile';
4062 4   100     28 $param->{max_type} ||= 'max';
4063 4   100     28 $param->{min_value} ||= 0;
4064 4 100       22 $param->{mid_value} = 50 unless defined $param->{mid_value};
4065 4   100     35 $param->{max_value} ||= 0;
4066 4   100     26 $param->{min_color} ||= '#F8696B';
4067 4   100     23 $param->{mid_color} ||= '#FFEB84';
4068 4   100     19 $param->{max_color} ||= '#63BE7B';
4069              
4070 4         20 $param->{max_color} = $self->_get_palette_color( $param->{max_color} );
4071 4         18 $param->{mid_color} = $self->_get_palette_color( $param->{mid_color} );
4072 4         24 $param->{min_color} = $self->_get_palette_color( $param->{min_color} );
4073             }
4074              
4075              
4076             # Special handling for data bar.
4077 149 100       490 if ( $param->{type} eq 'dataBar' ) {
4078              
4079             # Excel 2007 data bars don't use any additional formatting.
4080 29         75 $param->{format} = undef;
4081              
4082 29 100       113 if ( !defined $param->{min_type} ) {
4083 22         61 $param->{min_type} = 'min';
4084 22         59 $param->{_x14_min_type} = 'autoMin';
4085             }
4086             else {
4087 7         25 $param->{_x14_min_type} = $param->{min_type};
4088             }
4089              
4090 29 100       89 if ( !defined $param->{max_type} ) {
4091 23         69 $param->{max_type} = 'max';
4092 23         70 $param->{_x14_max_type} = 'autoMax';
4093             }
4094             else {
4095 6         13 $param->{_x14_max_type} = $param->{max_type};
4096             }
4097              
4098 29   100     149 $param->{min_value} ||= 0;
4099 29   100     131 $param->{max_value} ||= 0;
4100 29   100     127 $param->{bar_color} ||= '#638EC6';
4101 29   66     163 $param->{bar_border_color} ||= $param->{bar_color};
4102 29   100     152 $param->{bar_only} ||= 0;
4103 29   100     178 $param->{bar_no_border} ||= 0;
4104 29   100     149 $param->{bar_solid} ||= 0;
4105 29   100     154 $param->{bar_direction} ||= '';
4106 29   100     163 $param->{bar_negative_color} ||= '#FF0000';
4107 29   100     144 $param->{bar_negative_border_color} ||= '#FF0000';
4108 29   100     149 $param->{bar_negative_color_same} ||= 0;
4109 29   100     134 $param->{bar_negative_border_color_same} ||= 0;
4110 29   100     139 $param->{bar_axis_position} ||= '';
4111 29   100     147 $param->{bar_axis_color} ||= '#000000';
4112              
4113             $param->{bar_color} =
4114 29         110 $self->_get_palette_color( $param->{bar_color} );
4115              
4116             $param->{bar_border_color} =
4117 29         109 $self->_get_palette_color( $param->{bar_border_color} );
4118              
4119             $param->{bar_negative_color} =
4120 29         89 $self->_get_palette_color( $param->{bar_negative_color} );
4121              
4122             $param->{bar_negative_border_color} =
4123 29         80 $self->_get_palette_color( $param->{bar_negative_border_color} );
4124              
4125             $param->{bar_axis_color} =
4126 29         97 $self->_get_palette_color( $param->{bar_axis_color} );
4127              
4128             }
4129              
4130             # Adjust for 2010 style data_bar parameters.
4131 149 100       446 if ( $param->{_is_data_bar_2010} ) {
4132              
4133 25         58 $self->{_excel_version} = 2010;
4134              
4135 25 100 66     126 if ( $param->{min_type} eq 'min' && $param->{min_value} == 0 ) {
4136 20         62 $param->{min_value} = undef;
4137             }
4138              
4139 25 100 66     134 if ( $param->{max_type} eq 'max' && $param->{max_value} == 0 ) {
4140 21         43 $param->{max_value} = undef;
4141             }
4142              
4143             # Store range for Excel 2010 data bars.
4144 25         68 $param->{_range} = $range;
4145             }
4146              
4147             # Strip the leading = from formulas.
4148 149 100       441 $param->{min_value} =~ s/^=// if defined $param->{min_value};
4149 149 100       432 $param->{mid_value} =~ s/^=// if defined $param->{mid_value};
4150 149 100       417 $param->{max_value} =~ s/^=// if defined $param->{max_value};
4151              
4152             # Store the validation information until we close the worksheet.
4153 149         265 push @{ $self->{_cond_formats}->{$range} }, $param;
  149         2341  
4154             }
4155              
4156              
4157             ###############################################################################
4158             #
4159             # Set the sub-properites for icons.
4160             #
4161             sub _set_icon_properties {
4162              
4163 37     37   59 my $self = shift;
4164 37         50 my $total_icons = shift;
4165 37         401 my $user_props = shift;
4166 37         63 my $props = [];
4167              
4168             # Set the default icon properties.
4169 37         102 for ( 0 .. $total_icons - 1 ) {
4170 138         375 push @$props,
4171             {
4172             criteria => 0,
4173             value => 0,
4174             type => 'percent'
4175             };
4176             }
4177              
4178             # Set the default icon values based on the number of icons.
4179 37 100       89 if ( $total_icons == 3 ) {
4180 18         35 $props->[0]->{value} = 67;
4181 18         33 $props->[1]->{value} = 33;
4182             }
4183              
4184 37 100       73 if ( $total_icons == 4 ) {
4185 11         21 $props->[0]->{value} = 75;
4186 11         19 $props->[1]->{value} = 50;
4187 11         19 $props->[2]->{value} = 25;
4188             }
4189              
4190 37 100       76 if ( $total_icons == 5 ) {
4191 8         13 $props->[0]->{value} = 80;
4192 8         16 $props->[1]->{value} = 60;
4193 8         12 $props->[2]->{value} = 40;
4194 8         14 $props->[3]->{value} = 20;
4195             }
4196              
4197             # Overwrite default properties with user defined properties.
4198 37 100       74 if ( defined $user_props ) {
4199              
4200             # Ensure we don't set user properties for lowest icon.
4201 13         23 my $max_data = @$user_props;
4202 13 100       28 if ( $max_data >= $total_icons ) {
4203 2         4 $max_data = $total_icons -1;
4204             }
4205              
4206 13         30 for my $i ( 0 .. $max_data - 1 ) {
4207              
4208             # Set the user defined 'value' property.
4209 30 100       79 if ( defined $user_props->[$i]->{value} ) {
4210 24         47 $props->[$i]->{value} = $user_props->[$i]->{value};
4211 24         57 $props->[$i]->{value} =~ s/^=//;
4212             }
4213              
4214             # Set the user defined 'type' property.
4215 30 100       67 if ( defined $user_props->[$i]->{type} ) {
4216              
4217 14         25 my $type = $user_props->[$i]->{type};
4218              
4219 14 50 100     86 if ( $type ne 'percent'
      100        
      66        
4220             && $type ne 'percentile'
4221             && $type ne 'number'
4222             && $type ne 'formula' )
4223             {
4224 0         0 carp "Unknown icon property type '$props->{type}' for sub-"
4225             . "property 'type' in conditional_formatting()";
4226             }
4227             else {
4228 14         25 $props->[$i]->{type} = $type;
4229              
4230 14 100       34 if ( $props->[$i]->{type} eq 'number' ) {
4231 2         5 $props->[$i]->{type} = 'num';
4232             }
4233             }
4234             }
4235              
4236             # Set the user defined 'criteria' property.
4237 30 100 100     98 if ( defined $user_props->[$i]->{criteria}
4238             && $user_props->[$i]->{criteria} eq '>' )
4239             {
4240 7         12 $props->[$i]->{criteria} = 1;
4241             }
4242              
4243             }
4244              
4245             }
4246              
4247 37         79 return $props;
4248             }
4249              
4250              
4251             ###############################################################################
4252             #
4253             # add_table()
4254             #
4255             # Add an Excel table to a worksheet.
4256             #
4257             sub add_table {
4258              
4259 48     48 0 486 my $self = shift;
4260 48         155 my $user_range = '';
4261 48         124 my %table;
4262             my @col_formats;
4263              
4264             # We would need to order the write statements very carefully within this
4265             # function to support optimisation mode. Disable add_table() when it is
4266             # on for now.
4267 48 50       251 if ( $self->{_optimization} == 1 ) {
4268 0         0 carp "add_table() isn't supported when set_optimization() is on";
4269 0         0 return -1;
4270             }
4271              
4272             # Check for a cell reference in A1 notation and substitute row and column
4273 48 50 33     455 if ( @_ && $_[0] =~ /^\D/ ) {
4274 48         229 @_ = $self->_substitute_cellref( @_ );
4275             }
4276              
4277             # Check for a valid number of args.
4278 48 50       225 if ( @_ < 4 ) {
4279 0         0 carp "Not enough parameters to add_table()";
4280 0         0 return -1;
4281             }
4282              
4283 48         167 my ( $row1, $col1, $row2, $col2 ) = @_;
4284              
4285             # Check that row and col are valid without storing the values.
4286 48 50       197 return -2 if $self->_check_dimensions( $row1, $col1, 1, 1 );
4287 48 50       201 return -2 if $self->_check_dimensions( $row2, $col2, 1, 1 );
4288              
4289              
4290             # The final hashref contains the validation parameters.
4291 48   100     271 my $param = $_[4] || {};
4292              
4293             # Check that the last parameter is a hash list.
4294 48 50       399 if ( ref $param ne 'HASH' ) {
4295 0         0 carp "Last parameter '$param' in add_table() must be a hash ref";
4296 0         0 return -3;
4297             }
4298              
4299              
4300             # List of valid input parameters.
4301 48         925 my %valid_parameter = (
4302             autofilter => 1,
4303             banded_columns => 1,
4304             banded_rows => 1,
4305             columns => 1,
4306             data => 1,
4307             first_column => 1,
4308             header_row => 1,
4309             last_column => 1,
4310             name => 1,
4311             style => 1,
4312             total_row => 1,
4313             );
4314              
4315             # Check for valid input parameters.
4316 48         395 for my $param_key ( keys %$param ) {
4317 44 50       183 if ( not exists $valid_parameter{$param_key} ) {
4318 0         0 carp "Unknown parameter '$param_key' in add_table()";
4319 0         0 return -3;
4320             }
4321             }
4322              
4323             # Turn on Excel's defaults.
4324 48 100       995 $param->{banded_rows} = 1 if !defined $param->{banded_rows};
4325 48 100       1297 $param->{header_row} = 1 if !defined $param->{header_row};
4326 48 100       670 $param->{autofilter} = 1 if !defined $param->{autofilter};
4327              
4328             # Set the table options.
4329 48 100       210 $table{_show_first_col} = $param->{first_column} ? 1 : 0;
4330 48 100       159 $table{_show_last_col} = $param->{last_column} ? 1 : 0;
4331 48 100       169 $table{_show_row_stripes} = $param->{banded_rows} ? 1 : 0;
4332 48 100       149 $table{_show_col_stripes} = $param->{banded_columns} ? 1 : 0;
4333 48 100       163 $table{_header_row_count} = $param->{header_row} ? 1 : 0;
4334 48 100       144 $table{_totals_row_shown} = $param->{total_row} ? 1 : 0;
4335              
4336              
4337             # Set the table name.
4338 48 100       163 if ( defined $param->{name} ) {
4339 1         3 my $name = $param->{name};
4340              
4341             # Warn if the name contains invalid chars as defined by Excel help.
4342 1 50 33     11 if ( $name !~ m/^[\w\\][\w\\.]*$/ || $name =~ m/^\d/ ) {
4343 0         0 carp "Invalid character in name '$name' used in add_table()";
4344 0         0 return -3;
4345             }
4346              
4347             # Warn if the name looks like a cell name.
4348 1 50       5 if ( $name =~ m/^[a-zA-Z][a-zA-Z]?[a-dA-D]?[0-9]+$/ ) {
4349 0         0 carp "Invalid name '$name' looks like a cell name in add_table()";
4350 0         0 return -3;
4351             }
4352              
4353             # Warn if the name looks like a R1C1.
4354 1 50 33     16 if ( $name =~ m/^[rcRC]$/ || $name =~ m/^[rcRC]\d+[rcRC]\d+$/ ) {
4355 0         0 carp "Invalid name '$name' like a RC cell ref in add_table()";
4356 0         0 return -3;
4357             }
4358              
4359 1         6 $table{_name} = $param->{name};
4360             }
4361              
4362             # Set the table style.
4363 48 100       166 if ( defined $param->{style} ) {
4364 3         7 $table{_style} = $param->{style};
4365              
4366             # Remove whitespace from style name.
4367 3         19 $table{_style} =~ s/\s//g;
4368             }
4369             else {
4370 45         126 $table{_style} = "TableStyleMedium9";
4371             }
4372              
4373              
4374             # Swap last row/col for first row/col as necessary.
4375 48 50       180 if ( $row1 > $row2 ) {
4376 0         0 ( $row1, $row2 ) = ( $row2, $row1 );
4377             }
4378              
4379 48 50       165 if ( $col1 > $col2 ) {
4380 0         0 ( $col1, $col2 ) = ( $col2, $col1 );
4381             }
4382              
4383              
4384             # Set the data range rows (without the header and footer).
4385 48         103 my $first_data_row = $row1;
4386 48         89 my $last_data_row = $row2;
4387 48 100       173 $first_data_row++ if $param->{header_row};
4388 48 100       168 $last_data_row-- if $param->{total_row};
4389              
4390              
4391             # Set the table and autofilter ranges.
4392 48         263 $table{_range} = xl_range( $row1, $row2, $col1, $col2 );
4393 48         216 $table{_a_range} = xl_range( $row1, $last_data_row, $col1, $col2 );
4394              
4395              
4396             # If the header row if off the default is to turn autofilter off.
4397 48 100       190 if ( !$param->{header_row} ) {
4398 3         9 $param->{autofilter} = 0;
4399             }
4400              
4401             # Set the autofilter range.
4402 48 100       146 if ( $param->{autofilter} ) {
4403 44         137 $table{_autofilter} = $table{_a_range};
4404             }
4405              
4406             # Add the table columns.
4407 48         101 my %seen_names;
4408 48         93 my $col_id = 1;
4409 48         157 for my $col_num ( $col1 .. $col2 ) {
4410              
4411             # Set up the default column data.
4412 212         1139 my $col_data = {
4413             _id => $col_id,
4414             _name => 'Column' . $col_id,
4415             _total_string => '',
4416             _total_function => '',
4417             _formula => '',
4418             _format => undef,
4419             _name_format => undef,
4420             };
4421              
4422             # Overwrite the defaults with any use defined values.
4423 212 100       538 if ( $param->{columns} ) {
4424              
4425             # Check if there are user defined values for this column.
4426 85 100       248 if ( my $user_data = $param->{columns}->[ $col_id - 1 ] ) {
4427              
4428             # Map user defined values to internal values.
4429             $col_data->{_name} = $user_data->{header}
4430 84 100       197 if $user_data->{header};
4431              
4432             # Excel requires unique case insensitive header names.
4433 84         152 my $name = $col_data->{_name};
4434 84         169 my $key = lc $name;
4435 84 100       190 if (exists $seen_names{$key}) {
4436 1         265 carp "add_table() contains duplicate name: '$name'";
4437 1         48 return -1;
4438             }
4439             else {
4440 83         213 $seen_names{$key} = 1;
4441             }
4442              
4443             # Get the header format if defined.
4444 83         163 $col_data->{_name_format} = $user_data->{header_format};
4445              
4446             # Handle the column formula.
4447 83 100       184 if ( $user_data->{formula} ) {
4448 3         28 my $formula = $user_data->{formula};
4449              
4450             # Remove the leading = from formula.
4451 3         15 $formula =~ s/^=//;
4452              
4453             # Covert Excel 2010 "@" ref to 2007 "#This Row".
4454 3         8 $formula =~ s/@/[#This Row],/g;
4455              
4456 3         7 $col_data->{_formula} = $formula;
4457              
4458 3         12 for my $row ( $first_data_row .. $last_data_row ) {
4459             $self->write_formula( $row, $col_num, $formula,
4460 24         56 $user_data->{format} );
4461             }
4462             }
4463              
4464             # Handle the function for the total row.
4465 83 100       225 if ( $user_data->{total_function} ) {
    100          
4466 40         81 my $function = $user_data->{total_function};
4467              
4468             # Massage the function name.
4469 40         74 $function = lc $function;
4470 40         102 $function =~ s/_//g;
4471 40         94 $function =~ s/\s//g;
4472              
4473 40 100       107 $function = 'countNums' if $function eq 'countnums';
4474 40 100       98 $function = 'stdDev' if $function eq 'stddev';
4475              
4476 40         72 $col_data->{_total_function} = $function;
4477              
4478             my $formula = _table_function_to_formula(
4479             $function,
4480             $col_data->{_name}
4481              
4482 40         106 );
4483              
4484 40   100     331 my $value = $user_data->{total_value} || 0;
4485              
4486             $self->write_formula( $row2, $col_num, $formula,
4487 40         151 $user_data->{format}, $value );
4488              
4489             }
4490             elsif ( $user_data->{total_string} ) {
4491              
4492             # Total label only (not a function).
4493 9         30 my $total_string = $user_data->{total_string};
4494 9         28 $col_data->{_total_string} = $total_string;
4495              
4496             $self->write_string( $row2, $col_num, $total_string,
4497 9         51 $user_data->{format} );
4498             }
4499              
4500             # Get the dxf format index.
4501 83 100 66     342 if ( defined $user_data->{format} && ref $user_data->{format} )
4502             {
4503             $col_data->{_format} =
4504 9         191 $user_data->{format}->get_dxf_index();
4505             }
4506              
4507             # Store the column format for writing the cell data.
4508             # It doesn't matter if it is undefined.
4509 83         225 $col_formats[ $col_id - 1 ] = $user_data->{format};
4510             }
4511             }
4512              
4513             # Store the column data.
4514 211         330 push @{ $table{_columns} }, $col_data;
  211         484  
4515              
4516             # Write the column headers to the worksheet.
4517 211 100       504 if ( $param->{header_row} ) {
4518             $self->write_string( $row1, $col_num, $col_data->{_name},
4519 201         574 $col_data->{_name_format} );
4520             }
4521              
4522 211         487 $col_id++;
4523             } # Table columns.
4524              
4525              
4526             # Write the cell data if supplied.
4527 47 100       218 if ( my $data = $param->{data} ) {
4528              
4529 6         12 my $i = 0; # For indexing the row data.
4530 6         20 for my $row ( $first_data_row .. $last_data_row ) {
4531 22         53 my $j = 0; # For indexing the col data.
4532              
4533 22         44 for my $col ( $col1 .. $col2 ) {
4534              
4535 84         142 my $token = $data->[$i]->[$j];
4536              
4537 84 100       180 if ( defined $token ) {
4538 77         173 $self->write( $row, $col, $token, $col_formats[$j] );
4539             }
4540              
4541 84         156 $j++;
4542             }
4543 22         40 $i++;
4544             }
4545             }
4546              
4547              
4548             # Store the table data.
4549 47         184 push @{ $self->{_tables} }, \%table;
  47         152  
4550              
4551 47         330 return \%table;
4552             }
4553              
4554              
4555             ###############################################################################
4556             #
4557             # add_sparkline()
4558             #
4559             # Add sparklines to the worksheet.
4560             #
4561             sub add_sparkline {
4562              
4563 58     58 0 398 my $self = shift;
4564 58         82 my $param = shift;
4565 58         83 my $sparkline = {};
4566              
4567             # Check that the last parameter is a hash list.
4568 58 50       149 if ( ref $param ne 'HASH' ) {
4569 0         0 carp "Parameter list in add_sparkline() must be a hash ref";
4570 0         0 return -1;
4571             }
4572              
4573             # List of valid input parameters.
4574 58         582 my %valid_parameter = (
4575             location => 1,
4576             range => 1,
4577             type => 1,
4578             high_point => 1,
4579             low_point => 1,
4580             negative_points => 1,
4581             first_point => 1,
4582             last_point => 1,
4583             markers => 1,
4584             style => 1,
4585             series_color => 1,
4586             negative_color => 1,
4587             markers_color => 1,
4588             first_color => 1,
4589             last_color => 1,
4590             high_color => 1,
4591             low_color => 1,
4592             max => 1,
4593             min => 1,
4594             axis => 1,
4595             reverse => 1,
4596             empty_cells => 1,
4597             show_hidden => 1,
4598             plot_hidden => 1,
4599             date_axis => 1,
4600             weight => 1,
4601             );
4602              
4603             # Check for valid input parameters.
4604 58         231 for my $param_key ( keys %$param ) {
4605 212 50       435 if ( not exists $valid_parameter{$param_key} ) {
4606 0         0 carp "Unknown parameter '$param_key' in add_sparkline()";
4607 0         0 return -2;
4608             }
4609             }
4610              
4611             # 'location' is a required parameter.
4612 58 50       138 if ( not exists $param->{location} ) {
4613 0         0 carp "Parameter 'location' is required in add_sparkline()";
4614 0         0 return -3;
4615             }
4616              
4617             # 'range' is a required parameter.
4618 58 50       121 if ( not exists $param->{range} ) {
4619 0         0 carp "Parameter 'range' is required in add_sparkline()";
4620 0         0 return -3;
4621             }
4622              
4623              
4624             # Handle the sparkline type.
4625 58   100     181 my $type = $param->{type} || 'line';
4626              
4627 58 50 100     170 if ( $type ne 'line' && $type ne 'column' && $type ne 'win_loss' ) {
      66        
4628 0         0 carp "Parameter 'type' must be 'line', 'column' "
4629             . "or 'win_loss' in add_sparkline()";
4630 0         0 return -4;
4631             }
4632              
4633 58 100       116 $type = 'stacked' if $type eq 'win_loss';
4634 58         106 $sparkline->{_type} = $type;
4635              
4636              
4637             # We handle single location/range values or array refs of values.
4638 58 100       129 if ( ref $param->{location} ) {
4639 2         7 $sparkline->{_locations} = $param->{location};
4640 2         7 $sparkline->{_ranges} = $param->{range};
4641             }
4642             else {
4643 56         120 $sparkline->{_locations} = [ $param->{location} ];
4644 56         106 $sparkline->{_ranges} = [ $param->{range} ];
4645             }
4646              
4647 58         85 my $range_count = @{ $sparkline->{_ranges} };
  58         104  
4648 58         81 my $location_count = @{ $sparkline->{_locations} };
  58         89  
4649              
4650             # The ranges and locations must match.
4651 58 50       122 if ( $range_count != $location_count ) {
4652 0         0 carp "Must have the same number of location and range "
4653             . "parameters in add_sparkline()";
4654 0         0 return -5;
4655             }
4656              
4657             # Store the count.
4658 58         76 $sparkline->{_count} = @{ $sparkline->{_locations} };
  58         104  
4659              
4660             # Get the worksheet name for the range conversion below.
4661 58         178 my $sheetname = quote_sheetname( $self->{_name} );
4662              
4663             # Cleanup the input ranges.
4664 58         149 for my $range ( @{ $sparkline->{_ranges} } ) {
  58         126  
4665              
4666             # Remove the absolute reference $ symbols.
4667 59         127 $range =~ s{\$}{}g;
4668              
4669             # Remove the = from xl_range_formula(.
4670 59         102 $range =~ s{^=}{};
4671              
4672             # Convert a simple range into a full Sheet1!A1:D1 range.
4673 59 100       141 if ( $range !~ /!/ ) {
4674 54         129 $range = $sheetname . "!" . $range;
4675             }
4676             }
4677              
4678             # Cleanup the input locations.
4679 58         88 for my $location ( @{ $sparkline->{_locations} } ) {
  58         106  
4680 59         105 $location =~ s{\$}{}g;
4681             }
4682              
4683             # Map options.
4684 58         114 $sparkline->{_high} = $param->{high_point};
4685 58         88 $sparkline->{_low} = $param->{low_point};
4686 58         84 $sparkline->{_negative} = $param->{negative_points};
4687 58         124 $sparkline->{_first} = $param->{first_point};
4688 58         96 $sparkline->{_last} = $param->{last_point};
4689 58         100 $sparkline->{_markers} = $param->{markers};
4690 58         98 $sparkline->{_min} = $param->{min};
4691 58         88 $sparkline->{_max} = $param->{max};
4692 58         85 $sparkline->{_axis} = $param->{axis};
4693 58         83 $sparkline->{_reverse} = $param->{reverse};
4694 58         89 $sparkline->{_hidden} = $param->{show_hidden};
4695 58         143 $sparkline->{_weight} = $param->{weight};
4696              
4697             # Map empty cells options.
4698 58   100     201 my $empty = $param->{empty_cells} || '';
4699              
4700 58 100       161 if ( $empty eq 'zero' ) {
    100          
4701 1         2 $sparkline->{_empty} = 0;
4702             }
4703             elsif ( $empty eq 'connect' ) {
4704 1         2 $sparkline->{_empty} = 'span';
4705             }
4706             else {
4707 56         94 $sparkline->{_empty} = 'gap';
4708             }
4709              
4710              
4711             # Map the date axis range.
4712 58         95 my $date_range = $param->{date_axis};
4713              
4714 58 100 66     152 if ( $date_range && $date_range !~ /!/ ) {
4715 1         3 $date_range = $sheetname . "!" . $date_range;
4716             }
4717 58         101 $sparkline->{_date_axis} = $date_range;
4718              
4719              
4720             # Set the sparkline styles.
4721 58   100     144 my $style_id = $param->{style} || 0;
4722 58         119 my $style = $Excel::Writer::XLSX::Package::Theme::spark_styles[$style_id];
4723              
4724 58         127 $sparkline->{_series_color} = $style->{series};
4725 58         120 $sparkline->{_negative_color} = $style->{negative};
4726 58         95 $sparkline->{_markers_color} = $style->{markers};
4727 58         106 $sparkline->{_first_color} = $style->{first};
4728 58         95 $sparkline->{_last_color} = $style->{last};
4729 58         97 $sparkline->{_high_color} = $style->{high};
4730 58         114 $sparkline->{_low_color} = $style->{low};
4731              
4732             # Override the style colours with user defined colors.
4733 58         175 $self->_set_spark_color( $sparkline, $param, 'series_color' );
4734 58         129 $self->_set_spark_color( $sparkline, $param, 'negative_color' );
4735 58         123 $self->_set_spark_color( $sparkline, $param, 'markers_color' );
4736 58         128 $self->_set_spark_color( $sparkline, $param, 'first_color' );
4737 58         140 $self->_set_spark_color( $sparkline, $param, 'last_color' );
4738 58         130 $self->_set_spark_color( $sparkline, $param, 'high_color' );
4739 58         110 $self->_set_spark_color( $sparkline, $param, 'low_color' );
4740              
4741 58         75 push @{ $self->{_sparklines} }, $sparkline;
  58         311  
4742             }
4743              
4744              
4745             ###############################################################################
4746             #
4747             # insert_button()
4748             #
4749             # Insert a button form object into the worksheet.
4750             #
4751             sub insert_button {
4752              
4753 28     28 0 203 my $self = shift;
4754              
4755             # Check for a cell reference in A1 notation and substitute row and column
4756 28 50       181 if ( $_[0] =~ /^\D/ ) {
4757 28         117 @_ = $self->_substitute_cellref( @_ );
4758             }
4759              
4760             # Check the number of args.
4761 28 50       115 if ( @_ < 3 ) { return -1 }
  0         0  
4762              
4763 28         123 my $button = $self->_button_params( @_ );
4764              
4765 28         54 push @{ $self->{_buttons_array} }, $button;
  28         73  
4766              
4767 28         122 $self->{_has_vml} = 1;
4768             }
4769              
4770              
4771             ###############################################################################
4772             #
4773             # set_vba_name()
4774             #
4775             # Set the VBA name for the worksheet.
4776             #
4777             sub set_vba_name {
4778              
4779 6     6 0 28 my $self = shift;
4780 6         14 my $vba_codemame = shift;
4781              
4782 6 100       27 if ( $vba_codemame ) {
4783 2         19 $self->{_vba_codename} = $vba_codemame;
4784             }
4785             else {
4786 4         20 $self->{_vba_codename} = $self->{_name};
4787             }
4788             }
4789              
4790              
4791             ###############################################################################
4792             #
4793             # Internal methods.
4794             #
4795             ###############################################################################
4796              
4797              
4798             ###############################################################################
4799             #
4800             # _table_function_to_formula
4801             #
4802             # Convert a table total function to a worksheet formula.
4803             #
4804             sub _table_function_to_formula {
4805              
4806 40     40   70 my $function = shift;
4807 40         62 my $col_name = shift;
4808 40         61 my $formula = '';
4809              
4810             # Escape special characters, as required by Excel.
4811 40         70 $col_name =~ s/'/''/g;
4812 40         73 $col_name =~ s/#/'#/g;
4813 40         71 $col_name =~ s/\[/'[/g;
4814 40         67 $col_name =~ s/]/']/g;
4815              
4816 40         220 my %subtotals = (
4817             average => 101,
4818             countNums => 102,
4819             count => 103,
4820             max => 104,
4821             min => 105,
4822             stdDev => 107,
4823             sum => 109,
4824             var => 110,
4825             );
4826              
4827 40 50       134 if ( exists $subtotals{$function} ) {
4828 40         71 my $func_num = $subtotals{$function};
4829 40         120 $formula = qq{SUBTOTAL($func_num,[$col_name])};
4830             }
4831             else {
4832 0         0 carp "Unsupported function '$function' in add_table()";
4833             }
4834              
4835 40         127 return $formula;
4836             }
4837              
4838              
4839             ###############################################################################
4840             #
4841             # _set_spark_color()
4842             #
4843             # Set the sparkline colour.
4844             #
4845             sub _set_spark_color {
4846              
4847 406     406   509 my $self = shift;
4848 406         472 my $sparkline = shift;
4849 406         456 my $param = shift;
4850 406         460 my $user_color = shift;
4851 406         568 my $spark_color = '_' . $user_color;
4852              
4853 406 100       737 return unless $param->{$user_color};
4854              
4855             $sparkline->{$spark_color} =
4856 8         17 { _rgb => $self->_get_palette_color( $param->{$user_color} ) };
4857             }
4858              
4859              
4860             ###############################################################################
4861             #
4862             # _get_palette_color()
4863             #
4864             # Convert from an Excel internal colour index to a XML style #RRGGBB index
4865             # based on the default or user defined values in the Workbook palette.
4866             #
4867             sub _get_palette_color {
4868              
4869 173     173   295 my $self = shift;
4870 173         277 my $index = shift;
4871 173         273 my $palette = $self->{_palette};
4872              
4873             # Handle colours in #XXXXXX RGB format.
4874 173 100       617 if ( $index =~ m/^#([0-9A-F]{6})$/i ) {
4875 167         605 return "FF" . uc( $1 );
4876             }
4877              
4878             # Adjust the colour index.
4879 6         14 $index -= 8;
4880              
4881             # Palette is passed in from the Workbook class.
4882 6         13 my @rgb = @{ $palette->[$index] };
  6         20  
4883              
4884 6         47 return sprintf "FF%02X%02X%02X", @rgb[0, 1, 2];
4885             }
4886              
4887              
4888             ###############################################################################
4889             #
4890             # _substitute_cellref()
4891             #
4892             # Substitute an Excel cell reference in A1 notation for zero based row and
4893             # column values in an argument list.
4894             #
4895             # Ex: ("A4", "Hello") is converted to (3, 0, "Hello").
4896             #
4897             sub _substitute_cellref {
4898              
4899 2446     2446   4645 my $self = shift;
4900 2446         6120 my $cell = uc( shift );
4901              
4902             # Convert a column range: 'A:A' or 'B:G'.
4903             # A range such as A:A is equivalent to A1:Rowmax, so add rows as required
4904 2446 100       9607 if ( $cell =~ /\$?([A-Z]{1,3}):\$?([A-Z]{1,3})/ ) {
4905 191         1182 my ( $row1, $col1 ) = $self->_cell_to_rowcol( $1 . '1' );
4906             my ( $row2, $col2 ) =
4907 191         1365 $self->_cell_to_rowcol( $2 . $self->{_xls_rowmax} );
4908 191         911 return $row1, $col1, $row2, $col2, @_;
4909             }
4910              
4911             # Convert a cell range: 'A1:B7'
4912 2255 100       6908 if ( $cell =~ /\$?([A-Z]{1,3}\$?\d+):\$?([A-Z]{1,3}\$?\d+)/ ) {
4913 194         645 my ( $row1, $col1 ) = $self->_cell_to_rowcol( $1 );
4914 194         737 my ( $row2, $col2 ) = $self->_cell_to_rowcol( $2 );
4915 194         1114 return $row1, $col1, $row2, $col2, @_;
4916             }
4917              
4918             # Convert a cell reference: 'A1' or 'AD2000'
4919 2061 50       10269 if ( $cell =~ /\$?([A-Z]{1,3}\$?\d+)/ ) {
4920 2061         7182 my ( $row1, $col1 ) = $self->_cell_to_rowcol( $1 );
4921 2061         9548 return $row1, $col1, @_;
4922              
4923             }
4924              
4925 0         0 croak( "Unknown cell reference $cell" );
4926             }
4927              
4928              
4929             ###############################################################################
4930             #
4931             # _cell_to_rowcol($cell_ref)
4932             #
4933             # Convert an Excel cell reference in A1 notation to a zero based row and column
4934             # reference; converts C1 to (0, 2).
4935             #
4936             # See also: http://www.perlmonks.org/index.pl?node_id=270352
4937             #
4938             # Returns: ($row, $col, $row_absolute, $col_absolute)
4939             #
4940             #
4941             sub _cell_to_rowcol {
4942              
4943 2831     2831   5270 my $self = shift;
4944              
4945 2831         7101 my $cell = $_[0];
4946 2831         9101 $cell =~ /(\$?)([A-Z]{1,3})(\$?)(\d+)/;
4947              
4948 2831 50       9306 my $col_abs = $1 eq "" ? 0 : 1;
4949 2831         6306 my $col = $2;
4950 2831 100       7962 my $row_abs = $3 eq "" ? 0 : 1;
4951 2831         11187 my $row = $4;
4952              
4953             # Convert base26 column string to number
4954             # All your Base are belong to us.
4955 2831         9028 my @chars = split //, $col;
4956 2831         5158 my $expn = 0;
4957 2831         4955 $col = 0;
4958              
4959 2831         7784 while ( @chars ) {
4960 2873         5978 my $char = pop( @chars ); # LS char first
4961 2873         8984 $col += ( ord( $char ) - ord( 'A' ) + 1 ) * ( 26**$expn );
4962 2873         7199 $expn++;
4963             }
4964              
4965             # Convert 1-index to zero-index
4966 2831         7168 $row--;
4967 2831         4652 $col--;
4968              
4969             # TODO Check row and column range
4970 2831         9435 return $row, $col, $row_abs, $col_abs;
4971             }
4972              
4973              
4974             ###############################################################################
4975             #
4976             # _xl_rowcol_to_cell($row, $col)
4977             #
4978             # Optimised version of xl_rowcol_to_cell from Utility.pm for the inner loop
4979             # of _write_cell().
4980             #
4981              
4982             our @col_names = ( 'A' .. 'XFD' );
4983              
4984             sub _xl_rowcol_to_cell {
4985 10107     10107   25370 return $col_names[ $_[1] ] . ( $_[0] + 1 );
4986             }
4987              
4988              
4989             ###############################################################################
4990             #
4991             # _sort_pagebreaks()
4992             #
4993             # This is an internal method that is used to filter elements of the array of
4994             # pagebreaks used in the _store_hbreak() and _store_vbreak() methods. It:
4995             # 1. Removes duplicate entries from the list.
4996             # 2. Sorts the list.
4997             # 3. Removes 0 from the list if present.
4998             #
4999             sub _sort_pagebreaks {
5000              
5001 1990     1990   4009 my $self = shift;
5002              
5003 1990 100       7439 return () unless @_;
5004              
5005 11         25 my %hash;
5006             my @array;
5007              
5008 11         837 @hash{@_} = undef; # Hash slice to remove duplicates
5009 11         196 @array = sort { $a <=> $b } keys %hash; # Numerical sort
  9066         11817  
5010 11 100       110 shift @array if $array[0] == 0; # Remove zero
5011              
5012             # The Excel 2007 specification says that the maximum number of page breaks
5013             # is 1026. However, in practice it is actually 1023.
5014 11         29 my $max_num_breaks = 1023;
5015 11 100       39 splice( @array, $max_num_breaks ) if @array > $max_num_breaks;
5016              
5017 11         228 return @array;
5018             }
5019              
5020              
5021             ###############################################################################
5022             #
5023             # _check_dimensions($row, $col, $ignore_row, $ignore_col)
5024             #
5025             # Check that $row and $col are valid and store max and min values for use in
5026             # other methods/elements.
5027             #
5028             # The $ignore_row/$ignore_col flags is used to indicate that we wish to
5029             # perform the dimension check without storing the value.
5030             #
5031             # The ignore flags are use by set_row() and data_validate.
5032             #
5033             sub _check_dimensions {
5034              
5035 16086     16086   23099 my $self = shift;
5036 16086         24018 my $row = $_[0];
5037 16086         22142 my $col = $_[1];
5038 16086         22247 my $ignore_row = $_[2];
5039 16086         21802 my $ignore_col = $_[3];
5040              
5041              
5042 16086 50       29420 return -2 if not defined $row;
5043 16086 50       31758 return -2 if $row >= $self->{_xls_rowmax};
5044              
5045 16086 50       28624 return -2 if not defined $col;
5046 16086 50       29971 return -2 if $col >= $self->{_xls_colmax};
5047              
5048             # In optimization mode we don't change dimensions for rows that are
5049             # already written.
5050 16086 100 66     66251 if ( !$ignore_row && !$ignore_col && $self->{_optimization} == 1 ) {
      100        
5051 308 100       683 return -2 if $row < $self->{_previous_row};
5052             }
5053              
5054 16085 100       29096 if ( !$ignore_row ) {
5055              
5056 15177 100 100     46767 if ( not defined $self->{_dim_rowmin} or $row < $self->{_dim_rowmin} ) {
5057 806         2375 $self->{_dim_rowmin} = $row;
5058             }
5059              
5060 15177 100 100     46118 if ( not defined $self->{_dim_rowmax} or $row > $self->{_dim_rowmax} ) {
5061 4539         8869 $self->{_dim_rowmax} = $row;
5062             }
5063             }
5064              
5065 16085 100       28625 if ( !$ignore_col ) {
5066              
5067 15219 100 100     45410 if ( not defined $self->{_dim_colmin} or $col < $self->{_dim_colmin} ) {
5068 819         2463 $self->{_dim_colmin} = $col;
5069             }
5070              
5071 15219 100 100     45195 if ( not defined $self->{_dim_colmax} or $col > $self->{_dim_colmax} ) {
5072 2204         4744 $self->{_dim_colmax} = $col;
5073             }
5074             }
5075              
5076 16085         35456 return 0;
5077             }
5078              
5079              
5080             ###############################################################################
5081             #
5082             # _position_object_pixels()
5083             #
5084             # Calculate the vertices that define the position of a graphical object within
5085             # the worksheet in pixels.
5086             #
5087             # +------------+------------+
5088             # | A | B |
5089             # +-----+------------+------------+
5090             # | |(x1,y1) | |
5091             # | 1 |(A1)._______|______ |
5092             # | | | | |
5093             # | | | | |
5094             # +-----+----| Object |-----+
5095             # | | | | |
5096             # | 2 | |______________. |
5097             # | | | (B2)|
5098             # | | | (x2,y2)|
5099             # +---- +------------+------------+
5100             #
5101             # Example of an object that covers some of the area from cell A1 to cell B2.
5102             #
5103             # Based on the width and height of the object we need to calculate 8 vars:
5104             #
5105             # $col_start, $row_start, $col_end, $row_end, $x1, $y1, $x2, $y2.
5106             #
5107             # We also calculate the absolute x and y position of the top left vertex of
5108             # the object. This is required for images.
5109             #
5110             # $x_abs, $y_abs
5111             #
5112             # The width and height of the cells that the object occupies can be variable
5113             # and have to be taken into account.
5114             #
5115             # The values of $col_start and $row_start are passed in from the calling
5116             # function. The values of $col_end and $row_end are calculated by subtracting
5117             # the width and height of the object from the width and height of the
5118             # underlying cells.
5119             #
5120             # The anchor/object position defines how images are scaled for hidden rows and
5121             # columns. For option 1 "Move and size with cells" the size of the hidden
5122             # row/column is subtracted from the image.
5123             #
5124             sub _position_object_pixels {
5125              
5126 4705     4705   7686 my $self = shift;
5127              
5128 4705         32983 my $col_start; # Col containing upper left corner of object.
5129             my $x1; # Distance to left side of object.
5130              
5131 4705         0 my $row_start; # Row containing top left corner of object.
5132 4705         0 my $y1; # Distance to top of object.
5133              
5134 4705         0 my $col_end; # Col containing lower right corner of object.
5135 4705         0 my $x2; # Distance to right side of object.
5136              
5137 4705         0 my $row_end; # Row containing bottom right corner of object.
5138 4705         0 my $y2; # Distance to bottom of object.
5139              
5140 4705         0 my $width; # Width of object frame.
5141 4705         0 my $height; # Height of object frame.
5142              
5143 4705         6891 my $x_abs = 0; # Absolute distance to left side of object.
5144 4705         6847 my $y_abs = 0; # Absolute distance to top side of object.
5145              
5146 4705         7116 my $anchor; # The type of object positioning.
5147              
5148 4705         10704 ( $col_start, $row_start, $x1, $y1, $width, $height, $anchor ) = @_;
5149              
5150             # Adjust start column for negative offsets.
5151 4705   100     12532 while ( $x1 < 0 && $col_start > 0) {
5152 8         42 $x1 += $self->_size_col( $col_start - 1);
5153 8         28 $col_start--;
5154             }
5155              
5156             # Adjust start row for negative offsets.
5157 4705   100     11106 while ( $y1 < 0 && $row_start > 0) {
5158 4         18 $y1 += $self->_size_row( $row_start - 1);
5159 4         14 $row_start--;
5160             }
5161              
5162             # Ensure that the image isn't shifted off the page at top left.
5163 4705 100       9499 $x1 = 0 if $x1 < 0;
5164 4705 100       9020 $y1 = 0 if $y1 < 0;
5165              
5166             # Calculate the absolute x offset of the top-left vertex.
5167 4705 100       9529 if ( $self->{_col_size_changed} ) {
5168 46         179 for my $col_id ( 0 .. $col_start -1 ) {
5169 187         470 $x_abs += $self->_size_col( $col_id );
5170             }
5171             }
5172             else {
5173             # Optimisation for when the column widths haven't changed.
5174 4659         8103 $x_abs += $self->{_default_col_pixels} * $col_start;
5175             }
5176              
5177 4705         6862 $x_abs += $x1;
5178              
5179             # Calculate the absolute y offset of the top-left vertex.
5180             # Store the column change to allow optimisations.
5181 4705 100       8685 if ( $self->{_row_size_changed} ) {
5182 23         100 for my $row_id ( 0 .. $row_start -1 ) {
5183 132         271 $y_abs += $self->_size_row( $row_id );
5184             }
5185             }
5186             else {
5187             # Optimisation for when the row heights haven't changed.
5188 4682         7740 $y_abs += $self->{_default_row_pixels} * $row_start;
5189             }
5190              
5191 4705         7021 $y_abs += $y1;
5192              
5193              
5194             # Adjust start column for offsets that are greater than the col width.
5195 4705 100       10877 if ($self->_size_col( $col_start) > 0 ) {
5196 4704         9787 while ( $x1 >= $self->_size_col( $col_start ) ) {
5197 148         274 $x1 -= $self->_size_col( $col_start );
5198 148         297 $col_start++;
5199             }
5200             }
5201              
5202             # Adjust start row for offsets that are greater than the row height.
5203 4705 100       11149 if ( $self->_size_row( $row_start ) > 0 ) {
5204 4701         9381 while ( $y1 >= $self->_size_row( $row_start ) ) {
5205 247         415 $y1 -= $self->_size_row( $row_start );
5206 247         416 $row_start++;
5207             }
5208             }
5209              
5210             # Initialise end cell to the same as the start cell.
5211 4705         7993 $col_end = $col_start;
5212 4705         7057 $row_end = $row_start;
5213              
5214             # Only offset the image in the cell if the row/col isn't hidden.
5215 4705 100       9139 if ($self->_size_col( $col_start) > 0 ) {
5216 4704         7515 $width = $width + $x1;
5217             }
5218              
5219 4705 100       9282 if ( $self->_size_row( $row_start ) > 0 ) {
5220 4701         7369 $height = $height + $y1;
5221             }
5222              
5223             # Subtract the underlying cell widths to find the end cell of the object.
5224 4705         10239 while ( $width >= $self->_size_col( $col_end, $anchor ) ) {
5225 11097         19660 $width -= $self->_size_col( $col_end, $anchor );
5226 11097         18960 $col_end++;
5227             }
5228              
5229              
5230             # Subtract the underlying cell heights to find the end cell of the object.
5231 4705         9822 while ( $height >= $self->_size_row( $row_end, $anchor ) ) {
5232 22274         38207 $height -= $self->_size_row( $row_end, $anchor );
5233 22274         37868 $row_end++;
5234             }
5235              
5236             # The end vertices are whatever is left from the width and height.
5237 4705         7859 $x2 = $width;
5238 4705         6689 $y2 = $height;
5239              
5240             return (
5241 4705         14021 $col_start, $row_start, $x1, $y1,
5242             $col_end, $row_end, $x2, $y2,
5243             $x_abs, $y_abs
5244              
5245             );
5246             }
5247              
5248              
5249             ###############################################################################
5250             #
5251             # _position_object_emus()
5252             #
5253             # Calculate the vertices that define the position of a graphical object within
5254             # the worksheet in EMUs.
5255             #
5256             # The vertices are expressed as English Metric Units (EMUs). There are 12,700
5257             # EMUs per point. Therefore, 12,700 * 3 /4 = 9,525 EMUs per pixel.
5258             #
5259             sub _position_object_emus {
5260              
5261 483     483   1234 my $self = shift;
5262              
5263             my (
5264 483         2203 $col_start, $row_start, $x1, $y1,
5265             $col_end, $row_end, $x2, $y2,
5266             $x_abs, $y_abs
5267              
5268             ) = $self->_position_object_pixels( @_ );
5269              
5270             # Convert the pixel values to EMUs. See above.
5271 483         1866 $x1 = int( 0.5 + 9_525 * $x1 );
5272 483         1306 $y1 = int( 0.5 + 9_525 * $y1 );
5273 483         2268 $x2 = int( 0.5 + 9_525 * $x2 );
5274 483         1317 $y2 = int( 0.5 + 9_525 * $y2 );
5275 483         1265 $x_abs = int( 0.5 + 9_525 * $x_abs );
5276 483         1219 $y_abs = int( 0.5 + 9_525 * $y_abs );
5277              
5278             return (
5279 483         2284 $col_start, $row_start, $x1, $y1,
5280             $col_end, $row_end, $x2, $y2,
5281             $x_abs, $y_abs
5282              
5283             );
5284             }
5285              
5286              
5287             ###############################################################################
5288             #
5289             # _position_shape_emus()
5290             #
5291             # Calculate the vertices that define the position of a shape object within
5292             # the worksheet in EMUs. Save the vertices with the object.
5293             #
5294             # The vertices are expressed as English Metric Units (EMUs). There are 12,700
5295             # EMUs per point. Therefore, 12,700 * 3 /4 = 9,525 EMUs per pixel.
5296             #
5297             sub _position_shape_emus {
5298              
5299 41     41   70 my $self = shift;
5300 41         64 my $shape = shift;
5301              
5302             my (
5303             $col_start, $row_start, $x1, $y1, $col_end,
5304             $row_end, $x2, $y2, $x_abs, $y_abs
5305             )
5306             = $self->_position_object_pixels(
5307             $shape->{_column_start},
5308             $shape->{_row_start},
5309             $shape->{_x_offset},
5310             $shape->{_y_offset},
5311             $shape->{_width} * $shape->{_scale_x},
5312             $shape->{_height} * $shape->{_scale_y},
5313             $shape->{_drawing}
5314 41         182 );
5315              
5316             # Now that x2/y2 have been calculated with a potentially negative
5317             # width/height we use the absolute value and convert to EMUs.
5318 41         129 $shape->{_width_emu} = int( abs( $shape->{_width} * 9_525 ) );
5319 41         98 $shape->{_height_emu} = int( abs( $shape->{_height} * 9_525 ) );
5320              
5321 41         106 $shape->{_column_start} = int( $col_start );
5322 41         121 $shape->{_row_start} = int( $row_start );
5323 41         91 $shape->{_column_end} = int( $col_end );
5324 41         77 $shape->{_row_end} = int( $row_end );
5325              
5326             # Convert the pixel values to EMUs. See above.
5327 41         76 $shape->{_x1} = int( $x1 * 9_525 );
5328 41         66 $shape->{_y1} = int( $y1 * 9_525 );
5329 41         69 $shape->{_x2} = int( $x2 * 9_525 );
5330 41         77 $shape->{_y2} = int( $y2 * 9_525 );
5331 41         66 $shape->{_x_abs} = int( $x_abs * 9_525 );
5332 41         91 $shape->{_y_abs} = int( $y_abs * 9_525 );
5333             }
5334              
5335             ###############################################################################
5336             #
5337             # _size_col($col)
5338             #
5339             # Convert the width of a cell from user's units to pixels. Excel rounds the
5340             # column width to the nearest pixel. If the width hasn't been set by the user
5341             # we use the default value. A hidden column is treated as having a width of
5342             # zero unless it has the special "object_position" of 4 (size with cells).
5343             #
5344             sub _size_col {
5345              
5346 41504     41504   57710 my $self = shift;
5347 41504         54706 my $col = shift;
5348 41504   100     95827 my $anchor = shift || 0;
5349              
5350 41504         55113 my $max_digit_width = 7; # For Calabri 11.
5351 41504         53649 my $padding = 5;
5352 41504         52864 my $pixels;
5353              
5354              
5355             # Look up the cell value to see if it has been changed.
5356 41504 100       70045 if ( exists $self->{_col_sizes}->{$col} )
5357             {
5358 170         305 my $width = $self->{_col_sizes}->{$col}[0];
5359 170         290 my $hidden = $self->{_col_sizes}->{$col}[1];
5360              
5361             # Convert to pixels.
5362 170 100 100     620 if ( $hidden == 1 && $anchor != 4 ) {
    50          
5363 8         18 $pixels = 0;
5364             }
5365             elsif ( $width < 1 ) {
5366 0         0 $pixels = int( $width * ( $max_digit_width + $padding ) + 0.5 );
5367             }
5368             else {
5369 162         371 $pixels = int( $width * $max_digit_width + 0.5 ) + $padding;
5370             }
5371             }
5372             else {
5373 41334         57114 $pixels = $self->{_default_col_pixels};
5374             }
5375              
5376 41504         83347 return $pixels;
5377             }
5378              
5379              
5380             ###############################################################################
5381             #
5382             # _size_row($row)
5383             #
5384             # Convert the height of a cell from user's units to pixels. If the height
5385             # hasn't been set by the user we use the default value. A hidden row is
5386             # treated as having a height of zero unless it has the special
5387             # "object_position" of 4 (size with cells).
5388             #
5389             sub _size_row {
5390              
5391 63994     63994   89351 my $self = shift;
5392 63994         85272 my $row = shift;
5393 63994   100     140009 my $anchor = shift || 0;
5394 63994         82776 my $pixels;
5395              
5396             # Look up the cell value to see if it has been changed
5397 63994 100       108157 if ( exists $self->{_row_sizes}->{$row} ) {
5398 84         161 my $height = $self->{_row_sizes}->{$row}[0];
5399 84         130 my $hidden = $self->{_row_sizes}->{$row}[1];
5400              
5401 84 100 100     307 if ( $hidden == 1 && $anchor != 4 ) {
5402 20         54 $pixels = 0;
5403             }
5404             else {
5405 64         148 $pixels = int( 4 / 3 * $height );
5406             }
5407             }
5408             else {
5409 63910         96336 $pixels = int( 4 / 3 * $self->{_default_row_height} );
5410             }
5411              
5412 63994         115920 return $pixels;
5413             }
5414              
5415              
5416             ###############################################################################
5417             #
5418             # _get_shared_string_index()
5419             #
5420             # Add a string to the shared string table, if it isn't already there, and
5421             # return the string index.
5422             #
5423             sub _get_shared_string_index {
5424              
5425 2714     2714   4437 my $self = shift;
5426 2714         4363 my $str = shift;
5427              
5428             # Add the string to the shared string table.
5429 2714 100       4006 if ( not exists ${ $self->{_str_table} }->{$str} ) {
  2714         7270  
5430 1123         1674 ${ $self->{_str_table} }->{$str} = ${ $self->{_str_unique} }++;
  1123         3697  
  1123         2932  
5431             }
5432              
5433 2714         4573 ${ $self->{_str_total} }++;
  2714         5132  
5434 2714         4284 my $index = ${ $self->{_str_table} }->{$str};
  2714         5314  
5435              
5436 2714         5796 return $index;
5437             }
5438              
5439              
5440             ###############################################################################
5441             #
5442             # _get_drawing_rel_index()
5443             #
5444             # Get the index used to address a drawing rel link.
5445             #
5446             sub _get_drawing_rel_index {
5447              
5448 539     539   1250 my $self = shift;
5449 539         1149 my $target = shift;
5450              
5451 539 100       2258 if ( ! defined $target ) {
    100          
5452             # Undefined values for drawings like charts will always be unique.
5453 415         1567 return ++$self->{_drawing_rels_id};
5454             }
5455             elsif ( exists $self->{_drawing_rels}->{$target} ) {
5456 3         16 return $self->{_drawing_rels}->{$target};
5457             }
5458             else {
5459 121         306 $self->{_drawing_rels}->{$target} = ++$self->{_drawing_rels_id};
5460 121         672 return $self->{_drawing_rels_id};
5461             }
5462             }
5463              
5464              
5465             ###############################################################################
5466             #
5467             # _get_vml_drawing_rel_index()
5468             #
5469             # Get the index used to address a vml_drawing rel link.
5470             #
5471             sub _get_vml_drawing_rel_index {
5472              
5473 44     44   78 my $self = shift;
5474 44         78 my $target = shift;
5475              
5476 44 100       124 if ( exists $self->{_vml_drawing_rels}->{$target} ) {
5477 10         24 return $self->{_vml_drawing_rels}->{$target};
5478             }
5479             else {
5480 34         88 $self->{_vml_drawing_rels}->{$target} = ++$self->{_vml_drawing_rels_id};
5481 34         90 return $self->{_vml_drawing_rels_id};
5482             }
5483             }
5484              
5485              
5486             ###############################################################################
5487             #
5488             # insert_chart( $row, $col, $chart, $x, $y, $x_scale, $y_scale )
5489             #
5490             # Insert a chart into a worksheet. The $chart argument should be a Chart
5491             # object or else it is assumed to be a filename of an external binary file.
5492             # The latter is for backwards compatibility.
5493             #
5494             sub insert_chart {
5495              
5496 374     374 0 2963 my $self = shift;
5497              
5498             # Check for a cell reference in A1 notation and substitute row and column.
5499 374 50       2315 if ( $_[0] =~ /^\D/ ) {
5500 374         1778 @_ = $self->_substitute_cellref( @_ );
5501             }
5502              
5503 374         1149 my $row = $_[0];
5504 374         938 my $col = $_[1];
5505 374         830 my $chart = $_[2];
5506 374         2427 my $x_offset;
5507             my $y_offset;
5508 374         0 my $x_scale;
5509 374         0 my $y_scale;
5510 374         0 my $anchor;
5511              
5512 374 50       1845 croak "Insufficient arguments in insert_chart()" unless @_ >= 3;
5513              
5514 374 50       1785 if ( ref $chart ) {
5515              
5516             # Check for a Chart object.
5517 374 50       4257 croak "Not a Chart object in insert_chart()"
5518             unless $chart->isa( 'Excel::Writer::XLSX::Chart' );
5519              
5520             # Check that the chart is an embedded style chart.
5521             croak "Not a embedded style Chart object in insert_chart()"
5522 374 50       2213 unless $chart->{_embedded};
5523              
5524             }
5525              
5526 374 100       1742 if ( ref $_[3] eq 'HASH' ) {
5527             # Newer hashref bashed options.
5528 3         5 my $options = $_[3];
5529 3   50     16 $x_offset = $options->{x_offset} || 0;
5530 3   50     13 $y_offset = $options->{y_offset} || 0;
5531 3   100     36 $x_scale = $options->{x_scale} || 1;
5532 3   100     15 $y_scale = $options->{y_scale} || 1;
5533 3   100     12 $anchor = $options->{object_position} || 1;
5534             }
5535             else {
5536             # Older parameter based options.
5537 371   100     2459 $x_offset = $_[3] || 0;
5538 371   100     2349 $y_offset = $_[4] || 0;
5539 371   100     2880 $x_scale = $_[5] || 1;
5540 371   100     1880 $y_scale = $_[6] || 1;
5541 371   100     1947 $anchor = $_[7] || 1;
5542             }
5543              
5544             # Ensure a chart isn't inserted more than once.
5545 374 50 66     3197 if ( $chart->{_already_inserted}
      33        
5546             || $chart->{_combined} && $chart->{_combined}->{_already_inserted} )
5547             {
5548 0         0 carp "Chart cannot be inserted in a worksheet more than once";
5549 0         0 return;
5550             }
5551             else {
5552 374         997 $chart->{_already_inserted} = 1;
5553              
5554 374 100       1394 if ( $chart->{_combined} ) {
5555 10         34 $chart->{_combined}->{_already_inserted} = 1;
5556             }
5557             }
5558              
5559             # Use the values set with $chart->set_size(), if any.
5560 374 100       1530 $x_scale = $chart->{_x_scale} if $chart->{_x_scale} != 1;
5561 374 100       1379 $y_scale = $chart->{_y_scale} if $chart->{_y_scale} != 1;
5562 374 100       1364 $x_offset = $chart->{_x_offset} if $chart->{_x_offset};
5563 374 100       1447 $y_offset = $chart->{_y_offset} if $chart->{_y_offset};
5564              
5565 374         868 push @{ $self->{_charts} },
  374         2703  
5566             [ $row, $col, $chart, $x_offset, $y_offset, $x_scale, $y_scale, $anchor ];
5567             }
5568              
5569              
5570             ###############################################################################
5571             #
5572             # _prepare_chart()
5573             #
5574             # Set up chart/drawings.
5575             #
5576             sub _prepare_chart {
5577              
5578 374     374   982 my $self = shift;
5579 374         825 my $index = shift;
5580 374         779 my $chart_id = shift;
5581 374         873 my $drawing_id = shift;
5582 374         834 my $drawing_type = 1;
5583 374         781 my $drawing;
5584              
5585             my ( $row, $col, $chart, $x_offset, $y_offset, $x_scale, $y_scale, $anchor )
5586 374         764 = @{ $self->{_charts}->[$index] };
  374         1840  
5587              
5588 374         1248 $chart->{_id} = $chart_id - 1;
5589              
5590             # Use user specified dimensions, if any.
5591 374 50       1728 my $width = $chart->{_width} if $chart->{_width};
5592 374 50       1504 my $height = $chart->{_height} if $chart->{_height};
5593              
5594 374         1403 $width = int( 0.5 + ( $width * $x_scale ) );
5595 374         1045 $height = int( 0.5 + ( $height * $y_scale ) );
5596              
5597 374         1883 my @dimensions =
5598             $self->_position_object_emus( $col, $row, $x_offset, $y_offset, $width,
5599             $height, $anchor);
5600              
5601             # Set the chart name for the embedded object if it has been specified.
5602 374         1224 my $name = $chart->{_chart_name};
5603              
5604             # Create a Drawing object to use with worksheet unless one already exists.
5605 374 100       1733 if ( !$self->{_drawing} ) {
5606              
5607 362         3983 $drawing = Excel::Writer::XLSX::Drawing->new();
5608 362         2721 $drawing->{_embedded} = 1;
5609 362         1064 $self->{_drawing} = $drawing;
5610              
5611 362         848 push @{ $self->{_external_drawing_links} },
  362         2652  
5612             [ '/drawing', '../drawings/drawing' . $drawing_id . '.xml' ];
5613             }
5614             else {
5615 12         30 $drawing = $self->{_drawing};
5616             }
5617              
5618 374         2726 my $drawing_object = $drawing->_add_drawing_object();
5619              
5620 374         1151 $drawing_object->{_type} = $drawing_type;
5621 374         1300 $drawing_object->{_dimensions} = \@dimensions;
5622 374         882 $drawing_object->{_width} = 0;
5623 374         999 $drawing_object->{_height} = 0;
5624 374         947 $drawing_object->{_description} = $name;
5625 374         902 $drawing_object->{_shape} = undef;
5626 374         927 $drawing_object->{_anchor} = $anchor;
5627 374         2126 $drawing_object->{_rel_index} = $self->_get_drawing_rel_index();
5628 374         921 $drawing_object->{_url_rel_index} = 0;
5629 374         873 $drawing_object->{_tip} = undef;
5630              
5631 374         764 push @{ $self->{_drawing_links} },
  374         3178  
5632             [ '/chart', '../charts/chart' . $chart_id . '.xml' ];
5633             }
5634              
5635              
5636             ###############################################################################
5637             #
5638             # _get_range_data
5639             #
5640             # Returns a range of data from the worksheet _table to be used in chart
5641             # cached data. Strings are returned as SST ids and decoded in the workbook.
5642             # Return undefs for data that doesn't exist since Excel can chart series
5643             # with data missing.
5644             #
5645             sub _get_range_data {
5646              
5647 1062     1062   2097 my $self = shift;
5648              
5649 1062 50       3053 return () if $self->{_optimization};
5650              
5651 1062         1816 my @data;
5652 1062         2772 my ( $row_start, $col_start, $row_end, $col_end ) = @_;
5653              
5654             # TODO. Check for worksheet limits.
5655              
5656             # Iterate through the table data.
5657 1062         2953 for my $row_num ( $row_start .. $row_end ) {
5658              
5659             # Store undef if row doesn't exist.
5660 5044 100       11204 if ( !exists $self->{_table}->{$row_num} ) {
5661 5         11 push @data, undef;
5662 5         10 next;
5663             }
5664              
5665 5039         8362 for my $col_num ( $col_start .. $col_end ) {
5666              
5667 5039 100       11583 if ( my $cell = $self->{_table}->{$row_num}->{$col_num} ) {
5668              
5669 5035         7706 my $type = $cell->[0];
5670 5035         6949 my $token = $cell->[1];
5671              
5672              
5673 5035 100       9011 if ( $type eq 'n' ) {
    50          
    0          
    0          
    0          
5674              
5675             # Store a number.
5676 5010         11538 push @data, $token;
5677             }
5678             elsif ( $type eq 's' ) {
5679              
5680             # Store a string.
5681 25 50       64 if ( $self->{_optimization} == 0 ) {
5682 25         101 push @data, { 'sst_id' => $token };
5683             }
5684             else {
5685 0         0 push @data, $token;
5686             }
5687             }
5688             elsif ( $type eq 'f' ) {
5689              
5690             # Store a formula.
5691 0   0     0 push @data, $cell->[3] || 0;
5692             }
5693             elsif ( $type eq 'a' ) {
5694              
5695             # Store an array formula.
5696 0   0     0 push @data, $cell->[4] || 0;
5697             }
5698             elsif ( $type eq 'b' ) {
5699              
5700             # Store a empty cell.
5701 0         0 push @data, '';
5702             }
5703             }
5704             else {
5705              
5706             # Store undef if col doesn't exist.
5707 4         23 push @data, undef;
5708             }
5709             }
5710             }
5711              
5712 1062         4253 return @data;
5713             }
5714              
5715              
5716             ###############################################################################
5717             #
5718             # insert_image( $row, $col, $filename, $options )
5719             #
5720             # Insert an image into the worksheet.
5721             #
5722             sub insert_image {
5723              
5724 103     103 0 1004 my $self = shift;
5725              
5726             # Check for a cell reference in A1 notation and substitute row and column.
5727 103 100       612 if ( $_[0] =~ /^\D/ ) {
5728 101         474 @_ = $self->_substitute_cellref( @_ );
5729             }
5730              
5731 103         272 my $row = $_[0];
5732 103         264 my $col = $_[1];
5733 103         312 my $image = $_[2];
5734 103         848 my $x_offset;
5735             my $y_offset;
5736 103         0 my $x_scale;
5737 103         0 my $y_scale;
5738 103         0 my $anchor;
5739 103         0 my $url;
5740 103         0 my $tip;
5741              
5742 103 100       388 if ( ref $_[3] eq 'HASH' ) {
5743             # Newer hashref bashed options.
5744 24         46 my $options = $_[3];
5745 24   100     160 $x_offset = $options->{x_offset} || 0;
5746 24   100     121 $y_offset = $options->{y_offset} || 0;
5747 24   100     115 $x_scale = $options->{x_scale} || 1;
5748 24   100     129 $y_scale = $options->{y_scale} || 1;
5749 24   100     151 $anchor = $options->{object_position} || 2;
5750 24         89 $url = $options->{url};
5751 24         70 $tip = $options->{tip};
5752             }
5753             else {
5754             # Older parameter based options.
5755 79   100     419 $x_offset = $_[3] || 0;
5756 79   100     359 $y_offset = $_[4] || 0;
5757 79   100     385 $x_scale = $_[5] || 1;
5758 79   100     358 $y_scale = $_[6] || 1;
5759 79   100     330 $anchor = $_[7] || 2;
5760             }
5761              
5762 103 50       407 croak "Insufficient arguments in insert_image()" unless @_ >= 3;
5763 103 50       2215 croak "Couldn't locate $image: $!" unless -e $image;
5764              
5765 103         312 push @{ $self->{_images} },
  103         1170  
5766             [
5767             $row, $col, $image, $x_offset, $y_offset,
5768             $x_scale, $y_scale, $url, $tip, $anchor
5769             ];
5770             }
5771              
5772              
5773             ###############################################################################
5774             #
5775             # _prepare_image()
5776             #
5777             # Set up image/drawings.
5778             #
5779             sub _prepare_image {
5780              
5781 103     103   245 my $self = shift;
5782 103         228 my $index = shift;
5783 103         195 my $image_id = shift;
5784 103         191 my $drawing_id = shift;
5785 103         188 my $width = shift;
5786 103         190 my $height = shift;
5787 103         204 my $name = shift;
5788 103         197 my $image_type = shift;
5789 103         172 my $x_dpi = shift;
5790 103         207 my $y_dpi = shift;
5791 103         223 my $md5 = shift;
5792 103         202 my $drawing_type = 2;
5793 103         175 my $drawing;
5794              
5795             my (
5796             $row, $col, $image, $x_offset, $y_offset,
5797             $x_scale, $y_scale, $url, $tip, $anchor
5798 103         186 ) = @{ $self->{_images}->[$index] };
  103         454  
5799              
5800 103         215 $width *= $x_scale;
5801 103         212 $height *= $y_scale;
5802              
5803 103         317 $width *= 96 / $x_dpi;
5804 103         242 $height *= 96 / $y_dpi;
5805              
5806 103         435 my @dimensions =
5807             $self->_position_object_emus( $col, $row, $x_offset, $y_offset, $width,
5808             $height, $anchor);
5809              
5810             # Convert from pixels to emus.
5811 103         315 $width = int( 0.5 + ( $width * 9_525 ) );
5812 103         290 $height = int( 0.5 + ( $height * 9_525 ) );
5813              
5814             # Create a Drawing object to use with worksheet unless one already exists.
5815 103 100       540 if ( !$self->{_drawing} ) {
5816              
5817 77         797 $drawing = Excel::Writer::XLSX::Drawing->new();
5818 77         672 $drawing->{_embedded} = 1;
5819 77         232 $self->{_drawing} = $drawing;
5820              
5821 77         187 push @{ $self->{_external_drawing_links} },
  77         541  
5822             [ '/drawing', '../drawings/drawing' . $drawing_id . '.xml' ];
5823             }
5824             else {
5825 26         53 $drawing = $self->{_drawing};
5826             }
5827              
5828 103         505 my $drawing_object = $drawing->_add_drawing_object();
5829              
5830 103         281 $drawing_object->{_type} = $drawing_type;
5831 103         290 $drawing_object->{_dimensions} = \@dimensions;
5832 103         265 $drawing_object->{_width} = $width;
5833 103         243 $drawing_object->{_height} = $height;
5834 103         267 $drawing_object->{_description} = $name;
5835 103         209 $drawing_object->{_shape} = undef;
5836 103         253 $drawing_object->{_anchor} = $anchor;
5837 103         244 $drawing_object->{_rel_index} = 0;
5838 103         204 $drawing_object->{_url_rel_index} = 0;
5839 103         223 $drawing_object->{_tip} = $tip;
5840              
5841              
5842 103 100       338 if ( $url ) {
5843 21         52 my $rel_type = '/hyperlink';
5844 21         44 my $target_mode = 'External';
5845 21         41 my $target;
5846              
5847 21 100 100     204 if ( $url =~ m{^[fh]tt?ps?://} || $url =~ m{^mailto:} ) {
5848 16         70 $target = _escape_url( $url );
5849             }
5850              
5851 21 100       111 if ( $url =~ s{^external:}{file:///} ) {
5852 3         26 $target = _escape_url( $url );
5853              
5854             # Additional escape not required in worksheet hyperlinks.
5855 3         11 $target =~ s/#/%23/g;
5856             }
5857              
5858 21 100       84 if ( $url =~ s/^internal:/#/ ) {
5859 2         4 $target = $url;
5860 2         4 $target_mode = undef;
5861             }
5862              
5863 21         56 my $max_url = $self->{_max_url_length};
5864 21 50       88 if ( length $target > $max_url ) {
5865 0         0 carp "Ignoring URL '$url' where link or anchor > $max_url characters "
5866             . "since it exceeds Excel's limit for URLS. See LIMITATIONS "
5867             . "section of the Excel::Writer::XLSX documentation.";
5868             }
5869             else {
5870 21 100 66     130 if ( $target && !exists $self->{_drawing_rels}->{$url} ) {
5871 20         48 push @{ $self->{_drawing_links} },
  20         87  
5872             [ $rel_type, $target, $target_mode ];
5873             }
5874              
5875             $drawing_object->{_url_rel_index} =
5876 21         74 $self->_get_drawing_rel_index( $url );
5877             }
5878             }
5879              
5880 103 100       469 if ( !exists $self->{_drawing_rels}->{$md5} ) {
5881 101         213 push @{ $self->{_drawing_links} },
  101         552  
5882             [ '/image', '../media/image' . $image_id . '.' . $image_type ];
5883             }
5884              
5885 103         464 $drawing_object->{_rel_index} = $self->_get_drawing_rel_index( $md5 );
5886             }
5887              
5888              
5889             ###############################################################################
5890             #
5891             # _prepare_header_image()
5892             #
5893             # Set up an image without a drawing object for header/footer images.
5894             #
5895             sub _prepare_header_image {
5896              
5897 44     44   85 my $self = shift;
5898 44         75 my $image_id = shift;
5899 44         76 my $width = shift;
5900 44         76 my $height = shift;
5901 44         75 my $name = shift;
5902 44         78 my $image_type = shift;
5903 44         72 my $position = shift;
5904 44         71 my $x_dpi = shift;
5905 44         70 my $y_dpi = shift;
5906 44         69 my $md5 = shift;
5907              
5908             # Strip the extension from the filename.
5909 44         286 $name =~ s/\.[^\.]+$//;
5910              
5911 44 100       169 if ( !exists $self->{_vml_drawing_rels}->{$md5} ) {
5912 34         57 push @{ $self->{_vml_drawing_links} },
  34         169  
5913             [ '/image', '../media/image' . $image_id . '.' . $image_type ];
5914             }
5915              
5916 44         142 my $ref_id = $self->_get_vml_drawing_rel_index( $md5 );
5917              
5918 44         80 push @{ $self->{_header_images_array} },
  44         232  
5919             [ $width, $height, $name, $position, $x_dpi, $y_dpi, $ref_id ];
5920             }
5921              
5922              
5923             ###############################################################################
5924             #
5925             # insert_shape( $row, $col, $shape, $x, $y, $x_scale, $y_scale )
5926             #
5927             # Insert a shape into the worksheet.
5928             #
5929             sub insert_shape {
5930              
5931 45     45 0 211 my $self = shift;
5932              
5933             # Check for a cell reference in A1 notation and substitute row and column.
5934 45 100       196 if ( $_[0] =~ /^\D/ ) {
5935 41         136 @_ = $self->_substitute_cellref( @_ );
5936             }
5937              
5938             # Check the number of arguments.
5939 45 50       451 croak "Insufficient arguments in insert_shape()" unless @_ >= 3;
5940              
5941 45         96 my $shape = $_[2];
5942              
5943             # Verify we are being asked to insert a "shape" object.
5944 45 50       312 croak "Not a Shape object in insert_shape()"
5945             unless $shape->isa( 'Excel::Writer::XLSX::Shape' );
5946              
5947             # Set the shape properties.
5948 45         173 $shape->{_row_start} = $_[0];
5949 45         78 $shape->{_column_start} = $_[1];
5950 45   100     136 $shape->{_x_offset} = $_[3] || 0;
5951 45   100     124 $shape->{_y_offset} = $_[4] || 0;
5952              
5953             # Override shape scale if supplied as an argument. Otherwise, use the
5954             # existing shape scale factors.
5955 45 100       104 $shape->{_scale_x} = $_[5] if defined $_[5];
5956 45 100       98 $shape->{_scale_y} = $_[6] if defined $_[6];
5957 45   50     164 $shape->{_anchor} = $_[7] || 1;
5958              
5959             # Assign a shape ID.
5960 45         156 my $needs_id = 1;
5961 45         102 while ( $needs_id ) {
5962 90   100     229 my $id = $shape->{_id} || 0;
5963 90 100       234 my $used = exists $self->{_shape_hash}->{$id} ? 1 : 0;
5964              
5965             # Test if shape ID is already used. Otherwise assign a new one.
5966 90 100 100     299 if ( !$used && $id != 0 ) {
5967 45         107 $needs_id = 0;
5968             }
5969             else {
5970 45         121 $shape->{_id} = ++$self->{_last_shape_id};
5971             }
5972             }
5973              
5974 45         71 $shape->{_element} = $#{ $self->{_shapes} } + 1;
  45         114  
5975              
5976             # Allow lookup of entry into shape array by shape ID.
5977 45         162 $self->{_shape_hash}->{ $shape->{_id} } = $shape->{_element};
5978              
5979             # Create link to Worksheet color palette.
5980 45         83 $shape->{_palette} = $self->{_palette};
5981              
5982 45 50       104 if ( $shape->{_stencil} ) {
5983              
5984             # Insert a copy of the shape, not a reference so that the shape is
5985             # used as a stencil. Previously stamped copies don't get modified
5986             # if the stencil is modified.
5987 45         66 my $insert = { %{$shape} };
  45         880  
5988              
5989             # For connectors change x/y coords based on location of connected shapes.
5990 45         249 $self->_auto_locate_connectors( $insert );
5991              
5992             # Bless the copy into this class, so AUTOLOADED _get, _set methods
5993             #still work on the child.
5994 45         113 bless $insert, ref $shape;
5995              
5996 45         76 push @{ $self->{_shapes} }, $insert;
  45         103  
5997 45         157 return $insert;
5998             }
5999             else {
6000              
6001             # For connectors change x/y coords based on location of connected shapes.
6002 0         0 $self->_auto_locate_connectors( $shape );
6003              
6004             # Insert a link to the shape on the list of shapes. Connection to
6005             # the parent shape is maintained
6006 0         0 push @{ $self->{_shapes} }, $shape;
  0         0  
6007 0         0 return $shape;
6008             }
6009             }
6010              
6011              
6012             ###############################################################################
6013             #
6014             # _prepare_shape()
6015             #
6016             # Set up drawing shapes
6017             #
6018             sub _prepare_shape {
6019              
6020 41     41   89 my $self = shift;
6021 41         70 my $index = shift;
6022 41         70 my $drawing_id = shift;
6023 41         79 my $shape = $self->{_shapes}->[$index];
6024 41         59 my $drawing;
6025 41         65 my $drawing_type = 3;
6026              
6027             # Create a Drawing object to use with worksheet unless one already exists.
6028 41 100       110 if ( !$self->{_drawing} ) {
6029              
6030 10         100 $drawing = Excel::Writer::XLSX::Drawing->new();
6031 10         71 $drawing->{_embedded} = 1;
6032 10         29 $self->{_drawing} = $drawing;
6033              
6034 10         20 push @{ $self->{_external_drawing_links} },
  10         63  
6035             [ '/drawing', '../drawings/drawing' . $drawing_id . '.xml' ];
6036              
6037 10         36 $self->{_has_shapes} = 1;
6038             }
6039             else {
6040 31         49 $drawing = $self->{_drawing};
6041             }
6042              
6043             # Validate the he shape against various rules.
6044 41         188 $self->_validate_shape( $shape, $index );
6045              
6046 41         143 $self->_position_shape_emus( $shape );
6047              
6048             my @dimensions = (
6049             $shape->{_column_start}, $shape->{_row_start},
6050             $shape->{_x1}, $shape->{_y1},
6051             $shape->{_column_end}, $shape->{_row_end},
6052             $shape->{_x2}, $shape->{_y2},
6053             $shape->{_x_abs}, $shape->{_y_abs},
6054 41         164 );
6055              
6056 41         169 my $drawing_object = $drawing->_add_drawing_object();
6057              
6058 41         87 $drawing_object->{_type} = $drawing_type;
6059 41         87 $drawing_object->{_dimensions} = \@dimensions;
6060 41         76 $drawing_object->{_width} = $shape->{_width_emu};
6061 41         72 $drawing_object->{_height} = $shape->{_height_emu};
6062 41         72 $drawing_object->{_description} = $shape->{_name};
6063 41         75 $drawing_object->{_shape} = $shape;
6064 41         71 $drawing_object->{_anchor} = $shape->{_anchor};
6065 41         148 $drawing_object->{_rel_index} = $self->_get_drawing_rel_index();
6066 41         88 $drawing_object->{_url_rel_index} = 0;
6067 41         135 $drawing_object->{_tip} = undef;
6068             }
6069              
6070              
6071             ###############################################################################
6072             #
6073             # _auto_locate_connectors()
6074             #
6075             # Re-size connector shapes if they are connected to other shapes.
6076             #
6077             sub _auto_locate_connectors {
6078              
6079 45     45   75 my $self = shift;
6080 45         73 my $shape = shift;
6081              
6082             # Valid connector shapes.
6083 45         203 my $connector_shapes = {
6084             straightConnector => 1,
6085             Connector => 1,
6086             bentConnector => 1,
6087             curvedConnector => 1,
6088             line => 1,
6089             };
6090              
6091 45         98 my $shape_base = $shape->{_type};
6092              
6093             # Remove the number of segments from end of type.
6094 45         98 chop $shape_base;
6095              
6096 45 100       115 $shape->{_connect} = $connector_shapes->{$shape_base} ? 1 : 0;
6097              
6098 45 100       162 return unless $shape->{_connect};
6099              
6100             # Both ends have to be connected to size it.
6101 12 50 33     88 return unless ( $shape->{_start} and $shape->{_end} );
6102              
6103             # Both ends need to provide info about where to connect.
6104 12 50 33     52 return unless ( $shape->{_start_side} and $shape->{_end_side} );
6105              
6106 12         26 my $sid = $shape->{_start};
6107 12         26 my $eid = $shape->{_end};
6108              
6109 12         32 my $slink_id = $self->{_shape_hash}->{$sid};
6110 12         29 my ( $sls, $els );
6111 12 100       48 if ( defined $slink_id ) {
6112 11         24 $sls = $self->{_shapes}->[$slink_id]; # Start linked shape.
6113             }
6114             else {
6115 1         11 warn "missing start connection for '$shape->{_name}', id=$sid\n";
6116 1         8 return;
6117             }
6118              
6119 11         32 my $elink_id = $self->{_shape_hash}->{$eid};
6120 11 100       46 if ( defined $elink_id ) {
6121 10         20 $els = $self->{_shapes}->[$elink_id]; # Start linked shape.
6122             }
6123             else {
6124 1         10 warn "missing end connection for '$shape->{_name}', id=$eid\n";
6125 1         7 return;
6126             }
6127              
6128             # Assume shape connections are to the middle of an object, and
6129             # not a corner (for now).
6130 10         28 my $connect_type = $shape->{_start_side} . $shape->{_end_side};
6131 10         36 my $smidx = $sls->{_x_offset} + $sls->{_width} / 2;
6132 10         28 my $emidx = $els->{_x_offset} + $els->{_width} / 2;
6133 10         22 my $smidy = $sls->{_y_offset} + $sls->{_height} / 2;
6134 10         35 my $emidy = $els->{_y_offset} + $els->{_height} / 2;
6135 10         24 my $netx = abs( $smidx - $emidx );
6136 10         17 my $nety = abs( $smidy - $emidy );
6137              
6138 10 100       38 if ( $connect_type eq 'bt' ) {
    50          
6139 5         11 my $sy = $sls->{_y_offset} + $sls->{_height};
6140 5         9 my $ey = $els->{_y_offset};
6141              
6142 5         11 $shape->{_width} = abs( int( $emidx - $smidx ) );
6143 5         20 $shape->{_x_offset} = int( min( $smidx, $emidx ) );
6144             $shape->{_height} =
6145             abs(
6146 5         14 int( $els->{_y_offset} - ( $sls->{_y_offset} + $sls->{_height} ) )
6147             );
6148             $shape->{_y_offset} = int(
6149 5         16 min( ( $sls->{_y_offset} + $sls->{_height} ), $els->{_y_offset} ) );
6150 5 100       13 $shape->{_flip_h} = ( $smidx < $emidx ) ? 1 : 0;
6151 5         10 $shape->{_rotation} = 90;
6152              
6153 5 100       20 if ( $sy > $ey ) {
6154 2         6 $shape->{_flip_v} = 1;
6155              
6156             # Create 3 adjustments for an end shape vertically above a
6157             # start shape. Adjustments count from the upper left object.
6158 2 100       5 if ( $#{ $shape->{_adjustments} } < 0 ) {
  2         8  
6159 1         4 $shape->{_adjustments} = [ -10, 50, 110 ];
6160             }
6161              
6162 2         6 $shape->{_type} = 'bentConnector5';
6163             }
6164             }
6165             elsif ( $connect_type eq 'rl' ) {
6166             $shape->{_width} =
6167             abs(
6168 5         16 int( $els->{_x_offset} - ( $sls->{_x_offset} + $sls->{_width} ) ) );
6169 5         10 $shape->{_height} = abs( int( $emidy - $smidy ) );
6170             $shape->{_x_offset} =
6171 5         24 min( $sls->{_x_offset} + $sls->{_width}, $els->{_x_offset} );
6172 5         13 $shape->{_y_offset} = min( $smidy, $emidy );
6173              
6174 5 100 100     22 $shape->{_flip_h} = 1 if ( $smidx < $emidx ) and ( $smidy > $emidy );
6175 5 100 100     42 $shape->{_flip_h} = 1 if ( $smidx > $emidx ) and ( $smidy < $emidy );
6176 5 100       25 if ( $smidx > $emidx ) {
6177              
6178             # Create 3 adjustments if end shape is left of start
6179 2 100       3 if ( $#{ $shape->{_adjustments} } < 0 ) {
  2         8  
6180 1         2 $shape->{_adjustments} = [ -10, 50, 110 ];
6181             }
6182              
6183 2         7 $shape->{_type} = 'bentConnector5';
6184             }
6185             }
6186             else {
6187 0         0 warn "Connection $connect_type not implemented yet\n";
6188             }
6189             }
6190              
6191              
6192             ###############################################################################
6193             #
6194             # _validate_shape()
6195             #
6196             # Check shape attributes to ensure they are valid.
6197             #
6198             sub _validate_shape {
6199              
6200 41     41   66 my $self = shift;
6201 41         77 my $shape = shift;
6202 41         63 my $index = shift;
6203              
6204 41 50       566 if ( !grep ( /^$shape->{_align}$/, qw[l ctr r just] ) ) {
6205 0         0 croak "Shape $index ($shape->{_type}) alignment ($shape->{align}), "
6206             . "not in ('l', 'ctr', 'r', 'just')\n";
6207             }
6208              
6209 41 50       397 if ( !grep ( /^$shape->{_valign}$/, qw[t ctr b] ) ) {
6210 0         0 croak "Shape $index ($shape->{_type}) vertical alignment "
6211             . "($shape->{valign}), not ('t', 'ctr', 'b')\n";
6212             }
6213             }
6214              
6215              
6216             ###############################################################################
6217             #
6218             # _prepare_vml_objects()
6219             #
6220             # Turn the HoH that stores the comments into an array for easier handling
6221             # and set the external links for comments and buttons.
6222             #
6223             sub _prepare_vml_objects {
6224              
6225 53     53   128 my $self = shift;
6226 53         129 my $vml_data_id = shift;
6227 53         102 my $vml_shape_id = shift;
6228 53         120 my $vml_drawing_id = shift;
6229 53         107 my $comment_id = shift;
6230 53         115 my @comments;
6231              
6232              
6233             # We sort the comments by row and column but that isn't strictly required.
6234 53         126 my @rows = sort { $a <=> $b } keys %{ $self->{_comments} };
  1504         2010  
  53         363  
6235              
6236 53         177 for my $row ( @rows ) {
6237 310         501 my @cols = sort { $a <=> $b } keys %{ $self->{_comments}->{$row} };
  11311         16346  
  310         2800  
6238              
6239 310         849 for my $col ( @cols ) {
6240 4153         8815 my $user_options = $self->{_comments}->{$row}->{$col};
6241 4153         10739 my $params = [ $self->_comment_params( @$user_options ) ];
6242              
6243 4153         11951 $self->{_comments}->{$row}->{$col} = $params;
6244              
6245             # Set comment visibility if required and not already user defined.
6246 4153 100       8847 if ( $self->{_comments_visible} ) {
6247 10 100       24 if ( !defined $self->{_comments}->{$row}->{$col}->[4] ) {
6248 8         14 $self->{_comments}->{$row}->{$col}->[4] = 1;
6249             }
6250             }
6251              
6252             # Set comment author if not already user defined.
6253 4153 100       8990 if ( !defined $self->{_comments}->{$row}->{$col}->[3] ) {
6254             $self->{_comments}->{$row}->{$col}->[3] =
6255 4151         8295 $self->{_comments_author};
6256             }
6257              
6258 4153         11498 push @comments, $self->{_comments}->{$row}->{$col};
6259             }
6260             }
6261              
6262 53         138 push @{ $self->{_external_vml_links} },
  53         339  
6263             [ '/vmlDrawing', '../drawings/vmlDrawing' . $vml_drawing_id . '.vml' ];
6264              
6265 53 100       227 if ( $self->{_has_comments} ) {
6266              
6267 39         130 $self->{_comments_array} = \@comments;
6268              
6269 39         87 push @{ $self->{_external_comment_links} },
  39         191  
6270             [ '/comments', '../comments' . $comment_id . '.xml' ];
6271             }
6272              
6273 53         128 my $count = scalar @comments;
6274 53         176 my $start_data_id = $vml_data_id;
6275              
6276             # The VML o:idmap data id contains a comma separated range when there is
6277             # more than one 1024 block of comments, like this: data="1,2".
6278 53         285 for my $i ( 1 .. int( $count / 1024 ) ) {
6279 4         12 $vml_data_id = "$vml_data_id," . ( $start_data_id + $i );
6280             }
6281              
6282 53         172 $self->{_vml_data_id} = $vml_data_id;
6283 53         139 $self->{_vml_shape_id} = $vml_shape_id;
6284              
6285 53         320 return $count;
6286             }
6287              
6288              
6289             ###############################################################################
6290             #
6291             # _prepare_header_vml_objects()
6292             #
6293             # Set up external linkage for VML header/footer images.
6294             #
6295             sub _prepare_header_vml_objects {
6296              
6297 22     22   64 my $self = shift;
6298 22         46 my $vml_header_id = shift;
6299 22         396 my $vml_drawing_id = shift;
6300              
6301 22         244 $self->{_vml_header_id} = $vml_header_id;
6302              
6303 22         375 push @{ $self->{_external_vml_links} },
  22         329  
6304             [ '/vmlDrawing', '../drawings/vmlDrawing' . $vml_drawing_id . '.vml' ];
6305             }
6306              
6307              
6308             ###############################################################################
6309             #
6310             # _prepare_tables()
6311             #
6312             # Set the table ids for the worksheet tables.
6313             #
6314             sub _prepare_tables {
6315              
6316 39     39   188 my $self = shift;
6317 39         87 my $table_id = shift;
6318 39         83 my $seen = shift;
6319              
6320              
6321 39         81 for my $table ( @{ $self->{_tables} } ) {
  39         140  
6322              
6323 47         125 $table-> {_id} = $table_id;
6324              
6325             # Set the table name unless defined by the user.
6326 47 100       188 if ( !defined $table->{_name} ) {
6327              
6328             # Set a default name.
6329 46         194 $table->{_name} = 'Table' . $table_id;
6330             }
6331              
6332             # Check for duplicate table names.
6333 47         156 my $name = lc $table->{_name};
6334              
6335 47 50       184 if ( exists $seen->{$name} ) {
6336 0         0 die "error: invalid duplicate table name '$table->{_name}' found";
6337             }
6338             else {
6339 47         154 $seen->{$name} = 1;
6340             }
6341              
6342             # Store the link used for the rels file.
6343 47         199 my $link = [ '/table', '../tables/table' . $table_id . '.xml' ];
6344              
6345 47         112 push @{ $self->{_external_table_links} }, $link;
  47         161  
6346 47         168 $table_id++;
6347             }
6348             }
6349              
6350              
6351             ###############################################################################
6352             #
6353             # _comment_params()
6354             #
6355             # This method handles the additional optional parameters to write_comment() as
6356             # well as calculating the comment object position and vertices.
6357             #
6358             sub _comment_params {
6359              
6360 4153     4153   5797 my $self = shift;
6361              
6362 4153         6230 my $row = shift;
6363 4153         5634 my $col = shift;
6364 4153         5940 my $string = shift;
6365              
6366 4153         5791 my $default_width = 128;
6367 4153         5682 my $default_height = 74;
6368              
6369 4153         21972 my %params = (
6370             author => undef,
6371             color => 81,
6372             start_cell => undef,
6373             start_col => undef,
6374             start_row => undef,
6375             visible => undef,
6376             width => $default_width,
6377             height => $default_height,
6378             x_offset => undef,
6379             x_scale => 1,
6380             y_offset => undef,
6381             y_scale => 1,
6382             font => 'Tahoma',
6383             font_size => 8,
6384             font_family => 2,
6385             );
6386              
6387              
6388             # Overwrite the defaults with any user supplied values. Incorrect or
6389             # misspelled parameters are silently ignored.
6390 4153         28902 %params = ( %params, @_ );
6391              
6392              
6393             # Ensure that a width and height have been set.
6394 4153 50       11899 $params{width} = $default_width if not $params{width};
6395 4153 50       8266 $params{height} = $default_height if not $params{height};
6396              
6397              
6398             # Limit the string to the max number of chars.
6399 4153         5944 my $max_len = 32767;
6400              
6401 4153 50       8086 if ( length( $string ) > $max_len ) {
6402 0         0 $string = substr( $string, 0, $max_len );
6403             }
6404              
6405              
6406             # Set the comment background colour.
6407 4153         6491 my $color = $params{color};
6408 4153         10910 my $color_id = &Excel::Writer::XLSX::Format::_get_color( $color );
6409              
6410 4153 50       11134 if ( $color_id =~ m/^#[0-9A-F]{6}$/i ) {
    100          
6411 0         0 $params{color} = $color_id;
6412             }
6413             elsif ( $color_id == 0 ) {
6414 4152         7880 $params{color} = '#ffffe1';
6415             }
6416             else {
6417 1         2 my $palette = $self->{_palette};
6418              
6419             # Get the RGB color from the palette.
6420 1         2 my @rgb = @{ $palette->[ $color_id - 8 ] };
  1         3  
6421 1         7 my $rgb_color = sprintf "%02x%02x%02x", @rgb[0, 1, 2];
6422              
6423             # Minor modification to allow comparison testing. Change RGB colors
6424             # from long format, ffcc00 to short format fc0 used by VML.
6425 1         11 $rgb_color =~ s/^([0-9a-f])\1([0-9a-f])\2([0-9a-f])\3$/$1$2$3/;
6426              
6427 1         7 $params{color} = sprintf "#%s [%d]", $rgb_color, $color_id;
6428             }
6429              
6430              
6431             # Convert a cell reference to a row and column.
6432 4153 50       8267 if ( defined $params{start_cell} ) {
6433 0         0 my ( $row, $col ) = $self->_substitute_cellref( $params{start_cell} );
6434 0         0 $params{start_row} = $row;
6435 0         0 $params{start_col} = $col;
6436             }
6437              
6438              
6439             # Set the default start cell and offsets for the comment. These are
6440             # generally fixed in relation to the parent cell. However there are
6441             # some edge cases for cells at the, er, edges.
6442             #
6443 4153         6622 my $row_max = $self->{_xls_rowmax};
6444 4153         6109 my $col_max = $self->{_xls_colmax};
6445              
6446 4153 50       7964 if ( not defined $params{start_row} ) {
6447              
6448 4153 100       12445 if ( $row == 0 ) { $params{start_row} = 0 }
  51 50       96  
    50          
    100          
6449 0         0 elsif ( $row == $row_max - 3 ) { $params{start_row} = $row_max - 7 }
6450 0         0 elsif ( $row == $row_max - 2 ) { $params{start_row} = $row_max - 6 }
6451 1         3 elsif ( $row == $row_max - 1 ) { $params{start_row} = $row_max - 5 }
6452 4101         6597 else { $params{start_row} = $row - 1 }
6453             }
6454              
6455 4153 100       8339 if ( not defined $params{y_offset} ) {
6456              
6457 4152 100       11403 if ( $row == 0 ) { $params{y_offset} = 2 }
  51 50       108  
    50          
    100          
6458 0         0 elsif ( $row == $row_max - 3 ) { $params{y_offset} = 16 }
6459 0         0 elsif ( $row == $row_max - 2 ) { $params{y_offset} = 16 }
6460 1         2 elsif ( $row == $row_max - 1 ) { $params{y_offset} = 14 }
6461 4100         6355 else { $params{y_offset} = 10 }
6462             }
6463              
6464 4153 50       8171 if ( not defined $params{start_col} ) {
6465              
6466 4153 50       9613 if ( $col == $col_max - 3 ) { $params{start_col} = $col_max - 6 }
  0 50       0  
    100          
6467 0         0 elsif ( $col == $col_max - 2 ) { $params{start_col} = $col_max - 5 }
6468 1         2 elsif ( $col == $col_max - 1 ) { $params{start_col} = $col_max - 4 }
6469 4152         6611 else { $params{start_col} = $col + 1 }
6470             }
6471              
6472 4153 50       8073 if ( not defined $params{x_offset} ) {
6473              
6474 4153 50       9920 if ( $col == $col_max - 3 ) { $params{x_offset} = 49 }
  0 50       0  
    100          
6475 0         0 elsif ( $col == $col_max - 2 ) { $params{x_offset} = 49 }
6476 1         2 elsif ( $col == $col_max - 1 ) { $params{x_offset} = 49 }
6477 4152         6034 else { $params{x_offset} = 15 }
6478             }
6479              
6480              
6481             # Scale the size of the comment box if required.
6482 4153 50       7913 if ( $params{x_scale} ) {
6483 4153         7392 $params{width} = $params{width} * $params{x_scale};
6484             }
6485              
6486 4153 50       7202 if ( $params{y_scale} ) {
6487 4153         6587 $params{height} = $params{height} * $params{y_scale};
6488             }
6489              
6490             # Round the dimensions to the nearest pixel.
6491 4153         9072 $params{width} = int( 0.5 + $params{width} );
6492 4153         7025 $params{height} = int( 0.5 + $params{height} );
6493              
6494             # Calculate the positions of comment object.
6495             my @vertices = $self->_position_object_pixels(
6496             $params{start_col}, $params{start_row}, $params{x_offset},
6497             $params{y_offset}, $params{width}, $params{height}
6498 4153         11673 );
6499              
6500             # Add the width and height for VML.
6501 4153         8924 push @vertices, ( $params{width}, $params{height} );
6502              
6503             return (
6504             $row,
6505             $col,
6506             $string,
6507              
6508             $params{author},
6509             $params{visible},
6510             $params{color},
6511             $params{font},
6512             $params{font_size},
6513             $params{font_family},
6514              
6515 4153         39245 [@vertices],
6516             );
6517             }
6518              
6519              
6520             ###############################################################################
6521             #
6522             # _button_params()
6523             #
6524             # This method handles the parameters passed to insert_button() as well as
6525             # calculating the button object position and vertices.
6526             #
6527             sub _button_params {
6528              
6529 28     28   69 my $self = shift;
6530 28         61 my $row = shift;
6531 28         54 my $col = shift;
6532 28         51 my $params = shift;
6533 28         111 my $button = { _row => $row, _col => $col };
6534              
6535 28         61 my $button_number = 1 + @{ $self->{_buttons_array} };
  28         116  
6536              
6537             # Set the button caption.
6538 28         94 my $caption = $params->{caption};
6539              
6540             # Set a default caption if none was specified by user.
6541 28 100       104 if ( !defined $caption ) {
6542 24         63 $caption = 'Button ' . $button_number;
6543             }
6544              
6545 28         113 $button->{_font}->{_caption} = $caption;
6546              
6547              
6548             # Set the macro name.
6549 28 100       94 if ( $params->{macro} ) {
6550 5         25 $button->{_macro} = '[0]!' . $params->{macro};
6551             }
6552             else {
6553 23         89 $button->{_macro} = '[0]!Button' . $button_number . '_Click';
6554             }
6555              
6556              
6557             # Ensure that a width and height have been set.
6558 28         92 my $default_width = $self->{_default_col_pixels};
6559 28         58 my $default_height = $self->{_default_row_pixels};
6560 28 100       126 $params->{width} = $default_width if !$params->{width};
6561 28 100       116 $params->{height} = $default_height if !$params->{height};
6562              
6563             # Set the x/y offsets.
6564 28 100       94 $params->{x_offset} = 0 if !$params->{x_offset};
6565 28 100       97 $params->{y_offset} = 0 if !$params->{y_offset};
6566              
6567             # Scale the size of the button box if required.
6568 28 100       90 if ( $params->{x_scale} ) {
6569 1         3 $params->{width} = $params->{width} * $params->{x_scale};
6570             }
6571              
6572 28 100       94 if ( $params->{y_scale} ) {
6573 1         4 $params->{height} = $params->{height} * $params->{y_scale};
6574             }
6575              
6576             # Round the dimensions to the nearest pixel.
6577 28         257 $params->{width} = int( 0.5 + $params->{width} );
6578 28         156 $params->{height} = int( 0.5 + $params->{height} );
6579              
6580 28         345 $params->{start_row} = $row;
6581 28         277 $params->{start_col} = $col;
6582              
6583             # Calculate the positions of button object.
6584             my @vertices = $self->_position_object_pixels(
6585             $params->{start_col}, $params->{start_row}, $params->{x_offset},
6586             $params->{y_offset}, $params->{width}, $params->{height}
6587 28         158 );
6588              
6589             # Add the width and height for VML.
6590 28         103 push @vertices, ( $params->{width}, $params->{height} );
6591              
6592 28         67 $button->{_vertices} = \@vertices;
6593              
6594 28         79 return $button;
6595             }
6596              
6597              
6598             ###############################################################################
6599             #
6600             # Deprecated methods for backwards compatibility.
6601             #
6602             ###############################################################################
6603              
6604              
6605             # This method was mainly only required for Excel 5.
6606       0 0   sub write_url_range { }
6607              
6608             # Deprecated UTF-16 method required for the Excel 5 format.
6609             sub write_utf16be_string {
6610              
6611 1     1 0 9 my $self = shift;
6612              
6613             # Convert A1 notation if present.
6614 1 50       9 @_ = $self->_substitute_cellref( @_ ) if $_[0] =~ /^\D/;
6615              
6616             # Check the number of args.
6617 1 50       5 return -1 if @_ < 3;
6618              
6619             # Convert UTF16 string to UTF8.
6620 1         7 require Encode;
6621 1         6 my $utf8_string = Encode::decode( 'UTF-16BE', $_[2] );
6622              
6623 1         2847 return $self->write_string( $_[0], $_[1], $utf8_string, $_[3] );
6624             }
6625              
6626             # Deprecated UTF-16 method required for the Excel 5 format.
6627             sub write_utf16le_string {
6628              
6629 1     1 0 6 my $self = shift;
6630              
6631             # Convert A1 notation if present.
6632 1 50       9 @_ = $self->_substitute_cellref( @_ ) if $_[0] =~ /^\D/;
6633              
6634             # Check the number of args.
6635 1 50       4 return -1 if @_ < 3;
6636              
6637             # Convert UTF16 string to UTF8.
6638 1         5 require Encode;
6639 1         5 my $utf8_string = Encode::decode( 'UTF-16LE', $_[2] );
6640              
6641 1         50 return $self->write_string( $_[0], $_[1], $utf8_string, $_[3] );
6642             }
6643              
6644             # No longer required. Was used to avoid slow formula parsing.
6645             sub store_formula {
6646              
6647 5     5 0 2495 my $self = shift;
6648 5         9 my $string = shift;
6649              
6650 5         52 my @tokens = split /(\$?[A-I]?[A-Z]\$?\d+)/, $string;
6651              
6652 5         30 return \@tokens;
6653             }
6654              
6655             # No longer required. Was used to avoid slow formula parsing.
6656             sub repeat_formula {
6657              
6658 5     5 0 32 my $self = shift;
6659              
6660             # Convert A1 notation if present.
6661 5 50       19 @_ = $self->_substitute_cellref( @_ ) if $_[0] =~ /^\D/;
6662              
6663 5 50       14 if ( @_ < 2 ) { return -1 } # Check the number of args
  0         0  
6664              
6665 5         9 my $row = shift; # Zero indexed row
6666 5         10 my $col = shift; # Zero indexed column
6667 5         8 my $formula_ref = shift; # Array ref with formula tokens
6668 5         7 my $format = shift; # XF format
6669 5         14 my @pairs = @_; # Pattern/replacement pairs
6670              
6671              
6672             # Enforce an even number of arguments in the pattern/replacement list.
6673 5 50       18 croak "Odd number of elements in pattern/replacement list" if @pairs % 2;
6674              
6675             # Check that $formula is an array ref.
6676 5 50       17 croak "Not a valid formula" if ref $formula_ref ne 'ARRAY';
6677              
6678 5         15 my @tokens = @$formula_ref;
6679              
6680             # Allow the user to specify the result of the formula by appending a
6681             # result => $value pair to the end of the arguments.
6682 5         11 my $value = undef;
6683 5 50 66     22 if ( @pairs && $pairs[-2] eq 'result' ) {
6684 0         0 $value = pop @pairs;
6685 0         0 pop @pairs;
6686             }
6687              
6688             # Make the substitutions.
6689 5         14 while ( @pairs ) {
6690 6         13 my $pattern = shift @pairs;
6691 6         9 my $replace = shift @pairs;
6692              
6693 6         12 foreach my $token ( @tokens ) {
6694 16 100       91 last if $token =~ s/$pattern/$replace/;
6695             }
6696             }
6697              
6698 5         15 my $formula = join '', @tokens;
6699              
6700 5         62 return $self->write_formula( $row, $col, $formula, $format, $value );
6701             }
6702              
6703              
6704             ###############################################################################
6705             #
6706             # XML writing methods.
6707             #
6708             ###############################################################################
6709              
6710              
6711             ###############################################################################
6712             #
6713             # _write_worksheet()
6714             #
6715             # Write the element. This is the root element of Worksheet.
6716             #
6717             sub _write_worksheet {
6718              
6719 994     994   2718 my $self = shift;
6720 994         2973 my $schema = 'http://schemas.openxmlformats.org/';
6721 994         4108 my $xmlns = $schema . 'spreadsheetml/2006/main';
6722 994         3131 my $xmlns_r = $schema . 'officeDocument/2006/relationships';
6723 994         3170 my $xmlns_mc = $schema . 'markup-compatibility/2006';
6724              
6725 994         4159 my @attributes = (
6726             'xmlns' => $xmlns,
6727             'xmlns:r' => $xmlns_r,
6728             );
6729              
6730 994 100       5108 if ( $self->{_excel_version} == 2010 ) {
6731 23         81 push @attributes, ( 'xmlns:mc' => $xmlns_mc );
6732              
6733 23         79 push @attributes,
6734             ( 'xmlns:x14ac' => 'http://schemas.microsoft.com/'
6735             . 'office/spreadsheetml/2009/9/ac' );
6736              
6737 23         70 push @attributes, ( 'mc:Ignorable' => 'x14ac' );
6738              
6739             }
6740              
6741 994         8530 $self->xml_start_tag( 'worksheet', @attributes );
6742             }
6743              
6744              
6745             ###############################################################################
6746             #
6747             # _write_sheet_pr()
6748             #
6749             # Write the element for Sheet level properties.
6750             #
6751             sub _write_sheet_pr {
6752              
6753 996     996   2356 my $self = shift;
6754 996         2567 my @attributes = ();
6755              
6756 996 100 100     17040 if ( !$self->{_fit_page}
      100        
      100        
      100        
6757             && !$self->{_filter_on}
6758             && !$self->{_tab_color}
6759             && !$self->{_outline_changed}
6760             && !$self->{_vba_codename} )
6761             {
6762 971         2664 return;
6763             }
6764              
6765              
6766 25         79 my $codename = $self->{_vba_codename};
6767 25 100       95 push @attributes, ( 'codeName' => $codename ) if $codename;
6768 25 100       118 push @attributes, ( 'filterMode' => 1 ) if $self->{_filter_on};
6769              
6770 25 100 100     243 if ( $self->{_fit_page}
      100        
6771             || $self->{_tab_color}
6772             || $self->{_outline_changed} )
6773             {
6774 11         53 $self->xml_start_tag( 'sheetPr', @attributes );
6775 11         62 $self->_write_tab_color();
6776 11         52 $self->_write_outline_pr();
6777 11         41 $self->_write_page_set_up_pr();
6778 11         77 $self->xml_end_tag( 'sheetPr' );
6779             }
6780             else {
6781 14         148 $self->xml_empty_tag( 'sheetPr', @attributes );
6782             }
6783             }
6784              
6785              
6786             ##############################################################################
6787             #
6788             # _write_page_set_up_pr()
6789             #
6790             # Write the element.
6791             #
6792             sub _write_page_set_up_pr {
6793              
6794 13     13   44 my $self = shift;
6795              
6796 13 100       43 return unless $self->{_fit_page};
6797              
6798 9         37 my @attributes = ( 'fitToPage' => 1 );
6799              
6800 9         96 $self->xml_empty_tag( 'pageSetUpPr', @attributes );
6801             }
6802              
6803              
6804             ###############################################################################
6805             #
6806             # _write_dimension()
6807             #
6808             # Write the element. This specifies the range of cells in the
6809             # worksheet. As a special case, empty spreadsheets use 'A1' as a range.
6810             #
6811             sub _write_dimension {
6812              
6813 1003     1003   2674 my $self = shift;
6814 1003         2371 my $ref;
6815              
6816 1003 100 100     12838 if ( !defined $self->{_dim_rowmin} && !defined $self->{_dim_colmin} ) {
    100 66        
    100 100        
6817              
6818             # If the min dims are undefined then no dimensions have been set
6819             # and we use the default 'A1'.
6820 248         675 $ref = 'A1';
6821             }
6822             elsif ( !defined $self->{_dim_rowmin} && defined $self->{_dim_colmin} ) {
6823              
6824             # If the row dims aren't set but the column dims are then they
6825             # have been changed via set_column().
6826              
6827 6 100       38 if ( $self->{_dim_colmin} == $self->{_dim_colmax} ) {
6828              
6829             # The dimensions are a single cell and not a range.
6830 3         16 $ref = xl_rowcol_to_cell( 0, $self->{_dim_colmin} );
6831             }
6832             else {
6833              
6834             # The dimensions are a cell range.
6835 3         26 my $cell_1 = xl_rowcol_to_cell( 0, $self->{_dim_colmin} );
6836 3         13 my $cell_2 = xl_rowcol_to_cell( 0, $self->{_dim_colmax} );
6837              
6838 3         10 $ref = $cell_1 . ':' . $cell_2;
6839             }
6840              
6841             }
6842             elsif ($self->{_dim_rowmin} == $self->{_dim_rowmax}
6843             && $self->{_dim_colmin} == $self->{_dim_colmax} )
6844             {
6845              
6846             # The dimensions are a single cell and not a range.
6847 134         757 $ref = xl_rowcol_to_cell( $self->{_dim_rowmin}, $self->{_dim_colmin} );
6848             }
6849             else {
6850              
6851             # The dimensions are a cell range.
6852             my $cell_1 =
6853 615         3989 xl_rowcol_to_cell( $self->{_dim_rowmin}, $self->{_dim_colmin} );
6854             my $cell_2 =
6855 615         3015 xl_rowcol_to_cell( $self->{_dim_rowmax}, $self->{_dim_colmax} );
6856              
6857 615         2681 $ref = $cell_1 . ':' . $cell_2;
6858             }
6859              
6860              
6861 1003         3685 my @attributes = ( 'ref' => $ref );
6862              
6863 1003         8058 $self->xml_empty_tag( 'dimension', @attributes );
6864             }
6865              
6866              
6867             ###############################################################################
6868             #
6869             # _write_sheet_views()
6870             #
6871             # Write the element.
6872             #
6873             sub _write_sheet_views {
6874              
6875 1066     1066   2728 my $self = shift;
6876              
6877 1066         2780 my @attributes = ();
6878              
6879 1066         4758 $self->xml_start_tag( 'sheetViews', @attributes );
6880 1066         4961 $self->_write_sheet_view();
6881 1066         7755 $self->xml_end_tag( 'sheetViews' );
6882             }
6883              
6884              
6885             ###############################################################################
6886             #
6887             # _write_sheet_view()
6888             #
6889             # Write the element.
6890             #
6891             # Sample structure:
6892             #
6893             # showGridLines="0"
6894             # showRowColHeaders="0"
6895             # showZeros="0"
6896             # rightToLeft="1"
6897             # tabSelected="1"
6898             # showRuler="0"
6899             # showOutlineSymbols="0"
6900             # view="pageLayout"
6901             # zoomScale="121"
6902             # zoomScaleNormal="121"
6903             # workbookViewId="0"
6904             # />
6905             #
6906             sub _write_sheet_view {
6907              
6908 1073     1073   2781 my $self = shift;
6909 1073         2919 my $gridlines = $self->{_screen_gridlines};
6910 1073         2852 my $show_zeros = $self->{_show_zeros};
6911 1073         2720 my $right_to_left = $self->{_right_to_left};
6912 1073         2852 my $tab_selected = $self->{_selected};
6913 1073         2713 my $view = $self->{_page_view};
6914 1073         2673 my $zoom = $self->{_zoom};
6915 1073         2749 my $row_col_headers = $self->{_hide_row_col_headers};
6916 1073         2532 my $workbook_view_id = 0;
6917 1073         3137 my @attributes = ();
6918              
6919             # Hide screen gridlines if required.
6920 1073 100       4530 if ( !$gridlines ) {
6921 3         25 push @attributes, ( 'showGridLines' => 0 );
6922             }
6923              
6924             # Hide the row/column headers.
6925 1073 100       4425 if ( $row_col_headers ) {
6926 1         4 push @attributes, ( 'showRowColHeaders' => 0 );
6927             }
6928              
6929             # Hide zeroes in cells.
6930 1073 100       4469 if ( !$show_zeros ) {
6931 1         3 push @attributes, ( 'showZeros' => 0 );
6932             }
6933              
6934             # Display worksheet right to left for Hebrew, Arabic and others.
6935 1073 100       3925 if ( $right_to_left ) {
6936 1         3 push @attributes, ( 'rightToLeft' => 1 );
6937             }
6938              
6939             # Show that the sheet tab is selected.
6940 1073 100       4345 if ( $tab_selected ) {
6941 924         3613 push @attributes, ( 'tabSelected' => 1 );
6942             }
6943              
6944              
6945             # Turn outlines off. Also required in the outlinePr element.
6946 1073 100       4736 if ( !$self->{_outline_on} ) {
6947 1         3 push @attributes, ( "showOutlineSymbols" => 0 );
6948             }
6949              
6950             # Set the page view/layout mode if required.
6951             # TODO. Add pageBreakPreview mode when requested.
6952 1073 100       4303 if ( $view ) {
6953 2         5 push @attributes, ( 'view' => 'pageLayout' );
6954             }
6955              
6956             # Set the zoom level.
6957 1073 100       4442 if ( $zoom != 100 ) {
6958 2 50       9 push @attributes, ( 'zoomScale' => $zoom ) unless $view;
6959             push @attributes, ( 'zoomScaleNormal' => $zoom )
6960 2 100       6 if $self->{_zoom_scale_normal};
6961             }
6962              
6963 1073         3442 push @attributes, ( 'workbookViewId' => $workbook_view_id );
6964              
6965 1073 100 100     2229 if ( @{ $self->{_panes} } || @{ $self->{_selections} } ) {
  1073         5599  
  1021         4901  
6966 69         246 $self->xml_start_tag( 'sheetView', @attributes );
6967 69         239 $self->_write_panes();
6968 69         257 $self->_write_selections();
6969 69         246 $self->xml_end_tag( 'sheetView' );
6970             }
6971             else {
6972 1004         5054 $self->xml_empty_tag( 'sheetView', @attributes );
6973             }
6974             }
6975              
6976              
6977             ###############################################################################
6978             #
6979             # _write_selections()
6980             #
6981             # Write the elements.
6982             #
6983             sub _write_selections {
6984              
6985 69     69   107 my $self = shift;
6986              
6987 69         106 for my $selection ( @{ $self->{_selections} } ) {
  69         176  
6988 105         240 $self->_write_selection( @$selection );
6989             }
6990             }
6991              
6992              
6993             ###############################################################################
6994             #
6995             # _write_selection()
6996             #
6997             # Write the element.
6998             #
6999             sub _write_selection {
7000              
7001 106     106   170 my $self = shift;
7002 106         186 my $pane = shift;
7003 106         175 my $active_cell = shift;
7004 106         159 my $sqref = shift;
7005 106         189 my @attributes = ();
7006              
7007 106 100       256 push @attributes, ( 'pane' => $pane ) if $pane;
7008 106 100       255 push @attributes, ( 'activeCell' => $active_cell ) if $active_cell;
7009 106 100       297 push @attributes, ( 'sqref' => $sqref ) if $sqref;
7010              
7011 106         286 $self->xml_empty_tag( 'selection', @attributes );
7012             }
7013              
7014              
7015             ###############################################################################
7016             #
7017             # _write_sheet_format_pr()
7018             #
7019             # Write the element.
7020             #
7021             sub _write_sheet_format_pr {
7022              
7023 994     994   2843 my $self = shift;
7024 994         2353 my $base_col_width = 10;
7025 994         2906 my $default_row_height = $self->{_default_row_height};
7026 994         2697 my $row_level = $self->{_outline_row_level};
7027 994         2547 my $col_level = $self->{_outline_col_level};
7028 994         2577 my $zero_height = $self->{_default_row_zeroed};
7029              
7030 994         3473 my @attributes = ( 'defaultRowHeight' => $default_row_height );
7031              
7032 994 100       4783 if ( $self->{_default_row_height} != $self->{_original_row_height} ) {
7033 4         21 push @attributes, ( 'customHeight' => 1 );
7034             }
7035              
7036 994 100       4145 if ( $self->{_default_row_zeroed} ) {
7037 3         11 push @attributes, ( 'zeroHeight' => 1 );
7038             }
7039              
7040 994 100       3764 push @attributes, ( 'outlineLevelRow' => $row_level ) if $row_level;
7041 994 100       3696 push @attributes, ( 'outlineLevelCol' => $col_level ) if $col_level;
7042              
7043 994 100       4318 if ( $self->{_excel_version} == 2010 ) {
7044 23         62 push @attributes, ( 'x14ac:dyDescent' => '0.25' );
7045             }
7046              
7047 994         4438 $self->xml_empty_tag( 'sheetFormatPr', @attributes );
7048             }
7049              
7050              
7051             ##############################################################################
7052             #
7053             # _write_cols()
7054             #
7055             # Write the element and
7056             #
7057             sub _write_cols {
7058              
7059 993     993   2544 my $self = shift;
7060              
7061             # Exit unless some column have been formatted.
7062 993 100       2123 return unless %{ $self->{_colinfo} };
  993         4570  
7063              
7064 100         543 $self->xml_start_tag( 'cols' );
7065              
7066 100         329 for my $col ( sort keys %{ $self->{_colinfo} } ) {
  100         761  
7067 186         384 $self->_write_col_info( @{ $self->{_colinfo}->{$col} } );
  186         800  
7068             }
7069              
7070 100         512 $self->xml_end_tag( 'cols' );
7071             }
7072              
7073              
7074             ##############################################################################
7075             #
7076             # _write_col_info()
7077             #
7078             # Write the
7079             #
7080             sub _write_col_info {
7081              
7082 192     192   597 my $self = shift;
7083 192   100     713 my $min = $_[0] || 0; # First formatted column.
7084 192   100     698 my $max = $_[1] || 0; # Last formatted column.
7085 192         425 my $width = $_[2]; # Col width in user units.
7086 192         334 my $format = $_[3]; # Format index.
7087 192   100     851 my $hidden = $_[4] || 0; # Hidden flag.
7088 192   100     775 my $level = $_[5] || 0; # Outline level.
7089 192   50     832 my $collapsed = $_[6] || 0; # Outline level.
7090 192         355 my $custom_width = 1;
7091 192         316 my $xf_index = 0;
7092              
7093             # Get the format index.
7094 192 100       603 if ( ref( $format ) ) {
7095 20         147 $xf_index = $format->get_xf_index();
7096             }
7097              
7098             # Set the Excel default col width.
7099 192 100       578 if ( !defined $width ) {
7100 26 100       89 if ( !$hidden ) {
7101 17         38 $width = 8.43;
7102 17         34 $custom_width = 0;
7103             }
7104             else {
7105 9         24 $width = 0;
7106             }
7107             }
7108             else {
7109              
7110             # Width is defined but same as default.
7111 166 100       595 if ( $width == 8.43 ) {
7112 1         2 $custom_width = 0;
7113             }
7114             }
7115              
7116              
7117             # Convert column width from user units to character width.
7118 192         361 my $max_digit_width = 7; # For Calabri 11.
7119 192         406 my $padding = 5;
7120              
7121 192 100       611 if ( $width > 0 ) {
7122 183 100       558 if ( $width < 1 ) {
7123 22         52 $width =
7124             int( ( int( $width * ($max_digit_width + $padding) + 0.5 ) ) /
7125             $max_digit_width *
7126             256 ) / 256;
7127             }
7128             else {
7129 161         817 $width =
7130             int( ( int( $width * $max_digit_width + 0.5 ) + $padding ) /
7131             $max_digit_width *
7132             256 ) / 256;
7133             }
7134             }
7135              
7136 192         732 my @attributes = (
7137             'min' => $min + 1,
7138             'max' => $max + 1,
7139             'width' => $width,
7140             );
7141              
7142 192 100       628 push @attributes, ( 'style' => $xf_index ) if $xf_index;
7143 192 100       617 push @attributes, ( 'hidden' => 1 ) if $hidden;
7144 192 100       676 push @attributes, ( 'customWidth' => 1 ) if $custom_width;
7145 192 100       514 push @attributes, ( 'outlineLevel' => $level ) if $level;
7146 192 50       515 push @attributes, ( 'collapsed' => 1 ) if $collapsed;
7147              
7148              
7149 192         757 $self->xml_empty_tag( 'col', @attributes );
7150             }
7151              
7152              
7153             ###############################################################################
7154             #
7155             # _write_sheet_data()
7156             #
7157             # Write the element.
7158             #
7159             sub _write_sheet_data {
7160              
7161 986     986   2519 my $self = shift;
7162              
7163 986 100       4343 if ( not defined $self->{_dim_rowmin} ) {
7164              
7165             # If the dimensions aren't defined then there is no data to write.
7166 254         941 $self->xml_empty_tag( 'sheetData' );
7167             }
7168             else {
7169 732         3549 $self->xml_start_tag( 'sheetData' );
7170 732         4134 $self->_write_rows();
7171 732         3012 $self->xml_end_tag( 'sheetData' );
7172              
7173             }
7174              
7175             }
7176              
7177              
7178             ###############################################################################
7179             #
7180             # _write_optimized_sheet_data()
7181             #
7182             # Write the element when the memory optimisation is on. In which
7183             # case we read the data stored in the temp file and rewrite it to the XML
7184             # sheet file.
7185             #
7186             sub _write_optimized_sheet_data {
7187              
7188 8     8   17 my $self = shift;
7189              
7190 8 50       38 if ( not defined $self->{_dim_rowmin} ) {
7191              
7192             # If the dimensions aren't defined then there is no data to write.
7193 0         0 $self->xml_empty_tag( 'sheetData' );
7194             }
7195             else {
7196              
7197 8         33 $self->xml_start_tag( 'sheetData' );
7198              
7199 8         62 my $xlsx_fh = $self->xml_get_fh();
7200 8         17 my $cell_fh = $self->{_cell_data_fh};
7201              
7202 8         23 my $buffer;
7203              
7204             # Rewind the temp file.
7205 8         356 seek $cell_fh, 0, 0;
7206              
7207 8         272 while ( read( $cell_fh, $buffer, 4_096 ) ) {
7208 12         49 local $\ = undef; # Protect print from -l on commandline.
7209 12         442 print $xlsx_fh $buffer;
7210             }
7211              
7212 8         39 $self->xml_end_tag( 'sheetData' );
7213             }
7214             }
7215              
7216              
7217             ###############################################################################
7218             #
7219             # _write_rows()
7220             #
7221             # Write out the worksheet data as a series of rows and cells.
7222             #
7223             sub _write_rows {
7224              
7225 732     732   2125 my $self = shift;
7226              
7227 732         3830 $self->_calculate_spans();
7228              
7229 732         2905 for my $row_num ( $self->{_dim_rowmin} .. $self->{_dim_rowmax} ) {
7230              
7231             # Skip row if it doesn't contain row formatting, cell data or a comment.
7232 1052822 100 100     3764416 if ( !$self->{_set_rows}->{$row_num}
      100        
7233             && !$self->{_table}->{$row_num}
7234             && !$self->{_comments}->{$row_num} )
7235             {
7236 1048846         1478430 next;
7237             }
7238              
7239 3976         9777 my $span_index = int( $row_num / 16 );
7240 3976         7454 my $span = $self->{_row_spans}->[$span_index];
7241              
7242             # Write the cells if the row contains data.
7243 3976 100       10062 if ( my $row_ref = $self->{_table}->{$row_num} ) {
    100          
7244              
7245 3610 100       8420 if ( !$self->{_set_rows}->{$row_num} ) {
7246 3292         8725 $self->_write_row( $row_num, $span );
7247             }
7248             else {
7249             $self->_write_row( $row_num, $span,
7250 318         533 @{ $self->{_set_rows}->{$row_num} } );
  318         897  
7251             }
7252              
7253              
7254 3610         9929 for my $col_num ( $self->{_dim_colmin} .. $self->{_dim_colmax} ) {
7255 26693 100       59136 if ( my $col_ref = $self->{_table}->{$row_num}->{$col_num} ) {
7256 9797         20256 $self->_write_cell( $row_num, $col_num, $col_ref );
7257             }
7258             }
7259              
7260 3610         9735 $self->xml_end_tag( 'row' );
7261             }
7262             elsif ( $self->{_comments}->{$row_num} ) {
7263              
7264             $self->_write_empty_row( $row_num, $span,
7265 304         460 @{ $self->{_set_rows}->{$row_num} } );
  304         1064  
7266             }
7267             else {
7268              
7269             # Row attributes only.
7270             $self->_write_empty_row( $row_num, $span,
7271 62         108 @{ $self->{_set_rows}->{$row_num} } );
  62         220  
7272             }
7273             }
7274             }
7275              
7276              
7277             ###############################################################################
7278             #
7279             # _write_single_row()
7280             #
7281             # Write out the worksheet data as a single row with cells. This method is
7282             # used when memory optimisation is on. A single row is written and the data
7283             # table is reset. That way only one row of data is kept in memory at any one
7284             # time. We don't write span data in the optimised case since it is optional.
7285             #
7286             sub _write_single_row {
7287              
7288 296     296   442 my $self = shift;
7289 296   100     639 my $current_row = shift || 0;
7290 296         490 my $row_num = $self->{_previous_row};
7291              
7292             # Set the new previous row as the current row.
7293 296         485 $self->{_previous_row} = $current_row;
7294              
7295             # Skip row if it doesn't contain row formatting, cell data or a comment.
7296 296 0 66     1019 if ( !$self->{_set_rows}->{$row_num}
      33        
7297             && !$self->{_table}->{$row_num}
7298             && !$self->{_comments}->{$row_num} )
7299             {
7300 0         0 return;
7301             }
7302              
7303             # Write the cells if the row contains data.
7304 296 50       626 if ( my $row_ref = $self->{_table}->{$row_num} ) {
7305              
7306 296 100       559 if ( !$self->{_set_rows}->{$row_num} ) {
7307 295         635 $self->_write_row( $row_num );
7308             }
7309             else {
7310             $self->_write_row( $row_num, undef,
7311 1         2 @{ $self->{_set_rows}->{$row_num} } );
  1         21  
7312             }
7313              
7314 296         728 for my $col_num ( $self->{_dim_colmin} .. $self->{_dim_colmax} ) {
7315 325 100       894 if ( my $col_ref = $self->{_table}->{$row_num}->{$col_num} ) {
7316 305         624 $self->_write_cell( $row_num, $col_num, $col_ref );
7317             }
7318             }
7319              
7320 296         763 $self->xml_end_tag( 'row' );
7321             }
7322             else {
7323              
7324             # Row attributes or comments only.
7325             $self->_write_empty_row( $row_num, undef,
7326 0         0 @{ $self->{_set_rows}->{$row_num} } );
  0         0  
7327             }
7328              
7329             # Reset table.
7330 296         936 $self->{_table} = {};
7331              
7332             }
7333              
7334              
7335             ###############################################################################
7336             #
7337             # _calculate_spans()
7338             #
7339             # Calculate the "spans" attribute of the tag. This is an XLSX
7340             # optimisation and isn't strictly required. However, it makes comparing
7341             # files easier.
7342             #
7343             # The span is the same for each block of 16 rows.
7344             #
7345             sub _calculate_spans {
7346              
7347 750     750   1986 my $self = shift;
7348              
7349 750         3211 my @spans;
7350             my $span_min;
7351 750         0 my $span_max;
7352              
7353 750         3397 for my $row_num ( $self->{_dim_rowmin} .. $self->{_dim_rowmax} ) {
7354              
7355             # Calculate spans for cell data.
7356 1053128 100       1958938 if ( my $row_ref = $self->{_table}->{$row_num} ) {
7357              
7358 3916         8379 for my $col_num ( $self->{_dim_colmin} .. $self->{_dim_colmax} ) {
7359 31895 100       63969 if ( my $col_ref = $self->{_table}->{$row_num}->{$col_num} ) {
7360              
7361 10103 100       17528 if ( !defined $span_min ) {
7362 790         2009 $span_min = $col_num;
7363 790         2450 $span_max = $col_num;
7364             }
7365             else {
7366 9313 100       16672 $span_min = $col_num if $col_num < $span_min;
7367 9313 100       19718 $span_max = $col_num if $col_num > $span_max;
7368             }
7369             }
7370             }
7371             }
7372              
7373             # Calculate spans for comments.
7374 1053128 100       1791128 if ( defined $self->{_comments}->{$row_num} ) {
7375              
7376 310         632 for my $col_num ( $self->{_dim_colmin} .. $self->{_dim_colmax} ) {
7377 36973 100       69377 if ( defined $self->{_comments}->{$row_num}->{$col_num} ) {
7378              
7379 4153 100       6353 if ( !defined $span_min ) {
7380 33         90 $span_min = $col_num;
7381 33         81 $span_max = $col_num;
7382             }
7383             else {
7384 4120 50       6834 $span_min = $col_num if $col_num < $span_min;
7385 4120 100       7614 $span_max = $col_num if $col_num > $span_max;
7386             }
7387             }
7388             }
7389             }
7390              
7391 1053128 100 100     2867715 if ( ( ( $row_num + 1 ) % 16 == 0 )
7392             || $row_num == $self->{_dim_rowmax} )
7393             {
7394 66374         105505 my $span_index = int( $row_num / 16 );
7395              
7396 66374 100       124282 if ( defined $span_min ) {
7397 823         1969 $span_min++;
7398 823         1819 $span_max++;
7399 823         4027 $spans[$span_index] = "$span_min:$span_max";
7400 823         2608 $span_min = undef;
7401             }
7402             }
7403             }
7404              
7405 750         2997 $self->{_row_spans} = \@spans;
7406             }
7407              
7408              
7409             ###############################################################################
7410             #
7411             # _write_row()
7412             #
7413             # Write the element.
7414             #
7415             sub _write_row {
7416              
7417 4280     4280   7011 my $self = shift;
7418 4280         6944 my $r = shift;
7419 4280         6902 my $spans = shift;
7420 4280         6477 my $height = shift;
7421 4280         6585 my $format = shift;
7422 4280   100     12701 my $hidden = shift || 0;
7423 4280   100     12354 my $level = shift || 0;
7424 4280   100     11933 my $collapsed = shift || 0;
7425 4280   100     11383 my $empty_row = shift || 0;
7426 4280         6487 my $xf_index = 0;
7427              
7428 4280 100       10516 $height = $self->{_default_row_height} if !defined $height;
7429              
7430 4280         9747 my @attributes = ( 'r' => $r + 1 );
7431              
7432             # Get the format index.
7433 4280 100       9517 if ( ref( $format ) ) {
7434 11         48 $xf_index = $format->get_xf_index();
7435             }
7436              
7437 4280 100       11669 push @attributes, ( 'spans' => $spans ) if defined $spans;
7438 4280 100       8742 push @attributes, ( 's' => $xf_index ) if $xf_index;
7439 4280 100       8800 push @attributes, ( 'customFormat' => 1 ) if $format;
7440              
7441 4280 100       9932 if ( $height != $self->{_original_row_height} ) {
7442 67         169 push @attributes, ( 'ht' => $height );
7443             }
7444              
7445 4280 100       8859 push @attributes, ( 'hidden' => 1 ) if $hidden;
7446              
7447 4280 100       9745 if ( $height != $self->{_original_row_height} ) {
7448 67         153 push @attributes, ( 'customHeight' => 1 );
7449             }
7450              
7451 4280 100       8514 push @attributes, ( 'outlineLevel' => $level ) if $level;
7452 4280 100       8514 push @attributes, ( 'collapsed' => 1 ) if $collapsed;
7453              
7454 4280 100       9887 if ( $self->{_excel_version} == 2010 ) {
7455 60         110 push @attributes, ( 'x14ac:dyDescent' => '0.25' );
7456             }
7457              
7458 4280 100       9018 if ( $empty_row ) {
7459 367         1205 $self->xml_empty_tag_unencoded( 'row', @attributes );
7460             }
7461             else {
7462 3913         13776 $self->xml_start_tag_unencoded( 'row', @attributes );
7463             }
7464             }
7465              
7466              
7467             ###############################################################################
7468             #
7469             # _write_empty_row()
7470             #
7471             # Write and empty element, i.e., attributes only, no cell data.
7472             #
7473             sub _write_empty_row {
7474              
7475 367     367   631 my $self = shift;
7476              
7477             # Set the $empty_row parameter.
7478 367         662 $_[7] = 1;
7479              
7480 367         905 $self->_write_row( @_ );
7481             }
7482              
7483              
7484             ###############################################################################
7485             #
7486             # _write_cell()
7487             #
7488             # Write the element. This is the innermost loop so efficiency is
7489             # important where possible. The basic methodology is that the data of every
7490             # cell type is passed in as follows:
7491             #
7492             # [ $row, $col, $aref]
7493             #
7494             # The aref, called $cell below, contains the following structure in all types:
7495             #
7496             # [ $type, $token, $xf, @args ]
7497             #
7498             # Where $type: represents the cell type, such as string, number, formula, etc.
7499             # $token: is the actual data for the string, number, formula, etc.
7500             # $xf: is the XF format object.
7501             # @args: additional args relevant to the specific data type.
7502             #
7503             sub _write_cell {
7504              
7505 10107     10107   15097 my $self = shift;
7506 10107         15170 my $row = shift;
7507 10107         14615 my $col = shift;
7508 10107         13669 my $cell = shift;
7509 10107         16717 my $type = $cell->[0];
7510 10107         14264 my $token = $cell->[1];
7511 10107         14305 my $xf = $cell->[2];
7512 10107         13808 my $xf_index = 0;
7513              
7514 10107         33264 my %error_codes = (
7515             '#DIV/0!' => 1,
7516             '#N/A' => 1,
7517             '#NAME?' => 1,
7518             '#NULL!' => 1,
7519             '#NUM!' => 1,
7520             '#REF!' => 1,
7521             '#VALUE!' => 1,
7522             );
7523              
7524 10107         19625 my %boolean = ( 'TRUE' => 1, 'FALSE' => 0 );
7525              
7526             # Get the format index.
7527 10107 100       19573 if ( ref( $xf ) ) {
7528 410         1335 $xf_index = $xf->get_xf_index();
7529             }
7530              
7531 10107         20004 my $range = _xl_rowcol_to_cell( $row, $col );
7532 10107         21238 my @attributes = ( 'r' => $range );
7533              
7534             # Add the cell format index.
7535 10107 100 66     36908 if ( $xf_index ) {
    100          
    100          
7536 410         954 push @attributes, ( 's' => $xf_index );
7537             }
7538             elsif ( $self->{_set_rows}->{$row} && $self->{_set_rows}->{$row}->[1] ) {
7539 11         25 my $row_xf = $self->{_set_rows}->{$row}->[1];
7540 11         30 push @attributes, ( 's' => $row_xf->get_xf_index() );
7541             }
7542             elsif ( $self->{_col_formats}->{$col} ) {
7543 17         35 my $col_xf = $self->{_col_formats}->{$col};
7544 17         65 push @attributes, ( 's' => $col_xf->get_xf_index() );
7545             }
7546              
7547              
7548             # Write the various cell types.
7549 10107 100       21198 if ( $type eq 'n' ) {
    100          
    100          
    100          
    100          
    50          
7550              
7551             # Write a number.
7552 7025         18676 $self->xml_number_element( $token, @attributes );
7553             }
7554             elsif ( $type eq 's' ) {
7555              
7556             # Write a string.
7557 2940 100       5865 if ( $self->{_optimization} == 0 ) {
7558 2642         7665 $self->xml_string_element( $token, @attributes );
7559             }
7560             else {
7561              
7562 298         430 my $string = $token;
7563              
7564             # Escape control characters. See SharedString.pm for details.
7565 298         581 $string =~ s/(_x[0-9a-fA-F]{4}_)/_x005F$1/g;
7566 298         620 $string =~ s/([\x00-\x08\x0B-\x1F])/sprintf "_x%04X_", ord($1)/eg;
  30         127  
7567              
7568             # Write any rich strings without further tags.
7569 298 100 66     731 if ( $string =~ m{^} && $string =~ m{$} ) {
7570              
7571 8         41 $self->xml_rich_inline_string( $string, @attributes );
7572             }
7573             else {
7574              
7575             # Add attribute to preserve leading or trailing whitespace.
7576 290         417 my $preserve = 0;
7577 290 100 66     1233 if ( $string =~ /^\s/ || $string =~ /\s$/ ) {
7578 3         6 $preserve = 1;
7579             }
7580              
7581 290         905 $self->xml_inline_string( $string, $preserve, @attributes );
7582             }
7583             }
7584             }
7585             elsif ( $type eq 'f' ) {
7586              
7587             # Write a formula.
7588 75   100     252 my $value = $cell->[3] || 0;
7589              
7590             # Check if the formula value is a string.
7591 75 100 100     461 if ( $value
7592             && $value !~ /^([+-]?)(?=\d|\.\d)\d*(\.\d*)?([Ee]([+-]?\d+))?$/ )
7593             {
7594 17 100       50 if ( exists $boolean{$value} ) {
    100          
7595 2         5 push @attributes, ( 't' => 'b' );
7596 2         4 $value = $boolean{$value};
7597             }
7598             elsif ( exists $error_codes{$value} ) {
7599 8         16 push @attributes, ( 't' => 'e' );
7600             }
7601             else {
7602 7         26 push @attributes, ( 't' => 'str' );
7603 7         26 $value = Excel::Writer::XLSX::Package::XMLwriter::_escape_data(
7604             $value );
7605             }
7606             }
7607              
7608 75         390 $self->xml_formula_element( $token, $value, @attributes );
7609              
7610             }
7611             elsif ( $type eq 'a' ) {
7612              
7613             # Write an array formula.
7614 8         41 $self->xml_start_tag( 'c', @attributes );
7615 8         53 $self->_write_cell_array_formula( $token, $cell->[3] );
7616 8         38 $self->_write_cell_value( $cell->[4] );
7617 8         41 $self->xml_end_tag( 'c' );
7618             }
7619             elsif ( $type eq 'l' ) {
7620              
7621             # Write a boolean value.
7622 4         9 push @attributes, ( 't' => 'b' );
7623              
7624 4         17 $self->xml_start_tag( 'c', @attributes );
7625 4         15 $self->_write_cell_value( $cell->[1] );
7626 4         12 $self->xml_end_tag( 'c' );
7627             }
7628             elsif ( $type eq 'b' ) {
7629              
7630             # Write a empty cell.
7631 55         177 $self->xml_empty_tag( 'c', @attributes );
7632             }
7633             }
7634              
7635              
7636             ###############################################################################
7637             #
7638             # _write_cell_value()
7639             #
7640             # Write the cell value element.
7641             #
7642             sub _write_cell_value {
7643              
7644 13     13   40 my $self = shift;
7645 13 50       45 my $value = defined $_[0] ? $_[0] : '';
7646              
7647 13         64 $self->xml_data_element( 'v', $value );
7648             }
7649              
7650              
7651             ###############################################################################
7652             #
7653             # _write_cell_formula()
7654             #
7655             # Write the cell formula element.
7656             #
7657             sub _write_cell_formula {
7658              
7659 0     0   0 my $self = shift;
7660 0 0       0 my $formula = defined $_[0] ? $_[0] : '';
7661              
7662 0         0 $self->xml_data_element( 'f', $formula );
7663             }
7664              
7665              
7666             ###############################################################################
7667             #
7668             # _write_cell_array_formula()
7669             #
7670             # Write the cell array formula element.
7671             #
7672             sub _write_cell_array_formula {
7673              
7674 8     8   21 my $self = shift;
7675 8         22 my $formula = shift;
7676 8         29 my $range = shift;
7677              
7678 8         28 my @attributes = ( 't' => 'array', 'ref' => $range );
7679              
7680 8         59 $self->xml_data_element( 'f', $formula, @attributes );
7681             }
7682              
7683              
7684             ##############################################################################
7685             #
7686             # _write_sheet_calc_pr()
7687             #
7688             # Write the element for the worksheet calculation properties.
7689             #
7690             sub _write_sheet_calc_pr {
7691              
7692 1     1   9 my $self = shift;
7693 1         3 my $full_calc_on_load = 1;
7694              
7695 1         4 my @attributes = ( 'fullCalcOnLoad' => $full_calc_on_load );
7696              
7697 1         9 $self->xml_empty_tag( 'sheetCalcPr', @attributes );
7698             }
7699              
7700              
7701             ###############################################################################
7702             #
7703             # _write_phonetic_pr()
7704             #
7705             # Write the element.
7706             #
7707             sub _write_phonetic_pr {
7708              
7709 9     9   28 my $self = shift;
7710 9         24 my $font_id = 0;
7711 9         23 my $type = 'noConversion';
7712              
7713 9         35 my @attributes = (
7714             'fontId' => $font_id,
7715             'type' => $type,
7716             );
7717              
7718 9         37 $self->xml_empty_tag( 'phoneticPr', @attributes );
7719             }
7720              
7721              
7722             ###############################################################################
7723             #
7724             # _write_page_margins()
7725             #
7726             # Write the element.
7727             #
7728             sub _write_page_margins {
7729              
7730 1025     1025   2505 my $self = shift;
7731              
7732             my @attributes = (
7733             'left' => $self->{_margin_left},
7734             'right' => $self->{_margin_right},
7735             'top' => $self->{_margin_top},
7736             'bottom' => $self->{_margin_bottom},
7737             'header' => $self->{_margin_header},
7738             'footer' => $self->{_margin_footer},
7739 1025         6478 );
7740              
7741 1025         5335 $self->xml_empty_tag( 'pageMargins', @attributes );
7742             }
7743              
7744              
7745             ###############################################################################
7746             #
7747             # _write_page_setup()
7748             #
7749             # Write the element.
7750             #
7751             # The following is an example taken from Excel.
7752             #
7753             #
7754             # paperSize="9"
7755             # scale="110"
7756             # fitToWidth="2"
7757             # fitToHeight="2"
7758             # pageOrder="overThenDown"
7759             # orientation="portrait"
7760             # blackAndWhite="1"
7761             # draft="1"
7762             # horizontalDpi="200"
7763             # verticalDpi="200"
7764             # r:id="rId1"
7765             # />
7766             #
7767             sub _write_page_setup {
7768              
7769 1019     1019   2669 my $self = shift;
7770 1019         2602 my @attributes = ();
7771              
7772 1019 100       4220 return unless $self->{_page_setup_changed};
7773              
7774             # Set paper size.
7775 23 100       79 if ( $self->{_paper_size} ) {
7776 19         70 push @attributes, ( 'paperSize' => $self->{_paper_size} );
7777             }
7778              
7779             # Set the print_scale
7780 23 100       139 if ( $self->{_print_scale} != 100 ) {
7781 3         27 push @attributes, ( 'scale' => $self->{_print_scale} );
7782             }
7783              
7784             # Set the "Fit to page" properties.
7785 23 100 100     131 if ( $self->{_fit_page} && $self->{_fit_width} != 1 ) {
7786 3         9 push @attributes, ( 'fitToWidth' => $self->{_fit_width} );
7787             }
7788              
7789 23 100 100     122 if ( $self->{_fit_page} && $self->{_fit_height} != 1 ) {
7790 4         14 push @attributes, ( 'fitToHeight' => $self->{_fit_height} );
7791             }
7792              
7793             # Set the page print direction.
7794 23 100       99 if ( $self->{_page_order} ) {
7795 2         5 push @attributes, ( 'pageOrder' => "overThenDown" );
7796             }
7797              
7798             # Set start page.
7799 23 100       132 if ( $self->{_page_start} > 1 ) {
7800 2         8 push @attributes, ( 'firstPageNumber' => $self->{_page_start} );
7801             }
7802              
7803             # Set page orientation.
7804 23 100       109 if ( $self->{_orientation} == 0 ) {
7805 2         7 push @attributes, ( 'orientation' => 'landscape' );
7806             }
7807             else {
7808 21         84 push @attributes, ( 'orientation' => 'portrait' );
7809             }
7810              
7811             # Set print in black and white option.
7812 23 100       113 if ( $self->{_black_white} ) {
7813 1         3 push @attributes, ( 'blackAndWhite' => 1 );
7814             }
7815              
7816             # Set start page.
7817 23 100       141 if ( $self->{_page_start} != 0 ) {
7818 3         9 push @attributes, ( 'useFirstPageNumber' => 1 );
7819             }
7820              
7821             # Set the DPI. Mainly only for testing.
7822 23 50       73 if ( $self->{_horizontal_dpi} ) {
7823 0         0 push @attributes, ( 'horizontalDpi' => $self->{_horizontal_dpi} );
7824             }
7825              
7826 23 100       77 if ( $self->{_vertical_dpi} ) {
7827 5         19 push @attributes, ( 'verticalDpi' => $self->{_vertical_dpi} );
7828             }
7829              
7830              
7831 23         99 $self->xml_empty_tag( 'pageSetup', @attributes );
7832             }
7833              
7834              
7835             ##############################################################################
7836             #
7837             # _write_merge_cells()
7838             #
7839             # Write the element.
7840             #
7841             sub _write_merge_cells {
7842              
7843 996     996   2808 my $self = shift;
7844 996         2639 my $merged_cells = $self->{_merge};
7845 996         2739 my $count = @$merged_cells;
7846              
7847 996 100       4066 return unless $count;
7848              
7849 14         47 my @attributes = ( 'count' => $count );
7850              
7851 14         72 $self->xml_start_tag( 'mergeCells', @attributes );
7852              
7853 14         52 for my $merged_range ( @$merged_cells ) {
7854              
7855             # Write the mergeCell element.
7856 27         94 $self->_write_merge_cell( $merged_range );
7857             }
7858              
7859 14         88 $self->xml_end_tag( 'mergeCells' );
7860             }
7861              
7862              
7863             ##############################################################################
7864             #
7865             # _write_merge_cell()
7866             #
7867             # Write the element.
7868             #
7869             sub _write_merge_cell {
7870              
7871 28     28   64 my $self = shift;
7872 28         60 my $merged_range = shift;
7873 28         111 my ( $row_min, $col_min, $row_max, $col_max ) = @$merged_range;
7874              
7875              
7876             # Convert the merge dimensions to a cell range.
7877 28         95 my $cell_1 = xl_rowcol_to_cell( $row_min, $col_min );
7878 28         90 my $cell_2 = xl_rowcol_to_cell( $row_max, $col_max );
7879 28         75 my $ref = $cell_1 . ':' . $cell_2;
7880              
7881 28         90 my @attributes = ( 'ref' => $ref );
7882              
7883 28         95 $self->xml_empty_tag( 'mergeCell', @attributes );
7884             }
7885              
7886              
7887             ##############################################################################
7888             #
7889             # _write_print_options()
7890             #
7891             # Write the element.
7892             #
7893             sub _write_print_options {
7894              
7895 1022     1022   2502 my $self = shift;
7896 1022         2660 my @attributes = ();
7897              
7898 1022 100       4430 return unless $self->{_print_options_changed};
7899              
7900             # Set horizontal centering.
7901 10 100       28 if ( $self->{_hcenter} ) {
7902 4         11 push @attributes, ( 'horizontalCentered' => 1 );
7903             }
7904              
7905             # Set vertical centering.
7906 10 100       29 if ( $self->{_vcenter} ) {
7907 4         9 push @attributes, ( 'verticalCentered' => 1 );
7908             }
7909              
7910             # Enable row and column headers.
7911 10 100       26 if ( $self->{_print_headers} ) {
7912 2         8 push @attributes, ( 'headings' => 1 );
7913             }
7914              
7915             # Set printed gridlines.
7916 10 100       26 if ( $self->{_print_gridlines} ) {
7917 4         10 push @attributes, ( 'gridLines' => 1 );
7918             }
7919              
7920              
7921 10         45 $self->xml_empty_tag( 'printOptions', @attributes );
7922             }
7923              
7924              
7925             ##############################################################################
7926             #
7927             # _write_header_footer()
7928             #
7929             # Write the element.
7930             #
7931             sub _write_header_footer {
7932              
7933 1017     1017   2426 my $self = shift;
7934 1017         2604 my @attributes = ();
7935              
7936 1017 100       4197 if ( !$self->{_header_footer_scales} ) {
7937 2         8 push @attributes, ( 'scaleWithDoc' => 0 );
7938             }
7939              
7940 1017 100       4082 if ( !$self->{_header_footer_aligns} ) {
7941 10         32 push @attributes, ( 'alignWithMargins' => 0 );
7942             }
7943              
7944 1017 100       6357 if ( $self->{_header_footer_changed} ) {
    100          
7945 32         154 $self->xml_start_tag( 'headerFooter', @attributes );
7946 32 100       259 $self->_write_odd_header() if $self->{_header};
7947 32 100       163 $self->_write_odd_footer() if $self->{_footer};
7948 32         137 $self->xml_end_tag( 'headerFooter' );
7949             }
7950             elsif ( $self->{_excel2003_style} ) {
7951 7         30 $self->xml_empty_tag( 'headerFooter', @attributes );
7952             }
7953             }
7954              
7955              
7956             ##############################################################################
7957             #
7958             # _write_odd_header()
7959             #
7960             # Write the element.
7961             #
7962             sub _write_odd_header {
7963              
7964 30     30   87 my $self = shift;
7965 30         78 my $data = $self->{_header};
7966              
7967 30         207 $self->xml_data_element( 'oddHeader', $data );
7968             }
7969              
7970              
7971             ##############################################################################
7972             #
7973             # _write_odd_footer()
7974             #
7975             # Write the element.
7976             #
7977             sub _write_odd_footer {
7978              
7979 14     14   33 my $self = shift;
7980 14         32 my $data = $self->{_footer};
7981              
7982 14         63 $self->xml_data_element( 'oddFooter', $data );
7983             }
7984              
7985              
7986             ##############################################################################
7987             #
7988             # _write_row_breaks()
7989             #
7990             # Write the element.
7991             #
7992             sub _write_row_breaks {
7993              
7994 995     995   2513 my $self = shift;
7995              
7996 995         2615 my @page_breaks = $self->_sort_pagebreaks( @{ $self->{_hbreaks} } );
  995         5078  
7997 995         2723 my $count = scalar @page_breaks;
7998              
7999 995 100       3845 return unless @page_breaks;
8000              
8001 6         27 my @attributes = (
8002             'count' => $count,
8003             'manualBreakCount' => $count,
8004             );
8005              
8006 6         34 $self->xml_start_tag( 'rowBreaks', @attributes );
8007              
8008 6         24 for my $row_num ( @page_breaks ) {
8009 1035         1964 $self->_write_brk( $row_num, 16383 );
8010             }
8011              
8012 6         30 $self->xml_end_tag( 'rowBreaks' );
8013             }
8014              
8015              
8016             ##############################################################################
8017             #
8018             # _write_col_breaks()
8019             #
8020             # Write the element.
8021             #
8022             sub _write_col_breaks {
8023              
8024 995     995   2507 my $self = shift;
8025              
8026 995         2745 my @page_breaks = $self->_sort_pagebreaks( @{ $self->{_vbreaks} } );
  995         4038  
8027 995         2694 my $count = scalar @page_breaks;
8028              
8029 995 100       3797 return unless @page_breaks;
8030              
8031 5         16 my @attributes = (
8032             'count' => $count,
8033             'manualBreakCount' => $count,
8034             );
8035              
8036 5         28 $self->xml_start_tag( 'colBreaks', @attributes );
8037              
8038 5         14 for my $col_num ( @page_breaks ) {
8039 11         29 $self->_write_brk( $col_num, 1048575 );
8040             }
8041              
8042 5         19 $self->xml_end_tag( 'colBreaks' );
8043             }
8044              
8045              
8046             ##############################################################################
8047             #
8048             # _write_brk()
8049             #
8050             # Write the element.
8051             #
8052             sub _write_brk {
8053              
8054 1047     1047   1537 my $self = shift;
8055 1047         1521 my $id = shift;
8056 1047         1354 my $max = shift;
8057 1047         1434 my $man = 1;
8058              
8059 1047         2096 my @attributes = (
8060             'id' => $id,
8061             'max' => $max,
8062             'man' => $man,
8063             );
8064              
8065 1047         2193 $self->xml_empty_tag( 'brk', @attributes );
8066             }
8067              
8068              
8069             ##############################################################################
8070             #
8071             # _write_auto_filter()
8072             #
8073             # Write the element.
8074             #
8075             sub _write_auto_filter {
8076              
8077 1014     1014   2837 my $self = shift;
8078 1014         3060 my $ref = $self->{_autofilter_ref};
8079              
8080 1014 100       3960 return unless $ref;
8081              
8082 32         98 my @attributes = ( 'ref' => $ref );
8083              
8084 32 100       86 if ( $self->{_filter_on} ) {
8085              
8086             # Autofilter defined active filters.
8087 29         122 $self->xml_start_tag( 'autoFilter', @attributes );
8088              
8089 29         124 $self->_write_autofilters();
8090              
8091 29         153 $self->xml_end_tag( 'autoFilter' );
8092              
8093             }
8094             else {
8095              
8096             # Autofilter defined without active filters.
8097 3         18 $self->xml_empty_tag( 'autoFilter', @attributes );
8098             }
8099              
8100             }
8101              
8102              
8103             ###############################################################################
8104             #
8105             # _write_autofilters()
8106             #
8107             # Function to iterate through the columns that form part of an autofilter
8108             # range and write the appropriate filters.
8109             #
8110             sub _write_autofilters {
8111              
8112 29     29   57 my $self = shift;
8113              
8114 29         57 my ( $col1, $col2 ) = @{ $self->{_filter_range} };
  29         95  
8115              
8116 29         117 for my $col ( $col1 .. $col2 ) {
8117              
8118             # Skip if column doesn't have an active filter.
8119 116 100       332 next unless $self->{_filter_cols}->{$col};
8120              
8121             # Retrieve the filter tokens and write the autofilter records.
8122 30         65 my @tokens = @{ $self->{_filter_cols}->{$col} };
  30         100  
8123 30         71 my $type = $self->{_filter_type}->{$col};
8124              
8125             # Filters are relative to first column in the autofilter.
8126 30         163 $self->_write_filter_column( $col - $col1, $type, \@tokens );
8127             }
8128             }
8129              
8130              
8131             ##############################################################################
8132             #
8133             # _write_filter_column()
8134             #
8135             # Write the element.
8136             #
8137             sub _write_filter_column {
8138              
8139 31     31   79 my $self = shift;
8140 31         48 my $col_id = shift;
8141 31         60 my $type = shift;
8142 31         47 my $filters = shift;
8143              
8144 31         86 my @attributes = ( 'colId' => $col_id );
8145              
8146 31         123 $self->xml_start_tag( 'filterColumn', @attributes );
8147              
8148              
8149 31 100       99 if ( $type == 1 ) {
8150              
8151             # Type == 1 is the new XLSX style filter.
8152 15         75 $self->_write_filters( @$filters );
8153              
8154             }
8155             else {
8156              
8157             # Type == 0 is the classic "custom" filter.
8158 16         42 $self->_write_custom_filters( @$filters );
8159             }
8160              
8161 31         173 $self->xml_end_tag( 'filterColumn' );
8162             }
8163              
8164              
8165             ##############################################################################
8166             #
8167             # _write_filters()
8168             #
8169             # Write the element.
8170             #
8171             sub _write_filters {
8172              
8173 18     18   102 my $self = shift;
8174 18         54 my @filters = @_;
8175 18         60 my @non_blanks = grep { !/^blanks$/i } @filters;
  31         124  
8176 18         54 my @attributes = ();
8177              
8178 18 100       96 if ( @filters != @non_blanks ) {
8179 4         14 @attributes = ( 'blank' => 1 );
8180             }
8181              
8182 18 100 100     109 if ( @filters == 1 && @non_blanks == 0 ) {
8183              
8184             # Special case for blank cells only.
8185 2         9 $self->xml_empty_tag( 'filters', @attributes );
8186             }
8187             else {
8188              
8189             # General case.
8190 16         156 $self->xml_start_tag( 'filters', @attributes );
8191              
8192 16         94 for my $filter ( sort @non_blanks ) {
8193 27         73 $self->_write_filter( $filter );
8194             }
8195              
8196 16         76 $self->xml_end_tag( 'filters' );
8197             }
8198             }
8199              
8200              
8201             ##############################################################################
8202             #
8203             # _write_filter()
8204             #
8205             # Write the element.
8206             #
8207             sub _write_filter {
8208              
8209 28     28   74 my $self = shift;
8210 28         45 my $val = shift;
8211              
8212 28         110 my @attributes = ( 'val' => $val );
8213              
8214 28         142 $self->xml_empty_tag( 'filter', @attributes );
8215             }
8216              
8217              
8218             ##############################################################################
8219             #
8220             # _write_custom_filters()
8221             #
8222             # Write the element.
8223             #
8224             sub _write_custom_filters {
8225              
8226 18     18   65 my $self = shift;
8227 18         52 my @tokens = @_;
8228              
8229 18 100       53 if ( @tokens == 2 ) {
8230              
8231             # One filter expression only.
8232 14         53 $self->xml_start_tag( 'customFilters' );
8233 14         45 $self->_write_custom_filter( @tokens );
8234 14         45 $self->xml_end_tag( 'customFilters' );
8235              
8236             }
8237             else {
8238              
8239             # Two filter expressions.
8240              
8241 4         8 my @attributes;
8242              
8243             # Check if the "join" operand is "and" or "or".
8244 4 50       24 if ( $tokens[2] == 0 ) {
8245 4         11 @attributes = ( 'and' => 1 );
8246             }
8247             else {
8248 0         0 @attributes = ( 'and' => 0 );
8249             }
8250              
8251             # Write the two custom filters.
8252 4         107 $self->xml_start_tag( 'customFilters', @attributes );
8253 4         22 $self->_write_custom_filter( $tokens[0], $tokens[1] );
8254 4         16 $self->_write_custom_filter( $tokens[3], $tokens[4] );
8255 4         12 $self->xml_end_tag( 'customFilters' );
8256             }
8257             }
8258              
8259              
8260             ##############################################################################
8261             #
8262             # _write_custom_filter()
8263             #
8264             # Write the element.
8265             #
8266             sub _write_custom_filter {
8267              
8268 23     23   48 my $self = shift;
8269 23         42 my $operator = shift;
8270 23         38 my $val = shift;
8271 23         43 my @attributes = ();
8272              
8273 23         190 my %operators = (
8274             1 => 'lessThan',
8275             2 => 'equal',
8276             3 => 'lessThanOrEqual',
8277             4 => 'greaterThan',
8278             5 => 'notEqual',
8279             6 => 'greaterThanOrEqual',
8280             22 => 'equal',
8281             );
8282              
8283              
8284             # Convert the operator from a number to a descriptive string.
8285 23 50       72 if ( defined $operators{$operator} ) {
8286 23         48 $operator = $operators{$operator};
8287             }
8288             else {
8289 0         0 croak "Unknown operator = $operator\n";
8290             }
8291              
8292             # The 'equal' operator is the default attribute and isn't stored.
8293 23 100       81 push @attributes, ( 'operator' => $operator ) unless $operator eq 'equal';
8294 23         50 push @attributes, ( 'val' => $val );
8295              
8296 23         114 $self->xml_empty_tag( 'customFilter', @attributes );
8297             }
8298              
8299              
8300             ##############################################################################
8301             #
8302             # _write_hyperlinks()
8303             #
8304             # Process any stored hyperlinks in row/col order and write the
8305             # element. The attributes are different for internal and external links.
8306             #
8307             sub _write_hyperlinks {
8308              
8309 993     993   2556 my $self = shift;
8310 993         2315 my @hlink_refs;
8311              
8312             # Sort the hyperlinks into row order.
8313 993         2682 my @row_nums = sort { $a <=> $b } keys %{ $self->{_hyperlinks} };
  48         140  
  993         5060  
8314              
8315             # Exit if there are no hyperlinks to process.
8316 993 100       4135 return if !@row_nums;
8317              
8318             # Iterate over the rows.
8319 49         256 for my $row_num ( @row_nums ) {
8320              
8321             # Sort the hyperlinks into column order.
8322 1         13 my @col_nums = sort { $a <=> $b }
8323 81         152 keys %{ $self->{_hyperlinks}->{$row_num} };
  81         376  
8324              
8325             # Iterate over the columns.
8326 81         209 for my $col_num ( @col_nums ) {
8327              
8328             # Get the link data for this cell.
8329 82         189 my $link = $self->{_hyperlinks}->{$row_num}->{$col_num};
8330 82         174 my $link_type = $link->{_link_type};
8331              
8332              
8333             # If the cell isn't a string then we have to add the url as
8334             # the string to display.
8335 82         148 my $display;
8336 82 50 66     789 if ( $self->{_table}
      66        
8337             && $self->{_table}->{$row_num}
8338             && $self->{_table}->{$row_num}->{$col_num} )
8339             {
8340 81         184 my $cell = $self->{_table}->{$row_num}->{$col_num};
8341 81 100       308 $display = $link->{_url} if $cell->[0] ne 's';
8342             }
8343              
8344              
8345 82 100       268 if ( $link_type == 1 ) {
8346              
8347             # External link with rel file relationship.
8348             push @hlink_refs,
8349             [
8350             $link_type, $row_num,
8351             $col_num, ++$self->{_rel_count},
8352             $link->{_str}, $display,
8353             $link->{_tip}
8354 74         322 ];
8355              
8356             # Links for use by the packager.
8357 74         343 push @{ $self->{_external_hyper_links} },
8358 74         135 [ '/hyperlink', $link->{_url}, 'External' ];
8359             }
8360             else {
8361              
8362             # Internal link with rel file relationship.
8363             push @hlink_refs,
8364             [
8365             $link_type, $row_num, $col_num,
8366             $link->{_url}, $link->{_str}, $link->{_tip}
8367 8         29 ];
8368             }
8369             }
8370             }
8371              
8372             # Write the hyperlink elements.
8373 49         243 $self->xml_start_tag( 'hyperlinks' );
8374              
8375 49         151 for my $aref ( @hlink_refs ) {
8376 82         280 my ( $type, @args ) = @$aref;
8377              
8378 82 100       364 if ( $type == 1 ) {
    50          
8379 74         299 $self->_write_hyperlink_external( @args );
8380             }
8381             elsif ( $type == 2 ) {
8382 8         33 $self->_write_hyperlink_internal( @args );
8383             }
8384             }
8385              
8386 49         273 $self->xml_end_tag( 'hyperlinks' );
8387             }
8388              
8389              
8390             ##############################################################################
8391             #
8392             # _write_hyperlink_external()
8393             #
8394             # Write the element for external links.
8395             #
8396             sub _write_hyperlink_external {
8397              
8398 75     75   155 my $self = shift;
8399 75         147 my $row = shift;
8400 75         139 my $col = shift;
8401 75         135 my $id = shift;
8402 75         151 my $location = shift;
8403 75         276 my $display = shift;
8404 75         143 my $tooltip = shift;
8405              
8406 75         305 my $ref = xl_rowcol_to_cell( $row, $col );
8407 75         216 my $r_id = 'rId' . $id;
8408              
8409 75         276 my @attributes = (
8410             'ref' => $ref,
8411             'r:id' => $r_id,
8412             );
8413              
8414 75 100       291 push @attributes, ( 'location' => $location ) if defined $location;
8415 75 100       229 push @attributes, ( 'display' => $display ) if defined $display;
8416 75 100       223 push @attributes, ( 'tooltip' => $tooltip ) if defined $tooltip;
8417              
8418 75         301 $self->xml_empty_tag( 'hyperlink', @attributes );
8419             }
8420              
8421              
8422             ##############################################################################
8423             #
8424             # _write_hyperlink_internal()
8425             #
8426             # Write the element for internal links.
8427             #
8428             sub _write_hyperlink_internal {
8429              
8430 11     11   86 my $self = shift;
8431 11         16 my $row = shift;
8432 11         19 my $col = shift;
8433 11         17 my $location = shift;
8434 11         18 my $display = shift;
8435 11         19 my $tooltip = shift;
8436              
8437 11         87 my $ref = xl_rowcol_to_cell( $row, $col );
8438              
8439 11         32 my @attributes = ( 'ref' => $ref, 'location' => $location );
8440              
8441 11 100       29 push @attributes, ( 'tooltip' => $tooltip ) if defined $tooltip;
8442 11         22 push @attributes, ( 'display' => $display );
8443              
8444 11         43 $self->xml_empty_tag( 'hyperlink', @attributes );
8445             }
8446              
8447              
8448             ##############################################################################
8449             #
8450             # _write_panes()
8451             #
8452             # Write the frozen or split elements.
8453             #
8454             sub _write_panes {
8455              
8456 83     83   197 my $self = shift;
8457 83         127 my @panes = @{ $self->{_panes} };
  83         235  
8458              
8459 83 100       208 return unless @panes;
8460              
8461 66 100       219 if ( $panes[4] == 2 ) {
8462 38         105 $self->_write_split_panes( @panes );
8463             }
8464             else {
8465 28         78 $self->_write_freeze_panes( @panes );
8466             }
8467             }
8468              
8469              
8470             ##############################################################################
8471             #
8472             # _write_freeze_panes()
8473             #
8474             # Write the element for freeze panes.
8475             #
8476             sub _write_freeze_panes {
8477              
8478 28     28   45 my $self = shift;
8479 28         39 my @attributes;
8480              
8481 28         81 my ( $row, $col, $top_row, $left_col, $type ) = @_;
8482              
8483 28         51 my $y_split = $row;
8484 28         44 my $x_split = $col;
8485 28         78 my $top_left_cell = xl_rowcol_to_cell( $top_row, $left_col );
8486 28         109 my $active_pane;
8487             my $state;
8488 28         0 my $active_cell;
8489 28         0 my $sqref;
8490              
8491             # Move user cell selection to the panes.
8492 28 100       41 if ( @{ $self->{_selections} } ) {
  28         74  
8493 7         12 ( undef, $active_cell, $sqref ) = @{ $self->{_selections}->[0] };
  7         21  
8494 7         17 $self->{_selections} = [];
8495             }
8496              
8497             # Set the active pane.
8498 28 100 100     134 if ( $row && $col ) {
    100          
8499 13         30 $active_pane = 'bottomRight';
8500              
8501 13         67 my $row_cell = xl_rowcol_to_cell( $row, 0 );
8502 13         39 my $col_cell = xl_rowcol_to_cell( 0, $col );
8503              
8504 13         29 push @{ $self->{_selections} },
  13         74  
8505             (
8506             [ 'topRight', $col_cell, $col_cell ],
8507             [ 'bottomLeft', $row_cell, $row_cell ],
8508             [ 'bottomRight', $active_cell, $sqref ]
8509             );
8510             }
8511             elsif ( $col ) {
8512 7         13 $active_pane = 'topRight';
8513 7         19 push @{ $self->{_selections} }, [ 'topRight', $active_cell, $sqref ];
  7         27  
8514             }
8515             else {
8516 8         20 $active_pane = 'bottomLeft';
8517 8         13 push @{ $self->{_selections} }, [ 'bottomLeft', $active_cell, $sqref ];
  8         43  
8518             }
8519              
8520             # Set the pane type.
8521 28 100       76 if ( $type == 0 ) {
    50          
8522 25         52 $state = 'frozen';
8523             }
8524             elsif ( $type == 1 ) {
8525 3         8 $state = 'frozenSplit';
8526             }
8527             else {
8528 0         0 $state = 'split';
8529             }
8530              
8531              
8532 28 100       73 push @attributes, ( 'xSplit' => $x_split ) if $x_split;
8533 28 100       70 push @attributes, ( 'ySplit' => $y_split ) if $y_split;
8534              
8535 28         54 push @attributes, ( 'topLeftCell' => $top_left_cell );
8536 28         51 push @attributes, ( 'activePane' => $active_pane );
8537 28         59 push @attributes, ( 'state' => $state );
8538              
8539              
8540 28         105 $self->xml_empty_tag( 'pane', @attributes );
8541             }
8542              
8543              
8544             ##############################################################################
8545             #
8546             # _write_split_panes()
8547             #
8548             # Write the element for split panes.
8549             #
8550             # See also, implementers note for split_panes().
8551             #
8552             sub _write_split_panes {
8553              
8554 38     38   53 my $self = shift;
8555 38         90 my @attributes;
8556             my $y_split;
8557 38         0 my $x_split;
8558 38         56 my $has_selection = 0;
8559 38         85 my $active_pane;
8560             my $active_cell;
8561 38         0 my $sqref;
8562              
8563 38         86 my ( $row, $col, $top_row, $left_col, $type ) = @_;
8564 38         106 $y_split = $row;
8565 38         55 $x_split = $col;
8566              
8567             # Move user cell selection to the panes.
8568 38 100       70 if ( @{ $self->{_selections} } ) {
  38         92  
8569 8         10 ( undef, $active_cell, $sqref ) = @{ $self->{_selections}->[0] };
  8         20  
8570 8         16 $self->{_selections} = [];
8571 8         11 $has_selection = 1;
8572             }
8573              
8574             # Convert the row and col to 1/20 twip units with padding.
8575 38 100       105 $y_split = int( 20 * $y_split + 300 ) if $y_split;
8576 38 100       117 $x_split = $self->_calculate_x_split_width( $x_split ) if $x_split;
8577              
8578             # For non-explicit topLeft definitions, estimate the cell offset based
8579             # on the pixels dimensions. This is only a workaround and doesn't take
8580             # adjusted cell dimensions into account.
8581 38 100 100     147 if ( $top_row == $row && $left_col == $col ) {
8582 26         70 $top_row = int( 0.5 + ( $y_split - 300 ) / 20 / 15 );
8583 26         67 $left_col = int( 0.5 + ( $x_split - 390 ) / 20 / 3 * 4 / 64 );
8584             }
8585              
8586 38         125 my $top_left_cell = xl_rowcol_to_cell( $top_row, $left_col );
8587              
8588             # If there is no selection set the active cell to the top left cell.
8589 38 100       81 if ( !$has_selection ) {
8590 30         51 $active_cell = $top_left_cell;
8591 30         46 $sqref = $top_left_cell;
8592             }
8593              
8594             # Set the Cell selections.
8595 38 100 100     136 if ( $row && $col ) {
    100          
8596 10         20 $active_pane = 'bottomRight';
8597              
8598 10         26 my $row_cell = xl_rowcol_to_cell( $top_row, 0 );
8599 10         26 my $col_cell = xl_rowcol_to_cell( 0, $left_col );
8600              
8601 10         23 push @{ $self->{_selections} },
  10         56  
8602             (
8603             [ 'topRight', $col_cell, $col_cell ],
8604             [ 'bottomLeft', $row_cell, $row_cell ],
8605             [ 'bottomRight', $active_cell, $sqref ]
8606             );
8607             }
8608             elsif ( $col ) {
8609 14         28 $active_pane = 'topRight';
8610 14         22 push @{ $self->{_selections} }, [ 'topRight', $active_cell, $sqref ];
  14         48  
8611             }
8612             else {
8613 14         34 $active_pane = 'bottomLeft';
8614 14         21 push @{ $self->{_selections} }, [ 'bottomLeft', $active_cell, $sqref ];
  14         53  
8615             }
8616              
8617 38 100       102 push @attributes, ( 'xSplit' => $x_split ) if $x_split;
8618 38 100       90 push @attributes, ( 'ySplit' => $y_split ) if $y_split;
8619 38         76 push @attributes, ( 'topLeftCell' => $top_left_cell );
8620 38 100       77 push @attributes, ( 'activePane' => $active_pane ) if $has_selection;
8621              
8622 38         121 $self->xml_empty_tag( 'pane', @attributes );
8623             }
8624              
8625              
8626             ##############################################################################
8627             #
8628             # _calculate_x_split_width()
8629             #
8630             # Convert column width from user units to pane split width.
8631             #
8632             sub _calculate_x_split_width {
8633              
8634 24     24   44 my $self = shift;
8635 24         39 my $width = shift;
8636              
8637 24         37 my $max_digit_width = 7; # For Calabri 11.
8638 24         36 my $padding = 5;
8639 24         34 my $pixels;
8640              
8641             # Convert to pixels.
8642 24 50       68 if ( $width < 1 ) {
8643 0         0 $pixels = int( $width * ( $max_digit_width + $padding ) + 0.5 );
8644             }
8645             else {
8646 24         66 $pixels = int( $width * $max_digit_width + 0.5 ) + $padding;
8647             }
8648              
8649             # Convert to points.
8650 24         52 my $points = $pixels * 3 / 4;
8651              
8652             # Convert to twips (twentieths of a point).
8653 24         40 my $twips = $points * 20;
8654              
8655             # Add offset/padding.
8656 24         40 $width = $twips + 390;
8657              
8658 24         46 return $width;
8659             }
8660              
8661              
8662             ##############################################################################
8663             #
8664             # _write_tab_color()
8665             #
8666             # Write the element.
8667             #
8668             sub _write_tab_color {
8669              
8670 13     13   58 my $self = shift;
8671 13         33 my $color_index = $self->{_tab_color};
8672              
8673 13 100       42 return unless $color_index;
8674              
8675 5         19 my $rgb = $self->_get_palette_color( $color_index );
8676              
8677 5         16 my @attributes = ( 'rgb' => $rgb );
8678              
8679 5         28 $self->xml_empty_tag( 'tabColor', @attributes );
8680             }
8681              
8682              
8683             ##############################################################################
8684             #
8685             # _write_outline_pr()
8686             #
8687             # Write the element.
8688             #
8689             sub _write_outline_pr {
8690              
8691 11     11   25 my $self = shift;
8692 11         28 my @attributes = ();
8693              
8694 11 100       52 return unless $self->{_outline_changed};
8695              
8696 1 50       6 push @attributes, ( "applyStyles" => 1 ) if $self->{_outline_style};
8697 1 50       5 push @attributes, ( "summaryBelow" => 0 ) if !$self->{_outline_below};
8698 1 50       4 push @attributes, ( "summaryRight" => 0 ) if !$self->{_outline_right};
8699 1 50       4 push @attributes, ( "showOutlineSymbols" => 0 ) if !$self->{_outline_on};
8700              
8701 1         20 $self->xml_empty_tag( 'outlinePr', @attributes );
8702             }
8703              
8704              
8705             ##############################################################################
8706             #
8707             # _write_sheet_protection()
8708             #
8709             # Write the element.
8710             #
8711             sub _write_sheet_protection {
8712              
8713 1039     1039   2890 my $self = shift;
8714 1039         2642 my @attributes;
8715              
8716 1039 100       6361 return unless $self->{_protect};
8717              
8718 27         42 my %arg = %{ $self->{_protect} };
  27         192  
8719              
8720 27 100       99 push @attributes, ( "password" => $arg{password} ) if $arg{password};
8721 27 100       93 push @attributes, ( "sheet" => 1 ) if $arg{sheet};
8722 27 100       72 push @attributes, ( "content" => 1 ) if $arg{content};
8723 27 100       68 push @attributes, ( "objects" => 1 ) if !$arg{objects};
8724 27 100       69 push @attributes, ( "scenarios" => 1 ) if !$arg{scenarios};
8725 27 100       57 push @attributes, ( "formatCells" => 0 ) if $arg{format_cells};
8726 27 100       57 push @attributes, ( "formatColumns" => 0 ) if $arg{format_columns};
8727 27 100       62 push @attributes, ( "formatRows" => 0 ) if $arg{format_rows};
8728 27 100       51 push @attributes, ( "insertColumns" => 0 ) if $arg{insert_columns};
8729 27 100       56 push @attributes, ( "insertRows" => 0 ) if $arg{insert_rows};
8730 27 100       58 push @attributes, ( "insertHyperlinks" => 0 ) if $arg{insert_hyperlinks};
8731 27 100       58 push @attributes, ( "deleteColumns" => 0 ) if $arg{delete_columns};
8732 27 100       63 push @attributes, ( "deleteRows" => 0 ) if $arg{delete_rows};
8733              
8734             push @attributes, ( "selectLockedCells" => 1 )
8735 27 100       58 if !$arg{select_locked_cells};
8736              
8737 27 100       54 push @attributes, ( "sort" => 0 ) if $arg{sort};
8738 27 100       58 push @attributes, ( "autoFilter" => 0 ) if $arg{autofilter};
8739 27 100       61 push @attributes, ( "pivotTables" => 0 ) if $arg{pivot_tables};
8740              
8741             push @attributes, ( "selectUnlockedCells" => 1 )
8742 27 100       62 if !$arg{select_unlocked_cells};
8743              
8744              
8745 27         140 $self->xml_empty_tag( 'sheetProtection', @attributes );
8746             }
8747              
8748              
8749             ##############################################################################
8750             #
8751             # _write_drawings()
8752             #
8753             # Write the elements.
8754             #
8755             sub _write_drawings {
8756              
8757 1014     1014   2320 my $self = shift;
8758              
8759 1014 100       4332 return unless $self->{_drawing};
8760              
8761 470         2419 $self->_write_drawing( ++$self->{_rel_count} );
8762             }
8763              
8764              
8765             ##############################################################################
8766             #
8767             # _write_drawing()
8768             #
8769             # Write the element.
8770             #
8771             sub _write_drawing {
8772              
8773 470     470   1285 my $self = shift;
8774 470         1135 my $id = shift;
8775 470         1707 my $r_id = 'rId' . $id;
8776              
8777 470         1749 my @attributes = ( 'r:id' => $r_id );
8778              
8779 470         2363 $self->xml_empty_tag( 'drawing', @attributes );
8780             }
8781              
8782              
8783             ##############################################################################
8784             #
8785             # _write_legacy_drawing()
8786             #
8787             # Write the element.
8788             #
8789             sub _write_legacy_drawing {
8790              
8791 994     994   2464 my $self = shift;
8792 994         2183 my $id;
8793              
8794 994 100       4388 return unless $self->{_has_vml};
8795              
8796             # Increment the relationship id for any drawings or comments.
8797 54         155 $id = ++$self->{_rel_count};
8798              
8799 54         381 my @attributes = ( 'r:id' => 'rId' . $id );
8800              
8801 54         356 $self->xml_empty_tag( 'legacyDrawing', @attributes );
8802             }
8803              
8804              
8805              
8806             ##############################################################################
8807             #
8808             # _write_legacy_drawing_hf()
8809             #
8810             # Write the element.
8811             #
8812             sub _write_legacy_drawing_hf {
8813              
8814 993     993   2629 my $self = shift;
8815 993         2157 my $id;
8816              
8817 993 100       3958 return unless $self->{_has_header_vml};
8818              
8819             # Increment the relationship id for any drawings or comments.
8820 22         55 $id = ++$self->{_rel_count};
8821              
8822 22         91 my @attributes = ( 'r:id' => 'rId' . $id );
8823              
8824 22         87 $self->xml_empty_tag( 'legacyDrawingHF', @attributes );
8825             }
8826              
8827              
8828             #
8829             # Note, the following font methods are, more or less, duplicated from the
8830             # Excel::Writer::XLSX::Package::Styles class. I will look at implementing
8831             # this is a cleaner encapsulated mode at a later stage.
8832             #
8833              
8834              
8835             ##############################################################################
8836             #
8837             # _write_font()
8838             #
8839             # Write the element.
8840             #
8841             sub _write_font {
8842              
8843 56     56   102 my $self = shift;
8844 56         103 my $format = shift;
8845              
8846 56         168 $self->{_rstring}->xml_start_tag( 'rPr' );
8847              
8848 56 100       228 $self->{_rstring}->xml_empty_tag( 'b' ) if $format->{_bold};
8849 56 100       176 $self->{_rstring}->xml_empty_tag( 'i' ) if $format->{_italic};
8850 56 50       164 $self->{_rstring}->xml_empty_tag( 'strike' ) if $format->{_font_strikeout};
8851 56 50       144 $self->{_rstring}->xml_empty_tag( 'outline' ) if $format->{_font_outline};
8852 56 50       160 $self->{_rstring}->xml_empty_tag( 'shadow' ) if $format->{_font_shadow};
8853              
8854             # Handle the underline variants.
8855 56 50       145 $self->_write_underline( $format->{_underline} ) if $format->{_underline};
8856              
8857 56 50       164 $self->_write_vert_align( 'superscript' ) if $format->{_font_script} == 1;
8858 56 50       142 $self->_write_vert_align( 'subscript' ) if $format->{_font_script} == 2;
8859              
8860 56         225 $self->{_rstring}->xml_empty_tag( 'sz', 'val', $format->{_size} );
8861              
8862 56 50       222 if ( my $theme = $format->{_theme} ) {
    100          
8863 0         0 $self->_write_rstring_color( 'theme' => $theme );
8864             }
8865             elsif ( my $color = $format->{_color} ) {
8866 1         4 $color = $self->_get_palette_color( $color );
8867              
8868 1         7 $self->_write_rstring_color( 'rgb' => $color );
8869             }
8870             else {
8871 55         182 $self->_write_rstring_color( 'theme' => 1 );
8872             }
8873              
8874 56         247 $self->{_rstring}->xml_empty_tag( 'rFont', 'val', $format->{_font} );
8875             $self->{_rstring}
8876 56         237 ->xml_empty_tag( 'family', 'val', $format->{_font_family} );
8877              
8878 56 50 33     346 if ( $format->{_font} eq 'Calibri' && !$format->{_hyperlink} ) {
8879             $self->{_rstring}
8880 56         166 ->xml_empty_tag( 'scheme', 'val', $format->{_font_scheme} );
8881             }
8882              
8883 56         183 $self->{_rstring}->xml_end_tag( 'rPr' );
8884             }
8885              
8886              
8887             ###############################################################################
8888             #
8889             # _write_underline()
8890             #
8891             # Write the underline font element.
8892             #
8893             sub _write_underline {
8894              
8895 0     0   0 my $self = shift;
8896 0         0 my $underline = shift;
8897 0         0 my @attributes;
8898              
8899             # Handle the underline variants.
8900 0 0       0 if ( $underline == 2 ) {
    0          
    0          
8901 0         0 @attributes = ( val => 'double' );
8902             }
8903             elsif ( $underline == 33 ) {
8904 0         0 @attributes = ( val => 'singleAccounting' );
8905             }
8906             elsif ( $underline == 34 ) {
8907 0         0 @attributes = ( val => 'doubleAccounting' );
8908             }
8909             else {
8910 0         0 @attributes = (); # Default to single underline.
8911             }
8912              
8913 0         0 $self->{_rstring}->xml_empty_tag( 'u', @attributes );
8914              
8915             }
8916              
8917              
8918             ##############################################################################
8919             #
8920             # _write_vert_align()
8921             #
8922             # Write the font sub-element.
8923             #
8924             sub _write_vert_align {
8925              
8926 0     0   0 my $self = shift;
8927 0         0 my $val = shift;
8928              
8929 0         0 my @attributes = ( 'val' => $val );
8930              
8931 0         0 $self->{_rstring}->xml_empty_tag( 'vertAlign', @attributes );
8932             }
8933              
8934              
8935             ##############################################################################
8936             #
8937             # _write_rstring_color()
8938             #
8939             # Write the element.
8940             #
8941             sub _write_rstring_color {
8942              
8943 56     56   100 my $self = shift;
8944 56         104 my $name = shift;
8945 56         138 my $value = shift;
8946              
8947 56         143 my @attributes = ( $name => $value );
8948              
8949 56         161 $self->{_rstring}->xml_empty_tag( 'color', @attributes );
8950             }
8951              
8952              
8953             #
8954             # End font duplication code.
8955             #
8956              
8957              
8958             ##############################################################################
8959             #
8960             # _write_data_validations()
8961             #
8962             # Write the element.
8963             #
8964             sub _write_data_validations {
8965              
8966 1049     1049   2846 my $self = shift;
8967 1049         2775 my @validations = @{ $self->{_validations} };
  1049         3382  
8968 1049         2815 my $count = @validations;
8969              
8970 1049 100       3902 return unless $count;
8971              
8972 62         141 my @attributes = ( 'count' => $count );
8973              
8974 62         259 $self->xml_start_tag( 'dataValidations', @attributes );
8975              
8976 62         149 for my $validation ( @validations ) {
8977              
8978             # Write the dataValidation element.
8979 64         168 $self->_write_data_validation( $validation );
8980             }
8981              
8982 62         184 $self->xml_end_tag( 'dataValidations' );
8983             }
8984              
8985              
8986             ##############################################################################
8987             #
8988             # _write_data_validation()
8989             #
8990             # Write the element.
8991             #
8992             sub _write_data_validation {
8993              
8994 64     64   92 my $self = shift;
8995 64         99 my $param = shift;
8996 64         115 my $sqref = '';
8997 64         123 my @attributes = ();
8998              
8999              
9000             # Set the cell range(s) for the data validation.
9001 64         108 for my $cells ( @{ $param->{cells} } ) {
  64         142  
9002              
9003             # Add a space between multiple cell ranges.
9004 68 100       180 $sqref .= ' ' if $sqref ne '';
9005              
9006 68         158 my ( $row_first, $col_first, $row_last, $col_last ) = @$cells;
9007              
9008             # Swap last row/col for first row/col as necessary
9009 68 50       149 if ( $row_first > $row_last ) {
9010 0         0 ( $row_first, $row_last ) = ( $row_last, $row_first );
9011             }
9012              
9013 68 50       145 if ( $col_first > $col_last ) {
9014 0         0 ( $col_first, $col_last ) = ( $col_last, $col_first );
9015             }
9016              
9017             # If the first and last cell are the same write a single cell.
9018 68 100 66     247 if ( ( $row_first == $row_last ) && ( $col_first == $col_last ) ) {
9019 65         245 $sqref .= xl_rowcol_to_cell( $row_first, $col_first );
9020             }
9021             else {
9022 3         16 $sqref .= xl_range( $row_first, $row_last, $col_first, $col_last );
9023             }
9024             }
9025              
9026              
9027 64 100       187 if ( $param->{validate} ne 'none' ) {
9028              
9029 62         139 push @attributes, ( 'type' => $param->{validate} );
9030              
9031 62 100       147 if ( $param->{criteria} ne 'between' ) {
9032 26         48 push @attributes, ( 'operator' => $param->{criteria} );
9033             }
9034              
9035             }
9036              
9037 64 100       173 if ( $param->{error_type} ) {
9038             push @attributes, ( 'errorStyle' => 'warning' )
9039 2 100       8 if $param->{error_type} == 1;
9040             push @attributes, ( 'errorStyle' => 'information' )
9041 2 100       6 if $param->{error_type} == 2;
9042             }
9043              
9044 64 100       177 push @attributes, ( 'allowBlank' => 1 ) if $param->{ignore_blank};
9045 64 100       153 push @attributes, ( 'showDropDown' => 1 ) if !$param->{dropdown};
9046 64 100       180 push @attributes, ( 'showInputMessage' => 1 ) if $param->{show_input};
9047 64 100       181 push @attributes, ( 'showErrorMessage' => 1 ) if $param->{show_error};
9048              
9049             push @attributes, ( 'errorTitle' => $param->{error_title} )
9050 64 100       173 if $param->{error_title};
9051              
9052             push @attributes, ( 'error' => $param->{error_message} )
9053 64 100       139 if $param->{error_message};
9054              
9055             push @attributes, ( 'promptTitle' => $param->{input_title} )
9056 64 100       153 if $param->{input_title};
9057              
9058             push @attributes, ( 'prompt' => $param->{input_message} )
9059 64 100       147 if $param->{input_message};
9060              
9061 64         134 push @attributes, ( 'sqref' => $sqref );
9062              
9063 64 100       164 if ( $param->{validate} eq 'none' ) {
9064 2         16 $self->xml_empty_tag( 'dataValidation', @attributes );
9065             }
9066             else {
9067 62         222 $self->xml_start_tag( 'dataValidation', @attributes );
9068              
9069             # Write the formula1 element.
9070 62         205 $self->_write_formula_1( $param->{value} );
9071              
9072             # Write the formula2 element.
9073             $self->_write_formula_2( $param->{maximum} )
9074 62 100       195 if defined $param->{maximum};
9075              
9076 62         175 $self->xml_end_tag( 'dataValidation' );
9077             }
9078             }
9079              
9080              
9081             ##############################################################################
9082             #
9083             # _write_formula_1()
9084             #
9085             # Write the element.
9086             #
9087             sub _write_formula_1 {
9088              
9089 62     62   113 my $self = shift;
9090 62         96 my $formula = shift;
9091              
9092             # Convert a list array ref into a comma separated string.
9093 62 100       159 if ( ref $formula eq 'ARRAY' ) {
9094 10         41 $formula = join ',', @$formula;
9095 10         32 $formula = qq("$formula");
9096             }
9097              
9098 62         170 $formula =~ s/^=//; # Remove formula symbol.
9099              
9100 62         204 $self->xml_data_element( 'formula1', $formula );
9101             }
9102              
9103              
9104             ##############################################################################
9105             #
9106             # _write_formula_2()
9107             #
9108             # Write the element.
9109             #
9110             sub _write_formula_2 {
9111              
9112 24     24   43 my $self = shift;
9113 24         34 my $formula = shift;
9114              
9115 24         53 $formula =~ s/^=//; # Remove formula symbol.
9116              
9117 24         54 $self->xml_data_element( 'formula2', $formula );
9118             }
9119              
9120              
9121             ##############################################################################
9122             #
9123             # _write_conditional_formats()
9124             #
9125             # Write the Worksheet conditional formats.
9126             #
9127             sub _write_conditional_formats {
9128              
9129 997     997   2417 my $self = shift;
9130 997         2555 my @ranges = sort keys %{ $self->{_cond_formats} };
  997         5380  
9131              
9132 997 100       4047 return unless scalar @ranges;
9133              
9134 63         206 for my $range ( @ranges ) {
9135             $self->_write_conditional_formatting( $range,
9136 110         392 $self->{_cond_formats}->{$range} );
9137             }
9138             }
9139              
9140              
9141             ##############################################################################
9142             #
9143             # _write_conditional_formatting()
9144             #
9145             # Write the element.
9146             #
9147             sub _write_conditional_formatting {
9148              
9149 110     110   213 my $self = shift;
9150 110         239 my $range = shift;
9151 110         187 my $params = shift;
9152              
9153 110         291 my @attributes = ( 'sqref' => $range );
9154              
9155 110         435 $self->xml_start_tag( 'conditionalFormatting', @attributes );
9156              
9157 110         303 for my $param ( @$params ) {
9158              
9159             # Write the cfRule element.
9160 149         436 $self->_write_cf_rule( $param );
9161             }
9162              
9163 110         402 $self->xml_end_tag( 'conditionalFormatting' );
9164             }
9165              
9166             ##############################################################################
9167             #
9168             # _write_cf_rule()
9169             #
9170             # Write the element.
9171             #
9172             sub _write_cf_rule {
9173              
9174 149     149   316 my $self = shift;
9175 149         248 my $param = shift;
9176              
9177 149         398 my @attributes = ( 'type' => $param->{type} );
9178              
9179             push @attributes, ( 'dxfId' => $param->{format} )
9180 149 100       477 if defined $param->{format};
9181              
9182 149         399 push @attributes, ( 'priority' => $param->{priority} );
9183              
9184             push @attributes, ( 'stopIfTrue' => 1 )
9185 149 100       432 if $param->{stop_if_true};
9186              
9187 149 100 100     2232 if ( $param->{type} eq 'cellIs' ) {
    100 100        
    100 100        
    100 100        
    100 100        
    100 100        
    100          
    100          
    100          
    100          
    100          
    50          
9188 36         153 push @attributes, ( 'operator' => $param->{criteria} );
9189              
9190 36         173 $self->xml_start_tag( 'cfRule', @attributes );
9191              
9192 36 100 66     210 if ( defined $param->{minimum} && defined $param->{maximum} ) {
9193 5         23 $self->_write_formula( $param->{minimum} );
9194 5         26 $self->_write_formula( $param->{maximum} );
9195             }
9196             else {
9197 31         94 my $value = $param->{value};
9198              
9199             # String "Cell" values must be quoted, apart from ranges.
9200 31 100 100     429 if ( $value !~ /(\$?)([A-Z]{1,3})(\$?)(\d+)/
9201             && $value !~
9202             /^([+-]?)(?=\d|\.\d)\d*(\.\d*)?([Ee]([+-]?\d+))?$/ )
9203             {
9204 2 100       12 if ( $value !~ /^".*"$/ ) {
9205 1         4 $value = qq("$value");
9206             }
9207             }
9208              
9209 31         139 $self->_write_formula( $value );
9210             }
9211              
9212 36         206 $self->xml_end_tag( 'cfRule' );
9213             }
9214             elsif ( $param->{type} eq 'aboveAverage' ) {
9215 10 100       27 if ( $param->{criteria} =~ /below/ ) {
9216 5         11 push @attributes, ( 'aboveAverage' => 0 );
9217             }
9218              
9219 10 100       24 if ( $param->{criteria} =~ /equal/ ) {
9220 2         5 push @attributes, ( 'equalAverage' => 1 );
9221             }
9222              
9223 10 100       30 if ( $param->{criteria} =~ /([123]) std dev/ ) {
9224 6         15 push @attributes, ( 'stdDev' => $1 );
9225             }
9226              
9227 10         24 $self->xml_empty_tag( 'cfRule', @attributes );
9228             }
9229             elsif ( $param->{type} eq 'top10' ) {
9230 4 100 66     16 if ( defined $param->{criteria} && $param->{criteria} eq '%' ) {
9231 2         4 push @attributes, ( 'percent' => 1 );
9232             }
9233              
9234 4 100       10 if ( $param->{direction} ) {
9235 2         5 push @attributes, ( 'bottom' => 1 );
9236             }
9237              
9238 4   50     10 my $rank = $param->{value} || 10;
9239 4         8 push @attributes, ( 'rank' => $rank );
9240              
9241 4         22 $self->xml_empty_tag( 'cfRule', @attributes );
9242             }
9243             elsif ( $param->{type} eq 'duplicateValues' ) {
9244 1         5 $self->xml_empty_tag( 'cfRule', @attributes );
9245             }
9246             elsif ( $param->{type} eq 'uniqueValues' ) {
9247 1         5 $self->xml_empty_tag( 'cfRule', @attributes );
9248             }
9249             elsif ($param->{type} eq 'containsText'
9250             || $param->{type} eq 'notContainsText'
9251             || $param->{type} eq 'beginsWith'
9252             || $param->{type} eq 'endsWith' )
9253             {
9254 8         19 push @attributes, ( 'operator' => $param->{criteria} );
9255 8         15 push @attributes, ( 'text' => $param->{value} );
9256              
9257 8         22 $self->xml_start_tag( 'cfRule', @attributes );
9258 8         25 $self->_write_formula( $param->{formula} );
9259 8         21 $self->xml_end_tag( 'cfRule' );
9260             }
9261             elsif ( $param->{type} eq 'timePeriod' ) {
9262 10         21 push @attributes, ( 'timePeriod' => $param->{criteria} );
9263              
9264 10         28 $self->xml_start_tag( 'cfRule', @attributes );
9265 10         35 $self->_write_formula( $param->{formula} );
9266 10         29 $self->xml_end_tag( 'cfRule' );
9267             }
9268             elsif ($param->{type} eq 'containsBlanks'
9269             || $param->{type} eq 'notContainsBlanks'
9270             || $param->{type} eq 'containsErrors'
9271             || $param->{type} eq 'notContainsErrors' )
9272             {
9273 4         17 $self->xml_start_tag( 'cfRule', @attributes );
9274 4         13 $self->_write_formula( $param->{formula} );
9275 4         12 $self->xml_end_tag( 'cfRule' );
9276             }
9277             elsif ( $param->{type} eq 'colorScale' ) {
9278              
9279 5         26 $self->xml_start_tag( 'cfRule', @attributes );
9280 5         34 $self->_write_color_scale( $param );
9281 5         17 $self->xml_end_tag( 'cfRule' );
9282             }
9283             elsif ( $param->{type} eq 'dataBar' ) {
9284              
9285 29         116 $self->xml_start_tag( 'cfRule', @attributes );
9286              
9287 29         126 $self->_write_data_bar( $param );
9288              
9289 29 100       106 if ($param->{_is_data_bar_2010}) {
9290 25         74 $self->_write_data_bar_ext( $param );
9291             }
9292              
9293 29         95 $self->xml_end_tag( 'cfRule' );
9294             }
9295             elsif ( $param->{type} eq 'expression' ) {
9296              
9297 4         14 $self->xml_start_tag( 'cfRule', @attributes );
9298 4         14 $self->_write_formula( $param->{criteria} );
9299 4         13 $self->xml_end_tag( 'cfRule' );
9300             }
9301             elsif ( $param->{type} eq 'iconSet' ) {
9302              
9303 37         117 $self->xml_start_tag( 'cfRule', @attributes );
9304 37         119 $self->_write_icon_set( $param );
9305 37         84 $self->xml_end_tag( 'cfRule' );
9306             }
9307             }
9308              
9309              
9310             ##############################################################################
9311             #
9312             # _write_icon_set()
9313             #
9314             # Write the element.
9315             #
9316             sub _write_icon_set {
9317              
9318 37     37   63 my $self = shift;
9319 37         54 my $param = shift;
9320 37         68 my $icon_style = $param->{icon_style};
9321 37         64 my $total_icons = $param->{total_icons};
9322 37         55 my $icons = $param->{icons};
9323 37         48 my $i;
9324              
9325 37         52 my @attributes = ();
9326              
9327             # Don't set attribute for default style.
9328 37 100       81 if ( $icon_style ne '3TrafficLights' ) {
9329 36         76 @attributes = ( 'iconSet' => $icon_style );
9330             }
9331              
9332 37 50 66     111 if ( exists $param->{'icons_only'} && $param->{'icons_only'} ) {
9333 4         10 push @attributes, ( 'showValue' => 0 );
9334             }
9335              
9336 37 50 66     82 if ( exists $param->{'reverse_icons'} && $param->{'reverse_icons'} ) {
9337 6         14 push @attributes, ( 'reverse' => 1 );
9338             }
9339              
9340 37         99 $self->xml_start_tag( 'iconSet', @attributes );
9341              
9342             # Write the properites for different icon styles.
9343 37         56 for my $icon ( reverse @{ $param->{icons} } ) {
  37         85  
9344             $self->_write_cfvo(
9345             $icon->{'type'},
9346             $icon->{'value'},
9347 138         293 $icon->{'criteria'}
9348             );
9349             }
9350              
9351 37         94 $self->xml_end_tag( 'iconSet' );
9352             }
9353              
9354             ##############################################################################
9355             #
9356             # _write_formula()
9357             #
9358             # Write the element.
9359             #
9360             sub _write_formula {
9361              
9362 67     67   132 my $self = shift;
9363 67         127 my $data = shift;
9364              
9365             # Remove equality from formula.
9366 67         158 $data =~ s/^=//;
9367              
9368 67         316 $self->xml_data_element( 'formula', $data );
9369             }
9370              
9371              
9372             ##############################################################################
9373             #
9374             # _write_color_scale()
9375             #
9376             # Write the element.
9377             #
9378             sub _write_color_scale {
9379              
9380 5     5   19 my $self = shift;
9381 5         10 my $param = shift;
9382              
9383 5         18 $self->xml_start_tag( 'colorScale' );
9384              
9385 5         28 $self->_write_cfvo( $param->{min_type}, $param->{min_value} );
9386              
9387 5 100       23 if ( defined $param->{mid_type} ) {
9388 4         13 $self->_write_cfvo( $param->{mid_type}, $param->{mid_value} );
9389             }
9390              
9391 5         28 $self->_write_cfvo( $param->{max_type}, $param->{max_value} );
9392              
9393 5         27 $self->_write_color( 'rgb' => $param->{min_color} );
9394              
9395 5 100       20 if ( defined $param->{mid_color} ) {
9396 4         20 $self->_write_color( 'rgb' => $param->{mid_color} );
9397             }
9398              
9399 5         27 $self->_write_color( 'rgb' => $param->{max_color} );
9400              
9401 5         21 $self->xml_end_tag( 'colorScale' );
9402             }
9403              
9404              
9405             ##############################################################################
9406             #
9407             # _write_data_bar()
9408             #
9409             # Write the element.
9410             #
9411             sub _write_data_bar {
9412              
9413 29     29   68 my $self = shift;
9414 29         56 my $data_bar = shift;
9415 29         58 my @attributes = ();
9416              
9417 29 100       110 if ( $data_bar->{bar_only} ) {
9418 2         6 push @attributes, ( 'showValue', 0 );
9419             }
9420              
9421 29         129 $self->xml_start_tag( 'dataBar', @attributes );
9422              
9423 29         132 $self->_write_cfvo( $data_bar->{min_type}, $data_bar->{min_value} );
9424 29         116 $self->_write_cfvo( $data_bar->{max_type}, $data_bar->{max_value} );
9425              
9426 29         124 $self->_write_color( 'rgb' => $data_bar->{bar_color} );
9427              
9428 29         104 $self->xml_end_tag( 'dataBar' );
9429             }
9430              
9431              
9432             ##############################################################################
9433             #
9434             # _write_data_bar_ext()
9435             #
9436             # Write the dataBar extension element.
9437             #
9438             sub _write_data_bar_ext {
9439              
9440 25     25   60 my $self = shift;
9441 25         39 my $param = shift;
9442              
9443             # Create a pseudo GUID for each unique Excel 2010 data bar.
9444 25         57 my $worksheet_count = $self->{_index} + 1;
9445 25         41 my $data_bar_count = @{ $self->{_data_bars_2010} } + 1;
  25         67  
9446              
9447 25         139 my $guid = sprintf "{DA7ABA51-AAAA-BBBB-%04X-%012X}", $worksheet_count,
9448             $data_bar_count;
9449              
9450             # Store the 2010 data bar parameters to write the extLst elements.
9451 25         65 $param->{_guid} = $guid;
9452 25         60 push @{$self->{_data_bars_2010}}, $param;
  25         59  
9453              
9454 25         94 $self->xml_start_tag( 'extLst' );
9455 25         103 $self->_write_ext('{B025F937-C7B1-47D3-B67F-A62EFF666E3E}');
9456              
9457 25         162 $self->xml_data_element( 'x14:id', $guid);
9458              
9459 25         86 $self->xml_end_tag( 'ext' );
9460 25         70 $self->xml_end_tag( 'extLst' );
9461             }
9462              
9463              
9464             ##############################################################################
9465             #
9466             # _write_cfvo()
9467             #
9468             # Write the element.
9469             #
9470             sub _write_cfvo {
9471              
9472 210     210   319 my $self = shift;
9473 210         379 my $type = shift;
9474 210         295 my $value = shift;
9475 210         285 my $criteria = shift;
9476              
9477 210         412 my @attributes = ( 'type' => $type );
9478              
9479 210 100       468 if ( defined $value ) {
9480 169         312 push @attributes, ( 'val', $value );
9481             }
9482              
9483 210 100       401 if ( $criteria ) {
9484 7         17 push @attributes, ( 'gte', 0 );
9485             }
9486              
9487 210         500 $self->xml_empty_tag( 'cfvo', @attributes );
9488             }
9489              
9490              
9491             ##############################################################################
9492             #
9493             # _write_x14_cfvo()
9494             #
9495             # Write the element.
9496             #
9497             sub _write_x14_cfvo {
9498              
9499 50     50   81 my $self = shift;
9500 50         85 my $type = shift;
9501 50         86 my $value = shift;
9502              
9503 50         110 my @attributes = ( 'type' => $type );
9504              
9505 50 100 100     332 if ( $type eq 'min'
      100        
      100        
9506             || $type eq 'max'
9507             || $type eq 'autoMin'
9508             || $type eq 'autoMax' )
9509             {
9510 41         114 $self->xml_empty_tag( 'x14:cfvo', @attributes );
9511             }
9512             else {
9513 9         29 $self->xml_start_tag( 'x14:cfvo', @attributes );
9514 9         29 $self->xml_data_element( 'xm:f', $value );
9515 9         23 $self->xml_end_tag( 'x14:cfvo' );
9516             }
9517             }
9518              
9519              
9520             ##############################################################################
9521             #
9522             # _write_color()
9523             #
9524             # Write the element.
9525             #
9526             sub _write_color {
9527              
9528 43     43   81 my $self = shift;
9529 43         91 my $name = shift;
9530 43         74 my $value = shift;
9531              
9532 43         110 my @attributes = ( $name => $value );
9533              
9534 43         134 $self->xml_empty_tag( 'color', @attributes );
9535             }
9536              
9537              
9538             ##############################################################################
9539             #
9540             # _write_table_parts()
9541             #
9542             # Write the element.
9543             #
9544             sub _write_table_parts {
9545              
9546 993     993   2520 my $self = shift;
9547 993         2351 my @tables = @{ $self->{_tables} };
  993         3408  
9548 993         2608 my $count = scalar @tables;
9549              
9550             # Return if worksheet doesn't contain any tables.
9551 993 100       3687 return unless $count;
9552              
9553 27         119 my @attributes = ( 'count' => $count, );
9554              
9555 27         131 $self->xml_start_tag( 'tableParts', @attributes );
9556              
9557 27         95 for my $table ( @tables ) {
9558              
9559             # Write the tablePart element.
9560 35         143 $self->_write_table_part( ++$self->{_rel_count} );
9561              
9562             }
9563              
9564 27         113 $self->xml_end_tag( 'tableParts' );
9565             }
9566              
9567              
9568             ##############################################################################
9569             #
9570             # _write_table_part()
9571             #
9572             # Write the element.
9573             #
9574             sub _write_table_part {
9575              
9576 35     35   75 my $self = shift;
9577 35         76 my $id = shift;
9578 35         98 my $r_id = 'rId' . $id;
9579              
9580 35         127 my @attributes = ( 'r:id' => $r_id, );
9581              
9582 35         127 $self->xml_empty_tag( 'tablePart', @attributes );
9583             }
9584              
9585              
9586             ##############################################################################
9587             #
9588             # _write_ext_list()
9589             #
9590             # Write the element for data bars and sparklines.
9591             #
9592             sub _write_ext_list {
9593              
9594 993     993   2475 my $self = shift;
9595 993         2118 my $has_data_bars = scalar @{ $self->{_data_bars_2010} };
  993         3166  
9596 993         2124 my $has_sparklines = scalar @{ $self->{_sparklines} };
  993         2637  
9597              
9598 993 100 100     6921 if ( !$has_data_bars and !$has_sparklines ) {
9599 971         3678 return;
9600             }
9601              
9602             # Write the extLst element.
9603 22         104 $self->xml_start_tag( 'extLst' );
9604              
9605 22 100       83 if ( $has_data_bars ) {
9606 11         52 $self->_write_ext_list_data_bars();
9607             }
9608              
9609 22 100       112 if ( $has_sparklines ) {
9610 12         52 $self->_write_ext_list_sparklines();
9611             }
9612              
9613 22         257 $self->xml_end_tag( 'extLst' );
9614             }
9615              
9616              
9617             ##############################################################################
9618             #
9619             # _write_ext_list_data_bars()
9620             #
9621             # Write the Excel 2010 data_bar subelements.
9622             #
9623             sub _write_ext_list_data_bars {
9624              
9625 11     11   35 my $self = shift;
9626 11         20 my @data_bars = @{ $self->{_data_bars_2010} };
  11         49  
9627              
9628             # Write the ext element.
9629 11         51 $self->_write_ext('{78C0D931-6437-407d-A8EE-F0AAD7539E65}');
9630              
9631              
9632 11         49 $self->xml_start_tag( 'x14:conditionalFormattings' );
9633              
9634             # Write each of the Excel 2010 conditional formatting data bar elements.
9635 11         39 for my $data_bar (@data_bars) {
9636              
9637             # Write the x14:conditionalFormatting element.
9638 25         78 $self->_write_conditional_formatting_2010($data_bar);
9639             }
9640              
9641 11         69 $self->xml_end_tag( 'x14:conditionalFormattings' );
9642 11         41 $self->xml_end_tag( 'ext' );
9643              
9644              
9645             }
9646              
9647              
9648             ##############################################################################
9649             #
9650             # _write_conditional_formatting()
9651             #
9652             # Write the element.
9653             #
9654             sub _write_conditional_formatting_2010 {
9655              
9656 25     25   53 my $self = shift;
9657 25         53 my $data_bar = shift;
9658 25         45 my $xmlns_xm = 'http://schemas.microsoft.com/office/excel/2006/main';
9659              
9660 25         58 my @attributes = ( 'xmlns:xm' => $xmlns_xm );
9661              
9662 25         96 $self->xml_start_tag( 'x14:conditionalFormatting', @attributes );
9663              
9664             # Write the '
9665 25         126 $self->_write_x14_cf_rule( $data_bar );
9666              
9667             # Write the x14:dataBar element.
9668 25         114 $self->_write_x14_data_bar( $data_bar );
9669              
9670             # Write the x14 max and min data bars.
9671             $self->_write_x14_cfvo( $data_bar->{_x14_min_type},
9672 25         105 $data_bar->{min_value} );
9673              
9674             $self->_write_x14_cfvo( $data_bar->{_x14_max_type},
9675 25         107 $data_bar->{max_value} );
9676              
9677             # Write the x14:borderColor element.
9678 25 100       84 if ( !$data_bar->{bar_no_border} ) {
9679 24         94 $self->_write_x14_border_color( $data_bar->{bar_border_color} );
9680             }
9681              
9682             # Write the x14:negativeFillColor element.
9683 25 100       91 if ( !$data_bar->{bar_negative_color_same} ) {
9684             $self->_write_x14_negative_fill_color(
9685 24         128 $data_bar->{bar_negative_color} );
9686             }
9687              
9688             # Write the x14:negativeBorderColor element.
9689 25 100 100     164 if ( !$data_bar->{bar_no_border}
9690             && !$data_bar->{bar_negative_border_color_same} )
9691             {
9692             $self->_write_x14_negative_border_color(
9693 23         82 $data_bar->{bar_negative_border_color} );
9694             }
9695              
9696             # Write the x14:axisColor element.
9697 25 100       102 if ( $data_bar->{bar_axis_position} ne 'none') {
9698 24         75 $self->_write_x14_axis_color($data_bar->{bar_axis_color});
9699             }
9700              
9701             # Write closing elements.
9702 25         141 $self->xml_end_tag( 'x14:dataBar' );
9703 25         84 $self->xml_end_tag( 'x14:cfRule' );
9704              
9705             # Add the conditional format range.
9706 25         241 $self->xml_data_element( 'xm:sqref', $data_bar->{_range} );
9707              
9708 25         84 $self->xml_end_tag( 'x14:conditionalFormatting' );
9709             }
9710              
9711              
9712             ##############################################################################
9713             #
9714             # _write_x14_cf_rule()
9715             #
9716             # Write the <' element.
9717             #
9718             sub _write_x14_cf_rule {
9719              
9720 25     25   58 my $self = shift;
9721 25         42 my $data_bar = shift;
9722 25         46 my $type = 'dataBar';
9723 25         65 my $id = $data_bar->{_guid};
9724              
9725 25         81 my @attributes = (
9726             'type' => $type,
9727             'id' => $id,
9728             );
9729              
9730 25         75 $self->xml_start_tag( 'x14:cfRule', @attributes );
9731              
9732             }
9733              
9734              
9735             ##############################################################################
9736             #
9737             # _write_x14_data_bar()
9738             #
9739             # Write the element.
9740             #
9741             sub _write_x14_data_bar {
9742              
9743 25     25   51 my $self = shift;
9744 25         49 my $data_bar = shift;
9745 25         47 my $min_length = 0;
9746 25         41 my $max_length = 100;
9747              
9748 25         69 my @attributes = (
9749             'minLength' => $min_length,
9750             'maxLength' => $max_length,
9751             );
9752              
9753 25 100       70 if ( !$data_bar->{bar_no_border} ) {
9754 24         66 push @attributes, ( 'border', 1 );
9755             }
9756              
9757 25 100       78 if ( $data_bar->{bar_solid} ) {
9758 1         3 push @attributes, ( 'gradient', 0 );
9759             }
9760              
9761 25 100       88 if ( $data_bar->{bar_direction} eq 'left' ) {
9762 1         3 push @attributes, ( 'direction', 'leftToRight' );
9763             }
9764              
9765 25 100       127 if ( $data_bar->{bar_direction} eq 'right' ) {
9766 1         2 push @attributes, ( 'direction', 'rightToLeft' );
9767             }
9768              
9769 25 100       86 if ( $data_bar->{bar_negative_color_same} ) {
9770 1         3 push @attributes, ( 'negativeBarColorSameAsPositive', 1 );
9771             }
9772              
9773 25 100 100     133 if ( !$data_bar->{bar_no_border}
9774             && !$data_bar->{bar_negative_border_color_same} )
9775             {
9776 23         67 push @attributes, ( 'negativeBarBorderColorSameAsPositive', 0 );
9777             }
9778              
9779 25 100       83 if ( $data_bar->{bar_axis_position} eq 'middle') {
9780 1         3 push @attributes, ( 'axisPosition', 'middle' );
9781             }
9782              
9783 25 100       150 if ( $data_bar->{bar_axis_position} eq 'none') {
9784 1         3 push @attributes, ( 'axisPosition', 'none' );
9785             }
9786              
9787 25         89 $self->xml_start_tag( 'x14:dataBar', @attributes );
9788             }
9789              
9790              
9791             ##############################################################################
9792             #
9793             # _write_x14_border_color()
9794             #
9795             # Write the element.
9796             #
9797             sub _write_x14_border_color {
9798              
9799 24     24   61 my $self = shift;
9800 24         55 my $rgb = shift;
9801              
9802 24         62 my @attributes = ( 'rgb' => $rgb );
9803              
9804 24         100 $self->xml_empty_tag( 'x14:borderColor', @attributes );
9805             }
9806              
9807              
9808             ##############################################################################
9809             #
9810             # _write_x14_negative_fill_color()
9811             #
9812             # Write the element.
9813             #
9814             sub _write_x14_negative_fill_color {
9815              
9816 24     24   53 my $self = shift;
9817 24         43 my $rgb = shift;
9818              
9819 24         61 my @attributes = ( 'rgb' => $rgb );
9820              
9821 24         82 $self->xml_empty_tag( 'x14:negativeFillColor', @attributes );
9822             }
9823              
9824              
9825             ##############################################################################
9826             #
9827             # _write_x14_negative_border_color()
9828             #
9829             # Write the element.
9830             #
9831             sub _write_x14_negative_border_color {
9832              
9833 23     23   43 my $self = shift;
9834 23         42 my $rgb = shift;
9835              
9836 23         58 my @attributes = ( 'rgb' => $rgb );
9837              
9838 23         84 $self->xml_empty_tag( 'x14:negativeBorderColor', @attributes );
9839             }
9840              
9841              
9842             ##############################################################################
9843             #
9844             # _write_x14_axis_color()
9845             #
9846             # Write the element.
9847             #
9848             sub _write_x14_axis_color {
9849              
9850 24     24   49 my $self = shift;
9851 24         41 my $rgb = shift;
9852              
9853 24         54 my @attributes = ( 'rgb' => $rgb );
9854              
9855 24         92 $self->xml_empty_tag( 'x14:axisColor', @attributes );
9856             }
9857              
9858              
9859             ##############################################################################
9860             #
9861             # _write_ext_list_sparklines()
9862             #
9863             # Write the sparkline subelements.
9864             #
9865             sub _write_ext_list_sparklines {
9866              
9867 12     12   29 my $self = shift;
9868 12         21 my @sparklines = @{ $self->{_sparklines} };
  12         41  
9869 12         27 my $count = scalar @sparklines;
9870              
9871             # Write the ext element.
9872 12         57 $self->_write_ext('{05C60535-1F16-4fd2-B633-F4F36F0B64E0}');
9873              
9874             # Write the x14:sparklineGroups element.
9875 12         55 $self->_write_sparkline_groups();
9876              
9877             # Write the sparkline elements.
9878 12         39 for my $sparkline ( reverse @sparklines ) {
9879              
9880             # Write the x14:sparklineGroup element.
9881 58         173 $self->_write_sparkline_group( $sparkline );
9882              
9883             # Write the x14:colorSeries element.
9884 58         219 $self->_write_color_series( $sparkline->{_series_color} );
9885              
9886             # Write the x14:colorNegative element.
9887 58         184 $self->_write_color_negative( $sparkline->{_negative_color} );
9888              
9889             # Write the x14:colorAxis element.
9890 58         150 $self->_write_color_axis();
9891              
9892             # Write the x14:colorMarkers element.
9893 58         183 $self->_write_color_markers( $sparkline->{_markers_color} );
9894              
9895             # Write the x14:colorFirst element.
9896 58         153 $self->_write_color_first( $sparkline->{_first_color} );
9897              
9898             # Write the x14:colorLast element.
9899 58         168 $self->_write_color_last( $sparkline->{_last_color} );
9900              
9901             # Write the x14:colorHigh element.
9902 58         146 $self->_write_color_high( $sparkline->{_high_color} );
9903              
9904             # Write the x14:colorLow element.
9905 58         175 $self->_write_color_low( $sparkline->{_low_color} );
9906              
9907 58 100       187 if ( $sparkline->{_date_axis} ) {
9908 1         7 $self->xml_data_element( 'xm:f', $sparkline->{_date_axis} );
9909             }
9910              
9911 58         154 $self->_write_sparklines( $sparkline );
9912              
9913 58         112 $self->xml_end_tag( 'x14:sparklineGroup' );
9914             }
9915              
9916              
9917 12         56 $self->xml_end_tag( 'x14:sparklineGroups' );
9918 12         98 $self->xml_end_tag( 'ext' );
9919             }
9920              
9921              
9922             ##############################################################################
9923             #
9924             # _write_sparklines()
9925             #
9926             # Write the element and subelements.
9927             #
9928             sub _write_sparklines {
9929              
9930 58     58   112 my $self = shift;
9931 58         101 my $sparkline = shift;
9932              
9933             # Write the sparkline elements.
9934 58         177 $self->xml_start_tag( 'x14:sparklines' );
9935              
9936 58         173 for my $i ( 0 .. $sparkline->{_count} - 1 ) {
9937 59         107 my $range = $sparkline->{_ranges}->[$i];
9938 59         114 my $location = $sparkline->{_locations}->[$i];
9939              
9940 59         163 $self->xml_start_tag( 'x14:sparkline' );
9941 59         200 $self->xml_data_element( 'xm:f', $range );
9942 59         147 $self->xml_data_element( 'xm:sqref', $location );
9943 59         137 $self->xml_end_tag( 'x14:sparkline' );
9944             }
9945              
9946              
9947 58         127 $self->xml_end_tag( 'x14:sparklines' );
9948             }
9949              
9950              
9951             ##############################################################################
9952             #
9953             # _write_ext()
9954             #
9955             # Write the element for sparklines.
9956             #
9957             sub _write_ext {
9958              
9959 48     48   105 my $self = shift;
9960 48         87 my $uri = shift;
9961 48         91 my $schema = 'http://schemas.microsoft.com/office/';
9962 48         128 my $xmlns_x14 = $schema . 'spreadsheetml/2009/9/main';
9963              
9964 48         145 my @attributes = (
9965             'xmlns:x14' => $xmlns_x14,
9966             'uri' => $uri,
9967             );
9968              
9969 48         173 $self->xml_start_tag( 'ext', @attributes );
9970             }
9971              
9972              
9973             ##############################################################################
9974             #
9975             # _write_sparkline_groups()
9976             #
9977             # Write the element.
9978             #
9979             sub _write_sparkline_groups {
9980              
9981 12     12   28 my $self = shift;
9982 12         23 my $xmlns_xm = 'http://schemas.microsoft.com/office/excel/2006/main';
9983              
9984 12         40 my @attributes = ( 'xmlns:xm' => $xmlns_xm );
9985              
9986 12         56 $self->xml_start_tag( 'x14:sparklineGroups', @attributes );
9987              
9988             }
9989              
9990              
9991             ##############################################################################
9992             #
9993             # _write_sparkline_group()
9994             #
9995             # Write the element.
9996             #
9997             # Example for order.
9998             #
9999             #
10000             # manualMax="0"
10001             # manualMin="0"
10002             # lineWeight="2.25"
10003             # type="column"
10004             # dateAxis="1"
10005             # displayEmptyCellsAs="span"
10006             # markers="1"
10007             # high="1"
10008             # low="1"
10009             # first="1"
10010             # last="1"
10011             # negative="1"
10012             # displayXAxis="1"
10013             # displayHidden="1"
10014             # minAxisType="custom"
10015             # maxAxisType="custom"
10016             # rightToLeft="1">
10017             #
10018             sub _write_sparkline_group {
10019              
10020 58     58   97 my $self = shift;
10021 58         87 my $opts = shift;
10022 58         91 my $empty = $opts->{_empty};
10023 58         97 my $user_max = 0;
10024 58         81 my $user_min = 0;
10025 58         74 my @a;
10026              
10027 58 100       127 if ( defined $opts->{_max} ) {
10028              
10029 4 100       14 if ( $opts->{_max} eq 'group' ) {
10030 2         5 $opts->{_cust_max} = 'group';
10031             }
10032             else {
10033 2         5 push @a, ( 'manualMax' => $opts->{_max} );
10034 2         5 $opts->{_cust_max} = 'custom';
10035             }
10036             }
10037              
10038 58 100       127 if ( defined $opts->{_min} ) {
10039              
10040 4 100       12 if ( $opts->{_min} eq 'group' ) {
10041 1         2 $opts->{_cust_min} = 'group';
10042             }
10043             else {
10044 3         7 push @a, ( 'manualMin' => $opts->{_min} );
10045 3         7 $opts->{_cust_min} = 'custom';
10046             }
10047             }
10048              
10049              
10050             # Ignore the default type attribute (line).
10051 58 100       139 if ( $opts->{_type} ne 'line' ) {
10052 9         22 push @a, ( 'type' => $opts->{_type} );
10053             }
10054              
10055 58 100       125 push @a, ( 'lineWeight' => $opts->{_weight} ) if $opts->{_weight};
10056 58 100       132 push @a, ( 'dateAxis' => 1 ) if $opts->{_date_axis};
10057 58 100       137 push @a, ( 'displayEmptyCellsAs' => $empty ) if $empty;
10058              
10059 58 100       116 push @a, ( 'markers' => 1 ) if $opts->{_markers};
10060 58 100       125 push @a, ( 'high' => 1 ) if $opts->{_high};
10061 58 100       152 push @a, ( 'low' => 1 ) if $opts->{_low};
10062 58 100       118 push @a, ( 'first' => 1 ) if $opts->{_first};
10063 58 100       115 push @a, ( 'last' => 1 ) if $opts->{_last};
10064 58 100       115 push @a, ( 'negative' => 1 ) if $opts->{_negative};
10065 58 100       114 push @a, ( 'displayXAxis' => 1 ) if $opts->{_axis};
10066 58 100       118 push @a, ( 'displayHidden' => 1 ) if $opts->{_hidden};
10067 58 100       118 push @a, ( 'minAxisType' => $opts->{_cust_min} ) if $opts->{_cust_min};
10068 58 100       125 push @a, ( 'maxAxisType' => $opts->{_cust_max} ) if $opts->{_cust_max};
10069 58 100       113 push @a, ( 'rightToLeft' => 1 ) if $opts->{_reverse};
10070              
10071 58         171 $self->xml_start_tag( 'x14:sparklineGroup', @a );
10072             }
10073              
10074              
10075             ##############################################################################
10076             #
10077             # _write_spark_color()
10078             #
10079             # Helper function for the sparkline color functions below.
10080             #
10081             sub _write_spark_color {
10082              
10083 464     464   583 my $self = shift;
10084 464         571 my $element = shift;
10085 464         541 my $color = shift;
10086 464         529 my @attr;
10087              
10088 464 100       945 push @attr, ( 'rgb' => $color->{_rgb} ) if defined $color->{_rgb};
10089 464 100       963 push @attr, ( 'theme' => $color->{_theme} ) if defined $color->{_theme};
10090 464 100       798 push @attr, ( 'tint' => $color->{_tint} ) if defined $color->{_tint};
10091              
10092 464         898 $self->xml_empty_tag( $element, @attr );
10093             }
10094              
10095              
10096             ##############################################################################
10097             #
10098             # _write_color_series()
10099             #
10100             # Write the element.
10101             #
10102             sub _write_color_series {
10103              
10104 58     58   90 my $self = shift;
10105              
10106 58         141 $self->_write_spark_color( 'x14:colorSeries', @_ );
10107             }
10108              
10109              
10110             ##############################################################################
10111             #
10112             # _write_color_negative()
10113             #
10114             # Write the element.
10115             #
10116             sub _write_color_negative {
10117              
10118 58     58   93 my $self = shift;
10119              
10120 58         109 $self->_write_spark_color( 'x14:colorNegative', @_ );
10121             }
10122              
10123              
10124             ##############################################################################
10125             #
10126             # _write_color_axis()
10127             #
10128             # Write the element.
10129             #
10130             sub _write_color_axis {
10131              
10132 58     58   82 my $self = shift;
10133              
10134 58         143 $self->_write_spark_color( 'x14:colorAxis', { _rgb => 'FF000000' } );
10135             }
10136              
10137              
10138             ##############################################################################
10139             #
10140             # _write_color_markers()
10141             #
10142             # Write the element.
10143             #
10144             sub _write_color_markers {
10145              
10146 58     58   87 my $self = shift;
10147              
10148 58         107 $self->_write_spark_color( 'x14:colorMarkers', @_ );
10149             }
10150              
10151              
10152             ##############################################################################
10153             #
10154             # _write_color_first()
10155             #
10156             # Write the element.
10157             #
10158             sub _write_color_first {
10159              
10160 58     58   95 my $self = shift;
10161              
10162 58         115 $self->_write_spark_color( 'x14:colorFirst', @_ );
10163             }
10164              
10165              
10166             ##############################################################################
10167             #
10168             # _write_color_last()
10169             #
10170             # Write the element.
10171             #
10172             sub _write_color_last {
10173              
10174 58     58   80 my $self = shift;
10175              
10176 58         104 $self->_write_spark_color( 'x14:colorLast', @_ );
10177             }
10178              
10179              
10180             ##############################################################################
10181             #
10182             # _write_color_high()
10183             #
10184             # Write the element.
10185             #
10186             sub _write_color_high {
10187              
10188 58     58   86 my $self = shift;
10189              
10190 58         114 $self->_write_spark_color( 'x14:colorHigh', @_ );
10191             }
10192              
10193              
10194             ##############################################################################
10195             #
10196             # _write_color_low()
10197             #
10198             # Write the element.
10199             #
10200             sub _write_color_low {
10201              
10202 58     58   77 my $self = shift;
10203              
10204 58         129 $self->_write_spark_color( 'x14:colorLow', @_ );
10205             }
10206              
10207              
10208             1;
10209              
10210              
10211             __END__