File Coverage

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