File Coverage

blib/lib/Excel/Writer/XLSX/Workbook.pm
Criterion Covered Total %
statement 979 1039 94.2
branch 272 324 83.9
condition 112 146 76.7
subroutine 81 88 92.0
pod 0 26 0.0
total 1444 1623 88.9


line stmt bran cond sub pod time code
1             package Excel::Writer::XLSX::Workbook;
2              
3             ###############################################################################
4             #
5             # Workbook - A class for writing Excel Workbooks.
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 1040     1040   16983 use 5.008002;
  1040         3535  
18 1040     1040   5689 use strict;
  1040         2071  
  1040         21692  
19 1040     1040   5174 use warnings;
  1040         2294  
  1040         25825  
20 1040     1040   5688 use Carp;
  1040         2259  
  1040         67450  
21 1040     1040   491329 use IO::File;
  1040         8553633  
  1040         117278  
22 1040     1040   8027 use File::Find;
  1040         2193  
  1040         70759  
23 1040     1040   753087 use File::Temp qw(tempfile);
  1040         13717456  
  1040         66732  
24 1040     1040   8441 use File::Basename 'fileparse';
  1040         2312  
  1040         87546  
25 1040     1040   662176 use Archive::Zip;
  1040         62926768  
  1040         53491  
26 1040     1040   9344 use Digest::MD5 qw(md5_hex);
  1040         2494  
  1040         68781  
27 1040     1040   1441076 use Excel::Writer::XLSX::Worksheet;
  1040         6955  
  1040         146096  
28 1040     1040   608258 use Excel::Writer::XLSX::Chartsheet;
  1040         3154  
  1040         51363  
29 1040     1040   7529 use Excel::Writer::XLSX::Format;
  1040         2228  
  1040         32893  
30 1040     1040   484489 use Excel::Writer::XLSX::Shape;
  1040         3045  
  1040         48385  
31 1040     1040   1164319 use Excel::Writer::XLSX::Chart;
  1040         4677  
  1040         74790  
32 1040     1040   619262 use Excel::Writer::XLSX::Package::Packager;
  1040         5199  
  1040         69161  
33 1040     1040   9501 use Excel::Writer::XLSX::Package::XMLwriter;
  1040         3544  
  1040         44762  
34 1040     1040   9352 use Excel::Writer::XLSX::Utility qw(xl_cell_to_rowcol xl_rowcol_to_cell);
  1040         2158  
  1040         10510830  
