1package Spreadsheet::WriteExcel::Worksheet;
2
3###############################################################################
4#
5# Worksheet - A writer class for Excel Worksheets.
6#
7#
8# Used in conjunction with Spreadsheet::WriteExcel
9#
10# Copyright 2000-2010, John McNamara, jmcnamara@cpan.org
11#
12# Documentation after __END__
13#
14
15use Exporter;
16use strict;
17use Carp;
18use Spreadsheet::WriteExcel::BIFFwriter;
19use Spreadsheet::WriteExcel::Format;
20use Spreadsheet::WriteExcel::Formula;
21
22
23
24use vars qw($VERSION @ISA);
25@ISA = qw(Spreadsheet::WriteExcel::BIFFwriter);
26
27$VERSION = '2.40';
28
29###############################################################################
30#
31# new()
32#
33# Constructor. Creates a new Worksheet object from a BIFFwriter object
34#
35sub new {
36
37    my $class                     = shift;
38    my $self                      = Spreadsheet::WriteExcel::BIFFwriter->new();
39    my $rowmax                    = 65536;
40    my $colmax                    = 256;
41    my $strmax                    = 0;
42
43    $self->{_name}                = $_[0];
44    $self->{_index}               = $_[1];
45    $self->{_encoding}            = $_[2];
46    $self->{_activesheet}         = $_[3];
47    $self->{_firstsheet}          = $_[4];
48    $self->{_url_format}          = $_[5];
49    $self->{_parser}              = $_[6];
50    $self->{_tempdir}             = $_[7];
51
52    $self->{_str_total}           = $_[8];
53    $self->{_str_unique}          = $_[9];
54    $self->{_str_table}           = $_[10];
55    $self->{_1904}                = $_[11];
56    $self->{_compatibility}       = $_[12];
57    $self->{_palette}             = $_[13];
58
59    $self->{_sheet_type}          = 0x0000;
60    $self->{_ext_sheets}          = [];
61    $self->{_using_tmpfile}       = 1;
62    $self->{_filehandle}          = "";
63    $self->{_fileclosed}          = 0;
64    $self->{_offset}              = 0;
65    $self->{_xls_rowmax}          = $rowmax;
66    $self->{_xls_colmax}          = $colmax;
67    $self->{_xls_strmax}          = $strmax;
68    $self->{_dim_rowmin}          = undef;
69    $self->{_dim_rowmax}          = undef;
70    $self->{_dim_colmin}          = undef;
71    $self->{_dim_colmax}          = undef;
72    $self->{_colinfo}             = [];
73    $self->{_selection}           = [0, 0];
74    $self->{_panes}               = [];
75    $self->{_active_pane}         = 3;
76    $self->{_frozen}              = 0;
77    $self->{_frozen_no_split}     = 1;
78    $self->{_selected}            = 0;
79    $self->{_hidden}              = 0;
80    $self->{_active}              = 0;
81    $self->{_tab_color}           = 0;
82
83    $self->{_first_row}           = 0;
84    $self->{_first_col}           = 0;
85    $self->{_display_formulas}    = 0;
86    $self->{_display_headers}     = 1;
87    $self->{_display_zeros}       = 1;
88    $self->{_display_arabic}      = 0;
89
90    $self->{_paper_size}          = 0x0;
91    $self->{_orientation}         = 0x1;
92    $self->{_header}              = '';
93    $self->{_footer}              = '';
94    $self->{_header_encoding}     = 0;
95    $self->{_footer_encoding}     = 0;
96    $self->{_hcenter}             = 0;
97    $self->{_vcenter}             = 0;
98    $self->{_margin_header}       = 0.50;
99    $self->{_margin_footer}       = 0.50;
100    $self->{_margin_left}         = 0.75;
101    $self->{_margin_right}        = 0.75;
102    $self->{_margin_top}          = 1.00;
103    $self->{_margin_bottom}       = 1.00;
104
105    $self->{_title_rowmin}        = undef;
106    $self->{_title_rowmax}        = undef;
107    $self->{_title_colmin}        = undef;
108    $self->{_title_colmax}        = undef;
109    $self->{_print_rowmin}        = undef;
110    $self->{_print_rowmax}        = undef;
111    $self->{_print_colmin}        = undef;
112    $self->{_print_colmax}        = undef;
113
114    $self->{_print_gridlines}     = 1;
115    $self->{_screen_gridlines}    = 1;
116    $self->{_print_headers}       = 0;
117
118    $self->{_page_order}          = 0;
119    $self->{_black_white}         = 0;
120    $self->{_draft_quality}       = 0;
121    $self->{_print_comments}      = 0;
122    $self->{_page_start}          = 1;
123    $self->{_custom_start}        = 0;
124
125    $self->{_fit_page}            = 0;
126    $self->{_fit_width}           = 0;
127    $self->{_fit_height}          = 0;
128
129    $self->{_hbreaks}             = [];
130    $self->{_vbreaks}             = [];
131
132    $self->{_protect}             = 0;
133    $self->{_password}            = undef;
134
135    $self->{_col_sizes}           = {};
136    $self->{_row_sizes}           = {};
137
138    $self->{_col_formats}         = {};
139    $self->{_row_formats}         = {};
140
141    $self->{_zoom}                = 100;
142    $self->{_print_scale}         = 100;
143    $self->{_page_view}           = 0;
144
145    $self->{_leading_zeros}       = 0;
146
147    $self->{_outline_row_level}   = 0;
148    $self->{_outline_style}       = 0;
149    $self->{_outline_below}       = 1;
150    $self->{_outline_right}       = 1;
151    $self->{_outline_on}          = 1;
152
153    $self->{_write_match}         = [];
154
155    $self->{_object_ids}          = [];
156    $self->{_images}              = {};
157    $self->{_images_array}        = [];
158    $self->{_charts}              = {};
159    $self->{_charts_array}        = [];
160    $self->{_comments}            = {};
161    $self->{_comments_array}      = [];
162    $self->{_comments_author}     = '';
163    $self->{_comments_author_enc} = 0;
164    $self->{_comments_visible}    = 0;
165
166    $self->{_filter_area}         = [];
167    $self->{_filter_count}        = 0;
168    $self->{_filter_on}           = 0;
169
170    $self->{_writing_url}         = 0;
171
172    $self->{_db_indices}          = [];
173
174    $self->{_validations}         = [];
175
176    bless $self, $class;
177    $self->_initialize();
178    return $self;
179}
180
181
182###############################################################################
183#
184# _initialize()
185#
186# Open a tmp file to store the majority of the Worksheet data. If this fails,
187# for example due to write permissions, store the data in memory. This can be
188# slow for large files.
189#
190sub _initialize {
191
192    my $self = shift;
193    my $fh;
194    my $tmp_dir;
195
196    # The following code is complicated by Windows limitations. Porters can
197    # choose a more direct method.
198
199
200
201    # In the default case we use IO::File->new_tmpfile(). This may fail, in
202    # particular with IIS on Windows, so we allow the user to specify a temp
203    # directory via File::Temp.
204    #
205    if (defined $self->{_tempdir}) {
206
207        # Delay loading File:Temp to reduce the module dependencies.
208        eval { require File::Temp };
209        die "The File::Temp module must be installed in order ".
210            "to call set_tempdir().\n" if $@;
211
212
213        # Trap but ignore File::Temp errors.
214        eval { $fh = File::Temp::tempfile(DIR => $self->{_tempdir}) };
215
216        # Store the failed tmp dir in case of errors.
217        $tmp_dir = $self->{_tempdir} || File::Spec->tmpdir if not $fh;
218    }
219    else {
220
221        $fh = IO::File->new_tmpfile();
222
223        # Store the failed tmp dir in case of errors.
224        $tmp_dir = "POSIX::tmpnam() directory" if not $fh;
225    }
226
227
228    # Check if the temp file creation was successful. Else store data in memory.
229    if ($fh) {
230
231        # binmode file whether platform requires it or not.
232        binmode($fh);
233
234        # Store filehandle
235        $self->{_filehandle} = $fh;
236    }
237    else {
238
239        # Set flag to store data in memory if XX::tempfile() failed.
240        $self->{_using_tmpfile} = 0;
241
242        if ($self->{_index} == 0 && $^W) {
243            my $dir = $self->{_tempdir} || File::Spec->tmpdir();
244
245            warn "Unable to create temp files in $tmp_dir. Data will be ".
246                 "stored in memory. Refer to set_tempdir() in the ".
247                 "Spreadsheet::WriteExcel documentation.\n" ;
248        }
249    }
250}
251
252
253###############################################################################
254#
255# _close()
256#
257# Add data to the beginning of the workbook (note the reverse order)
258# and to the end of the workbook.
259#
260sub _close {
261
262    my $self = shift;
263
264    ################################################
265    # Prepend in reverse order!!
266    #
267
268    # Prepend the sheet dimensions
269    $self->_store_dimensions();
270
271    # Prepend the autofilter filters.
272    $self->_store_autofilters;
273
274    # Prepend the sheet autofilter info.
275    $self->_store_autofilterinfo();
276
277    # Prepend the sheet filtermode record.
278    $self->_store_filtermode();
279
280    # Prepend the COLINFO records if they exist
281    if (@{$self->{_colinfo}}){
282        my @colinfo = @{$self->{_colinfo}};
283        while (@colinfo) {
284            my $arrayref = pop @colinfo;
285            $self->_store_colinfo(@$arrayref);
286        }
287    }
288
289    # Prepend the DEFCOLWIDTH record
290    $self->_store_defcol();
291
292    # Prepend the sheet password
293    $self->_store_password();
294
295    # Prepend the sheet protection
296    $self->_store_protect();
297    $self->_store_obj_protect();
298
299    # Prepend the page setup
300    $self->_store_setup();
301
302    # Prepend the bottom margin
303    $self->_store_margin_bottom();
304
305    # Prepend the top margin
306    $self->_store_margin_top();
307
308    # Prepend the right margin
309    $self->_store_margin_right();
310
311    # Prepend the left margin
312    $self->_store_margin_left();
313
314    # Prepend the page vertical centering
315    $self->_store_vcenter();
316
317    # Prepend the page horizontal centering
318    $self->_store_hcenter();
319
320    # Prepend the page footer
321    $self->_store_footer();
322
323    # Prepend the page header
324    $self->_store_header();
325
326    # Prepend the vertical page breaks
327    $self->_store_vbreak();
328
329    # Prepend the horizontal page breaks
330    $self->_store_hbreak();
331
332    # Prepend WSBOOL
333    $self->_store_wsbool();
334
335    # Prepend the default row height.
336    $self->_store_defrow();
337
338    # Prepend GUTS
339    $self->_store_guts();
340
341    # Prepend GRIDSET
342    $self->_store_gridset();
343
344    # Prepend PRINTGRIDLINES
345    $self->_store_print_gridlines();
346
347    # Prepend PRINTHEADERS
348    $self->_store_print_headers();
349
350    #
351    # End of prepend. Read upwards from here.
352    ################################################
353
354    # Append
355    $self->_store_table();
356    $self->_store_images();
357    $self->_store_charts();
358    $self->_store_filters();
359    $self->_store_comments();
360    $self->_store_window2();
361    $self->_store_page_view();
362    $self->_store_zoom();
363    $self->_store_panes(@{$self->{_panes}}) if @{$self->{_panes}};
364    $self->_store_selection(@{$self->{_selection}});
365    $self->_store_validation_count();
366    $self->_store_validations();
367    $self->_store_tab_color();
368    $self->_store_eof();
369
370    # Prepend the BOF and INDEX records
371    $self->_store_index();
372    $self->_store_bof(0x0010);
373}
374
375
376###############################################################################
377#
378# _compatibility_mode()
379#
380# Set the compatibility mode.
381#
382# See the explanation in Workbook::compatibility_mode(). This private method
383# is mainly used for test purposes.
384#
385sub _compatibility_mode {
386
387    my $self      = shift;
388
389    if (defined($_[0])) {
390        $self->{_compatibility} = $_[0];
391    }
392    else {
393        $self->{_compatibility} = 1;
394    }
395}
396
397
398###############################################################################
399#
400# get_name().
401#
402# Retrieve the worksheet name.
403#
404# Note, there is no set_name() method because names are used in formulas and
405# converted to internal indices. Allowing the user to change sheet names
406# after they have been set in add_worksheet() is asking for trouble.
407#
408sub get_name {
409
410    my $self    = shift;
411
412    return $self->{_name};
413}
414
415
416###############################################################################
417#
418# get_data().
419#
420# Retrieves data from memory in one chunk, or from disk in $buffer
421# sized chunks.
422#
423sub get_data {
424
425    my $self   = shift;
426    my $buffer = 4096;
427    my $tmp;
428
429    # Return data stored in memory
430    if (defined $self->{_data}) {
431        $tmp           = $self->{_data};
432        $self->{_data} = undef;
433        my $fh         = $self->{_filehandle};
434        seek($fh, 0, 0) if $self->{_using_tmpfile};
435        return $tmp;
436    }
437
438    # Return data stored on disk
439    if ($self->{_using_tmpfile}) {
440        return $tmp if read($self->{_filehandle}, $tmp, $buffer);
441    }
442
443    # No data to return
444    return undef;
445}
446
447
448###############################################################################
449#
450# select()
451#
452# Set this worksheet as a selected worksheet, i.e. the worksheet has its tab
453# highlighted.
454#
455sub select {
456
457    my $self = shift;
458
459    $self->{_hidden}         = 0; # Selected worksheet can't be hidden.
460    $self->{_selected}       = 1;
461}
462
463
464###############################################################################
465#
466# activate()
467#
468# Set this worksheet as the active worksheet, i.e. the worksheet that is
469# displayed when the workbook is opened. Also set it as selected.
470#
471sub activate {
472
473    my $self = shift;
474
475    $self->{_hidden}         = 0; # Active worksheet can't be hidden.
476    $self->{_selected}       = 1;
477    ${$self->{_activesheet}} = $self->{_index};
478}
479
480
481###############################################################################
482#
483# hide()
484#
485# Hide this worksheet.
486#
487sub hide {
488
489    my $self = shift;
490
491    $self->{_hidden}         = 1;
492
493    # A hidden worksheet shouldn't be active or selected.
494    $self->{_selected}       = 0;
495    ${$self->{_activesheet}} = 0;
496    ${$self->{_firstsheet}}  = 0;
497}
498
499
500###############################################################################
501#
502# set_first_sheet()
503#
504# Set this worksheet as the first visible sheet. This is necessary
505# when there are a large number of worksheets and the activated
506# worksheet is not visible on the screen.
507#
508sub set_first_sheet {
509
510    my $self = shift;
511
512    $self->{_hidden}         = 0; # Active worksheet can't be hidden.
513    ${$self->{_firstsheet}}  = $self->{_index};
514}
515
516
517###############################################################################
518#
519# protect($password)
520#
521# Set the worksheet protection flag to prevent accidental modification and to
522# hide formulas if the locked and hidden format properties have been set.
523#
524sub protect {
525
526    my $self = shift;
527
528    $self->{_protect}   = 1;
529    $self->{_password}  = $self->_encode_password($_[0]) if defined $_[0];
530
531}
532
533
534###############################################################################
535#
536# set_column($firstcol, $lastcol, $width, $format, $hidden, $level)
537#
538# Set the width of a single column or a range of columns.
539# See also: _store_colinfo
540#
541sub set_column {
542
543    my $self = shift;
544    my @data = @_;
545    my $cell = $data[0];
546
547    # Check for a cell reference in A1 notation and substitute row and column
548    if ($cell =~ /^\D/) {
549        @data = $self->_substitute_cellref(@_);
550
551        # Returned values $row1 and $row2 aren't required here. Remove them.
552        shift  @data;       # $row1
553        splice @data, 1, 1; # $row2
554    }
555
556    return if @data < 3; # Ensure at least $firstcol, $lastcol and $width
557    return if not defined $data[0]; # Columns must be defined.
558    return if not defined $data[1];
559
560    # Assume second column is the same as first if 0. Avoids KB918419 bug.
561    $data[1] = $data[0] if $data[1] == 0;
562
563    # Ensure 2nd col is larger than first. Also for KB918419 bug.
564    ($data[0], $data[1]) = ($data[1], $data[0]) if $data[0] > $data[1];
565
566    # Limit columns to Excel max of 255.
567    $data[0] = 255 if $data[0] > 255;
568    $data[1] = 255 if $data[1] > 255;
569
570    push @{$self->{_colinfo}}, [ @data ];
571
572
573    # Store the col sizes for use when calculating image vertices taking
574    # hidden columns into account. Also store the column formats.
575    #
576    my $width  = $data[4] ? 0 : $data[2]; # Set width to zero if col is hidden
577       $width  ||= 0;                     # Ensure width isn't undef.
578    my $format = $data[3];
579
580    my ($firstcol, $lastcol) = @data;
581
582    foreach my $col ($firstcol .. $lastcol) {
583        $self->{_col_sizes}->{$col}   = $width;
584        $self->{_col_formats}->{$col} = $format if defined $format;
585    }
586}
587
588
589###############################################################################
590#
591# set_selection()
592#
593# Set which cell or cells are selected in a worksheet: see also the
594# sub _store_selection
595#
596sub set_selection {
597
598    my $self = shift;
599
600    # Check for a cell reference in A1 notation and substitute row and column
601    if ($_[0] =~ /^\D/) {
602        @_ = $self->_substitute_cellref(@_);
603    }
604
605    $self->{_selection} = [ @_ ];
606}
607
608
609###############################################################################
610#
611# freeze_panes()
612#
613# Set panes and mark them as frozen. See also _store_panes().
614#
615sub freeze_panes {
616
617    my $self = shift;
618
619    # Check for a cell reference in A1 notation and substitute row and column
620    if ($_[0] =~ /^\D/) {
621        @_ = $self->_substitute_cellref(@_);
622    }
623
624    # Extra flag indicated a split and freeze.
625    $self->{_frozen_no_split} = 0 if $_[4];
626
627    $self->{_frozen} = 1;
628    $self->{_panes}  = [ @_ ];
629}
630
631
632###############################################################################
633#
634# split_panes()
635#
636# Set panes and mark them as split. See also _store_panes().
637#
638sub split_panes {
639
640    my $self = shift;
641
642    $self->{_frozen}            = 0;
643    $self->{_frozen_no_split}   = 0;
644    $self->{_panes}             = [ @_ ];
645}
646
647# Older method name for backwards compatibility.
648*thaw_panes = *split_panes;
649
650
651###############################################################################
652#
653# set_portrait()
654#
655# Set the page orientation as portrait.
656#
657sub set_portrait {
658
659    my $self = shift;
660
661    $self->{_orientation} = 1;
662}
663
664
665###############################################################################
666#
667# set_landscape()
668#
669# Set the page orientation as landscape.
670#
671sub set_landscape {
672
673    my $self = shift;
674
675    $self->{_orientation} = 0;
676}
677
678
679###############################################################################
680#
681# set_page_view()
682#
683# Set the page view mode for Mac Excel.
684#
685sub set_page_view {
686
687    my $self = shift;
688
689    $self->{_page_view} = defined $_[0] ? $_[0] : 1;
690}
691
692
693###############################################################################
694#
695# set_tab_color()
696#
697# Set the colour of the worksheet colour.
698#
699sub set_tab_color {
700
701    my $self  = shift;
702
703    my $color = &Spreadsheet::WriteExcel::Format::_get_color($_[0]);
704       $color = 0 if $color == 0x7FFF; # Default color.
705
706    $self->{_tab_color} = $color;
707}
708
709
710###############################################################################
711#
712# set_paper()
713#
714# Set the paper type. Ex. 1 = US Letter, 9 = A4
715#
716sub set_paper {
717
718    my $self = shift;
719
720    $self->{_paper_size} = $_[0] || 0;
721}
722
723
724###############################################################################
725#
726# set_header()
727#
728# Set the page header caption and optional margin.
729#
730sub set_header {
731
732    my $self     = shift;
733    my $string   = $_[0] || '';
734    my $margin   = $_[1] || 0.50;
735    my $encoding = $_[2] || 0;
736
737    # Handle utf8 strings in perl 5.8.
738    if ($] >= 5.008) {
739        require Encode;
740
741        if (Encode::is_utf8($string)) {
742            $string = Encode::encode("UTF-16BE", $string);
743            $encoding = 1;
744        }
745    }
746
747    my $limit    = $encoding ? 255 *2 : 255;
748
749    if (length $string >= $limit) {
750        carp 'Header string must be less than 255 characters';
751        return;
752    }
753
754    $self->{_header}          = $string;
755    $self->{_margin_header}   = $margin;
756    $self->{_header_encoding} = $encoding;
757}
758
759
760###############################################################################
761#
762# set_footer()
763#
764# Set the page footer caption and optional margin.
765#
766sub set_footer {
767
768    my $self     = shift;
769    my $string   = $_[0] || '';
770    my $margin   = $_[1] || 0.50;
771    my $encoding = $_[2] || 0;
772
773    # Handle utf8 strings in perl 5.8.
774    if ($] >= 5.008) {
775        require Encode;
776
777        if (Encode::is_utf8($string)) {
778            $string = Encode::encode("UTF-16BE", $string);
779            $encoding = 1;
780        }
781    }
782
783    my $limit    = $encoding ? 255 *2 : 255;
784
785
786    if (length $string >= $limit) {
787        carp 'Footer string must be less than 255 characters';
788        return;
789    }
790
791    $self->{_footer}          = $string;
792    $self->{_margin_footer}   = $margin;
793    $self->{_footer_encoding} = $encoding;
794}
795
796
797###############################################################################
798#
799# center_horizontally()
800#
801# Center the page horizontally.
802#
803sub center_horizontally {
804
805    my $self = shift;
806
807    if (defined $_[0]) {
808        $self->{_hcenter} = $_[0];
809    }
810    else {
811        $self->{_hcenter} = 1;
812    }
813}
814
815
816###############################################################################
817#
818# center_vertically()
819#
820# Center the page horizontally.
821#
822sub center_vertically {
823
824    my $self = shift;
825
826    if (defined $_[0]) {
827        $self->{_vcenter} = $_[0];
828    }
829    else {
830        $self->{_vcenter} = 1;
831    }
832}
833
834
835###############################################################################
836#
837# set_margins()
838#
839# Set all the page margins to the same value in inches.
840#
841sub set_margins {
842
843    my $self = shift;
844
845    $self->set_margin_left($_[0]);
846    $self->set_margin_right($_[0]);
847    $self->set_margin_top($_[0]);
848    $self->set_margin_bottom($_[0]);
849}
850
851
852###############################################################################
853#
854# set_margins_LR()
855#
856# Set the left and right margins to the same value in inches.
857#
858sub set_margins_LR {
859
860    my $self = shift;
861
862    $self->set_margin_left($_[0]);
863    $self->set_margin_right($_[0]);
864}
865
866
867###############################################################################
868#
869# set_margins_TB()
870#
871# Set the top and bottom margins to the same value in inches.
872#
873sub set_margins_TB {
874
875    my $self = shift;
876
877    $self->set_margin_top($_[0]);
878    $self->set_margin_bottom($_[0]);
879}
880
881
882###############################################################################
883#
884# set_margin_left()
885#
886# Set the left margin in inches.
887#
888sub set_margin_left {
889
890    my $self = shift;
891
892    $self->{_margin_left} = defined $_[0] ? $_[0] : 0.75;
893}
894
895
896###############################################################################
897#
898# set_margin_right()
899#
900# Set the right margin in inches.
901#
902sub set_margin_right {
903
904    my $self = shift;
905
906    $self->{_margin_right} = defined $_[0] ? $_[0] : 0.75;
907}
908
909
910###############################################################################
911#
912# set_margin_top()
913#
914# Set the top margin in inches.
915#
916sub set_margin_top {
917
918    my $self = shift;
919
920    $self->{_margin_top} = defined $_[0] ? $_[0] : 1.00;
921}
922
923
924###############################################################################
925#
926# set_margin_bottom()
927#
928# Set the bottom margin in inches.
929#
930sub set_margin_bottom {
931
932    my $self = shift;
933
934    $self->{_margin_bottom} = defined $_[0] ? $_[0] : 1.00;
935}
936
937
938###############################################################################
939#
940# repeat_rows($first_row, $last_row)
941#
942# Set the rows to repeat at the top of each printed page. See also the
943# _store_name_xxxx() methods in Workbook.pm.
944#
945sub repeat_rows {
946
947    my $self = shift;
948
949    $self->{_title_rowmin}  = $_[0];
950    $self->{_title_rowmax}  = $_[1] || $_[0]; # Second row is optional
951}
952
953
954###############################################################################
955#
956# repeat_columns($first_col, $last_col)
957#
958# Set the columns to repeat at the left hand side of each printed page.
959# See also the _store_names() methods in Workbook.pm.
960#
961sub repeat_columns {
962
963    my $self = shift;
964
965    # Check for a cell reference in A1 notation and substitute row and column
966    if ($_[0] =~ /^\D/) {
967        @_ = $self->_substitute_cellref(@_);
968
969        # Returned values $row1 and $row2 aren't required here. Remove them.
970        shift  @_;       # $row1
971        splice @_, 1, 1; # $row2
972    }
973
974    $self->{_title_colmin}  = $_[0];
975    $self->{_title_colmax}  = $_[1] || $_[0]; # Second col is optional
976}
977
978
979###############################################################################
980#
981# print_area($first_row, $first_col, $last_row, $last_col)
982#
983# Set the area of each worksheet that will be printed. See also the
984# _store_names() methods in Workbook.pm.
985#
986sub print_area {
987
988    my $self = shift;
989
990    # Check for a cell reference in A1 notation and substitute row and column
991    if ($_[0] =~ /^\D/) {
992        @_ = $self->_substitute_cellref(@_);
993    }
994
995    return if @_ != 4; # Require 4 parameters
996
997    $self->{_print_rowmin} = $_[0];
998    $self->{_print_colmin} = $_[1];
999    $self->{_print_rowmax} = $_[2];
1000    $self->{_print_colmax} = $_[3];
1001}
1002
1003
1004###############################################################################
1005#
1006# autofilter($first_row, $first_col, $last_row, $last_col)
1007#
1008# Set the autofilter area in the worksheet.
1009#
1010sub autofilter {
1011
1012    my $self = shift;
1013
1014    # Check for a cell reference in A1 notation and substitute row and column
1015    if ($_[0] =~ /^\D/) {
1016        @_ = $self->_substitute_cellref(@_);
1017    }
1018
1019    return if @_ != 4; # Require 4 parameters
1020
1021    my ($row1, $col1, $row2, $col2) = @_;
1022
1023    # Reverse max and min values if necessary.
1024    ($row1, $row2) = ($row2, $row1) if $row2 < $row1;
1025    ($col1, $col2) = ($col2, $col1) if $col2 < $col1;
1026
1027    # Store the Autofilter information
1028    $self->{_filter_area}  = [$row1, $row2, $col1, $col2];
1029    $self->{_filter_count} = 1+ $col2 -$col1;
1030}
1031
1032
1033###############################################################################
1034#
1035# filter_column($column, $criteria, ...)
1036#
1037# Set the column filter criteria.
1038#
1039sub filter_column {
1040
1041    my $self        = shift;
1042    my $col         = $_[0];
1043    my $expression  = $_[1];
1044
1045
1046    croak "Must call autofilter() before filter_column()"
1047                                                 unless $self->{_filter_count};
1048    croak "Incorrect number of arguments to filter_column()" unless @_ == 2;
1049
1050
1051    # Check for a column reference in A1 notation and substitute.
1052    if ($col =~ /^\D/) {
1053        # Convert col ref to a cell ref and then to a col number.
1054        (undef, $col) = $self->_substitute_cellref($col . '1');
1055    }
1056
1057    my (undef, undef, $col_first, $col_last) = @{$self->{_filter_area}};
1058
1059    # Reject column if it is outside filter range.
1060    if ($col < $col_first or $col > $col_last) {
1061        croak "Column '$col' outside autofilter() column range " .
1062              "($col_first .. $col_last)";
1063    }
1064
1065
1066    my @tokens = $self->_extract_filter_tokens($expression);
1067
1068    croak "Incorrect number of tokens in expression '$expression'"
1069          unless (@tokens == 3 or @tokens == 7);
1070
1071
1072    @tokens = $self->_parse_filter_expression($expression, @tokens);
1073
1074    $self->{_filter_cols}->{$col} = [@tokens];
1075    $self->{_filter_on}           = 1;
1076}
1077
1078
1079###############################################################################
1080#
1081# _extract_filter_tokens($expression)
1082#
1083# Extract the tokens from the filter expression. The tokens are mainly non-
1084# whitespace groups. The only tricky part is to extract string tokens that
1085# contain whitespace and/or quoted double quotes (Excel's escaped quotes).
1086#
1087# Examples: 'x <  2000'
1088#           'x >  2000 and x <  5000'
1089#           'x = "foo"'
1090#           'x = "foo bar"'
1091#           'x = "foo "" bar"'
1092#
1093sub _extract_filter_tokens {
1094
1095    my $self        = shift;
1096    my $expression  = $_[0];
1097
1098    return unless $expression;
1099
1100    my @tokens = ($expression  =~ /"(?:[^"]|"")*"|\S+/g); #"
1101
1102    # Remove leading and trailing quotes and unescape other quotes
1103    for (@tokens) {
1104        s/^"//;     #"
1105        s/"$//;     #"
1106        s/""/"/g;   #"
1107    }
1108
1109    return @tokens;
1110}
1111
1112
1113###############################################################################
1114#
1115# _parse_filter_expression(@token)
1116#
1117# Converts the tokens of a possibly conditional expression into 1 or 2
1118# sub expressions for further parsing.
1119#
1120# Examples:
1121#          ('x', '==', 2000) -> exp1
1122#          ('x', '>',  2000, 'and', 'x', '<', 5000) -> exp1 and exp2
1123#
1124sub _parse_filter_expression {
1125
1126    my $self        = shift;
1127    my $expression  = shift;
1128    my @tokens      = @_;
1129
1130    # The number of tokens will be either 3 (for 1 expression)
1131    # or 7 (for 2  expressions).
1132    #
1133    if (@tokens == 7) {
1134
1135        my $conditional = $tokens[3];
1136
1137        if    ($conditional =~ /^(and|&&)$/) {
1138            $conditional = 0;
1139        }
1140        elsif ($conditional =~ /^(or|\|\|)$/) {
1141            $conditional = 1;
1142        }
1143        else {
1144            croak "Token '$conditional' is not a valid conditional " .
1145                  "in filter expression '$expression'";
1146        }
1147
1148        my @expression_1 = $self->_parse_filter_tokens($expression,
1149                                                       @tokens[0, 1, 2]);
1150        my @expression_2 = $self->_parse_filter_tokens($expression,
1151                                                       @tokens[4, 5, 6]);
1152
1153        return (@expression_1, $conditional, @expression_2);
1154    }
1155    else {
1156        return $self->_parse_filter_tokens($expression, @tokens);
1157    }
1158}
1159
1160
1161###############################################################################
1162#
1163# _parse_filter_tokens(@token)
1164#
1165# Parse the 3 tokens of a filter expression and return the operator and token.
1166#
1167sub _parse_filter_tokens {
1168
1169    my $self        = shift;
1170    my $expression  = shift;
1171    my @tokens      = @_;
1172
1173    my %operators = (
1174                        '==' => 2,
1175                        '='  => 2,
1176                        '=~' => 2,
1177                        'eq' => 2,
1178
1179                        '!=' => 5,
1180                        '!~' => 5,
1181                        'ne' => 5,
1182                        '<>' => 5,
1183
1184                        '<'  => 1,
1185                        '<=' => 3,
1186                        '>'  => 4,
1187                        '>=' => 6,
1188                    );
1189
1190    my $operator = $operators{$tokens[1]};
1191    my $token    = $tokens[2];
1192
1193
1194    # Special handling of "Top" filter expressions.
1195    if ($tokens[0] =~ /^top|bottom$/i) {
1196
1197        my $value = $tokens[1];
1198
1199        if ($value =~ /\D/ or
1200            $value < 1     or
1201            $value > 500)
1202        {
1203            croak "The value '$value' in expression '$expression' " .
1204                   "must be in the range 1 to 500";
1205        }
1206
1207        $token = lc $token;
1208
1209        if ($token ne 'items' and $token ne '%') {
1210            croak "The type '$token' in expression '$expression' " .
1211                   "must be either 'items' or '%'";
1212        }
1213
1214        if ($tokens[0] =~ /^top$/i) {
1215            $operator = 30;
1216        }
1217        else {
1218            $operator = 32;
1219        }
1220
1221        if ($tokens[2] eq '%') {
1222            $operator++;
1223        }
1224
1225        $token    = $value;
1226    }
1227
1228
1229    if (not $operator and $tokens[0]) {
1230        croak "Token '$tokens[1]' is not a valid operator " .
1231              "in filter expression '$expression'";
1232    }
1233
1234
1235    # Special handling for Blanks/NonBlanks.
1236    if ($token =~ /^blanks|nonblanks$/i) {
1237
1238        # Only allow Equals or NotEqual in this context.
1239        if ($operator != 2 and $operator != 5) {
1240            croak "The operator '$tokens[1]' in expression '$expression' " .
1241                   "is not valid in relation to Blanks/NonBlanks'";
1242        }
1243
1244        $token = lc $token;
1245
1246        # The operator should always be 2 (=) to flag a "simple" equality in
1247        # the binary record. Therefore we convert <> to =.
1248        if ($token eq 'blanks') {
1249            if ($operator == 5) {
1250                $operator = 2;
1251                $token    = 'nonblanks';
1252            }
1253        }
1254        else {
1255            if ($operator == 5) {
1256                $operator = 2;
1257                $token    = 'blanks';
1258            }
1259        }
1260    }
1261
1262
1263    # if the string token contains an Excel match character then change the
1264    # operator type to indicate a non "simple" equality.
1265    if ($operator == 2 and $token =~ /[*?]/) {
1266        $operator = 22;
1267    }
1268
1269
1270    return ($operator, $token);
1271}
1272
1273
1274###############################################################################
1275#
1276# hide_gridlines()
1277#
1278# Set the option to hide gridlines on the screen and the printed page.
1279# There are two ways of doing this in the Excel BIFF format: The first is by
1280# setting the DspGrid field of the WINDOW2 record, this turns off the screen
1281# and subsequently the print gridline. The second method is to via the
1282# PRINTGRIDLINES and GRIDSET records, this turns off the printed gridlines
1283# only. The first method is probably sufficient for most cases. The second
1284# method is supported for backwards compatibility. Porters take note.
1285#
1286sub hide_gridlines {
1287
1288    my $self   = shift;
1289    my $option = $_[0];
1290
1291    $option = 1 unless defined $option; # Default to hiding printed gridlines
1292
1293    if ($option == 0) {
1294        $self->{_print_gridlines}  = 1; # 1 = display, 0 = hide
1295        $self->{_screen_gridlines} = 1;
1296    }
1297    elsif ($option == 1) {
1298        $self->{_print_gridlines}  = 0;
1299        $self->{_screen_gridlines} = 1;
1300    }
1301    else {
1302        $self->{_print_gridlines}  = 0;
1303        $self->{_screen_gridlines} = 0;
1304    }
1305}
1306
1307
1308###############################################################################
1309#
1310# print_row_col_headers()
1311#
1312# Set the option to print the row and column headers on the printed page.
1313# See also the _store_print_headers() method below.
1314#
1315sub print_row_col_headers {
1316
1317    my $self = shift;
1318
1319    if (defined $_[0]) {
1320        $self->{_print_headers} = $_[0];
1321    }
1322    else {
1323        $self->{_print_headers} = 1;
1324    }
1325}
1326
1327
1328###############################################################################
1329#
1330# fit_to_pages($width, $height)
1331#
1332# Store the vertical and horizontal number of pages that will define the
1333# maximum area printed. See also _store_setup() and _store_wsbool() below.
1334#
1335sub fit_to_pages {
1336
1337    my $self = shift;
1338
1339    $self->{_fit_page}      = 1;
1340    $self->{_fit_width}     = $_[0] || 0;
1341    $self->{_fit_height}    = $_[1] || 0;
1342}
1343
1344
1345###############################################################################
1346#
1347# set_h_pagebreaks(@breaks)
1348#
1349# Store the horizontal page breaks on a worksheet.
1350#
1351sub set_h_pagebreaks {
1352
1353    my $self = shift;
1354
1355    push @{$self->{_hbreaks}}, @_;
1356}
1357
1358
1359###############################################################################
1360#
1361# set_v_pagebreaks(@breaks)
1362#
1363# Store the vertical page breaks on a worksheet.
1364#
1365sub set_v_pagebreaks {
1366
1367    my $self = shift;
1368
1369    push @{$self->{_vbreaks}}, @_;
1370}
1371
1372
1373###############################################################################
1374#
1375# set_zoom($scale)
1376#
1377# Set the worksheet zoom factor.
1378#
1379sub set_zoom {
1380
1381    my $self  = shift;
1382    my $scale = $_[0] || 100;
1383
1384    # Confine the scale to Excel's range
1385    if ($scale < 10 or $scale > 400) {
1386        carp "Zoom factor $scale outside range: 10 <= zoom <= 400";
1387        $scale = 100;
1388    }
1389
1390    $self->{_zoom} = int $scale;
1391}
1392
1393
1394###############################################################################
1395#
1396# set_print_scale($scale)
1397#
1398# Set the scale factor for the printed page.
1399#
1400sub set_print_scale {
1401
1402    my $self  = shift;
1403    my $scale = $_[0] || 100;
1404
1405    # Confine the scale to Excel's range
1406    if ($scale < 10 or $scale > 400) {
1407        carp "Print scale $scale outside range: 10 <= zoom <= 400";
1408        $scale = 100;
1409    }
1410
1411    # Turn off "fit to page" option
1412    $self->{_fit_page}    = 0;
1413
1414    $self->{_print_scale} = int $scale;
1415}
1416
1417
1418###############################################################################
1419#
1420# keep_leading_zeros()
1421#
1422# Causes the write() method to treat integers with a leading zero as a string.
1423# This ensures that any leading zeros such, as in zip codes, are maintained.
1424#
1425sub keep_leading_zeros {
1426
1427    my $self = shift;
1428
1429    if (defined $_[0]) {
1430        $self->{_leading_zeros} = $_[0];
1431    }
1432    else {
1433        $self->{_leading_zeros} = 1;
1434    }
1435}
1436
1437
1438###############################################################################
1439#
1440# show_comments()
1441#
1442# Make any comments in the worksheet visible.
1443#
1444sub show_comments {
1445
1446    my $self = shift;
1447
1448    $self->{_comments_visible} = defined $_[0] ? $_[0] : 1;
1449}
1450
1451
1452###############################################################################
1453#
1454# set_comments_author()
1455#
1456# Set the default author of the cell comments.
1457#
1458sub set_comments_author {
1459
1460    my $self = shift;
1461
1462    $self->{_comments_author}     = defined $_[0] ? $_[0] : '';
1463    $self->{_comments_author_enc} =         $_[1] ? 1     : 0;
1464}
1465
1466
1467###############################################################################
1468#
1469# right_to_left()
1470#
1471# Display the worksheet right to left for some eastern versions of Excel.
1472#
1473sub right_to_left {
1474
1475    my $self = shift;
1476
1477    $self->{_display_arabic} = defined $_[0] ? $_[0] : 1;
1478}
1479
1480
1481###############################################################################
1482#
1483# hide_zero()
1484#
1485# Hide cell zero values.
1486#
1487sub hide_zero {
1488
1489    my $self = shift;
1490
1491    $self->{_display_zeros} = defined $_[0] ? not $_[0] : 0;
1492}
1493
1494
1495###############################################################################
1496#
1497# print_across()
1498#
1499# Set the order in which pages are printed.
1500#
1501sub print_across {
1502
1503    my $self = shift;
1504
1505    $self->{_page_order} = defined $_[0] ? $_[0] : 1;
1506}
1507
1508
1509###############################################################################
1510#
1511# set_start_page()
1512#
1513# Set the start page number.
1514#
1515sub set_start_page {
1516
1517    my $self = shift;
1518    return unless defined $_[0];
1519
1520    $self->{_page_start}    = $_[0];
1521    $self->{_custom_start}  = 1;
1522}
1523
1524
1525###############################################################################
1526#
1527# set_first_row_column()
1528#
1529# Set the topmost and leftmost visible row and column.
1530# TODO: Document this when tested fully for interaction with panes.
1531#
1532sub set_first_row_column {
1533
1534    my $self = shift;
1535
1536    my $row  = $_[0] || 0;
1537    my $col  = $_[1] || 0;
1538
1539    $row = 65535 if $row > 65535;
1540    $col = 255   if $col > 255;
1541
1542    $self->{_first_row} = $row;
1543    $self->{_first_col} = $col;
1544}
1545
1546
1547###############################################################################
1548#
1549# add_write_handler($re, $code_ref)
1550#
1551# Allow the user to add their own matches and handlers to the write() method.
1552#
1553sub add_write_handler {
1554
1555    my $self = shift;
1556
1557    return unless @_ == 2;
1558    return unless ref $_[1] eq 'CODE';
1559
1560    push @{$self->{_write_match}}, [ @_ ];
1561}
1562
1563
1564
1565###############################################################################
1566#
1567# write($row, $col, $token, $format)
1568#
1569# Parse $token and call appropriate write method. $row and $column are zero
1570# indexed. $format is optional.
1571#
1572# The write_url() methods have a flag to prevent recursion when writing a
1573# string that looks like a url.
1574#
1575# Returns: return value of called subroutine
1576#
1577sub write {
1578
1579    my $self = shift;
1580
1581    # Check for a cell reference in A1 notation and substitute row and column
1582    if ($_[0] =~ /^\D/) {
1583        @_ = $self->_substitute_cellref(@_);
1584    }
1585
1586    my $token = $_[2];
1587
1588    # Handle undefs as blanks
1589    $token = '' unless defined $token;
1590
1591
1592    # First try user defined matches.
1593    for my $aref (@{$self->{_write_match}}) {
1594        my $re  = $aref->[0];
1595        my $sub = $aref->[1];
1596
1597        if ($token =~ /$re/) {
1598            my $match = &$sub($self, @_);
1599            return $match if defined $match;
1600        }
1601    }
1602
1603
1604    # Match an array ref.
1605    if (ref $token eq "ARRAY") {
1606        return $self->write_row(@_);
1607    }
1608    # Match integer with leading zero(s)
1609    elsif ($self->{_leading_zeros} and $token =~ /^0\d+$/) {
1610        return $self->write_string(@_);
1611    }
1612    # Match number
1613    elsif ($token =~ /^([+-]?)(?=\d|\.\d)\d*(\.\d*)?([Ee]([+-]?\d+))?$/) {
1614        return $self->write_number(@_);
1615    }
1616    # Match http, https or ftp URL
1617    elsif ($token =~ m|^[fh]tt?ps?://|    and not $self->{_writing_url}) {
1618        return $self->write_url(@_);
1619    }
1620    # Match mailto:
1621    elsif ($token =~ m/^mailto:/          and not $self->{_writing_url}) {
1622        return $self->write_url(@_);
1623    }
1624    # Match internal or external sheet link
1625    elsif ($token =~ m[^(?:in|ex)ternal:] and not $self->{_writing_url}) {
1626        return $self->write_url(@_);
1627    }
1628    # Match formula
1629    elsif ($token =~ /^=/) {
1630        return $self->write_formula(@_);
1631    }
1632    # Match blank
1633    elsif ($token eq '') {
1634        splice @_, 2, 1; # remove the empty string from the parameter list
1635        return $self->write_blank(@_);
1636    }
1637    else {
1638        return $self->write_string(@_);
1639    }
1640}
1641
1642
1643###############################################################################
1644#
1645# write_row($row, $col, $array_ref, $format)
1646#
1647# Write a row of data starting from ($row, $col). Call write_col() if any of
1648# the elements of the array ref are in turn array refs. This allows the writing
1649# of 1D or 2D arrays of data in one go.
1650#
1651# Returns: the first encountered error value or zero for no errors
1652#
1653sub write_row {
1654
1655    my $self = shift;
1656
1657
1658    # Check for a cell reference in A1 notation and substitute row and column
1659    if ($_[0] =~ /^\D/) {
1660        @_ = $self->_substitute_cellref(@_);
1661    }
1662
1663    # Catch non array refs passed by user.
1664    if (ref $_[2] ne 'ARRAY') {
1665        croak "Not an array ref in call to write_row()$!";
1666    }
1667
1668    my $row     = shift;
1669    my $col     = shift;
1670    my $tokens  = shift;
1671    my @options = @_;
1672    my $error   = 0;
1673    my $ret;
1674
1675    foreach my $token (@$tokens) {
1676
1677        # Check for nested arrays
1678        if (ref $token eq "ARRAY") {
1679            $ret = $self->write_col($row, $col, $token, @options);
1680        } else {
1681            $ret = $self->write    ($row, $col, $token, @options);
1682        }
1683
1684        # Return only the first error encountered, if any.
1685        $error ||= $ret;
1686        $col++;
1687    }
1688
1689    return $error;
1690}
1691
1692
1693###############################################################################
1694#
1695# write_col($row, $col, $array_ref, $format)
1696#
1697# Write a column of data starting from ($row, $col). Call write_row() if any of
1698# the elements of the array ref are in turn array refs. This allows the writing
1699# of 1D or 2D arrays of data in one go.
1700#
1701# Returns: the first encountered error value or zero for no errors
1702#
1703sub write_col {
1704
1705    my $self = shift;
1706
1707
1708    # Check for a cell reference in A1 notation and substitute row and column
1709    if ($_[0] =~ /^\D/) {
1710        @_ = $self->_substitute_cellref(@_);
1711    }
1712
1713    # Catch non array refs passed by user.
1714    if (ref $_[2] ne 'ARRAY') {
1715        croak "Not an array ref in call to write_col()$!";
1716    }
1717
1718    my $row     = shift;
1719    my $col     = shift;
1720    my $tokens  = shift;
1721    my @options = @_;
1722    my $error   = 0;
1723    my $ret;
1724
1725    foreach my $token (@$tokens) {
1726
1727        # write() will deal with any nested arrays
1728        $ret = $self->write($row, $col, $token, @options);
1729
1730        # Return only the first error encountered, if any.
1731        $error ||= $ret;
1732        $row++;
1733    }
1734
1735    return $error;
1736}
1737
1738
1739###############################################################################
1740#
1741# write_comment($row, $col, $comment)
1742#
1743# Write a comment to the specified row and column (zero indexed).
1744#
1745# Returns  0 : normal termination
1746#         -1 : insufficient number of arguments
1747#         -2 : row or column out of range
1748#
1749sub write_comment {
1750
1751    my $self = shift;
1752
1753
1754    # Check for a cell reference in A1 notation and substitute row and column
1755    if ($_[0] =~ /^\D/) {
1756        @_ = $self->_substitute_cellref(@_);
1757    }
1758
1759    if (@_ < 3) { return -1 } # Check the number of args
1760
1761
1762    my $row = $_[0];
1763    my $col = $_[1];
1764
1765    # Check for pairs of optional arguments, i.e. an odd number of args.
1766    croak "Uneven number of additional arguments" unless @_ % 2;
1767
1768
1769    # Check that row and col are valid and store max and min values
1770    return -2 if $self->_check_dimensions($row, $col);
1771
1772
1773    # We have to avoid duplicate comments in cells or else Excel will complain.
1774    $self->{_comments}->{$row}->{$col} = [ $self->_comment_params(@_) ];
1775
1776}
1777
1778
1779###############################################################################
1780#
1781# _XF()
1782#
1783# Returns an index to the XF record in the workbook.
1784#
1785# Note: this is a function, not a method.
1786#
1787sub _XF {
1788
1789    my $self   = $_[0];
1790    my $row    = $_[1];
1791    my $col    = $_[2];
1792    my $format = $_[3];
1793
1794    my $error = "Error: refer to merge_range() in the documentation. " .
1795                 "Can't use previously merged format in non-merged cell";
1796
1797    if (ref($format)) {
1798        # Temp code to prevent merged formats in non-merged cells.
1799        croak $error if $format->{_used_merge} == 1;
1800        $format->{_used_merge} = -1;
1801
1802        return $format->get_xf_index();
1803    }
1804    elsif (exists $self->{_row_formats}->{$row}) {
1805        # Temp code to prevent merged formats in non-merged cells.
1806        croak $error if $self->{_row_formats}->{$row}->{_used_merge} == 1;
1807        $self->{_row_formats}->{$row}->{_used_merge} = -1;
1808
1809        return $self->{_row_formats}->{$row}->get_xf_index();
1810    }
1811    elsif (exists $self->{_col_formats}->{$col}) {
1812        # Temp code to prevent merged formats in non-merged cells.
1813        croak $error if $self->{_col_formats}->{$col}->{_used_merge} == 1;
1814        $self->{_col_formats}->{$col}->{_used_merge} = -1;
1815
1816        return $self->{_col_formats}->{$col}->get_xf_index();
1817    }
1818    else {
1819        return 0x0F;
1820    }
1821}
1822
1823
1824###############################################################################
1825###############################################################################
1826#
1827# Internal methods
1828#
1829
1830
1831###############################################################################
1832#
1833# _append(), overridden.
1834#
1835# Store Worksheet data in memory using the base class _append() or to a
1836# temporary file, the default.
1837#
1838sub _append {
1839
1840    my $self = shift;
1841    my $data = '';
1842
1843    if ($self->{_using_tmpfile}) {
1844        $data = join('', @_);
1845
1846        # Add CONTINUE records if necessary
1847        $data = $self->_add_continue($data) if length($data) > $self->{_limit};
1848
1849        # Protect print() from -l on the command line.
1850        local $\ = undef;
1851
1852        print {$self->{_filehandle}} $data;
1853        $self->{_datasize} += length($data);
1854    }
1855    else {
1856        $data = $self->SUPER::_append(@_);
1857    }
1858
1859    return $data;
1860}
1861
1862
1863###############################################################################
1864#
1865# _substitute_cellref()
1866#
1867# Substitute an Excel cell reference in A1 notation for  zero based row and
1868# column values in an argument list.
1869#
1870# Ex: ("A4", "Hello") is converted to (3, 0, "Hello").
1871#
1872sub _substitute_cellref {
1873
1874    my $self = shift;
1875    my $cell = uc(shift);
1876
1877    # Convert a column range: 'A:A' or 'B:G'.
1878    # A range such as A:A is equivalent to A1:65536, so add rows as required
1879    if ($cell =~ /\$?([A-I]?[A-Z]):\$?([A-I]?[A-Z])/) {
1880        my ($row1, $col1) =  $self->_cell_to_rowcol($1 .'1');
1881        my ($row2, $col2) =  $self->_cell_to_rowcol($2 .'65536');
1882        return $row1, $col1, $row2, $col2, @_;
1883    }
1884
1885    # Convert a cell range: 'A1:B7'
1886    if ($cell =~ /\$?([A-I]?[A-Z]\$?\d+):\$?([A-I]?[A-Z]\$?\d+)/) {
1887        my ($row1, $col1) =  $self->_cell_to_rowcol($1);
1888        my ($row2, $col2) =  $self->_cell_to_rowcol($2);
1889        return $row1, $col1, $row2, $col2, @_;
1890    }
1891
1892    # Convert a cell reference: 'A1' or 'AD2000'
1893    if ($cell =~ /\$?([A-I]?[A-Z]\$?\d+)/) {
1894        my ($row1, $col1) =  $self->_cell_to_rowcol($1);
1895        return $row1, $col1, @_;
1896
1897    }
1898
1899    croak("Unknown cell reference $cell");
1900}
1901
1902
1903###############################################################################
1904#
1905# _cell_to_rowcol($cell_ref)
1906#
1907# Convert an Excel cell reference in A1 notation to a zero based row and column
1908# reference; converts C1 to (0, 2).
1909#
1910# Returns: row, column
1911#
1912sub _cell_to_rowcol {
1913
1914    my $self = shift;
1915    my $cell = shift;
1916
1917    $cell =~ /\$?([A-I]?[A-Z])\$?(\d+)/;
1918
1919    my $col     = $1;
1920    my $row     = $2;
1921
1922    # Convert base26 column string to number
1923    # All your Base are belong to us.
1924    my @chars = split //, $col;
1925    my $expn  = 0;
1926    $col      = 0;
1927
1928    while (@chars) {
1929        my $char = pop(@chars); # LS char first
1930        $col += (ord($char) -ord('A') +1) * (26**$expn);
1931        $expn++;
1932    }
1933
1934    # Convert 1-index to zero-index
1935    $row--;
1936    $col--;
1937
1938    return $row, $col;
1939}
1940
1941
1942###############################################################################
1943#
1944# _sort_pagebreaks()
1945#
1946#
1947# This is an internal method that is used to filter elements of the array of
1948# pagebreaks used in the _store_hbreak() and _store_vbreak() methods. It:
1949#   1. Removes duplicate entries from the list.
1950#   2. Sorts the list.
1951#   3. Removes 0 from the list if present.
1952#
1953sub _sort_pagebreaks {
1954
1955    my $self= shift;
1956
1957    my %hash;
1958    my @array;
1959
1960    @hash{@_} = undef;                       # Hash slice to remove duplicates
1961    @array    = sort {$a <=> $b} keys %hash; # Numerical sort
1962    shift @array if $array[0] == 0;          # Remove zero
1963
1964    # 1000 vertical pagebreaks appears to be an internal Excel 5 limit.
1965    # It is slightly higher in Excel 97/200, approx. 1026
1966    splice(@array, 1000) if (@array > 1000);
1967
1968    return @array
1969}
1970
1971
1972###############################################################################
1973#
1974# _encode_password($password)
1975#
1976# Based on the algorithm provided by Daniel Rentz of OpenOffice.
1977#
1978#
1979sub _encode_password {
1980
1981    use integer;
1982
1983    my $self      = shift;
1984    my $plaintext = $_[0];
1985    my $password;
1986    my $count;
1987    my @chars;
1988    my $i = 0;
1989
1990    $count = @chars = split //, $plaintext;
1991
1992    foreach my $char (@chars) {
1993        my $low_15;
1994        my $high_15;
1995        $char     = ord($char) << ++$i;
1996        $low_15   = $char & 0x7fff;
1997        $high_15  = $char & 0x7fff << 15;
1998        $high_15  = $high_15 >> 15;
1999        $char     = $low_15 | $high_15;
2000    }
2001
2002    $password  = 0x0000;
2003    $password ^= $_ for @chars;
2004    $password ^= $count;
2005    $password ^= 0xCE4B;
2006
2007    return $password;
2008}
2009
2010
2011###############################################################################
2012#
2013# outline_settings($visible, $symbols_below, $symbols_right, $auto_style)
2014#
2015# This method sets the properties for outlining and grouping. The defaults
2016# correspond to Excel's defaults.
2017#
2018sub outline_settings {
2019
2020    my $self                = shift;
2021
2022    $self->{_outline_on}    = defined $_[0] ? $_[0] : 1;
2023    $self->{_outline_below} = defined $_[1] ? $_[1] : 1;
2024    $self->{_outline_right} = defined $_[2] ? $_[2] : 1;
2025    $self->{_outline_style} =         $_[3] || 0;
2026
2027    # Ensure this is a boolean vale for Window2
2028    $self->{_outline_on}    = 1 if $self->{_outline_on};
2029}
2030
2031
2032
2033
2034###############################################################################
2035###############################################################################
2036#
2037# BIFF RECORDS
2038#
2039
2040
2041###############################################################################
2042#
2043# write_number($row, $col, $num, $format)
2044#
2045# Write a double to the specified row and column (zero indexed).
2046# An integer can be written as a double. Excel will display an
2047# integer. $format is optional.
2048#
2049# Returns  0 : normal termination
2050#         -1 : insufficient number of arguments
2051#         -2 : row or column out of range
2052#
2053sub write_number {
2054
2055    my $self = shift;
2056
2057    # Check for a cell reference in A1 notation and substitute row and column
2058    if ($_[0] =~ /^\D/) {
2059        @_ = $self->_substitute_cellref(@_);
2060    }
2061
2062    if (@_ < 3) { return -1 }                    # Check the number of args
2063
2064    my $record  = 0x0203;                        # Record identifier
2065    my $length  = 0x000E;                        # Number of bytes to follow
2066
2067    my $row     = $_[0];                         # Zero indexed row
2068    my $col     = $_[1];                         # Zero indexed column
2069    my $num     = $_[2];
2070    my $xf      = _XF($self, $row, $col, $_[3]); # The cell format
2071
2072    # Check that row and col are valid and store max and min values
2073    return -2 if $self->_check_dimensions($row, $col);
2074
2075    my $header    = pack("vv",  $record, $length);
2076    my $data      = pack("vvv", $row, $col, $xf);
2077    my $xl_double = pack("d",   $num);
2078
2079    if ($self->{_byte_order}) { $xl_double = reverse $xl_double }
2080
2081    # Store the data or write immediately depending on the compatibility mode.
2082    if ($self->{_compatibility}) {
2083        $self->{_table}->[$row]->[$col] = $header . $data . $xl_double;
2084    }
2085    else {
2086        $self->_append($header, $data, $xl_double);
2087    }
2088
2089    return 0;
2090}
2091
2092
2093###############################################################################
2094#
2095# write_string ($row, $col, $string, $format)
2096#
2097# Write a string to the specified row and column (zero indexed).
2098# $format is optional.
2099# Returns  0 : normal termination
2100#         -1 : insufficient number of arguments
2101#         -2 : row or column out of range
2102#         -3 : long string truncated to max chars
2103#
2104sub write_string {
2105
2106    my $self = shift;
2107
2108    # Check for a cell reference in A1 notation and substitute row and column
2109    if ($_[0] =~ /^\D/) {
2110        @_ = $self->_substitute_cellref(@_);
2111    }
2112
2113    if (@_ < 3) { return -1 }                        # Check the number of args
2114
2115    my $record      = 0x00FD;                        # Record identifier
2116    my $length      = 0x000A;                        # Bytes to follow
2117
2118    my $row         = $_[0];                         # Zero indexed row
2119    my $col         = $_[1];                         # Zero indexed column
2120    my $strlen      = length($_[2]);
2121    my $str         = $_[2];
2122    my $xf          = _XF($self, $row, $col, $_[3]); # The cell format
2123    my $encoding    = 0x0;
2124    my $str_error   = 0;
2125
2126
2127    # Handle utf8 strings in perl 5.8.
2128    if ($] >= 5.008) {
2129        require Encode;
2130
2131        if (Encode::is_utf8($str)) {
2132            my $tmp = Encode::encode("UTF-16LE", $str);
2133            return $self->write_utf16le_string($row, $col, $tmp, $_[3]);
2134        }
2135    }
2136
2137
2138    # Check that row and col are valid and store max and min values
2139    return -2 if $self->_check_dimensions($row, $col);
2140
2141    # Limit the string to the max number of chars.
2142    if ($strlen > 32767) {
2143        $str       = substr($str, 0, 32767);
2144        $str_error = -3;
2145    }
2146
2147
2148    # Prepend the string with the type.
2149    my $str_header  = pack("vC", length($str), $encoding);
2150    $str            = $str_header . $str;
2151
2152
2153    if (not exists ${$self->{_str_table}}->{$str}) {
2154        ${$self->{_str_table}}->{$str} = ${$self->{_str_unique}}++;
2155    }
2156
2157
2158    ${$self->{_str_total}}++;
2159
2160
2161    my $header = pack("vv",   $record, $length);
2162    my $data   = pack("vvvV", $row, $col, $xf, ${$self->{_str_table}}->{$str});
2163
2164
2165    # Store the data or write immediately depending on the compatibility mode.
2166    if ($self->{_compatibility}) {
2167        $self->{_table}->[$row]->[$col] = $header . $data;
2168    }
2169    else {
2170        $self->_append($header, $data);
2171    }
2172
2173    return $str_error;
2174}
2175
2176
2177###############################################################################
2178#
2179# write_blank($row, $col, $format)
2180#
2181# Write a blank cell to the specified row and column (zero indexed).
2182# A blank cell is used to specify formatting without adding a string
2183# or a number.
2184#
2185# A blank cell without a format serves no purpose. Therefore, we don't write
2186# a BLANK record unless a format is specified. This is mainly an optimisation
2187# for the write_row() and write_col() methods.
2188#
2189# Returns  0 : normal termination (including no format)
2190#         -1 : insufficient number of arguments
2191#         -2 : row or column out of range
2192#
2193sub write_blank {
2194
2195    my $self = shift;
2196
2197    # Check for a cell reference in A1 notation and substitute row and column
2198    if ($_[0] =~ /^\D/) {
2199        @_ = $self->_substitute_cellref(@_);
2200    }
2201
2202    # Check the number of args
2203    return -1 if @_ < 2;
2204
2205    # Don't write a blank cell unless it has a format
2206    return 0 if not defined $_[2];
2207
2208
2209    my $record  = 0x0201;                        # Record identifier
2210    my $length  = 0x0006;                        # Number of bytes to follow
2211
2212    my $row     = $_[0];                         # Zero indexed row
2213    my $col     = $_[1];                         # Zero indexed column
2214    my $xf      = _XF($self, $row, $col, $_[2]); # The cell format
2215
2216    # Check that row and col are valid and store max and min values
2217    return -2 if $self->_check_dimensions($row, $col);
2218
2219    my $header    = pack("vv",  $record, $length);
2220    my $data      = pack("vvv", $row, $col, $xf);
2221
2222    # Store the data or write immediately depending on the compatibility mode.
2223    if ($self->{_compatibility}) {
2224        $self->{_table}->[$row]->[$col] = $header . $data;
2225    }
2226    else {
2227        $self->_append($header, $data);
2228    }
2229
2230    return 0;
2231}
2232
2233
2234###############################################################################
2235#
2236# write_formula($row, $col, $formula, $format, $value)
2237#
2238# Write a formula to the specified row and column (zero indexed).
2239# The textual representation of the formula is passed to the parser in
2240# Formula.pm which returns a packed binary string.
2241#
2242# $format is optional.
2243#
2244# $value is an optional result of the formula that can be supplied by the user.
2245#
2246# Returns  0 : normal termination
2247#         -1 : insufficient number of arguments
2248#         -2 : row or column out of range
2249#
2250sub write_formula {
2251
2252    my $self = shift;
2253
2254    # Check for a cell reference in A1 notation and substitute row and column
2255    if ($_[0] =~ /^\D/) {
2256        @_ = $self->_substitute_cellref(@_);
2257    }
2258
2259    if (@_ < 3) { return -1 }   # Check the number of args
2260
2261    return if ! defined $_[2];
2262
2263    my $record    = 0x0006;     # Record identifier
2264    my $length;                 # Bytes to follow
2265
2266    my $row       = $_[0];      # Zero indexed row
2267    my $col       = $_[1];      # Zero indexed column
2268    my $formula   = $_[2];      # The formula text string
2269    my $value     = $_[4];      # The formula value.
2270
2271
2272    my $xf        = _XF($self, $row, $col, $_[3]);  # The cell format
2273    my $chn       = 0x0000;                         # Must be zero
2274    my $is_string = 0;                              # Formula evaluates to str
2275    my $num;                                        # Current value of formula
2276    my $grbit;                                      # Option flags
2277
2278
2279    # Excel normally stores the last calculated value of the formula in $num.
2280    # Clearly we are not in a position to calculate this "a priori". Instead
2281    # we set $num to zero and set the option flags in $grbit to ensure
2282    # automatic calculation of the formula when the file is opened.
2283    # As a workaround for some non-Excel apps we also allow the user to
2284    # specify the result of the formula.
2285    #
2286    ($num, $grbit, $is_string) = $self->_encode_formula_result($value);
2287
2288
2289    # Check that row and col are valid and store max and min values
2290    return -2 if $self->_check_dimensions($row, $col);
2291
2292    # Strip the = sign at the beginning of the formula string
2293    $formula    =~ s(^=)();
2294
2295    my $tmp     = $formula;
2296
2297    # Parse the formula using the parser in Formula.pm
2298    my $parser  = $self->{_parser};
2299
2300    # In order to raise formula errors from the point of view of the calling
2301    # program we use an eval block and re-raise the error from here.
2302    #
2303    eval { $formula = $parser->parse_formula($formula) };
2304
2305    if ($@) {
2306        $@ =~ s/\n$//;  # Strip the \n used in the Formula.pm die()
2307        croak $@;       # Re-raise the error
2308    }
2309
2310
2311    my $formlen = length($formula); # Length of the binary string
2312       $length  = 0x16 + $formlen;  # Length of the record data
2313
2314    my $header  = pack("vv",    $record, $length);
2315    my $data    = pack("vvv",   $row, $col, $xf);
2316       $data   .= $num;
2317       $data   .= pack("vVv",   $grbit, $chn, $formlen);
2318
2319    # The STRING record if the formula evaluates to a string.
2320    my $string  = '';
2321       $string  = $self->_get_formula_string($value) if $is_string;
2322
2323
2324    # Store the data or write immediately depending on the compatibility mode.
2325    if ($self->{_compatibility}) {
2326        $self->{_table}->[$row]->[$col] = $header . $data . $formula . $string;
2327    }
2328    else {
2329        $self->_append($header, $data, $formula, $string);
2330    }
2331
2332    return 0;
2333}
2334
2335
2336###############################################################################
2337#
2338# _encode_formula_result()
2339#
2340# Encode the user supplied result for a formula.
2341#
2342sub _encode_formula_result {
2343
2344    my $self = shift;
2345
2346    my $value     = $_[0];      # Result to be encoded.
2347    my $is_string = 0;          # Formula evaluates to str.
2348    my $num;                    # Current value of formula.
2349    my $grbit;                  # Option flags.
2350
2351    if (not defined $value) {
2352        $grbit  = 0x03;
2353        $num    = pack "d", 0;
2354    }
2355    else {
2356        # The user specified the result of the formula. We turn off the recalc
2357        # flag and check the result type.
2358        $grbit  = 0x00;
2359
2360        if ($value =~ /^([+-]?)(?=\d|\.\d)\d*(\.\d*)?([Ee]([+-]?\d+))?$/) {
2361            # Value is a number.
2362            $num = pack "d", $value;
2363        }
2364        else {
2365
2366            my %bools = (
2367                            'TRUE'    => [1,  1],
2368                            'FALSE'   => [1,  0],
2369                            '#NULL!'  => [2,  0],
2370                            '#DIV/0!' => [2,  7],
2371                            '#VALUE!' => [2, 15],
2372                            '#REF!'   => [2, 23],
2373                            '#NAME?'  => [2, 29],
2374                            '#NUM!'   => [2, 36],
2375                            '#N/A'    => [2, 42],
2376                        );
2377
2378            if (exists $bools{$value}) {
2379                # Value is a boolean.
2380                $num = pack "vvvv", $bools{$value}->[0],
2381                                    $bools{$value}->[1],
2382                                    0,
2383                                    0xFFFF;
2384            }
2385            else {
2386                # Value is a string.
2387                $num = pack "vvvv", 0,
2388                                    0,
2389                                    0,
2390                                    0xFFFF;
2391                $is_string = 1;
2392            }
2393        }
2394    }
2395
2396    return ($num, $grbit, $is_string);
2397}
2398
2399
2400###############################################################################
2401#
2402# _get_formula_string()
2403#
2404# Pack the string value when a formula evaluates to a string. The value cannot
2405# be calculated by the module and thus must be supplied by the user.
2406#
2407sub _get_formula_string {
2408
2409    my $self = shift;
2410
2411    my $record    = 0x0207;         # Record identifier
2412    my $length    = 0x00;           # Bytes to follow
2413    my $string    = $_[0];          # Formula string.
2414    my $strlen    = length $_[0];   # Length of the formula string (chars).
2415    my $encoding  = 0;              # String encoding.
2416
2417
2418    # Handle utf8 strings in perl 5.8.
2419    if ($] >= 5.008) {
2420        require Encode;
2421
2422        if (Encode::is_utf8($string)) {
2423            $string = Encode::encode("UTF-16BE", $string);
2424            $encoding = 1;
2425        }
2426    }
2427
2428
2429    $length       = 0x03 + length $string;  # Length of the record data
2430
2431    my $header    = pack("vv", $record, $length);
2432    my $data      = pack("vC", $strlen, $encoding);
2433
2434    return $header . $data . $string;
2435}
2436
2437
2438###############################################################################
2439#
2440# store_formula($formula)
2441#
2442# Pre-parse a formula. This is used in conjunction with repeat_formula()
2443# to repetitively rewrite a formula without re-parsing it.
2444#
2445sub store_formula {
2446
2447    my $self    = shift;
2448    my $formula = $_[0];      # The formula text string
2449
2450    # Strip the = sign at the beginning of the formula string
2451    $formula    =~ s(^=)();
2452
2453    # Parse the formula using the parser in Formula.pm
2454    my $parser  = $self->{_parser};
2455
2456    # In order to raise formula errors from the point of view of the calling
2457    # program we use an eval block and re-raise the error from here.
2458    #
2459    my @tokens;
2460    eval { @tokens = $parser->parse_formula($formula) };
2461
2462    if ($@) {
2463        $@ =~ s/\n$//;  # Strip the \n used in the Formula.pm die()
2464        croak $@;       # Re-raise the error
2465    }
2466
2467
2468    # Return the parsed tokens in an anonymous array
2469    return [@tokens];
2470}
2471
2472
2473###############################################################################
2474#
2475# repeat_formula($row, $col, $formula, $format, ($pattern => $replacement,...))
2476#
2477# Write a formula to the specified row and column (zero indexed) by
2478# substituting $pattern $replacement pairs in the $formula created via
2479# store_formula(). This allows the user to repetitively rewrite a formula
2480# without the significant overhead of parsing.
2481#
2482# Returns  0 : normal termination
2483#         -1 : insufficient number of arguments
2484#         -2 : row or column out of range
2485#
2486sub repeat_formula {
2487
2488    my $self = shift;
2489
2490    # Check for a cell reference in A1 notation and substitute row and column
2491    if ($_[0] =~ /^\D/) {
2492        @_ = $self->_substitute_cellref(@_);
2493    }
2494
2495    if (@_ < 2) { return -1 }   # Check the number of args
2496
2497    my $record      = 0x0006;   # Record identifier
2498    my $length;                 # Bytes to follow
2499
2500    my $row         = shift;    # Zero indexed row
2501    my $col         = shift;    # Zero indexed column
2502    my $formula_ref = shift;    # Array ref with formula tokens
2503    my $format      = shift;    # XF format
2504    my @pairs       = @_;       # Pattern/replacement pairs
2505
2506
2507    # Enforce an even number of arguments in the pattern/replacement list
2508    croak "Odd number of elements in pattern/replacement list" if @pairs %2;
2509
2510    # Check that $formula is an array ref
2511    croak "Not a valid formula" if ref $formula_ref ne 'ARRAY';
2512
2513    my @tokens  = @$formula_ref;
2514
2515    # Ensure that there are tokens to substitute
2516    croak "No tokens in formula" unless @tokens;
2517
2518
2519    # As a temporary and undocumented measure we allow the user to specify the
2520    # result of the formula by appending a result => $value pair to the end
2521    # of the arguments.
2522    my $value = undef;
2523    if (@pairs && $pairs[-2] eq 'result') {
2524        $value = pop @pairs;
2525                 pop @pairs;
2526    }
2527
2528
2529    while (@pairs) {
2530        my $pattern = shift @pairs;
2531        my $replace = shift @pairs;
2532
2533        foreach my $token (@tokens) {
2534            last if $token =~ s/$pattern/$replace/;
2535        }
2536    }
2537
2538
2539    # Change the parameters in the formula cached by the Formula.pm object
2540    my $parser    = $self->{_parser};
2541    my $formula   = $parser->parse_tokens(@tokens);
2542
2543    croak "Unrecognised token in formula" unless defined $formula;
2544
2545
2546    my $xf        = _XF($self, $row, $col, $format); # The cell format
2547    my $chn       = 0x0000;                          # Must be zero
2548    my $is_string = 0;                               # Formula evaluates to str
2549    my $num;                                         # Current value of formula
2550    my $grbit;                                       # Option flags
2551
2552    # Excel normally stores the last calculated value of the formula in $num.
2553    # Clearly we are not in a position to calculate this "a priori". Instead
2554    # we set $num to zero and set the option flags in $grbit to ensure
2555    # automatic calculation of the formula when the file is opened.
2556    # As a workaround for some non-Excel apps we also allow the user to
2557    # specify the result of the formula.
2558    #
2559    ($num, $grbit, $is_string) = $self->_encode_formula_result($value);
2560
2561    # Check that row and col are valid and store max and min values
2562    return -2 if $self->_check_dimensions($row, $col);
2563
2564
2565    my $formlen   = length($formula); # Length of the binary string
2566    $length       = 0x16 + $formlen;  # Length of the record data
2567
2568    my $header    = pack("vv",    $record, $length);
2569    my $data      = pack("vvv",   $row, $col, $xf);
2570       $data     .= $num;
2571       $data     .= pack("vVv",   $grbit, $chn, $formlen);
2572
2573
2574    # The STRING record if the formula evaluates to a string.
2575    my $string  = '';
2576       $string  = $self->_get_formula_string($value) if $is_string;
2577
2578
2579    # Store the data or write immediately depending on the compatibility mode.
2580    if ($self->{_compatibility}) {
2581        $self->{_table}->[$row]->[$col] = $header . $data . $formula . $string;
2582    }
2583    else {
2584        $self->_append($header, $data, $formula, $string);
2585    }
2586
2587    return 0;
2588}
2589
2590
2591###############################################################################
2592#
2593# write_url($row, $col, $url, $string, $format)
2594#
2595# Write a hyperlink. This is comprised of two elements: the visible label and
2596# the invisible link. The visible label is the same as the link unless an
2597# alternative string is specified.
2598#
2599# The parameters $string and $format are optional and their order is
2600# interchangeable for backward compatibility reasons.
2601#
2602# The hyperlink can be to a http, ftp, mail, internal sheet, or external
2603# directory url.
2604#
2605# Returns  0 : normal termination
2606#         -1 : insufficient number of arguments
2607#         -2 : row or column out of range
2608#         -3 : long string truncated to 255 chars
2609#
2610sub write_url {
2611
2612    my $self = shift;
2613
2614    # Check for a cell reference in A1 notation and substitute row and column
2615    if ($_[0] =~ /^\D/) {
2616        @_ = $self->_substitute_cellref(@_);
2617    }
2618
2619    # Check the number of args
2620    return -1 if @_ < 3;
2621
2622    # Add start row and col to arg list
2623    return $self->write_url_range($_[0], $_[1], @_);
2624}
2625
2626
2627###############################################################################
2628#
2629# write_url_range($row1, $col1, $row2, $col2, $url, $string, $format)
2630#
2631# This is the more general form of write_url(). It allows a hyperlink to be
2632# written to a range of cells. This function also decides the type of hyperlink
2633# to be written. These are either, Web (http, ftp, mailto), Internal
2634# (Sheet1!A1) or external ('c:\temp\foo.xls#Sheet1!A1').
2635#
2636# See also write_url() above for a general description and return values.
2637#
2638sub write_url_range {
2639
2640    my $self = shift;
2641
2642    # Check for a cell reference in A1 notation and substitute row and column
2643    if ($_[0] =~ /^\D/) {
2644        @_ = $self->_substitute_cellref(@_);
2645    }
2646
2647    # Check the number of args
2648    return -1 if @_ < 5;
2649
2650
2651    # Reverse the order of $string and $format if necessary. We work on a copy
2652    # in order to protect the callers args. We don't use "local @_" in case of
2653    # perl50005 threads.
2654    #
2655    my @args = @_;
2656
2657    ($args[5], $args[6]) = ($args[6], $args[5]) if ref $args[5];
2658
2659    my $url = $args[4];
2660
2661
2662    # Check for internal/external sheet links or default to web link
2663    return $self->_write_url_internal(@args) if $url =~ m[^internal:];
2664    return $self->_write_url_external(@args) if $url =~ m[^external:];
2665    return $self->_write_url_web(@args);
2666}
2667
2668
2669###############################################################################
2670#
2671# _write_url_web($row1, $col1, $row2, $col2, $url, $string, $format)
2672#
2673# Used to write http, ftp and mailto hyperlinks.
2674# The link type ($options) is 0x03 is the same as absolute dir ref without
2675# sheet. However it is differentiated by the $unknown2 data stream.
2676#
2677# See also write_url() above for a general description and return values.
2678#
2679sub _write_url_web {
2680
2681    my $self    = shift;
2682
2683    my $record      = 0x01B8;                       # Record identifier
2684    my $length      = 0x00000;                      # Bytes to follow
2685
2686    my $row1        = $_[0];                        # Start row
2687    my $col1        = $_[1];                        # Start column
2688    my $row2        = $_[2];                        # End row
2689    my $col2        = $_[3];                        # End column
2690    my $url         = $_[4];                        # URL string
2691    my $str         = $_[5];                        # Alternative label
2692    my $xf          = $_[6] || $self->{_url_format};# The cell format
2693
2694
2695    # Write the visible label but protect against url recursion in write().
2696    $str                  = $url unless defined $str;
2697    $self->{_writing_url} = 1;
2698    my $error             = $self->write($row1, $col1, $str, $xf);
2699    $self->{_writing_url} = 0;
2700    return $error         if $error == -2;
2701
2702
2703    # Pack the undocumented parts of the hyperlink stream
2704    my $unknown1    = pack("H*", "D0C9EA79F9BACE118C8200AA004BA90B02000000");
2705    my $unknown2    = pack("H*", "E0C9EA79F9BACE118C8200AA004BA90B");
2706
2707
2708    # Pack the option flags
2709    my $options     = pack("V", 0x03);
2710
2711
2712    # URL encoding.
2713    my $encoding    = 0;
2714
2715    # Convert an Utf8 URL type and to a null terminated wchar string.
2716    if ($] >= 5.008) {
2717        require Encode;
2718
2719        if (Encode::is_utf8($url)) {
2720            $url      = Encode::encode("UTF-16LE", $url);
2721            $url     .= "\0\0"; # URL is null terminated.
2722            $encoding = 1;
2723        }
2724    }
2725
2726    # Convert an Ascii URL type and to a null terminated wchar string.
2727    if ($encoding == 0) {
2728        $url       .= "\0";
2729        $url        = pack 'v*', unpack 'c*', $url;
2730    }
2731
2732
2733    # Pack the length of the URL
2734    my $url_len     = pack("V", length($url));
2735
2736
2737    # Calculate the data length
2738    $length         = 0x34 + length($url);
2739
2740
2741    # Pack the header data
2742    my $header      = pack("vv",   $record, $length);
2743    my $data        = pack("vvvv", $row1, $row2, $col1, $col2);
2744
2745
2746    # Write the packed data
2747    $self->_append( $header,
2748                    $data,
2749                    $unknown1,
2750                    $options,
2751                    $unknown2,
2752                    $url_len,
2753                    $url);
2754
2755    return $error;
2756}
2757
2758
2759###############################################################################
2760#
2761# _write_url_internal($row1, $col1, $row2, $col2, $url, $string, $format)
2762#
2763# Used to write internal reference hyperlinks such as "Sheet1!A1".
2764#
2765# See also write_url() above for a general description and return values.
2766#
2767sub _write_url_internal {
2768
2769    my $self    = shift;
2770
2771    my $record      = 0x01B8;                       # Record identifier
2772    my $length      = 0x00000;                      # Bytes to follow
2773
2774    my $row1        = $_[0];                        # Start row
2775    my $col1        = $_[1];                        # Start column
2776    my $row2        = $_[2];                        # End row
2777    my $col2        = $_[3];                        # End column
2778    my $url         = $_[4];                        # URL string
2779    my $str         = $_[5];                        # Alternative label
2780    my $xf          = $_[6] || $self->{_url_format};# The cell format
2781
2782    # Strip URL type
2783    $url            =~ s[^internal:][];
2784
2785
2786    # Write the visible label but protect against url recursion in write().
2787    $str                  = $url unless defined $str;
2788    $self->{_writing_url} = 1;
2789    my $error             = $self->write($row1, $col1, $str, $xf);
2790    $self->{_writing_url} = 0;
2791    return $error         if $error == -2;
2792
2793
2794    # Pack the undocumented parts of the hyperlink stream
2795    my $unknown1    = pack("H*", "D0C9EA79F9BACE118C8200AA004BA90B02000000");
2796
2797
2798    # Pack the option flags
2799    my $options     = pack("V", 0x08);
2800
2801
2802    # URL encoding.
2803    my $encoding    = 0;
2804
2805
2806    # Convert an Utf8 URL type and to a null terminated wchar string.
2807    if ($] >= 5.008) {
2808        require Encode;
2809
2810        if (Encode::is_utf8($url)) {
2811            # Quote sheet name if not already, i.e., Sheet!A1 to 'Sheet!A1'.
2812            $url      =~ s/^(.+)!/'$1'!/ if not $url =~ /^'/;
2813
2814            $url      = Encode::encode("UTF-16LE", $url);
2815            $url     .= "\0\0"; # URL is null terminated.
2816            $encoding = 1;
2817        }
2818    }
2819
2820
2821    # Convert an Ascii URL type and to a null terminated wchar string.
2822    if ($encoding == 0) {
2823        $url       .= "\0";
2824        $url        = pack 'v*', unpack 'c*', $url;
2825    }
2826
2827
2828    # Pack the length of the URL as chars (not wchars)
2829    my $url_len     = pack("V", int(length($url)/2));
2830
2831
2832    # Calculate the data length
2833    $length         = 0x24 + length($url);
2834
2835
2836    # Pack the header data
2837    my $header      = pack("vv",   $record, $length);
2838    my $data        = pack("vvvv", $row1, $row2, $col1, $col2);
2839
2840
2841    # Write the packed data
2842    $self->_append( $header,
2843                    $data,
2844                    $unknown1,
2845                    $options,
2846                    $url_len,
2847                    $url);
2848
2849    return $error;
2850}
2851
2852
2853###############################################################################
2854#
2855# _write_url_external($row1, $col1, $row2, $col2, $url, $string, $format)
2856#
2857# Write links to external directory names such as 'c:\foo.xls',
2858# c:\foo.xls#Sheet1!A1', '../../foo.xls'. and '../../foo.xls#Sheet1!A1'.
2859#
2860# Note: Excel writes some relative links with the $dir_long string. We ignore
2861# these cases for the sake of simpler code.
2862#
2863# See also write_url() above for a general description and return values.
2864#
2865sub _write_url_external {
2866
2867    my $self    = shift;
2868
2869    # Network drives are different. We will handle them separately
2870    # MS/Novell network drives and shares start with \\
2871    return $self->_write_url_external_net(@_) if $_[4] =~ m[^external:\\\\];
2872
2873
2874    my $record      = 0x01B8;                       # Record identifier
2875    my $length      = 0x00000;                      # Bytes to follow
2876
2877    my $row1        = $_[0];                        # Start row
2878    my $col1        = $_[1];                        # Start column
2879    my $row2        = $_[2];                        # End row
2880    my $col2        = $_[3];                        # End column
2881    my $url         = $_[4];                        # URL string
2882    my $str         = $_[5];                        # Alternative label
2883    my $xf          = $_[6] || $self->{_url_format};# The cell format
2884
2885
2886    # Strip URL type and change Unix dir separator to Dos style (if needed)
2887    #
2888    $url            =~ s[^external:][];
2889    $url            =~ s[/][\\]g;
2890
2891
2892    # Write the visible label but protect against url recursion in write().
2893    ($str = $url)         =~ s[\#][ - ] unless defined $str;
2894    $self->{_writing_url} = 1;
2895    my $error             = $self->write($row1, $col1, $str, $xf);
2896    $self->{_writing_url} = 0;
2897    return $error         if $error == -2;
2898
2899
2900    # Determine if the link is relative or absolute:
2901    # Absolute if link starts with DOS drive specifier like C:
2902    # Otherwise default to 0x00 for relative link.
2903    #
2904    my $absolute    = 0x00;
2905       $absolute    = 0x02  if $url =~ m/^[A-Za-z]:/;
2906
2907
2908    # Determine if the link contains a sheet reference and change some of the
2909    # parameters accordingly.
2910    # Split the dir name and sheet name (if it exists)
2911    #
2912    my ($dir_long , $sheet) = split /\#/, $url;
2913    my $link_type           = 0x01 | $absolute;
2914    my $sheet_len;
2915
2916    if (defined $sheet) {
2917        $link_type |= 0x08;
2918        $sheet_len  = pack("V", length($sheet) + 0x01);
2919        $sheet      = join("\0", split('', $sheet));
2920        $sheet     .= "\0\0\0";
2921    }
2922    else {
2923        $sheet_len  = '';
2924        $sheet      = '';
2925    }
2926
2927
2928    # Pack the link type
2929    $link_type      = pack("V", $link_type);
2930
2931
2932    # Calculate the up-level dir count e.g. (..\..\..\ == 3)
2933    my $up_count    = 0;
2934    $up_count++       while $dir_long =~ s[^\.\.\\][];
2935    $up_count       = pack("v", $up_count);
2936
2937
2938    # Store the short dos dir name (null terminated)
2939    my $dir_short   = $dir_long . "\0";
2940
2941
2942    # Store the long dir name as a wchar string (non-null terminated)
2943    $dir_long       = join("\0", split('', $dir_long));
2944    $dir_long       = $dir_long . "\0";
2945
2946
2947    # Pack the lengths of the dir strings
2948    my $dir_short_len = pack("V", length $dir_short      );
2949    my $dir_long_len  = pack("V", length $dir_long       );
2950    my $stream_len    = pack("V", length($dir_long) + 0x06);
2951
2952
2953    # Pack the undocumented parts of the hyperlink stream
2954    my $unknown1 =pack("H*",'D0C9EA79F9BACE118C8200AA004BA90B02000000'       );
2955    my $unknown2 =pack("H*",'0303000000000000C000000000000046'               );
2956    my $unknown3 =pack("H*",'FFFFADDE000000000000000000000000000000000000000');
2957    my $unknown4 =pack("v",  0x03                                            );
2958
2959
2960    # Pack the main data stream
2961    my $data        = pack("vvvv", $row1, $row2, $col1, $col2) .
2962                      $unknown1     .
2963                      $link_type    .
2964                      $unknown2     .
2965                      $up_count     .
2966                      $dir_short_len.
2967                      $dir_short    .
2968                      $unknown3     .
2969                      $stream_len   .
2970                      $dir_long_len .
2971                      $unknown4     .
2972                      $dir_long     .
2973                      $sheet_len    .
2974                      $sheet        ;
2975
2976
2977    # Pack the header data
2978    $length         = length $data;
2979    my $header      = pack("vv",   $record, $length);
2980
2981
2982    # Write the packed data
2983    $self->_append($header, $data);
2984
2985    return $error;
2986}
2987
2988
2989
2990
2991###############################################################################
2992#
2993# _write_url_external_net($row1, $col1, $row2, $col2, $url, $string, $format)
2994#
2995# Write links to external MS/Novell network drives and shares such as
2996# '//NETWORK/share/foo.xls' and '//NETWORK/share/foo.xls#Sheet1!A1'.
2997#
2998# See also write_url() above for a general description and return values.
2999#
3000sub _write_url_external_net {
3001
3002    my $self    = shift;
3003
3004    my $record      = 0x01B8;                       # Record identifier
3005    my $length      = 0x00000;                      # Bytes to follow
3006
3007    my $row1        = $_[0];                        # Start row
3008    my $col1        = $_[1];                        # Start column
3009    my $row2        = $_[2];                        # End row
3010    my $col2        = $_[3];                        # End column
3011    my $url         = $_[4];                        # URL string
3012    my $str         = $_[5];                        # Alternative label
3013    my $xf          = $_[6] || $self->{_url_format};# The cell format
3014
3015
3016    # Strip URL type and change Unix dir separator to Dos style (if needed)
3017    #
3018    $url            =~ s[^external:][];
3019    $url            =~ s[/][\\]g;
3020
3021
3022    # Write the visible label but protect against url recursion in write().
3023    ($str = $url)         =~ s[\#][ - ] unless defined $str;
3024    $self->{_writing_url} = 1;
3025    my $error             = $self->write($row1, $col1, $str, $xf);
3026    $self->{_writing_url} = 0;
3027    return $error         if $error == -2;
3028
3029
3030    # Determine if the link contains a sheet reference and change some of the
3031    # parameters accordingly.
3032    # Split the dir name and sheet name (if it exists)
3033    #
3034    my ($dir_long , $sheet) = split /\#/, $url;
3035    my $link_type           = 0x0103; # Always absolute
3036    my $sheet_len;
3037
3038    if (defined $sheet) {
3039        $link_type |= 0x08;
3040        $sheet_len  = pack("V", length($sheet) + 0x01);
3041        $sheet      = join("\0", split('', $sheet));
3042        $sheet     .= "\0\0\0";
3043    }
3044    else {
3045        $sheet_len   = '';
3046        $sheet       = '';
3047    }
3048
3049    # Pack the link type
3050    $link_type      = pack("V", $link_type);
3051
3052
3053    # Make the string null terminated
3054    $dir_long       = $dir_long . "\0";
3055
3056
3057    # Pack the lengths of the dir string
3058    my $dir_long_len  = pack("V", length $dir_long);
3059
3060
3061    # Store the long dir name as a wchar string (non-null terminated)
3062    $dir_long       = join("\0", split('', $dir_long));
3063    $dir_long       = $dir_long . "\0";
3064
3065
3066    # Pack the undocumented part of the hyperlink stream
3067    my $unknown1    = pack("H*",'D0C9EA79F9BACE118C8200AA004BA90B02000000');
3068
3069
3070    # Pack the main data stream
3071    my $data        = pack("vvvv", $row1, $row2, $col1, $col2) .
3072                      $unknown1     .
3073                      $link_type    .
3074                      $dir_long_len .
3075                      $dir_long     .
3076                      $sheet_len    .
3077                      $sheet        ;
3078
3079
3080    # Pack the header data
3081    $length         = length $data;
3082    my $header      = pack("vv",   $record, $length);
3083
3084
3085    # Write the packed data
3086    $self->_append($header, $data);
3087
3088    return $error;
3089}
3090
3091
3092###############################################################################
3093#
3094# write_date_time ($row, $col, $string, $format)
3095#
3096# Write a datetime string in ISO8601 "yyyy-mm-ddThh:mm:ss.ss" format as a
3097# number representing an Excel date. $format is optional.
3098#
3099# Returns  0 : normal termination
3100#         -1 : insufficient number of arguments
3101#         -2 : row or column out of range
3102#         -3 : Invalid date_time, written as string
3103#
3104sub write_date_time {
3105
3106    my $self = shift;
3107
3108    # Check for a cell reference in A1 notation and substitute row and column
3109    if ($_[0] =~ /^\D/) {
3110        @_ = $self->_substitute_cellref(@_);
3111    }
3112
3113    if (@_ < 3) { return -1 }                        # Check the number of args
3114
3115    my $row       = $_[0];                           # Zero indexed row
3116    my $col       = $_[1];                           # Zero indexed column
3117    my $str       = $_[2];
3118
3119
3120    # Check that row and col are valid and store max and min values
3121    return -2 if $self->_check_dimensions($row, $col);
3122
3123    my $error     = 0;
3124    my $date_time = $self->convert_date_time($str);
3125
3126    if (defined $date_time) {
3127        $error = $self->write_number($row, $col, $date_time, $_[3]);
3128    }
3129    else {
3130        # The date isn't valid so write it as a string.
3131        $self->write_string($row, $col, $str, $_[3]);
3132        $error = -3;
3133    }
3134    return $error;
3135}
3136
3137
3138
3139###############################################################################
3140#
3141# convert_date_time($date_time_string)
3142#
3143# The function takes a date and time in ISO8601 "yyyy-mm-ddThh:mm:ss.ss" format
3144# and converts it to a decimal number representing a valid Excel date.
3145#
3146# Dates and times in Excel are represented by real numbers. The integer part of
3147# the number stores the number of days since the epoch and the fractional part
3148# stores the percentage of the day in seconds. The epoch can be either 1900 or
3149# 1904.
3150#
3151# Parameter: Date and time string in one of the following formats:
3152#               yyyy-mm-ddThh:mm:ss.ss  # Standard
3153#               yyyy-mm-ddT             # Date only
3154#                         Thh:mm:ss.ss  # Time only
3155#
3156# Returns:
3157#            A decimal number representing a valid Excel date, or
3158#            undef if the date is invalid.
3159#
3160sub convert_date_time {
3161
3162    my $self      = shift;
3163    my $date_time = $_[0];
3164
3165    my $days      = 0; # Number of days since epoch
3166    my $seconds   = 0; # Time expressed as fraction of 24h hours in seconds
3167
3168    my ($year, $month, $day);
3169    my ($hour, $min, $sec);
3170
3171
3172    # Strip leading and trailing whitespace.
3173    $date_time =~ s/^\s+//;
3174    $date_time =~ s/\s+$//;
3175
3176    # Check for invalid date char.
3177    return if     $date_time =~ /[^0-9T:\-\.Z]/;
3178
3179    # Check for "T" after date or before time.
3180    return unless $date_time =~ /\dT|T\d/;
3181
3182    # Strip trailing Z in ISO8601 date.
3183    $date_time =~ s/Z$//;
3184
3185
3186    # Split into date and time.
3187    my ($date, $time) = split /T/, $date_time;
3188
3189
3190    # We allow the time portion of the input DateTime to be optional.
3191    if ($time ne '') {
3192        # Match hh:mm:ss.sss+ where the seconds are optional
3193        if ($time =~ /^(\d\d):(\d\d)(:(\d\d(\.\d+)?))?/) {
3194            $hour   = $1;
3195            $min    = $2;
3196            $sec    = $4 || 0;
3197        }
3198        else {
3199            return undef; # Not a valid time format.
3200        }
3201
3202        # Some boundary checks
3203        return if $hour >= 24;
3204        return if $min  >= 60;
3205        return if $sec  >= 60;
3206
3207        # Excel expresses seconds as a fraction of the number in 24 hours.
3208        $seconds = ($hour *60*60 + $min *60 + $sec) / (24 *60 *60);
3209    }
3210
3211
3212    # We allow the date portion of the input DateTime to be optional.
3213    return $seconds if $date eq '';
3214
3215
3216    # Match date as yyyy-mm-dd.
3217    if ($date =~ /^(\d\d\d\d)-(\d\d)-(\d\d)$/) {
3218        $year   = $1;
3219        $month  = $2;
3220        $day    = $3;
3221    }
3222    else {
3223        return undef; # Not a valid date format.
3224    }
3225
3226    # Set the epoch as 1900 or 1904. Defaults to 1900.
3227    my $date_1904 = $self->{_1904};
3228
3229
3230    # Special cases for Excel.
3231    if (not $date_1904) {
3232        return      $seconds if $date eq '1899-12-31'; # Excel 1900 epoch
3233        return      $seconds if $date eq '1900-01-00'; # Excel 1900 epoch
3234        return 60 + $seconds if $date eq '1900-02-29'; # Excel false leapday
3235    }
3236
3237
3238    # We calculate the date by calculating the number of days since the epoch
3239    # and adjust for the number of leap days. We calculate the number of leap
3240    # days by normalising the year in relation to the epoch. Thus the year 2000
3241    # becomes 100 for 4 and 100 year leapdays and 400 for 400 year leapdays.
3242    #
3243    my $epoch   = $date_1904 ? 1904 : 1900;
3244    my $offset  = $date_1904 ?    4 :    0;
3245    my $norm    = 300;
3246    my $range   = $year -$epoch;
3247
3248
3249    # Set month days and check for leap year.
3250    my @mdays   = (31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31);
3251    my $leap    = 0;
3252       $leap    = 1  if $year % 4 == 0 and $year % 100 or $year % 400 == 0;
3253    $mdays[1]   = 29 if $leap;
3254
3255
3256    # Some boundary checks
3257    return if $year  < $epoch or $year  > 9999;
3258    return if $month < 1      or $month > 12;
3259    return if $day   < 1      or $day   > $mdays[$month -1];
3260
3261    # Accumulate the number of days since the epoch.
3262    $days  = $day;                              # Add days for current month
3263    $days += $mdays[$_] for 0 .. $month -2;     # Add days for past months
3264    $days += $range *365;                       # Add days for past years
3265    $days += int(($range)                /  4); # Add leapdays
3266    $days -= int(($range +$offset)       /100); # Subtract 100 year leapdays
3267    $days += int(($range +$offset +$norm)/400); # Add 400 year leapdays
3268    $days -= $leap;                             # Already counted above
3269
3270
3271    # Adjust for Excel erroneously treating 1900 as a leap year.
3272    $days++ if $date_1904 == 0 and $days > 59;
3273
3274    return $days + $seconds;
3275}
3276
3277
3278
3279
3280
3281###############################################################################
3282#
3283# set_row($row, $height, $XF, $hidden, $level)
3284#
3285# This method is used to set the height and XF format for a row.
3286# Writes the  BIFF record ROW.
3287#
3288sub set_row {
3289
3290    my $self        = shift;
3291    my $record      = 0x0208;               # Record identifier
3292    my $length      = 0x0010;               # Number of bytes to follow
3293
3294    my $row         = $_[0];                # Row Number
3295    my $colMic      = 0x0000;               # First defined column
3296    my $colMac      = 0x0000;               # Last defined column
3297    my $miyRw;                              # Row height
3298    my $irwMac      = 0x0000;               # Used by Excel to optimise loading
3299    my $reserved    = 0x0000;               # Reserved
3300    my $grbit       = 0x0000;               # Option flags
3301    my $ixfe;                               # XF index
3302    my $height      = $_[1];                # Row height
3303    my $format      = $_[2];                # Format object
3304    my $hidden      = $_[3] || 0;           # Hidden flag
3305    my $level       = $_[4] || 0;           # Outline level
3306    my $collapsed   = $_[5] || 0;           # Collapsed row
3307
3308
3309    return unless defined $row;  # Ensure at least $row is specified.
3310
3311    # Check that row and col are valid and store max and min values
3312    return -2 if $self->_check_dimensions($row, 0, 0, 1);
3313
3314    # Check for a format object
3315    if (ref $format) {
3316        $ixfe = $format->get_xf_index();
3317    }
3318    else {
3319        $ixfe = 0x0F;
3320    }
3321
3322
3323    # Set the row height in units of 1/20 of a point. Note, some heights may
3324    # not be obtained exactly due to rounding in Excel.
3325    #
3326    if (defined $height) {
3327        $miyRw = $height *20;
3328    }
3329    else {
3330        $miyRw = 0xff; # The default row height
3331        $height = 0;
3332    }
3333
3334
3335    # Set the limits for the outline levels (0 <= x <= 7).
3336    $level = 0 if $level < 0;
3337    $level = 7 if $level > 7;
3338
3339    $self->{_outline_row_level} = $level if $level >$self->{_outline_row_level};
3340
3341
3342    # Set the options flags.
3343    # 0x10: The fCollapsed flag indicates that the row contains the "+"
3344    #       when an outline group is collapsed.
3345    # 0x20: The fDyZero height flag indicates a collapsed or hidden row.
3346    # 0x40: The fUnsynced flag is used to show that the font and row heights
3347    #       are not compatible. This is usually the case for WriteExcel.
3348    # 0x80: The fGhostDirty flag indicates that the row has been formatted.
3349    #
3350    $grbit |= $level;
3351    $grbit |= 0x0010 if $collapsed;
3352    $grbit |= 0x0020 if $hidden;
3353    $grbit |= 0x0040;
3354    $grbit |= 0x0080 if $format;
3355    $grbit |= 0x0100;
3356
3357
3358    my $header   = pack("vv",       $record, $length);
3359    my $data     = pack("vvvvvvvv", $row, $colMic, $colMac, $miyRw,
3360                                    $irwMac,$reserved, $grbit, $ixfe);
3361
3362
3363    # Store the data or write immediately depending on the compatibility mode.
3364    if ($self->{_compatibility}) {
3365        $self->{_row_data}->{$_[0]} = $header . $data;
3366    }
3367    else {
3368        $self->_append($header, $data);
3369    }
3370
3371
3372    # Store the row sizes for use when calculating image vertices.
3373    # Also store the row formats.
3374    $self->{_row_sizes}->{$_[0]}   = $height;
3375    $self->{_row_formats}->{$_[0]} = $format if defined $format;
3376}
3377
3378
3379
3380###############################################################################
3381#
3382# _write_row_default()
3383#
3384# Write a default row record, in compatibility mode, for rows that don't have
3385# user specified values..
3386#
3387sub _write_row_default {
3388
3389    my $self        = shift;
3390    my $record      = 0x0208;               # Record identifier
3391    my $length      = 0x0010;               # Number of bytes to follow
3392
3393    my $row         = $_[0];                # Row Number
3394    my $colMic      = $_[1];                # First defined column
3395    my $colMac      = $_[2];                # Last defined column
3396    my $miyRw       = 0xFF;                 # Row height
3397    my $irwMac      = 0x0000;               # Used by Excel to optimise loading
3398    my $reserved    = 0x0000;               # Reserved
3399    my $grbit       = 0x0100;               # Option flags
3400    my $ixfe        = 0x0F;                 # XF index
3401
3402    my $header   = pack("vv",       $record, $length);
3403    my $data     = pack("vvvvvvvv", $row, $colMic, $colMac, $miyRw,
3404                                    $irwMac,$reserved, $grbit, $ixfe);
3405
3406    $self->_append($header, $data);
3407}
3408
3409
3410###############################################################################
3411#
3412# _check_dimensions($row, $col, $ignore_row, $ignore_col)
3413#
3414# Check that $row and $col are valid and store max and min values for use in
3415# DIMENSIONS record. See, _store_dimensions().
3416#
3417# The $ignore_row/$ignore_col flags is used to indicate that we wish to
3418# perform the dimension check without storing the value.
3419#
3420# The ignore flags are use by set_row() and data_validate.
3421#
3422sub _check_dimensions {
3423
3424    my $self        = shift;
3425    my $row         = $_[0];
3426    my $col         = $_[1];
3427    my $ignore_row  = $_[2];
3428    my $ignore_col  = $_[3];
3429
3430
3431    return -2 if not defined $row;
3432    return -2 if $row >= $self->{_xls_rowmax};
3433
3434    return -2 if not defined $col;
3435    return -2 if $col >= $self->{_xls_colmax};
3436
3437
3438    if (not $ignore_row) {
3439
3440        if (not defined $self->{_dim_rowmin} or $row < $self->{_dim_rowmin}) {
3441            $self->{_dim_rowmin} = $row;
3442        }
3443
3444        if (not defined $self->{_dim_rowmax} or $row > $self->{_dim_rowmax}) {
3445            $self->{_dim_rowmax} = $row;
3446        }
3447    }
3448
3449    if (not $ignore_col) {
3450
3451        if (not defined $self->{_dim_colmin} or $col < $self->{_dim_colmin}) {
3452            $self->{_dim_colmin} = $col;
3453        }
3454
3455        if (not defined $self->{_dim_colmax} or $col > $self->{_dim_colmax}) {
3456            $self->{_dim_colmax} = $col;
3457        }
3458    }
3459
3460    return 0;
3461}
3462
3463
3464###############################################################################
3465#
3466# _store_dimensions()
3467#
3468# Writes Excel DIMENSIONS to define the area in which there is cell data.
3469#
3470# Notes:
3471#   Excel stores the max row/col as row/col +1.
3472#   Max and min values of 0 are used to indicate that no cell data.
3473#   We set the undef member data to 0 since it is used by _store_table().
3474#   Inserting images or charts doesn't change the DIMENSION data.
3475#
3476sub _store_dimensions {
3477
3478    my $self      = shift;
3479    my $record    = 0x0200;         # Record identifier
3480    my $length    = 0x000E;         # Number of bytes to follow
3481    my $row_min;                    # First row
3482    my $row_max;                    # Last row plus 1
3483    my $col_min;                    # First column
3484    my $col_max;                    # Last column plus 1
3485    my $reserved  = 0x0000;         # Reserved by Excel
3486
3487    if (defined $self->{_dim_rowmin}) {$row_min = $self->{_dim_rowmin}    }
3488    else                              {$row_min = 0                       }
3489
3490    if (defined $self->{_dim_rowmax}) {$row_max = $self->{_dim_rowmax} + 1}
3491    else                              {$row_max = 0                       }
3492
3493    if (defined $self->{_dim_colmin}) {$col_min = $self->{_dim_colmin}    }
3494    else                              {$col_min = 0                       }
3495
3496    if (defined $self->{_dim_colmax}) {$col_max = $self->{_dim_colmax} + 1}
3497    else                              {$col_max = 0                       }
3498
3499
3500    # Set member data to the new max/min value for use by _store_table().
3501    $self->{_dim_rowmin} = $row_min;
3502    $self->{_dim_rowmax} = $row_max;
3503    $self->{_dim_colmin} = $col_min;
3504    $self->{_dim_colmax} = $col_max;
3505
3506
3507    my $header    = pack("vv",    $record, $length);
3508    my $data      = pack("VVvvv", $row_min, $row_max,
3509                                  $col_min, $col_max, $reserved);
3510    $self->_prepend($header, $data);
3511}
3512
3513
3514###############################################################################
3515#
3516# _store_window2()
3517#
3518# Write BIFF record Window2.
3519#
3520sub _store_window2 {
3521
3522    use integer;    # Avoid << shift bug in Perl 5.6.0 on HP-UX
3523
3524    my $self           = shift;
3525    my $record         = 0x023E;     # Record identifier
3526    my $length         = 0x0012;     # Number of bytes to follow
3527
3528    my $grbit          = 0x00B6;     # Option flags
3529    my $rwTop          = $self->{_first_row};   # Top visible row
3530    my $colLeft        = $self->{_first_col};   # Leftmost visible column
3531    my $rgbHdr         = 0x00000040;            # Row/col heading, grid color
3532
3533    my $wScaleSLV      = 0x0000;                # Zoom in page break preview
3534    my $wScaleNormal   = 0x0000;                # Zoom in normal view
3535    my $reserved       = 0x00000000;
3536
3537
3538    # The options flags that comprise $grbit
3539    my $fDspFmla       = $self->{_display_formulas}; # 0 - bit
3540    my $fDspGrid       = $self->{_screen_gridlines}; # 1
3541    my $fDspRwCol      = $self->{_display_headers};  # 2
3542    my $fFrozen        = $self->{_frozen};           # 3
3543    my $fDspZeros      = $self->{_display_zeros};    # 4
3544    my $fDefaultHdr    = 1;                          # 5
3545    my $fArabic        = $self->{_display_arabic};   # 6
3546    my $fDspGuts       = $self->{_outline_on};       # 7
3547    my $fFrozenNoSplit = $self->{_frozen_no_split};  # 0 - bit
3548    my $fSelected      = $self->{_selected};         # 1
3549    my $fPaged         = $self->{_active};           # 2
3550    my $fBreakPreview  = 0;                          # 3
3551
3552    $grbit             = $fDspFmla;
3553    $grbit            |= $fDspGrid       << 1;
3554    $grbit            |= $fDspRwCol      << 2;
3555    $grbit            |= $fFrozen        << 3;
3556    $grbit            |= $fDspZeros      << 4;
3557    $grbit            |= $fDefaultHdr    << 5;
3558    $grbit            |= $fArabic        << 6;
3559    $grbit            |= $fDspGuts       << 7;
3560    $grbit            |= $fFrozenNoSplit << 8;
3561    $grbit            |= $fSelected      << 9;
3562    $grbit            |= $fPaged         << 10;
3563    $grbit            |= $fBreakPreview  << 11;
3564
3565    my $header  = pack("vv",      $record, $length);
3566    my $data    = pack("vvvVvvV", $grbit, $rwTop, $colLeft, $rgbHdr,
3567                                  $wScaleSLV, $wScaleNormal, $reserved );
3568
3569    $self->_append($header, $data);
3570}
3571
3572
3573###############################################################################
3574#
3575# _store_page_view()
3576#
3577# Set page view mode. Only applicable to Mac Excel.
3578#
3579sub _store_page_view {
3580
3581    my $self    = shift;
3582
3583    return unless $self->{_page_view};
3584
3585    my $data    = pack "H*", 'C8081100C808000000000040000000000900000000';
3586
3587    $self->_append($data);
3588}
3589
3590
3591###############################################################################
3592#
3593# _store_tab_color()
3594#
3595# Write the Tab Color BIFF record.
3596#
3597sub _store_tab_color {
3598
3599    my $self    = shift;
3600    my $color   = $self->{_tab_color};
3601
3602    return unless $color;
3603
3604    my $record  = 0x0862;      # Record identifier
3605    my $length  = 0x0014;      # Number of bytes to follow
3606
3607    my $zero    = 0x0000;
3608    my $unknown = 0x0014;
3609
3610    my $header  = pack("vv", $record, $length);
3611    my $data    = pack("vvvvvvvvvv", $record, $zero, $zero, $zero, $zero,
3612                                     $zero, $unknown, $zero, $color, $zero);
3613
3614    $self->_append($header, $data);
3615}
3616
3617
3618###############################################################################
3619#
3620# _store_defrow()
3621#
3622# Write BIFF record DEFROWHEIGHT.
3623#
3624sub _store_defrow {
3625
3626    my $self     = shift;
3627    my $record   = 0x0225;      # Record identifier
3628    my $length   = 0x0004;      # Number of bytes to follow
3629
3630    my $grbit    = 0x0000;      # Options.
3631    my $height   = 0x00FF;      # Default row height
3632
3633    my $header   = pack("vv", $record, $length);
3634    my $data     = pack("vv", $grbit,  $height);
3635
3636    $self->_prepend($header, $data);
3637}
3638
3639
3640###############################################################################
3641#
3642# _store_defcol()
3643#
3644# Write BIFF record DEFCOLWIDTH.
3645#
3646sub _store_defcol {
3647
3648    my $self     = shift;
3649    my $record   = 0x0055;      # Record identifier
3650    my $length   = 0x0002;      # Number of bytes to follow
3651
3652    my $colwidth = 0x0008;      # Default column width
3653
3654    my $header   = pack("vv", $record, $length);
3655    my $data     = pack("v",  $colwidth);
3656
3657    $self->_prepend($header, $data);
3658}
3659
3660
3661###############################################################################
3662#
3663# _store_colinfo($firstcol, $lastcol, $width, $format, $hidden)
3664#
3665# Write BIFF record COLINFO to define column widths
3666#
3667# Note: The SDK says the record length is 0x0B but Excel writes a 0x0C
3668# length record.
3669#
3670sub _store_colinfo {
3671
3672    my $self     = shift;
3673    my $record   = 0x007D;          # Record identifier
3674    my $length   = 0x000B;          # Number of bytes to follow
3675
3676    my $colFirst = $_[0] || 0;      # First formatted column
3677    my $colLast  = $_[1] || 0;      # Last formatted column
3678    my $width    = $_[2] || 8.43;   # Col width in user units, 8.43 is default
3679    my $coldx;                      # Col width in internal units
3680    my $pixels;                     # Col width in pixels
3681
3682    # Excel rounds the column width to the nearest pixel. Therefore we first
3683    # convert to pixels and then to the internal units. The pixel to users-units
3684    # relationship is different for values less than 1.
3685    #
3686    if ($width < 1) {
3687        $pixels = int($width *12);
3688    }
3689    else {
3690        $pixels = int($width *7 ) +5;
3691    }
3692
3693    $coldx = int($pixels *256/7);
3694
3695
3696    my $ixfe;                          # XF index
3697    my $grbit       = 0x0000;          # Option flags
3698    my $reserved    = 0x00;            # Reserved
3699    my $format      = $_[3];           # Format object
3700    my $hidden      = $_[4] || 0;      # Hidden flag
3701    my $level       = $_[5] || 0;      # Outline level
3702    my $collapsed   = $_[6] || 0;      # Outline level
3703
3704
3705    # Check for a format object
3706    if (ref $format) {
3707        $ixfe = $format->get_xf_index();
3708    }
3709    else {
3710        $ixfe = 0x0F;
3711    }
3712
3713
3714    # Set the limits for the outline levels (0 <= x <= 7).
3715    $level = 0 if $level < 0;
3716    $level = 7 if $level > 7;
3717
3718
3719    # Set the options flags. (See set_row() for more details).
3720    $grbit |= 0x0001 if $hidden;
3721    $grbit |= $level << 8;
3722    $grbit |= 0x1000 if $collapsed;
3723
3724
3725    my $header   = pack("vv",     $record, $length);
3726    my $data     = pack("vvvvvC", $colFirst, $colLast, $coldx,
3727                                  $ixfe, $grbit, $reserved);
3728
3729    $self->_prepend($header, $data);
3730}
3731
3732
3733###############################################################################
3734#
3735# _store_filtermode()
3736#
3737# Write BIFF record FILTERMODE to indicate that the worksheet contains
3738# AUTOFILTER record, ie. autofilters with a filter set.
3739#
3740sub _store_filtermode {
3741
3742    my $self        = shift;
3743
3744    my $record      = 0x009B;      # Record identifier
3745    my $length      = 0x0000;      # Number of bytes to follow
3746
3747    # Only write the record if the worksheet contains a filtered autofilter.
3748    return unless $self->{_filter_on};
3749
3750    my $header   = pack("vv", $record, $length);
3751
3752    $self->_prepend($header);
3753}
3754
3755
3756###############################################################################
3757#
3758# _store_autofilterinfo()
3759#
3760# Write BIFF record AUTOFILTERINFO.
3761#
3762sub _store_autofilterinfo {
3763
3764    my $self        = shift;
3765
3766    my $record      = 0x009D;      # Record identifier
3767    my $length      = 0x0002;      # Number of bytes to follow
3768    my $num_filters = $self->{_filter_count};
3769
3770    # Only write the record if the worksheet contains an autofilter.
3771    return unless $self->{_filter_count};
3772
3773    my $header   = pack("vv", $record, $length);
3774    my $data     = pack("v",  $num_filters);
3775
3776    $self->_prepend($header, $data);
3777}
3778
3779
3780###############################################################################
3781#
3782# _store_selection($first_row, $first_col, $last_row, $last_col)
3783#
3784# Write BIFF record SELECTION.
3785#
3786sub _store_selection {
3787
3788    my $self     = shift;
3789    my $record   = 0x001D;                  # Record identifier
3790    my $length   = 0x000F;                  # Number of bytes to follow
3791
3792    my $pnn      = $self->{_active_pane};   # Pane position
3793    my $rwAct    = $_[0];                   # Active row
3794    my $colAct   = $_[1];                   # Active column
3795    my $irefAct  = 0;                       # Active cell ref
3796    my $cref     = 1;                       # Number of refs
3797
3798    my $rwFirst  = $_[0];                   # First row in reference
3799    my $colFirst = $_[1];                   # First col in reference
3800    my $rwLast   = $_[2] || $rwFirst;       # Last  row in reference
3801    my $colLast  = $_[3] || $colFirst;      # Last  col in reference
3802
3803    # Swap last row/col for first row/col as necessary
3804    if ($rwFirst > $rwLast) {
3805        ($rwFirst, $rwLast) = ($rwLast, $rwFirst);
3806    }
3807
3808    if ($colFirst > $colLast) {
3809        ($colFirst, $colLast) = ($colLast, $colFirst);
3810    }
3811
3812
3813    my $header   = pack("vv",           $record, $length);
3814    my $data     = pack("CvvvvvvCC",    $pnn, $rwAct, $colAct,
3815                                        $irefAct, $cref,
3816                                        $rwFirst, $rwLast,
3817                                        $colFirst, $colLast);
3818
3819    $self->_append($header, $data);
3820}
3821
3822
3823###############################################################################
3824#
3825# _store_externcount($count)
3826#
3827# Write BIFF record EXTERNCOUNT to indicate the number of external sheet
3828# references in a worksheet.
3829#
3830# Excel only stores references to external sheets that are used in formulas.
3831# For simplicity we store references to all the sheets in the workbook
3832# regardless of whether they are used or not. This reduces the overall
3833# complexity and eliminates the need for a two way dialogue between the formula
3834# parser the worksheet objects.
3835#
3836sub _store_externcount {
3837
3838    my $self     = shift;
3839    my $record   = 0x0016;          # Record identifier
3840    my $length   = 0x0002;          # Number of bytes to follow
3841
3842    my $cxals    = $_[0];           # Number of external references
3843
3844    my $header   = pack("vv", $record, $length);
3845    my $data     = pack("v",  $cxals);
3846
3847    $self->_prepend($header, $data);
3848}
3849
3850
3851###############################################################################
3852#
3853# _store_externsheet($sheetname)
3854#
3855#
3856# Writes the Excel BIFF EXTERNSHEET record. These references are used by
3857# formulas. A formula references a sheet name via an index. Since we store a
3858# reference to all of the external worksheets the EXTERNSHEET index is the same
3859# as the worksheet index.
3860#
3861sub _store_externsheet {
3862
3863    my $self      = shift;
3864
3865    my $record    = 0x0017;         # Record identifier
3866    my $length;                     # Number of bytes to follow
3867
3868    my $sheetname = $_[0];          # Worksheet name
3869    my $cch;                        # Length of sheet name
3870    my $rgch;                       # Filename encoding
3871
3872    # References to the current sheet are encoded differently to references to
3873    # external sheets.
3874    #
3875    if ($self->{_name} eq $sheetname) {
3876        $sheetname = '';
3877        $length    = 0x02;  # The following 2 bytes
3878        $cch       = 1;     # The following byte
3879        $rgch      = 0x02;  # Self reference
3880    }
3881    else {
3882        $length    = 0x02 + length($_[0]);
3883        $cch       = length($sheetname);
3884        $rgch      = 0x03;  # Reference to a sheet in the current workbook
3885    }
3886
3887    my $header     = pack("vv",  $record, $length);
3888    my $data       = pack("CC", $cch, $rgch);
3889
3890    $self->_prepend($header, $data, $sheetname);
3891}
3892
3893
3894###############################################################################
3895#
3896# _store_panes()
3897#
3898#
3899# Writes the Excel BIFF PANE record.
3900# The panes can either be frozen or thawed (unfrozen).
3901# Frozen panes are specified in terms of a integer number of rows and columns.
3902# Thawed panes are specified in terms of Excel's units for rows and columns.
3903#
3904sub _store_panes {
3905
3906    my $self        = shift;
3907    my $record      = 0x0041;       # Record identifier
3908    my $length      = 0x000A;       # Number of bytes to follow
3909
3910    my $y           = $_[0] || 0;   # Vertical split position
3911    my $x           = $_[1] || 0;   # Horizontal split position
3912    my $rwTop       = $_[2];        # Top row visible
3913    my $colLeft     = $_[3];        # Leftmost column visible
3914    my $no_split    = $_[4];        # No used here.
3915    my $pnnAct      = $_[5];        # Active pane
3916
3917
3918    # Code specific to frozen or thawed panes.
3919    if ($self->{_frozen}) {
3920        # Set default values for $rwTop and $colLeft
3921        $rwTop   = $y unless defined $rwTop;
3922        $colLeft = $x unless defined $colLeft;
3923    }
3924    else {
3925        # Set default values for $rwTop and $colLeft
3926        $rwTop   = 0  unless defined $rwTop;
3927        $colLeft = 0  unless defined $colLeft;
3928
3929        # Convert Excel's row and column units to the internal units.
3930        # The default row height is 12.75
3931        # The default column width is 8.43
3932        # The following slope and intersection values were interpolated.
3933        #
3934        $y = 20*$y      + 255;
3935        $x = 113.879*$x + 390;
3936    }
3937
3938
3939    # Determine which pane should be active. There is also the undocumented
3940    # option to override this should it be necessary: may be removed later.
3941    #
3942    if (not defined $pnnAct) {
3943        $pnnAct = 0 if ($x != 0 && $y != 0); # Bottom right
3944        $pnnAct = 1 if ($x != 0 && $y == 0); # Top right
3945        $pnnAct = 2 if ($x == 0 && $y != 0); # Bottom left
3946        $pnnAct = 3 if ($x == 0 && $y == 0); # Top left
3947    }
3948
3949    $self->{_active_pane} = $pnnAct; # Used in _store_selection
3950
3951    my $header     = pack("vv",    $record, $length);
3952    my $data       = pack("vvvvv", $x, $y, $rwTop, $colLeft, $pnnAct);
3953
3954    $self->_append($header, $data);
3955}
3956
3957
3958###############################################################################
3959#
3960# _store_setup()
3961#
3962# Store the page setup SETUP BIFF record.
3963#
3964sub _store_setup {
3965
3966    use integer;    # Avoid << shift bug in Perl 5.6.0 on HP-UX
3967
3968    my $self         = shift;
3969    my $record       = 0x00A1;                  # Record identifier
3970    my $length       = 0x0022;                  # Number of bytes to follow
3971
3972
3973    my $iPaperSize   = $self->{_paper_size};    # Paper size
3974    my $iScale       = $self->{_print_scale};   # Print scaling factor
3975    my $iPageStart   = $self->{_page_start};    # Starting page number
3976    my $iFitWidth    = $self->{_fit_width};     # Fit to number of pages wide
3977    my $iFitHeight   = $self->{_fit_height};    # Fit to number of pages high
3978    my $grbit        = 0x00;                    # Option flags
3979    my $iRes         = 0x0258;                  # Print resolution
3980    my $iVRes        = 0x0258;                  # Vertical print resolution
3981    my $numHdr       = $self->{_margin_header}; # Header Margin
3982    my $numFtr       = $self->{_margin_footer}; # Footer Margin
3983    my $iCopies      = 0x01;                    # Number of copies
3984
3985
3986    my $fLeftToRight = $self->{_page_order};    # Print over then down
3987    my $fLandscape   = $self->{_orientation};   # Page orientation
3988    my $fNoPls       = 0x0;                     # Setup not read from printer
3989    my $fNoColor     = $self->{_black_white};   # Print black and white
3990    my $fDraft       = $self->{_draft_quality}; # Print draft quality
3991    my $fNotes       = $self->{_print_comments};# Print notes
3992    my $fNoOrient    = 0x0;                     # Orientation not set
3993    my $fUsePage     = $self->{_custom_start};  # Use custom starting page
3994
3995
3996    $grbit           = $fLeftToRight;
3997    $grbit          |= $fLandscape    << 1;
3998    $grbit          |= $fNoPls        << 2;
3999    $grbit          |= $fNoColor      << 3;
4000    $grbit          |= $fDraft        << 4;
4001    $grbit          |= $fNotes        << 5;
4002    $grbit          |= $fNoOrient     << 6;
4003    $grbit          |= $fUsePage      << 7;
4004
4005
4006    $numHdr = pack("d", $numHdr);
4007    $numFtr = pack("d", $numFtr);
4008
4009    if ($self->{_byte_order}) {
4010        $numHdr = reverse $numHdr;
4011        $numFtr = reverse $numFtr;
4012    }
4013
4014    my $header = pack("vv",         $record, $length);
4015    my $data1  = pack("vvvvvvvv",   $iPaperSize,
4016                                    $iScale,
4017                                    $iPageStart,
4018                                    $iFitWidth,
4019                                    $iFitHeight,
4020                                    $grbit,
4021                                    $iRes,
4022                                    $iVRes);
4023    my $data2  = $numHdr .$numFtr;
4024    my $data3  = pack("v", $iCopies);
4025
4026    $self->_prepend($header, $data1, $data2, $data3);
4027
4028}
4029
4030###############################################################################
4031#
4032# _store_header()
4033#
4034# Store the header caption BIFF record.
4035#
4036sub _store_header {
4037
4038    my $self        = shift;
4039
4040    my $record      = 0x0014;                       # Record identifier
4041    my $length;                                     # Bytes to follow
4042
4043    my $str         = $self->{_header};             # header string
4044    my $cch         = length($str);                 # Length of header string
4045    my $encoding    = $self->{_header_encoding};    # Character encoding
4046
4047
4048    # Character length is num of chars not num of bytes
4049    $cch           /= 2 if $encoding;
4050
4051    # Change the UTF-16 name from BE to LE
4052    $str            = pack 'n*', unpack 'v*', $str if $encoding;
4053
4054    $length         = 3 + length($str);
4055
4056    my $header      = pack("vv",  $record, $length);
4057    my $data        = pack("vC",  $cch, $encoding);
4058
4059    $self->_prepend($header, $data, $str);
4060}
4061
4062
4063###############################################################################
4064#
4065# _store_footer()
4066#
4067# Store the footer caption BIFF record.
4068#
4069sub _store_footer {
4070
4071    my $self        = shift;
4072
4073    my $record      = 0x0015;                       # Record identifier
4074    my $length;                                     # Bytes to follow
4075
4076    my $str         = $self->{_footer};             # footer string
4077    my $cch         = length($str);                 # Length of footer string
4078    my $encoding    = $self->{_footer_encoding};    # Character encoding
4079
4080
4081    # Character length is num of chars not num of bytes
4082    $cch           /= 2 if $encoding;
4083
4084    # Change the UTF-16 name from BE to LE
4085    $str            = pack 'n*', unpack 'v*', $str if $encoding;
4086
4087    $length         = 3 + length($str);
4088
4089    my $header      = pack("vv",  $record, $length);
4090    my $data        = pack("vC",  $cch, $encoding);
4091
4092    $self->_prepend($header, $data, $str);
4093}
4094
4095
4096###############################################################################
4097#
4098# _store_hcenter()
4099#
4100# Store the horizontal centering HCENTER BIFF record.
4101#
4102sub _store_hcenter {
4103
4104    my $self     = shift;
4105
4106    my $record   = 0x0083;              # Record identifier
4107    my $length   = 0x0002;              # Bytes to follow
4108
4109    my $fHCenter = $self->{_hcenter};   # Horizontal centering
4110
4111    my $header    = pack("vv",  $record, $length);
4112    my $data      = pack("v",   $fHCenter);
4113
4114    $self->_prepend($header, $data);
4115}
4116
4117
4118###############################################################################
4119#
4120# _store_vcenter()
4121#
4122# Store the vertical centering VCENTER BIFF record.
4123#
4124sub _store_vcenter {
4125
4126    my $self     = shift;
4127
4128    my $record   = 0x0084;              # Record identifier
4129    my $length   = 0x0002;              # Bytes to follow
4130
4131    my $fVCenter = $self->{_vcenter};   # Horizontal centering
4132
4133    my $header    = pack("vv",  $record, $length);
4134    my $data      = pack("v",   $fVCenter);
4135
4136    $self->_prepend($header, $data);
4137}
4138
4139
4140###############################################################################
4141#
4142# _store_margin_left()
4143#
4144# Store the LEFTMARGIN BIFF record.
4145#
4146sub _store_margin_left {
4147
4148    my $self    = shift;
4149
4150    my $record  = 0x0026;                   # Record identifier
4151    my $length  = 0x0008;                   # Bytes to follow
4152
4153    my $margin  = $self->{_margin_left};    # Margin in inches
4154
4155    my $header    = pack("vv",  $record, $length);
4156    my $data      = pack("d",   $margin);
4157
4158    if ($self->{_byte_order}) { $data = reverse $data }
4159
4160    $self->_prepend($header, $data);
4161}
4162
4163
4164###############################################################################
4165#
4166# _store_margin_right()
4167#
4168# Store the RIGHTMARGIN BIFF record.
4169#
4170sub _store_margin_right {
4171
4172    my $self    = shift;
4173
4174    my $record  = 0x0027;                   # Record identifier
4175    my $length  = 0x0008;                   # Bytes to follow
4176
4177    my $margin  = $self->{_margin_right};   # Margin in inches
4178
4179    my $header    = pack("vv",  $record, $length);
4180    my $data      = pack("d",   $margin);
4181
4182    if ($self->{_byte_order}) { $data = reverse $data }
4183
4184    $self->_prepend($header, $data);
4185}
4186
4187
4188###############################################################################
4189#
4190# _store_margin_top()
4191#
4192# Store the TOPMARGIN BIFF record.
4193#
4194sub _store_margin_top {
4195
4196    my $self    = shift;
4197
4198    my $record  = 0x0028;                   # Record identifier
4199    my $length  = 0x0008;                   # Bytes to follow
4200
4201    my $margin  = $self->{_margin_top};     # Margin in inches
4202
4203    my $header    = pack("vv",  $record, $length);
4204    my $data      = pack("d",   $margin);
4205
4206    if ($self->{_byte_order}) { $data = reverse $data }
4207
4208    $self->_prepend($header, $data);
4209}
4210
4211
4212###############################################################################
4213#
4214# _store_margin_bottom()
4215#
4216# Store the BOTTOMMARGIN BIFF record.
4217#
4218sub _store_margin_bottom {
4219
4220    my $self    = shift;
4221
4222    my $record  = 0x0029;                   # Record identifier
4223    my $length  = 0x0008;                   # Bytes to follow
4224
4225    my $margin  = $self->{_margin_bottom};  # Margin in inches
4226
4227    my $header    = pack("vv",  $record, $length);
4228    my $data      = pack("d",   $margin);
4229
4230    if ($self->{_byte_order}) { $data = reverse $data }
4231
4232    $self->_prepend($header, $data);
4233}
4234
4235
4236###############################################################################
4237#
4238# merge_cells($first_row, $first_col, $last_row, $last_col)
4239#
4240# This is an Excel97/2000 method. It is required to perform more complicated
4241# merging than the normal align merge in Format.pm
4242#
4243sub merge_cells {
4244
4245    my $self    = shift;
4246
4247    # Check for a cell reference in A1 notation and substitute row and column
4248    if ($_[0] =~ /^\D/) {
4249        @_ = $self->_substitute_cellref(@_);
4250    }
4251
4252    my $record  = 0x00E5;                   # Record identifier
4253    my $length  = 0x000A;                   # Bytes to follow
4254
4255    my $cref     = 1;                       # Number of refs
4256    my $rwFirst  = $_[0];                   # First row in reference
4257    my $colFirst = $_[1];                   # First col in reference
4258    my $rwLast   = $_[2] || $rwFirst;       # Last  row in reference
4259    my $colLast  = $_[3] || $colFirst;      # Last  col in reference
4260
4261
4262    # Excel doesn't allow a single cell to be merged
4263    return if $rwFirst == $rwLast and $colFirst == $colLast;
4264
4265    # Swap last row/col with first row/col as necessary
4266    ($rwFirst,  $rwLast ) = ($rwLast,  $rwFirst ) if $rwFirst  > $rwLast;
4267    ($colFirst, $colLast) = ($colLast, $colFirst) if $colFirst > $colLast;
4268
4269    my $header   = pack("vv",       $record, $length);
4270    my $data     = pack("vvvvv",    $cref,
4271                                    $rwFirst, $rwLast,
4272                                    $colFirst, $colLast);
4273
4274    $self->_append($header, $data);
4275}
4276
4277
4278###############################################################################
4279#
4280# merge_range($row1, $col1, $row2, $col2, $string, $format, $encoding)
4281#
4282# This is a wrapper to ensure correct use of the merge_cells method, i.e., write
4283# the first cell of the range, write the formatted blank cells in the range and
4284# then call the merge_cells record. Failing to do the steps in this order will
4285# cause Excel 97 to crash.
4286#
4287sub merge_range {
4288
4289    my $self    = shift;
4290
4291    # Check for a cell reference in A1 notation and substitute row and column
4292    if ($_[0] =~ /^\D/) {
4293        @_ = $self->_substitute_cellref(@_);
4294    }
4295    croak "Incorrect number of arguments" if @_ != 6 and @_ != 7;
4296    croak "Format argument is not a format object" unless ref $_[5];
4297
4298    my $rwFirst  = $_[0];
4299    my $colFirst = $_[1];
4300    my $rwLast   = $_[2];
4301    my $colLast  = $_[3];
4302    my $string   = $_[4];
4303    my $format   = $_[5];
4304    my $encoding = $_[6] ? 1 : 0;
4305
4306
4307    # Temp code to prevent merged formats in non-merged cells.
4308    my $error = "Error: refer to merge_range() in the documentation. " .
4309                "Can't use previously non-merged format in merged cells";
4310
4311    croak $error if $format->{_used_merge} == -1;
4312    $format->{_used_merge} = 0; # Until the end of this function.
4313
4314
4315    # Set the merge_range property of the format object. For BIFF8+.
4316    $format->set_merge_range();
4317
4318    # Excel doesn't allow a single cell to be merged
4319    croak "Can't merge single cell" if $rwFirst  == $rwLast and
4320                                       $colFirst == $colLast;
4321
4322    # Swap last row/col with first row/col as necessary
4323    ($rwFirst,  $rwLast ) = ($rwLast,  $rwFirst ) if $rwFirst  > $rwLast;
4324    ($colFirst, $colLast) = ($colLast, $colFirst) if $colFirst > $colLast;
4325
4326    # Write the first cell
4327    if ($encoding) {
4328        $self->write_utf16be_string($rwFirst, $colFirst, $string, $format);
4329    }
4330    else {
4331        $self->write               ($rwFirst, $colFirst, $string, $format);
4332    }
4333
4334    # Pad out the rest of the area with formatted blank cells.
4335    for my $row ($rwFirst .. $rwLast) {
4336        for my $col ($colFirst .. $colLast) {
4337            next if $row == $rwFirst and $col == $colFirst;
4338            $self->write_blank($row, $col, $format);
4339        }
4340    }
4341
4342    $self->merge_cells($rwFirst, $colFirst, $rwLast, $colLast);
4343
4344    # Temp code to prevent merged formats in non-merged cells.
4345    $format->{_used_merge} = 1;
4346
4347}
4348
4349
4350###############################################################################
4351#
4352# _store_print_headers()
4353#
4354# Write the PRINTHEADERS BIFF record.
4355#
4356sub _store_print_headers {
4357
4358    my $self        = shift;
4359
4360    my $record      = 0x002a;                   # Record identifier
4361    my $length      = 0x0002;                   # Bytes to follow
4362
4363    my $fPrintRwCol = $self->{_print_headers};  # Boolean flag
4364
4365    my $header      = pack("vv",  $record, $length);
4366    my $data        = pack("v",   $fPrintRwCol);
4367
4368    $self->_prepend($header, $data);
4369}
4370
4371
4372###############################################################################
4373#
4374# _store_print_gridlines()
4375#
4376# Write the PRINTGRIDLINES BIFF record. Must be used in conjunction with the
4377# GRIDSET record.
4378#
4379sub _store_print_gridlines {
4380
4381    my $self        = shift;
4382
4383    my $record      = 0x002b;                    # Record identifier
4384    my $length      = 0x0002;                    # Bytes to follow
4385
4386    my $fPrintGrid  = $self->{_print_gridlines}; # Boolean flag
4387
4388    my $header      = pack("vv",  $record, $length);
4389    my $data        = pack("v",   $fPrintGrid);
4390
4391    $self->_prepend($header, $data);
4392}
4393
4394
4395###############################################################################
4396#
4397# _store_gridset()
4398#
4399# Write the GRIDSET BIFF record. Must be used in conjunction with the
4400# PRINTGRIDLINES record.
4401#
4402sub _store_gridset {
4403
4404    my $self        = shift;
4405
4406    my $record      = 0x0082;                        # Record identifier
4407    my $length      = 0x0002;                        # Bytes to follow
4408
4409    my $fGridSet    = not $self->{_print_gridlines}; # Boolean flag
4410
4411    my $header      = pack("vv",  $record, $length);
4412    my $data        = pack("v",   $fGridSet);
4413
4414    $self->_prepend($header, $data);
4415
4416}
4417
4418
4419###############################################################################
4420#
4421# _store_guts()
4422#
4423# Write the GUTS BIFF record. This is used to configure the gutter margins
4424# where Excel outline symbols are displayed. The visibility of the gutters is
4425# controlled by a flag in WSBOOL. See also _store_wsbool().
4426#
4427# We are all in the gutter but some of us are looking at the stars.
4428#
4429sub _store_guts {
4430
4431    my $self        = shift;
4432
4433    my $record      = 0x0080;   # Record identifier
4434    my $length      = 0x0008;   # Bytes to follow
4435
4436    my $dxRwGut     = 0x0000;   # Size of row gutter
4437    my $dxColGut    = 0x0000;   # Size of col gutter
4438
4439    my $row_level   = $self->{_outline_row_level};
4440    my $col_level   = 0;
4441
4442
4443    # Calculate the maximum column outline level. The equivalent calculation
4444    # for the row outline level is carried out in set_row().
4445    #
4446    foreach my $colinfo (@{$self->{_colinfo}}) {
4447        # Skip cols without outline level info.
4448        next if @{$colinfo} < 6;
4449        $col_level = @{$colinfo}[5] if @{$colinfo}[5] > $col_level;
4450    }
4451
4452
4453    # Set the limits for the outline levels (0 <= x <= 7).
4454    $col_level = 0 if $col_level < 0;
4455    $col_level = 7 if $col_level > 7;
4456
4457
4458    # The displayed level is one greater than the max outline levels
4459    $row_level++ if $row_level > 0;
4460    $col_level++ if $col_level > 0;
4461
4462    my $header      = pack("vv",   $record, $length);
4463    my $data        = pack("vvvv", $dxRwGut, $dxColGut, $row_level, $col_level);
4464
4465    $self->_prepend($header, $data);
4466
4467}
4468
4469
4470###############################################################################
4471#
4472# _store_wsbool()
4473#
4474# Write the WSBOOL BIFF record, mainly for fit-to-page. Used in conjunction
4475# with the SETUP record.
4476#
4477sub _store_wsbool {
4478
4479    my $self        = shift;
4480
4481    my $record      = 0x0081;   # Record identifier
4482    my $length      = 0x0002;   # Bytes to follow
4483
4484    my $grbit       = 0x0000;   # Option flags
4485
4486    # Set the option flags
4487    $grbit |= 0x0001;                            # Auto page breaks visible
4488    $grbit |= 0x0020 if $self->{_outline_style}; # Auto outline styles
4489    $grbit |= 0x0040 if $self->{_outline_below}; # Outline summary below
4490    $grbit |= 0x0080 if $self->{_outline_right}; # Outline summary right
4491    $grbit |= 0x0100 if $self->{_fit_page};      # Page setup fit to page
4492    $grbit |= 0x0400 if $self->{_outline_on};    # Outline symbols displayed
4493
4494
4495    my $header      = pack("vv",  $record, $length);
4496    my $data        = pack("v",   $grbit);
4497
4498    $self->_prepend($header, $data);
4499}
4500
4501
4502###############################################################################
4503#
4504# _store_hbreak()
4505#
4506# Write the HORIZONTALPAGEBREAKS BIFF record.
4507#
4508sub _store_hbreak {
4509
4510    my $self    = shift;
4511
4512    # Return if the user hasn't specified pagebreaks
4513    return unless @{$self->{_hbreaks}};
4514
4515    # Sort and filter array of page breaks
4516    my @breaks  = $self->_sort_pagebreaks(@{$self->{_hbreaks}});
4517
4518    my $record  = 0x001b;               # Record identifier
4519    my $cbrk    = scalar @breaks;       # Number of page breaks
4520    my $length  = 2 + 6*$cbrk;          # Bytes to follow
4521
4522
4523    my $header  = pack("vv",  $record, $length);
4524    my $data    = pack("v",   $cbrk);
4525
4526    # Append each page break
4527    foreach my $break (@breaks) {
4528        $data .= pack("vvv", $break, 0x0000, 0x00ff);
4529    }
4530
4531    $self->_prepend($header, $data);
4532}
4533
4534
4535###############################################################################
4536#
4537# _store_vbreak()
4538#
4539# Write the VERTICALPAGEBREAKS BIFF record.
4540#
4541sub _store_vbreak {
4542
4543    my $self    = shift;
4544
4545    # Return if the user hasn't specified pagebreaks
4546    return unless @{$self->{_vbreaks}};
4547
4548    # Sort and filter array of page breaks
4549    my @breaks  = $self->_sort_pagebreaks(@{$self->{_vbreaks}});
4550
4551    my $record  = 0x001a;               # Record identifier
4552    my $cbrk    = scalar @breaks;       # Number of page breaks
4553    my $length  = 2 + 6*$cbrk;          # Bytes to follow
4554
4555
4556    my $header  = pack("vv",  $record, $length);
4557    my $data    = pack("v",   $cbrk);
4558
4559    # Append each page break
4560    foreach my $break (@breaks) {
4561        $data .= pack("vvv", $break, 0x0000, 0xffff);
4562    }
4563
4564    $self->_prepend($header, $data);
4565}
4566
4567
4568###############################################################################
4569#
4570# _store_protect()
4571#
4572# Set the Biff PROTECT record to indicate that the worksheet is protected.
4573#
4574sub _store_protect {
4575
4576    my $self        = shift;
4577
4578    # Exit unless sheet protection has been specified
4579    return unless $self->{_protect};
4580
4581    my $record      = 0x0012;               # Record identifier
4582    my $length      = 0x0002;               # Bytes to follow
4583
4584    my $fLock       = $self->{_protect};    # Worksheet is protected
4585
4586    my $header      = pack("vv", $record, $length);
4587    my $data        = pack("v",  $fLock);
4588
4589    $self->_prepend($header, $data);
4590}
4591
4592
4593###############################################################################
4594#
4595# _store_obj_protect()
4596#
4597# Set the Biff OBJPROTECT record to indicate that objects are protected.
4598#
4599sub _store_obj_protect {
4600
4601    my $self        = shift;
4602
4603    # Exit unless sheet protection has been specified
4604    return unless $self->{_protect};
4605
4606    my $record      = 0x0063;               # Record identifier
4607    my $length      = 0x0002;               # Bytes to follow
4608
4609    my $fLock       = $self->{_protect};    # Worksheet is protected
4610
4611    my $header      = pack("vv", $record, $length);
4612    my $data        = pack("v",  $fLock);
4613
4614    $self->_prepend($header, $data);
4615}
4616
4617
4618###############################################################################
4619#
4620# _store_password()
4621#
4622# Write the worksheet PASSWORD record.
4623#
4624sub _store_password {
4625
4626    my $self        = shift;
4627
4628    # Exit unless sheet protection and password have been specified
4629    return unless $self->{_protect} and defined $self->{_password};
4630
4631    my $record      = 0x0013;               # Record identifier
4632    my $length      = 0x0002;               # Bytes to follow
4633
4634    my $wPassword   = $self->{_password};   # Encoded password
4635
4636    my $header      = pack("vv", $record, $length);
4637    my $data        = pack("v",  $wPassword);
4638
4639    $self->_prepend($header, $data);
4640}
4641
4642
4643#
4644# Note about compatibility mode.
4645#
4646# Excel doesn't require every possible Biff record to be present in a file.
4647# In particular if the indexing records INDEX, ROW and DBCELL aren't present
4648# it just ignores the fact and reads the cells anyway. This is also true of
4649# the EXTSST record. Gnumeric and OOo also take this approach. This allows
4650# WriteExcel to ignore these records in order to minimise the amount of data
4651# stored in memory. However, other third party applications that read Excel
4652# files often expect these records to be present. In "compatibility mode"
4653# WriteExcel writes these records and tries to be as close to an Excel
4654# generated file as possible.
4655#
4656# This requires additional data to be stored in memory until the file is
4657# about to be written. This incurs a memory and speed penalty and may not be
4658# suitable for very large files.
4659#
4660
4661
4662
4663###############################################################################
4664#
4665# _store_table()
4666#
4667# Write cell data stored in the worksheet row/col table.
4668#
4669# This is only used when compatibity_mode() is in operation.
4670#
4671# This method writes ROW data, then cell data (NUMBER, LABELSST, etc) and then
4672# DBCELL records in blocks of 32 rows. This is explained in detail (for a
4673# change) in the Excel SDK and in the OOo Excel file format doc.
4674#
4675sub _store_table {
4676
4677    my $self = shift;
4678
4679    return unless $self->{_compatibility};
4680
4681    # Offset from the DBCELL record back to the first ROW of the 32 row block.
4682    my $row_offset = 0;
4683
4684    # Track rows that have cell data or modified by set_row().
4685    my @written_rows;
4686
4687
4688    # Write the ROW records with updated max/min col fields.
4689    #
4690    for my $row (0 .. $self->{_dim_rowmax} -1) {
4691        # Skip unless there is cell data in row or the row has been modified.
4692        next unless $self->{_table}->[$row] or $self->{_row_data}->{$row};
4693
4694        # Store the rows with data.
4695        push @written_rows, $row;
4696
4697        # Increase the row offset by the length of a ROW record;
4698        $row_offset += 20;
4699
4700        # The max/min cols in the ROW records are the same as in DIMENSIONS.
4701        my $col_min = $self->{_dim_colmin};
4702        my $col_max = $self->{_dim_colmax};
4703
4704        # Write a user specified ROW record (modified by set_row()).
4705        if ($self->{_row_data}->{$row}) {
4706            # Rewrite the min and max cols for user defined row record.
4707            my $packed_row = $self->{_row_data}->{$row};
4708            substr $packed_row, 6, 4, pack('vv', $col_min, $col_max);
4709            $self->_append($packed_row);
4710        }
4711        else {
4712            # Write a default Row record if there isn't a  user defined ROW.
4713            $self->_write_row_default($row, $col_min, $col_max);
4714        }
4715
4716
4717
4718        # If 32 rows have been written or we are at the last row in the
4719        # worksheet then write the cell data and the DBCELL record.
4720        #
4721        if (@written_rows == 32 or $row == $self->{_dim_rowmax} -1) {
4722
4723            # Offsets to the first cell of each row.
4724            my @cell_offsets;
4725            push @cell_offsets, $row_offset - 20;
4726
4727            # Write the cell data in each row and sum their lengths for the
4728            # cell offsets.
4729            #
4730            for my $row (@written_rows) {
4731                my $cell_offset = 0;
4732
4733                for my $col (@{$self->{_table}->[$row]}) {
4734                    next unless $col;
4735                    $self->_append($col);
4736                    my $length = length $col;
4737                    $row_offset  += $length;
4738                    $cell_offset += $length;
4739                }
4740                push @cell_offsets, $cell_offset;
4741            }
4742
4743            # The last offset isn't required.
4744            pop @cell_offsets;
4745
4746            # Stores the DBCELL offset for use in the INDEX record.
4747            push @{$self->{_db_indices}}, $self->{_datasize};
4748
4749            # Write the DBCELL record.
4750            $self->_store_dbcell($row_offset, @cell_offsets);
4751
4752            # Clear the variable for the next block of rows.
4753            @written_rows   = ();
4754            @cell_offsets   = ();
4755            $row_offset     = 0;
4756        }
4757    }
4758}
4759
4760
4761###############################################################################
4762#
4763# _store_dbcell()
4764#
4765# Store the DBCELL record using the offset calculated in _store_table().
4766#
4767# This is only used when compatibity_mode() is in operation.
4768#
4769sub _store_dbcell {
4770
4771    my $self            = shift;
4772    my $row_offset      = shift;
4773    my @cell_offsets    = @_;
4774
4775
4776    my $record          = 0x00D7;                 # Record identifier
4777    my $length          = 4 + 2 * @cell_offsets;  # Bytes to follow
4778
4779
4780    my $header          = pack 'vv', $record, $length;
4781    my $data            = pack 'V',  $row_offset;
4782       $data           .= pack 'v', $_ for @cell_offsets;
4783
4784    $self->_append($header, $data);
4785}
4786
4787
4788###############################################################################
4789#
4790# _store_index()
4791#
4792# Store the INDEX record using the DBCELL offsets calculated in _store_table().
4793#
4794# This is only used when compatibity_mode() is in operation.
4795#
4796sub _store_index {
4797
4798    my $self = shift;
4799
4800    return unless $self->{_compatibility};
4801
4802    my @indices     = @{$self->{_db_indices}};
4803    my $reserved    = 0x00000000;
4804    my $row_min     = $self->{_dim_rowmin};
4805    my $row_max     = $self->{_dim_rowmax};
4806
4807    my $record      = 0x020B;             # Record identifier
4808    my $length      = 16 + 4 * @indices;  # Bytes to follow
4809
4810    my $header      = pack 'vv',   $record, $length;
4811    my $data        = pack 'VVVV', $reserved,
4812                                   $row_min,
4813                                   $row_max,
4814                                   $reserved;
4815
4816    for my $index (@indices) {
4817       $data .= pack 'V', $index + $self->{_offset} + 20 + $length +4;
4818    }
4819
4820    $self->_prepend($header, $data);
4821
4822}
4823
4824
4825###############################################################################
4826#
4827# insert_chart($row, $col, $chart, $x, $y, $scale_x, $scale_y)
4828#
4829# Insert a chart into a worksheet. The $chart argument should be a Chart
4830# object or else it is assumed to be a filename of an external binary file.
4831# The latter is for backwards compatibility.
4832#
4833sub insert_chart {
4834
4835    my $self        = shift;
4836
4837    # Check for a cell reference in A1 notation and substitute row and column
4838    if ($_[0] =~ /^\D/) {
4839        @_ = $self->_substitute_cellref(@_);
4840    }
4841
4842    my $row         = $_[0];
4843    my $col         = $_[1];
4844    my $chart       = $_[2];
4845    my $x_offset    = $_[3] || 0;
4846    my $y_offset    = $_[4] || 0;
4847    my $scale_x     = $_[5] || 1;
4848    my $scale_y     = $_[6] || 1;
4849
4850    croak "Insufficient arguments in insert_chart()" unless @_ >= 3;
4851
4852    if ( ref $chart ) {
4853        # Check for a Chart object.
4854        croak "Not a Chart object in insert_chart()"
4855          unless $chart->isa( 'Spreadsheet::WriteExcel::Chart' );
4856
4857        # Check that the chart is an embedded style chart.
4858        croak "Not a embedded style Chart object in insert_chart()"
4859          unless $chart->{_embedded};
4860
4861    }
4862    else {
4863
4864        # Assume an external bin filename.
4865        croak "Couldn't locate $chart in insert_chart(): $!" unless -e $chart;
4866    }
4867
4868    $self->{_charts}->{$row}->{$col} =  [
4869                                           $row,
4870                                           $col,
4871                                           $chart,
4872                                           $x_offset,
4873                                           $y_offset,
4874                                           $scale_x,
4875                                           $scale_y,
4876                                        ];
4877
4878}
4879
4880# Older method name for backwards compatibility.
4881*embed_chart = *insert_chart;
4882
4883###############################################################################
4884#
4885# insert_image($row, $col, $filename, $x, $y, $scale_x, $scale_y)
4886#
4887# Insert an image into the worksheet.
4888#
4889sub insert_image {
4890
4891    my $self        = shift;
4892
4893    # Check for a cell reference in A1 notation and substitute row and column
4894    if ($_[0] =~ /^\D/) {
4895        @_ = $self->_substitute_cellref(@_);
4896    }
4897
4898    my $row         = $_[0];
4899    my $col         = $_[1];
4900    my $image       = $_[2];
4901    my $x_offset    = $_[3] || 0;
4902    my $y_offset    = $_[4] || 0;
4903    my $scale_x     = $_[5] || 1;
4904    my $scale_y     = $_[6] || 1;
4905
4906    croak "Insufficient arguments in insert_image()" unless @_ >= 3;
4907    croak "Couldn't locate $image: $!"               unless -e $image;
4908
4909    $self->{_images}->{$row}->{$col} = [
4910                                           $row,
4911                                           $col,
4912                                           $image,
4913                                           $x_offset,
4914                                           $y_offset,
4915                                           $scale_x,
4916                                           $scale_y,
4917                                        ];
4918
4919}
4920
4921# Older method name for backwards compatibility.
4922*insert_bitmap = *insert_image;
4923
4924
4925###############################################################################
4926#
4927#  _position_object()
4928#
4929# Calculate the vertices that define the position of a graphical object within
4930# the worksheet.
4931#
4932#         +------------+------------+
4933#         |     A      |      B     |
4934#   +-----+------------+------------+
4935#   |     |(x1,y1)     |            |
4936#   |  1  |(A1)._______|______      |
4937#   |     |    |              |     |
4938#   |     |    |              |     |
4939#   +-----+----|    BITMAP    |-----+
4940#   |     |    |              |     |
4941#   |  2  |    |______________.     |
4942#   |     |            |        (B2)|
4943#   |     |            |     (x2,y2)|
4944#   +---- +------------+------------+
4945#
4946# Example of a bitmap that covers some of the area from cell A1 to cell B2.
4947#
4948# Based on the width and height of the bitmap we need to calculate 8 vars:
4949#     $col_start, $row_start, $col_end, $row_end, $x1, $y1, $x2, $y2.
4950# The width and height of the cells are also variable and have to be taken into
4951# account.
4952# The values of $col_start and $row_start are passed in from the calling
4953# function. The values of $col_end and $row_end are calculated by subtracting
4954# the width and height of the bitmap from the width and height of the
4955# underlying cells.
4956# The vertices are expressed as a percentage of the underlying cell width as
4957# follows (rhs values are in pixels):
4958#
4959#       x1 = X / W *1024
4960#       y1 = Y / H *256
4961#       x2 = (X-1) / W *1024
4962#       y2 = (Y-1) / H *256
4963#
4964#       Where:  X is distance from the left side of the underlying cell
4965#               Y is distance from the top of the underlying cell
4966#               W is the width of the cell
4967#               H is the height of the cell
4968#
4969# Note: the SDK incorrectly states that the height should be expressed as a
4970# percentage of 1024.
4971#
4972sub _position_object {
4973
4974    my $self = shift;
4975
4976    my $col_start;  # Col containing upper left corner of object
4977    my $x1;         # Distance to left side of object
4978
4979    my $row_start;  # Row containing top left corner of object
4980    my $y1;         # Distance to top of object
4981
4982    my $col_end;    # Col containing lower right corner of object
4983    my $x2;         # Distance to right side of object
4984
4985    my $row_end;    # Row containing bottom right corner of object
4986    my $y2;         # Distance to bottom of object
4987
4988    my $width;      # Width of image frame
4989    my $height;     # Height of image frame
4990
4991    ($col_start, $row_start, $x1, $y1, $width, $height) = @_;
4992
4993
4994    # Adjust start column for offsets that are greater than the col width
4995    while ($x1 >= $self->_size_col($col_start)) {
4996        $x1 -= $self->_size_col($col_start);
4997        $col_start++;
4998    }
4999
5000    # Adjust start row for offsets that are greater than the row height
5001    while ($y1 >= $self->_size_row($row_start)) {
5002        $y1 -= $self->_size_row($row_start);
5003        $row_start++;
5004    }
5005
5006
5007    # Initialise end cell to the same as the start cell
5008    $col_end    = $col_start;
5009    $row_end    = $row_start;
5010
5011    $width      = $width  + $x1;
5012    $height     = $height + $y1;
5013
5014
5015    # Subtract the underlying cell widths to find the end cell of the image
5016    while ($width >= $self->_size_col($col_end)) {
5017        $width -= $self->_size_col($col_end);
5018        $col_end++;
5019    }
5020
5021    # Subtract the underlying cell heights to find the end cell of the image
5022    while ($height >= $self->_size_row($row_end)) {
5023        $height -= $self->_size_row($row_end);
5024        $row_end++;
5025    }
5026
5027    # Bitmap isn't allowed to start or finish in a hidden cell, i.e. a cell
5028    # with zero eight or width.
5029    #
5030    return if $self->_size_col($col_start) == 0;
5031    return if $self->_size_col($col_end)   == 0;
5032    return if $self->_size_row($row_start) == 0;
5033    return if $self->_size_row($row_end)   == 0;
5034
5035    # Convert the pixel values to the percentage value expected by Excel
5036    $x1 = $x1     / $self->_size_col($col_start)   * 1024;
5037    $y1 = $y1     / $self->_size_row($row_start)   *  256;
5038    $x2 = $width  / $self->_size_col($col_end)     * 1024;
5039    $y2 = $height / $self->_size_row($row_end)     *  256;
5040
5041    # Simulate ceil() without calling POSIX::ceil().
5042    $x1 = int($x1 +0.5);
5043    $y1 = int($y1 +0.5);
5044    $x2 = int($x2 +0.5);
5045    $y2 = int($y2 +0.5);
5046
5047    return( $col_start, $x1,
5048            $row_start, $y1,
5049            $col_end,   $x2,
5050            $row_end,   $y2
5051          );
5052}
5053
5054
5055###############################################################################
5056#
5057# _size_col($col)
5058#
5059# Convert the width of a cell from user's units to pixels. Excel rounds the
5060# column width to the nearest pixel. If the width hasn't been set by the user
5061# we use the default value. If the column is hidden we use a value of zero.
5062#
5063sub _size_col {
5064
5065    my $self = shift;
5066    my $col  = $_[0];
5067
5068    # Look up the cell value to see if it has been changed
5069    if (exists $self->{_col_sizes}->{$col}) {
5070        my $width = $self->{_col_sizes}->{$col};
5071
5072        # The relationship is different for user units less than 1.
5073        if ($width < 1) {
5074            return int($width *12);
5075        }
5076        else {
5077            return int($width *7 ) +5;
5078        }
5079    }
5080    else {
5081        return 64;
5082    }
5083}
5084
5085
5086###############################################################################
5087#
5088# _size_row($row)
5089#
5090# Convert the height of a cell from user's units to pixels. By interpolation
5091# the relationship is: y = 4/3x. If the height hasn't been set by the user we
5092# use the default value. If the row is hidden we use a value of zero. (Not
5093# possible to hide row yet).
5094#
5095sub _size_row {
5096
5097    my $self = shift;
5098    my $row  = $_[0];
5099
5100    # Look up the cell value to see if it has been changed
5101    if (exists $self->{_row_sizes}->{$row}) {
5102        if ($self->{_row_sizes}->{$row} == 0) {
5103            return 0;
5104        }
5105        else {
5106            return int (4/3 * $self->{_row_sizes}->{$row});
5107        }
5108    }
5109    else {
5110        return 17;
5111    }
5112}
5113
5114
5115###############################################################################
5116#
5117# _store_zoom($zoom)
5118#
5119#
5120# Store the window zoom factor. This should be a reduced fraction but for
5121# simplicity we will store all fractions with a numerator of 100.
5122#
5123sub _store_zoom {
5124
5125    my $self        = shift;
5126
5127    # If scale is 100 we don't need to write a record
5128    return if $self->{_zoom} == 100;
5129
5130    my $record      = 0x00A0;               # Record identifier
5131    my $length      = 0x0004;               # Bytes to follow
5132
5133    my $header      = pack("vv", $record, $length   );
5134    my $data        = pack("vv", $self->{_zoom}, 100);
5135
5136    $self->_append($header, $data);
5137}
5138
5139
5140###############################################################################
5141#
5142# write_utf16be_string($row, $col, $string, $format)
5143#
5144# Write a Unicode string to the specified row and column (zero indexed).
5145# $format is optional.
5146# Returns  0 : normal termination
5147#         -1 : insufficient number of arguments
5148#         -2 : row or column out of range
5149#         -3 : long string truncated to 255 chars
5150#
5151sub write_utf16be_string {
5152
5153    my $self = shift;
5154
5155    # Check for a cell reference in A1 notation and substitute row and column
5156    if ($_[0] =~ /^\D/) {
5157        @_ = $self->_substitute_cellref(@_);
5158    }
5159
5160    if (@_ < 3) { return -1 }                        # Check the number of args
5161
5162    my $record      = 0x00FD;                        # Record identifier
5163    my $length      = 0x000A;                        # Bytes to follow
5164
5165    my $row         = $_[0];                         # Zero indexed row
5166    my $col         = $_[1];                         # Zero indexed column
5167    my $strlen      = length($_[2]);
5168    my $str         = $_[2];
5169    my $xf          = _XF($self, $row, $col, $_[3]); # The cell format
5170    my $encoding    = 0x1;
5171    my $str_error   = 0;
5172
5173    # Check that row and col are valid and store max and min values
5174    return -2 if $self->_check_dimensions($row, $col);
5175
5176    # Limit the utf16 string to the max number of chars (not bytes).
5177    if ($strlen > 32767* 2) {
5178        $str       = substr($str, 0, 32767*2);
5179        $str_error = -3;
5180    }
5181
5182
5183    my $num_bytes = length $str;
5184    my $num_chars = int($num_bytes / 2);
5185
5186
5187    # Check for a valid 2-byte char string.
5188    croak "Uneven number of bytes in Unicode string" if $num_bytes % 2;
5189
5190
5191    # Change from UTF16 big-endian to little endian
5192    $str = pack "v*", unpack "n*", $str;
5193
5194
5195    # Add the encoding and length header to the string.
5196    my $str_header  = pack("vC", $num_chars, $encoding);
5197    $str            = $str_header . $str;
5198
5199
5200    if (not exists ${$self->{_str_table}}->{$str}) {
5201        ${$self->{_str_table}}->{$str} = ${$self->{_str_unique}}++;
5202    }
5203
5204
5205    ${$self->{_str_total}}++;
5206
5207
5208    my $header = pack("vv",   $record, $length);
5209    my $data   = pack("vvvV", $row, $col, $xf, ${$self->{_str_table}}->{$str});
5210
5211    # Store the data or write immediately depending on the compatibility mode.
5212    if ($self->{_compatibility}) {
5213        $self->{_table}->[$row]->[$col] = $header . $data;
5214    }
5215    else {
5216        $self->_append($header, $data);
5217    }
5218
5219    return $str_error;
5220}
5221
5222
5223###############################################################################
5224#
5225# write_utf16le_string($row, $col, $string, $format)
5226#
5227# Write a UTF-16LE string to the specified row and column (zero indexed).
5228# $format is optional.
5229# Returns  0 : normal termination
5230#         -1 : insufficient number of arguments
5231#         -2 : row or column out of range
5232#         -3 : long string truncated to 255 chars
5233#
5234sub write_utf16le_string {
5235
5236    my $self = shift;
5237
5238    # Check for a cell reference in A1 notation and substitute row and column
5239    if ($_[0] =~ /^\D/) {
5240        @_ = $self->_substitute_cellref(@_);
5241    }
5242
5243    if (@_ < 3) { return -1 }                        # Check the number of args
5244
5245    my $record      = 0x00FD;                        # Record identifier
5246    my $length      = 0x000A;                        # Bytes to follow
5247
5248    my $row         = $_[0];                         # Zero indexed row
5249    my $col         = $_[1];                         # Zero indexed column
5250    my $str         = $_[2];
5251    my $format      = $_[3];                         # The cell format
5252
5253
5254    # Change from UTF16 big-endian to little endian
5255    $str = pack "v*", unpack "n*", $str;
5256
5257
5258    return $self->write_utf16be_string($row, $col, $str, $format);
5259}
5260
5261
5262# Older method name for backwards compatibility.
5263*write_unicode    = *write_utf16be_string;
5264*write_unicode_le = *write_utf16le_string;
5265
5266
5267
5268###############################################################################
5269#
5270# _store_autofilters()
5271#
5272# Function to iterate through the columns that form part of an autofilter
5273# range and write Biff AUTOFILTER records if a filter expression has been set.
5274#
5275sub _store_autofilters {
5276
5277    my $self = shift;
5278
5279    # Skip all columns if no filter have been set.
5280    return unless $self->{_filter_on};
5281
5282    my (undef, undef, $col1, $col2) = @{$self->{_filter_area}};
5283
5284    for my $i ($col1 .. $col2) {
5285        # Reverse order since records are being pre-pended.
5286        my $col = $col2 -$i;
5287
5288        # Skip if column doesn't have an active filter.
5289        next unless $self->{_filter_cols}->{$col};
5290
5291        # Retrieve the filter tokens
5292        my @tokens =  @{$self->{_filter_cols}->{$col}};
5293
5294        # Filter columns are relative to the first column in the filter.
5295        my $filter_col = $col - $col1;
5296
5297        # Write the autofilter records.
5298        $self->_store_autofilter($filter_col, @tokens);
5299    }
5300}
5301
5302
5303###############################################################################
5304#
5305# _store_autofilter()
5306#
5307# Function to write worksheet AUTOFILTER records. These contain 2 Biff Doper
5308# structures to represent the 2 possible filter conditions.
5309#
5310sub _store_autofilter {
5311
5312    my $self            = shift;
5313
5314    my $record          = 0x009E;
5315    my $length          = 0x0000;
5316
5317    my $index           = $_[0];
5318    my $operator_1      = $_[1];
5319    my $token_1         = $_[2];
5320    my $join            = $_[3]; # And/Or
5321    my $operator_2      = $_[4];
5322    my $token_2         = $_[5];
5323
5324    my $top10_active    = 0;
5325    my $top10_direction = 0;
5326    my $top10_percent   = 0;
5327    my $top10_value     = 101;
5328
5329    my $grbit       = $join;
5330    my $optimised_1 = 0;
5331    my $optimised_2 = 0;
5332    my $doper_1     = '';
5333    my $doper_2     = '';
5334    my $string_1    = '';
5335    my $string_2    = '';
5336
5337    # Excel used an optimisation in the case of a simple equality.
5338    $optimised_1 = 1 if                         $operator_1 == 2;
5339    $optimised_2 = 1 if defined $operator_2 and $operator_2 == 2;
5340
5341
5342    # Convert non-simple equalities back to type 2. See  _parse_filter_tokens().
5343    $operator_1 = 2 if                         $operator_1 == 22;
5344    $operator_2 = 2 if defined $operator_2 and $operator_2 == 22;
5345
5346
5347    # Handle a "Top" style expression.
5348    if ($operator_1 >= 30) {
5349        # Remove the second expression if present.
5350        $operator_2 = undef;
5351        $token_2    = undef;
5352
5353        # Set the active flag.
5354        $top10_active    = 1;
5355
5356        if ($operator_1 == 30 or $operator_1 == 31) {
5357            $top10_direction = 1;
5358        }
5359
5360        if ($operator_1 == 31 or $operator_1 == 33) {
5361            $top10_percent = 1;
5362        }
5363
5364        if ($top10_direction == 1) {
5365            $operator_1 = 6
5366        }
5367        else {
5368            $operator_1 = 3
5369        }
5370
5371        $top10_value     = $token_1;
5372        $token_1         = 0;
5373    }
5374
5375
5376    $grbit     |= $optimised_1      << 2;
5377    $grbit     |= $optimised_2      << 3;
5378    $grbit     |= $top10_active     << 4;
5379    $grbit     |= $top10_direction  << 5;
5380    $grbit     |= $top10_percent    << 6;
5381    $grbit     |= $top10_value      << 7;
5382
5383    ($doper_1, $string_1) = $self->_pack_doper($operator_1, $token_1);
5384    ($doper_2, $string_2) = $self->_pack_doper($operator_2, $token_2);
5385
5386    my $data    = pack 'v', $index;
5387       $data   .= pack 'v', $grbit;
5388       $data   .= $doper_1;
5389       $data   .= $doper_2;
5390       $data   .= $string_1;
5391       $data   .= $string_2;
5392
5393       $length  = length $data;
5394    my $header  = pack('vv',  $record, $length);
5395
5396    $self->_prepend($header, $data);
5397}
5398
5399
5400###############################################################################
5401#
5402# _pack_doper()
5403#
5404# Create a Biff Doper structure that represents a filter expression. Depending
5405# on the type of the token we pack an Empty, String or Number doper.
5406#
5407sub _pack_doper {
5408
5409    my $self        = shift;
5410
5411    my $operator    = $_[0];
5412    my $token       = $_[1];
5413
5414    my $doper       = '';
5415    my $string      = '';
5416
5417
5418    # Return default doper for non-defined filters.
5419    if (not defined $operator) {
5420        return ($self->_pack_unused_doper, $string);
5421    }
5422
5423
5424    if ($token =~ /^blanks|nonblanks$/i) {
5425        $doper  = $self->_pack_blanks_doper($operator, $token);
5426    }
5427    elsif ($operator == 2 or
5428        $token    !~ /^([+-]?)(?=\d|\.\d)\d*(\.\d*)?([Ee]([+-]?\d+))?$/)
5429    {
5430        # Excel treats all tokens as strings if the operator is equality, =.
5431
5432        $string = $token;
5433
5434        my $encoding = 0;
5435        my $length   = length $string;
5436
5437        # Handle utf8 strings in perl 5.8.
5438        if ($] >= 5.008) {
5439            require Encode;
5440
5441            if (Encode::is_utf8($string)) {
5442                $string = Encode::encode("UTF-16BE", $string);
5443                $encoding = 1;
5444            }
5445        }
5446
5447        $string = pack('C', $encoding) . $string;
5448        $doper  = $self->_pack_string_doper($operator, $length);
5449    }
5450    else {
5451        $string = '';
5452        $doper  = $self->_pack_number_doper($operator, $token);
5453    }
5454
5455    return ($doper, $string);
5456}
5457
5458
5459###############################################################################
5460#
5461# _pack_unused_doper()
5462#
5463# Pack an empty Doper structure.
5464#
5465sub _pack_unused_doper {
5466
5467    my $self        = shift;
5468
5469    return pack 'C10', (0x0) x 10;
5470}
5471
5472
5473###############################################################################
5474#
5475# _pack_blanks_doper()
5476#
5477# Pack an Blanks/NonBlanks Doper structure.
5478#
5479sub _pack_blanks_doper {
5480
5481    my $self        = shift;
5482
5483    my $operator    = $_[0];
5484    my $token       = $_[1];
5485    my $type;
5486
5487    if ($token eq 'blanks') {
5488        $type     = 0x0C;
5489        $operator = 2;
5490
5491    }
5492    else {
5493        $type     = 0x0E;
5494        $operator = 5;
5495    }
5496
5497
5498    my $doper       = pack 'CCVV',    $type,         # Data type
5499                                      $operator,     #
5500                                      0x0000,        # Reserved
5501                                      0x0000;        # Reserved
5502    return $doper;
5503}
5504
5505
5506###############################################################################
5507#
5508# _pack_string_doper()
5509#
5510# Pack an string Doper structure.
5511#
5512sub _pack_string_doper {
5513
5514    my $self        = shift;
5515
5516    my $operator    = $_[0];
5517    my $length      = $_[1];
5518    my $doper       = pack 'CCVCCCC', 0x06,          # Data type
5519                                      $operator,     #
5520                                      0x0000,        # Reserved
5521                                      $length,       # String char length.
5522                                      0x0, 0x0, 0x0; # Reserved
5523    return $doper;
5524}
5525
5526
5527###############################################################################
5528#
5529# _pack_number_doper()
5530#
5531# Pack an IEEE double number Doper structure.
5532#
5533sub _pack_number_doper {
5534
5535    my $self        = shift;
5536
5537    my $operator    = $_[0];
5538    my $number      = $_[1];
5539       $number      = pack 'd', $number;
5540       $number      = reverse $number if $self->{_byte_order};
5541
5542    my $doper       = pack 'CC', 0x04, $operator;
5543       $doper      .= $number;
5544
5545    return $doper;
5546}
5547
5548
5549#
5550# Methods related to comments and MSO objects.
5551#
5552
5553
5554###############################################################################
5555#
5556# _prepare_images()
5557#
5558# Turn the HoH that stores the images into an array for easier handling.
5559#
5560sub _prepare_images {
5561
5562    my $self    = shift;
5563
5564    my $count   = 0;
5565    my @images;
5566
5567
5568    # We sort the images by row and column but that isn't strictly required.
5569    #
5570    my @rows = sort {$a <=> $b} keys %{$self->{_images}};
5571
5572    for my $row (@rows) {
5573        my @cols = sort {$a <=> $b} keys %{$self->{_images}->{$row}};
5574
5575        for my $col (@cols) {
5576            push @images, $self->{_images}->{$row}->{$col};
5577            $count++;
5578        }
5579    }
5580
5581    $self->{_images}       = {};
5582    $self->{_images_array} = \@images;
5583
5584    return $count;
5585}
5586
5587
5588###############################################################################
5589#
5590# _prepare_comments()
5591#
5592# Turn the HoH that stores the comments into an array for easier handling.
5593#
5594sub _prepare_comments {
5595
5596    my $self    = shift;
5597
5598    my $count   = 0;
5599    my @comments;
5600
5601
5602    # We sort the comments by row and column but that isn't strictly required.
5603    #
5604    my @rows = sort {$a <=> $b} keys %{$self->{_comments}};
5605
5606    for my $row (@rows) {
5607        my @cols = sort {$a <=> $b} keys %{$self->{_comments}->{$row}};
5608
5609        for my $col (@cols) {
5610            push @comments, $self->{_comments}->{$row}->{$col};
5611            $count++;
5612        }
5613    }
5614
5615    $self->{_comments}       = {};
5616    $self->{_comments_array} = \@comments;
5617
5618    return $count;
5619}
5620
5621
5622###############################################################################
5623#
5624# _prepare_charts()
5625#
5626# Turn the HoH that stores the charts into an array for easier handling.
5627#
5628sub _prepare_charts {
5629
5630    my $self    = shift;
5631
5632    my $count   = 0;
5633    my @charts;
5634
5635
5636    # We sort the charts by row and column but that isn't strictly required.
5637    #
5638    my @rows = sort {$a <=> $b} keys %{$self->{_charts}};
5639
5640    for my $row (@rows) {
5641        my @cols = sort {$a <=> $b} keys %{$self->{_charts}->{$row}};
5642
5643        for my $col (@cols) {
5644            push @charts, $self->{_charts}->{$row}->{$col};
5645            $count++;
5646        }
5647    }
5648
5649    $self->{_charts}       = {};
5650    $self->{_charts_array} = \@charts;
5651
5652    return $count;
5653}
5654
5655
5656###############################################################################
5657#
5658# _store_images()
5659#
5660# Store the collections of records that make up images.
5661#
5662sub _store_images {
5663
5664    my $self            = shift;
5665
5666    my $record          = 0x00EC;           # Record identifier
5667    my $length          = 0x0000;           # Bytes to follow
5668
5669    my @ids             = @{$self->{_object_ids  }};
5670    my $spid            = shift @ids;
5671
5672    my @images          = @{$self->{_images_array}};
5673    my $num_images      = scalar @images;
5674
5675    my $num_filters     = $self->{_filter_count};
5676    my $num_comments    = @{$self->{_comments_array}};
5677    my $num_charts      = @{$self->{_charts_array  }};
5678
5679    # Skip this if there aren't any images.
5680    return unless $num_images;
5681
5682    for my $i (0 .. $num_images-1) {
5683        my $row         =   $images[$i]->[0];
5684        my $col         =   $images[$i]->[1];
5685        my $name        =   $images[$i]->[2];
5686        my $x_offset    =   $images[$i]->[3];
5687        my $y_offset    =   $images[$i]->[4];
5688        my $scale_x     =   $images[$i]->[5];
5689        my $scale_y     =   $images[$i]->[6];
5690        my $image_id    =   $images[$i]->[7];
5691        my $type        =   $images[$i]->[8];
5692        my $width       =   $images[$i]->[9];
5693        my $height      =   $images[$i]->[10];
5694
5695        $width  *= $scale_x if $scale_x;
5696        $height *= $scale_y if $scale_y;
5697
5698
5699        # Calculate the positions of image object.
5700        my @vertices = $self->_position_object( $col,
5701                                                $row,
5702                                                $x_offset,
5703                                                $y_offset,
5704                                                $width,
5705                                                $height
5706                                              );
5707
5708        if ($i == 0) {
5709            # Write the parent MSODRAWIING record.
5710            my $dg_length    = 156 + 84*($num_images -1);
5711            my $spgr_length  = 132 + 84*($num_images -1);
5712
5713               $dg_length   += 120 *$num_charts;
5714               $spgr_length += 120 *$num_charts;
5715
5716               $dg_length   +=  96 *$num_filters;
5717               $spgr_length +=  96 *$num_filters;
5718
5719               $dg_length   += 128 *$num_comments;
5720               $spgr_length += 128 *$num_comments;
5721
5722
5723
5724            my $data        = $self->_store_mso_dg_container($dg_length);
5725               $data       .= $self->_store_mso_dg(@ids);
5726               $data       .= $self->_store_mso_spgr_container($spgr_length);
5727               $data       .= $self->_store_mso_sp_container(40);
5728               $data       .= $self->_store_mso_spgr();
5729               $data       .= $self->_store_mso_sp(0x0, $spid++, 0x0005);
5730               $data       .= $self->_store_mso_sp_container(76);
5731               $data       .= $self->_store_mso_sp(75, $spid++, 0x0A00);
5732               $data       .= $self->_store_mso_opt_image($image_id);
5733               $data       .= $self->_store_mso_client_anchor(2, @vertices);
5734               $data       .= $self->_store_mso_client_data();
5735
5736            $length         = length $data;
5737            my $header      = pack("vv", $record, $length);
5738            $self->_append($header, $data);
5739
5740        }
5741        else {
5742            # Write the child MSODRAWIING record.
5743            my $data        = $self->_store_mso_sp_container(76);
5744               $data       .= $self->_store_mso_sp(75, $spid++, 0x0A00);
5745               $data       .= $self->_store_mso_opt_image($image_id);
5746               $data       .= $self->_store_mso_client_anchor(2, @vertices);
5747               $data       .= $self->_store_mso_client_data();
5748
5749            $length         = length $data;
5750            my $header      = pack("vv", $record, $length);
5751            $self->_append($header, $data);
5752
5753
5754        }
5755
5756        $self->_store_obj_image($i+1);
5757    }
5758
5759    $self->{_object_ids}->[0] = $spid;
5760}
5761
5762
5763
5764###############################################################################
5765#
5766# _store_charts()
5767#
5768# Store the collections of records that make up charts.
5769#
5770sub _store_charts {
5771
5772    my $self            = shift;
5773
5774    my $record          = 0x00EC;           # Record identifier
5775    my $length          = 0x0000;           # Bytes to follow
5776
5777    my @ids             = @{$self->{_object_ids}};
5778    my $spid            = shift @ids;
5779
5780    my @charts          = @{$self->{_charts_array}};
5781    my $num_charts      = scalar @charts;
5782
5783    my $num_filters     = $self->{_filter_count};
5784    my $num_comments    = @{$self->{_comments_array}};
5785
5786    # Number of objects written so far.
5787    my $num_objects     = @{$self->{_images_array}};
5788
5789    # Skip this if there aren't any charts.
5790    return unless $num_charts;
5791
5792    for my $i (0 .. $num_charts-1 ) {
5793        my $row         =   $charts[$i]->[0];
5794        my $col         =   $charts[$i]->[1];
5795        my $chart        =   $charts[$i]->[2];
5796        my $x_offset    =   $charts[$i]->[3];
5797        my $y_offset    =   $charts[$i]->[4];
5798        my $scale_x     =   $charts[$i]->[5];
5799        my $scale_y     =   $charts[$i]->[6];
5800        my $width       =   526;
5801        my $height      =   319;
5802
5803        $width  *= $scale_x if $scale_x;
5804        $height *= $scale_y if $scale_y;
5805
5806        # Calculate the positions of chart object.
5807        my @vertices = $self->_position_object( $col,
5808                                                $row,
5809                                                $x_offset,
5810                                                $y_offset,
5811                                                $width,
5812                                                $height
5813                                              );
5814
5815
5816        if ($i == 0 and not $num_objects) {
5817            # Write the parent MSODRAWIING record.
5818            my $dg_length    = 192 + 120*($num_charts -1);
5819            my $spgr_length  = 168 + 120*($num_charts -1);
5820
5821               $dg_length   +=  96 *$num_filters;
5822               $spgr_length +=  96 *$num_filters;
5823
5824               $dg_length   += 128 *$num_comments;
5825               $spgr_length += 128 *$num_comments;
5826
5827
5828            my $data        = $self->_store_mso_dg_container($dg_length);
5829               $data       .= $self->_store_mso_dg(@ids);
5830               $data       .= $self->_store_mso_spgr_container($spgr_length);
5831               $data       .= $self->_store_mso_sp_container(40);
5832               $data       .= $self->_store_mso_spgr();
5833               $data       .= $self->_store_mso_sp(0x0, $spid++, 0x0005);
5834               $data       .= $self->_store_mso_sp_container(112);
5835               $data       .= $self->_store_mso_sp(201, $spid++, 0x0A00);
5836               $data       .= $self->_store_mso_opt_chart();
5837               $data       .= $self->_store_mso_client_anchor(0, @vertices);
5838               $data       .= $self->_store_mso_client_data();
5839
5840            $length         = length $data;
5841            my $header      = pack("vv", $record, $length);
5842            $self->_append($header, $data);
5843
5844        }
5845        else {
5846            # Write the child MSODRAWIING record.
5847            my $data        = $self->_store_mso_sp_container(112);
5848               $data       .= $self->_store_mso_sp(201, $spid++, 0x0A00);
5849               $data       .= $self->_store_mso_opt_chart();
5850               $data       .= $self->_store_mso_client_anchor(0, @vertices);
5851               $data       .= $self->_store_mso_client_data();
5852
5853            $length         = length $data;
5854            my $header      = pack("vv", $record, $length);
5855            $self->_append($header, $data);
5856
5857
5858        }
5859
5860        $self->_store_obj_chart($num_objects+$i+1);
5861        $self->_store_chart_binary($chart);
5862    }
5863
5864
5865    # Simulate the EXTERNSHEET link between the chart and data using a formula
5866    # such as '=Sheet1!A1'.
5867    # TODO. Won't work for external data refs. Also should use a more direct
5868    #       method.
5869    #
5870    my $name = $self->{_name};
5871    if ($self->{_encoding} && $] >= 5.008) {
5872        require Encode;
5873        $name = Encode::decode('UTF-16BE', $name);
5874    }
5875    $self->store_formula("='$name'!A1");
5876
5877    $self->{_object_ids}->[0] = $spid;
5878}
5879
5880
5881###############################################################################
5882#
5883# _store_chart_binary
5884#
5885# Add the binary data for a chart. This could either be from a Chart object
5886# or from an external binary file (for backwards compatibility).
5887#
5888sub _store_chart_binary {
5889
5890    my $self  = shift;
5891    my $chart = $_[0];
5892    my $tmp;
5893
5894
5895    if ( ref $chart ) {
5896        $chart->_close();
5897        my $tmp = $chart->get_data();
5898        $self->_append( $tmp );
5899    }
5900    else {
5901
5902        my $filehandle = FileHandle->new( $chart )
5903          or die "Couldn't open $chart in insert_chart(): $!.\n";
5904
5905        binmode( $filehandle );
5906
5907        while ( read( $filehandle, $tmp, 4096 ) ) {
5908            $self->_append( $tmp );
5909        }
5910    }
5911}
5912
5913
5914###############################################################################
5915#
5916# _store_filters()
5917#
5918# Store the collections of records that make up filters.
5919#
5920sub _store_filters {
5921
5922    my $self            = shift;
5923
5924    my $record          = 0x00EC;           # Record identifier
5925    my $length          = 0x0000;           # Bytes to follow
5926
5927    my @ids             = @{$self->{_object_ids}};
5928    my $spid            = shift @ids;
5929
5930    my $filter_area     = $self->{_filter_area};
5931    my $num_filters     = $self->{_filter_count};
5932
5933    my $num_comments    = @{$self->{_comments_array}};
5934
5935    # Number of objects written so far.
5936    my $num_objects     = @{$self->{_images_array}}
5937                        + @{$self->{_charts_array}};
5938
5939    # Skip this if there aren't any filters.
5940    return unless $num_filters;
5941
5942
5943    my ($row1, $row2, $col1, $col2) = @$filter_area;
5944
5945    for my $i (0 .. $num_filters-1 ) {
5946
5947        my @vertices = ( $col1 +$i,
5948                         0,
5949                         $row1,
5950                         0,
5951                         $col1 +$i +1,
5952                         0,
5953                         $row1 +1,
5954                         0);
5955
5956        if ($i == 0 and not $num_objects) {
5957            # Write the parent MSODRAWIING record.
5958            my $dg_length    = 168 + 96*($num_filters -1);
5959            my $spgr_length  = 144 + 96*($num_filters -1);
5960
5961               $dg_length   += 128 *$num_comments;
5962               $spgr_length += 128 *$num_comments;
5963
5964
5965            my $data        = $self->_store_mso_dg_container($dg_length);
5966               $data       .= $self->_store_mso_dg(@ids);
5967               $data       .= $self->_store_mso_spgr_container($spgr_length);
5968               $data       .= $self->_store_mso_sp_container(40);
5969               $data       .= $self->_store_mso_spgr();
5970               $data       .= $self->_store_mso_sp(0x0, $spid++, 0x0005);
5971               $data       .= $self->_store_mso_sp_container(88);
5972               $data       .= $self->_store_mso_sp(201, $spid++, 0x0A00);
5973               $data       .= $self->_store_mso_opt_filter();
5974               $data       .= $self->_store_mso_client_anchor(1, @vertices);
5975               $data       .= $self->_store_mso_client_data();
5976
5977            $length         = length $data;
5978            my $header      = pack("vv", $record, $length);
5979            $self->_append($header, $data);
5980
5981        }
5982        else {
5983            # Write the child MSODRAWIING record.
5984            my $data        = $self->_store_mso_sp_container(88);
5985               $data       .= $self->_store_mso_sp(201, $spid++, 0x0A00);
5986               $data       .= $self->_store_mso_opt_filter();
5987               $data       .= $self->_store_mso_client_anchor(1, @vertices);
5988               $data       .= $self->_store_mso_client_data();
5989
5990            $length         = length $data;
5991            my $header      = pack("vv", $record, $length);
5992            $self->_append($header, $data);
5993
5994
5995        }
5996
5997        $self->_store_obj_filter($num_objects+$i+1, $col1 +$i);
5998    }
5999
6000
6001    # Simulate the EXTERNSHEET link between the filter and data using a formula
6002    # such as '=Sheet1!A1'.
6003    # TODO. Won't work for external data refs. Also should use a more direct
6004    #       method.
6005    #
6006    my $formula = "='$self->{_name}'!A1";
6007    $self->store_formula($formula);
6008
6009    $self->{_object_ids}->[0] = $spid;
6010}
6011
6012
6013###############################################################################
6014#
6015# _store_comments()
6016#
6017# Store the collections of records that make up cell comments.
6018#
6019# NOTE: We write the comment objects last since that makes it a little easier
6020# to write the NOTE records directly after the MSODRAWIING records.
6021#
6022sub _store_comments {
6023
6024    my $self            = shift;
6025
6026    my $record          = 0x00EC;           # Record identifier
6027    my $length          = 0x0000;           # Bytes to follow
6028
6029    my @ids             = @{$self->{_object_ids}};
6030    my $spid            = shift @ids;
6031
6032    my @comments        = @{$self->{_comments_array}};
6033    my $num_comments    = scalar @comments;
6034
6035    # Number of objects written so far.
6036    my $num_objects     = @{$self->{_images_array}}
6037                        +   $self->{_filter_count}
6038                        + @{$self->{_charts_array}};
6039
6040    # Skip this if there aren't any comments.
6041    return unless $num_comments;
6042
6043    for my $i (0 .. $num_comments-1) {
6044
6045        my $row         =   $comments[$i]->[0];
6046        my $col         =   $comments[$i]->[1];
6047        my $str         =   $comments[$i]->[2];
6048        my $encoding    =   $comments[$i]->[3];
6049        my $visible     =   $comments[$i]->[6];
6050        my $color       =   $comments[$i]->[7];
6051        my @vertices    = @{$comments[$i]->[8]};
6052        my $str_len     = length $str;
6053           $str_len    /= 2 if $encoding; # Num of chars not bytes.
6054        my $formats     = [[0, 9], [$str_len, 0]];
6055
6056
6057        if ($i == 0 and not $num_objects) {
6058            # Write the parent MSODRAWIING record.
6059            my $dg_length   = 200 + 128*($num_comments -1);
6060            my $spgr_length = 176 + 128*($num_comments -1);
6061
6062            my $data        = $self->_store_mso_dg_container($dg_length);
6063               $data       .= $self->_store_mso_dg(@ids);
6064               $data       .= $self->_store_mso_spgr_container($spgr_length);
6065               $data       .= $self->_store_mso_sp_container(40);
6066               $data       .= $self->_store_mso_spgr();
6067               $data       .= $self->_store_mso_sp(0x0, $spid++, 0x0005);
6068               $data       .= $self->_store_mso_sp_container(120);
6069               $data       .= $self->_store_mso_sp(202, $spid++, 0x0A00);
6070               $data       .= $self->_store_mso_opt_comment(0x80, $visible, $color);
6071               $data       .= $self->_store_mso_client_anchor(3, @vertices);
6072               $data       .= $self->_store_mso_client_data();
6073
6074            $length         = length $data;
6075            my $header      = pack("vv", $record, $length);
6076            $self->_append($header, $data);
6077
6078        }
6079        else {
6080            # Write the child MSODRAWIING record.
6081            my $data        = $self->_store_mso_sp_container(120);
6082               $data       .= $self->_store_mso_sp(202, $spid++, 0x0A00);
6083               $data       .= $self->_store_mso_opt_comment(0x80, $visible, $color);
6084               $data       .= $self->_store_mso_client_anchor(3, @vertices);
6085               $data       .= $self->_store_mso_client_data();
6086
6087            $length         = length $data;
6088            my $header      = pack("vv", $record, $length);
6089            $self->_append($header, $data);
6090
6091
6092        }
6093
6094        $self->_store_obj_comment($num_objects+$i+1);
6095        $self->_store_mso_drawing_text_box();
6096        $self->_store_txo($str_len);
6097        $self->_store_txo_continue_1($str, $encoding);
6098        $self->_store_txo_continue_2($formats);
6099    }
6100
6101
6102    # Write the NOTE records after MSODRAWIING records.
6103    for my $i (0 .. $num_comments-1) {
6104
6105        my $row         = $comments[$i]->[0];
6106        my $col         = $comments[$i]->[1];
6107        my $author      = $comments[$i]->[4];
6108        my $author_enc  = $comments[$i]->[5];
6109        my $visible     = $comments[$i]->[6];
6110
6111        $self->_store_note($row, $col, $num_objects+$i+1,
6112                           $author, $author_enc, $visible);
6113    }
6114}
6115
6116
6117###############################################################################
6118#
6119# _store_mso_dg_container()
6120#
6121# Write the Escher DgContainer record that is part of MSODRAWING.
6122#
6123sub _store_mso_dg_container {
6124
6125    my $self        = shift;
6126
6127    my $type        = 0xF002;
6128    my $version     = 15;
6129    my $instance    = 0;
6130    my $data        = '';
6131    my $length      = $_[0];
6132
6133
6134    return $self->_add_mso_generic($type, $version, $instance, $data, $length);
6135}
6136
6137
6138###############################################################################
6139#
6140# _store_mso_dg()
6141#
6142# Write the Escher Dg record that is part of MSODRAWING.
6143#
6144sub _store_mso_dg {
6145
6146    my $self        = shift;
6147
6148    my $type        = 0xF008;
6149    my $version     = 0;
6150    my $instance    = $_[0];
6151    my $data        = '';
6152    my $length      = 8;
6153
6154    my $num_shapes  = $_[1];
6155    my $max_spid    = $_[2];
6156
6157    $data           = pack "VV", $num_shapes, $max_spid;
6158
6159    return $self->_add_mso_generic($type, $version, $instance, $data, $length);
6160}
6161
6162
6163###############################################################################
6164#
6165# _store_mso_spgr_container()
6166#
6167# Write the Escher SpgrContainer record that is part of MSODRAWING.
6168#
6169sub _store_mso_spgr_container {
6170
6171    my $self        = shift;
6172
6173    my $type        = 0xF003;
6174    my $version     = 15;
6175    my $instance    = 0;
6176    my $data        = '';
6177    my $length      = $_[0];
6178
6179
6180    return $self->_add_mso_generic($type, $version, $instance, $data, $length);
6181}
6182
6183
6184###############################################################################
6185#
6186# _store_mso_sp_container()
6187#
6188# Write the Escher SpContainer record that is part of MSODRAWING.
6189#
6190sub _store_mso_sp_container {
6191
6192    my $self        = shift;
6193
6194    my $type        = 0xF004;
6195    my $version     = 15;
6196    my $instance    = 0;
6197    my $data        = '';
6198    my $length      = $_[0];
6199
6200
6201    return $self->_add_mso_generic($type, $version, $instance, $data, $length);
6202}
6203
6204
6205###############################################################################
6206#
6207# _store_mso_spgr()
6208#
6209# Write the Escher Spgr record that is part of MSODRAWING.
6210#
6211sub _store_mso_spgr {
6212
6213    my $self        = shift;
6214
6215    my $type        = 0xF009;
6216    my $version     = 1;
6217    my $instance    = 0;
6218    my $data        = pack "VVVV", 0, 0, 0, 0;
6219    my $length      = 16;
6220
6221
6222    return $self->_add_mso_generic($type, $version, $instance, $data, $length);
6223}
6224
6225
6226###############################################################################
6227#
6228# _store_mso_sp()
6229#
6230# Write the Escher Sp record that is part of MSODRAWING.
6231#
6232sub _store_mso_sp {
6233
6234    my $self        = shift;
6235
6236    my $type        = 0xF00A;
6237    my $version     = 2;
6238    my $instance    = $_[0];
6239    my $data        = '';
6240    my $length      = 8;
6241
6242    my $spid        = $_[1];
6243    my $options     = $_[2];
6244
6245    $data           = pack "VV", $spid, $options;
6246
6247    return $self->_add_mso_generic($type, $version, $instance, $data, $length);
6248}
6249
6250
6251###############################################################################
6252#
6253# _store_mso_opt_comment()
6254#
6255# Write the Escher Opt record that is part of MSODRAWING.
6256#
6257sub _store_mso_opt_comment {
6258
6259    my $self        = shift;
6260
6261    my $type        = 0xF00B;
6262    my $version     = 3;
6263    my $instance    = 9;
6264    my $data        = '';
6265    my $length      = 54;
6266
6267    my $spid        = $_[0];
6268    my $visible     = $_[1];
6269    my $colour      = $_[2] || 0x50;
6270
6271
6272    # Use the visible flag if set by the user or else use the worksheet value.
6273    # Note that the value used is the opposite of _store_note().
6274    #
6275    if (defined $visible) {
6276        $visible = $visible                   ? 0x0000 : 0x0002;
6277    }
6278    else {
6279        $visible = $self->{_comments_visible} ? 0x0000 : 0x0002;
6280    }
6281
6282
6283    $data    = pack "V",  $spid;
6284    $data   .= pack "H*", '0000BF00080008005801000000008101' ;
6285    $data   .= pack "C",  $colour;
6286    $data   .= pack "H*", '000008830150000008BF011000110001' .
6287                          '02000000003F0203000300BF03';
6288    $data   .= pack "v",  $visible;
6289    $data   .= pack "H*", '0A00';
6290
6291
6292    return $self->_add_mso_generic($type, $version, $instance, $data, $length);
6293}
6294
6295
6296###############################################################################
6297#
6298# _store_mso_opt_image()
6299#
6300# Write the Escher Opt record that is part of MSODRAWING.
6301#
6302sub _store_mso_opt_image {
6303
6304    my $self        = shift;
6305
6306    my $type        = 0xF00B;
6307    my $version     = 3;
6308    my $instance    = 3;
6309    my $data        = '';
6310    my $length      = undef;
6311    my $spid        = $_[0];
6312
6313    $data    = pack 'v', 0x4104;        # Blip -> pib
6314    $data   .= pack 'V', $spid;
6315    $data   .= pack 'v', 0x01BF;        # Fill Style -> fNoFillHitTest
6316    $data   .= pack 'V', 0x00010000;
6317    $data   .= pack 'v', 0x03BF;        # Group Shape -> fPrint
6318    $data   .= pack 'V', 0x00080000;
6319
6320
6321    return $self->_add_mso_generic($type, $version, $instance, $data, $length);
6322}
6323
6324
6325###############################################################################
6326#
6327# _store_mso_opt_chart()
6328#
6329# Write the Escher Opt record that is part of MSODRAWING.
6330#
6331sub _store_mso_opt_chart {
6332
6333    my $self        = shift;
6334
6335    my $type        = 0xF00B;
6336    my $version     = 3;
6337    my $instance    = 9;
6338    my $data        = '';
6339    my $length      = undef;
6340
6341    $data    = pack 'v', 0x007F;        # Protection -> fLockAgainstGrouping
6342    $data   .= pack 'V', 0x01040104;
6343
6344    $data   .= pack 'v', 0x00BF;        # Text -> fFitTextToShape
6345    $data   .= pack 'V', 0x00080008;
6346
6347    $data   .= pack 'v', 0x0181;        # Fill Style -> fillColor
6348    $data   .= pack 'V', 0x0800004E ;
6349
6350    $data   .= pack 'v', 0x0183;        # Fill Style -> fillBackColor
6351    $data   .= pack 'V', 0x0800004D;
6352
6353    $data   .= pack 'v', 0x01BF;        # Fill Style -> fNoFillHitTest
6354    $data   .= pack 'V', 0x00110010;
6355
6356    $data   .= pack 'v', 0x01C0;        # Line Style -> lineColor
6357    $data   .= pack 'V', 0x0800004D;
6358
6359    $data   .= pack 'v', 0x01FF;        # Line Style -> fNoLineDrawDash
6360    $data   .= pack 'V', 0x00080008;
6361
6362    $data   .= pack 'v', 0x023F;        # Shadow Style -> fshadowObscured
6363    $data   .= pack 'V', 0x00020000;
6364
6365    $data   .= pack 'v', 0x03BF;        # Group Shape -> fPrint
6366    $data   .= pack 'V', 0x00080000;
6367
6368
6369    return $self->_add_mso_generic($type, $version, $instance, $data, $length);
6370}
6371
6372
6373###############################################################################
6374#
6375# _store_mso_opt_filter()
6376#
6377# Write the Escher Opt record that is part of MSODRAWING.
6378#
6379sub _store_mso_opt_filter {
6380
6381    my $self        = shift;
6382
6383    my $type        = 0xF00B;
6384    my $version     = 3;
6385    my $instance    = 5;
6386    my $data        = '';
6387    my $length      = undef;
6388
6389
6390
6391    $data    = pack 'v', 0x007F;        # Protection -> fLockAgainstGrouping
6392    $data   .= pack 'V', 0x01040104;
6393
6394    $data   .= pack 'v', 0x00BF;        # Text -> fFitTextToShape
6395    $data   .= pack 'V', 0x00080008;
6396
6397    $data   .= pack 'v', 0x01BF;        # Fill Style -> fNoFillHitTest
6398    $data   .= pack 'V', 0x00010000;
6399
6400    $data   .= pack 'v', 0x01FF;        # Line Style -> fNoLineDrawDash
6401    $data   .= pack 'V', 0x00080000;
6402
6403    $data   .= pack 'v', 0x03BF;        # Group Shape -> fPrint
6404    $data   .= pack 'V', 0x000A0000;
6405
6406
6407    return $self->_add_mso_generic($type, $version, $instance, $data, $length);
6408}
6409
6410
6411###############################################################################
6412#
6413# _store_mso_client_anchor()
6414#
6415# Write the Escher ClientAnchor record that is part of MSODRAWING.
6416#
6417sub _store_mso_client_anchor {
6418
6419    my $self        = shift;
6420
6421    my $type        = 0xF010;
6422    my $version     = 0;
6423    my $instance    = 0;
6424    my $data        = '';
6425    my $length      = 18;
6426
6427    my $flag        = shift;
6428
6429    my $col_start   = $_[0];    # Col containing upper left corner of object
6430    my $x1          = $_[1];    # Distance to left side of object
6431
6432    my $row_start   = $_[2];    # Row containing top left corner of object
6433    my $y1          = $_[3];    # Distance to top of object
6434
6435    my $col_end     = $_[4];    # Col containing lower right corner of object
6436    my $x2          = $_[5];    # Distance to right side of object
6437
6438    my $row_end     = $_[6];    # Row containing bottom right corner of object
6439    my $y2          = $_[7];    # Distance to bottom of object
6440
6441    $data   = pack "v9",    $flag,
6442                            $col_start, $x1,
6443                            $row_start, $y1,
6444                            $col_end,   $x2,
6445                            $row_end,   $y2;
6446
6447
6448
6449    return $self->_add_mso_generic($type, $version, $instance, $data, $length);
6450}
6451
6452
6453###############################################################################
6454#
6455# _store_mso_client_data()
6456#
6457# Write the Escher ClientData record that is part of MSODRAWING.
6458#
6459sub _store_mso_client_data {
6460
6461    my $self        = shift;
6462
6463    my $type        = 0xF011;
6464    my $version     = 0;
6465    my $instance    = 0;
6466    my $data        = '';
6467    my $length      = 0;
6468
6469
6470    return $self->_add_mso_generic($type, $version, $instance, $data, $length);
6471}
6472
6473
6474###############################################################################
6475#
6476# _store_obj_comment()
6477#
6478# Write the OBJ record that is part of cell comments.
6479#
6480sub _store_obj_comment {
6481
6482    my $self        = shift;
6483
6484    my $record      = 0x005D;   # Record identifier
6485    my $length      = 0x0034;   # Bytes to follow
6486
6487    my $obj_id      = $_[0];    # Object ID number.
6488    my $obj_type    = 0x0019;   # Object type (comment).
6489    my $data        = '';       # Record data.
6490
6491    my $sub_record  = 0x0000;   # Sub-record identifier.
6492    my $sub_length  = 0x0000;   # Length of sub-record.
6493    my $sub_data    = '';       # Data of sub-record.
6494    my $options     = 0x4011;
6495    my $reserved    = 0x0000;
6496
6497    # Add ftCmo (common object data) subobject
6498    $sub_record     = 0x0015;   # ftCmo
6499    $sub_length     = 0x0012;
6500    $sub_data       = pack "vvvVVV", $obj_type, $obj_id,   $options,
6501                                     $reserved, $reserved, $reserved;
6502    $data           = pack("vv",     $sub_record, $sub_length);
6503    $data          .= $sub_data;
6504
6505
6506    # Add ftNts (note structure) subobject
6507    $sub_record     = 0x000D;   # ftNts
6508    $sub_length     = 0x0016;
6509    $sub_data       = pack "VVVVVv", ($reserved) x 6;
6510    $data          .= pack("vv",     $sub_record, $sub_length);
6511    $data          .= $sub_data;
6512
6513
6514    # Add ftEnd (end of object) subobject
6515    $sub_record     = 0x0000;   # ftNts
6516    $sub_length     = 0x0000;
6517    $data          .= pack("vv",     $sub_record, $sub_length);
6518
6519
6520    # Pack the record.
6521    my $header  = pack("vv",        $record, $length);
6522
6523    $self->_append($header, $data);
6524
6525}
6526
6527
6528###############################################################################
6529#
6530# _store_obj_image()
6531#
6532# Write the OBJ record that is part of image records.
6533#
6534sub _store_obj_image {
6535
6536    my $self        = shift;
6537
6538    my $record      = 0x005D;   # Record identifier
6539    my $length      = 0x0026;   # Bytes to follow
6540
6541    my $obj_id      = $_[0];    # Object ID number.
6542    my $obj_type    = 0x0008;   # Object type (Picture).
6543    my $data        = '';       # Record data.
6544
6545    my $sub_record  = 0x0000;   # Sub-record identifier.
6546    my $sub_length  = 0x0000;   # Length of sub-record.
6547    my $sub_data    = '';       # Data of sub-record.
6548    my $options     = 0x6011;
6549    my $reserved    = 0x0000;
6550
6551    # Add ftCmo (common object data) subobject
6552    $sub_record     = 0x0015;   # ftCmo
6553    $sub_length     = 0x0012;
6554    $sub_data       = pack 'vvvVVV', $obj_type, $obj_id,   $options,
6555                                     $reserved, $reserved, $reserved;
6556    $data           = pack 'vv',     $sub_record, $sub_length;
6557    $data          .= $sub_data;
6558
6559
6560    # Add ftCf (Clipboard format) subobject
6561    $sub_record     = 0x0007;   # ftCf
6562    $sub_length     = 0x0002;
6563    $sub_data       = pack 'v',      0xFFFF;
6564    $data          .= pack 'vv',     $sub_record, $sub_length;
6565    $data          .= $sub_data;
6566
6567    # Add ftPioGrbit (Picture option flags) subobject
6568    $sub_record     = 0x0008;   # ftPioGrbit
6569    $sub_length     = 0x0002;
6570    $sub_data       = pack 'v',      0x0001;
6571    $data          .= pack 'vv',     $sub_record, $sub_length;
6572    $data          .= $sub_data;
6573
6574
6575    # Add ftEnd (end of object) subobject
6576    $sub_record     = 0x0000;   # ftNts
6577    $sub_length     = 0x0000;
6578    $data          .= pack 'vv',     $sub_record, $sub_length;
6579
6580
6581    # Pack the record.
6582    my $header  = pack('vv',        $record, $length);
6583
6584    $self->_append($header, $data);
6585
6586}
6587
6588
6589###############################################################################
6590#
6591# _store_obj_chart()
6592#
6593# Write the OBJ record that is part of chart records.
6594#
6595sub _store_obj_chart {
6596
6597    my $self        = shift;
6598
6599    my $record      = 0x005D;   # Record identifier
6600    my $length      = 0x001A;   # Bytes to follow
6601
6602    my $obj_id      = $_[0];    # Object ID number.
6603    my $obj_type    = 0x0005;   # Object type (chart).
6604    my $data        = '';       # Record data.
6605
6606    my $sub_record  = 0x0000;   # Sub-record identifier.
6607    my $sub_length  = 0x0000;   # Length of sub-record.
6608    my $sub_data    = '';       # Data of sub-record.
6609    my $options     = 0x6011;
6610    my $reserved    = 0x0000;
6611
6612    # Add ftCmo (common object data) subobject
6613    $sub_record     = 0x0015;   # ftCmo
6614    $sub_length     = 0x0012;
6615    $sub_data       = pack 'vvvVVV', $obj_type, $obj_id,   $options,
6616                                     $reserved, $reserved, $reserved;
6617    $data           = pack 'vv',     $sub_record, $sub_length;
6618    $data          .= $sub_data;
6619
6620    # Add ftEnd (end of object) subobject
6621    $sub_record     = 0x0000;   # ftNts
6622    $sub_length     = 0x0000;
6623    $data          .= pack 'vv',     $sub_record, $sub_length;
6624
6625
6626    # Pack the record.
6627    my $header  = pack('vv',        $record, $length);
6628
6629    $self->_append($header, $data);
6630
6631}
6632
6633
6634
6635
6636###############################################################################
6637#
6638# _store_obj_filter()
6639#
6640# Write the OBJ record that is part of filter records.
6641#
6642sub _store_obj_filter {
6643
6644    my $self        = shift;
6645
6646    my $record      = 0x005D;   # Record identifier
6647    my $length      = 0x0046;   # Bytes to follow
6648
6649    my $obj_id      = $_[0];    # Object ID number.
6650    my $obj_type    = 0x0014;   # Object type (combo box).
6651    my $data        = '';       # Record data.
6652
6653    my $sub_record  = 0x0000;   # Sub-record identifier.
6654    my $sub_length  = 0x0000;   # Length of sub-record.
6655    my $sub_data    = '';       # Data of sub-record.
6656    my $options     = 0x2101;
6657    my $reserved    = 0x0000;
6658
6659    # Add ftCmo (common object data) subobject
6660    $sub_record     = 0x0015;   # ftCmo
6661    $sub_length     = 0x0012;
6662    $sub_data       = pack 'vvvVVV', $obj_type, $obj_id,   $options,
6663                                     $reserved, $reserved, $reserved;
6664    $data           = pack 'vv',     $sub_record, $sub_length;
6665    $data          .= $sub_data;
6666
6667    # Add ftSbs Scroll bar subobject
6668    $sub_record     = 0x000C;   # ftSbs
6669    $sub_length     = 0x0014;
6670    $sub_data       = pack 'H*', '0000000000000000640001000A00000010000100';
6671    $data          .= pack 'vv',     $sub_record, $sub_length;
6672    $data          .= $sub_data;
6673
6674
6675    # Add ftLbsData (List box data) subobject
6676    $sub_record     = 0x0013;   # ftLbsData
6677    $sub_length     = 0x1FEE;   # Special case (undocumented).
6678
6679
6680    # If the filter is active we set one of the undocumented flags.
6681    my $col         = $_[1];
6682
6683    if ($self->{_filter_cols}->{$col}) {
6684        $sub_data       = pack 'H*', '000000000100010300000A0008005700';
6685    }
6686    else {
6687        $sub_data       = pack 'H*', '00000000010001030000020008005700';
6688    }
6689
6690    $data          .= pack 'vv',     $sub_record, $sub_length;
6691    $data          .= $sub_data;
6692
6693
6694    # Add ftEnd (end of object) subobject
6695    $sub_record     = 0x0000;   # ftNts
6696    $sub_length     = 0x0000;
6697    $data          .= pack 'vv', $sub_record, $sub_length;
6698
6699    # Pack the record.
6700    my $header  = pack('vv',        $record, $length);
6701
6702    $self->_append($header, $data);
6703}
6704
6705
6706###############################################################################
6707#
6708# _store_mso_drawing_text_box()
6709#
6710# Write the MSODRAWING ClientTextbox record that is part of comments.
6711#
6712sub _store_mso_drawing_text_box {
6713
6714    my $self        = shift;
6715
6716    my $record      = 0x00EC;           # Record identifier
6717    my $length      = 0x0008;           # Bytes to follow
6718
6719
6720    my $data        = $self->_store_mso_client_text_box();
6721    my $header      = pack("vv", $record, $length);
6722
6723    $self->_append($header, $data);
6724}
6725
6726
6727###############################################################################
6728#
6729# _store_mso_client_text_box()
6730#
6731# Write the Escher ClientTextbox record that is part of MSODRAWING.
6732#
6733sub _store_mso_client_text_box {
6734
6735    my $self        = shift;
6736
6737    my $type        = 0xF00D;
6738    my $version     = 0;
6739    my $instance    = 0;
6740    my $data        = '';
6741    my $length      = 0;
6742
6743
6744    return $self->_add_mso_generic($type, $version, $instance, $data, $length);
6745}
6746
6747
6748###############################################################################
6749#
6750# _store_txo()
6751#
6752# Write the worksheet TXO record that is part of cell comments.
6753#
6754sub _store_txo {
6755
6756    my $self        = shift;
6757
6758    my $record      = 0x01B6;               # Record identifier
6759    my $length      = 0x0012;               # Bytes to follow
6760
6761    my $string_len  = $_[0];                # Length of the note text.
6762    my $format_len  = $_[1] || 16;          # Length of the format runs.
6763    my $rotation    = $_[2] || 0;           # Options
6764    my $grbit       = 0x0212;               # Options
6765    my $reserved    = 0x0000;               # Options
6766
6767    # Pack the record.
6768    my $header  = pack("vv",        $record, $length);
6769    my $data    = pack("vvVvvvV",   $grbit, $rotation, $reserved, $reserved,
6770                                    $string_len, $format_len, $reserved);
6771
6772    $self->_append($header, $data);
6773
6774}
6775
6776
6777###############################################################################
6778#
6779# _store_txo_continue_1()
6780#
6781# Write the first CONTINUE record to follow the TXO record. It contains the
6782# text data.
6783#
6784sub _store_txo_continue_1 {
6785
6786    my $self        = shift;
6787
6788    my $record      = 0x003C;               # Record identifier
6789    my $string      = $_[0];                # Comment string.
6790    my $encoding    = $_[1] || 0;           # Encoding of the string.
6791
6792
6793    # Split long comment strings into smaller continue blocks if necessary.
6794    # We can't let BIFFwriter::_add_continue() handled this since an extra
6795    # encoding byte has to be added similar to the SST block.
6796    #
6797    # We make the limit size smaller than the _add_continue() size and even
6798    # so that UTF16 chars occur in the same block.
6799    #
6800    my $limit = 8218;
6801    while (length($string) > $limit) {
6802        my $tmp_str = substr($string, 0, $limit, "");
6803
6804        my $data    = pack("C", $encoding) . $tmp_str;
6805        my $length  = length $data;
6806        my $header  = pack("vv", $record, $length);
6807
6808        $self->_append($header, $data);
6809    }
6810
6811    # Pack the record.
6812    my $data    = pack("C", $encoding) . $string;
6813    my $length  = length $data;
6814    my $header  = pack("vv", $record, $length);
6815
6816    $self->_append($header, $data);
6817
6818}
6819
6820
6821###############################################################################
6822#
6823# _store_txo_continue_2()
6824#
6825# Write the second CONTINUE record to follow the TXO record. It contains the
6826# formatting information for the string.
6827#
6828sub _store_txo_continue_2 {
6829
6830    my $self        = shift;
6831
6832    my $record      = 0x003C;               # Record identifier
6833    my $length      = 0x0000;               # Bytes to follow
6834    my $formats     = $_[0];                # Formatting information
6835
6836
6837    # Pack the record.
6838    my $data = '';
6839
6840    for my $a_ref (@$formats) {
6841        $data .= pack "vvV", $a_ref->[0], $a_ref->[1], 0x0;
6842    }
6843
6844    $length     = length $data;
6845    my $header  = pack("vv", $record, $length);
6846
6847
6848    $self->_append($header, $data);
6849
6850}
6851
6852
6853###############################################################################
6854#
6855# _store_note()
6856#
6857# Write the worksheet NOTE record that is part of cell comments.
6858#
6859sub _store_note {
6860
6861    my $self        = shift;
6862
6863    my $record      = 0x001C;               # Record identifier
6864    my $length      = 0x000C;               # Bytes to follow
6865
6866    my $row         = $_[0];
6867    my $col         = $_[1];
6868    my $obj_id      = $_[2];
6869    my $author      = $_[3] || $self->{_comments_author};
6870    my $author_enc  = $_[4] || $self->{_comments_author_enc};
6871    my $visible     = $_[5];
6872
6873
6874    # Use the visible flag if set by the user or else use the worksheet value.
6875    # The flag is also set in _store_mso_opt_comment() but with the opposite
6876    # value.
6877    if (defined $visible) {
6878        $visible = $visible                   ? 0x0002 : 0x0000;
6879    }
6880    else {
6881        $visible = $self->{_comments_visible} ? 0x0002 : 0x0000;
6882    }
6883
6884
6885    # Get the number of chars in the author string (not bytes).
6886    my $num_chars  = length $author;
6887       $num_chars /= 2 if $author_enc;
6888
6889
6890    # Null terminate the author string.
6891    $author .= "\0";
6892
6893
6894    # Pack the record.
6895    my $data    = pack("vvvvvC", $row, $col, $visible, $obj_id,
6896                                 $num_chars, $author_enc);
6897
6898    $length     = length($data) + length($author);
6899    my $header  = pack("vv", $record, $length);
6900
6901    $self->_append($header, $data, $author);
6902}
6903
6904
6905###############################################################################
6906#
6907# _comment_params()
6908#
6909# This method handles the additional optional parameters to write_comment() as
6910# well as calculating the comment object position and vertices.
6911#
6912sub _comment_params {
6913
6914    my $self            = shift;
6915
6916    my $row             = shift;
6917    my $col             = shift;
6918    my $string          = shift;
6919
6920    my $default_width   = 128;
6921    my $default_height  = 74;
6922
6923    my %params  = (
6924                    author          => '',
6925                    author_encoding => 0,
6926                    encoding        => 0,
6927                    color           => undef,
6928                    start_cell      => undef,
6929                    start_col       => undef,
6930                    start_row       => undef,
6931                    visible         => undef,
6932                    width           => $default_width,
6933                    height          => $default_height,
6934                    x_offset        => undef,
6935                    x_scale         => 1,
6936                    y_offset        => undef,
6937                    y_scale         => 1,
6938                  );
6939
6940
6941    # Overwrite the defaults with any user supplied values. Incorrect or
6942    # misspelled parameters are silently ignored.
6943    %params     = (%params, @_);
6944
6945
6946    # Ensure that a width and height have been set.
6947    $params{width}  = $default_width  if not $params{width};
6948    $params{height} = $default_height if not $params{height};
6949
6950
6951    # Check that utf16 strings have an even number of bytes.
6952    if ($params{encoding}) {
6953        croak "Uneven number of bytes in comment string"
6954               if length($string) % 2;
6955
6956        # Change from UTF-16BE to UTF-16LE
6957        $string = pack 'v*', unpack 'n*', $string;
6958    }
6959
6960    if ($params{author_encoding}) {
6961        croak "Uneven number of bytes in author string"
6962                if length($params{author}) % 2;
6963
6964        # Change from UTF-16BE to UTF-16LE
6965        $params{author} = pack 'v*', unpack 'n*', $params{author};
6966    }
6967
6968
6969    # Handle utf8 strings in perl 5.8.
6970    if ($] >= 5.008) {
6971        require Encode;
6972
6973        if (Encode::is_utf8($string)) {
6974            $string = Encode::encode("UTF-16LE", $string);
6975            $params{encoding} = 1;
6976        }
6977
6978        if (Encode::is_utf8($params{author})) {
6979            $params{author} = Encode::encode("UTF-16LE", $params{author});
6980            $params{author_encoding} = 1;
6981        }
6982    }
6983
6984
6985    # Limit the string to the max number of chars (not bytes).
6986    my $max_len  = 32767;
6987       $max_len *= 2 if $params{encoding};
6988
6989    if (length($string) > $max_len) {
6990        $string       = substr($string, 0, $max_len);
6991    }
6992
6993
6994    # Set the comment background colour.
6995    my $color       = $params{color};
6996       $color       = &Spreadsheet::WriteExcel::Format::_get_color($color);
6997       $color       = 0x50 if $color == 0x7FFF; # Default color.
6998    $params{color}  = $color;
6999
7000
7001    # Convert a cell reference to a row and column.
7002    if (defined $params{start_cell}) {
7003        my ($row, $col)    = $self->_substitute_cellref($params{start_cell});
7004        $params{start_row} = $row;
7005        $params{start_col} = $col;
7006    }
7007
7008
7009    # Set the default start cell and offsets for the comment. These are
7010    # generally fixed in relation to the parent cell. However there are
7011    # some edge cases for cells at the, er, edges.
7012    #
7013    if (not defined $params{start_row}) {
7014
7015        if    ($row == 0    ) {$params{start_row} = 0      }
7016        elsif ($row == 65533) {$params{start_row} = 65529  }
7017        elsif ($row == 65534) {$params{start_row} = 65530  }
7018        elsif ($row == 65535) {$params{start_row} = 65531  }
7019        else                  {$params{start_row} = $row -1}
7020    }
7021
7022    if (not defined $params{y_offset}) {
7023
7024        if    ($row == 0    ) {$params{y_offset}  = 2      }
7025        elsif ($row == 65533) {$params{y_offset}  = 4      }
7026        elsif ($row == 65534) {$params{y_offset}  = 4      }
7027        elsif ($row == 65535) {$params{y_offset}  = 2      }
7028        else                  {$params{y_offset}  = 7      }
7029    }
7030
7031    if (not defined $params{start_col}) {
7032
7033        if    ($col == 253  ) {$params{start_col} = 250    }
7034        elsif ($col == 254  ) {$params{start_col} = 251    }
7035        elsif ($col == 255  ) {$params{start_col} = 252    }
7036        else                  {$params{start_col} = $col +1}
7037    }
7038
7039    if (not defined $params{x_offset}) {
7040
7041        if    ($col == 253  ) {$params{x_offset}  = 49     }
7042        elsif ($col == 254  ) {$params{x_offset}  = 49     }
7043        elsif ($col == 255  ) {$params{x_offset}  = 49     }
7044        else                  {$params{x_offset}  = 15     }
7045    }
7046
7047
7048    # Scale the size of the comment box if required.
7049    if ($params{x_scale}) {
7050        $params{width}  = $params{width}  * $params{x_scale};
7051    }
7052
7053    if ($params{y_scale}) {
7054        $params{height} = $params{height} * $params{y_scale};
7055    }
7056
7057
7058    # Calculate the positions of comment object.
7059    my @vertices = $self->_position_object( $params{start_col},
7060                                            $params{start_row},
7061                                            $params{x_offset},
7062                                            $params{y_offset},
7063                                            $params{width},
7064                                            $params{height}
7065                                          );
7066
7067    return(
7068           $row,
7069           $col,
7070           $string,
7071           $params{encoding},
7072           $params{author},
7073           $params{author_encoding},
7074           $params{visible},
7075           $params{color},
7076           [@vertices]
7077          );
7078}
7079
7080
7081
7082#
7083# DATA VALIDATION
7084#
7085
7086###############################################################################
7087#
7088# data_validation($row, $col, {...})
7089#
7090# This method handles the interface to Excel data validation.
7091# Somewhat ironically the this requires a lot of validation code since the
7092# interface is flexible and covers a several types of data validation.
7093#
7094# We allow data validation to be called on one cell or a range of cells. The
7095# hashref contains the validation parameters and must be the last param:
7096#    data_validation($row, $col, {...})
7097#    data_validation($first_row, $first_col, $last_row, $last_col, {...})
7098#
7099# Returns  0 : normal termination
7100#         -1 : insufficient number of arguments
7101#         -2 : row or column out of range
7102#         -3 : incorrect parameter.
7103#
7104sub data_validation {
7105
7106    my $self = shift;
7107
7108    # Check for a cell reference in A1 notation and substitute row and column
7109    if ($_[0] =~ /^\D/) {
7110        @_ = $self->_substitute_cellref(@_);
7111    }
7112
7113    # Check for a valid number of args.
7114    if (@_ != 5 && @_ != 3) { return -1 }
7115
7116    # The final hashref contains the validation parameters.
7117    my $param = pop;
7118
7119    # Make the last row/col the same as the first if not defined.
7120    my ($row1, $col1, $row2, $col2) = @_;
7121    if (!defined $row2) {
7122        $row2 = $row1;
7123        $col2 = $col1;
7124    }
7125
7126    # Check that row and col are valid without storing the values.
7127    return -2 if $self->_check_dimensions($row1, $col1, 1, 1);
7128    return -2 if $self->_check_dimensions($row2, $col2, 1, 1);
7129
7130
7131    # Check that the last parameter is a hash list.
7132    if (ref $param ne 'HASH') {
7133        carp "Last parameter '$param' in data_validation() must be a hash ref";
7134        return -3;
7135    }
7136
7137    # List of valid input parameters.
7138    my %valid_parameter = (
7139                              validate          => 1,
7140                              criteria          => 1,
7141                              value             => 1,
7142                              source            => 1,
7143                              minimum           => 1,
7144                              maximum           => 1,
7145                              ignore_blank      => 1,
7146                              dropdown          => 1,
7147                              show_input        => 1,
7148                              input_title       => 1,
7149                              input_message     => 1,
7150                              show_error        => 1,
7151                              error_title       => 1,
7152                              error_message     => 1,
7153                              error_type        => 1,
7154                              other_cells       => 1,
7155                          );
7156
7157    # Check for valid input parameters.
7158    for my $param_key (keys %$param) {
7159        if (not exists $valid_parameter{$param_key}) {
7160            carp "Unknown parameter '$param_key' in data_validation()";
7161            return -3;
7162        }
7163    }
7164
7165    # Map alternative parameter names 'source' or 'minimum' to 'value'.
7166    $param->{value} = $param->{source}  if defined $param->{source};
7167    $param->{value} = $param->{minimum} if defined $param->{minimum};
7168
7169    # 'validate' is a required parameter.
7170    if (not exists $param->{validate}) {
7171        carp "Parameter 'validate' is required in data_validation()";
7172        return -3;
7173    }
7174
7175
7176    # List of  valid validation types.
7177    my %valid_type = (
7178                              'any'             => 0,
7179                              'any value'       => 0,
7180                              'whole number'    => 1,
7181                              'whole'           => 1,
7182                              'integer'         => 1,
7183                              'decimal'         => 2,
7184                              'list'            => 3,
7185                              'date'            => 4,
7186                              'time'            => 5,
7187                              'text length'     => 6,
7188                              'length'          => 6,
7189                              'custom'          => 7,
7190                      );
7191
7192
7193    # Check for valid validation types.
7194    if (not exists $valid_type{lc($param->{validate})}) {
7195        carp "Unknown validation type '$param->{validate}' for parameter " .
7196             "'validate' in data_validation()";
7197        return -3;
7198    }
7199    else {
7200        $param->{validate} = $valid_type{lc($param->{validate})};
7201    }
7202
7203
7204    # No action is required for validation type 'any'.
7205    # TODO: we should perhaps store 'any' for message only validations.
7206    return 0 if $param->{validate} == 0;
7207
7208
7209    # The list and custom validations don't have a criteria so we use a default
7210    # of 'between'.
7211    if ($param->{validate} == 3 || $param->{validate} == 7) {
7212        $param->{criteria}  = 'between';
7213        $param->{maximum}   = undef;
7214    }
7215
7216    # 'criteria' is a required parameter.
7217    if (not exists $param->{criteria}) {
7218        carp "Parameter 'criteria' is required in data_validation()";
7219        return -3;
7220    }
7221
7222
7223    # List of valid criteria types.
7224    my %criteria_type = (
7225                              'between'                     => 0,
7226                              'not between'                 => 1,
7227                              'equal to'                    => 2,
7228                              '='                           => 2,
7229                              '=='                          => 2,
7230                              'not equal to'                => 3,
7231                              '!='                          => 3,
7232                              '<>'                          => 3,
7233                              'greater than'                => 4,
7234                              '>'                           => 4,
7235                              'less than'                   => 5,
7236                              '<'                           => 5,
7237                              'greater than or equal to'    => 6,
7238                              '>='                          => 6,
7239                              'less than or equal to'       => 7,
7240                              '<='                          => 7,
7241                      );
7242
7243    # Check for valid criteria types.
7244    if (not exists $criteria_type{lc($param->{criteria})}) {
7245        carp "Unknown criteria type '$param->{criteria}' for parameter " .
7246             "'criteria' in data_validation()";
7247        return -3;
7248    }
7249    else {
7250        $param->{criteria} = $criteria_type{lc($param->{criteria})};
7251    }
7252
7253
7254    # 'Between' and 'Not between' criteria require 2 values.
7255    if ($param->{criteria} == 0 || $param->{criteria} == 1) {
7256        if (not exists $param->{maximum}) {
7257            carp "Parameter 'maximum' is required in data_validation() " .
7258                 "when using 'between' or 'not between' criteria";
7259            return -3;
7260        }
7261    }
7262    else {
7263        $param->{maximum} = undef;
7264    }
7265
7266
7267
7268    # List of valid error dialog types.
7269    my %error_type = (
7270                              'stop'        => 0,
7271                              'warning'     => 1,
7272                              'information' => 2,
7273                     );
7274
7275    # Check for valid error dialog types.
7276    if (not exists $param->{error_type}) {
7277        $param->{error_type} = 0;
7278    }
7279    elsif (not exists $error_type{lc($param->{error_type})}) {
7280        carp "Unknown criteria type '$param->{error_type}' for parameter " .
7281             "'error_type' in data_validation()";
7282        return -3;
7283    }
7284    else {
7285        $param->{error_type} = $error_type{lc($param->{error_type})};
7286    }
7287
7288
7289    # Convert date/times value if required.
7290    if ($param->{validate} == 4 || $param->{validate} == 5) {
7291        if ($param->{value} =~ /T/) {
7292            my $date_time = $self->convert_date_time($param->{value});
7293
7294            if (!defined $date_time) {
7295                carp "Invalid date/time value '$param->{value}' " .
7296                     "in data_validation()";
7297                return -3;
7298            }
7299            else {
7300                $param->{value} = $date_time;
7301            }
7302        }
7303        if (defined $param->{maximum} && $param->{maximum} =~ /T/) {
7304            my $date_time = $self->convert_date_time($param->{maximum});
7305
7306            if (!defined $date_time) {
7307                carp "Invalid date/time value '$param->{maximum}' " .
7308                     "in data_validation()";
7309                return -3;
7310            }
7311            else {
7312                $param->{maximum} = $date_time;
7313            }
7314        }
7315    }
7316
7317
7318    # Set some defaults if they haven't been defined by the user.
7319    $param->{ignore_blank}  = 1 if !defined $param->{ignore_blank};
7320    $param->{dropdown}      = 1 if !defined $param->{dropdown};
7321    $param->{show_input}    = 1 if !defined $param->{show_input};
7322    $param->{show_error}    = 1 if !defined $param->{show_error};
7323
7324
7325    # These are the cells to which the validation is applied.
7326    $param->{cells} = [[$row1, $col1, $row2, $col2]];
7327
7328    # A (for now) undocumented parameter to pass additional cell ranges.
7329    if (exists $param->{other_cells}) {
7330
7331        push @{$param->{cells}}, @{$param->{other_cells}};
7332    }
7333
7334    # Store the validation information until we close the worksheet.
7335    push @{$self->{_validations}}, $param;
7336}
7337
7338
7339###############################################################################
7340#
7341# _store_validation_count()
7342#
7343# Store the count of the DV records to follow.
7344#
7345# Note, this could be wrapped into _store_dv() but we may require separate
7346# handling of the object id at a later stage.
7347#
7348sub _store_validation_count {
7349
7350    my $self = shift;
7351
7352    my $dv_count = @{$self->{_validations}};
7353    my $obj_id   = -1;
7354
7355    return unless $dv_count;
7356
7357    $self->_store_dval($obj_id , $dv_count);
7358}
7359
7360
7361###############################################################################
7362#
7363# _store_validations()
7364#
7365# Store the data_validation records.
7366#
7367sub _store_validations {
7368
7369    my $self = shift;
7370
7371    return unless scalar @{$self->{_validations}};
7372
7373    for my $param (@{$self->{_validations}}) {
7374        $self->_store_dv(   $param->{cells},
7375                            $param->{validate},
7376                            $param->{criteria},
7377                            $param->{value},
7378                            $param->{maximum},
7379                            $param->{input_title},
7380                            $param->{input_message},
7381                            $param->{error_title},
7382                            $param->{error_message},
7383                            $param->{error_type},
7384                            $param->{ignore_blank},
7385                            $param->{dropdown},
7386                            $param->{show_input},
7387                            $param->{show_error},
7388                            );
7389    }
7390}
7391
7392
7393###############################################################################
7394#
7395# _store_dval()
7396#
7397# Store the DV record which contains the number of and information common to
7398# all DV structures.
7399#
7400sub _store_dval {
7401
7402    my $self        = shift;
7403
7404    my $record      = 0x01B2;       # Record identifier
7405    my $length      = 0x0012;       # Bytes to follow
7406
7407    my $obj_id      = $_[0];        # Object ID number.
7408    my $dv_count    = $_[1];        # Count of DV structs to follow.
7409
7410    my $flags       = 0x0004;       # Option flags.
7411    my $x_coord     = 0x00000000;   # X coord of input box.
7412    my $y_coord     = 0x00000000;   # Y coord of input box.
7413
7414
7415    # Pack the record.
7416    my $header = pack('vv', $record, $length);
7417    my $data   = pack('vVVVV', $flags, $x_coord, $y_coord, $obj_id, $dv_count);
7418
7419    $self->_append($header, $data);
7420}
7421
7422
7423###############################################################################
7424#
7425# _store_dv()
7426#
7427# Store the DV record that specifies the data validation criteria and options
7428# for a range of cells..
7429#
7430sub _store_dv {
7431
7432    my $self            = shift;
7433
7434    my $record          = 0x01BE;       # Record identifier
7435    my $length          = 0x0000;       # Bytes to follow
7436
7437    my $flags           = 0x00000000;   # DV option flags.
7438
7439    my $cells           = $_[0];        # Aref of cells to which DV applies.
7440    my $validation_type = $_[1];        # Type of data validation.
7441    my $criteria_type   = $_[2];        # Validation criteria.
7442    my $formula_1       = $_[3];        # Value/Source/Minimum formula.
7443    my $formula_2       = $_[4];        # Maximum formula.
7444    my $input_title     = $_[5];        # Title of input message.
7445    my $input_message   = $_[6];        # Text of input message.
7446    my $error_title     = $_[7];        # Title of error message.
7447    my $error_message   = $_[8];        # Text of input message.
7448    my $error_type      = $_[9];        # Error dialog type.
7449    my $ignore_blank    = $_[10];       # Ignore blank cells.
7450    my $dropdown        = $_[11];       # Display dropdown with list.
7451    my $input_box       = $_[12];       # Display input box.
7452    my $error_box       = $_[13];       # Display error box.
7453    my $ime_mode        = 0;            # IME input mode for far east fonts.
7454    my $str_lookup      = 0;            # See below.
7455
7456    # Set the string lookup flag for 'list' validations with a string array.
7457    if ($validation_type == 3 && ref $formula_1 eq 'ARRAY')  {
7458        $str_lookup = 1;
7459    }
7460
7461    # The dropdown flag is stored as a negated value.
7462    my $no_dropdown = not $dropdown;
7463
7464    # Set the required flags.
7465    $flags |= $validation_type;
7466    $flags |= $error_type       << 4;
7467    $flags |= $str_lookup       << 7;
7468    $flags |= $ignore_blank     << 8;
7469    $flags |= $no_dropdown      << 9;
7470    $flags |= $ime_mode         << 10;
7471    $flags |= $input_box        << 18;
7472    $flags |= $error_box        << 19;
7473    $flags |= $criteria_type    << 20;
7474
7475    # Pack the validation formulas.
7476    $formula_1 = $self->_pack_dv_formula($formula_1);
7477    $formula_2 = $self->_pack_dv_formula($formula_2);
7478
7479    # Pack the input and error dialog strings.
7480    $input_title   = $self->_pack_dv_string($input_title,   32 );
7481    $error_title   = $self->_pack_dv_string($error_title,   32 );
7482    $input_message = $self->_pack_dv_string($input_message, 255);
7483    $error_message = $self->_pack_dv_string($error_message, 255);
7484
7485    # Pack the DV cell data.
7486    my $dv_count = scalar @$cells;
7487    my $dv_data  = pack 'v', $dv_count;
7488    for my $range (@$cells) {
7489        $dv_data .= pack 'vvvv', $range->[0],
7490                                 $range->[2],
7491                                 $range->[1],
7492                                 $range->[3];
7493    }
7494
7495    # Pack the record.
7496    my $data   = pack 'V', $flags;
7497       $data  .= $input_title;
7498       $data  .= $error_title;
7499       $data  .= $input_message;
7500       $data  .= $error_message;
7501       $data  .= $formula_1;
7502       $data  .= $formula_2;
7503       $data  .= $dv_data;
7504
7505    my $header = pack('vv', $record, length $data);
7506
7507    $self->_append($header, $data);
7508}
7509
7510
7511###############################################################################
7512#
7513# _pack_dv_string()
7514#
7515# Pack the strings used in the input and error dialog captions and messages.
7516# Captions are limited to 32 characters. Messages are limited to 255 chars.
7517#
7518sub _pack_dv_string {
7519
7520    my $self        = shift;
7521
7522    my $string      = $_[0];
7523    my $max_length  = $_[1];
7524
7525    my $str_length  = 0;
7526    my $encoding    = 0;
7527
7528    # The default empty string is "\0".
7529    if (!defined $string || $string eq '') {
7530        $string = "\0";
7531    }
7532
7533    # Excel limits DV captions to 32 chars and messages to 255.
7534    if (length $string > $max_length) {
7535        $string = substr($string, 0, $max_length);
7536    }
7537
7538    $str_length = length $string;
7539
7540    # Handle utf8 strings in perl 5.8.
7541    if ($] >= 5.008) {
7542        require Encode;
7543
7544        if (Encode::is_utf8($string)) {
7545            $string = Encode::encode("UTF-16LE", $string);
7546            $encoding = 1;
7547        }
7548    }
7549
7550    return pack('vC', $str_length, $encoding) . $string;
7551}
7552
7553
7554###############################################################################
7555#
7556# _pack_dv_formula()
7557#
7558# Pack the formula used in the DV record. This is the same as an cell formula
7559# with some additional header information. Note, DV formulas in Excel use
7560# relative addressing (R1C1 and ptgXxxN) however we use the Formula.pm's
7561# default absolute addressing (A1 and ptgXxx).
7562#
7563sub _pack_dv_formula {
7564
7565    my $self        = shift;
7566
7567    my $formula     = $_[0];
7568    my $encoding    = 0;
7569    my $length      = 0;
7570    my $unused      = 0x0000;
7571    my @tokens;
7572
7573    # Return a default structure for unused formulas.
7574    if (!defined $formula || $formula eq '') {
7575        return pack('vv', 0, $unused);
7576    }
7577
7578    # Pack a list array ref as a null separated string.
7579    if (ref $formula eq 'ARRAY') {
7580        $formula   = join "\0", @$formula;
7581        $formula   = qq("$formula");
7582    }
7583
7584    # Strip the = sign at the beginning of the formula string
7585    $formula    =~ s(^=)();
7586
7587    # Parse the formula using the parser in Formula.pm
7588    my $parser  = $self->{_parser};
7589
7590    # In order to raise formula errors from the point of view of the calling
7591    # program we use an eval block and re-raise the error from here.
7592    #
7593    eval { @tokens = $parser->parse_formula($formula) };
7594
7595    if ($@) {
7596        $@ =~ s/\n$//;  # Strip the \n used in the Formula.pm die()
7597        croak $@;       # Re-raise the error
7598    }
7599    else {
7600        # TODO test for non valid ptgs such as Sheet2!A1
7601    }
7602    # Force 2d ranges to be a reference class.
7603    s/_range2d/_range2dR/ for @tokens;
7604    s/_name/_nameR/       for @tokens;
7605
7606    # Parse the tokens into a formula string.
7607    $formula = $parser->parse_tokens(@tokens);
7608
7609
7610    return pack('vv', length $formula, $unused) . $formula;
7611}
7612
7613
7614
7615
7616
76171;
7618
7619
7620__END__
7621
7622=encoding latin1
7623
7624=head1 NAME
7625
7626Worksheet - A writer class for Excel Worksheets.
7627
7628=head1 SYNOPSIS
7629
7630See the documentation for Spreadsheet::WriteExcel
7631
7632=head1 DESCRIPTION
7633
7634This module is used in conjunction with Spreadsheet::WriteExcel.
7635
7636=head1 AUTHOR
7637
7638John McNamara jmcnamara@cpan.org
7639
7640=head1 COPYRIGHT
7641
7642Copyright MM-MMX, John McNamara.
7643
7644All Rights Reserved. This module is free software. It may be used, redistributed and/or modified under the same terms as Perl itself.
7645
7646