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