35              
36             our @ISA = qw(Excel::Writer::XLSX::Package::XMLwriter);
37             our $VERSION = '1.03';
38              
39              
40             ###############################################################################
41             #
42             # Public and private API methods.
43             #
44             ###############################################################################
45              
46              
47             ###############################################################################
48             #
49             # new()
50             #
51             # Constructor.
52             #
53             sub new {
54              
55 809     809 0 2488 my $class = shift;
56 809         7257 my $self = Excel::Writer::XLSX::Package::XMLwriter->new();
57              
58 809   50     8132 $self->{_filename} = $_[0] || '';
59 809   100     5883 my $options = $_[1] || {};
60              
61 809         2885 $self->{_tempdir} = undef;
62 809         2763 $self->{_date_1904} = 0;
63 809         2251 $self->{_activesheet} = 0;
64 809         2279 $self->{_firstsheet} = 0;
65 809         2181 $self->{_selected} = 0;
66 809         2761 $self->{_fileclosed} = 0;
67 809         2158 $self->{_filehandle} = undef;
68 809         2309 $self->{_internal_fh} = 0;
69 809         2377 $self->{_sheet_name} = 'Sheet';
70 809         2384 $self->{_chart_name} = 'Chart';
71 809         2102 $self->{_sheetname_count} = 0;
72 809         2038 $self->{_chartname_count} = 0;
73 809         2292 $self->{_worksheets} = [];
74 809         3106 $self->{_charts} = [];
75 809         2624 $self->{_drawings} = [];
76 809         2432 $self->{_sheetnames} = {};
77 809         2291 $self->{_formats} = [];
78 809         2273 $self->{_xf_formats} = [];
79 809         2333 $self->{_xf_format_indices} = {};
80 809         2269 $self->{_dxf_formats} = [];
81 809         2295 $self->{_dxf_format_indices} = {};
82 809         2326 $self->{_palette} = [];
83 809         2192 $self->{_font_count} = 0;
84 809         2219 $self->{_num_format_count} = 0;
85 809         2306 $self->{_defined_names} = [];
86 809         2284 $self->{_named_ranges} = [];
87 809         2301 $self->{_custom_colors} = [];
88 809         2300 $self->{_doc_properties} = {};
89 809         2374 $self->{_custom_properties} = [];
90 809         7896 $self->{_createtime} = [ gmtime() ];
91 809         2784 $self->{_num_vml_files} = 0;
92 809         2181 $self->{_num_comment_files} = 0;
93 809         2102 $self->{_optimization} = 0;
94 809         2296 $self->{_x_window} = 240;
95 809         1890 $self->{_y_window} = 15;
96 809         1870 $self->{_window_width} = 16095;
97 809         1863 $self->{_window_height} = 9660;
98 809         1853 $self->{_tab_ratio} = 600;
99 809         2004 $self->{_excel2003_style} = 0;
100 809         2089 $self->{_max_url_length} = 2079;
101              
102 809         2100 $self->{_default_format_properties} = {};
103              
104 809 50       3313 if ( exists $options->{tempdir} ) {
105 0         0 $self->{_tempdir} = $options->{tempdir};
106             }
107              
108 809 50       3196 if ( exists $options->{date_1904} ) {
109 0         0 $self->{_date_1904} = $options->{date_1904};
110             }
111              
112 809 50       2926 if ( exists $options->{optimization} ) {
113 0         0 $self->{_optimization} = $options->{optimization};
114             }
115              
116 809 100       2846 if ( exists $options->{default_format_properties} ) {
117             $self->{_default_format_properties} =
118 1         4 $options->{default_format_properties};
119             }
120              
121 809 100       3642 if ( exists $options->{excel2003_style} ) {
122 8         26 $self->{_excel2003_style} = 1;
123             }
124              
125 809 100       3149 if ( exists $options->{max_url_length} ) {
126 1         3 $self->{_max_url_length} = $options->{max_url_length};
127              
128 1 50       4 if ($self->{_max_url_length} < 255) {
129 0         0 $self->{_max_url_length} = 2079;
130             }
131             }
132              
133             # Structures for the shared strings data.
134 809         2382 $self->{_str_total} = 0;
135 809         2261 $self->{_str_unique} = 0;
136 809         2180 $self->{_str_table} = {};
137 809         2243 $self->{_str_array} = [];
138              
139             # Formula calculation default settings.
140 809         2021 $self->{_calc_id} = 124519;
141 809         2188 $self->{_calc_mode} = 'auto';
142 809         1942 $self->{_calc_on_load} = 1;
143              
144              
145 809         2180 bless $self, $class;
146              
147             # Add the default cell format.
148 809 100       5916 if ( $self->{_excel2003_style} ) {
149 8         46 $self->add_format( xf_index => 0, font_family => 0 );
150             }
151             else {
152 801         4266 $self->add_format( xf_index => 0 );
153             }
154              
155             # Add a default URL format.
156 809         3624 $self->{_default_url_format} = $self->add_format( hyperlink => 1 );
157              
158             # Check for a filename unless it is an existing filehandle
159 809 50 66     7129 if ( not ref $self->{_filename} and $self->{_filename} eq '' ) {
160 0         0 carp 'Filename required by Excel::Writer::XLSX->new()';
161 0         0 return undef;
162             }
163              
164              
165             # If filename is a reference we assume that it is a valid filehandle.
166 809 100       5627 if ( ref $self->{_filename} ) {
    50          
167              
168 55         166 $self->{_filehandle} = $self->{_filename};
169 55         145 $self->{_internal_fh} = 0;
170             }
171             elsif ( $self->{_filename} eq '-' ) {
172              
173             # Support special filename/filehandle '-' for backward compatibility.
174 0         0 binmode STDOUT;
175 0         0 $self->{_filehandle} = \*STDOUT;
176 0         0 $self->{_internal_fh} = 0;
177             }
178             else {
179 754         7464 my $fh = IO::File->new( $self->{_filename}, 'w' );
180              
181 754 50       210104 return undef unless defined $fh;
182              
183 754         2920 $self->{_filehandle} = $fh;
184 754         2492 $self->{_internal_fh} = 1;
185             }
186              
187              
188             # Set colour palette.
189 809         4785 $self->set_color_palette();
190              
191 809         3253 return $self;
192             }
193              
194              
195             ###############################################################################
196             #
197             # _assemble_xml_file()
198             #
199             # Assemble and write the XML file.
200             #
201             sub _assemble_xml_file {
202              
203 812     812   3915 my $self = shift;
204              
205             # Prepare format object for passing to Style.pm.
206 812         6845 $self->_prepare_format_properties();
207              
208 812         7276 $self->xml_declaration;
209              
210             # Write the root workbook element.
211 812         7022 $self->_write_workbook();
212              
213             # Write the XLSX file version.
214 812         5640 $self->_write_file_version();
215              
216             # Write the workbook properties.
217 812         5514 $self->_write_workbook_pr();
218              
219             # Write the workbook view properties.
220 812         5587 $self->_write_book_views();
221              
222             # Write the worksheet names and ids.
223 812         6169 $self->_write_sheets();
224              
225             # Write the workbook defined names.
226 812         6344 $self->_write_defined_names();
227              
228             # Write the workbook calculation properties.
229 812         5845 $self->_write_calc_pr();
230              
231             # Write the workbook extension storage.
232             #$self->_write_ext_lst();
233              
234             # Close the workbook tag.
235 812         3777 $self->xml_end_tag( 'workbook' );
236              
237             # Close the XML writer filehandle.
238 812         6004 $self->xml_get_fh()->close();
239             }
240              
241              
242             ###############################################################################
243             #
244             # close()
245             #
246             # Calls finalization methods.
247             #
248             sub close {
249              
250 809     809 0 10500 my $self = shift;
251              
252             # In case close() is called twice, by user and by DESTROY.
253 809 50       4394 return if $self->{_fileclosed};
254              
255             # Test filehandle in case new() failed and the user didn't check.
256 809 50       3760 return undef if !defined $self->{_filehandle};
257              
258 809         2597 $self->{_fileclosed} = 1;
259 809         6237 $self->_store_workbook();
260              
261             # Return the file close value.
262 809 100       17620150 if ( $self->{_internal_fh} ) {
263 754         5773 return $self->{_filehandle}->close();
264             }
265             else {
266             # Return true and let users deal with their own filehandles.
267 55         2773 return 1;
268             }
269             }
270              
271              
272             ###############################################################################
273             #
274             # DESTROY()
275             #
276             # Close the workbook if it hasn't already been explicitly closed.
277             #
278             sub DESTROY {
279              
280 809     809   30700453 my $self = shift;
281              
282 809         8189 local ( $@, $!, $^E, $? );
283              
284 809 100       69746 $self->close() if not $self->{_fileclosed};
285             }
286              
287              
288             ###############################################################################
289             #
290             # sheets(slice,...)
291             #
292             # An accessor for the _worksheets[] array
293             #
294             # Returns: an optionally sliced list of the worksheet objects in a workbook.
295             #
296             sub sheets {
297              
298 9     9 0 55 my $self = shift;
299              
300 9 50       31 if ( @_ ) {
301              
302             # Return a slice of the array
303 0         0 return @{ $self->{_worksheets} }[@_];
  0         0  
304             }
305             else {
306              
307             # Return the entire list
308 9         19 return @{ $self->{_worksheets} };
  9         85  
309             }
310             }
311              
312              
313             ###############################################################################
314             #
315             # get_worksheet_by_name(name)
316             #
317             # Return a worksheet object in the workbook using the sheetname.
318             #
319             sub get_worksheet_by_name {
320              
321 4     4 0 798 my $self = shift;
322 4         6 my $sheetname = shift;
323              
324 4 100       12 return undef if not defined $sheetname;
325              
326 3         7 return $self->{_sheetnames}->{$sheetname};
327             }
328              
329              
330             ###############################################################################
331             #
332             # worksheets()
333             #
334             # An accessor for the _worksheets[] array.
335             # This method is now deprecated. Use the sheets() method instead.
336             #
337             # Returns: an array reference
338             #
339             sub worksheets {
340              
341 0     0 0 0 my $self = shift;
342              
343 0         0 return $self->{_worksheets};
344             }
345              
346              
347             ###############################################################################
348             #
349             # add_worksheet($name)
350             #
351             # Add a new worksheet to the Excel workbook.
352             #
353             # Returns: reference to a worksheet object
354             #
355             sub add_worksheet {
356              
357 952     952 0 13016 my $self = shift;
358 952         2484 my $index = @{ $self->{_worksheets} };
  952         7389  
359 952         7320 my $name = $self->_check_sheetname( $_[0] );
360 936         2863 my $fh = undef;
361              
362             # Porters take note, the following scheme of passing references to Workbook
363             # data (in the \$self->{_foo} cases) instead of a reference to the Workbook
364             # itself is a workaround to avoid circular references between Workbook and
365             # Worksheet objects. Feel free to implement this in any way the suits your
366             # language.
367             #
368             my @init_data = (
369             $fh,
370             $name,
371             $index,
372              
373             \$self->{_activesheet},
374             \$self->{_firstsheet},
375              
376             \$self->{_str_total},
377             \$self->{_str_unique},
378             \$self->{_str_table},
379              
380             $self->{_date_1904},
381             $self->{_palette},
382             $self->{_optimization},
383             $self->{_tempdir},
384             $self->{_excel2003_style},
385             $self->{_default_url_format},
386             $self->{_max_url_length},
387 936         7263 );
388              
389 936         9071 my $worksheet = Excel::Writer::XLSX::Worksheet->new( @init_data );
390 936         3148 $self->{_worksheets}->[$index] = $worksheet;
391 936         3245 $self->{_sheetnames}->{$name} = $worksheet;
392              
393 936         4314 return $worksheet;
394             }
395              
396              
397             ###############################################################################
398             #
399             # add_chart( %args )
400             #
401             # Create a chart for embedding or as a new sheet.
402             #
403             sub add_chart {
404              
405 405     405 0 3942 my $self = shift;
406 405         2121 my %arg = @_;
407 405         1059 my $name = '';
408 405         884 my $index = @{ $self->{_worksheets} };
  405         1227  
409 405         998 my $fh = undef;
410              
411             # Type must be specified so we can create the required chart instance.
412 405         1012 my $type = $arg{type};
413 405 50       1672 if ( !defined $type ) {
414 0         0 croak "Must define chart type in add_chart()";
415             }
416              
417             # Ensure that the chart defaults to non embedded.
418 405   100     1655 my $embedded = $arg{embedded} || 0;
419              
420             # Check the worksheet name for non-embedded charts.
421 405 100       1456 if ( !$embedded ) {
422 20         102 $name = $self->_check_sheetname( $arg{name}, 1 );
423             }
424              
425              
426             my @init_data = (
427              
428             $fh,
429             $name,
430             $index,
431              
432             \$self->{_activesheet},
433             \$self->{_firstsheet},
434              
435             \$self->{_str_total},
436             \$self->{_str_unique},
437             \$self->{_str_table},
438              
439             $self->{_date_1904},
440             $self->{_palette},
441             $self->{_optimization},
442 405         2591 );
443              
444              
445 405         3817 my $chart = Excel::Writer::XLSX::Chart->factory( $type, $arg{subtype} );
446              
447             # If the chart isn't embedded let the workbook control it.
448 405 100       1890 if ( !$embedded ) {
449              
450 20         179 my $drawing = Excel::Writer::XLSX::Drawing->new();
451 20         160 my $chartsheet = Excel::Writer::XLSX::Chartsheet->new( @init_data );
452              
453 20         152 $chart->{_palette} = $self->{_palette};
454              
455 20         104 $chartsheet->{_chart} = $chart;
456 20         79 $chartsheet->{_drawing} = $drawing;
457              
458 20         56 $self->{_worksheets}->[$index] = $chartsheet;
459 20         56 $self->{_sheetnames}->{$name} = $chartsheet;
460              
461 20         44 push @{ $self->{_charts} }, $chart;
  20         114  
462              
463 20         112 return $chartsheet;
464             }
465             else {
466              
467             # Set the embedded chart name if present.
468 385 100       1376 $chart->{_chart_name} = $arg{name} if $arg{name};
469              
470             # Set index to 0 so that the activate() and set_first_sheet() methods
471             # point back to the first worksheet if used for embedded charts.
472 385         2483 $chart->{_index} = 0;
473 385         1066 $chart->{_palette} = $self->{_palette};
474 385         2823 $chart->_set_embedded_config_data();
475 385         761 push @{ $self->{_charts} }, $chart;
  385         1357  
476              
477 385         1961 return $chart;
478             }
479              
480             }
481              
482              
483             ###############################################################################
484             #
485             # _check_sheetname( $name )
486             #
487             # Check for valid worksheet names. We check the length, if it contains any
488             # invalid characters and if the name is unique in the workbook.
489             #
490             sub _check_sheetname {
491              
492 972     972   3067 my $self = shift;
493 972   100     7499 my $name = shift || "";
494 972   100     5258 my $chart = shift || 0;
495 972         5808 my $invalid_char = qr([\[\]:*?/\\]);
496              
497             # Increment the Sheet/Chart number used for default sheet names below.
498 972 100       3780 if ( $chart ) {
499 20         50 $self->{_chartname_count}++;
500             }
501             else {
502 952         2689 $self->{_sheetname_count}++;
503             }
504              
505             # Supply default Sheet/Chart name if none has been defined.
506 972 100       4125 if ( $name eq "" ) {
507              
508 906 100       2902 if ( $chart ) {
509 20         66 $name = $self->{_chart_name} . $self->{_chartname_count};
510             }
511             else {
512 886         3593 $name = $self->{_sheet_name} . $self->{_sheetname_count};
513             }
514             }
515              
516             # Check that sheet name is <= 31. Excel limit.
517 972 100       6588 croak "Sheetname $name must be <= 31 chars" if length $name > 31;
518              
519             # Check that sheetname doesn't contain any invalid characters
520 971 100       6674 if ( $name =~ $invalid_char ) {
521 6         411 croak 'Invalid character []:*?/\\ in worksheet name: ' . $name;
522             }
523              
524             # Check that sheetname doesn't start or end with an apostrophe.
525 965 100 100     7091 if ( $name =~ /^'/ || $name =~ /'$/) {
526 3         196 croak "Worksheet name $name cannot start or end with an apostrophe";
527             }
528              
529             # Check that sheetname isn't a reserved word.
530 962 100       5009 if ( lc($name) eq 'history') {
531 4         257 croak "Worksheet name cannot be Excel reserved word 'History'";
532             }
533              
534             # Check that the worksheet name doesn't already exist since this is a fatal
535             # error in Excel 97. The check must also exclude case insensitive matches.
536 958         2331 foreach my $worksheet ( @{ $self->{_worksheets} } ) {
  958         3961  
537 498         730 my $name_a = $name;
538 498         1075 my $name_b = $worksheet->{_name};
539              
540 498 100       1247 if ( lc( $name_a ) eq lc( $name_b ) ) {
541 2         294 croak "Worksheet name '$name', with case ignored, is already used.";
542             }
543             }
544              
545 956         4687 return $name;
546             }
547              
548              
549             ###############################################################################
550             #
551             # add_format(%properties)
552             #
553             # Add a new format to the Excel workbook.
554             #
555             sub add_format {
556              
557 1867     1867 0 5403 my $self = shift;
558              
559             my @init_data =
560 1867         6341 ( \$self->{_xf_format_indices}, \$self->{_dxf_format_indices} );
561              
562             # Change default format style for Excel2003/XLS format.
563 1867 100       5967 if ( $self->{_excel2003_style} ) {
564 19         66 push @init_data, ( font => 'Arial', size => 10, theme => -1 );
565             }
566              
567             # Add the default format properties.
568 1867         3459 push @init_data, %{ $self->{_default_format_properties} };
  1867         5181  
569              
570             # Add the user defined properties.
571 1867         4940 push @init_data, @_;
572              
573 1867         10259 my $format = Excel::Writer::XLSX::Format->new( @init_data );
574              
575 1867         3846 push @{ $self->{_formats} }, $format; # Store format reference
  1867         5665  
576              
577 1867         5914 return $format;
578             }
579              
580              
581             ###############################################################################
582             #
583             # add_shape(%properties)
584             #
585             # Add a new shape to the Excel workbook.
586             #
587             sub add_shape {
588              
589 19     19 0 184 my $self = shift;
590 19         41 my $fh = undef;
591 19         120 my $shape = Excel::Writer::XLSX::Shape->new( $fh, @_ );
592              
593 19         94 $shape->{_palette} = $self->{_palette};
594              
595              
596 19         38 push @{ $self->{_shapes} }, $shape; # Store shape reference.
  19         64  
597              
598 19         60 return $shape;
599             }
600              
601             ###############################################################################
602             #
603             # set_1904()
604             #
605             # Set the date system: 0 = 1900 (the default), 1 = 1904
606             #
607             sub set_1904 {
608              
609 1     1 0 14 my $self = shift;
610              
611 1 50       7 if ( defined( $_[0] ) ) {
612 0         0 $self->{_date_1904} = $_[0];
613             }
614             else {
615 1         9 $self->{_date_1904} = 1;
616             }
617             }
618              
619              
620             ###############################################################################
621             #
622             # get_1904()
623             #
624             # Return the date system: 0 = 1900, 1 = 1904
625             #
626             sub get_1904 {
627              
628 0     0 0 0 my $self = shift;
629              
630 0         0 return $self->{_date_1904};
631             }
632              
633              
634             ###############################################################################
635             #
636             # set_custom_color()
637             #
638             # Change the RGB components of the elements in the colour palette.
639             #
640             sub set_custom_color {
641              
642 5     5 0 34 my $self = shift;
643              
644              
645             # Match a HTML #xxyyzz style parameter
646 5 100 66     30 if ( defined $_[1] and $_[1] =~ /^#(\w\w)(\w\w)(\w\w)/ ) {
647 3         16 @_ = ( $_[0], hex $1, hex $2, hex $3 );
648             }
649              
650              
651 5   50     14 my $index = $_[0] || 0;
652 5   50     16 my $red = $_[1] || 0;
653 5   100     23 my $green = $_[2] || 0;
654 5   100     13 my $blue = $_[3] || 0;
655              
656 5         10 my $aref = $self->{_palette};
657              
658             # Check that the colour index is the right range
659 5 50 33     22 if ( $index < 8 or $index > 64 ) {
660 0         0 carp "Color index $index outside range: 8 <= index <= 64";
661 0         0 return 0;
662             }
663              
664             # Check that the colour components are in the right range
665 5 50 33     44 if ( ( $red < 0 or $red > 255 )
      33        
      33        
      33        
      33        
666             || ( $green < 0 or $green > 255 )
667             || ( $blue < 0 or $blue > 255 ) )
668             {
669 0         0 carp "Color component outside range: 0 <= color <= 255";
670 0         0 return 0;
671             }
672              
673 5         10 $index -= 8; # Adjust colour index (wingless dragonfly)
674              
675             # Set the RGB value.
676 5         13 my @rgb = ( $red, $green, $blue );
677 5         22 $aref->[$index] = [@rgb];
678              
679             # Store the custom colors for the style.xml file.
680 5         9 push @{ $self->{_custom_colors} }, sprintf "FF%02X%02X%02X", @rgb;
  5         24  
681              
682 5         16 return $index + 8;
683             }
684              
685              
686             ###############################################################################
687             #
688             # set_color_palette()
689             #
690             # Sets the colour palette to the Excel defaults.
691             #
692             sub set_color_palette {
693              
694 809     809 0 2374 my $self = shift;
695              
696             $self->{_palette} = [
697 809         28093 [ 0x00, 0x00, 0x00, 0x00 ], # 8
698             [ 0xff, 0xff, 0xff, 0x00 ], # 9
699             [ 0xff, 0x00, 0x00, 0x00 ], # 10
700             [ 0x00, 0xff, 0x00, 0x00 ], # 11
701             [ 0x00, 0x00, 0xff, 0x00 ], # 12
702             [ 0xff, 0xff, 0x00, 0x00 ], # 13
703             [ 0xff, 0x00, 0xff, 0x00 ], # 14
704             [ 0x00, 0xff, 0xff, 0x00 ], # 15
705             [ 0x80, 0x00, 0x00, 0x00 ], # 16
706             [ 0x00, 0x80, 0x00, 0x00 ], # 17
707             [ 0x00, 0x00, 0x80, 0x00 ], # 18
708             [ 0x80, 0x80, 0x00, 0x00 ], # 19
709             [ 0x80, 0x00, 0x80, 0x00 ], # 20
710             [ 0x00, 0x80, 0x80, 0x00 ], # 21
711             [ 0xc0, 0xc0, 0xc0, 0x00 ], # 22
712             [ 0x80, 0x80, 0x80, 0x00 ], # 23
713             [ 0x99, 0x99, 0xff, 0x00 ], # 24
714             [ 0x99, 0x33, 0x66, 0x00 ], # 25
715             [ 0xff, 0xff, 0xcc, 0x00 ], # 26
716             [ 0xcc, 0xff, 0xff, 0x00 ], # 27
717             [ 0x66, 0x00, 0x66, 0x00 ], # 28
718             [ 0xff, 0x80, 0x80, 0x00 ], # 29
719             [ 0x00, 0x66, 0xcc, 0x00 ], # 30
720             [ 0xcc, 0xcc, 0xff, 0x00 ], # 31
721             [ 0x00, 0x00, 0x80, 0x00 ], # 32
722             [ 0xff, 0x00, 0xff, 0x00 ], # 33
723             [ 0xff, 0xff, 0x00, 0x00 ], # 34
724             [ 0x00, 0xff, 0xff, 0x00 ], # 35
725             [ 0x80, 0x00, 0x80, 0x00 ], # 36
726             [ 0x80, 0x00, 0x00, 0x00 ], # 37
727             [ 0x00, 0x80, 0x80, 0x00 ], # 38
728             [ 0x00, 0x00, 0xff, 0x00 ], # 39
729             [ 0x00, 0xcc, 0xff, 0x00 ], # 40
730             [ 0xcc, 0xff, 0xff, 0x00 ], # 41
731             [ 0xcc, 0xff, 0xcc, 0x00 ], # 42
732             [ 0xff, 0xff, 0x99, 0x00 ], # 43
733             [ 0x99, 0xcc, 0xff, 0x00 ], # 44
734             [ 0xff, 0x99, 0xcc, 0x00 ], # 45
735             [ 0xcc, 0x99, 0xff, 0x00 ], # 46
736             [ 0xff, 0xcc, 0x99, 0x00 ], # 47
737             [ 0x33, 0x66, 0xff, 0x00 ], # 48
738             [ 0x33, 0xcc, 0xcc, 0x00 ], # 49
739             [ 0x99, 0xcc, 0x00, 0x00 ], # 50
740             [ 0xff, 0xcc, 0x00, 0x00 ], # 51
741             [ 0xff, 0x99, 0x00, 0x00 ], # 52
742             [ 0xff, 0x66, 0x00, 0x00 ], # 53
743             [ 0x66, 0x66, 0x99, 0x00 ], # 54
744             [ 0x96, 0x96, 0x96, 0x00 ], # 55
745             [ 0x00, 0x33, 0x66, 0x00 ], # 56
746             [ 0x33, 0x99, 0x66, 0x00 ], # 57
747             [ 0x00, 0x33, 0x00, 0x00 ], # 58
748             [ 0x33, 0x33, 0x00, 0x00 ], # 59
749             [ 0x99, 0x33, 0x00, 0x00 ], # 60
750             [ 0x99, 0x33, 0x66, 0x00 ], # 61
751             [ 0x33, 0x33, 0x99, 0x00 ], # 62
752             [ 0x33, 0x33, 0x33, 0x00 ], # 63
753             ];
754              
755 809         2973 return 0;
756             }
757              
758              
759             ###############################################################################
760             #
761             # set_tempdir()
762             #
763             # Change the default temp directory.
764             #
765             sub set_tempdir {
766              
767 1     1 0 11 my $self = shift;
768 1         2 my $dir = shift;
769              
770 1 50 33     21 croak "$dir is not a valid directory" if defined $dir and not -d $dir;
771              
772 1         5 $self->{_tempdir} = $dir;
773              
774             }
775              
776              
777             ###############################################################################
778             #
779             # define_name()
780             #
781             # Create a defined name in Excel. We handle global/workbook level names and
782             # local/worksheet names.
783             #
784             sub define_name {
785              
786 39     39 0 211 my $self = shift;
787 39         64 my $name = shift;
788 39         115 my $formula = shift;
789 39         61 my $sheet_index = undef;
790 39         59 my $sheetname = '';
791 39         59 my $full_name = $name;
792              
793             # Remove the = sign from the formula if it exists.
794 39         126 $formula =~ s/^=//;
795              
796             # Local defined names are formatted like "Sheet1!name".
797 39 100       132 if ( $name =~ /^(.*)!(.*)$/ ) {
798 9         21 $sheetname = $1;
799 9         27 $name = $2;
800 9         30 $sheet_index = $self->_get_sheet_index( $sheetname );
801             }
802             else {
803 30         50 $sheet_index = -1; # Use -1 to indicate global names.
804             }
805              
806             # Warn if the sheet index wasn't found.
807 39 50       93 if ( !defined $sheet_index ) {
808 0         0 carp "Unknown sheet name $sheetname in defined_name()";
809 0         0 return -1;
810             }
811              
812             # Warn if the name contains invalid chars as defined by Excel help.
813 39 100 100     258 if ( $name !~ m/^[\w\\][\w\\.]*$/ || $name =~ m/^\d/ ) {
814 4         487 carp "Invalid character in name '$name' used in defined_name()";
815 4         201 return -1;
816             }
817              
818             # Warn if the name looks like a cell name.
819 35 100       112 if ( $name =~ m/^[a-zA-Z][a-zA-Z]?[a-dA-D]?[0-9]+$/ ) {
820 4         325 carp "Invalid name '$name' looks like a cell name in defined_name()";
821 4         151 return -1;
822             }
823              
824             # Warn if the name looks like a R1C1.
825 31 100 100     138 if ( $name =~ m/^[rcRC]$/ || $name =~ m/^[rcRC]\d+[rcRC]\d+$/ ) {
826 6         468 carp "Invalid name '$name' like a RC cell ref in defined_name()";
827 6         221 return -1;
828             }
829              
830 25         44 push @{ $self->{_defined_names} }, [ $name, $sheet_index, $formula ];
  25         92  
831             }
832              
833              
834             ###############################################################################
835             #
836             # set_size()
837             #
838             # Set the workbook size.
839             #
840             sub set_size {
841              
842 5     5 0 34 my $self = shift;
843 5         10 my $width = shift;
844 5         11 my $height = shift;
845              
846 5 100       13 if ( !$width ) {
847 2         5 $self->{_window_width} = 16095;
848             }
849             else {
850             # Convert to twips at 96 dpi.
851 3         13 $self->{_window_width} = int( $width * 1440 / 96 );
852             }
853              
854 5 100       13 if ( !$height ) {
855 2         5 $self->{_window_height} = 9660;
856             }
857             else {
858             # Convert to twips at 96 dpi.
859 3         11 $self->{_window_height} = int( $height * 1440 / 96 );
860             }
861             }
862              
863              
864             ###############################################################################
865             #
866             # set_tab_ratio()
867             #
868             # Set the ratio of space for worksheet tabs.
869             #
870             sub set_tab_ratio {
871              
872 4     4 0 45 my $self = shift;
873 4         8 my $tab_ratio = shift;
874              
875 4 100       12 if (!defined $tab_ratio) {
876 1         3 return;
877             }
878              
879 3 50 33     20 if ( $tab_ratio < 0 or $tab_ratio > 100 ) {
880 0         0 carp "Tab ratio outside range: 0 <= zoom <= 100";
881             }
882             else {
883 3         12 $self->{_tab_ratio} = int( $tab_ratio * 10 );
884             }
885             }
886              
887              
888             ###############################################################################
889             #
890             # set_properties()
891             #
892             # Set the document properties such as Title, Author etc. These are written to
893             # property sets in the OLE container.
894             #
895             sub set_properties {
896              
897 2     2 0 22 my $self = shift;
898 2         16 my %param = @_;
899              
900             # Ignore if no args were passed.
901 2 50       8 return -1 unless @_;
902              
903             # List of valid input parameters.
904 2         20 my %valid = (
905             title => 1,
906             subject => 1,
907             author => 1,
908             keywords => 1,
909             comments => 1,
910             last_author => 1,
911             created => 1,
912             category => 1,
913             manager => 1,
914             company => 1,
915             status => 1,
916             hyperlink_base => 1,
917             );
918              
919             # Check for valid input parameters.
920 2         9 for my $parameter ( keys %param ) {
921 10 50       25 if ( not exists $valid{$parameter} ) {
922 0         0 carp "Unknown parameter '$parameter' in set_properties()";
923 0         0 return -1;
924             }
925             }
926              
927             # Set the creation time unless specified by the user.
928 2 50       15 if ( !exists $param{created} ) {
929 2         7 $param{created} = $self->{_createtime};
930             }
931              
932              
933 2         12 $self->{_doc_properties} = \%param;
934             }
935              
936              
937             ###############################################################################
938             #
939             # set_custom_property()
940             #
941             # Set a user defined custom document property.
942             #
943             sub set_custom_property {
944              
945 18     18 0 101 my $self = shift;
946 18         30 my $name = shift;
947 18         31 my $value = shift;
948 18         27 my $type = shift;
949              
950              
951             # Valid types.
952 18         72 my %valid_type = (
953             'text' => 1,
954             'date' => 1,
955             'number' => 1,
956             'number_int' => 1,
957             'bool' => 1,
958             );
959              
960 18 50 33     81 if ( !defined $name || !defined $value ) {
961 0         0 carp "The name and value parameters must be defined "
962             . "in set_custom_property()";
963              
964 0         0 return -1;
965             }
966              
967             # Determine the type for strings and numbers if it hasn't been specified.
968 18 100       38 if ( !$type ) {
969 7 100       69 if ( $value =~ /^\d+$/ ) {
    100          
970 1         6 $type = 'number_int';
971             }
972             elsif ( $value =~
973             /^([+-]?)(?=[0-9]|\.[0-9])[0-9]*(\.[0-9]*)?([Ee]([+-]?[0-9]+))?$/ )
974             {
975 2         3 $type = 'number';
976             }
977             else {
978 4         12 $type = 'text';
979             }
980             }
981              
982             # Check for valid validation types.
983 18 50       43 if ( !exists $valid_type{$type} ) {
984 0         0 carp "Unknown custom type '$type' in set_custom_property()";
985 0         0 return -1;
986             }
987              
988             # Check for strings longer than Excel's limit of 255 chars.
989 18 50 66     57 if ( $type eq 'text' and length $value > 255 ) {
990 0         0 carp "Length of text custom value '$value' exceeds "
991             . "Excel's limit of 255 in set_custom_property()";
992 0         0 return -1;
993             }
994 18 50       40 if ( length $value > 255 ) {
995 0         0 carp "Length of custom name '$name' exceeds "
996             . "Excel's limit of 255 in set_custom_property()";
997 0         0 return -1;
998             }
999              
1000 18         26 push @{ $self->{_custom_properties} }, [ $name, $value, $type ];
  18         80  
1001             }
1002              
1003              
1004              
1005             ###############################################################################
1006             #
1007             # add_vba_project()
1008             #
1009             # Add a vbaProject binary to the XLSX file.
1010             #
1011             sub add_vba_project {
1012              
1013 5     5 0 65 my $self = shift;
1014 5         14 my $vba_project = shift;
1015              
1016 5 50       24 croak "No vbaProject.bin specified in add_vba_project()"
1017             if not $vba_project;
1018              
1019 5 50       127 croak "Couldn't locate $vba_project in add_vba_project(): $!"
1020             unless -e $vba_project;
1021              
1022 5 50       26 if ( !$self->{_vba_codemame} ) {
1023 5         15 $self->{_vba_codename} = 'ThisWorkbook';
1024             }
1025              
1026 5         19 $self->{_vba_project} = $vba_project;
1027             }
1028              
1029              
1030             ###############################################################################
1031             #
1032             # set_vba_name()
1033             #
1034             # Set the VBA name for the workbook.
1035             #
1036             sub set_vba_name {
1037              
1038 4     4 0 36 my $self = shift;
1039 4         9 my $vba_codemame = shift;
1040              
1041 4 100       14 if ( $vba_codemame ) {
1042 2         6 $self->{_vba_codename} = $vba_codemame;
1043             }
1044             else {
1045 2         6 $self->{_vba_codename} = 'ThisWorkbook';
1046             }
1047             }
1048              
1049              
1050             ###############################################################################
1051             #
1052             # set_calc_mode()
1053             #
1054             # Set the Excel caclcuation mode for the workbook.
1055             #
1056             sub set_calc_mode {
1057              
1058 3     3 0 26 my $self = shift;
1059 3   50     13 my $mode = shift || 'auto';
1060 3         5 my $calc_id = shift;
1061              
1062 3         8 $self->{_calc_mode} = $mode;
1063              
1064 3 100       16 if ( $mode eq 'manual' ) {
    100          
1065 1         5 $self->{_calc_mode} = 'manual';
1066 1         3 $self->{_calc_on_load} = 0;
1067             }
1068             elsif ( $mode eq 'auto_except_tables' ) {
1069 1         3 $self->{_calc_mode} = 'autoNoTable';
1070             }
1071              
1072 3 100       13 $self->{_calc_id} = $calc_id if defined $calc_id;
1073             }
1074              
1075              
1076             ###############################################################################
1077             #
1078             # get_default_url_format()
1079             #
1080             # Get the default url format used when a user defined format isn't specified
1081             # with write_url(). The format is the hyperlink style defined by Excel for the
1082             # default theme.
1083             #
1084             sub get_default_url_format {
1085              
1086 1     1 0 14 my $self = shift;
1087              
1088 1         6 return $self->{_default_url_format};
1089             }
1090              
1091              
1092             ###############################################################################
1093             #
1094             # _store_workbook()
1095             #
1096             # Assemble worksheets into a workbook.
1097             #
1098             sub _store_workbook {
1099              
1100 809     809   2242 my $self = shift;
1101 809         9070 my $tempdir = File::Temp->newdir( DIR => $self->{_tempdir} );
1102 809         506484 my $packager = Excel::Writer::XLSX::Package::Packager->new();
1103 809         7972 my $zip = Archive::Zip->new();
1104              
1105              
1106             # Add a default worksheet if non have been added.
1107 809 100       37009 $self->add_worksheet() if not @{ $self->{_worksheets} };
  809         5031  
1108              
1109             # Ensure that at least one worksheet has been selected.
1110 809 100       4279 if ( $self->{_activesheet} == 0 ) {
1111 800         3410 $self->{_worksheets}->[0]->{_selected} = 1;
1112 800         2931 $self->{_worksheets}->[0]->{_hidden} = 0;
1113             }
1114              
1115             # Set the active sheet.
1116 809         2197 for my $sheet ( @{ $self->{_worksheets} } ) {
  809         3873  
1117 956 100       5036 $sheet->{_active} = 1 if $sheet->{_index} == $self->{_activesheet};
1118             }
1119              
1120             # Set the sheet vba_codename if the workbook has a vbaProject binary.
1121 809 100       3713 if ( $self->{_vba_project} ) {
1122 5         12 for my $sheet ( @{ $self->{_worksheets} } ) {
  5         15  
1123 5 100       18 if ( !$sheet->{_vba_codename} ) {
1124 2         10 $sheet->set_vba_name();
1125             }
1126             }
1127             }
1128              
1129             # Convert the SST strings data structure.
1130 809         7320 $self->_prepare_sst_string_data();
1131              
1132             # Prepare the worksheet VML elements such as comments and buttons.
1133 809         7033 $self->_prepare_vml_objects();
1134              
1135             # Set the defined names for the worksheets such as Print Titles.
1136 809         6416 $self->_prepare_defined_names();
1137              
1138             # Prepare the drawings, charts and images.
1139 809         5971 $self->_prepare_drawings();
1140              
1141             # Add cached data to charts.
1142 809         7615 $self->_add_chart_data();
1143              
1144             # Prepare the worksheet tables.
1145 809         6214 $self->_prepare_tables();
1146              
1147             # Package the workbook.
1148 809         5549 $packager->_add_workbook( $self );
1149 809         4582 $packager->_set_package_dir( $tempdir );
1150 809         4106 $packager->_create_package();
1151              
1152             # Free up the Packager object.
1153 809         10998 $packager = undef;
1154              
1155             # Add the files to the zip archive. Due to issues with Archive::Zip in
1156             # taint mode we can't use addTree() so we have to build the file list
1157             # with File::Find and pass each one to addFile().
1158 809         2438 my @xlsx_files;
1159              
1160 809 100   17503   5365 my $wanted = sub { push @xlsx_files, $File::Find::name if -f };
  17503         1029881  
1161              
1162 809         77811 File::Find::find(
1163             {
1164             wanted => $wanted,
1165             untaint => 1,
1166             untaint_pattern => qr|^(.+)$|
1167             },
1168             $tempdir
1169             );
1170              
1171             # Re-order the XML files before adding them to the Zip container to match
1172             # (mainly) the order used by Excel and thus satisfy mime-type heuristics
1173             # such as file(1) and magic.
1174 809         5572 my @tmp = grep { m{/xl/} } @xlsx_files;
  9888         24556  
1175 809         2600 @xlsx_files = grep { !m{/xl/} } @xlsx_files;
  9888         21103  
1176 809         3409 @xlsx_files = ( @tmp, @xlsx_files );
1177              
1178 809         2288 @tmp = grep { m{workbook\.xml$} } @xlsx_files;
  9888         19859  
1179 809         2768 @xlsx_files = grep { !m{workbook\.xml$} } @xlsx_files;
  9888         19982  
1180 809         3443 @xlsx_files = ( @tmp, @xlsx_files );
1181              
1182 809         2255 @tmp = grep { m{_rels/workbook\.xml\.rels$} } @xlsx_files;
  9888         19253  
1183 809         2517 @xlsx_files = grep { !m{_rels/workbook\.xml\.rels$} } @xlsx_files;
  9888         19893  
1184 809         3483 @xlsx_files = ( @tmp, @xlsx_files );
1185              
1186 809         2337 @tmp = grep { m{_rels/\.rels$} } @xlsx_files;
  9888         19278  
1187 809         2620 @xlsx_files = grep { !m{_rels/\.rels$} } @xlsx_files;
  9888         19815  
1188 809         3443 @xlsx_files = ( @tmp, @xlsx_files );
1189              
1190 809         2420 @tmp = grep { m{\[Content_Types\]\.xml$} } @xlsx_files;
  9888         18861  
1191 809         2495 @xlsx_files = grep { !m{\[Content_Types\]\.xml$} } @xlsx_files;
  9888         19897  
1192 809         3742 @xlsx_files = ( @tmp, @xlsx_files );
1193              
1194             # Store the xlsx component files with the temp dir name removed.
1195 809         2936 for my $filename ( @xlsx_files ) {
1196 9888         18518 my $short_name = $filename;
1197 9888         34458 $short_name =~ s{^\Q$tempdir\E/?}{};
1198 9888         158928 my $member = $zip->addFile( $filename, $short_name );
1199              
1200             # Set the file member datetime to 1980-01-01 00:00:00 like Excel so
1201             # that apps can produce a consistent binary file. Note, we don't use
1202             # the Archive::Zip::setLastModFileDateTimeFromUnix() function directly
1203             # since it doesn't allow the time 00:00:00 for this date.
1204 9888         2811307 $member->{'lastModFileDateTime'} = 2162688;
1205             }
1206              
1207 809 100       5278 if ( $self->{_internal_fh} ) {
1208              
1209 754 50       5259 if ( $zip->writeToFileHandle( $self->{_filehandle} ) != 0 ) {
1210 0         0 carp 'Error writing zip container for xlsx file.';
1211             }
1212             }
1213             else {
1214              
1215             # Archive::Zip needs to rewind a filehandle to write the zip headers.
1216             # This won't work for arbitrary user defined filehandles so we use
1217             # a temp file based filehandle to create the zip archive and then
1218             # stream that to the filehandle.
1219 55         270 my $tmp_fh = tempfile( DIR => $self->{_tempdir} );
1220 55         30151 my $is_seekable = 1;
1221              
1222 55 50       362 if ( $zip->writeToFileHandle( $tmp_fh, $is_seekable ) != 0 ) {
1223 0         0 carp 'Error writing zip container for xlsx file.';
1224             }
1225              
1226 55         844859 my $buffer;
1227 55         1074 seek $tmp_fh, 0, 0;
1228              
1229 55         833 while ( read( $tmp_fh, $buffer, 4_096 ) ) {
1230 110         472 local $\ = undef; # Protect print from -l on commandline.
1231 110         203 print { $self->{_filehandle} } $buffer;
  110         5495  
1232             }
1233             }
1234             }
1235              
1236              
1237             ###############################################################################
1238             #
1239             # _prepare_sst_string_data()
1240             #
1241             # Convert the SST string data from a hash to an array.
1242             #
1243             sub _prepare_sst_string_data {
1244              
1245 809     809   2482 my $self = shift;
1246              
1247 809         1994 my @strings;
1248 809         4310 $#strings = $self->{_str_unique} - 1; # Pre-extend array
1249              
1250 809         2253 while ( my $key = each %{ $self->{_str_table} } ) {
  1860         7397  
1251 1051         2760 $strings[ $self->{_str_table}->{$key} ] = $key;
1252             }
1253              
1254             # The SST data could be very large, free some memory (maybe).
1255 809         4461 $self->{_str_table} = undef;
1256 809         2884 $self->{_str_array} = \@strings;
1257              
1258             }
1259              
1260              
1261             ###############################################################################
1262             #
1263             # _prepare_format_properties()
1264             #
1265             # Prepare all of the format properties prior to passing them to Styles.pm.
1266             #
1267             sub _prepare_format_properties {
1268              
1269 821     821   2826 my $self = shift;
1270              
1271             # Separate format objects into XF and DXF formats.
1272 821         5584 $self->_prepare_formats();
1273              
1274             # Set the font index for the format objects.
1275 821         8377 $self->_prepare_fonts();
1276              
1277             # Set the number format index for the format objects.
1278 821         7117 $self->_prepare_num_formats();
1279              
1280             # Set the border index for the format objects.
1281 821         6564 $self->_prepare_borders();
1282              
1283             # Set the fill index for the format objects.
1284 821         5506 $self->_prepare_fills();
1285              
1286              
1287             }
1288              
1289              
1290             ###############################################################################
1291             #
1292             # _prepare_formats()
1293             #
1294             # Iterate through the XF Format objects and separate them into XF and DXF
1295             # formats.
1296             #
1297             sub _prepare_formats {
1298              
1299 821     821   2828 my $self = shift;
1300              
1301 821         3203 for my $format ( @{ $self->{_formats} } ) {
  821         4559  
1302 1942         5048 my $xf_index = $format->{_xf_index};
1303 1942         4580 my $dxf_index = $format->{_dxf_index};
1304              
1305 1942 100       5835 if ( defined $xf_index ) {
1306 1111         4941 $self->{_xf_formats}->[$xf_index] = $format;
1307             }
1308              
1309 1942 100       8091 if ( defined $dxf_index ) {
1310 33         90 $self->{_dxf_formats}->[$dxf_index] = $format;
1311             }
1312             }
1313             }
1314              
1315              
1316             ###############################################################################
1317             #
1318             # _set_default_xf_indices()
1319             #
1320             # Set the default index for each format. This is mainly used for testing.
1321             #
1322             sub _set_default_xf_indices {
1323              
1324 9     9   90 my $self = shift;
1325              
1326             # Delete the default url format.
1327 9         23 splice @{ $self->{_formats} }, 1, 1;
  9         38  
1328              
1329 9         31 for my $format ( @{ $self->{_formats} } ) {
  9         33  
1330 49         122 $format->get_xf_index();
1331             }
1332             }
1333              
1334              
1335             ###############################################################################
1336             #
1337             # _prepare_fonts()
1338             #
1339             # Iterate through the XF Format objects and give them an index to non-default
1340             # font elements.
1341             #
1342             sub _prepare_fonts {
1343              
1344 821     821   3191 my $self = shift;
1345              
1346 821         4354 my %fonts;
1347 821         4017 my $index = 0;
1348              
1349 821         4783 for my $format ( @{ $self->{_xf_formats} } ) {
  821         5904  
1350 1111         9522 my $key = $format->get_font_key();
1351              
1352 1111 100       6659 if ( exists $fonts{$key} ) {
1353              
1354             # Font has already been used.
1355 122         251 $format->{_font_index} = $fonts{$key};
1356 122         301 $format->{_has_font} = 0;
1357             }
1358             else {
1359              
1360             # This is a new font.
1361 989         7039 $fonts{$key} = $index;
1362 989         3530 $format->{_font_index} = $index;
1363 989         3579 $format->{_has_font} = 1;
1364 989         3241 $index++;
1365             }
1366             }
1367              
1368 821         3856 $self->{_font_count} = $index;
1369              
1370             # For the DXF formats we only need to check if the properties have changed.
1371 821         3408 for my $format ( @{ $self->{_dxf_formats} } ) {
  821         4072  
1372              
1373             # The only font properties that can change for a DXF format are: color,
1374             # bold, italic, underline and strikethrough.
1375 33 100 100     450 if ( $format->{_color}
      66        
      66        
      100        
1376             || $format->{_bold}
1377             || $format->{_italic}
1378             || $format->{_underline}
1379             || $format->{_font_strikeout} )
1380             {
1381 11         41 $format->{_has_dxf_font} = 1;
1382             }
1383             }
1384             }
1385              
1386              
1387             ###############################################################################
1388             #
1389             # _prepare_num_formats()
1390             #
1391             # Iterate through the XF Format objects and give them an index to non-default
1392             # number format elements.
1393             #
1394             # User defined records start from index 0xA4.
1395             #
1396             sub _prepare_num_formats {
1397              
1398 821     821   2973 my $self = shift;
1399              
1400 821         3344 my %num_formats;
1401 821         3395 my $index = 164;
1402 821         3428 my $num_format_count = 0;
1403              
1404 821         5310 for my $format ( @{ $self->{_xf_formats} }, @{ $self->{_dxf_formats} } ) {
  821         3777  
  821         3645  
1405 1144         3586 my $num_format = $format->{_num_format};
1406              
1407              
1408             # Check if $num_format is an index to a built-in number format.
1409             # Also check for a string of zeros, which is a valid number format
1410             # string but would evaluate to zero.
1411             #
1412 1144 100 66     10196 if ( $num_format =~ m/^\d+$/ && $num_format !~ m/^0+\d/ ) {
    100          
1413              
1414             # Number format '0' is indexed as 1 in Excel.
1415 34 100       156 if ($num_format == 0) {
1416 1         3 $num_format = 1;
1417             }
1418              
1419             # Index to a built-in number format.
1420 34         81 $format->{_num_format_index} = $num_format;
1421 34         89 next;
1422             }
1423             elsif ( $num_format eq 'General' ) {
1424             # The 'General' format has an number format index of 0.
1425 1087         6050 $format->{_num_format_index} = 0;
1426 1087         3261 next;
1427             }
1428              
1429              
1430 23 100       69 if ( exists( $num_formats{$num_format} ) ) {
1431              
1432             # Number format has already been used.
1433 3         7 $format->{_num_format_index} = $num_formats{$num_format};
1434             }
1435             else {
1436              
1437             # Add a new number format.
1438 20         49 $num_formats{$num_format} = $index;
1439 20         52 $format->{_num_format_index} = $index;
1440 20         36 $index++;
1441              
1442             # Only increase font count for XF formats (not for DXF formats).
1443 20 100       114 if ( $format->{_xf_index} ) {
1444 18         39 $num_format_count++;
1445             }
1446             }
1447             }
1448              
1449 821         3669 $self->{_num_format_count} = $num_format_count;
1450             }
1451              
1452              
1453             ###############################################################################
1454             #
1455             # _prepare_borders()
1456             #
1457             # Iterate through the XF Format objects and give them an index to non-default
1458             # border elements.
1459             #
1460             sub _prepare_borders {
1461              
1462 821     821   2101 my $self = shift;
1463              
1464 821         1735 my %borders;
1465 821         1919 my $index = 0;
1466              
1467 821         1923 for my $format ( @{ $self->{_xf_formats} } ) {
  821         2952  
1468 1111         5081 my $key = $format->get_border_key();
1469              
1470 1111 100       4150 if ( exists $borders{$key} ) {
1471              
1472             # Border has already been used.
1473 245         625 $format->{_border_index} = $borders{$key};
1474 245         737 $format->{_has_border} = 0;
1475             }
1476             else {
1477              
1478             # This is a new border.
1479 866         2752 $borders{$key} = $index;
1480 866         2297 $format->{_border_index} = $index;
1481 866         2378 $format->{_has_border} = 1;
1482 866         2447 $index++;
1483             }
1484             }
1485              
1486 821         2530 $self->{_border_count} = $index;
1487              
1488             # For the DXF formats we only need to check if the properties have changed.
1489 821         1833 for my $format ( @{ $self->{_dxf_formats} } ) {
  821         3356  
1490 33         115 my $key = $format->get_border_key();
1491              
1492 33 100       208 if ( $key =~ m/[^0:]/ ) {
1493 1         5 $format->{_has_dxf_border} = 1;
1494             }
1495             }
1496              
1497             }
1498              
1499              
1500             ###############################################################################
1501             #
1502             # _prepare_fills()
1503             #
1504             # Iterate through the XF Format objects and give them an index to non-default
1505             # fill elements.
1506             #
1507             # The user defined fill properties start from 2 since there are 2 default
1508             # fills: patternType="none" and patternType="gray125".
1509             #
1510             sub _prepare_fills {
1511              
1512 821     821   2190 my $self = shift;
1513              
1514 821         1881 my %fills;
1515 821         1976 my $index = 2; # Start from 2. See above.
1516              
1517             # Add the default fills.
1518 821         2492 $fills{'0:0:0'} = 0;
1519 821         2258 $fills{'17:0:0'} = 1;
1520              
1521              
1522             # Store the DXF colours separately since them may be reversed below.
1523 821         1802 for my $format ( @{ $self->{_dxf_formats} } ) {
  821         3286  
1524 33 100 100     274 if ( $format->{_pattern}
      66        
1525             || $format->{_bg_color}
1526             || $format->{_fg_color} )
1527             {
1528 17         64 $format->{_has_dxf_fill} = 1;
1529 17         148 $format->{_dxf_bg_color} = $format->{_bg_color};
1530 17         73 $format->{_dxf_fg_color} = $format->{_fg_color};
1531             }
1532             }
1533              
1534              
1535 821         2109 for my $format ( @{ $self->{_xf_formats} } ) {
  821         2746  
1536              
1537             # The following logical statements jointly take care of special cases
1538             # in relation to cell colours and patterns:
1539             # 1. For a solid fill (_pattern == 1) Excel reverses the role of
1540             # foreground and background colours, and
1541             # 2. If the user specifies a foreground or background colour without
1542             # a pattern they probably wanted a solid fill, so we fill in the
1543             # defaults.
1544             #
1545 1111 100 100     5320 if ( $format->{_pattern} == 1
      100        
1546             && $format->{_bg_color} ne '0'
1547             && $format->{_fg_color} ne '0' )
1548             {
1549 3         10 my $tmp = $format->{_fg_color};
1550 3         6 $format->{_fg_color} = $format->{_bg_color};
1551 3         8 $format->{_bg_color} = $tmp;
1552             }
1553              
1554 1111 100 100     9191 if ( $format->{_pattern} <= 1
      100        
1555             && $format->{_bg_color} ne '0'
1556             && $format->{_fg_color} eq '0' )
1557             {
1558 6         20 $format->{_fg_color} = $format->{_bg_color};
1559 6         21 $format->{_bg_color} = 0;
1560 6         11 $format->{_pattern} = 1;
1561             }
1562              
1563 1111 100 100     10902 if ( $format->{_pattern} <= 1
      100        
1564             && $format->{_bg_color} eq '0'
1565             && $format->{_fg_color} ne '0' )
1566             {
1567 13         23 $format->{_bg_color} = 0;
1568 13         21 $format->{_pattern} = 1;
1569             }
1570              
1571              
1572 1111         4970 my $key = $format->get_fill_key();
1573              
1574 1111 100       4197 if ( exists $fills{$key} ) {
1575              
1576             # Fill has already been used.
1577 1095         2961 $format->{_fill_index} = $fills{$key};
1578 1095         3415 $format->{_has_fill} = 0;
1579             }
1580             else {
1581              
1582             # This is a new fill.
1583 16         36 $fills{$key} = $index;
1584 16         29 $format->{_fill_index} = $index;
1585 16         27 $format->{_has_fill} = 1;
1586 16         28 $index++;
1587             }
1588             }
1589              
1590 821         2803 $self->{_fill_count} = $index;
1591              
1592              
1593             }
1594              
1595              
1596             ###############################################################################
1597             #
1598             # _prepare_defined_names()
1599             #
1600             # Iterate through the worksheets and store any defined names in addition to
1601             # any user defined names. Stores the defined names for the Workbook.xml and
1602             # the named ranges for App.xml.
1603             #
1604             sub _prepare_defined_names {
1605              
1606 810     810   2215 my $self = shift;
1607              
1608 810         1966 my @defined_names = @{ $self->{_defined_names} };
  810         2941  
1609              
1610 810         2151 for my $sheet ( @{ $self->{_worksheets} } ) {
  810         3186  
1611              
1612             # Check for Print Area settings.
1613 959 100       3978 if ( $sheet->{_autofilter} ) {
1614              
1615 11         34 my $range = $sheet->{_autofilter};
1616 11         29 my $hidden = 1;
1617              
1618             # Store the defined names.
1619             push @defined_names,
1620 11         57 [ '_xlnm._FilterDatabase', $sheet->{_index}, $range, $hidden ];
1621              
1622             }
1623              
1624             # Check for Print Area settings.
1625 959 100       3792 if ( $sheet->{_print_area} ) {
1626              
1627 8         28 my $range = $sheet->{_print_area};
1628              
1629             # Store the defined names.
1630             push @defined_names,
1631 8         37 [ '_xlnm.Print_Area', $sheet->{_index}, $range ];
1632             }
1633              
1634             # Check for repeat rows/cols. aka, Print Titles.
1635 959 100 100     7319 if ( $sheet->{_repeat_cols} || $sheet->{_repeat_rows} ) {
1636 7         14 my $range = '';
1637              
1638 7 100 100     42 if ( $sheet->{_repeat_cols} && $sheet->{_repeat_rows} ) {
1639 2         8 $range = $sheet->{_repeat_cols} . ',' . $sheet->{_repeat_rows};
1640             }
1641             else {
1642 5         28 $range = $sheet->{_repeat_cols} . $sheet->{_repeat_rows};
1643             }
1644              
1645             # Store the defined names.
1646             push @defined_names,
1647 7         31 [ '_xlnm.Print_Titles', $sheet->{_index}, $range ];
1648             }
1649              
1650             }
1651              
1652 810         4065 @defined_names = _sort_defined_names( @defined_names );
1653 810         2678 $self->{_defined_names} = \@defined_names;
1654 810         3608 $self->{_named_ranges} = _extract_named_ranges( @defined_names );
1655             }
1656              
1657              
1658             ###############################################################################
1659             #
1660             # _sort_defined_names()
1661             #
1662             # Sort internal and user defined names in the same order as used by Excel.
1663             # This may not be strictly necessary but unsorted elements caused a lot of
1664             # issues in the Spreadsheet::WriteExcel binary version. Also makes
1665             # comparison testing easier.
1666             #
1667             sub _sort_defined_names {
1668              
1669 811     811   3040 my @names = @_;
1670              
1671             #<<< Perltidy ignore this.
1672              
1673             @names = sort {
1674             # Primary sort based on the defined name.
1675 811 50       3489 _normalise_defined_name( $a->[0] )
  117         228  
1676             cmp
1677             _normalise_defined_name( $b->[0] )
1678              
1679             ||
1680             # Secondary sort based on the sheet name.
1681             _normalise_sheet_name( $a->[2] )
1682             cmp
1683             _normalise_sheet_name( $b->[2] )
1684              
1685             } @names;
1686             #>>>
1687              
1688 811         2425 return @names;
1689             }
1690              
1691             # Used in the above sort routine to normalise the defined names. Removes any
1692             # leading '_xmln.' from internal names and lowercases the strings.
1693             sub _normalise_defined_name {
1694 234     234   385 my $name = shift;
1695              
1696 234         392 $name =~ s/^_xlnm.//;
1697 234         343 $name = lc $name;
1698              
1699 234         531 return $name;
1700             }
1701              
1702             # Used in the above sort routine to normalise the worksheet names for the
1703             # secondary sort. Removes leading quote and lowercases the strings.
1704             sub _normalise_sheet_name {
1705 24     24   40 my $name = shift;
1706              
1707 24         49 $name =~ s/^'//;
1708 24         43 $name = lc $name;
1709              
1710 24         54 return $name;
1711             }
1712              
1713              
1714             ###############################################################################
1715             #
1716             # _extract_named_ranges()
1717             #
1718             # Extract the named ranges from the sorted list of defined names. These are
1719             # used in the App.xml file.
1720             #
1721             sub _extract_named_ranges {
1722              
1723 811     811   6163 my @defined_names = @_;
1724 811         1914 my @named_ranges;
1725              
1726             NAME:
1727 811         2676 for my $defined_name ( @defined_names ) {
1728              
1729 76         146 my $name = $defined_name->[0];
1730 76         316 my $index = $defined_name->[1];
1731 76         131 my $range = $defined_name->[2];
1732              
1733             # Skip autoFilter ranges.
1734 76 100       378 next NAME if $name eq '_xlnm._FilterDatabase';
1735              
1736             # We are only interested in defined names with ranges.
1737 64 100       282 if ( $range =~ /^([^!]+)!/ ) {
1738 57         136 my $sheet_name = $1;
1739              
1740             # Match Print_Area and Print_Titles xlnm types.
1741 57 100       212 if ( $name =~ /^_xlnm\.(.*)$/ ) {
    100          
1742 18         55 my $xlnm_type = $1;
1743 18         57 $name = $sheet_name . '!' . $xlnm_type;
1744             }
1745             elsif ( $index != -1 ) {
1746 16         38 $name = $sheet_name . '!' . $name;
1747             }
1748              
1749 57         149 push @named_ranges, $name;
1750             }
1751             }
1752              
1753 811         2910 return \@named_ranges;
1754             }
1755              
1756              
1757             ###############################################################################
1758             #
1759             # _prepare_drawings()
1760             #
1761             # Iterate through the worksheets and set up any chart or image drawings.
1762             #
1763             sub _prepare_drawings {
1764              
1765 809     809   2476 my $self = shift;
1766 809         2075 my $chart_ref_id = 0;
1767 809         2069 my $image_ref_id = 0;
1768 809         1907 my $drawing_id = 0;
1769 809         2153 my $ref_id = 0;
1770 809         2281 my %image_ids = ();
1771 809         2131 my %header_image_ids = ();
1772              
1773 809         1742 for my $sheet ( @{ $self->{_worksheets} } ) {
  809         2876  
1774              
1775 956         1982 my $chart_count = scalar @{ $sheet->{_charts} };
  956         2736  
1776 956         2037 my $image_count = scalar @{ $sheet->{_images} };
  956         2513  
1777 956         1980 my $shape_count = scalar @{ $sheet->{_shapes} };
  956         2410  
1778              
1779 956         1921 my $header_image_count = scalar @{ $sheet->{_header_images} };
  956         2473  
1780 956         1886 my $footer_image_count = scalar @{ $sheet->{_footer_images} };
  956         2457  
1781 956         2164 my $has_drawing = 0;
1782              
1783              
1784             # Check that some image or drawing needs to be processed.
1785 956 100 100     8082 if ( !$chart_count
      100        
      100        
      100        
1786             && !$image_count
1787             && !$shape_count
1788             && !$header_image_count
1789             && !$footer_image_count )
1790             {
1791 468         1396 next;
1792             }
1793              
1794             # Don't increase the drawing_id header/footer images.
1795 488 100 100     2668 if ( $chart_count || $image_count || $shape_count ) {
      100        
1796 469         1104 $drawing_id++;
1797 469         1164 $has_drawing = 1;
1798             }
1799              
1800             # Prepare the worksheet images.
1801 488         2260 for my $index ( 0 .. $image_count - 1 ) {
1802              
1803 103         275 my $filename = $sheet->{_images}->[$index]->[2];
1804              
1805 103         576 my ( $type, $width, $height, $name, $x_dpi, $y_dpi, $md5 ) =
1806             $self->_get_image_properties( $filename );
1807              
1808 103 100       477 if ( exists $image_ids{$md5} ) {
1809 11         24 $ref_id = $image_ids{$md5};
1810             }
1811             else {
1812 92         223 $ref_id = ++$image_ref_id;
1813 92         265 $image_ids{$md5} = $ref_id;
1814 92         178 push @{ $self->{_images} }, [ $filename, $type ];
  92         460  
1815             }
1816              
1817 103         662 $sheet->_prepare_image(
1818             $index, $ref_id, $drawing_id, $width, $height,
1819             $name, $type, $x_dpi, $y_dpi, $md5
1820             );
1821             }
1822              
1823             # Prepare the worksheet charts.
1824 488         1821 for my $index ( 0 .. $chart_count - 1 ) {
1825 394         830 $chart_ref_id++;
1826 394         2476 $sheet->_prepare_chart( $index, $chart_ref_id, $drawing_id );
1827             }
1828              
1829             # Prepare the worksheet shapes.
1830 488         2000 for my $index ( 0 .. $shape_count - 1 ) {
1831 41         134 $sheet->_prepare_shape( $index, $drawing_id );
1832             }
1833              
1834             # Prepare the header images.
1835 488         1901 for my $index ( 0 .. $header_image_count - 1 ) {
1836              
1837 32         82 my $filename = $sheet->{_header_images}->[$index]->[0];
1838 32         66 my $position = $sheet->{_header_images}->[$index]->[1];
1839              
1840 32         136 my ( $type, $width, $height, $name, $x_dpi, $y_dpi, $md5 ) =
1841             $self->_get_image_properties( $filename );
1842              
1843 32 100       143 if ( exists $header_image_ids{$md5} ) {
1844 4         13 $ref_id = $header_image_ids{$md5};
1845             }
1846             else {
1847 28         56 $ref_id = ++$image_ref_id;
1848 28         76 $header_image_ids{$md5} = $ref_id;
1849 28         52 push @{ $self->{_images} }, [ $filename, $type ];
  28         113  
1850             }
1851              
1852 32         188 $sheet->_prepare_header_image(
1853             $ref_id, $width, $height, $name, $type,
1854             $position, $x_dpi, $y_dpi, $md5
1855             );
1856             }
1857              
1858             # Prepare the footer images.
1859 488         1792 for my $index ( 0 .. $footer_image_count - 1 ) {
1860              
1861 12         31 my $filename = $sheet->{_footer_images}->[$index]->[0];
1862 12         23 my $position = $sheet->{_footer_images}->[$index]->[1];
1863              
1864 12         33 my ( $type, $width, $height, $name, $x_dpi, $y_dpi, $md5 ) =
1865             $self->_get_image_properties( $filename );
1866              
1867 12 100       47 if ( exists $header_image_ids{$md5} ) {
1868 8         19 $ref_id = $header_image_ids{$md5};
1869             }
1870             else {
1871 4         7 $ref_id = ++$image_ref_id;
1872 4         10 $header_image_ids{$md5} = $ref_id;
1873 4         6 push @{ $self->{_images} }, [ $filename, $type ];
  4         15  
1874             }
1875              
1876 12         51 $sheet->_prepare_header_image(
1877             $ref_id, $width, $height, $name, $type,
1878             $position, $x_dpi, $y_dpi, $md5
1879             );
1880             }
1881              
1882              
1883 488 100       1964 if ( $has_drawing ) {
1884 469         1472 my $drawing = $sheet->{_drawing};
1885 469         1351 push @{ $self->{_drawings} }, $drawing;
  469         1977  
1886             }
1887             }
1888              
1889              
1890             # Remove charts that were created but not inserted into worksheets.
1891 809         2470 my @chart_data;
1892              
1893 809         1921 for my $chart ( @{ $self->{_charts} } ) {
  809         2826  
1894 405 100       1700 if ( $chart->{_id} != -1 ) {
1895 394         1209 push @chart_data, $chart;
1896             }
1897             }
1898              
1899             # Sort the workbook charts references into the order that the were
1900             # written from the worksheets above.
1901 809         2671 @chart_data = sort { $a->{_id} <=> $b->{_id} } @chart_data;
  64         158  
1902              
1903 809         2833 $self->{_charts} = \@chart_data;
1904 809         3131 $self->{_drawing_count} = $drawing_id;
1905             }
1906              
1907              
1908             ###############################################################################
1909             #
1910             # _prepare_vml_objects()
1911             #
1912             # Iterate through the worksheets and set up the VML objects.
1913             #
1914             sub _prepare_vml_objects {
1915              
1916 809     809   4242 my $self = shift;
1917 809         2051 my $comment_id = 0;
1918 809         2050 my $vml_drawing_id = 0;
1919 809         1956 my $vml_data_id = 1;
1920 809         3022 my $vml_header_id = 0;
1921 809         2093 my $vml_shape_id = 1024;
1922 809         1858 my $vml_files = 0;
1923 809         3466 my $comment_files = 0;
1924              
1925 809         1851 for my $sheet ( @{ $self->{_worksheets} } ) {
  809         3032  
1926              
1927 956 100 100     8033 next if !$sheet->{_has_vml} and !$sheet->{_has_header_vml};
1928 74         183 $vml_files = 1;
1929              
1930              
1931 74 100       270 if ( $sheet->{_has_vml} ) {
1932              
1933 53 100       209 $comment_files++ if $sheet->{_has_comments};
1934 53 100       169 $comment_id++ if $sheet->{_has_comments};
1935 53         118 $vml_drawing_id++;
1936              
1937 53         400 my $count =
1938             $sheet->_prepare_vml_objects( $vml_data_id, $vml_shape_id,
1939             $vml_drawing_id, $comment_id );
1940              
1941             # Each VML file should start with a shape id incremented by 1024.
1942 53         211 $vml_data_id += 1 * int( ( 1024 + $count ) / 1024 );
1943 53         172 $vml_shape_id += 1024 * int( ( 1024 + $count ) / 1024 );
1944              
1945             }
1946              
1947 74 100       359 if ( $sheet->{_has_header_vml} ) {
1948 22         52 $vml_header_id++;
1949 22         38 $vml_drawing_id++;
1950 22         123 $sheet->_prepare_header_vml_objects( $vml_header_id,
1951             $vml_drawing_id );
1952             }
1953              
1954             }
1955              
1956 809         2950 $self->{_num_vml_files} = $vml_files;
1957 809         2266 $self->{_num_comment_files} = $comment_files;
1958              
1959             # Add a font format for cell comments.
1960 809 100       4163 if ( $comment_files > 0 ) {
1961             my $format = Excel::Writer::XLSX::Format->new(
1962             \$self->{_xf_format_indices},
1963             \$self->{_dxf_format_indices},
1964 32         341 font => 'Tahoma',
1965             size => 8,
1966             color_indexed => 81,
1967             font_only => 1,
1968             );
1969              
1970 32         225 $format->get_xf_index();
1971              
1972 32         98 push @{ $self->{_formats} }, $format;
  32         136  
1973             }
1974             }
1975              
1976              
1977             ###############################################################################
1978             #
1979             # _prepare_tables()
1980             #
1981             # Set the table ids for the worksheet tables.
1982             #
1983             sub _prepare_tables {
1984              
1985 809     809   2124 my $self = shift;
1986 809         2006 my $table_id = 0;
1987 809         2111 my $seen = {};
1988              
1989 809         1922 for my $sheet ( @{ $self->{_worksheets} } ) {
  809         2986  
1990              
1991 956         2036 my $table_count = scalar @{ $sheet->{_tables} };
  956         2609  
1992              
1993 956 100       4420 next unless $table_count;
1994              
1995 27         201 $sheet->_prepare_tables( $table_id + 1, $seen );
1996              
1997 27         116 $table_id += $table_count;
1998             }
1999             }
2000              
2001              
2002             ###############################################################################
2003             #
2004             # _add_chart_data()
2005             #
2006             # Add "cached" data to charts to provide the numCache and strCache data for
2007             # series and title/axis ranges.
2008             #
2009             sub _add_chart_data {
2010              
2011 809     809   2176 my $self = shift;
2012 809         3365 my %worksheets;
2013             my %seen_ranges;
2014 809         0 my @charts;
2015              
2016             # Map worksheet names to worksheet objects.
2017 809         1846 for my $worksheet ( @{ $self->{_worksheets} } ) {
  809         2982  
2018 956         3322 $worksheets{ $worksheet->{_name} } = $worksheet;
2019             }
2020              
2021             # Build an array of the worksheet charts including any combined charts.
2022 809         2111 for my $chart ( @{ $self->{_charts} } ) {
  809         2839  
2023 394         1053 push @charts, $chart;
2024              
2025 394 100       1815 if ($chart->{_combined}) {
2026 10         38 push @charts, $chart->{_combined};
2027             }
2028             }
2029              
2030              
2031             CHART:
2032 809         2985 for my $chart ( @charts ) {
2033              
2034             RANGE:
2035 404         964 while ( my ( $range, $id ) = each %{ $chart->{_formula_ids} } ) {
  1502         7101  
2036              
2037             # Skip if the series has user defined data.
2038 1098 100       3302 if ( defined $chart->{_formula_data}->[$id] ) {
2039 16 50 33     54 if ( !exists $seen_ranges{$range}
2040             || !defined $seen_ranges{$range} )
2041             {
2042 16         40 my $data = $chart->{_formula_data}->[$id];
2043 16         41 $seen_ranges{$range} = $data;
2044             }
2045 16         41 next RANGE;
2046             }
2047              
2048             # Check to see if the data is already cached locally.
2049 1082 100       3064 if ( exists $seen_ranges{$range} ) {
2050 20         40 $chart->{_formula_data}->[$id] = $seen_ranges{$range};
2051 20         52 next RANGE;
2052             }
2053              
2054             # Convert the range formula to a sheet name and cell range.
2055 1062         4345 my ( $sheetname, @cells ) = $self->_get_chart_range( $range );
2056              
2057             # Skip if we couldn't parse the formula.
2058 1062 50       3075 next RANGE if !defined $sheetname;
2059              
2060             # Handle non-contiguous ranges: (Sheet1!$A$1:$A$2,Sheet1!$A$4:$A$5).
2061             # We don't try to parse the ranges. We just return an empty list.
2062 1062 50       3293 if ( $sheetname =~ m/^\([^,]+,/ ) {
2063 0         0 $chart->{_formula_data}->[$id] = [];
2064 0         0 $seen_ranges{$range} = [];
2065 0         0 next RANGE;
2066             }
2067              
2068             # Die if the name is unknown since it indicates a user error in
2069             # a chart series formula.
2070 1062 50       3138 if ( !exists $worksheets{$sheetname} ) {
2071 0         0 die "Unknown worksheet reference '$sheetname' in range "
2072             . "'$range' passed to add_series().\n";
2073             }
2074              
2075             # Find the worksheet object based on the sheet name.
2076 1062         2125 my $worksheet = $worksheets{$sheetname};
2077              
2078             # Get the data from the worksheet table.
2079 1062         5458 my @data = $worksheet->_get_range_data( @cells );
2080              
2081             # Convert shared string indexes to strings.
2082 1062         2616 for my $token ( @data ) {
2083 5044 100       9949 if ( ref $token ) {
2084 25         60 $token = $self->{_str_array}->[ $token->{sst_id} ];
2085              
2086             # Ignore rich strings for now. Deparse later if necessary.
2087 25 50 33     113 if ( $token =~ m{^} && $token =~ m{$} ) {
2088 0         0 $token = '';
2089             }
2090             }
2091             }
2092              
2093             # Add the data to the chart.
2094 1062         2751 $chart->{_formula_data}->[$id] = \@data;
2095              
2096             # Store range data locally to avoid lookup if seen again.
2097 1062         3669 $seen_ranges{$range} = \@data;
2098             }
2099             }
2100             }
2101              
2102              
2103             ###############################################################################
2104             #
2105             # _get_chart_range()
2106             #
2107             # Convert a range formula such as Sheet1!$B$1:$B$5 into a sheet name and cell
2108             # range such as ( 'Sheet1', 0, 1, 4, 1 ).
2109             #
2110             sub _get_chart_range {
2111              
2112 1069     1069   2151 my $self = shift;
2113 1069         1883 my $range = shift;
2114 1069         3850 my $cell_1;
2115             my $cell_2;
2116 1069         0 my $sheetname;
2117 1069         0 my $cells;
2118              
2119             # Split the range formula into sheetname and cells at the last '!'.
2120 1069         2700 my $pos = rindex $range, '!';
2121 1069 100       3018 if ( $pos > 0 ) {
2122 1068         2639 $sheetname = substr $range, 0, $pos;
2123 1068         2531 $cells = substr $range, $pos + 1;
2124             }
2125             else {
2126 1         5 return undef;
2127             }
2128              
2129             # Split the cell range into 2 cells or else use single cell for both.
2130 1068 100       4134 if ( $cells =~ ':' ) {
2131 1048         3874 ( $cell_1, $cell_2 ) = split /:/, $cells;
2132             }
2133             else {
2134 20         56 ( $cell_1, $cell_2 ) = ( $cells, $cells );
2135             }
2136              
2137             # Remove leading/trailing apostrophes and convert escaped quotes to single.
2138 1068         2791 $sheetname =~ s/^'//g;
2139 1068         2231 $sheetname =~ s/'$//g;
2140 1068         2144 $sheetname =~ s/''/'/g;
2141              
2142 1068         4867 my ( $row_start, $col_start ) = xl_cell_to_rowcol( $cell_1 );
2143 1068         5853 my ( $row_end, $col_end ) = xl_cell_to_rowcol( $cell_2 );
2144              
2145             # Check that we have a 1D range only.
2146 1068 100 100     5903 if ( $row_start != $row_end && $col_start != $col_end ) {
2147 1         5 return undef;
2148             }
2149              
2150 1067         4083 return ( $sheetname, $row_start, $col_start, $row_end, $col_end );
2151             }
2152              
2153              
2154             ###############################################################################
2155             #
2156             # _store_externs()
2157             #
2158             # Write the EXTERNCOUNT and EXTERNSHEET records. These are used as indexes for
2159             # the NAME records.
2160             #
2161             sub _store_externs {
2162              
2163 0     0   0 my $self = shift;
2164              
2165             }
2166              
2167              
2168             ###############################################################################
2169             #
2170             # _store_names()
2171             #
2172             # Write the NAME record to define the print area and the repeat rows and cols.
2173             #
2174             sub _store_names {
2175              
2176 0     0   0 my $self = shift;
2177              
2178             }
2179              
2180              
2181             ###############################################################################
2182             #
2183             # _quote_sheetname()
2184             #
2185             # Sheetnames used in references should be quoted if they contain any spaces,
2186             # special characters or if the look like something that isn't a sheet name.
2187             # TODO. We need to handle more special cases.
2188             #
2189             sub _quote_sheetname {
2190              
2191 0     0   0 my $self = shift;
2192 0         0 my $sheetname = $_[0];
2193              
2194 0 0       0 if ( $sheetname =~ /^Sheet\d+$/ ) {
2195 0         0 return $sheetname;
2196             }
2197             else {
2198 0         0 return qq('$sheetname');
2199             }
2200             }
2201              
2202              
2203             ###############################################################################
2204             #
2205             # _get_image_properties()
2206             #
2207             # Extract information from the image file such as dimension, type, filename,
2208             # and extension. Also keep track of previously seen images to optimise out
2209             # any duplicates.
2210             #
2211             sub _get_image_properties {
2212              
2213 147     147   332 my $self = shift;
2214 147         272 my $filename = shift;
2215              
2216 147         427 my $type;
2217             my $width;
2218 147         0 my $height;
2219 147         264 my $x_dpi = 96;
2220 147         282 my $y_dpi = 96;
2221 147         236 my $image_name;
2222              
2223              
2224 147         3989 ( $image_name ) = fileparse( $filename );
2225              
2226             # Open the image file and import the data.
2227 147         1163 my $fh = FileHandle->new( $filename );
2228 147 50       12627 croak "Couldn't import $filename: $!" unless defined $fh;
2229 147         524 binmode $fh;
2230              
2231             # Slurp the file into a string and do some size calcs.
2232 147         263 my $data = do { local $/; <$fh> };
  147         602  
  147         5073  
2233 147         494 my $size = length $data;
2234 147         1630 my $md5 = md5_hex($data);
2235              
2236              
2237 147 100       1422 if ( unpack( 'x A3', $data ) eq 'PNG' ) {
    100          
    50          
2238              
2239             # Test for PNGs.
2240 80         747 ( $type, $width, $height, $x_dpi, $y_dpi ) =
2241             $self->_process_png( $data, $filename );
2242              
2243 80         303 $self->{_image_types}->{png} = 1;
2244             }
2245             elsif ( unpack( 'n', $data ) == 0xFFD8 ) {
2246              
2247             # Test for JPEG files.
2248 66         430 ( $type, $width, $height, $x_dpi, $y_dpi ) =
2249             $self->_process_jpg( $data, $filename );
2250              
2251 66         219 $self->{_image_types}->{jpeg} = 1;
2252             }
2253             elsif ( unpack( 'A2', $data ) eq 'BM' ) {
2254              
2255             # Test for BMPs.
2256 1         17 ( $type, $width, $height ) = $self->_process_bmp( $data, $filename );
2257              
2258 1         4 $self->{_image_types}->{bmp} = 1;
2259             }
2260             else {
2261 0         0 croak "Unsupported image format for file: $filename\n";
2262             }
2263              
2264             # Set a default dpi for images with 0 dpi.
2265 147 100       535 $x_dpi = 96 if $x_dpi == 0;
2266 147 100       427 $y_dpi = 96 if $y_dpi == 0;
2267              
2268 147         835 $fh->close;
2269              
2270 147         3550 return ( $type, $width, $height, $image_name, $x_dpi, $y_dpi, $md5 );
2271             }
2272              
2273              
2274             ###############################################################################
2275             #
2276             # _process_png()
2277             #
2278             # Extract width and height information from a PNG file.
2279             #
2280             sub _process_png {
2281              
2282 80     80   234 my $self = shift;
2283 80         200 my $data = $_[0];
2284 80         204 my $filename = $_[1];
2285              
2286 80         228 my $type = 'png';
2287 80         309 my $width = 0;
2288 80         168 my $height = 0;
2289 80         161 my $x_dpi = 96;
2290 80         185 my $y_dpi = 96;
2291              
2292 80         178 my $offset = 8;
2293 80         188 my $data_length = length $data;
2294              
2295             # Search through the image data to read the height and width in the
2296             # IHDR element. Also read the DPI in the pHYs element.
2297 80         337 while ( $offset < $data_length ) {
2298              
2299 469         1094 my $length = unpack "N", substr $data, $offset + 0, 4;
2300 469         1076 my $type = unpack "A4", substr $data, $offset + 4, 4;
2301              
2302 469 100       1121 if ( $type eq "IHDR" ) {
2303 80         262 $width = unpack "N", substr $data, $offset + 8, 4;
2304 80         279 $height = unpack "N", substr $data, $offset + 12, 4;
2305             }
2306              
2307 469 100       988 if ( $type eq "pHYs" ) {
2308 11         39 my $x_ppu = unpack "N", substr $data, $offset + 8, 4;
2309 11         29 my $y_ppu = unpack "N", substr $data, $offset + 12, 4;
2310 11         37 my $units = unpack "C", substr $data, $offset + 16, 1;
2311              
2312 11 50       32 if ( $units == 1 ) {
2313 11         30 $x_dpi = $x_ppu * 0.0254;
2314 11         19 $y_dpi = $y_ppu * 0.0254;
2315             }
2316             }
2317              
2318 469         713 $offset = $offset + $length + 12;
2319              
2320 469 100       1218 last if $type eq "IEND";
2321             }
2322              
2323 80 50       287 if ( not defined $height ) {
2324 0         0 croak "$filename: no size data found in png image.\n";
2325             }
2326              
2327 80         450 return ( $type, $width, $height, $x_dpi, $y_dpi );
2328             }
2329              
2330              
2331             ###############################################################################
2332             #
2333             # _process_bmp()
2334             #
2335             # Extract width and height information from a BMP file.
2336             #
2337             # Most of the checks came from old Spredsheet::WriteExcel code.
2338             #
2339             sub _process_bmp {
2340              
2341 1     1   4 my $self = shift;
2342 1         4 my $data = $_[0];
2343 1         2 my $filename = $_[1];
2344 1         2 my $type = 'bmp';
2345              
2346              
2347             # Check that the file is big enough to be a bitmap.
2348 1 50       4 if ( length $data <= 0x36 ) {
2349 0         0 croak "$filename doesn't contain enough data.";
2350             }
2351              
2352              
2353             # Read the bitmap width and height. Verify the sizes.
2354 1         5 my ( $width, $height ) = unpack "x18 V2", $data;
2355              
2356 1 50       4 if ( $width > 0xFFFF ) {
2357 0         0 croak "$filename: largest image width $width supported is 65k.";
2358             }
2359              
2360 1 50       3 if ( $height > 0xFFFF ) {
2361 0         0 croak "$filename: largest image height supported is 65k.";
2362             }
2363              
2364             # Read the bitmap planes and bpp data. Verify them.
2365 1         4 my ( $planes, $bitcount ) = unpack "x26 v2", $data;
2366              
2367 1 50       3 if ( $bitcount != 24 ) {
2368 0         0 croak "$filename isn't a 24bit true color bitmap.";
2369             }
2370              
2371 1 50       4 if ( $planes != 1 ) {
2372 0         0 croak "$filename: only 1 plane supported in bitmap image.";
2373             }
2374              
2375              
2376             # Read the bitmap compression. Verify compression.
2377 1         3 my $compression = unpack "x30 V", $data;
2378              
2379 1 50       3 if ( $compression != 0 ) {
2380 0         0 croak "$filename: compression not supported in bitmap image.";
2381             }
2382              
2383 1         4 return ( $type, $width, $height );
2384             }
2385              
2386              
2387             ###############################################################################
2388             #
2389             # _process_jpg()
2390             #
2391             # Extract width and height information from a JPEG file.
2392             #
2393             sub _process_jpg {
2394              
2395 66     66   146 my $self = shift;
2396 66         138 my $data = $_[0];
2397 66         120 my $filename = $_[1];
2398 66         137 my $type = 'jpeg';
2399 66         102 my $x_dpi = 96;
2400 66         118 my $y_dpi = 96;
2401 66         145 my $width;
2402             my $height;
2403              
2404 66         122 my $offset = 2;
2405 66         131 my $data_length = length $data;
2406              
2407             # Search through the image data to read the JPEG markers.
2408 66         220 while ( $offset < $data_length ) {
2409              
2410 622         1211 my $marker = unpack "n", substr $data, $offset + 0, 2;
2411 622         1138 my $length = unpack "n", substr $data, $offset + 2, 2;
2412              
2413             # Read the height and width in the 0xFFCn elements (except C4, C8 and
2414             # CC which aren't SOF markers).
2415 622 100 100     1905 if ( ( $marker & 0xFFF0 ) == 0xFFC0
      66        
2416             && $marker != 0xFFC4
2417             && $marker != 0xFFCC )
2418             {
2419 66         177 $height = unpack "n", substr $data, $offset + 5, 2;
2420 66         182 $width = unpack "n", substr $data, $offset + 7, 2;
2421             }
2422              
2423             # Read the DPI in the 0xFFE0 element.
2424 622 100       1186 if ( $marker == 0xFFE0 ) {
2425 65         192 my $units = unpack "C", substr $data, $offset + 11, 1;
2426 65         162 my $x_density = unpack "n", substr $data, $offset + 12, 2;
2427 65         161 my $y_density = unpack "n", substr $data, $offset + 14, 2;
2428              
2429 65 100       183 if ( $units == 1 ) {
2430 64         115 $x_dpi = $x_density;
2431 64         129 $y_dpi = $y_density;
2432             }
2433              
2434 65 50       175 if ( $units == 2 ) {
2435 0         0 $x_dpi = $x_density * 2.54;
2436 0         0 $y_dpi = $y_density * 2.54;
2437             }
2438             }
2439              
2440 622         839 $offset = $offset + $length + 2;
2441 622 100       1427 last if $marker == 0xFFDA;
2442             }
2443              
2444 66 50       195 if ( not defined $height ) {
2445 0         0 croak "$filename: no size data found in jpeg image.\n";
2446             }
2447              
2448 66         281 return ( $type, $width, $height, $x_dpi, $y_dpi );
2449             }
2450              
2451              
2452             ###############################################################################
2453             #
2454             # _get_sheet_index()
2455             #
2456             # Convert a sheet name to its index. Return undef otherwise.
2457             #
2458             sub _get_sheet_index {
2459              
2460 9     9   17 my $self = shift;
2461 9         13 my $sheetname = shift;
2462 9         17 my $sheet_index = undef;
2463              
2464 9         20 $sheetname =~ s/^'//;
2465 9         22 $sheetname =~ s/'$//;
2466              
2467 9 50       21 if ( exists $self->{_sheetnames}->{$sheetname} ) {
2468 9         23 return $self->{_sheetnames}->{$sheetname}->{_index};
2469             }
2470             else {
2471 0         0 return undef;
2472             }
2473             }
2474              
2475              
2476             ###############################################################################
2477             #
2478             # set_optimization()
2479             #
2480             # Set the speed/memory optimisation level.
2481             #
2482             sub set_optimization {
2483              
2484 8     8 0 114 my $self = shift;
2485 8 50       35 my $level = defined $_[0] ? $_[0] : 1;
2486              
2487 8 50       69 croak "set_optimization() must be called before add_worksheet()"
2488             if $self->sheets();
2489              
2490 8         32 $self->{_optimization} = $level;
2491             }
2492              
2493              
2494             ###############################################################################
2495             #
2496             # Deprecated methods for backwards compatibility.
2497             #
2498             ###############################################################################
2499              
2500             # No longer required by Excel::Writer::XLSX.
2501       0 0   sub compatibility_mode { }
2502       0 0   sub set_codepage { }
2503              
2504              
2505             ###############################################################################
2506             #
2507             # XML writing methods.
2508             #
2509             ###############################################################################
2510              
2511              
2512             ###############################################################################
2513             #
2514             # _write_workbook()
2515             #
2516             # Write element.
2517             #
2518             sub _write_workbook {
2519              
2520 813     813   2313 my $self = shift;
2521 813         2320 my $schema = 'http://schemas.openxmlformats.org';
2522 813         3555 my $xmlns = $schema . '/spreadsheetml/2006/main';
2523 813         2624 my $xmlns_r = $schema . '/officeDocument/2006/relationships';
2524              
2525 813         3354 my @attributes = (
2526             'xmlns' => $xmlns,
2527             'xmlns:r' => $xmlns_r,
2528             );
2529              
2530 813         5380 $self->xml_start_tag( 'workbook', @attributes );
2531             }
2532              
2533              
2534             ###############################################################################
2535             #
2536             # write_file_version()
2537             #
2538             # Write the element.
2539             #
2540             sub _write_file_version {
2541              
2542 813     813   2315 my $self = shift;
2543 813         2459 my $app_name = 'xl';
2544 813         2153 my $last_edited = 4;
2545 813         2115 my $lowest_edited = 4;
2546 813         2154 my $rup_build = 4505;
2547              
2548 813         3989 my @attributes = (
2549             'appName' => $app_name,
2550             'lastEdited' => $last_edited,
2551             'lowestEdited' => $lowest_edited,
2552             'rupBuild' => $rup_build,
2553             );
2554              
2555 813 100       3693 if ( $self->{_vba_project} ) {
2556 5         17 push @attributes, codeName => '{37E998C4-C9E5-D4B9-71C8-EB1FF731991C}';
2557             }
2558              
2559 813         5685 $self->xml_empty_tag( 'fileVersion', @attributes );
2560             }
2561              
2562              
2563             ###############################################################################
2564             #
2565             # _write_workbook_pr()
2566             #
2567             # Write element.
2568             #
2569             sub _write_workbook_pr {
2570              
2571 813     813   2264 my $self = shift;
2572 813         2322 my $date_1904 = $self->{_date_1904};
2573 813         1846 my $show_ink_annotation = 0;
2574 813         1930 my $auto_compress_pictures = 0;
2575 813         2025 my $default_theme_version = 124226;
2576 813         2132 my $codename = $self->{_vba_codename};
2577 813         1799 my @attributes;
2578              
2579 813 100       3352 push @attributes, ( 'codeName' => $codename ) if $codename;
2580 813 100       3556 push @attributes, ( 'date1904' => 1 ) if $date_1904;
2581 813         3250 push @attributes, ( 'defaultThemeVersion' => $default_theme_version );
2582              
2583 813         3794 $self->xml_empty_tag( 'workbookPr', @attributes );
2584             }
2585              
2586              
2587             ###############################################################################
2588             #
2589             # _write_book_views()
2590             #
2591             # Write element.
2592             #
2593             sub _write_book_views {
2594              
2595 813     813   2201 my $self = shift;
2596              
2597 813         3552 $self->xml_start_tag( 'bookViews' );
2598 813         6750 $self->_write_workbook_view();
2599 813         5702 $self->xml_end_tag( 'bookViews' );
2600             }
2601              
2602             ###############################################################################
2603             #
2604             # _write_workbook_view()
2605             #
2606             # Write element.
2607             #
2608             sub _write_workbook_view {
2609              
2610 825     825   2169 my $self = shift;
2611 825         2510 my $x_window = $self->{_x_window};
2612 825         2154 my $y_window = $self->{_y_window};
2613 825         2279 my $window_width = $self->{_window_width};
2614 825         2118 my $window_height = $self->{_window_height};
2615 825         2236 my $tab_ratio = $self->{_tab_ratio};
2616 825         2421 my $active_tab = $self->{_activesheet};
2617 825         2150 my $first_sheet = $self->{_firstsheet};
2618              
2619 825         4187 my @attributes = (
2620             'xWindow' => $x_window,
2621             'yWindow' => $y_window,
2622             'windowWidth' => $window_width,
2623             'windowHeight' => $window_height,
2624             );
2625              
2626             # Store the tabRatio attribute when it isn't the default.
2627 825 100       3889 push @attributes, ( tabRatio => $tab_ratio ) if $tab_ratio != 600;
2628              
2629             # Store the firstSheet attribute when it isn't the default.
2630 825 100       3573 push @attributes, ( firstSheet => $first_sheet + 1 ) if $first_sheet > 0;
2631              
2632             # Store the activeTab attribute when it isn't the first sheet.
2633 825 100       3525 push @attributes, ( activeTab => $active_tab ) if $active_tab > 0;
2634              
2635 825         4211 $self->xml_empty_tag( 'workbookView', @attributes );
2636             }
2637              
2638             ###############################################################################
2639             #
2640             # _write_sheets()
2641             #
2642             # Write element.
2643             #
2644             sub _write_sheets {
2645              
2646 813     813   2297 my $self = shift;
2647 813         1969 my $id_num = 1;
2648              
2649 813         3901 $self->xml_start_tag( 'sheets' );
2650              
2651 813         2021 for my $worksheet ( @{ $self->{_worksheets} } ) {
  813         3433  
2652             $self->_write_sheet( $worksheet->{_name}, $id_num++,
2653 962         7846 $worksheet->{_hidden} );
2654             }
2655              
2656 813         3644 $self->xml_end_tag( 'sheets' );
2657             }
2658              
2659              
2660             ###############################################################################
2661             #
2662             # _write_sheet()
2663             #
2664             # Write element.
2665             #
2666             sub _write_sheet {
2667              
2668 965     965   2607 my $self = shift;
2669 965         2201 my $name = shift;
2670 965         2151 my $sheet_id = shift;
2671 965         2256 my $hidden = shift;
2672 965         3221 my $r_id = 'rId' . $sheet_id;
2673              
2674 965         3895 my @attributes = (
2675             'name' => $name,
2676             'sheetId' => $sheet_id,
2677             );
2678              
2679 965 100       3561 push @attributes, ( 'state' => 'hidden' ) if $hidden;
2680 965         3362 push @attributes, ( 'r:id' => $r_id );
2681              
2682              
2683 965         4165 $self->xml_empty_tag( 'sheet', @attributes );
2684             }
2685              
2686              
2687             ###############################################################################
2688             #
2689             # _write_calc_pr()
2690             #
2691             # Write element.
2692             #
2693             sub _write_calc_pr {
2694              
2695 816     816   2075 my $self = shift;
2696 816         2369 my $calc_id = $self->{_calc_id};
2697 816         1994 my $concurrent_calc = 0;
2698              
2699 816         3136 my @attributes = ( calcId => $calc_id );
2700              
2701 816 100       6634 if ( $self->{_calc_mode} eq 'manual' ) {
    100          
2702 2         4 push @attributes, 'calcMode' => 'manual';
2703 2         4 push @attributes, 'calcOnSave' => 0;
2704             }
2705             elsif ( $self->{_calc_mode} eq 'autoNoTable' ) {
2706 2         6 push @attributes, calcMode => 'autoNoTable';
2707             }
2708              
2709 816 100       3762 if ( $self->{_calc_on_load} ) {
2710 814         2652 push @attributes, 'fullCalcOnLoad' => 1;
2711             }
2712              
2713              
2714 816         3725 $self->xml_empty_tag( 'calcPr', @attributes );
2715             }
2716              
2717              
2718             ###############################################################################
2719             #
2720             # _write_ext_lst()
2721             #
2722             # Write element.
2723             #
2724             sub _write_ext_lst {
2725              
2726 1     1   14 my $self = shift;
2727              
2728 1         6 $self->xml_start_tag( 'extLst' );
2729 1         6 $self->_write_ext();
2730 1         2 $self->xml_end_tag( 'extLst' );
2731             }
2732              
2733              
2734             ###############################################################################
2735             #
2736             # _write_ext()
2737             #
2738             # Write element.
2739             #
2740             sub _write_ext {
2741              
2742 2     2   21 my $self = shift;
2743 2         5 my $xmlns_mx = 'http://schemas.microsoft.com/office/mac/excel/2008/main';
2744 2         4 my $uri = 'http://schemas.microsoft.com/office/mac/excel/2008/main';
2745              
2746 2         6 my @attributes = (
2747             'xmlns:mx' => $xmlns_mx,
2748             'uri' => $uri,
2749             );
2750              
2751 2         14 $self->xml_start_tag( 'ext', @attributes );
2752 2         14 $self->_write_mx_arch_id();
2753 2         14 $self->xml_end_tag( 'ext' );
2754             }
2755              
2756             ###############################################################################
2757             #
2758             # _write_mx_arch_id()
2759             #
2760             # Write element.
2761             #
2762             sub _write_mx_arch_id {
2763              
2764 3     3   22 my $self = shift;
2765 3         7 my $Flags = 2;
2766              
2767 3         9 my @attributes = ( 'Flags' => $Flags, );
2768              
2769 3         21 $self->xml_empty_tag( 'mx:ArchID', @attributes );
2770             }
2771              
2772              
2773             ##############################################################################
2774             #
2775             # _write_defined_names()
2776             #
2777             # Write the element.
2778             #
2779             sub _write_defined_names {
2780              
2781 814     814   2282 my $self = shift;
2782              
2783 814 100       1830 return unless @{ $self->{_defined_names} };
  814         4593  
2784              
2785 30         145 $self->xml_start_tag( 'definedNames' );
2786              
2787 30         73 for my $aref ( @{ $self->{_defined_names} } ) {
  30         106  
2788 62         246 $self->_write_defined_name( $aref );
2789             }
2790              
2791 30         177 $self->xml_end_tag( 'definedNames' );
2792             }
2793              
2794              
2795             ##############################################################################
2796             #
2797             # _write_defined_name()
2798             #
2799             # Write the element.
2800             #
2801             sub _write_defined_name {
2802              
2803 63     63   154 my $self = shift;
2804 63         124 my $data = shift;
2805              
2806 63         152 my $name = $data->[0];
2807 63         109 my $id = $data->[1];
2808 63         127 my $range = $data->[2];
2809 63         128 my $hidden = $data->[3];
2810              
2811 63         159 my @attributes = ( 'name' => $name );
2812              
2813 63 100       200 push @attributes, ( 'localSheetId' => $id ) if $id != -1;
2814 63 100       252 push @attributes, ( 'hidden' => 1 ) if $hidden;
2815              
2816 63         387 $self->xml_data_element( 'definedName', $range, @attributes );
2817             }
2818              
2819              
2820             1;
2821              
2822              
2823             __END__