diff --git a/Changes b/Changes index dd43701..f6b9663 100644 --- a/Changes +++ b/Changes @@ -1,5 +1,36 @@ Revision history for Perl module Spreadsheet::WriteExcel. + +2.20 October 6 2007 - Major + + + + Added autofilter() and filter_column() method and + autofilter.pl example. + + + Added embed_chart() method to allow extracted chart + templates to be embedded in a worksheet. + Added demo4.pl and demo5.pl examples. + + + Added the insert_image() method and proper Excel 97 + style image handling for PNG and BMP. + Images now work with Gnumeric and OpenOffice. + insert_bitmap() is now deprecated. + + ! Made pane split optional when calling freeze_panes(). + Also renamed thaw_panes() as split_panes(). The old + method name is still available, but deprected.. + + ! Renamed write_unicode() and write_unicode_le() methods + to the more explicit write_utf16be_string() and + write_utf16le_string(). The old method names are + still available, but deprected. + + +2.19 June 14 2007 - Major + + + Beta testing only. Not release to CPAN. + + 2.18 January 18 2007 - Minor ! Correct invalid user set_column() calls to prevent diff --git a/MANIFEST b/MANIFEST index 598030a..07b53ea 100644 --- a/MANIFEST +++ b/MANIFEST @@ -29,6 +29,9 @@ t/21_escher.t t/22_mso_drawing_group.t t/23_note.t t/24_txo.t +t/26_autofilter.t +t/27_autofilter.t +t/28_autofilter.t doc/index.html doc/WriteExcel.html @@ -54,6 +57,7 @@ doc/tpj0503-0004-05.gif doc/win32ole.gif examples/README +examples/autofilter.pl examples/autofit.pl examples/bigfile.pl examples/bug_report.pl @@ -95,7 +99,7 @@ examples/panes.pl examples/protection.pl examples/regions.pl examples/repeat.pl -examples/republic.bmp +examples/republic.png examples/right_to_left.pl examples/row_wrap.pl examples/sales.pl @@ -107,7 +111,6 @@ examples/stocks.pl examples/tab2xls.pl examples/tab_colors.pl examples/textwrap.pl -examples/unicode.pl examples/unicode_2022_jp.pl examples/unicode_2022_jp.txt examples/unicode_8859_11.pl @@ -121,7 +124,6 @@ examples/unicode_cp1251.txt examples/unicode_cp1256.pl examples/unicode_cp1256.txt examples/unicode_cyrillic.pl -examples/unicode_japan.pl examples/unicode_koi8r.pl examples/unicode_koi8r.txt examples/unicode_list.pl @@ -129,6 +131,8 @@ examples/unicode_polish_utf8.pl examples/unicode_polish_utf8.txt examples/unicode_shift_jis.pl examples/unicode_shift_jis.txt +examples/unicode_utf16.pl +examples/unicode_utf16_japan.pl examples/win32ole.pl examples/writeA1.pl examples/write_arrays.pl @@ -145,8 +149,12 @@ charts/charts.txt charts/demo1.pl charts/demo2.pl charts/demo3.pl +charts/demo4.pl +charts/demo5.pl charts/Chart1.xls charts/Chart2.xls charts/Chart3.xls +charts/Chart4.xls +charts/Chart5.xls bin/chartex diff --git a/META.yml b/META.yml index 3d4161d..5a11506 100644 --- a/META.yml +++ b/META.yml @@ -1,6 +1,6 @@ name: Spreadsheet-WriteExcel abstract: Write to a cross platform Excel binary file -version: 2.18 +version: 2.20 version_from: lib/Spreadsheet/WriteExcel.pm installdirs: site requires: diff --git a/Makefile.PL b/Makefile.PL index 82ef8f7..d07f301 100644 --- a/Makefile.PL +++ b/Makefile.PL @@ -13,7 +13,7 @@ WriteMakefile( 'NEEDS_LINKING' => 0, 'PREREQ_PM' => { Parse::RecDescent => 0, File::Temp => 0, - OLE::Storage_Lite => 0.14 + OLE::Storage_Lite => 0.14, }, 'dist' => { COMPRESS => 'gzip --best', SUFFIX => 'gz' }, 'EXE_FILES' => ['bin/chartex'], diff --git a/bin/chartex b/bin/chartex index cc81440..22fb193 100644 --- a/bin/chartex +++ b/bin/chartex @@ -5,7 +5,7 @@ # chartex - A utility to extract charts from an Excel file for # insertion into a Spreadsheet::WriteExcel file. # -# reverse('©'), September 2004, John McNamara, jmcnamara@cpan.org +# reverse('©'), September 2007, John McNamara, jmcnamara@cpan.org # # Documentation after __END__ # @@ -25,7 +25,8 @@ my $chart_index = 1; my $sheet_index = -1; my @sheetnames; my @exrefs; - +my $depth_count = 0; +my $max_font = 0; # # Do the Getopt and Pod::Usage routines. @@ -109,29 +110,76 @@ while (read $tmpfile, $header, 4) { my $filename = sprintf "%s%02d.bin", $chart_name, $chart_index; open CHART, ">$filename" or die "Couldn't open $filename: $!"; binmode CHART; - printf "\nExtracting \"%s\" to %s", $sheetnames[$sheet_index], - $filename; + + my $sheet_name = $sheetnames[$sheet_index]; + $sheet_name .= ' embedded' if $depth_count; + + printf "\nExtracting \%s\ to %s", $sheet_name, $filename; $in_chart = 1; $chart_index++; } - $sheet_index++; + $depth_count++; + } + + + # FBI, Chart fonts + if ($record == 0x1060) { + + my $index = substr $data, 8, 2, ''; + $index = unpack 'v', $index; + + # Ignore the inbuilt fonts. + if ($index >= 5) { + $max_font = $index if $index > $max_font; + + # Shift index past S::WE fonts + $index += 2; + } + + $data .= pack 'v', $index; + } + + # FONTX, Chart fonts + if ($record == 0x1026) { + + my $index = unpack 'v', $data; + + # Ignore the inbuilt fonts. + if ($index >= 5) { + $max_font = $index if $index > $max_font; + + # Shift index past S::WE fonts + $index += 2; + } + + $data = pack 'v', $index; } + + if ($in_chart) { print CHART $header, $data; } + # EOF if ($record == 0x000A) { $in_chart = 0; + $depth_count--; + $sheet_index++ if $depth_count == 0; +; } } - -print "\n\n", ('=' x 60), "\n"; -print "Add the following near the start of your program.\n"; -print "Change variable name \$worksheet if required.\n\n"; +if ($chart_index > 1) { + print "\n\n"; + print "Add the following near the start of your program\n"; + print "and change the variable names if required.\n\n"; +} +else { + print "\nNo charts found in workbook\n"; +} for my $aref (@exrefs) { my $sheet1 = $sheetnames[$aref->[1]]; @@ -148,9 +196,19 @@ for my $aref (@exrefs) { $range = "'$range'" if $range =~ /[^\w:]/; - print " \$worksheet->store_formula(\"=$range!A1\");\n"; + print " \$worksheet->store_formula('=$range!A1');\n"; } +print "\n"; + +for my $i (5 .. $max_font) { + + printf " my \$chart_font_%d = \$workbook->add_format(font_only => 1);\n", + $i -4; + +} + + @@ -213,7 +271,7 @@ John McNamara jmcnamara@cpan.org =head1 VERSION -Version 0.01. +Version 0.02. =head1 COPYRIGHT diff --git a/charts/Chart1.xls b/charts/Chart1.xls index 0b3b726..f7d698e 100644 Binary files a/charts/Chart1.xls and b/charts/Chart1.xls differ diff --git a/charts/Chart2.xls b/charts/Chart2.xls index 0a0b29d..3d4a4e4 100644 Binary files a/charts/Chart2.xls and b/charts/Chart2.xls differ diff --git a/charts/Chart3.xls b/charts/Chart3.xls index 58ff348..862b4d1 100644 Binary files a/charts/Chart3.xls and b/charts/Chart3.xls differ diff --git a/charts/Chart4.xls b/charts/Chart4.xls new file mode 100644 index 0000000..b756be9 Binary files /dev/null and b/charts/Chart4.xls differ diff --git a/charts/Chart5.xls b/charts/Chart5.xls new file mode 100644 index 0000000..612cccf Binary files /dev/null and b/charts/Chart5.xls differ diff --git a/charts/README b/charts/README index c99ba43..1fe3114 100644 --- a/charts/README +++ b/charts/README @@ -12,10 +12,14 @@ The files are as follows: demo1.pl A demo of a line chart. demo2.pl A demo of a pie chart. demo3.pl A demo of a Open-High-Low-Close stock chart. + demo4.pl A demo of a Open-High-Low-Close stock chart (embedded). + demo5.pl A demo of a line chart (embedded). Chart1.xls A template for use with demo1.pl. Chart2.xls A template for use with demo2.pl. Chart3.xls A template for use with demo3.pl. + Chart4.xls A template for use with demo4.pl. + Chart5.xls A template for use with demo5.pl. * If you performed a normal installation then the chartex utility should be installed to your 'somepath/perl/bin' directory and @@ -33,6 +37,12 @@ You can run the examples as follows: perl chartex.pl -c=demo3 Chart3.xls perl demo3.pl + perl chartex.pl -c=demo4 Chart4.xls + perl demo4.pl + + perl chartex.pl -c=demo5 Chart5.xls + perl demo5.pl + See charts.txt for further details of how to include externally generated charts in a Spreadsheet::WriteExcel file. diff --git a/charts/chartex.pl b/charts/chartex.pl index 091ddb1..2dd2330 100644 --- a/charts/chartex.pl +++ b/charts/chartex.pl @@ -5,7 +5,7 @@ # chartex - A utility to extract charts from an Excel file for # insertion into a Spreadsheet::WriteExcel file. # -# reverse('©'), September 2004, John McNamara, jmcnamara@cpan.org +# reverse('©'), September 2007, John McNamara, jmcnamara@cpan.org # # Documentation after __END__ # @@ -25,7 +25,8 @@ my $sheet_index = -1; my @sheetnames; my @exrefs; - +my $depth_count = 0; +my $max_font = 0; # # Do the Getopt and Pod::Usage routines. @@ -109,29 +110,76 @@ my $filename = sprintf "%s%02d.bin", $chart_name, $chart_index; open CHART, ">$filename" or die "Couldn't open $filename: $!"; binmode CHART; - printf "\nExtracting \"%s\" to %s", $sheetnames[$sheet_index], - $filename; + + my $sheet_name = $sheetnames[$sheet_index]; + $sheet_name .= ' embedded' if $depth_count; + + printf "\nExtracting \%s\ to %s", $sheet_name, $filename; $in_chart = 1; $chart_index++; } - $sheet_index++; + $depth_count++; + } + + + # FBI, Chart fonts + if ($record == 0x1060) { + + my $index = substr $data, 8, 2, ''; + $index = unpack 'v', $index; + + # Ignore the inbuilt fonts. + if ($index >= 5) { + $max_font = $index if $index > $max_font; + + # Shift index past S::WE fonts + $index += 2; + } + + $data .= pack 'v', $index; + } + + # FONTX, Chart fonts + if ($record == 0x1026) { + + my $index = unpack 'v', $data; + + # Ignore the inbuilt fonts. + if ($index >= 5) { + $max_font = $index if $index > $max_font; + + # Shift index past S::WE fonts + $index += 2; + } + + $data = pack 'v', $index; } + + if ($in_chart) { print CHART $header, $data; } + # EOF if ($record == 0x000A) { $in_chart = 0; + $depth_count--; + $sheet_index++ if $depth_count == 0; +; } } - -print "\n\n", ('=' x 60), "\n"; -print "Add the following near the start of your program.\n"; -print "Change variable name \$worksheet if required.\n\n"; +if ($chart_index > 1) { + print "\n\n"; + print "Add the following near the start of your program\n"; + print "and change the variable names if required.\n\n"; +} +else { + print "\nNo charts found in workbook\n"; +} for my $aref (@exrefs) { my $sheet1 = $sheetnames[$aref->[1]]; @@ -151,6 +199,16 @@ print " \$worksheet->store_formula('=$range!A1');\n"; } +print "\n"; + +for my $i (5 .. $max_font) { + + printf " my \$chart_font_%d = \$workbook->add_format(font_only => 1);\n", + $i -4; + +} + + @@ -213,7 +271,7 @@ =head1 AUTHOR =head1 VERSION -Version 0.01. +Version 0.02. =head1 COPYRIGHT diff --git a/charts/demo1.pl b/charts/demo1.pl index e541ae7..1d05d5e 100644 --- a/charts/demo1.pl +++ b/charts/demo1.pl @@ -30,9 +30,9 @@ # Add some extra formats to cover formats used in the charts. -$workbook->add_format(color => 1); -$workbook->add_format(color => 2); -$workbook->add_format(color => 3); +my $chart_font_1 = $workbook->add_format(font_only => 1); +my $chart_font_2 = $workbook->add_format(font_only => 1); +my $chart_font_3 = $workbook->add_format(font_only => 1); # Add all other formats (if any). diff --git a/charts/demo2.pl b/charts/demo2.pl index e22c9f1..68c81b4 100644 --- a/charts/demo2.pl +++ b/charts/demo2.pl @@ -30,10 +30,11 @@ # Add some extra formats to cover formats used in the charts. -$workbook->add_format(color => 1, bold => 1); -$workbook->add_format(color => 2); -$workbook->add_format(color => 3); - +my $chart_font_1 = $workbook->add_format(font_only => 1); +my $chart_font_2 = $workbook->add_format(font_only => 1); +my $chart_font_3 = $workbook->add_format(font_only => 1); +my $chart_font_4 = $workbook->add_format(font_only => 1); +my $chart_font_5 = $workbook->add_format(font_only => 1); # Add all other formats. my $bold = $workbook->add_format(bold => 1); diff --git a/charts/demo3.pl b/charts/demo3.pl index d67375c..e6bfbf3 100644 --- a/charts/demo3.pl +++ b/charts/demo3.pl @@ -30,11 +30,11 @@ # Add some extra formats to cover formats used in the charts. -$workbook->add_format(color => 1); -$workbook->add_format(color => 2); -$workbook->add_format(color => 3, bold => 1); -$workbook->add_format(color => 4); - +my $chart_font_1 = $workbook->add_format(font_only => 1); +my $chart_font_2 = $workbook->add_format(font_only => 1); +my $chart_font_3 = $workbook->add_format(font_only => 1); +my $chart_font_4 = $workbook->add_format(font_only => 1); +my $chart_font_5 = $workbook->add_format(font_only => 1); # Add all other formats. my $bold = $workbook->add_format(bold => 1); @@ -47,7 +47,7 @@ $worksheet->write('A1', 'Date', $bold); $worksheet->write('B1', 'Open', $bold); $worksheet->write('C1', 'High', $bold); -$worksheet->write('D1', 'Low', $bold); +$worksheet->write('D1', 'Low', $bold); $worksheet->write('E1', 'Close', $bold); diff --git a/charts/demo4.pl b/charts/demo4.pl new file mode 100644 index 0000000..6ad6f72 --- /dev/null +++ b/charts/demo4.pl @@ -0,0 +1,109 @@ +#!/usr/bin/perl -w + +############################################################################### +# +# Simple example of how to embed an externally created chart into a +# Spreadsheet:: WriteExcel worksheet. +# +# +# This example adds an "Open-high-low-close" stock chart extracted from the +# file Chart3.xls as follows: +# +# perl chartex.pl -c=demo4 Chart4.xls +# +# reverse('©'), September 2007, John McNamara, jmcnamara@cpan.org +# + +use strict; +use Spreadsheet::WriteExcel; + +my $workbook = Spreadsheet::WriteExcel->new("demo4.xls"); +my $worksheet = $workbook->add_worksheet(); + + +# Add the chart extracted using the chartex utility +$worksheet->embed_chart('G2', 'demo401.bin', 3, 3, 1.08, 1.21); + + +# Add some extra formats to cover formats used in the charts. +my $chart_font_1 = $workbook->add_format(font_only => 1); +my $chart_font_2 = $workbook->add_format(font_only => 1, bold => 1); +my $chart_font_3 = $workbook->add_format(font_only => 1); +my $chart_font_4 = $workbook->add_format(font_only => 1); + +# Add all other formats. +my $bold = $workbook->add_format(bold => 1); +my $date_format = $workbook->add_format(num_format => 'dd/mm/yyyy'); + + +# Adjust column widths and add some headers +$worksheet->set_column('A:A', 12); + +$worksheet->write('A1', 'Date', $bold); +$worksheet->write('B1', 'Open', $bold); +$worksheet->write('C1', 'High', $bold); +$worksheet->write('D1', 'Low', $bold); +$worksheet->write('E1', 'Close', $bold); + + +# Add data to range that the chart refers to. +my @dates = ( + + "2004-08-19T", + "2004-08-20T", + "2004-08-23T", + "2004-08-24T", + "2004-08-25T", + "2004-08-26T", + "2004-08-27T", + "2004-08-30T", + "2004-08-31T", + "2004-09-01T", + "2004-09-02T", + "2004-09-03T", + "2004-09-07T", + "2004-09-08T", + "2004-09-09T", + "2004-09-10T", + "2004-09-13T", + "2004-09-14T", + "2004-09-15T", + "2004-09-16T", + "2004-09-17T", + "2004-09-20T", + "2004-09-21T", +); + +# Open-High-Low-Close prices +my @prices = ( + + [100.00, 104.06, 95.96, 100.34], + [101.01, 109.08, 100.50, 108.31], + [110.75, 113.48, 109.05, 109.40], + [111.24, 111.60, 103.57, 104.87], + [104.96, 108.00, 103.88, 106.00], + [104.95, 107.95, 104.66, 107.91], + [108.10, 108.62, 105.69, 106.15], + [105.28, 105.49, 102.01, 102.01], + [102.30, 103.71, 102.16, 102.37], + [102.70, 102.97, 99.67, 100.25], + [ 99.19, 102.37, 98.94, 101.51], + [100.95, 101.74, 99.32, 100.01], + [101.01, 102.00, 99.61, 101.58], + [100.74, 103.03, 100.50, 102.30], + [102.53, 102.71, 101.00, 102.31], + [101.60, 106.56, 101.30, 105.33], + [106.63, 108.41, 106.46, 107.50], + [107.45, 112.00, 106.79, 111.49], + [110.56, 114.23, 110.20, 112.00], + [112.34, 115.80, 111.65, 113.97], + [114.42, 117.49, 113.55, 117.49], + [116.95, 121.60, 116.77, 119.36], + [119.81, 120.42, 117.51, 117.84], +); + + + +my $row = 1; +$worksheet->write_date_time($row++, 0, $_, $date_format) for @dates; +$worksheet->write_col('B2', \@prices); diff --git a/charts/demo5.pl b/charts/demo5.pl new file mode 100644 index 0000000..c289a43 --- /dev/null +++ b/charts/demo5.pl @@ -0,0 +1,41 @@ +#!/usr/bin/perl -w + +############################################################################### +# +# Simple example of how to embed an externally created chart into a +# Spreadsheet:: WriteExcel worksheet. +# +# +# This example adds a line chart extracted from the file Chart1.xls as follows: +# +# perl chartex.pl -c=demo5 Chart5.xls +# +# +# reverse('©'), September 2007, John McNamara, jmcnamara@cpan.org +# + +use strict; +use Spreadsheet::WriteExcel; + +my $workbook = Spreadsheet::WriteExcel->new('demo5.xls'); +my $worksheet = $workbook->add_worksheet(); + + +# Embed a chart extracted using the chartex utility +$worksheet->embed_chart('D3', 'demo501.bin'); + + +# Add some extra formats to cover formats used in the charts. +my $chart_font_1 = $workbook->add_format(font_only => 1); +my $chart_font_2 = $workbook->add_format(font_only => 1); + +# Add all other formats (if any). + + +# Add data to range that the chart refers to. +my @nums = (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ); +my @squares = (0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100); + +$worksheet->write_col('A1', \@nums ); +$worksheet->write_col('B1', \@squares); + diff --git a/doc/WriteExcel.html b/doc/WriteExcel.html index 610675e..4b8c397 100644 --- a/doc/WriteExcel.html +++ b/doc/WriteExcel.html @@ -63,8 +63,8 @@
  • write($row, $column, $token, $format)
  • write_number($row, $column, $number, $format)
  • write_string($row, $column, $string, $format)
  • -
  • write_unicode($row, $column, $string, $format)
  • -
  • write_unicode_le($row, $column, $string, $format)
  • +
  • write_utf16be_string($row, $column, $string, $format)
  • +
  • write_utf16le_string($row, $column, $string, $format)
  • keep_leading_zeros()
  • write_blank($row, $column, $format)
  • write_row($row, $column, $array_ref, $format)
  • @@ -78,7 +78,8 @@
  • write_comment($row, $column, $string, ...)
  • show_comments()
  • add_write_handler($re, $code_ref)
  • -
  • insert_bitmap($row, $col, $filename, $x, $y, $scale_x, $scale_y)
  • +
  • insert_image($row, $col, $filename, $x, $y, $scale_x, $scale_y)
  • +
  • embed_chart($row, $col, $filename, $x, $y, $scale_x, $scale_y)
  • get_name()
  • activate()
  • select()
  • @@ -90,7 +91,7 @@
  • set_column($first_col, $last_col, $width, $format, $hidden, $level)
  • outline_settings($visible, $symbols_below, $symbols_right, $auto_style)
  • freeze_panes($row, $col, $top_row, $left_col)
  • -
  • thaw_panes($y, $x, $top_row, $left_col)
  • +
  • split_panes($y, $x, $top_row, $left_col)
  • merge_range($first_row, $first_col, $last_row, $last_col, $token, $format, $encoding)
  • set_zoom($scale)
  • right_to_left()
  • @@ -167,8 +168,9 @@ +
  • UNICODE IN EXCEL
  • COLOURS IN EXCEL
  • -
  • DATES IN EXCEL
  • +
  • DATES AND TIME IN EXCEL
  • OUTLINES AND GROUPING IN EXCEL
  • FORMULAS AND FUNCTIONS IN EXCEL
  • @@ -224,8 +227,8 @@

    NAME


    VERSION

    -This document refers to version 2.18 of Spreadsheet::WriteExcel, released -January 18, 2007. +This document refers to version 2.20 of Spreadsheet::WriteExcel, released +October 6, 2007.

    @@ -344,7 +347,7 @@

    QUICK START

    This will create an Excel file called perl.xls with a single worksheet and the text "Hi Excel!" in the relevant cell. And that's it. Okay, so there is actually a zeroth -step as well, but use module goes without saying. There are also more than 70 examples that come with +step as well, but use module goes without saying. There are also more than 80 examples that come with the distribution and which you can use to get you started. See EXAMPLES.

    @@ -545,9 +548,8 @@

    new()

    Note about the requirement for binmode(): An Excel file is comprised of binary data. Therefore, if you are using a filehandle you should ensure that you binmode() it prior to passing it to new().You should do this regardless of whether you are on a Windows platform or -not. This applies especially to users of perl 5.8 on systems where utf8 is -likely to be in operation such as RedHat Linux 9. If your program, either -intentionally or not, writes UTF8 data to a filehandle that is passed to new() it will corrupt the Excel file that is created. +not. This applies especially to users of perl 5.8 on systems where UTF-8 is likely to be in operation such as RedHat Linux 9. If your program, +either intentionally or not, writes UTF-8 data to a filehandle that is passed to new() it will corrupt the Excel file that is created.

    @@ -719,7 +721,7 @@

    add_worksheet($sheetname, $encoding)

    -On systems with perl 5.8 and later the add_worksheet() method will also handle strings in Perl's utf8 format. +On systems with perl 5.8 and later the add_worksheet() method will also handle strings in <UTF-8> format.

    @@ -728,8 +730,7 @@

    add_worksheet($sheetname, $encoding)

    -On earlier Perl systems your can specify UTF-16BE worksheet names using an -additional encoding parameter: +On earlier Perl systems your can specify UTF-16BE worksheet names using an additional encoding parameter:

    @@ -920,7 +921,7 @@

    set_1904()

    -See also DATES IN EXCEL for more information about working with Excel's date system. +See also DATES AND TIME IN EXCEL for more information about working with Excel's date system.

    @@ -974,8 +975,8 @@

    WORKSHEET METHODS

    write() write_number() write_string() - write_unicode() - write_unicode_le() + write_utf16be_string() + write_utf16le_string() keep_leading_zeros() write_blank() write_row() @@ -989,7 +990,7 @@

    WORKSHEET METHODS

    write_comment() show_comments() add_write_handler() - insert_bitmap() + insert_image() get_name() activate() select() @@ -1001,12 +1002,13 @@

    WORKSHEET METHODS

    set_column() outline_settings() freeze_panes() - thaw_panes() + split_panes() merge_range() set_zoom() right_to_left() hide_zero() set_tab_color() + autofilter()

    @@ -1223,7 +1225,7 @@

    write($row, $column, $token, $format)

    -On systems with perl 5.8 and later the write() method will also handle Unicode strings in Perl's utf8 format. +On systems with perl 5.8 and later the write() method will also handle Unicode strings in UTF-8 format.

    @@ -1283,7 +1285,7 @@

    write_string($row, $column, $string, $

    -On systems with perl 5.8 and later the write() method will also handle strings in Perl's utf8 format. With older perls you can also write Unicode in UTF16 format via the write_unicode() method. See also the unicode_*.pl programs in the examples directory of the distro. +On systems with perl 5.8 and later the write() method will also handle strings in UTF-8 format. With older perls you can also write Unicode in UTF16 format via the write_utf16be_string() method. See also the unicode_*.pl programs in the examples directory of the distro.

    @@ -1315,25 +1317,15 @@

    write_string($row, $column, $string, $


    -

    write_unicode($row, $column, $string, $format)

    +

    write_utf16be_string($row, $column, $string, $format)

    -This method is used to write Unicode strings to a cell in Excel. It is -functionally the same as the write_string() method except that the string should be in UTF-16 Unicode format. +This method is used to write UTF-16BE strings to a cell in Excel. It is functionally the same as the write_string() method except that the string should be in UTF-16BE Unicode format. It is generally easier, when using Spreadsheet::WriteExcel, +to write unicode strings in UTF-8 format, see UNICODE IN EXCEL. The write_utf16be_string() method is mainly of use in versions of perl prior to 5.8.

    -Note: on systems with perl 5.8 and later the write() and write_string()methods will also handle strings in Perl's utf8 format. With older perls you must use the write_unicode() method. - -

    -

    -The Unicode format required by Excel is UTF-16. Additionally Spreadsheet::WriteExcel requires that the 16-bit characters are in big-endian order. This is -generally referred to as UTF-16BE. To write UTF-16 strings in little-endian -format use the write_unicode_le() method. - -

    -

    -The following is a simple example showing how to write some Unicode -strings: +The following is a simple example showing how to write some Unicode strings +in UTF-16BE format:

    @@ -1350,7 +1342,7 @@

    write_unicode($row, $column, $string,

    -    my $workbook  = Spreadsheet::WriteExcel->new('unicode.xls');
    +    my $workbook  = Spreadsheet::WriteExcel->new('utf_16_be.xls');
         my $worksheet = $workbook->add_worksheet();
     

    @@ -1375,20 +1367,20 @@

    write_unicode($row, $column, $string,

    -    $worksheet->write_unicode('A3', $smiley, $big_font);
    +    $worksheet->write_utf16be_string('A3', $smiley, $big_font);
     

         # Write a phrase in Cyrillic using a hex-encoded string
         #
    -    my $uni_str = pack "H*", "042d0442043e0020044404400430043704300020043d" .
    -                             "043000200440044304410441043a043e043c0021";
    +    my $str = pack "H*", "042d0442043e0020044404400430043704300020043d" .
    +                         "043000200440044304410441043a043e043c0021";
     

    -    $worksheet->write_unicode('A5', $uni_str);
    +    $worksheet->write_utf16be_string('A5', $str);
     

    @@ -1401,64 +1393,11 @@

    write_unicode($row, $column, $string,

    -    $worksheet->write_unicode('A7', $utf16);
    -
    -

    -

    -The following is an example of creating an Excel file with some Japanese -text. You will need to have a Unicode font installed, such as Arial Unicode MS, to view the results: - -

    -

    -

    -    #!/usr/bin/perl -w
    -
    -

    -

    -

    -    use strict;
    -    use Spreadsheet::WriteExcel;
    -
    -

    -

    -

    -    my $workbook  = Spreadsheet::WriteExcel->new('unicode.xls');
    -    my $worksheet = $workbook->add_worksheet();
    -
    -

    -

    -

    -    # It is only required to specify a Unicode font via add_format() if
    -    # you are using Excel 97. For Excel 2000+ the text will display
    -    # with the default font (if you have Unicode fonts installed).
    -    #
    -    my $uni_font  = $workbook->add_format(font => 'Arial Unicode MS');
    -
    -

    -

    -

    -    my $kanji     = pack 'n*', 0x65e5, 0x672c;
    -    my $katakana  = pack 'n*', 0xff86, 0xff8e, 0xff9d;
    -    my $hiragana  = pack 'n*', 0x306b, 0x307b, 0x3093;
    -
    -

    -

    -

    -    $worksheet->write_unicode('A1', $kanji,    $uni_font);
    -    $worksheet->write_unicode('A2', $katakana, $uni_font);
    -    $worksheet->write_unicode('A3', $hiragana, $uni_font);
    -
    -

    -

    -

    -    $worksheet->write('B1', 'Kanji');
    -    $worksheet->write('B2', 'Katakana');
    -    $worksheet->write('B3', 'Hiragana');
    +    $worksheet->write_utf16be_string('A7', $utf16);
     

    -Note: You can convert ascii encodings to the required UTF-16BE format using -one of the many Unicode modules on CPAN. For example Unicode::Map and Unicode::String: UTF-16BE format using one of the many Unicode modules on CPAN. For example Unicode::Map and Unicode::String: http://search.cpan.org/author/MSCHWARTZ/Unicode-Map/Map.pm and http://search.cpan.org/author/GAAS/Unicode-String/String.pm @@ -1470,6 +1409,14 @@

    write_unicode($row, $column, $string, HREF="http://search.cpan.org/search?query=unicode&mode=all">http://search.cpan.org/search?query=unicode&mode=all +

    +

    +UTF-16BE is the format most often returned by Perl modules that generate UTF-16. To write UTF-16 strings in little-endian format use the write_utf16be_string_le() method below. + +

    +

    +The write_utf16be_string() method was previously called write_unicode(). That, overly general, name is still supported but deprecated. +

    See also the unicode_*.pl programs in the examples directory of the distro. @@ -1477,27 +1424,21 @@

    write_unicode($row, $column, $string,


    -

    write_unicode_le($row, $column, $string, $format)

    +

    write_utf16le_string($row, $column, $string, $format)

    -This method is the same as write_unicode() except that the string should be 16-bit characters in little-endian format. -This is generally referred to as UTF-16LE. +This method is the same as write_utf16be() except that the string should be 16-bit characters in little-endian format. +This is generally referred to as UTF-16LE. See UNICODE IN EXCEL.

    -UTF-16 data can be changed from little-endian to big-endian format (and +UTF-16 data can be changed from little-endian to big-endian format (and vice-versa) as follows:

    -    $utf16 = pack "n*", unpack "v*", $utf16;
    +    $utf16be = pack "n*", unpack "v*", $utf16le;
     
    -

    -

    -Note, it is slightly faster to write little-endian data via -write_unicode_le() than it is to write big-endian data via -write_unicode(). -


    @@ -1934,7 +1875,7 @@

    write_date_time($row, $col, $date_stri

    -A date should always have a $format, otherwise it will appear as a number, see DATES IN EXCEL and CELL FORMATTING. Here is a typical example: +A date should always have a $format, otherwise it will appear as a number, see DATES AND TIME IN EXCEL and CELL FORMATTING. Here is a typical example:

    @@ -2373,11 +2314,6 @@

    repeat_formula($row, $col, $formula, $

    write_comment($row, $column, $string, ...)

    -NOTE: This method is currently incompatible with insert_bitmap(). You can use either method but not both in the same workbook. This will be -fixed soon. - -

    -

    The write_comment() method is used to add a comment to a cell. A cell comment is indicated in Excel by a small red triangle in the upper right-hand corner of the cell. Moving the cursor over the red triangle will reveal the comment. @@ -2404,7 +2340,7 @@

    write_comment($row, $column, $string,

    -On systems with perl 5.8 and later the write_comment() method will also handle strings in Perl's utf8 format. +On systems with perl 5.8 and later the write_comment() method will also handle strings in UTF-8 format.

    @@ -2451,8 +2387,7 @@

    write_comment($row, $column, $string,
    Option: encoding

    -This option is used to indicate that the comment string is encoded as -UTF-16BE. +This option is used to indicate that the comment string is encoded as UTF-16BE.

    @@ -2467,7 +2402,7 @@

    write_comment($row, $column, $string,

    If you wish to use Unicode characters in the comment string then the -preferred method is to use perl 5.8 and UTF-8 strings. +preferred method is to use perl 5.8 and UTF-8 strings, see UNICODE IN EXCEL.

    Option: author
    @@ -2485,8 +2420,7 @@

    write_comment($row, $column, $string,

    Option: author_encoding

    -This option is used to indicate that the author string is encoded as -UTF-16BE. +This option is used to indicate that the author string is encoded as UTF-16BE.

    Option: visible
    @@ -2703,9 +2637,9 @@

    add_write_handler($re, $code_ref)

    -The method is use as follows. say you wished to write 7 digit ID numbers as -a string so that any leading zeros were preserved*, you could do something -like the following: +The method is used as follows. say you wished to write 7 digit ID numbers +as a string so that any leading zeros were preserved*, you could do +something like the following:

    @@ -2812,36 +2746,18 @@

    add_write_handler($re, $code_ref)


    -

    insert_bitmap($row, $col, $filename, $x, $y, $scale_x, $scale_y)

    -

    -NOTE: This method is currently incompatible with write_comment(). You can use either method but not both in the same workbook. This will be -fixed soon. - -

    -

    -NOTE: The images inserted using this method do not display in OpenOffice.org or -Gnumeric. This is related to the previous note and will also be fixed soon. - -

    +

    insert_image($row, $col, $filename, $x, $y, $scale_x, $scale_y)

    -This method can be used to insert a bitmap into a worksheet. The bitmap -must be a 24 bit, true colour, bitmap. No other format is supported. The $x, $y, $scale_x and $scale_y parameters are optional. +This method can be used to insert a image into a worksheet. The image can +be in PNG or BMP format. The $x, $y, $scale_x and $scale_y parameters are optional.

    -    $worksheet1->insert_bitmap('A1', 'perl.bmp');
    -    $worksheet2->insert_bitmap('A1', '../images/perl.bmp');
    -    $worksheet3->insert_bitmap('A1', '.c:\images\perl.bmp');
    +    $worksheet1->insert_image('A1', 'perl.bmp');
    +    $worksheet2->insert_image('A1', '../images/perl.bmp');
    +    $worksheet3->insert_image('A1', '.c:\images\perl.bmp');
     
    -

    -

    -Note: you must call set_row() or set_column() before insert_bitmap() if you wish to change the default dimensions of any of the rows or columns -that the images occupies. The height of a row can also change if you use a -font that is larger than the default. This in turn will affect the scaling -of your image. To avoid this you should explicitly set the height of the -row using set_row() if it contains a font size that will change the row height. -

    The parameters $x and $y can be used to specify an offset from the top left hand corner of the cell @@ -2850,7 +2766,7 @@

    insert_bitmap($row, $col, $filename, $

    -    $worksheet1->insert_bitmap('A1', 'perl.bmp', 32, 10);
    +    $worksheet1->insert_image('A1', 'perl.bmp', 32, 10);
     

    @@ -2888,29 +2804,85 @@

    insert_bitmap($row, $col, $filename, $

         # Scale the inserted image: width x 2.0, height x 0.8
    -    $worksheet->insert_bitmap('A1', 'perl.bmp', 0, 0, 2, 0.8);
    +    $worksheet->insert_image('A1', 'perl.bmp', 0, 0, 2, 0.8);
    +
    +

    +

    +See also the images.pl program in the examples directory of the distro. + +

    +

    +Note: you must call set_row() or set_column() before insert_image() if you wish to change the default dimensions of any of the rows or columns +that the image occupies. The height of a row can also change if you use a +font that is larger than the default. This in turn will affect the scaling +of your image. To avoid this you should explicitly set the height of the +row using set_row() if it contains a font size that will change the row height. + +

    +

    +BMP images must be 24 bit, true colour, bitmaps. In general it is best to +avoid BMP images since they aren't compressed. The older insert_bitmap() method is still supported but deprecated. + +

    +

    +


    +

    embed_chart($row, $col, $filename, $x, $y, $scale_x, $scale_y)

    +

    +This method can be used to insert a chart into a worksheet. The chart must +first be extracted from an existing Excel file. See the separate Charts documentation. + +

    +

    +Here is an example: + +

    +

    +

    +    $worksheet->embed_chart('B2', 'sales_chart.bin');
     

    -Note: although Excel allows you to import several graphics formats such as -gif, jpeg, png and eps these are converted internally into a proprietary -format. One of the few non-proprietary formats that Excel supports is 24 -bit, true colour, bitmaps. Therefore if you wish to use images in any other -format you must first use an external application such as the ImageMagick convert utility to convert them to 24 bit bitmaps. +The $x, $y, $scale_x and $scale_y parameters are optional. + +

    +

    +The parameters $x and $y can be used to specify an offset from the top left hand corner of the cell +specified by $row and $col. The offset values are in pixels. See the insert_image method above for more information on sizes.

    -    convert test.png test.bmp
    +    $worksheet1->embed_chart('B2', 'sales_chart.bin', 3, 3);
     

    -A later release will support the use of file handles and pre-encoded bitmap -strings. +The parameters $scale_x and $scale_y can be used to scale the inserted image horizontally and vertically:

    -See also the images.pl program in the examples directory of the distro. +

    +    # Scale the width by 120% and the height by 150%
    +    $worksheet->embed_chart('B2', 'sales_chart.bin', 0, 0, 1.2, 1.5);
    +
    +

    +

    +The easiest way to calculate the required scaling is to create a test chart +worksheet with Spreadsheet::WriteExcel. Then open the file, select the +chart and drag the corner to get the required size. While holding down the +mouse the scale of the resized chart is shown to the left of the formula +bar. + +

    +

    +See also the example programs in the charts directory of the distro. + +

    +

    +Note: you must call set_row() or set_column() before embed_chart() if you wish to change the default dimensions of any of the rows or columns +that the chart occupies. The height of a row can also change if you use a +font that is larger than the default. This in turn will affect the scaling +of your chart. To avoid this you should explicitly set the height of the +row using set_row() if it contains a font size that will change the row height.

    @@ -2926,6 +2898,11 @@

    get_name()

    print $sheet->get_name(); } +

    +

    +For reasons related to the design of Spreadsheet::WriteExcel and to the +internals of Excel there is no set_name() method. The only way to set the worksheet name is via the add_worksheet() method. +


    @@ -2949,8 +2926,11 @@

    activate()

    This is similar to the Excel VBA activate method. More than one worksheet -can be selected via the select() method, however only one worksheet can be active. The default value is the -first worksheet. +can be selected via the select() method, see below, however only one worksheet can be active. + +

    +

    +The default active worksheet is the first worksheet.

    @@ -2971,7 +2951,7 @@

    select()

    A selected worksheet has its tab highlighted. Selecting worksheets is a way of grouping them together so that, for example, several worksheets could be -printed in one go. A worksheet that has been activated via the activate() method will also appear as selected. You probably won't need to use the select() method very often. +printed in one go. A worksheet that has been activated via the activate() method will also appear as selected.

    @@ -2983,7 +2963,7 @@

    hide()

    -    $worksheet->hide();
    +    $worksheet2->hide();
     

    @@ -2993,8 +2973,16 @@

    hide()

    A hidden worksheet can not be activated or selected so this method is -mutually exclusive with the activate() and select() methods. +mutually exclusive with the activate() and select() methods. In addition, since the first worksheet will default to being the +active worksheet, you cannot hide the first worksheet without activating +another sheet: +

    +

    +

    +    $worksheet2->activate();
    +    $worksheet1->hide();
    +


    @@ -3442,7 +3430,7 @@

    freeze_panes($row, $col, $top_row, $le


    -

    thaw_panes($y, $x, $top_row, $left_col)

    +

    split_panes($y, $x, $top_row, $left_col)

    This method can be used to divide a worksheet into horizontal or vertical regions known as panes. This method is different from the freeze_panes() method in that the splits between the panes will be visible to the user and @@ -3469,9 +3457,9 @@

    thaw_panes($y, $x, $top_row, $left_col)<

    -    $worksheet->thaw_panes(12.75, 0,    1, 0); # First row
    -    $worksheet->thaw_panes(0,     8.43, 0, 1); # First column
    -    $worksheet->thaw_panes(12.75, 8.43, 1, 1); # First row and column
    +    $worksheet->split_panes(12.75, 0,    1, 0); # First row
    +    $worksheet->split_panes(0,     8.43, 0, 1); # First column
    +    $worksheet->split_panes(12.75, 8.43, 1, 1); # First row and column
     

    @@ -3481,6 +3469,11 @@

    thaw_panes($y, $x, $top_row, $left_col)<

    See also the freeze_panes() method and the panes.pl program in the examples directory of the distribution. +

    +

    +Note: This split_panes() method was called thaw_panes() in older versions. The older name is still available for backwards +compatiblity. +


    @@ -3531,7 +3524,7 @@

    merge_range($first_row, $first_col,

    -On systems with perl 5.8 and later the merge_range() method will also handle strings in Perl's utf8 format. +On systems with perl 5.8 and later the merge_range() method will also handle strings in UTF-8 format.

    @@ -3540,8 +3533,7 @@

    merge_range($first_row, $first_col,

    -On earlier Perl systems your can specify UTF-16BE worksheet names using an -additional encoding parameter: +On earlier Perl systems your can specify UTF-16BE worksheet names using an additional encoding parameter:

    @@ -3631,6 +3623,180 @@

    set_tab_color()

    See the tab_colors.pl program in the examples directory of the distro. +

    +

    +


    +

    autofilter($first_row, $first_col, $last_row, $last_col)

    +

    +This method allows an autofilter to be added to a worksheet. An autofilter +is a way of adding drop down lists to the headers of a 2D range of +worksheet data. This is turn allow users to filter the data based on simple +criteria so that some data is highlighted and some is hidden. + +

    +

    +To add an autofilter to a worksheet: + +

    +

    +

    +    $worksheet->autofilter(0, 0, 10, 3);
    +    $worksheet->autofilter('A1:D11');    # Same as above in A1 notation.
    +
    +

    +

    +Filter conditions can be applied using the filter_column() method. + +

    +

    +See the autofilter.pl program in the examples directory of the distro for a more detailed +example. + +

    +

    +


    +

    filter_column($column, $expression)

    +

    +The filter_column method can be used to filter columns in a autofilter range based on simple +conditions. + +

    +

    +NOTE: It isn't sufficient to just specify the filter condition. You must also +hide any rows that don't match the filter condition. Rows are hidden using +the set_row() visible parameter. Spreadsheet::WriteExcel cannot do this automatically since it isn't part of the file format. See +the autofilter.pl program in the examples directory of the distro for an example. + +

    +

    +The conditions for the filter are specified using simple expressions: + +

    +

    +

    +    $worksheet->filter_column('A', 'x > 2000');
    +    $worksheet->filter_column('B', 'x > 2000 and x < 5000');
    +
    +

    +

    +The $column parameter can either be a zero indexed column number or a string column +name. + +

    +

    +The following operators are available: + +

    +

    +

    +    Operator        Synonyms
    +       ==           =   eq  =~
    +       !=           <>  ne  !=
    +       >
    +       <
    +       >=
    +       <=
    +
    +

    +

    +

    +       and          &&
    +       or           ||
    +
    +

    +

    +The operator synonyms are just syntactic sugar to make you more comfortable +using the expressions. It is important to remember that the expressions +will be interpreted by Excel and not by perl. + +

    +

    +An expression can comprise a single statement or two statements separated +by the and and or operators. For example: + +

    +

    +

    +    'x <  2000'
    +    'x >  2000'
    +    'x == 2000'
    +    'x >  2000 and x <  5000'
    +    'x == 2000 or  x == 5000'
    +
    +

    +

    +Filtering of blank or non-blank data can be achieved by using a value of Blanks or NonBlanks in the expression: + +

    +

    +

    +    'x == Blanks'
    +    'x == NonBlanks'
    +
    +

    +

    +Top 10 style filters can be specified using a expression like the +following: + +

    +

    +

    +    Top|Bottom 1-500 Items|%
    +
    +

    +

    +For example: + +

    +

    +

    +    'Top    10 Items'
    +    'Bottom  5 Items'
    +    'Top    25 %'
    +    'Bottom 50 %'
    +
    +

    +

    +Excel also allows some simple string matching operations: + +

    +

    +

    +    'x =~ b*'   # begins with b
    +    'x !~ b*'   # doesn't begin with b
    +    'x =~ *b'   # ends with b
    +    'x !~ *b'   # doesn't end with b
    +    'x =~ *b*'  # contains b
    +    'x !~ *b*'  # doesn't contains b
    +
    +

    +

    +You can also use * to match any character or number and ? to match any single character or number. No other regular expression +quantifier is supported by Excel's filters. Excel's regular expression +characters can be escaped using ~. + +

    +

    +The placeholder variable x in the above examples can be replaced by any simple string. The actual +placeholder name is ignored internally so the following are all equivalent: + +

    +

    +

    +    'x     < 2000'
    +    'col   < 2000'
    +    'Price < 2000'
    +
    +

    +

    +Also, note that a filter condition can only be applied to a column in a +range specified by the autofilter() Worksheet method. + +

    +

    +See the autofilter.pl program in the examples directory of the distro for a more detailed +example. +


    @@ -4079,7 +4245,7 @@

    set_header($string, $margin)

    -On systems with perl 5.8 and later the set_header() method can also handle Unicode strings in Perl's utf8 format. +On systems with perl 5.8 and later the set_header() method can also handle Unicode strings in UTF-8 format.

    @@ -5102,7 +5268,7 @@

    set_num_format()

    -The number system used for dates is described in DATES IN EXCEL. +The number system used for dates is described in DATES AND TIME IN EXCEL.

    @@ -5587,21 +5753,83 @@

    set_border()

         Default state:      Border is off
         Default action:     Set border type 1
    -    Valid args:         0 No border
    -                        1 Thin single border
    -                        2 Medium single border
    -                        3 Dashed border
    -                        4 Dotted border
    -                        5 Thick single border
    -                        6 Double line border
    -                        7 Hair border
    +    Valid args:         0-13, See below.
     

    A cell border is comprised of a border on the bottom, top, left and right. -These can be set to the same value using set_border() or individually using the relevant method calls shown above. Examples of -the available border styles are shown in the 'Borders' worksheet created by -formats.pl. +These can be set to the same value using set_border() or individually using the relevant method calls shown above. + +

    +

    +The following shows the border styles sorted by Spreadsheet::WriteExcel +index number: + +

    +

    +

    +    Index   Name            Weight   Style
    +    =====   =============   ======   ===========
    +    0       None            0
    +    1       Continuous      1        -----------
    +    2       Continuous      2        -----------
    +    3       Dash            1        - - - - - -
    +    4       Dot             1        . . . . . .
    +    5       Continuous      3        -----------
    +    6       Double          3        ===========
    +    7       Continuous      0        -----------
    +    8       Dash            2        - - - - - -
    +    9       Dash Dot        1        - . - . - .
    +    10      Dash Dot        2        - . - . - .
    +    11      Dash Dot Dot    1        - . . - . .
    +    12      Dash Dot Dot    2        - . . - . .
    +    13      SlantDash Dot   2        / - . / - .
    +
    +

    +

    +The following shows the borders sorted by style: + +

    +

    +

    +    Name            Weight   Style         Index
    +    =============   ======   ===========   =====
    +    Continuous      0        -----------   7
    +    Continuous      1        -----------   1
    +    Continuous      2        -----------   2
    +    Continuous      3        -----------   5
    +    Dash            1        - - - - - -   3
    +    Dash            2        - - - - - -   8
    +    Dash Dot        1        - . - . - .   9
    +    Dash Dot        2        - . - . - .   10
    +    Dash Dot Dot    1        - . . - . .   11
    +    Dash Dot Dot    2        - . . - . .   12
    +    Dot             1        . . . . . .   4
    +    Double          3        ===========   6
    +    None            0                      0
    +    SlantDash Dot   2        / - . / - .   13
    +
    +

    +

    +The following shows the borders in the order shown in the Excel Dialog. + +

    +

    +

    +    Index   Style             Index   Style
    +    =====   =====             =====   =====
    +    0       None              12      - . . - . .
    +    7       -----------       13      / - . / - .
    +    4       . . . . . .       10      - . - . - .
    +    11      - . . - . .       8       - - - - - -
    +    9       - . - . - .       2       -----------
    +    3       - - - - - -       5       -----------
    +    1       -----------       6       ===========
    +
    +

    +

    +Examples of the available border styles are shown in the 'Borders' +worksheet created by formats.pl.

    @@ -5663,6 +5891,105 @@

    copy($format)

    Note: this is not a copy constructor, both objects must exist prior to copying. +

    +

    +


    +

    UNICODE IN EXCEL

    +

    +For a more general introduction to Unicode handling in Perl see perlunitut and perluniintro. + +

    +

    +When using Spreadsheet::WriteExcel the best and easiest way to write unicode strings to an Excel file is to +use UTF-8 encoded strings and perl 5.8 (or later). The module also allows you to +write unicode strings using older perls but it generally requires more +work, as explained below. + +

    +

    +Internally, Excel encodes unicode data as UTF-16LE (where LE means little-endian). If you are using perl 5.8+ then +Spreadsheet::WriteExcel will convert UTF-8 strings to UTF-16LE when required. No further intervention is required from the programmer, for +example: + +

    +

    +

    +    # perl 5.8+ example:
    +    my $smiley = "\x{263A}";
    +
    +

    +

    +

    +    $worksheet->write('A1', 'Hello world');
    +    $worksheet->write('A2', $smiley);
    +
    +

    +

    +Spreadsheet::WriteExcel also lets you write unicode data as UTF-16. Since the majority of CPAN modules default to UTF-16BE (big-endian) Spreadsheet::WriteExcel also uses UTF-16BE and converts it internally to UTF-16LE: + +

    +

    +

    +    # perl 5.005 example:
    +    my $smiley = pack "n", 0x263A;
    +
    +

    +

    +

    +    $worksheet->write               ('A3', 'Hello world');
    +    $worksheet->write_utf16be_string('A4', $smiley);
    +
    +

    +

    +Although the above examples look similar there is an important difference. +With uft8 and perl 5.8+ Spreadsheet::WriteExcel treats UTF-8 strings in exactly the same way as any other string. However, with UTF16 data we need to distinguish it from other strings either by calling a +separate function or by passing an additional flag to indicate the data +type. + +

    +

    +If you are dealing with non-ASCII character sets then your data probably +won't be in UTF-8 to begin with. However, perl 5.8+ provides useful tools in the guise of the Encode module to help you to convert to the required UTF-8 format. For example: + +

    +

    +

    +    use Encode 'decode';
    +
    +

    +

    +

    +    my $string = 'some string with koi8-r characters';
    +       $string = decode('koi8-r', $string); # koi8-r to utf8
    +
    +

    +

    +Alternatively you can read data from an encoded file and convert it to UTF-8 as you read it in: + +

    +

    +

    +    my $file = 'unicode_koi8r.txt';
    +    open FH, '<:encoding(koi8-r)', $file  or die "Couldn't open $file: $!\n";
    +
    +

    +

    +

    +    my $row = 0;
    +    while (<FH>) {
    +        # Data read in is now in utf8 format.
    +        chomp;
    +        $worksheet->write($row++, 0,  $_);
    +    }
    +
    +

    +

    +These methodologies are explained in more detail in perlunitut, perluniintro and perlunicode. + +

    +

    +See also the unicode_*.pl programs in the examples directory of the distro. +


    @@ -5782,7 +6109,7 @@

    COLOURS IN EXCEL


    -

    DATES IN EXCEL

    +

    DATES AND TIME IN EXCEL

    Dates and times in Excel are represented by real numbers, for example "Jan 1 2001 12:30 AM" is represented by the number 36892.521. @@ -6743,7 +7070,8 @@

    Additional Examples

         Advanced
         ========
    -    autofit.pl              Simuluate Excel's autofit for colums widths.
    +    autofilter.pl           Examples of worksheet autofilters.
    +    autofit.pl              Simulate Excel's autofit for column widths.
         bigfile.pl              Write past the 7MB limit with OLE::Storage_Lite.
         cgi.pl                  A simple CGI program.
         chess.pl                An example of formatting using properties.
    @@ -6794,9 +7122,9 @@ 

    Additional Examples

         Unicode
         =======
    -    unicode.pl              Simple example of using Unicode UTF16 strings.
    -    unicode_japan.pl        Write Japanese Unicode strings using UTF16.
    -    unicode_cyrillic.pl     Write Russian cyrillic strings using UTF8.
    +    unicode_utf16.pl        Simple example of using Unicode UTF16 strings.
    +    unicode_utf16_japan.pl  Write Japanese Unicode strings using UTF-16.
    +    unicode_cyrillic.pl     Write Russian cyrillic strings using UTF-8.
         unicode_list.pl         List the chars in a Unicode font.
         unicode_2022_jp.pl      Japanese: ISO-2022-JP to utf8 in perl 5.8.
         unicode_8859_11.pl      Thai:     ISO-8859_11 to utf8 in perl 5.8.
    @@ -6994,8 +7322,7 @@ 

    DIAGNOSTICS

    This error generally means that the Excel file has been corrupted. There are two likely causes of this: the file was FTPed in ASCII mode instead of -binary mode or else the file was created with UTF8 data returned by an XML -parser. See Warning about XML::Parser and Perl 5.6 for further details. +binary mode or else the file was created with UTF-8 data returned by an XML parser. See Warning about XML::Parser and perl 5.6 for further details.

    @@ -7150,8 +7477,8 @@

    WRITING EXCEL FILES

  • Spreadsheet::WriteExcel::FromXML

    This module allows you to turn a simple XML file into an Excel file using -Spreadsheet::WriteExcel as a backend. The format of the XML file is defined -by a supplied DTD: http://search.cpan.org/dist/Spreadsheet-WriteExcel-FromXML @@ -7286,25 +7613,24 @@

    READING EXCEL FILES


    -

    Warning about XML::Parser and Perl 5.6

    +

    Warning about XML::Parser and perl 5.6

    You must be careful when using Spreadsheet::WriteExcel in conjunction with -Perl 5.6 and XML::Parser (and other XML parsers) due to the fact that the -data returned by the parser is generally in UTF8 format. +perl 5.6 and XML::Parser (and other XML parsers) due to the fact that the +data returned by the parser is generally in UTF-8 format.

    -When UTF8 strings are added to Spreadsheet::WriteExcel's internal data it -causes the generated Excel file to become corrupt. +When UTF-8 strings are added to Spreadsheet::WriteExcel's internal data it causes the +generated Excel file to become corrupt.

    -Note, this doesn't affect Perl 5.005 (which doesn't try to handle UTF8) or -5.8 (which handles it correctly). +Note, this doesn't affect perl 5.005 (which doesn't try to handle UTF-8) or 5.8 (which handles it correctly).

    -To avoid this problem you should upgrade to Perl 5.8, if possible, or else +To avoid this problem you should upgrade to perl 5.8, if possible, or else you should convert the output data from XML::Parser to ASCII or ISO-8859-1 using one of the following methods: @@ -7328,8 +7654,8 @@

    BUGS

    -XML and UTF8 data on Perl 5.6 can cause Excel files created by -Spreadsheet::WriteExcel to become corrupt. See Warning about XML::Parser and Perl 5.6 for further details. +XML and UTF-8 data on perl 5.6 can cause Excel files created by Spreadsheet::WriteExcel +to become corrupt. See Warning about XML::Parser and perl 5.6 for further details.

    @@ -7352,20 +7678,11 @@

    BUGS

    -OpenOffice.org: Images are not displayed. Some formatting is not displayed -correctly. - -

    -

    -Gnumeric: Images are not displayed. Some formatting is not displayed -correctly. URLs are not displayed as links. Page setup may cause Gnumeric -to crash. +OpenOffice.org: No known issues in this release.

    -The lack of a portable way of writing a little-endian 64 bit IEEE float. -There is beta code available to fix this. Let me know if you wish to test -it on your platform. +Gnumeric: No known issues in this release.

    @@ -7380,8 +7697,7 @@

    TO DO

    Also, here are some of the most requested features that probably won't get @@ -7450,7 +7766,7 @@

    MAILING LIST


    -

    DONATATIONS

    +

    DONATIONS

    If you'd care to donate to the Spreadsheet::WriteExcel project, you can do so via PayPal: @@ -7503,7 +7819,7 @@

    SEE ALSO

    -"Reading and writing Excel files with Perl" by Teodor Zlatanov, atIBM +"Reading and writing Excel files with Perl" by Teodor Zlatanov, at IBM developerWorks: http://www-106.ibm.com/developerworks/library/l-pexcel/ @@ -7530,7 +7846,7 @@

    SEE ALSO


    -

    ACKNOWLEDGEMENTS

    +

    ACKNOWLEDGMENTS

    The following people contributed to the debugging and testing of Spreadsheet::WriteExcel: @@ -7566,6 +7882,11 @@

    ACKNOWLEDGEMENTS

    Many thanks to Ron McKelvey, Ronzo Consulting for Siemens, who sponsored the development of the formula caching routines. +

    +

    +Many thanks to Cassens Transport who sponsored the development of the +embedded charts and autofilters. +

    Additional thanks to Takanori Kawai for translating the documentation into @@ -7617,31 +7938,37 @@

    DISCLAIMER OF WARRANTY


    -

    AUTHOR

    +

    LICENSE

    -John McNamara jmcnamara@cpan.org +Either the Perl Artistic Licence http://dev.perl.org/licenses/artistic.html +or the GPL http://www.opensource.org/licenses/gpl-license.php +

    -

    -    I was nineteen when I came to town, they called it the Summer of Love
    -    They were burning babies, burning flags. The hawks against the doves
    -    I took a job in the steamie down on Cauldrum Street
    -    And I fell in love with a laundry girl who was working next to me
    -
    +
    +

    AUTHOR

    +

    +John McNamara jmcnamara@cpan.org +

    -    Oh she was a rare thing, fine as a bee's wing
    -    So fine a breath of wind might blow her away
    -    She was a lost child, oh she was running wild
    -    She said "As long as there's no price on love, I'll stay.
    -    And you wouldn't want me any other way"
    +    This, no song of an ingenue,
    +    This, no ballad of innocence;
    +    This, the rhyme of a lady who
    +    Followed ever her natural bents.
    +    This, a solo of sapience,
    +    This, a chantey of sophistry,
    +    This, the sum of experiments,
    +    I loved them until they loved me.
     

    -        -- Richard Thompson
    +        -- Dorothy Parker
     

    diff --git a/examples/README b/examples/README index 9aa50d0..ee41600 100644 --- a/examples/README +++ b/examples/README @@ -14,7 +14,8 @@ stats.pl Basic formulas and functions. Advanced ======== -autofit.pl Simuluate Excel's autofit for colums widths. +autofilter.pl Examples of worksheet autofilters. +autofit.pl Simulate Excel's autofit for column widths. bigfile.pl Write past the 7MB limit with OLE::Storage_Lite. cgi.pl A simple CGI program. chess.pl An example of formatting using properties. @@ -63,9 +64,9 @@ write_to_scalar.pl Example of writing an Excel file to a Perl scalar. Unicode ======= -unicode.pl Simple example of using Unicode UTF16 strings. -unicode_japan.pl Write Japanese Unicode strings using UTF16. -unicode_cyrillic.pl Write Russian cyrillic strings using UTF8. +unicode_utf16.pl Simple example of using Unicode UTF16 strings. +unicode_utf16_japan.pl Write Japanese Unicode strings using UTF-16. +unicode_cyrillic.pl Write Russian cyrillic strings using UTF-8. unicode_list.pl List the chars in a Unicode font. unicode_2022_jp.pl Japanese: ISO-2022-JP to utf8 in perl 5.8. unicode_8859_11.pl Thai: ISO-8859_11 to utf8 in perl 5.8. @@ -91,4 +92,4 @@ Developer ========= convertA1.pl Helper functions for dealing with A1 notation. function_locale.pl Add non-English function names to Formula.pm. -writeA1.pl Example of how to extend the module. +writeA1.pl Example of how to extend the module. \ No newline at end of file diff --git a/examples/autofilter.pl b/examples/autofilter.pl new file mode 100644 index 0000000..5917df8 --- /dev/null +++ b/examples/autofilter.pl @@ -0,0 +1,266 @@ +#!/usr/bin/perl -w + +####################################################################### +# +# Example of how to create autofilters with Spreadsheet::WriteExcel. +# +# reverse('©'), September 2007, John McNamara, jmcnamara@cpan.org +# + +use strict; +use Spreadsheet::WriteExcel; + +my $workbook = Spreadsheet::WriteExcel->new('autofilter.xls'); + +die "Couldn't create new Excel file: $!.\n" unless defined $workbook; + +my $worksheet1 = $workbook->add_worksheet(); +my $worksheet2 = $workbook->add_worksheet(); +my $worksheet3 = $workbook->add_worksheet(); +my $worksheet4 = $workbook->add_worksheet(); +my $worksheet5 = $workbook->add_worksheet(); +my $worksheet6 = $workbook->add_worksheet(); + +my $bold = $workbook->add_format(bold => 1); + + +# Extract the data embedded at the end of this file. +my @headings = split ' ', ; +my @data; +push @data, [split] while ; + + +# Set up several sheets with the same data. +for my $worksheet ($workbook->sheets()) { + $worksheet->set_column('A:D', 12); + $worksheet->set_row(0, 20, $bold); + $worksheet->write('A1', \@headings); +} + + +############################################################################### +# +# Example 1. Autofilter without conditions. +# + +$worksheet1->autofilter('A1:D51'); +$worksheet1->write('A2', [[@data]]); + + +############################################################################### +# +# +# Example 2. Autofilter with a filter condition in the first column. +# + +# The range in this example is the same as above but in row-column notation. +$worksheet2->autofilter(0, 0, 50, 3); + +# The placeholder "Region" in the filter is ignored and can be any string +# that adds clarity to the expression. +# +$worksheet2->filter_column(0, 'Region eq East'); + +# +# Hide the rows that don't match the filter criteria. +# +my $row = 1; + +for my $row_data (@data) { + my $region = $row_data->[0]; + + if ($region eq 'East') { + # Row is visible. + } + else { + # Hide row. + $worksheet2->set_row($row, undef, undef, 1); + } + + $worksheet2->write($row++, 0, $row_data); +} + + +############################################################################### +# +# +# Example 3. Autofilter with a dual filter condition in one of the columns. +# + +$worksheet3->autofilter('A1:D51'); + +$worksheet3->filter_column('A', 'x eq East or x eq South'); + +# +# Hide the rows that don't match the filter criteria. +# +$row = 1; + +for my $row_data (@data) { + my $region = $row_data->[0]; + + if ($region eq 'East' or $region eq 'South') { + # Row is visible. + } + else { + # Hide row. + $worksheet3->set_row($row, undef, undef, 1); + } + + $worksheet3->write($row++, 0, $row_data); +} + + +############################################################################### +# +# +# Example 4. Autofilter with filter conditions in two columns. +# + +$worksheet4->autofilter('A1:D51'); + +$worksheet4->filter_column('A', 'x eq East'); +$worksheet4->filter_column('C', 'x > 3000 and x < 8000' ); + +# +# Hide the rows that don't match the filter criteria. +# +$row = 1; + +for my $row_data (@data) { + my $region = $row_data->[0]; + my $volume = $row_data->[2]; + + if ($region eq 'East' and + $volume > 3000 and $volume < 8000 + ) + { + # Row is visible. + } + else { + # Hide row. + $worksheet4->set_row($row, undef, undef, 1); + } + + $worksheet4->write($row++, 0, $row_data); +} + + +############################################################################### +# +# +# Example 5. Autofilter with filter for blanks. +# + +# Create a blank cell in our test data. +$data[5]->[0] = ''; + + +$worksheet5->autofilter('A1:D51'); +$worksheet5->filter_column('A', 'x == Blanks'); + +# +# Hide the rows that don't match the filter criteria. +# +$row = 1; + +for my $row_data (@data) { + my $region = $row_data->[0]; + + if ($region eq '') + { + # Row is visible. + } + else { + # Hide row. + $worksheet5->set_row($row, undef, undef, 1); + } + + $worksheet5->write($row++, 0, $row_data); +} + + +############################################################################### +# +# +# Example 6. Autofilter with filter for non-blanks. +# + + +$worksheet6->autofilter('A1:D51'); +$worksheet6->filter_column('A', 'x == NonBlanks'); + +# +# Hide the rows that don't match the filter criteria. +# +$row = 1; + +for my $row_data (@data) { + my $region = $row_data->[0]; + + if ($region ne '') + { + # Row is visible. + } + else { + # Hide row. + $worksheet6->set_row($row, undef, undef, 1); + } + + $worksheet6->write($row++, 0, $row_data); +} + + + +__DATA__ +Region Item Volume Month +East Apple 9000 July +East Apple 5000 July +South Orange 9000 September +North Apple 2000 November +West Apple 9000 November +South Pear 7000 October +North Pear 9000 August +West Orange 1000 December +West Grape 1000 November +South Pear 10000 April +West Grape 6000 January +South Orange 3000 May +North Apple 3000 December +South Apple 7000 February +West Grape 1000 December +East Grape 8000 February +South Grape 10000 June +West Pear 7000 December +South Apple 2000 October +East Grape 7000 December +North Grape 6000 April +East Pear 8000 February +North Apple 7000 August +North Orange 7000 July +North Apple 6000 June +South Grape 8000 September +West Apple 3000 October +South Orange 10000 November +West Grape 4000 July +North Orange 5000 August +East Orange 1000 November +East Orange 4000 October +North Grape 5000 August +East Apple 1000 December +South Apple 10000 March +East Grape 7000 October +West Grape 1000 September +East Grape 10000 October +South Orange 8000 March +North Apple 4000 July +South Orange 5000 July +West Apple 4000 June +East Apple 5000 April +North Pear 3000 August +East Grape 9000 November +North Orange 8000 October +East Apple 10000 June +South Pear 1000 December +North Grape 10000 July +East Grape 6000 February diff --git a/examples/bug_report.pl b/examples/bug_report.pl index df3138d..370eaee 100644 --- a/examples/bug_report.pl +++ b/examples/bug_report.pl @@ -105,10 +105,13 @@ my @modules = qw( Spreadsheet::WriteExcel + Spreadsheet::ParseExcel + OLE::Storage_Lite Parse::RecDescent File::Temp - OLE::Storage_Lite - IO::Stringy + Digest::MD4 + Digest::Perl::MD4 + Digest::MD5 ); diff --git a/examples/demo.pl b/examples/demo.pl index fecfd5e..1edf1f5 100644 --- a/examples/demo.pl +++ b/examples/demo.pl @@ -62,7 +62,7 @@ $worksheet->write('A3', "Formatted text"); $worksheet->write('B3', "Hello Excel", $text_format); $worksheet->write('A4', "Unicode text"); -$worksheet->write_unicode('B4', $unicode); +$worksheet->write_utf16be_string('B4', $unicode); ####################################################################### # @@ -102,7 +102,7 @@ # Images # $worksheet->write('A10', "Images"); -$worksheet->insert_bitmap('B10', 'republic.bmp', 16, 8); +$worksheet->insert_image('B10', 'republic.png', 16, 8); ####################################################################### diff --git a/examples/formats.pl b/examples/formats.pl index dd12387..bba616c 100644 --- a/examples/formats.pl +++ b/examples/formats.pl @@ -295,7 +295,7 @@ sub borders { $worksheet->write(0, 5, "The style is highlighted in red for ", $heading); $worksheet->write(1, 5, "emphasis, the default color is black.", $heading); - for my $i (0..7){ + for my $i (0..13){ my $format = $workbook->add_format(); $format->set_border($i); $format->set_border_color('red'); @@ -307,9 +307,10 @@ sub borders { $worksheet->write((2*($i+1)), 3, "Border", $format); } - $worksheet->write(18, 0, "Diag type", $heading); - $worksheet->write(18, 1, "Index", $heading); - $worksheet->write(18, 3, "Style", $heading); + $worksheet->write(30, 0, "Diag type", $heading); + $worksheet->write(30, 1, "Index", $heading); + $worksheet->write(30, 3, "Style", $heading); + $worksheet->write(30, 5, "Diagonal Boder styles", $heading); for my $i (1..3){ my $format = $workbook->add_format(); @@ -318,10 +319,10 @@ sub borders { $format->set_diag_color('red'); $format->set_align('center'); - $worksheet->write((2*($i+9)), 0, $i, $center); - $worksheet->write((2*($i+9)), 1, sprintf("0x%02X", 1), $center); + $worksheet->write((2*($i+15)), 0, $i, $center); + $worksheet->write((2*($i+15)), 1, sprintf("0x%02X", $i), $center); - $worksheet->write((2*($i+9)), 3, "Border", $format); + $worksheet->write((2*($i+15)), 3, "Border", $format); } } diff --git a/examples/hide_sheet.pl b/examples/hide_sheet.pl index b821b1b..5a1dd63 100644 --- a/examples/hide_sheet.pl +++ b/examples/hide_sheet.pl @@ -18,9 +18,9 @@ # Sheet2 won't be visible until it is unhidden in Excel. $worksheet2->hide(); -$worksheet1->write(0, 0, "Hello"); -$worksheet2->write(0, 0, "Hello"); -$worksheet3->write(0, 0, "Hello"); +$worksheet1->write(0, 0, 'Sheet2 is hidden'); +$worksheet2->write(0, 0, 'How did you find me?'); +$worksheet3->write(0, 0, 'Sheet2 is hidden'); __END__ diff --git a/examples/images.pl b/examples/images.pl index a05cbd4..be5e91a 100644 --- a/examples/images.pl +++ b/examples/images.pl @@ -3,7 +3,7 @@ ####################################################################### # # Example of how to insert images into an Excel worksheet using the -# Spreadsheet::WriteExcel insert_bitmap() method. +# Spreadsheet::WriteExcel insert_image() method. # # reverse('©'), October 2001, John McNamara, jmcnamara@cpan.org # @@ -20,22 +20,22 @@ # Insert a basic image $worksheet1->write('A10', "Image inserted into worksheet."); -$worksheet1->insert_bitmap('A1', 'republic.bmp'); +$worksheet1->insert_image('A1', 'republic.png'); # Insert an image with an offset $worksheet2->write('A10', "Image inserted with an offset."); -$worksheet2->insert_bitmap('A1', 'republic.bmp', 32, 10); +$worksheet2->insert_image('A1', 'republic.png', 32, 10); # Insert a scaled image $worksheet3->write('A10', "Image scaled: width x 2, height x 0.8."); -$worksheet3->insert_bitmap('A1', 'republic.bmp', 0, 0, 2, 0.8); +$worksheet3->insert_image('A1', 'republic.png', 0, 0, 2, 0.8); # Insert an image over varied column and row sizes # This does not require any additional work # Set the cols and row sizes -# NOTE: you must do this before you call insert_bitmap() +# NOTE: you must do this before you call insert_image() $worksheet4->set_column('A:A', 5); $worksheet4->set_column('B:B', undef, undef, 1); # Hidden $worksheet4->set_column('C:D', 10); @@ -43,7 +43,7 @@ $worksheet4->set_row(3, 5); $worksheet4->write('A10', "Image inserted over scaled rows and columns."); -$worksheet4->insert_bitmap('A1', 'republic.bmp'); +$worksheet4->insert_image('A1', 'republic.png'); diff --git a/examples/panes.pl b/examples/panes.pl index a444368..5e9548e 100644 --- a/examples/panes.pl +++ b/examples/panes.pl @@ -17,18 +17,17 @@ my $worksheet3 = $workbook->add_worksheet('Panes 3'); my $worksheet4 = $workbook->add_worksheet('Panes 4'); -# Frozen panes +# Freeze panes $worksheet1->freeze_panes(1, 0); # 1 row + $worksheet2->freeze_panes(0, 1); # 1 column $worksheet3->freeze_panes(1, 1); # 1 row and column -# Un-frozen panes. The divisions must be specified in terms of row and column -# dimensions. The default row height is 12.75 and the default column width -# is 8.43 +# Split panes. +# The divisions must be specified in terms of row and column dimensions. +# The default row height is 12.75 and the default column width is 8.43 # -$worksheet4->thaw_panes(12.75, 8.43, 1, 1); # 1 row and column - - +$worksheet4->split_panes(12.75, 8.43, 1, 1); # 1 row and column ####################################################################### diff --git a/examples/republic.bmp b/examples/republic.bmp deleted file mode 100644 index 7b761af..0000000 Binary files a/examples/republic.bmp and /dev/null differ diff --git a/examples/republic.png b/examples/republic.png new file mode 100644 index 0000000..68c036a Binary files /dev/null and b/examples/republic.png differ diff --git a/examples/unicode_2022_jp.pl b/examples/unicode_2022_jp.pl index f40fb19..60951ae 100644 --- a/examples/unicode_2022_jp.pl +++ b/examples/unicode_2022_jp.pl @@ -14,8 +14,8 @@ # Perl 5.8 or later is required for proper utf8 handling. For older perl -# versions you should use UTF16 and the write_unicode() method. -# See the write_unicode section of the Spreadsheet::WriteExcel docs. +# versions you should use UTF16 and the write_utf16be_string() method. +# See the write_utf16be_string section of the Spreadsheet::WriteExcel docs. # require 5.008; diff --git a/examples/unicode_8859_11.pl b/examples/unicode_8859_11.pl index 2e2183e..7ccd6b2 100644 --- a/examples/unicode_8859_11.pl +++ b/examples/unicode_8859_11.pl @@ -14,8 +14,8 @@ # Perl 5.8 or later is required for proper utf8 handling. For older perl -# versions you should use UTF16 and the write_unicode() method. -# See the write_unicode section of the Spreadsheet::WriteExcel docs. +# versions you should use UTF16 and the write_utf16be_string() method. +# See the write_utf16be_string section of the Spreadsheet::WriteExcel docs. # require 5.008; diff --git a/examples/unicode_8859_7.pl b/examples/unicode_8859_7.pl index 9c849b8..c126e15 100644 --- a/examples/unicode_8859_7.pl +++ b/examples/unicode_8859_7.pl @@ -14,8 +14,8 @@ # Perl 5.8 or later is required for proper utf8 handling. For older perl -# versions you should use UTF16 and the write_unicode() method. -# See the write_unicode section of the Spreadsheet::WriteExcel docs. +# versions you should use UTF16 and the write_utf16be_string() method. +# See the write_utf16be_string section of the Spreadsheet::WriteExcel docs. # require 5.008; diff --git a/examples/unicode_big5.pl b/examples/unicode_big5.pl index e00d2fa..4a680aa 100644 --- a/examples/unicode_big5.pl +++ b/examples/unicode_big5.pl @@ -14,8 +14,8 @@ # Perl 5.8 or later is required for proper utf8 handling. For older perl -# versions you should use UTF16 and the write_unicode() method. -# See the write_unicode section of the Spreadsheet::WriteExcel docs. +# versions you should use UTF16 and the write_utf16be_string() method. +# See the write_utf16be_string section of the Spreadsheet::WriteExcel docs. # require 5.008; diff --git a/examples/unicode_cp1251.pl b/examples/unicode_cp1251.pl index 86ea063..94ae8f0 100644 --- a/examples/unicode_cp1251.pl +++ b/examples/unicode_cp1251.pl @@ -14,8 +14,8 @@ # Perl 5.8 or later is required for proper utf8 handling. For older perl -# versions you should use UTF16 and the write_unicode() method. -# See the write_unicode section of the Spreadsheet::WriteExcel docs. +# versions you should use UTF16 and the write_utf16be_string() method. +# See the write_utf16be_string section of the Spreadsheet::WriteExcel docs. # require 5.008; diff --git a/examples/unicode_cp1256.pl b/examples/unicode_cp1256.pl index b04f385..543c36d 100644 --- a/examples/unicode_cp1256.pl +++ b/examples/unicode_cp1256.pl @@ -14,8 +14,8 @@ # Perl 5.8 or later is required for proper utf8 handling. For older perl -# versions you should use UTF16 and the write_unicode() method. -# See the write_unicode section of the Spreadsheet::WriteExcel docs. +# versions you should use UTF16 and the write_utf16be_string() method. +# See the write_utf16be_string section of the Spreadsheet::WriteExcel docs. # require 5.008; diff --git a/examples/unicode_cyrillic.pl b/examples/unicode_cyrillic.pl index eef131e..59124db 100644 --- a/examples/unicode_cyrillic.pl +++ b/examples/unicode_cyrillic.pl @@ -11,8 +11,8 @@ # Perl 5.8 or later is required for proper utf8 handling. For older perl -# versions you should use UTF16 and the write_unicode() method. -# See the write_unicode section of the Spreadsheet::WriteExcel docs. +# versions you should use UTF16 and the write_utf16be_string() method. +# See the write_utf16be_string section of the Spreadsheet::WriteExcel docs. # require 5.008; diff --git a/examples/unicode_koi8r.pl b/examples/unicode_koi8r.pl index ec450f2..c071fdf 100644 --- a/examples/unicode_koi8r.pl +++ b/examples/unicode_koi8r.pl @@ -14,8 +14,8 @@ # Perl 5.8 or later is required for proper utf8 handling. For older perl -# versions you should use UTF16 and the write_unicode() method. -# See the write_unicode section of the Spreadsheet::WriteExcel docs. +# versions you should use UTF16 and the write_utf16be_string() method. +# See the write_utf16be_string section of the Spreadsheet::WriteExcel docs. # require 5.008; diff --git a/examples/unicode_list.pl b/examples/unicode_list.pl index 89f7a04..656e1a3 100644 --- a/examples/unicode_list.pl +++ b/examples/unicode_list.pl @@ -37,7 +37,7 @@ sprintf('0x%04X', $char), $courier); } else { - $worksheet->write_unicode($row, $col, + $worksheet->write_utf16be_string($row, $col, pack('n', $char++), $uni_font); } } diff --git a/examples/unicode_polish_utf8.pl b/examples/unicode_polish_utf8.pl index f55c26d..1eeb2e5 100644 --- a/examples/unicode_polish_utf8.pl +++ b/examples/unicode_polish_utf8.pl @@ -14,8 +14,8 @@ # Perl 5.8 or later is required for proper utf8 handling. For older perl -# versions you should use UTF16 and the write_unicode() method. -# See the write_unicode section of the Spreadsheet::WriteExcel docs. +# versions you should use UTF16 and the write_utf16be_string() method. +# See the write_utf16be_string section of the Spreadsheet::WriteExcel docs. # require 5.008; diff --git a/examples/unicode_shift_jis.pl b/examples/unicode_shift_jis.pl index 60049b4..5a8aed2 100644 --- a/examples/unicode_shift_jis.pl +++ b/examples/unicode_shift_jis.pl @@ -14,8 +14,8 @@ # Perl 5.8 or later is required for proper utf8 handling. For older perl -# versions you should use UTF16 and the write_unicode() method. -# See the write_unicode section of the Spreadsheet::WriteExcel docs. +# versions you should use UTF16 and the write_utf16be_string() method. +# See the write_utf16be_string section of the Spreadsheet::WriteExcel docs. # require 5.008; diff --git a/examples/unicode.pl b/examples/unicode_utf16.pl similarity index 76% rename from examples/unicode.pl rename to examples/unicode_utf16.pl index 4fd19f8..8da3634 100644 --- a/examples/unicode.pl +++ b/examples/unicode_utf16.pl @@ -15,7 +15,7 @@ use Spreadsheet::WriteExcel; -my $workbook = Spreadsheet::WriteExcel->new('unicode.xls'); +my $workbook = Spreadsheet::WriteExcel->new('unicode_utf16.xls'); my $worksheet = $workbook->add_worksheet(); @@ -23,17 +23,17 @@ my $smiley = pack "n", 0x263a; my $big_font = $workbook->add_format(size => 40); -$worksheet->write_unicode('A3', $smiley, $big_font); +$worksheet->write_utf16be_string('A3', $smiley, $big_font); # Write a phrase in Cyrillic my $uni_str = pack "H*", "042d0442043e002004440440043004370430002004". "3d043000200440044304410441043a043e043c0021"; -$worksheet->write_unicode('A5', $uni_str); +$worksheet->write_utf16be_string('A5', $uni_str); -$worksheet->write_unicode('A7', pack "H*", "0074006500730074"); +$worksheet->write_utf16be_string('A7', pack "H*", "0074006500730074"); diff --git a/examples/unicode_japan.pl b/examples/unicode_utf16_japan.pl similarity index 79% rename from examples/unicode_japan.pl rename to examples/unicode_utf16_japan.pl index 8004190..860bc24 100644 --- a/examples/unicode_japan.pl +++ b/examples/unicode_utf16_japan.pl @@ -19,7 +19,7 @@ use Spreadsheet::WriteExcel; -my $workbook = Spreadsheet::WriteExcel->new('japan.xls'); +my $workbook = Spreadsheet::WriteExcel->new('unicode_utf16_japan.xls'); my $worksheet = $workbook->add_worksheet(); @@ -34,9 +34,9 @@ -$worksheet->write_unicode('A1', $kanji, $uni_font); -$worksheet->write_unicode('A2', $katakana, $uni_font); -$worksheet->write_unicode('A3', $hiragana, $uni_font); +$worksheet->write_utf16be_string('A1', $kanji, $uni_font); +$worksheet->write_utf16be_string('A2', $katakana, $uni_font); +$worksheet->write_utf16be_string('A3', $hiragana, $uni_font); $worksheet->write('B1', 'Kanji'); diff --git a/lib/Spreadsheet/WriteExcel.pm b/lib/Spreadsheet/WriteExcel.pm index aa7a052..5ac4171 100644 --- a/lib/Spreadsheet/WriteExcel.pm +++ b/lib/Spreadsheet/WriteExcel.pm @@ -6,7 +6,7 @@ package Spreadsheet::WriteExcel; # # Spreadsheet::WriteExcel - Write to a cross-platform Excel binary file. # -# Copyright 2000-2006, John McNamara, jmcnamara@cpan.org +# Copyright 2000-2007, John McNamara, jmcnamara@cpan.org # # Documentation after __END__ # @@ -21,7 +21,7 @@ use Spreadsheet::WriteExcel::Workbook; use vars qw($VERSION @ISA); @ISA = qw(Spreadsheet::WriteExcel::Workbook Exporter); -$VERSION = '2.18'; # Action Packed, 18th January 2007. +$VERSION = '2.20'; # La Bruni. @@ -63,7 +63,7 @@ Spreadsheet::WriteExcel - Write to a cross-platform Excel binary file. =head1 VERSION -This document refers to version 2.18 of Spreadsheet::WriteExcel, released January 18, 2007. +This document refers to version 2.20 of Spreadsheet::WriteExcel, released October 6, 2007. @@ -129,7 +129,7 @@ Like this: $worksheet = $workbook->add_worksheet(); # Step 2 $worksheet->write('A1', "Hi Excel!"); # Step 3 -This will create an Excel file called C with a single worksheet and the text C<"Hi Excel!"> in the relevant cell. And that's it. Okay, so there is actually a zeroth step as well, but C goes without saying. There are also more than 70 examples that come with the distribution and which you can use to get you started. See L. +This will create an Excel file called C with a single worksheet and the text C<"Hi Excel!"> in the relevant cell. And that's it. Okay, so there is actually a zeroth step as well, but C goes without saying. There are also more than 80 examples that come with the distribution and which you can use to get you started. See L. Those of you who read the instructions first and assemble the furniture afterwards will know how to proceed. ;-) @@ -237,7 +237,7 @@ For example here is a way to write an Excel file to a scalar with C: See also the C and C programs in the C directory of the distro. -B C: An Excel file is comprised of binary data. Therefore, if you are using a filehandle you should ensure that you C it prior to passing it to C.You should do this regardless of whether you are on a Windows platform or not. This applies especially to users of perl 5.8 on systems where utf8 is likely to be in operation such as RedHat Linux 9. If your program, either intentionally or not, writes UTF8 data to a filehandle that is passed to C it will corrupt the Excel file that is created. +B C: An Excel file is comprised of binary data. Therefore, if you are using a filehandle you should ensure that you C it prior to passing it to C.You should do this regardless of whether you are on a Windows platform or not. This applies especially to users of perl 5.8 on systems where C is likely to be in operation such as RedHat Linux 9. If your program, either intentionally or not, writes C data to a filehandle that is passed to C it will corrupt the Excel file that is created. You don't have to worry about C if you are using filenames instead of filehandles. Spreadsheet::WriteExcel performs the C internally when it converts the filename to a filehandle. For more information about C see C and C in the main Perl documentation. @@ -326,11 +326,11 @@ If C<$sheetname> is not specified the default Excel convention will be followed, The worksheet name must be a valid Excel worksheet name, i.e. it cannot contain any of the following characters, C<[ ] : * ? / \> and it must be less than 32 characters. In addition, you cannot use the same, case insensitive, C<$sheetname> for more than one worksheet. -On systems with C and later the C method will also handle strings in Perl's C format. +On systems with C and later the C method will also handle strings in format. $worksheet = $workbook->add_worksheet("\x{263a}"); # Smiley -On earlier Perl systems your can specify UTF-16BE worksheet names using an additional encoding parameter: +On earlier Perl systems your can specify C worksheet names using an additional encoding parameter: my $name = pack "n", 0x263a; $worksheet = $workbook->add_worksheet($name, 1); # Smiley @@ -448,7 +448,7 @@ Excel stores dates as real numbers where the integer part stores the number of d Spreadsheet::WriteExcel stores dates in the 1900 format by default. If you wish to change this you can call the C workbook method. You can query the current value by calling the C workbook method. This returns 0 for 1900 and 1 for 1904. -See also L for more information about working with Excel's date system. +See also L for more information about working with Excel's date system. In general you probably won't need to use C. @@ -481,8 +481,8 @@ The following methods are available through a new worksheet: write() write_number() write_string() - write_unicode() - write_unicode_le() + write_utf16be_string() + write_utf16le_string() keep_leading_zeros() write_blank() write_row() @@ -496,7 +496,7 @@ The following methods are available through a new worksheet: write_comment() show_comments() add_write_handler() - insert_bitmap() + insert_image() get_name() activate() select() @@ -508,12 +508,13 @@ The following methods are available through a new worksheet: set_column() outline_settings() freeze_panes() - thaw_panes() + split_panes() merge_range() set_zoom() right_to_left() hide_zero() set_tab_color() + autofilter() @@ -632,7 +633,7 @@ One problem with the C method is that occasionally data looks like a nu You can also add your own data handlers to the C method using C. -On systems with C and later the C method will also handle Unicode strings in Perl's C format. +On systems with C and later the C method will also handle Unicode strings in C format. The C methods return: @@ -669,7 +670,7 @@ The maximum string size is 32767 characters. However the maximum string segment The C<$format> parameter is optional. -On systems with C and later the C method will also handle strings in Perl's C format. With older perls you can also write Unicode in C format via the C method. See also the C programs in the examples directory of the distro. +On systems with C and later the C method will also handle strings in C format. With older perls you can also write Unicode in C format via the C method. See also the C programs in the examples directory of the distro. In general it is sufficient to use the C method. However, you may sometimes wish to use the C method to write data that looks like a number but that you don't want treated as a number. For example, zip codes or phone numbers: @@ -687,16 +688,12 @@ See also the note about L. -=head2 write_unicode($row, $column, $string, $format) +=head2 write_utf16be_string($row, $column, $string, $format) -This method is used to write Unicode strings to a cell in Excel. It is functionally the same as the C method except that the string should be in UTF-16 Unicode format. +This method is used to write C strings to a cell in Excel. It is functionally the same as the C method except that the string should be in C Unicode format. It is generally easier, when using Spreadsheet::WriteExcel, to write unicode strings in C format, see L. The C method is mainly of use in versions of perl prior to 5.8. -B: on systems with C and later the C and Cmethods will also handle strings in Perl's C format. With older perls you must use the C method. -The Unicode format required by Excel is UTF-16. Additionally C requires that the 16-bit characters are in big-endian order. This is generally referred to as UTF-16BE. To write UTF-16 strings in little-endian format use the C method. - - -The following is a simple example showing how to write some Unicode strings: +The following is a simple example showing how to write some Unicode strings in C format: #!/usr/bin/perl -w @@ -705,7 +702,7 @@ The following is a simple example showing how to write some Unicode strings: use Spreadsheet::WriteExcel; use Unicode::Map(); - my $workbook = Spreadsheet::WriteExcel->new('unicode.xls'); + my $workbook = Spreadsheet::WriteExcel->new('utf_16_be.xls'); my $worksheet = $workbook->add_worksheet(); # Increase the column width for clarity @@ -719,16 +716,16 @@ The following is a simple example showing how to write some Unicode strings: # Increase the font size for legibility. my $big_font = $workbook->add_format(size => 72); - $worksheet->write_unicode('A3', $smiley, $big_font); + $worksheet->write_utf16be_string('A3', $smiley, $big_font); # Write a phrase in Cyrillic using a hex-encoded string # - my $uni_str = pack "H*", "042d0442043e0020044404400430043704300020043d" . - "043000200440044304410441043a043e043c0021"; + my $str = pack "H*", "042d0442043e0020044404400430043704300020043d" . + "043000200440044304410441043a043e043c0021"; - $worksheet->write_unicode('A5', $uni_str); + $worksheet->write_utf16be_string('A5', $str); @@ -737,62 +734,28 @@ The following is a simple example showing how to write some Unicode strings: my $map = Unicode::Map->new("ISO-8859-1"); my $utf16 = $map->to_unicode("Hello world!"); - $worksheet->write_unicode('A7', $utf16); - - -The following is an example of creating an Excel file with some Japanese text. You will need to have a Unicode font installed, such as C, to view the results: - - - #!/usr/bin/perl -w - - - use strict; - use Spreadsheet::WriteExcel; - - - my $workbook = Spreadsheet::WriteExcel->new('unicode.xls'); - my $worksheet = $workbook->add_worksheet(); - - - # It is only required to specify a Unicode font via add_format() if - # you are using Excel 97. For Excel 2000+ the text will display - # with the default font (if you have Unicode fonts installed). - # - my $uni_font = $workbook->add_format(font => 'Arial Unicode MS'); - + $worksheet->write_utf16be_string('A7', $utf16); - my $kanji = pack 'n*', 0x65e5, 0x672c; - my $katakana = pack 'n*', 0xff86, 0xff8e, 0xff9d; - my $hiragana = pack 'n*', 0x306b, 0x307b, 0x3093; +You can convert ASCII encodings to the required C format using one of the many Unicode modules on CPAN. For example C and C: http://search.cpan.org/author/MSCHWARTZ/Unicode-Map/Map.pm and http://search.cpan.org/author/GAAS/Unicode-String/String.pm +For a full list of the Perl Unicode modules see: http://search.cpan.org/search?query=unicode&mode=all - $worksheet->write_unicode('A1', $kanji, $uni_font); - $worksheet->write_unicode('A2', $katakana, $uni_font); - $worksheet->write_unicode('A3', $hiragana, $uni_font); - - - $worksheet->write('B1', 'Kanji'); - $worksheet->write('B2', 'Katakana'); - $worksheet->write('B3', 'Hiragana'); - +C is the format most often returned by C modules that generate C. To write C strings in little-endian format use the C method below. -Note: You can convert ascii encodings to the required UTF-16BE format using one of the many Unicode modules on CPAN. For example C and C: http://search.cpan.org/author/MSCHWARTZ/Unicode-Map/Map.pm and http://search.cpan.org/author/GAAS/Unicode-String/String.pm - -For a full list of the Perl Unicode modules see: http://search.cpan.org/search?query=unicode&mode=all +The C method was previously called C. That, overly general, name is still supported but deprecated. See also the C programs in the examples directory of the distro. -=head2 write_unicode_le($row, $column, $string, $format) -This method is the same as C except that the string should be 16-bit characters in little-endian format. This is generally referred to as UTF-16LE. +=head2 write_utf16le_string($row, $column, $string, $format) -UTF-16 data can be changed from little-endian to big-endian format (and vice-versa) as follows: +This method is the same as C except that the string should be 16-bit characters in little-endian format. This is generally referred to as C. See L. - $utf16 = pack "n*", unpack "v*", $utf16; +C data can be changed from little-endian to big-endian format (and vice-versa) as follows: -Note, it is slightly faster to write little-endian data via write_unicode_le() than it is to write big-endian data via write_unicode(). + $utf16be = pack "n*", unpack "v*", $utf16le; @@ -1026,7 +989,7 @@ The following variations on the C<$date_string> parameter are permitted: Note that the C is required in all cases. -A date should always have a C<$format>, otherwise it will appear as a number, see L and L. Here is a typical example: +A date should always have a C<$format>, otherwise it will appear as a number, see L and L. Here is a typical example: my $date_format = $workbook->add_format(num_format => 'mm/dd/yy'); $worksheet->write_date_time('A1', '2004-05-13T23:20', $date_format); @@ -1260,8 +1223,6 @@ See also the C program in the C directory of the distro. =head2 write_comment($row, $column, $string, ...) -B This method is currently incompatible with C. You can use either method but not both in the same workbook. This will be fixed soon. - The C method is used to add a comment to a cell. A cell comment is indicated in Excel by a small red triangle in the upper right-hand corner of the cell. Moving the cursor over the red triangle will reveal the comment. The following example shows how to add a comment to a cell: @@ -1274,7 +1235,7 @@ As usual you can replace the C<$row> and C<$column> parameters with an C cel $worksheet->write ('C3', 'Hello'); $worksheet->write_comment('C3', 'This is a comment.'); -On systems with C and later the C method will also handle strings in Perl's C format. +On systems with C and later the C method will also handle strings in C format. $worksheet->write_comment('C3', "\x{263a}"); # Smiley $worksheet->write_comment('C4', 'Comment ca va?'); @@ -1305,13 +1266,13 @@ Most of these options are quite specific and in general the default comment beha =item Option: encoding -This option is used to indicate that the comment string is encoded as UTF-16BE. +This option is used to indicate that the comment string is encoded as C. my $comment = pack "n", 0x263a; # UTF-16BE Smiley symbol $worksheet->write_comment('C3', $comment, encoding => 1); -If you wish to use Unicode characters in the comment string then the preferred method is to use perl 5.8 and UTF-8 strings. +If you wish to use Unicode characters in the comment string then the preferred method is to use perl 5.8 and C strings, see L. =item Option: author @@ -1323,7 +1284,7 @@ This option is used to indicate who the author of the comment is. Excel displays =item Option: author_encoding -This option is used to indicate that the author string is encoded as UTF-16BE. +This option is used to indicate that the author string is encoded as C. =item Option: visible @@ -1446,7 +1407,7 @@ The C method take two arguments, C<$re>, a regular expressi (In the these examples the C operator is used to quote the regular expression strings, see L for more details). -The method is use as follows. say you wished to write 7 digit ID numbers as a string so that any leading zeros were preserved*, you could do something like the following: +The method is used as follows. say you wished to write 7 digit ID numbers as a string so that any leading zeros were preserved*, you could do something like the following: $worksheet->add_write_handler(qr/^\d{7}$/, \&write_my_id); @@ -1509,24 +1470,17 @@ See the C programs in the C directory for further e -=head2 insert_bitmap($row, $col, $filename, $x, $y, $scale_x, $scale_y) - -B This method is currently incompatible with C. You can use either method but not both in the same workbook. This will be fixed soon. - -B The images inserted using this method do not display in OpenOffice.org or Gnumeric. This is related to the previous note and will also be fixed soon. +=head2 insert_image($row, $col, $filename, $x, $y, $scale_x, $scale_y) +This method can be used to insert a image into a worksheet. The image can be in PNG or BMP format. The C<$x>, C<$y>, C<$scale_x> and C<$scale_y> parameters are optional. -This method can be used to insert a bitmap into a worksheet. The bitmap must be a 24 bit, true colour, bitmap. No other format is supported. The C<$x>, C<$y>, C<$scale_x> and C<$scale_y> parameters are optional. - - $worksheet1->insert_bitmap('A1', 'perl.bmp'); - $worksheet2->insert_bitmap('A1', '../images/perl.bmp'); - $worksheet3->insert_bitmap('A1', '.c:\images\perl.bmp'); - -Note: you must call C or C before C if you wish to change the default dimensions of any of the rows or columns that the images occupies. The height of a row can also change if you use a font that is larger than the default. This in turn will affect the scaling of your image. To avoid this you should explicitly set the height of the row using C if it contains a font size that will change the row height. + $worksheet1->insert_image('A1', 'perl.bmp'); + $worksheet2->insert_image('A1', '../images/perl.bmp'); + $worksheet3->insert_image('A1', '.c:\images\perl.bmp'); The parameters C<$x> and C<$y> can be used to specify an offset from the top left hand corner of the cell specified by C<$row> and C<$col>. The offset values are in pixels. - $worksheet1->insert_bitmap('A1', 'perl.bmp', 32, 10); + $worksheet1->insert_image('A1', 'perl.bmp', 32, 10); The default width of a cell is 63 pixels. The default height of a cell is 17 pixels. The pixels offsets can be calculated using the following relationships: @@ -1545,15 +1499,42 @@ The offsets can be greater than the width or height of the underlying cell. This The parameters C<$scale_x> and C<$scale_y> can be used to scale the inserted image horizontally and vertically: # Scale the inserted image: width x 2.0, height x 0.8 - $worksheet->insert_bitmap('A1', 'perl.bmp', 0, 0, 2, 0.8); + $worksheet->insert_image('A1', 'perl.bmp', 0, 0, 2, 0.8); -Note: although Excel allows you to import several graphics formats such as gif, jpeg, png and eps these are converted internally into a proprietary format. One of the few non-proprietary formats that Excel supports is 24 bit, true colour, bitmaps. Therefore if you wish to use images in any other format you must first use an external application such as the ImageMagick I utility to convert them to 24 bit bitmaps. +See also the C program in the C directory of the distro. - convert test.png test.bmp +Note: you must call C or C before C if you wish to change the default dimensions of any of the rows or columns that the image occupies. The height of a row can also change if you use a font that is larger than the default. This in turn will affect the scaling of your image. To avoid this you should explicitly set the height of the row using C if it contains a font size that will change the row height. -A later release will support the use of file handles and pre-encoded bitmap strings. -See also the C program in the C directory of the distro. +BMP images must be 24 bit, true colour, bitmaps. In general it is best to avoid BMP images since they aren't compressed. The older C method is still supported but deprecated. + + + + +=head2 embed_chart($row, $col, $filename, $x, $y, $scale_x, $scale_y) + +This method can be used to insert a chart into a worksheet. The chart must first be extracted from an existing Excel file. See the separate C documentation. + +Here is an example: + + $worksheet->embed_chart('B2', 'sales_chart.bin'); + +The C<$x>, C<$y>, C<$scale_x> and C<$scale_y> parameters are optional. + +The parameters C<$x> and C<$y> can be used to specify an offset from the top left hand corner of the cell specified by C<$row> and C<$col>. The offset values are in pixels. See the C method above for more information on sizes. + + $worksheet1->embed_chart('B2', 'sales_chart.bin', 3, 3); + +The parameters C<$scale_x> and C<$scale_y> can be used to scale the inserted image horizontally and vertically: + + # Scale the width by 120% and the height by 150% + $worksheet->embed_chart('B2', 'sales_chart.bin', 0, 0, 1.2, 1.5); + +The easiest way to calculate the required scaling is to create a test chart worksheet with Spreadsheet::WriteExcel. Then open the file, select the chart and drag the corner to get the required size. While holding down the mouse the scale of the resized chart is shown to the left of the formula bar. + +See also the example programs in the C directory of the distro. + +Note: you must call C or C before C if you wish to change the default dimensions of any of the rows or columns that the chart occupies. The height of a row can also change if you use a font that is larger than the default. This in turn will affect the scaling of your chart. To avoid this you should explicitly set the height of the row using C if it contains a font size that will change the row height. @@ -1566,6 +1547,8 @@ The C method is used to retrieve the name of a worksheet. For exampl print $sheet->get_name(); } +For reasons related to the design of Spreadsheet::WriteExcel and to the internals of Excel there is no C method. The only way to set the worksheet name is via the C method. + @@ -1579,7 +1562,9 @@ The C method is used to specify which worksheet is initially visible $worksheet3->activate(); -This is similar to the Excel VBA activate method. More than one worksheet can be selected via the C method, however only one worksheet can be active. The default value is the first worksheet. +This is similar to the Excel VBA activate method. More than one worksheet can be selected via the C method, see below, however only one worksheet can be active. + +The default active worksheet is the first worksheet. @@ -1592,7 +1577,7 @@ The C method is used to indicate that a worksheet is selected in a mul $worksheet2->select(); $worksheet3->select(); -A selected worksheet has its tab highlighted. Selecting worksheets is a way of grouping them together so that, for example, several worksheets could be printed in one go. A worksheet that has been activated via the C method will also appear as selected. You probably won't need to use the C method very often. +A selected worksheet has its tab highlighted. Selecting worksheets is a way of grouping them together so that, for example, several worksheets could be printed in one go. A worksheet that has been activated via the C method will also appear as selected. @@ -1601,11 +1586,14 @@ A selected worksheet has its tab highlighted. Selecting worksheets is a way of g The C method is used to hide a worksheet: - $worksheet->hide(); + $worksheet2->hide(); You may wish to hide a worksheet in order to avoid confusing a user with intermediate data or calculations. -A hidden worksheet can not be activated or selected so this method is mutually exclusive with the C and C methods. +A hidden worksheet can not be activated or selected so this method is mutually exclusive with the C and C methods. In addition, since the first worksheet will default to being the active worksheet, you cannot hide the first worksheet without activating another sheet: + + $worksheet2->activate(); + $worksheet1->hide(); @@ -1833,7 +1821,7 @@ See also the C program in the C directory of the distributio -=head2 thaw_panes($y, $x, $top_row, $left_col) +=head2 split_panes($y, $x, $top_row, $left_col) This method can be used to divide a worksheet into horizontal or vertical regions known as panes. This method is different from the C method in that the splits between the panes will be visible to the user and each pane will have its own scroll bars. @@ -1843,15 +1831,18 @@ You can set one of the C<$y> and C<$x> parameters as zero if you do not want eit Example: - $worksheet->thaw_panes(12.75, 0, 1, 0); # First row - $worksheet->thaw_panes(0, 8.43, 0, 1); # First column - $worksheet->thaw_panes(12.75, 8.43, 1, 1); # First row and column + $worksheet->split_panes(12.75, 0, 1, 0); # First row + $worksheet->split_panes(0, 8.43, 0, 1); # First column + $worksheet->split_panes(12.75, 8.43, 1, 1); # First row and column You cannot use A1 notation with this method. See also the C method and the C program in the C directory of the distribution. +Note: This C method was called C in older versions. The older name is still available for backwards compatiblity. + + =head2 merge_range($first_row, $first_col, $last_row, $last_col, $token, $format, $encoding) @@ -1876,11 +1867,11 @@ C writes its C<$token> argument using the worksheet C me Setting the C property of the format isn't required when you are using C. In fact using it will exclude the use of any other horizontal alignment option. -On systems with C and later the C method will also handle strings in Perl's C format. +On systems with C and later the C method will also handle strings in C format. $worksheet->merge_range('B3:D4', "\x{263a}", $format); # Smiley -On earlier Perl systems your can specify UTF-16BE worksheet names using an additional encoding parameter: +On earlier Perl systems your can specify C worksheet names using an additional encoding parameter: my $str = pack "n", 0x263a; $worksheet->merge_range('B3:D4', $str, $format, 1); # Smiley @@ -1938,6 +1929,101 @@ The C method is used to change the colour of the worksheet tab. See the C program in the examples directory of the distro. + + +=head2 autofilter($first_row, $first_col, $last_row, $last_col) + +This method allows an autofilter to be added to a worksheet. An autofilter is a way of adding drop down lists to the headers of a 2D range of worksheet data. This is turn allow users to filter the data based on simple criteria so that some data is highlighted and some is hidden. + +To add an autofilter to a worksheet: + + $worksheet->autofilter(0, 0, 10, 3); + $worksheet->autofilter('A1:D11'); # Same as above in A1 notation. + +Filter conditions can be applied using the C method. + +See the C program in the examples directory of the distro for a more detailed example. + + + + +=head2 filter_column($column, $expression) + + +The C method can be used to filter columns in a autofilter range based on simple conditions. + +B It isn't sufficient to just specify the filter condition. You must also hide any rows that don't match the filter condition. Rows are hidden using the C C parameter. C cannot do this automatically since it isn't part of the file format. See the C program in the examples directory of the distro for an example. + +The conditions for the filter are specified using simple expressions: + + $worksheet->filter_column('A', 'x > 2000'); + $worksheet->filter_column('B', 'x > 2000 and x < 5000'); + +The C<$column> parameter can either be a zero indexed column number or a string column name. + +The following operators are available: + + Operator Synonyms + == = eq =~ + != <> ne != + > + < + >= + <= + + and && + or || + +The operator synonyms are just syntactic sugar to make you more comfortable using the expressions. It is important to remember that the expressions will be interpreted by Excel and not by perl. + +An expression can comprise a single statement or two statements separated by the C and C operators. For example: + + 'x < 2000' + 'x > 2000' + 'x == 2000' + 'x > 2000 and x < 5000' + 'x == 2000 or x == 5000' + +Filtering of blank or non-blank data can be achieved by using a value of C or C in the expression: + + 'x == Blanks' + 'x == NonBlanks' + +Top 10 style filters can be specified using a expression like the following: + + Top|Bottom 1-500 Items|% + +For example: + + 'Top 10 Items' + 'Bottom 5 Items' + 'Top 25 %' + 'Bottom 50 %' + +Excel also allows some simple string matching operations: + + 'x =~ b*' # begins with b + 'x !~ b*' # doesn't begin with b + 'x =~ *b' # ends with b + 'x !~ *b' # doesn't end with b + 'x =~ *b*' # contains b + 'x !~ *b*' # doesn't contains b + +You can also use C<*> to match any character or number and C to match any single character or number. No other regular expression quantifier is supported by Excel's filters. Excel's regular expression characters can be escaped using C<~>. + +The placeholder variable C in the above examples can be replaced by any simple string. The actual placeholder name is ignored internally so the following are all equivalent: + + 'x < 2000' + 'col < 2000' + 'Price < 2000' + +Also, note that a filter condition can only be applied to a column in a range specified by the C Worksheet method. + +See the C program in the examples directory of the distro for a more detailed example. + + + + =head1 PAGE SET-UP METHODS Page set-up methods affect the way that a worksheet looks when it is printed. They control features such as page headers and footers and margins. These methods are really just standard worksheet methods. They are documented here in a separate section for the sake of clarity. @@ -2221,7 +2307,7 @@ The header and footer margins are independent of the top and bottom margins. Note, the header or footer string must be less than 255 characters. Strings longer than this will not be written and a warning will be generated. -On systems with C and later the C method can also handle Unicode strings in Perl's C format. +On systems with C and later the C method can also handle Unicode strings in C format. $worksheet->set_header("&C\x{263a}") @@ -2855,7 +2941,7 @@ Using format strings you can define very sophisticated formatting of numbers. $worksheet->write(14, 0, '01209', $format13); -The number system used for dates is described in L. +The number system used for dates is described in L. The colour format should have one of the following values: @@ -3169,17 +3255,64 @@ For further examples see the 'Patterns' worksheet created by formats.pl. Default state: Border is off Default action: Set border type 1 - Valid args: 0 No border - 1 Thin single border - 2 Medium single border - 3 Dashed border - 4 Dotted border - 5 Thick single border - 6 Double line border - 7 Hair border + Valid args: 0-13, See below. + +A cell border is comprised of a border on the bottom, top, left and right. These can be set to the same value using C or individually using the relevant method calls shown above. + +The following shows the border styles sorted by Spreadsheet::WriteExcel index number: + + Index Name Weight Style + ===== ============= ====== =========== + 0 None 0 + 1 Continuous 1 ----------- + 2 Continuous 2 ----------- + 3 Dash 1 - - - - - - + 4 Dot 1 . . . . . . + 5 Continuous 3 ----------- + 6 Double 3 =========== + 7 Continuous 0 ----------- + 8 Dash 2 - - - - - - + 9 Dash Dot 1 - . - . - . + 10 Dash Dot 2 - . - . - . + 11 Dash Dot Dot 1 - . . - . . + 12 Dash Dot Dot 2 - . . - . . + 13 SlantDash Dot 2 / - . / - . -A cell border is comprised of a border on the bottom, top, left and right. These can be set to the same value using C or individually using the relevant method calls shown above. Examples of the available border styles are shown in the 'Borders' worksheet created by formats.pl. +The following shows the borders sorted by style: + + Name Weight Style Index + ============= ====== =========== ===== + Continuous 0 ----------- 7 + Continuous 1 ----------- 1 + Continuous 2 ----------- 2 + Continuous 3 ----------- 5 + Dash 1 - - - - - - 3 + Dash 2 - - - - - - 8 + Dash Dot 1 - . - . - . 9 + Dash Dot 2 - . - . - . 10 + Dash Dot Dot 1 - . . - . . 11 + Dash Dot Dot 2 - . . - . . 12 + Dot 1 . . . . . . 4 + Double 3 =========== 6 + None 0 0 + SlantDash Dot 2 / - . / - . 13 + + +The following shows the borders in the order shown in the Excel Dialog. + + Index Style Index Style + ===== ===== ===== ===== + 0 None 12 - . . - . . + 7 ----------- 13 / - . / - . + 4 . . . . . . 10 - . - . - . + 11 - . . - . . 8 - - - - - - + 9 - . - . - . 2 ----------- + 3 - - - - - - 5 ----------- + 1 ----------- 6 =========== + + +Examples of the available border styles are shown in the 'Borders' worksheet created by formats.pl. @@ -3224,6 +3357,57 @@ Note: this is not a copy constructor, both objects must exist prior to copying. +=head1 UNICODE IN EXCEL + +I L and L. + +When using C the best and easiest way to write unicode strings to an Excel file is to use C encoded strings and perl 5.8 (or later). The module also allows you to write unicode strings using older perls but it generally requires more work, as explained below. + +Internally, Excel encodes unicode data as C (where LE means little-endian). If you are using perl 5.8+ then Spreadsheet::WriteExcel will convert C strings to C when required. No further intervention is required from the programmer, for example: + + # perl 5.8+ example: + my $smiley = "\x{263A}"; + + $worksheet->write('A1', 'Hello world'); + $worksheet->write('A2', $smiley); + +Spreadsheet::WriteExcel also lets you write unicode data as C. Since the majority of CPAN modules default to C (big-endian) Spreadsheet::WriteExcel also uses C and converts it internally to C: + + # perl 5.005 example: + my $smiley = pack "n", 0x263A; + + $worksheet->write ('A3', 'Hello world'); + $worksheet->write_utf16be_string('A4', $smiley); + +Although the above examples look similar there is an important difference. With C and perl 5.8+ Spreadsheet::WriteExcel treats C strings in exactly the same way as any other string. However, with C data we need to distinguish it from other strings either by calling a separate function or by passing an additional flag to indicate the data type. + +If you are dealing with non-ASCII character sets then your data probably won't be in C to begin with. However, perl 5.8+ provides useful tools in the guise of the C module to help you to convert to the required C format. For example: + + use Encode 'decode'; + + my $string = 'some string with koi8-r characters'; + $string = decode('koi8-r', $string); # koi8-r to utf8 + +Alternatively you can read data from an encoded file and convert it to C as you read it in: + + + my $file = 'unicode_koi8r.txt'; + open FH, '<:encoding(koi8-r)', $file or die "Couldn't open $file: $!\n"; + + my $row = 0; + while () { + # Data read in is now in utf8 format. + chomp; + $worksheet->write($row++, 0, $_); + } + +These methodologies are explained in more detail in L, L and L. + +See also the C programs in the examples directory of the distro. + + + + =head1 COLOURS IN EXCEL Excel provides a colour palette of 56 colours. In Spreadsheet::WriteExcel these colours are accessed via their palette index in the range 8..63. This index is used to set the colour of fonts, cell patterns and cell borders. For example: @@ -3287,7 +3471,7 @@ A hex RGB chart: : http://www.hypersolutions.org/pages/rgbhex.html -=head1 DATES IN EXCEL +=head1 DATES AND TIME IN EXCEL Dates and times in Excel are represented by real numbers, for example "Jan 1 2001 12:30 AM" is represented by the number 36892.521. @@ -3874,7 +4058,8 @@ different features and options of the module. Advanced ======== - autofit.pl Simuluate Excel's autofit for colums widths. + autofilter.pl Examples of worksheet autofilters. + autofit.pl Simulate Excel's autofit for column widths. bigfile.pl Write past the 7MB limit with OLE::Storage_Lite. cgi.pl A simple CGI program. chess.pl An example of formatting using properties. @@ -3923,9 +4108,9 @@ different features and options of the module. Unicode ======= - unicode.pl Simple example of using Unicode UTF16 strings. - unicode_japan.pl Write Japanese Unicode strings using UTF16. - unicode_cyrillic.pl Write Russian cyrillic strings using UTF8. + unicode_utf16.pl Simple example of using Unicode UTF16 strings. + unicode_utf16_japan.pl Write Japanese Unicode strings using UTF-16. + unicode_cyrillic.pl Write Russian cyrillic strings using UTF-8. unicode_list.pl List the chars in a Unicode font. unicode_2022_jp.pl Japanese: ISO-2022-JP to utf8 in perl 5.8. unicode_8859_11.pl Thai: ISO-8859_11 to utf8 in perl 5.8. @@ -3955,7 +4140,6 @@ different features and options of the module. - =head1 LIMITATIONS The following limits are imposed by Excel: @@ -4055,7 +4239,7 @@ Operating system doesn't support 64 bit IEEE float or it is byte-ordered in a wa You may sometimes encounter the following error when trying to open a file in Excel: "file.xls cannot be accessed. The file may be read-only, or you may be trying to access a read-only location. Or, the server the document is stored on may not be responding." -This error generally means that the Excel file has been corrupted. There are two likely causes of this: the file was FTPed in ASCII mode instead of binary mode or else the file was created with UTF8 data returned by an XML parser. See L for further details. +This error generally means that the Excel file has been corrupted. There are two likely causes of this: the file was FTPed in ASCII mode instead of binary mode or else the file was created with C data returned by an XML parser. See L for further details. =back @@ -4126,7 +4310,7 @@ This module allows you to create an Excel file from an XML template in a manner =item * Spreadsheet::WriteExcel::FromXML -This module allows you to turn a simple XML file into an Excel file using Spreadsheet::WriteExcel as a backend. The format of the XML file is defined by a supplied DTD: http://search.cpan.org/dist/Spreadsheet-WriteExcel-FromXML +This module allows you to turn a simple XML file into an Excel file using Spreadsheet::WriteExcel as a back-end. The format of the XML file is defined by a supplied DTD: http://search.cpan.org/dist/Spreadsheet-WriteExcel-FromXML =item * Spreadsheet::WriteExcel::Simple @@ -4206,15 +4390,15 @@ If you wish to view Excel files on a Windows platform which doesn't have Excel i -=head1 Warning about XML::Parser and Perl 5.6 +=head1 Warning about XML::Parser and perl 5.6 -You must be careful when using Spreadsheet::WriteExcel in conjunction with Perl 5.6 and XML::Parser (and other XML parsers) due to the fact that the data returned by the parser is generally in UTF8 format. +You must be careful when using Spreadsheet::WriteExcel in conjunction with perl 5.6 and XML::Parser (and other XML parsers) due to the fact that the data returned by the parser is generally in C format. -When UTF8 strings are added to Spreadsheet::WriteExcel's internal data it causes the generated Excel file to become corrupt. +When C strings are added to Spreadsheet::WriteExcel's internal data it causes the generated Excel file to become corrupt. -Note, this doesn't affect Perl 5.005 (which doesn't try to handle UTF8) or 5.8 (which handles it correctly). +Note, this doesn't affect perl 5.005 (which doesn't try to handle C) or 5.8 (which handles it correctly). -To avoid this problem you should upgrade to Perl 5.8, if possible, or else you should convert the output data from XML::Parser to ASCII or ISO-8859-1 using one of the following methods: +To avoid this problem you should upgrade to perl 5.8, if possible, or else you should convert the output data from XML::Parser to ASCII or ISO-8859-1 using one of the following methods: $new_str = pack 'C*', unpack 'U*', $utf8_str; @@ -4229,7 +4413,7 @@ To avoid this problem you should upgrade to Perl 5.8, if possible, or else you s Formulas are formulae. -XML and UTF8 data on Perl 5.6 can cause Excel files created by Spreadsheet::WriteExcel to become corrupt. See L for further details. +XML and C data on perl 5.6 can cause Excel files created by Spreadsheet::WriteExcel to become corrupt. See L for further details. The format object that is used with a C method call is marked internally as being associated with a merged range.It is a fatal error to use a merged format in a non-merged cell. The current workaround is to use separate formats for merged and non-merged cell. This restriction will be removed in a future release. @@ -4237,11 +4421,9 @@ Nested formulas sometimes aren't parsed correctly and give a result of "#VALUE". Spreadsheet::ParseExcel: All formulas created by Spreadsheet::WriteExcel are read as having a value of zero. This is because Spreadsheet::WriteExcel only stores the formula and not the calculated result. -OpenOffice.org: Images are not displayed. Some formatting is not displayed correctly. - -Gnumeric: Images are not displayed. Some formatting is not displayed correctly. URLs are not displayed as links. Page setup may cause Gnumeric to crash. +OpenOffice.org: No known issues in this release. -The lack of a portable way of writing a little-endian 64 bit IEEE float. There is beta code available to fix this. Let me know if you wish to test it on your platform. +Gnumeric: No known issues in this release. If you wish to submit a bug report run the C program in the C directory of the distro. @@ -4254,9 +4436,7 @@ The roadmap is as follows: =over 4 -=item * Fix insert_bitmap to work with write_comment(), OpenOffice.org and Gnumeric. - -=item * Add AutoFilters. +=item * Add JPEG support to insert_image(). =back @@ -4288,7 +4468,7 @@ http://freshmeat.net/projects/writeexcel/ -=head1 DONATATIONS +=head1 DONATIONS If you'd care to donate to the Spreadsheet::WriteExcel project, you can do so via PayPal: http://tinyurl.com/7ayes @@ -4307,7 +4487,7 @@ Excel::Template: http://search.cpan.org/~rkinyon/Excel-Template/ DateTime::Format::Excel: http://search.cpan.org/dist/DateTime-Format-Excel -"Reading and writing Excel files with Perl" by Teodor Zlatanov, atIBM developerWorks: http://www-106.ibm.com/developerworks/library/l-pexcel/ +"Reading and writing Excel files with Perl" by Teodor Zlatanov, at IBM developerWorks: http://www-106.ibm.com/developerworks/library/l-pexcel/ "Excel-Dateien mit Perl erstellen - Controller im Gluck" by Peter Dintelmann and Christian Kirsch in the German Unix/web journal iX: http://www.heise.de/ix/artikel/2001/06/175/ @@ -4317,7 +4497,7 @@ Oesterly user brushes with fame: http://oesterly.com/releases/12102000.html -=head1 ACKNOWLEDGEMENTS +=head1 ACKNOWLEDGMENTS The following people contributed to the debugging and testing of Spreadsheet::WriteExcel: @@ -4330,6 +4510,8 @@ Andrew Benham, Bill Young, Cedric Bouvier, Charles Wybble, Daniel Rentz, David R Many thanks to Ron McKelvey, Ronzo Consulting for Siemens, who sponsored the development of the formula caching routines. +Many thanks to Cassens Transport who sponsored the development of the embedded charts and autofilters. + Additional thanks to Takanori Kawai for translating the documentation into Japanese. Gunnar Wolf maintains the Debian distro. @@ -4352,22 +4534,27 @@ In no event unless required by applicable law or agreed to in writing will any c +=head1 LICENSE + +Either the Perl Artistic Licence http://dev.perl.org/licenses/artistic.html or the GPL http://www.opensource.org/licenses/gpl-license.php + + + + =head1 AUTHOR John McNamara jmcnamara@cpan.org - I was nineteen when I came to town, they called it the Summer of Love - They were burning babies, burning flags. The hawks against the doves - I took a job in the steamie down on Cauldrum Street - And I fell in love with a laundry girl who was working next to me - - Oh she was a rare thing, fine as a bee's wing - So fine a breath of wind might blow her away - She was a lost child, oh she was running wild - She said "As long as there's no price on love, I'll stay. - And you wouldn't want me any other way" + This, no song of an ingenue, + This, no ballad of innocence; + This, the rhyme of a lady who + Followed ever her natural bents. + This, a solo of sapience, + This, a chantey of sophistry, + This, the sum of experiments, + I loved them until they loved me. - -- Richard Thompson + -- Dorothy Parker diff --git a/lib/Spreadsheet/WriteExcel/BIFFwriter.pm b/lib/Spreadsheet/WriteExcel/BIFFwriter.pm index 2fad66c..cf307c7 100644 --- a/lib/Spreadsheet/WriteExcel/BIFFwriter.pm +++ b/lib/Spreadsheet/WriteExcel/BIFFwriter.pm @@ -7,7 +7,7 @@ package Spreadsheet::WriteExcel::BIFFwriter; # # Used in conjunction with Spreadsheet::WriteExcel # -# Copyright 2000-2006, John McNamara, jmcnamara@cpan.org +# Copyright 2000-2007, John McNamara, jmcnamara@cpan.org # # Documentation after __END__ # @@ -24,7 +24,7 @@ use strict; use vars qw($VERSION @ISA); @ISA = qw(Exporter); -$VERSION = '2.01'; +$VERSION = '2.20'; ############################################################################### # @@ -45,10 +45,11 @@ sub new { my $class = $_[0]; my $self = { - _byte_order => '', - _data => '', - _datasize => 0, - _limit => 8224, + _byte_order => '', + _data => '', + _datasize => 0, + _limit => 8224, + _ignore_continue => 0, }; bless $self, $class; @@ -196,16 +197,21 @@ sub _store_eof { # This function take a long BIFF record and inserts CONTINUE records as # necessary. # +# Some records have their own specialised Continue blocks so there is also an +# option to bypass this function. +# sub _add_continue { my $self = shift; my $data = $_[0]; my $limit = $self->{_limit}; my $record = 0x003C; # Record identifier - my $length; # Number of bytes to follow my $header; my $tmp; + # Skip this if another method handles the continue blocks. + return $data if $self->{_ignore_continue}; + # The first 2080/8224 bytes remain intact. However, we have to change # the length field of the record. # diff --git a/lib/Spreadsheet/WriteExcel/Big.pm b/lib/Spreadsheet/WriteExcel/Big.pm index 64e8d8d..af00a28 100644 --- a/lib/Spreadsheet/WriteExcel/Big.pm +++ b/lib/Spreadsheet/WriteExcel/Big.pm @@ -7,7 +7,7 @@ package Spreadsheet::WriteExcel::Big; # Spreadsheet::WriteExcel - Write formatted text and numbers to a # cross-platform Excel binary file. # -# Copyright 2000-2006, John McNamara. +# Copyright 2000-2007, John McNamara. # # @@ -22,7 +22,7 @@ use Spreadsheet::WriteExcel::WorkbookBig; use vars qw($VERSION @ISA); @ISA = qw(Spreadsheet::WriteExcel::WorkbookBig Exporter); -$VERSION = '2.01'; # May 2000 +$VERSION = '2.20'; ############################################################################### # diff --git a/lib/Spreadsheet/WriteExcel/Chart.pm b/lib/Spreadsheet/WriteExcel/Chart.pm index 9b18bd1..ffc8a69 100644 --- a/lib/Spreadsheet/WriteExcel/Chart.pm +++ b/lib/Spreadsheet/WriteExcel/Chart.pm @@ -7,7 +7,7 @@ package Spreadsheet::WriteExcel::Chart; # # Used in conjunction with Spreadsheet::WriteExcel # -# Copyright 2000-2006, John McNamara, jmcnamara@cpan.org +# Copyright 2000-2007, John McNamara, jmcnamara@cpan.org # # Documentation after __END__ # @@ -24,7 +24,7 @@ use Spreadsheet::WriteExcel::BIFFwriter; use vars qw($VERSION @ISA); @ISA = qw(Spreadsheet::WriteExcel::BIFFwriter); -$VERSION = '2.13'; +$VERSION = '2.20'; ############################################################################### # diff --git a/lib/Spreadsheet/WriteExcel/Format.pm b/lib/Spreadsheet/WriteExcel/Format.pm index 3b238e4..bb3bd78 100644 --- a/lib/Spreadsheet/WriteExcel/Format.pm +++ b/lib/Spreadsheet/WriteExcel/Format.pm @@ -7,7 +7,7 @@ package Spreadsheet::WriteExcel::Format; # # Used in conjunction with Spreadsheet::WriteExcel # -# Copyright 2000-2006, John McNamara, jmcnamara@cpan.org +# Copyright 2000-2007, John McNamara, jmcnamara@cpan.org # # Documentation after __END__ # @@ -24,7 +24,7 @@ use Carp; use vars qw($AUTOLOAD $VERSION @ISA); @ISA = qw(Exporter); -$VERSION = '2.18'; +$VERSION = '2.20'; ############################################################################### # @@ -91,6 +91,8 @@ sub new { _diag_color => 0x40, _diag_border => 0, + _font_only => 0, + # Temp code to prevent merged formats in non-merged cells. _used_merge => 0, diff --git a/lib/Spreadsheet/WriteExcel/Formula.pm b/lib/Spreadsheet/WriteExcel/Formula.pm index 55a3df7..ebdeb82 100644 --- a/lib/Spreadsheet/WriteExcel/Formula.pm +++ b/lib/Spreadsheet/WriteExcel/Formula.pm @@ -7,7 +7,7 @@ package Spreadsheet::WriteExcel::Formula; # # Used in conjunction with Spreadsheet::WriteExcel # -# Copyright 2000-2006, John McNamara, jmcnamara@cpan.org +# Copyright 2000-2007, John McNamara, jmcnamara@cpan.org # # Documentation after __END__ # @@ -24,7 +24,7 @@ use Carp; use vars qw($VERSION @ISA); @ISA = qw(Exporter); -$VERSION = '2.04'; +$VERSION = '2.20'; ############################################################################### # diff --git a/lib/Spreadsheet/WriteExcel/OLEwriter.pm b/lib/Spreadsheet/WriteExcel/OLEwriter.pm index 2776c72..6b88499 100644 --- a/lib/Spreadsheet/WriteExcel/OLEwriter.pm +++ b/lib/Spreadsheet/WriteExcel/OLEwriter.pm @@ -7,7 +7,7 @@ package Spreadsheet::WriteExcel::OLEwriter; # # Used in conjunction with Spreadsheet::WriteExcel # -# Copyright 2000-2006, John McNamara, jmcnamara@cpan.org +# Copyright 2000-2007, John McNamara, jmcnamara@cpan.org # # Documentation after __END__ # @@ -24,7 +24,7 @@ use FileHandle; use vars qw($VERSION @ISA); @ISA = qw(Exporter); -$VERSION = '2.15'; +$VERSION = '2.20'; ############################################################################### # diff --git a/lib/Spreadsheet/WriteExcel/Utility.pm b/lib/Spreadsheet/WriteExcel/Utility.pm index 53fc982..2a6dd98 100644 --- a/lib/Spreadsheet/WriteExcel/Utility.pm +++ b/lib/Spreadsheet/WriteExcel/Utility.pm @@ -4,7 +4,7 @@ package Spreadsheet::WriteExcel::Utility; # # Utility - Helper functions for Spreadsheet::WriteExcel. # -# Copyright 2000-2006, John McNamara, jmcnamara@cpan.org +# Copyright 2000-2007, John McNamara, jmcnamara@cpan.org # # @@ -47,7 +47,7 @@ my @dates = qw( dates => \@dates ); -$VERSION = '2.01'; +$VERSION = '2.20'; diff --git a/lib/Spreadsheet/WriteExcel/Workbook.pm b/lib/Spreadsheet/WriteExcel/Workbook.pm index c917220..defb9e5 100644 --- a/lib/Spreadsheet/WriteExcel/Workbook.pm +++ b/lib/Spreadsheet/WriteExcel/Workbook.pm @@ -7,7 +7,7 @@ package Spreadsheet::WriteExcel::Workbook; # # Used in conjunction with Spreadsheet::WriteExcel # -# Copyright 2000-2006, John McNamara, jmcnamara@cpan.org +# Copyright 2000-2007, John McNamara, jmcnamara@cpan.org # # Documentation after __END__ # @@ -20,11 +20,13 @@ use Spreadsheet::WriteExcel::OLEwriter; use Spreadsheet::WriteExcel::Worksheet; use Spreadsheet::WriteExcel::Format; use Spreadsheet::WriteExcel::Chart; +#use Digest::MD5 'md5_hex'; +#use Digest::Perl::MD4 'md4_hex'; use vars qw($VERSION @ISA); @ISA = qw(Spreadsheet::WriteExcel::BIFFwriter Exporter); -$VERSION = '2.18'; +$VERSION = '2.20'; ############################################################################### # @@ -150,6 +152,7 @@ sub new { require Encode if $] >= 5.008; $self->_initialize(); + $self->_get_checksum_method(); return $self; } @@ -227,6 +230,42 @@ sub _initialize { } +############################################################################### +# +# _get_checksum_method. +# +# Check for modules available to calculate image checksum. Excel uses MD4 but +# MD5 will also work. +# +sub _get_checksum_method { + + my $self = shift; + + eval { require Digest::MD4}; + if (not $@) { + $self->{_checksum_method} = 1; + return; + } + + + eval { require Digest::Perl::MD4}; + if (not $@) { + $self->{_checksum_method} = 2; + return; + } + + + eval { require Digest::MD5}; + if (not $@) { + $self->{_checksum_method} = 3; + return; + } + + # Default. + $self->{_checksum_method} = 0; +} + + ############################################################################### # # _append(), overloaded. @@ -533,7 +572,7 @@ sub _check_sheetname { $error = 1 if lc($name_a) eq lc($name_b); } else { - # We can't easily do a case insensite test of the UTF16 names. + # We can't easily do a case insensitive test of the UTF16 names. # As a special case we check if all of the high bytes are nulls and # then do an ASCII style case insensitive test. @@ -850,7 +889,7 @@ sub _store_workbook { # Calculate the offsets required by the BOUNDSHEET records $self->_calc_sheet_offsets(); - # Add BOUNDSHEET records. For BIFF 7+ TODO .... + # Add BOUNDSHEET records. foreach my $sheet (@{$self->{_worksheets}}) { $self->_store_boundsheet($sheet->{_name}, $sheet->{_offset}, @@ -916,6 +955,10 @@ sub _store_OLE_file { eval { require OLE::Storage_Lite }; if (not $@) { + + # Protect print() from -l on the command line. + local $\ = undef; + my $stream = pack 'v*', unpack 'C*', 'Workbook'; my $OLE = OLE::Storage_Lite::PPS::File->newFile($stream); @@ -972,9 +1015,11 @@ sub _calc_sheet_offsets { # Add the length of the SUPBOOK, EXTERNSHEET and NAME records $offset += $self->_calculate_extern_sizes(); - # Add the length of the MSODRAWINGGROUP records - $offset += $self->{_mso_size}; - + # Add the length of the MSODRAWINGGROUP records including an extra 4 bytes + # for any CONTINUE headers. See _add_mso_drawing_group_continue(). + my $mso_size = $self->{_mso_size}; + $mso_size += 4 * int(($mso_size -1) / $self->{_limit}); + $offset += $mso_size ; foreach my $sheet (@{$self->{_worksheets}}) { $offset += $BOF + length($sheet->{_name}); @@ -998,13 +1043,12 @@ sub _calc_sheet_offsets { # Calculate the MSODRAWINGGROUP sizes and the indexes of the Worksheet # MSODRAWING records. # -# In the following SPID is shape id, according to Escher nonemclature. +# In the following SPID is shape id, according to Escher nomenclature. # sub _calc_mso_sizes { my $self = shift; - my $num_shapes = 0; my $mso_size = 0; # Size of the MSODRAWINGGROUP record my $start_spid = 1024; # Initial spid for each sheet my $max_spid = 1024; # spidMax @@ -1014,17 +1058,35 @@ sub _calc_mso_sizes { my @clusters = (); + $self->_process_images(); + + # Add Bstore container size if there are images. + $mso_size += 8 if @{$self->{_images_data}}; + + # Iterate through the worksheets, calculate the MSODRAWINGGROUP parameters # and space required to store the record and the MSODRAWING parameters # required by each worksheet. # foreach my $sheet (@{$self->{_worksheets}}) { - next if ref $sheet eq "Spreadsheet::WriteExcel::Chart"; - next unless $num_shapes = $sheet->_prepare_comments(); + next unless ref $sheet eq "Spreadsheet::WriteExcel::Worksheet"; + + my $num_images = $sheet->{_num_images} || 0; + my $image_mso_size = $sheet->{_image_mso_size} || 0; + my $num_comments = $sheet->_prepare_comments(); + my $num_charts = $sheet->_prepare_charts(); + my $num_filters = $sheet->{_filter_count}; + + next unless $num_images + $num_comments + $num_charts +$num_filters; + # Include 1 parent MSODRAWING shape, per sheet, in the shape count. - $num_shapes += 1; - $shapes_saved += $num_shapes; + my $num_shapes += 1 + $num_images + + $num_comments + + $num_charts + + $num_filters; + $shapes_saved += $num_shapes; + $mso_size += $image_mso_size; # Add a drawing object for each sheet with comments. @@ -1050,8 +1112,8 @@ sub _calc_mso_sizes { } - # Pass calculate values back to the worksheet - $sheet->{_comments_ids} = [$start_spid, $drawings_saved, + # Pass calculated values back to the worksheet + $sheet->{_object_ids} = [$start_spid, $drawings_saved, $num_shapes, $max_spid -1]; } @@ -1068,6 +1130,261 @@ sub _calc_mso_sizes { } + +############################################################################### +# +# _process_images() +# +# We need to process each image in each worksheet and extract information. +# Some of this information is stored and used in the Workbook and some is +# passed back into each Worksheet. The overall size for the image related +# BIFF structures in the Workbook is calculated here. +# +# MSO size = 8 bytes for bstore_container + +# 44 bytes for blip_store_entry + +# 25 bytes for blip +# = 77 + image size. +# +sub _process_images { + + my $self = shift; + + my %images_seen; + my @image_data; + my @previous_images; + my $image_id = 1; + my $images_size = 0; + + + foreach my $sheet (@{$self->{_worksheets}}) { + next unless ref $sheet eq "Spreadsheet::WriteExcel::Worksheet"; + next unless $sheet->_prepare_images(); + + my $num_images = 0; + my $image_mso_size = 0; + + + for my $image_ref (@{$sheet->{_images_array}}) { + my $filename = $image_ref->[2]; + $num_images++; + + # + # For each Worksheet image we get a structure like this + # [ + # $row, + # $col, + # $name, + # $x_offset, + # $y_offset, + # $scale_x, + # $scale_y, + # ] + # + # And we add additional information: + # + # $image_id, + # $type, + # $width, + # $height; + + if (not exists $images_seen{$filename}) { + # TODO should also match seen images based on checksum. + + # Open the image file and import the data. + my $fh = FileHandle->new($filename); + croak "Couldn't import $filename: $!" unless defined $fh; + binmode $fh; + + # Slurp the file into a string and do some size calcs. + my $data = do {local $/; <$fh>}; + my $size = length $data; + my $checksum1 = $self->_image_checksum($data, $image_id); + my $checksum2 = $checksum1; + my $ref_count = 1; + + + # Process the image and extract dimensions. + my ($type, $width, $height); + + if (unpack('x A3', $data) eq 'PNG') { + ($type, $width, $height) = $self->_process_png($data); + } + elsif (unpack('A2', $data) eq 'BM') { + ($type, $width, $height) = $self->_process_bmp($data, + $filename); + # The 14 byte header of the BMP is stripped off. + $data = substr $data, 14; + + # A checksum of the new image data is also required. + $checksum2 = $self->_image_checksum($data, + $image_id, + $image_id + ); + + # Adjust size -14 (header) + 16 (extra checksum). + $size += 2; + } + else { + croak "Unsupported image format for file: $filename\n"; + } + + + # Push the new data back into the Worksheet array; + push @$image_ref, $image_id, $type, $width, $height; + + # Also store new data for use in duplicate images. + push @previous_images, [$image_id, $type, $width, $height]; + + + # Store information required by the Workbook. + push @image_data, [$ref_count, $type, $data, $size, + $checksum1, $checksum2]; + + # Keep track of overall data size. + $images_size += $size +61; # Size for bstore container. + $image_mso_size += $size +69; # Size for dgg container. + + $images_seen{$filename} = $image_id++; + $fh->close; + } + else { + # We've processed this file already. + my $index = $images_seen{$filename} -1; + + # Increase image reference count. + $image_data[$index]->[0]++; + + # Add previously calculated data back onto the Worksheet array. + # $image_id, $type, $width, $height + my $a_ref = $sheet->{_images_array}->[$index]; + push @$image_ref, @{$previous_images[$index]}; + } + } + + # Store information required by the Worksheet. + $sheet->{_num_images} = $num_images; + $sheet->{_image_mso_size} = $image_mso_size; + + } + + + # Store information required by the Workbook. + $self->{_images_size} = $images_size; + $self->{_images_data} = \@image_data; # Store the data for MSODRAWINGGROUP. + +} + + +############################################################################### +# +# _image_checksum() +# +# Generate a checksum for the image using whichever module is available..The +# available modules are checked in _get_checksum_method(). Excel uses an MD4 +# checksum but any other will do. In the event of no checksum module being +# available we simulate a checksum using the image index. +# +sub _image_checksum { + + my $self = shift; + + my $data = $_[0]; + my $index1 = $_[1]; + my $index2 = $_[2] || 0; + + if ($self->{_checksum_method} == 1) { + # Digest::MD4 + return Digest::MD4::md4_hex($data); + } + elsif ($self->{_checksum_method} == 2) { + # Digest::Perl::MD4 + return Digest::Perl::MD4::md4_hex($data); + } + elsif ($self->{_checksum_method} == 3) { + # Digest::MD5 + return Digest::MD5::md5_hex($data); + } + else { + # Default + return sprintf '%016X%016X', $index2, $index1; + } +} + + +############################################################################### +# +# _process_png() +# +# Extract width and height information from a PNG file. +# +sub _process_png { + + my $self = shift; + + my $type = 6; # Excel Blip type (MSOBLIPTYPE). + my $width = unpack "N", substr $_[0], 16, 4; + my $height = unpack "N", substr $_[0], 20, 4; + + return ($type, $width, $height); +} + + +############################################################################### +# +# _process_bmp() +# +# Extract width and height information from a BMP file. +# +# Most of these checks came from the old Worksheet::_process_bitmap() method. +# +sub _process_bmp { + + my $self = shift; + my $data = $_[0]; + my $filename = $_[1]; + my $type = 7; # Excel Blip type (MSOBLIPTYPE). + + + # Check that the file is big enough to be a bitmap. + if (length $data <= 0x36) { + croak "$filename doesn't contain enough data."; + } + + + # Read the bitmap width and height. Verify the sizes. + my ($width, $height) = unpack "x18 V2", $data; + + if ($width > 0xFFFF) { + croak "$filename: largest image width $width supported is 65k."; + } + + if ($height > 0xFFFF) { + croak "$filename: largest image height supported is 65k."; + } + + # Read the bitmap planes and bpp data. Verify them. + my ($planes, $bitcount) = unpack "x26 v2", $data; + + if ($bitcount != 24) { + croak "$filename isn't a 24bit true color bitmap."; + } + + if ($planes != 1) { + croak "$filename: only 1 plane supported in bitmap image."; + } + + + # Read the bitmap compression. Verify compression. + my $compression = unpack "x30 V", $data; + + if ($compression != 0) { + croak "$filename: compression not supported in bitmap image."; + } + + return ($type, $width, $height); +} + + ############################################################################### # # _store_all_fonts() @@ -1087,7 +1404,7 @@ sub _store_all_fonts { } - # Add the font for comments. This is connected to any XF format. + # Add the font for comments. This isn't connected to any XF format. my $tmp = Spreadsheet::WriteExcel::Format->new(undef, font => 'Tahoma', size => 8); @@ -1105,17 +1422,23 @@ sub _store_all_fonts { $key = $format->get_font_key(); # The default font for cell formats. $fonts{$key} = 0; # Index of the default font + # Fonts that are marked as '_font_only' are always stored. These are used + # mainly for charts and may not have an associated XF record. foreach $format (@{$self->{_formats}}) { $key = $format->get_font_key(); - if (exists $fonts{$key}) { + if (not $format->{_font_only} and exists $fonts{$key}) { # FONT has already been used $format->{_font_index} = $fonts{$key}; } else { # Add a new FONT record - $fonts{$key} = $index; + + if (not $format->{_font_only}) { + $fonts{$key} = $index; + } + $format->{_font_index} = $index; $index++; $font = $format->get_font(); @@ -1242,11 +1565,25 @@ sub _store_names { my $ref = $ext_refs{$key}; $index++; + # Write a Name record if Autofilter has been defined + if ($worksheet->{_filter_count}) { + $self->_store_name_short( + $worksheet->{_index}, + 0x0D, # NAME type = Filter Database + $ref, + $worksheet->{_filter_area}->[0], + $worksheet->{_filter_area}->[1], + $worksheet->{_filter_area}->[2], + $worksheet->{_filter_area}->[3], + 1, # Hidden + ); + } + # Write a Name record if the print area has been defined if (defined $worksheet->{_print_rowmin}) { $self->_store_name_short( $worksheet->{_index}, - 0x06, # NAME type + 0x06, # NAME type = Print_Area $ref, $worksheet->{_print_rowmin}, $worksheet->{_print_rowmax}, @@ -1254,6 +1591,7 @@ sub _store_names { $worksheet->{_print_colmax} ); } + } $index = 0; @@ -1277,7 +1615,7 @@ sub _store_names { # Row title has been defined. $self->_store_name_long( $worksheet->{_index}, - 0x07, # NAME type + 0x07, # NAME type = Print_Titles $ref, $rowmin, $rowmax, @@ -1289,7 +1627,7 @@ sub _store_names { # Row title has been defined. $self->_store_name_short( $worksheet->{_index}, - 0x07, # NAME type + 0x07, # NAME type = Print_Titles $ref, $rowmin, $rowmax, @@ -1301,7 +1639,7 @@ sub _store_names { # Column title has been defined. $self->_store_name_short( $worksheet->{_index}, - 0x07, # NAME type + 0x07, # NAME type = Print_Titles $ref, 0x0000, 0xffff, @@ -1606,6 +1944,8 @@ sub _store_name_short { my $colmin = $_[2]; # Start column my $colmax = $_[3]; # end column + my $hidden = $_[4]; # Name is hidden + $grbit = 0x0021 if $hidden; my $header = pack("vv", $record, $length); my $data = pack("v", $grbit); @@ -1811,11 +2151,13 @@ sub _calculate_extern_sizes { my $rowmin = $worksheet->{_title_rowmin}; my $colmin = $worksheet->{_title_colmin}; + my $filter = $worksheet->{_filter_count}; my $key = "$index:$index"; $index++; - # Print area NAME records + # Add area NAME records + # if (defined $worksheet->{_print_rowmin}) { $ext_refs{$key} = $ext_ref_count++ if not exists $ext_refs{$key}; @@ -1823,7 +2165,8 @@ sub _calculate_extern_sizes { } - # Print title NAME records + # Add title NAME records + # if (defined $rowmin and defined $colmin) { $ext_refs{$key} = $ext_ref_count++ if not exists $ext_refs{$key}; @@ -1835,26 +2178,31 @@ sub _calculate_extern_sizes { $length += 31; } else { - # TODO + # TODO, may need this later. } + # Add Autofilter NAME records + # + if ($filter) { + $ext_refs{$key} = $ext_ref_count++ if not exists $ext_refs{$key}; + + $length += 31; + } } - # TODO + # Update the ref counts. $self->{_ext_ref_count} = $ext_ref_count; $self->{_ext_refs} = {%ext_refs}; - # If there are no external refs then we don't write, SUPBOOK, EXTERNSHEET # and NAME. Therefore the length is 0. return $length = 0 if $ext_ref_count == 0; - # The SUPBOOK record is 8 bytes $length += 8; @@ -2209,6 +2557,7 @@ sub _store_shared_strings { } } + # # Methods related to comments and MSO objects. # @@ -2231,17 +2580,86 @@ sub _add_mso_drawing_group { my $data = $self->_store_mso_dgg_container(); $data .= $self->_store_mso_dgg(@{$self->{_mso_clusters}}); + $data .= $self->_store_mso_bstore_container(); + $data .= $self->_store_mso_images(@$_) for @{$self->{_images_data}}; $data .= $self->_store_mso_opt(); $data .= $self->_store_mso_split_menu_colors(); $length = length $data; my $header = pack("vv", $record, $length); - $self->_append($header, $data); + $self->_add_mso_drawing_group_continue($header . $data); + + return $header . $data; # For testing only. +} + + +############################################################################### +# +# _add_mso_drawing_group_continue() +# +# See first the Spreadsheet::WriteExcel::BIFFwriter::_add_continue() method. +# +# Add specialised CONTINUE headers to large MSODRAWINGGROUP data block. +# We use the Excel 97 max block size of 8228 - 4 bytes for the header = 8224. +# +# The structure depends on the size of the data block: +# +# Case 1: <= 8224 bytes 1 MSODRAWINGGROUP +# Case 2: <= 2*8224 bytes 1 MSODRAWINGGROUP + 1 CONTINUE +# Case 3: > 2*8224 bytes 2 MSODRAWINGGROUP + n CONTINUE +# +sub _add_mso_drawing_group_continue { + + my $self = shift; + + my $data = $_[0]; + my $limit = 8228 -4; + my $mso_group = 0x00EB; # Record identifier + my $continue = 0x003C; # Record identifier + my $block_count = 1; + my $header; + my $tmp; + + # Ignore the base class _add_continue() method. + $self->{_ignore_continue} = 1; + + # Case 1 above. Just return the data as it is. + if (length $data <= $limit) { + $self->_append($data); + return; + } + + # Change length field of the first MSODRAWINGGROUP block. Case 2 and 3. + $tmp = substr($data, 0, $limit +4, ""); + substr($tmp, 2, 2, pack("v", $limit)); + $self->_append($tmp); + + + # Add MSODRAWINGGROUP and CONTINUE blocks for Case 3 above. + while (length($data) > $limit) { + if ($block_count == 1) { + # Add extra MSODRAWINGGROUP block header. + $header = pack("vv", $mso_group, $limit); + $block_count++; + } + else { + # Add normal CONTINUE header. + $header = pack("vv", $continue, $limit); + } + + $tmp = substr($data, 0, $limit, ""); + $self->_append($header, $tmp); + } - return $header . $data; + # Last CONTINUE block for remaining data. Case 2 and 3 above. + $header = pack("vv", $continue, length($data)); + $self->_append($header, $data); + + # Turn the base class _add_continue() method back on. + $self->{_ignore_continue} = 0; } @@ -2274,7 +2692,7 @@ sub _store_mso_dgg_container { # sub _store_mso_dgg { - my $self = shift; + my $self = shift; my $type = 0xF006; my $version = 0; @@ -2303,6 +2721,137 @@ sub _store_mso_dgg { } +############################################################################### +# +# _store_mso_bstore_container() +# +# Write the Escher BstoreContainer record that is part of MSODRAWINGGROUP. +# +sub _store_mso_bstore_container { + + my $self = shift; + + return '' unless $self->{_images_size}; + + my $type = 0xF001; + my $version = 15; + my $instance = @{$self->{_images_data}}; # Number of images. + my $data = ''; + my $length = $self->{_images_size} +8 *$instance; + + return $self->_add_mso_generic($type, $version, $instance, $data, $length); +} + + + +############################################################################### +# +# _store_mso_images() +# +# Write the Escher BstoreContainer record that is part of MSODRAWINGGROUP. +# +sub _store_mso_images { + + my $self = shift; + + my $ref_count = $_[0]; + my $image_type = $_[1]; + my $image = $_[2]; + my $size = $_[3]; + my $checksum1 = $_[4]; + my $checksum2 = $_[5]; + + my $blip_store_entry = $self->_store_mso_blip_store_entry($ref_count, + $image_type, + $size, + $checksum1); + + my $blip = $self->_store_mso_blip($image_type, + $image, + $size, + $checksum1, + $checksum2); + + return $blip_store_entry . $blip; +} + + + +############################################################################### +# +# _store_mso_blip_store_entry() +# +# Write the Escher BlipStoreEntry record that is part of MSODRAWINGGROUP. +# +sub _store_mso_blip_store_entry { + + my $self = shift; + + my $ref_count = $_[0]; + my $image_type = $_[1]; + my $size = $_[2]; + my $checksum1 = $_[3]; + + + my $type = 0xF007; + my $version = 2; + my $instance = $image_type; + my $length = $size +61; + my $data = pack('C', $image_type) # Win32 + . pack('C', $image_type) # Mac + . pack('H*', $checksum1) # Uid checksum + . pack('v', 0xFF) # Tag + . pack('V', $size +25) # Next Blip size + . pack('V', $ref_count) # Image ref count + . pack('V', 0x00000000) # File offset + . pack('C', 0x00) # Usage + . pack('C', 0x00) # Name length + . pack('C', 0x00) # Unused + . pack('C', 0x00) # Unused + ; + + return $self->_add_mso_generic($type, $version, $instance, $data, $length); +} + + +############################################################################### +# +# _store_mso_blip() +# +# Write the Escher Blip record that is part of MSODRAWINGGROUP. +# +sub _store_mso_blip { + + my $self = shift; + + my $image_type = $_[0]; + my $image_data = $_[1]; + my $size = $_[2]; + my $checksum1 = $_[3]; + my $checksum2 = $_[4]; + my $instance; + + $instance = 0x06E0 if $image_type == 6; # PNG + $instance = 0x07A9 if $image_type == 7; # BMP + + # BMPs contain an extra checksum for the stripped data. + if ( $image_type == 7) { + $checksum1 = $checksum2 . $checksum1; + } + + my $type = 0xF018 + $image_type; + my $version = 0x0000; + my $length = $size +17; + my $data = pack('H*', $checksum1) # Uid checksum + . pack('C', 0xFF) # Tag + . $image_data # Image + ; + + return $self->_add_mso_generic($type, $version, $instance, $data, $length); +} + + + ############################################################################### # # _store_mso_opt() diff --git a/lib/Spreadsheet/WriteExcel/WorkbookBig.pm b/lib/Spreadsheet/WriteExcel/WorkbookBig.pm index dc53223..31f75e5 100644 --- a/lib/Spreadsheet/WriteExcel/WorkbookBig.pm +++ b/lib/Spreadsheet/WriteExcel/WorkbookBig.pm @@ -7,7 +7,7 @@ package Spreadsheet::WriteExcel::WorkbookBig; # # Used in conjunction with Spreadsheet::WriteExcel # -# Copyright 2000-2006, John McNamara and Kawai Takanori. +# Copyright 2000-2007, John McNamara and Kawai Takanori. # # Documentation after __END__ # @@ -24,7 +24,7 @@ use Spreadsheet::WriteExcel::Format; use vars qw($VERSION @ISA); @ISA = qw(Spreadsheet::WriteExcel::Workbook Exporter); -$VERSION = '2.15'; +$VERSION = '2.20'; ############################################################################### # diff --git a/lib/Spreadsheet/WriteExcel/Worksheet.pm b/lib/Spreadsheet/WriteExcel/Worksheet.pm index abafb89..394e103 100644 --- a/lib/Spreadsheet/WriteExcel/Worksheet.pm +++ b/lib/Spreadsheet/WriteExcel/Worksheet.pm @@ -7,7 +7,7 @@ package Spreadsheet::WriteExcel::Worksheet; # # Used in conjunction with Spreadsheet::WriteExcel # -# Copyright 2000-2006, John McNamara, jmcnamara@cpan.org +# Copyright 2000-2007, John McNamara, jmcnamara@cpan.org # # Documentation after __END__ # @@ -24,7 +24,7 @@ use Spreadsheet::WriteExcel::Formula; use vars qw($VERSION @ISA); @ISA = qw(Spreadsheet::WriteExcel::BIFFwriter); -$VERSION = '2.18'; +$VERSION = '2.20'; ############################################################################### # @@ -34,127 +34,135 @@ $VERSION = '2.18'; # sub new { - my $class = shift; - my $self = Spreadsheet::WriteExcel::BIFFwriter->new(); - my $rowmax = 65536; - my $colmax = 256; - my $strmax = 0; - - $self->{_name} = $_[0]; - $self->{_index} = $_[1]; - $self->{_encoding} = $_[2]; - $self->{_activesheet} = $_[3]; - $self->{_firstsheet} = $_[4]; - $self->{_url_format} = $_[5]; - $self->{_parser} = $_[6]; - $self->{_tempdir} = $_[7]; - - $self->{_str_total} = $_[8]; - $self->{_str_unique} = $_[9]; - $self->{_str_table} = $_[10]; - $self->{_1904} = $_[11]; - - $self->{_type} = 0x0000; - $self->{_ext_sheets} = []; - $self->{_using_tmpfile} = 1; - $self->{_filehandle} = ""; - $self->{_fileclosed} = 0; - $self->{_offset} = 0; - $self->{_xls_rowmax} = $rowmax; - $self->{_xls_colmax} = $colmax; - $self->{_xls_strmax} = $strmax; - $self->{_dim_rowmin} = $rowmax +1; - $self->{_dim_rowmax} = 0; - $self->{_dim_colmin} = $colmax +1; - $self->{_dim_colmax} = 0; - $self->{_dim_changed} = 0; - $self->{_colinfo} = []; - $self->{_selection} = [0, 0]; - $self->{_panes} = []; - $self->{_active_pane} = 3; - $self->{_frozen} = 0; - $self->{_selected} = 0; - $self->{_hidden} = 0; - $self->{_active} = 0; - $self->{_tab_color} = 0; - - $self->{_first_row} = 0; - $self->{_first_col} = 0; - $self->{_display_formulas} = 0; - $self->{_display_headers} = 1; - $self->{_display_zeros} = 1; - $self->{_display_arabic} = 0; - - $self->{_paper_size} = 0x0; - $self->{_orientation} = 0x1; - $self->{_header} = ''; - $self->{_footer} = ''; - $self->{_header_encoding} = 0; - $self->{_footer_encoding} = 0; - $self->{_hcenter} = 0; - $self->{_vcenter} = 0; - $self->{_margin_header} = 0.50; - $self->{_margin_footer} = 0.50; - $self->{_margin_left} = 0.75; - $self->{_margin_right} = 0.75; - $self->{_margin_top} = 1.00; - $self->{_margin_bottom} = 1.00; - - $self->{_title_rowmin} = undef; - $self->{_title_rowmax} = undef; - $self->{_title_colmin} = undef; - $self->{_title_colmax} = undef; - $self->{_print_rowmin} = undef; - $self->{_print_rowmax} = undef; - $self->{_print_colmin} = undef; - $self->{_print_colmax} = undef; - - $self->{_print_gridlines} = 1; - $self->{_screen_gridlines} = 1; - $self->{_print_headers} = 0; - - $self->{_page_order} = 0; - $self->{_black_white} = 0; - $self->{_draft_quality} = 0; - $self->{_print_comments} = 0; - $self->{_page_start} = 1; - - $self->{_fit_page} = 0; - $self->{_fit_width} = 0; - $self->{_fit_height} = 0; - - $self->{_hbreaks} = []; - $self->{_vbreaks} = []; - - $self->{_protect} = 0; - $self->{_password} = undef; - - $self->{_col_sizes} = {}; - $self->{_row_sizes} = {}; - - $self->{_col_formats} = {}; - $self->{_row_formats} = {}; - - $self->{_zoom} = 100; - $self->{_print_scale} = 100; - $self->{_page_view} = 0; - - $self->{_leading_zeros} = 0; - - $self->{_outline_row_level} = 0; - $self->{_outline_style} = 0; - $self->{_outline_below} = 1; - $self->{_outline_right} = 1; - $self->{_outline_on} = 1; - - $self->{_write_match} = []; - $self->{_comments} = {}; - $self->{_comments_ids} = []; - $self->{_comments_author} = ''; - $self->{_comments_author_enc} = 0; - $self->{_comments_visible} = 0; - - $self->{_writing_url} = 0; + my $class = shift; + my $self = Spreadsheet::WriteExcel::BIFFwriter->new(); + my $rowmax = 65536; + my $colmax = 256; + my $strmax = 0; + + $self->{_name} = $_[0]; + $self->{_index} = $_[1]; + $self->{_encoding} = $_[2]; + $self->{_activesheet} = $_[3]; + $self->{_firstsheet} = $_[4]; + $self->{_url_format} = $_[5]; + $self->{_parser} = $_[6]; + $self->{_tempdir} = $_[7]; + + $self->{_str_total} = $_[8]; + $self->{_str_unique} = $_[9]; + $self->{_str_table} = $_[10]; + $self->{_1904} = $_[11]; + + $self->{_type} = 0x0000; + $self->{_ext_sheets} = []; + $self->{_using_tmpfile} = 1; + $self->{_filehandle} = ""; + $self->{_fileclosed} = 0; + $self->{_offset} = 0; + $self->{_xls_rowmax} = $rowmax; + $self->{_xls_colmax} = $colmax; + $self->{_xls_strmax} = $strmax; + $self->{_dim_rowmin} = $rowmax +1; + $self->{_dim_rowmax} = 0; + $self->{_dim_colmin} = $colmax +1; + $self->{_dim_colmax} = 0; + $self->{_dim_changed} = 0; + $self->{_colinfo} = []; + $self->{_selection} = [0, 0]; + $self->{_panes} = []; + $self->{_active_pane} = 3; + $self->{_frozen} = 0; + $self->{_frozen_no_split} = 1; + $self->{_selected} = 0; + $self->{_hidden} = 0; + $self->{_active} = 0; + $self->{_tab_color} = 0; + + $self->{_first_row} = 0; + $self->{_first_col} = 0; + $self->{_display_formulas} = 0; + $self->{_display_headers} = 1; + $self->{_display_zeros} = 1; + $self->{_display_arabic} = 0; + + $self->{_paper_size} = 0x0; + $self->{_orientation} = 0x1; + $self->{_header} = ''; + $self->{_footer} = ''; + $self->{_header_encoding} = 0; + $self->{_footer_encoding} = 0; + $self->{_hcenter} = 0; + $self->{_vcenter} = 0; + $self->{_margin_header} = 0.50; + $self->{_margin_footer} = 0.50; + $self->{_margin_left} = 0.75; + $self->{_margin_right} = 0.75; + $self->{_margin_top} = 1.00; + $self->{_margin_bottom} = 1.00; + + $self->{_title_rowmin} = undef; + $self->{_title_rowmax} = undef; + $self->{_title_colmin} = undef; + $self->{_title_colmax} = undef; + $self->{_print_rowmin} = undef; + $self->{_print_rowmax} = undef; + $self->{_print_colmin} = undef; + $self->{_print_colmax} = undef; + + $self->{_print_gridlines} = 1; + $self->{_screen_gridlines} = 1; + $self->{_print_headers} = 0; + + $self->{_page_order} = 0; + $self->{_black_white} = 0; + $self->{_draft_quality} = 0; + $self->{_print_comments} = 0; + $self->{_page_start} = 1; + + $self->{_fit_page} = 0; + $self->{_fit_width} = 0; + $self->{_fit_height} = 0; + + $self->{_hbreaks} = []; + $self->{_vbreaks} = []; + + $self->{_protect} = 0; + $self->{_password} = undef; + + $self->{_col_sizes} = {}; + $self->{_row_sizes} = {}; + + $self->{_col_formats} = {}; + $self->{_row_formats} = {}; + + $self->{_zoom} = 100; + $self->{_print_scale} = 100; + $self->{_page_view} = 0; + + $self->{_leading_zeros} = 0; + + $self->{_outline_row_level} = 0; + $self->{_outline_style} = 0; + $self->{_outline_below} = 1; + $self->{_outline_right} = 1; + $self->{_outline_on} = 1; + + $self->{_write_match} = []; + + $self->{_object_ids} = []; + $self->{_images} = {}; + $self->{_charts} = {}; + $self->{_comments} = {}; + $self->{_comments_author} = ''; + $self->{_comments_author_enc} = 0; + $self->{_comments_visible} = 0; + + $self->{_filter_area} = []; + $self->{_filter_count} = 0; + $self->{_filter_on} = 0; + + $self->{_writing_url} = 0; bless $self, $class; @@ -254,6 +262,15 @@ sub _close { # Prepend the sheet dimensions $self->_store_dimensions(); + # Prepend the autofilter filters. + $self->_store_autofilters; + + # Prepend the sheet autofilter info. + $self->_store_autofilterinfo(); + + # Prepend the sheet filtermode record. + $self->_store_filtermode(); + # Prepend the COLINFO records if they exist if (@{$self->{_colinfo}}){ my @colinfo = @{$self->{_colinfo}}; @@ -263,7 +280,7 @@ sub _close { } } - # Add the DEFCOLWIDTH record + # Prepend the DEFCOLWIDTH record $self->_store_defcol(); # Prepend the sheet password @@ -332,6 +349,9 @@ sub _close { ################################################ # Append + $self->_store_images(); + $self->_store_charts(); + $self->_store_filters(); $self->_store_comments(); $self->_store_window2(); $self->_store_page_view(); @@ -517,7 +537,7 @@ sub set_column { # Store the col sizes for use when calculating image vertices taking # hidden columns into account. Also store the column formats. # - my $width = $data[4] ? 0 : $data[2]; # Set width to zero if column is hidden + my $width = $data[4] ? 0 : $data[2]; # Set width to zero if col is hidden $width ||= 0; # Ensure width isn't undef. my $format = $data[3]; @@ -565,6 +585,9 @@ sub freeze_panes { @_ = $self->_substitute_cellref(@_); } + # Extra flag indicated a split and freeze. + $self->{_frozen_no_split} = 0 if $_[4]; + $self->{_frozen} = 1; $self->{_panes} = [ @_ ]; } @@ -572,18 +595,22 @@ sub freeze_panes { ############################################################################### # -# thaw_panes() +# split_panes() # -# Set panes and mark them as unfrozen. See also _store_panes(). +# Set panes and mark them as split. See also _store_panes(). # -sub thaw_panes { +sub split_panes { my $self = shift; - $self->{_frozen} = 0; - $self->{_panes} = [ @_ ]; + $self->{_frozen} = 0; + $self->{_frozen_no_split} = 0; + $self->{_panes} = [ @_ ]; } +# Older method name for backwards compatibility. +*thaw_panes = *split_panes; + ############################################################################### # @@ -938,6 +965,276 @@ sub print_area { } +############################################################################### +# +# autofilter($first_row, $first_col, $last_row, $last_col) +# +# Set the autofilter area in the worksheet. +# +sub autofilter { + + my $self = shift; + + # Check for a cell reference in A1 notation and substitute row and column + if ($_[0] =~ /^\D/) { + @_ = $self->_substitute_cellref(@_); + } + + return if @_ != 4; # Require 4 parameters + + my ($row1, $col1, $row2, $col2) = @_; + + # Reverse max and min values if necessary. + ($row1, $row2) = ($row2, $row1) if $row2 < $row1; + ($col1, $col2) = ($col2, $col1) if $col2 < $col1; + + # Store the Autofilter information + $self->{_filter_area} = [$row1, $row2, $col1, $col2]; + $self->{_filter_count} = 1+ $col2 -$col1; +} + + +############################################################################### +# +# filter_column($column, $criteria, ...) +# +# Set the column filter criteria. +# +sub filter_column { + + my $self = shift; + my $col = $_[0]; + my $expression = $_[1]; + + + croak "Must call autofilter() before filter_column()" + unless $self->{_filter_count}; + croak "Incorrect number of arguments to filter_column()" unless @_ == 2; + + + # Check for a column reference in A1 notation and substitute. + if ($col =~ /^\D/) { + # Convert col ref to a cell ref and then to a col number. + (undef, $col) = $self->_substitute_cellref($col . '1'); + } + + my (undef, undef, $col_first, $col_last) = @{$self->{_filter_area}}; + + # Reject column if it is outside filter range. + if ($col < $col_first or $col > $col_last) { + croak "Column '$col' outside autofilter() column range " . + "($col_first .. $col_last)"; + } + + + my @tokens = $self->_extract_filter_tokens($expression); + + croak "Incorrect number of tokens in expression '$expression'" + unless (@tokens == 3 or @tokens == 7); + + + @tokens = $self->_parse_filter_expression($expression, @tokens); + + $self->{_filter_cols}->{$col} = [@tokens]; + $self->{_filter_on} = 1; +} + + +############################################################################### +# +# _extract_filter_tokens($expression) +# +# Extract the tokens from the filter expression. The tokens are mainly non- +# whitespace groups. The only tricky part is to extract string tokens that +# contain whitespace and/or quoted double quotes (Excel's escaped quotes). +# +# Examples: 'x < 2000' +# 'x > 2000 and x < 5000' +# 'x = "foo"' +# 'x = "foo bar"' +# 'x = "foo "" bar"' +# +sub _extract_filter_tokens { + + my $self = shift; + my $expression = $_[0]; + + return unless $expression; + + my @tokens = ($expression =~ /"(?:[^"]|"")*"|\S+/g); #" + + # Remove leading and trailing quotes and unescape other quotes + for (@tokens) { + s/^"//; #" + s/"$//; #" + s/""/"/g; #" + } + + return @tokens; +} + + +############################################################################### +# +# _parse_filter_expression(@token) +# +# Converts the tokens of a possibly conditional expression into 1 or 2 +# sub expressions for further parsing. +# +# Examples: +# ('x', '==', 2000) -> exp1 +# ('x', '>', 2000, 'and', 'x', '<', 5000) -> exp1 and exp2 +# +sub _parse_filter_expression { + + my $self = shift; + my $expression = shift; + my @tokens = @_; + + # The number of tokens will be either 3 (for 1 expression) + # or 7 (for 2 expressions). + # + if (@tokens == 7) { + + my $conditional = $tokens[3]; + + if ($conditional =~ /^(and|&&)$/) { + $conditional = 0; + } + elsif ($conditional =~ /^(or|\|\|)$/) { + $conditional = 1; + } + else { + croak "Token '$conditional' is not a valid conditional " . + "in filter expression '$expression'"; + } + + my @expression_1 = $self->_parse_filter_tokens($expression, + @tokens[0, 1, 2]); + my @expression_2 = $self->_parse_filter_tokens($expression, + @tokens[4, 5, 6]); + + return (@expression_1, $conditional, @expression_2); + } + else { + return $self->_parse_filter_tokens($expression, @tokens); + } +} + + +############################################################################### +# +# _parse_filter_tokens(@token) +# +# Parse the 3 tokens of a filter expression and return the operator and token. +# +sub _parse_filter_tokens { + + my $self = shift; + my $expression = shift; + my @tokens = @_; + + my %operators = ( + '==' => 2, + '=' => 2, + '=~' => 2, + 'eq' => 2, + + '!=' => 5, + '!~' => 5, + 'ne' => 5, + '<>' => 5, + + '<' => 1, + '<=' => 3, + '>' => 4, + '>=' => 6, + ); + + my $operator = $operators{$tokens[1]}; + my $token = $tokens[2]; + + + # Special handling of "Top" filter expressions. + if ($tokens[0] =~ /^top|bottom$/i) { + + my $value = $tokens[1]; + + if ($value =~ /\D/ or + $value < 1 or + $value > 500) + { + croak "The value '$value' in expression '$expression' " . + "must be in the range 1 to 500"; + } + + $token = lc $token; + + if ($token ne 'items' and $token ne '%') { + croak "The type '$token' in expression '$expression' " . + "must be either 'items' or '%'"; + } + + if ($tokens[0] =~ /^top$/i) { + $operator = 30; + } + else { + $operator = 32; + } + + if ($tokens[2] eq '%') { + $operator++; + } + + $token = $value; + } + + + if (not $operator and $tokens[0]) { + croak "Token '$tokens[1]' is not a valid operator " . + "in filter expression '$expression'"; + } + + + # Special handling for Blanks/NonBlanks. + if ($token =~ /^blanks|nonblanks$/i) { + + # Only allow Equals or NotEqual in this context. + if ($operator != 2 and $operator != 5) { + croak "The operator '$tokens[1]' in expression '$expression' " . + "is not valid in relation to Blanks/NonBlanks'"; + } + + $token = lc $token; + + # The operator should always be 2 (=) to flag a "simple" equality in + # the binary record. Therefore we convert <> to =. + if ($token eq 'blanks') { + if ($operator == 5) { + $operator = 2; + $token = 'nonblanks'; + } + } + else { + if ($operator == 5) { + $operator = 2; + $token = 'blanks'; + } + } + } + + + # if the string token contains an Excel match character then change the + # operator type to indicate a non "simple" equality. + if ($operator == 2 and $token =~ /[*?]/) { + $operator = 22; + } + + + return ($operator, $token); +} + + ############################################################################### # # hide_gridlines() @@ -1776,7 +2073,7 @@ sub write_string { if (Encode::is_utf8($str)) { my $tmp = Encode::encode("UTF-16LE", $str); - return $self->write_unicode_le($row, $col, $tmp, $_[3]); + return $self->write_utf16le_string($row, $col, $tmp, $_[3]); } } @@ -1791,7 +2088,7 @@ sub write_string { } - # Preprend the string with the type. + # Prepend the string with the type. my $str_header = pack("vC", length($str), $encoding); $str = $str_header . $str; @@ -2211,7 +2508,7 @@ sub repeat_formula { # alternative string is specified. # # The parameters $string and $format are optional and their order is -# interchangeable for backward compatibity reasons. +# interchangeable for backward compatibility reasons. # # The hyperlink can be to a http, ftp, mail, internal sheet, or external # directory url. @@ -3061,7 +3358,7 @@ sub _store_window2 { my $fDefaultHdr = 1; # 5 my $fArabic = $self->{_display_arabic}; # 6 my $fDspGuts = $self->{_outline_on}; # 7 - my $fFrozenNoSplit = 0; # 0 - bit + my $fFrozenNoSplit = $self->{_frozen_no_split}; # 0 - bit my $fSelected = $self->{_selected}; # 1 my $fPaged = $self->{_active}; # 2 my $fBreakPreview = 0; # 3 @@ -3249,6 +3546,53 @@ sub _store_colinfo { } +############################################################################### +# +# _store_filtermode() +# +# Write BIFF record FILTERMODE to indicate that the worksheet contains +# AUTOFILTER record, ie. autofilters with a filter set. +# +sub _store_filtermode { + + my $self = shift; + + my $record = 0x009B; # Record identifier + my $length = 0x0000; # Number of bytes to follow + + # Only write the record if the worksheet contains a filtered autofilter. + return unless $self->{_filter_on}; + + my $header = pack("vv", $record, $length); + + $self->_prepend($header); +} + + +############################################################################### +# +# _store_autofilterinfo() +# +# Write BIFF record AUTOFILTERINFO. +# +sub _store_autofilterinfo { + + my $self = shift; + + my $record = 0x009D; # Record identifier + my $length = 0x0002; # Number of bytes to follow + my $num_filters = $self->{_filter_count}; + + # Only write the record if the worksheet contains an autofilter. + return unless $self->{_filter_count}; + + my $header = pack("vv", $record, $length); + my $data = pack("v", $num_filters); + + $self->_prepend($header, $data); +} + + ############################################################################### # # _store_selection($first_row, $first_col, $last_row, $last_col) @@ -3375,15 +3719,16 @@ sub _store_externsheet { # sub _store_panes { - my $self = shift; - my $record = 0x0041; # Record identifier - my $length = 0x000A; # Number of bytes to follow + my $self = shift; + my $record = 0x0041; # Record identifier + my $length = 0x000A; # Number of bytes to follow - my $y = $_[0] || 0; # Vertical split position - my $x = $_[1] || 0; # Horizontal split position - my $rwTop = $_[2]; # Top row visible - my $colLeft = $_[3]; # Leftmost column visible - my $pnnAct = $_[4]; # Active pane + my $y = $_[0] || 0; # Vertical split position + my $x = $_[1] || 0; # Horizontal split position + my $rwTop = $_[2]; # Top row visible + my $colLeft = $_[3]; # Leftmost column visible + my $no_split = $_[4]; # No used here. + my $pnnAct = $_[5]; # Active pane # Code specific to frozen or thawed panes. @@ -3799,10 +4144,10 @@ sub merge_range { # Write the first cell if ($encoding) { - $self->write_unicode($rwFirst, $colFirst, $string, $format); + $self->write_utf16be_string($rwFirst, $colFirst, $string, $format); } else { - $self->write ($rwFirst, $colFirst, $string, $format); + $self->write ($rwFirst, $colFirst, $string, $format); } # Pad out the rest of the area with formatted blank cells. @@ -4116,12 +4461,11 @@ sub _store_password { ############################################################################### # -# insert_bitmap($row, $col, $filename, $x, $y, $scale_x, $scale_y) +# embed_chart($row, $col, $filename, $x, $y, $scale_x, $scale_y) # -# Insert a 24bit bitmap image in a worksheet. The main record required is -# IMDATA but it must be proceeded by a OBJ record to define its position. +# TODO. # -sub insert_bitmap { +sub embed_chart { my $self = shift; @@ -4132,34 +4476,69 @@ sub insert_bitmap { my $row = $_[0]; my $col = $_[1]; - my $bitmap = $_[2]; - my $x = $_[3] || 0; - my $y = $_[4] || 0; + my $chart = $_[2]; + my $x_offset = $_[3] || 0; + my $y_offset = $_[4] || 0; my $scale_x = $_[5] || 1; my $scale_y = $_[6] || 1; - my ($width, $height, $size, $data) = $self->_process_bitmap($bitmap); + croak "Insufficient arguments in embed_chart()" unless @_ >= 3; + croak "Couldn't locate $chart: $!" unless -e $chart; - # Scale the frame of the image. - $width *= $scale_x; - $height *= $scale_y; + $self->{_charts}->{$row}->{$col} = [ + $row, + $col, + $chart, + $x_offset, + $y_offset, + $scale_x, + $scale_y, + ]; + +} - # Calculate the vertices of the image and write the OBJ record - my @vertices = $self->_position_object($col, $row, $x, $y, $width, $height); - $self->_store_obj_picture(@vertices ); - # Write the IMDATA record to store the bitmap data - my $record = 0x007f; - my $length = 8 + $size; - my $cf = 0x09; - my $env = 0x01; - my $lcb = $size; +############################################################################### +# +# insert_image($row, $col, $filename, $x, $y, $scale_x, $scale_y) +# +# Insert an image into the worksheet. +# +sub insert_image { - my $header = pack("vvvvV", $record, $length, $cf, $env, $lcb); + my $self = shift; + + # Check for a cell reference in A1 notation and substitute row and column + if ($_[0] =~ /^\D/) { + @_ = $self->_substitute_cellref(@_); + } + + my $row = $_[0]; + my $col = $_[1]; + my $image = $_[2]; + my $x_offset = $_[3] || 0; + my $y_offset = $_[4] || 0; + my $scale_x = $_[5] || 1; + my $scale_y = $_[6] || 1; + + croak "Insufficient arguments in insert_image()" unless @_ >= 3; + croak "Couldn't locate $image: $!" unless -e $image; + + $self->{_images}->{$row}->{$col} = [ + $row, + $col, + $image, + $x_offset, + $y_offset, + $scale_x, + $scale_y, + ]; - $self->_append($header, $data); } +# Older method name for backwards compatibility. +*insert_bitmap = *insert_image; + ############################################################################### # @@ -4351,190 +4730,6 @@ sub _size_row { } -############################################################################### -# -# _store_obj_picture( $col_start, $x1, -# $row_start, $y1, -# $col_end, $x2, -# $row_end, $y2 ) -# -# Store the OBJ record that precedes an IMDATA record. This could be generalise -# to support other Excel objects. -# -sub _store_obj_picture { - - my $self = shift; - - my $record = 0x005d; # Record identifier - my $length = 0x003c; # Bytes to follow - - my $cObj = 0x0001; # Count of objects in file (set to 1) - my $OT = 0x0008; # Object type. 8 = Picture - my $id = 0x0001; # Object ID - my $grbit = 0x0614; # Option flags - - my $colL = $_[0]; # Col containing upper left corner of object - my $dxL = $_[1]; # Distance from left side of cell - - my $rwT = $_[2]; # Row containing top left corner of object - my $dyT = $_[3]; # Distance from top of cell - - my $colR = $_[4]; # Col containing lower right corner of object - my $dxR = $_[5]; # Distance from right of cell - - my $rwB = $_[6]; # Row containing bottom right corner of object - my $dyB = $_[7]; # Distance from bottom of cell - - my $cbMacro = 0x0000; # Length of FMLA structure - my $Reserved1 = 0x0000; # Reserved - my $Reserved2 = 0x0000; # Reserved - - my $icvBack = 0x09; # Background colour - my $icvFore = 0x09; # Foreground colour - my $fls = 0x00; # Fill pattern - my $fAuto = 0x00; # Automatic fill - my $icv = 0x08; # Line colour - my $lns = 0xff; # Line style - my $lnw = 0x01; # Line weight - my $fAutoB = 0x00; # Automatic border - my $frs = 0x0000; # Frame style - my $cf = 0x0009; # Image format, 9 = bitmap - my $Reserved3 = 0x0000; # Reserved - my $cbPictFmla = 0x0000; # Length of FMLA structure - my $Reserved4 = 0x0000; # Reserved - my $grbit2 = 0x0001; # Option flags - my $Reserved5 = 0x0000; # Reserved - - - my $header = pack("vv", $record, $length); - my $data = pack("V", $cObj); - $data .= pack("v", $OT); - $data .= pack("v", $id); - $data .= pack("v", $grbit); - $data .= pack("v", $colL); - $data .= pack("v", $dxL); - $data .= pack("v", $rwT); - $data .= pack("v", $dyT); - $data .= pack("v", $colR); - $data .= pack("v", $dxR); - $data .= pack("v", $rwB); - $data .= pack("v", $dyB); - $data .= pack("v", $cbMacro); - $data .= pack("V", $Reserved1); - $data .= pack("v", $Reserved2); - $data .= pack("C", $icvBack); - $data .= pack("C", $icvFore); - $data .= pack("C", $fls); - $data .= pack("C", $fAuto); - $data .= pack("C", $icv); - $data .= pack("C", $lns); - $data .= pack("C", $lnw); - $data .= pack("C", $fAutoB); - $data .= pack("v", $frs); - $data .= pack("V", $cf); - $data .= pack("v", $Reserved3); - $data .= pack("v", $cbPictFmla); - $data .= pack("v", $Reserved4); - $data .= pack("v", $grbit2); - $data .= pack("V", $Reserved5); - - $self->_append($header, $data); -} - - -############################################################################### -# -# _process_bitmap() -# -# Convert a 24 bit bitmap into the modified internal format used by Windows. -# This is described in BITMAPCOREHEADER and BITMAPCOREINFO structures in the -# MSDN library. -# -sub _process_bitmap { - - my $self = shift; - my $bitmap = shift; - - # Open file and binmode the data in case the platform needs it. - my $fh = FileHandle->new($bitmap); - croak "Couldn't import $bitmap: $!" unless defined $fh; - binmode $fh; - - - # Slurp the file into a string. - my $data = do {local $/; <$fh>}; - - $fh->close; - - # Check that the file is big enough to be a bitmap. - if (length $data <= 0x36) { - croak "$bitmap doesn't contain enough data."; - } - - - # The first 2 bytes are used to identify the bitmap. - if (unpack("A2", $data) ne "BM") { - croak "$bitmap doesn't appear to be a valid bitmap image."; - } - - - # Remove bitmap data: ID. - $data = substr $data, 2; - - - # Read and remove the bitmap size. This is more reliable than reading - # the data size at offset 0x22. - # - my $size = unpack "V", substr $data, 0, 4, ""; - $size -= 0x36; # Subtract size of bitmap header. - $size += 0x0C; # Add size of BIFF header. - - - # Remove bitmap data: reserved, offset, header length. - $data = substr $data, 12; - - - # Read and remove the bitmap width and height. Verify the sizes. - my ($width, $height) = unpack "V2", substr $data, 0, 8, ""; - - if ($width > 0xFFFF) { - croak "$bitmap: largest image width supported is 65k."; - } - - if ($height > 0xFFFF) { - croak "$bitmap: largest image height supported is 65k."; - } - - # Read and remove the bitmap planes and bpp data. Verify them. - my ($planes, $bitcount) = unpack "v2", substr $data, 0, 4, ""; - - if ($bitcount != 24) { - croak "$bitmap isn't a 24bit true color bitmap."; - } - - if ($planes != 1) { - croak "$bitmap: only 1 plane supported in bitmap image."; - } - - - # Read and remove the bitmap compression. Verify compression. - my $compression = unpack "V", substr $data, 0, 4, ""; - - if ($compression != 0) { - croak "$bitmap: compression not supported in bitmap image."; - } - - # Remove bitmap data: data size, hres, vres, colours, imp. colours. - $data = substr $data, 20; - - # Add the BITMAPCOREHEADER data - my $header = pack("Vvvvv", 0x000c, $width, $height, 0x01, 0x18); - $data = $header . $data; - - return ($width, $height, $size, $data); -} - - ############################################################################### # # _store_zoom($zoom) @@ -4562,7 +4757,7 @@ sub _store_zoom { ############################################################################### # -# write_unicode ($row, $col, $string, $format) +# write_utf16be_string($row, $col, $string, $format) # # Write a Unicode string to the specified row and column (zero indexed). # $format is optional. @@ -4571,7 +4766,7 @@ sub _store_zoom { # -2 : row or column out of range # -3 : long string truncated to 255 chars # -sub write_unicode { +sub write_utf16be_string { my $self = shift; @@ -4637,21 +4832,18 @@ sub write_unicode { } - ############################################################################### # -# write_unicode_le ($row, $col, $string, $format) +# write_utf16le_string($row, $col, $string, $format) # -# Write a Unicode string to the specified row and column (zero indexed). +# Write a UTF-16LE string to the specified row and column (zero indexed). # $format is optional. # Returns 0 : normal termination # -1 : insufficient number of arguments # -2 : row or column out of range # -3 : long string truncated to 255 chars # -# TODO Refactor. Too much code share with write_unicode(). -# -sub write_unicode_le { +sub write_utf16le_string { my $self = shift; @@ -4667,87 +4859,747 @@ sub write_unicode_le { my $row = $_[0]; # Zero indexed row my $col = $_[1]; # Zero indexed column - my $strlen = length($_[2]); my $str = $_[2]; - my $xf = _XF($self, $row, $col, $_[3]); # The cell format - my $encoding = 0x1; - my $str_error = 0; + my $format = $_[3]; # The cell format - # Check that row and col are valid and store max and min values - return -2 if $self->_check_dimensions($row, $col); - # Limit the utf16 string to the max number of chars (not bytes). - if ($strlen > 32767* 2) { - $str = substr($str, 0, 32767*2); - $str_error = -3; + # Change from UTF16 big-endian to little endian + $str = pack "v*", unpack "n*", $str; + + + return $self->write_utf16be_string($row, $col, $str, $format); +} + + +# Older method name for backwards compatibility. +*write_unicode = *write_utf16be_string; +*write_unicode_le = *write_utf16le_string; + + + +############################################################################### +# +# _store_autofilters() +# +# Function to iterate through the columns that form part of an autofilter +# range and write Biff AUTOFILTER records if a filter expression has been set. +# +sub _store_autofilters { + + my $self = shift; + + # Skip all columns if no filter have been set. + return unless $self->{_filter_on}; + + my (undef, undef, $col1, $col2) = @{$self->{_filter_area}}; + + for my $i ($col1 .. $col2) { + # Reverse order since records are being pre-pended. + my $col = $col2 -$i; + + # Skip if column doesn't have an active filter. + next unless $self->{_filter_cols}->{$col}; + + # Retrieve the filter tokens and write the autofilter records. + my @tokens = @{$self->{_filter_cols}->{$col}}; + $self->_store_autofilter($col, @tokens); } +} - my $num_bytes = length $str; - my $num_chars = int($num_bytes / 2); +############################################################################### +# +# _store_autofilter() +# +# Function to write worksheet AUTOFILTER records. These contain 2 Biff Doper +# structures to represent the 2 possible filter conditions. +# +sub _store_autofilter { + my $self = shift; - # Check for a valid 2-byte char string. - croak "Uneven number of bytes in Unicode string" if $num_bytes % 2; + my $record = 0x009E; + my $length = 0x0000; - # Add the encoding and length header to the string. - my $str_header = pack("vC", $num_chars, $encoding); - $str = $str_header . $str; + my $index = $_[0]; + my $operator_1 = $_[1]; + my $token_1 = $_[2]; + my $join = $_[3]; # And/Or + my $operator_2 = $_[4]; + my $token_2 = $_[5]; + my $top10_active = 0; + my $top10_direction = 0; + my $top10_percent = 0; + my $top10_value = 101; - if (not exists ${$self->{_str_table}}->{$str}) { - ${$self->{_str_table}}->{$str} = ${$self->{_str_unique}}++; + my $grbit = $join; + my $optimised_1 = 0; + my $optimised_2 = 0; + my $doper_1 = ''; + my $doper_2 = ''; + my $string_1 = ''; + my $string_2 = ''; + + # Excel used an optimisation in the case of a simple equality. + $optimised_1 = 1 if $operator_1 == 2; + $optimised_2 = 1 if defined $operator_2 and $operator_2 == 2; + + + # Convert non-simple equalities back to type 2. See _parse_filter_tokens(). + $operator_1 = 2 if $operator_1 == 22; + $operator_2 = 2 if defined $operator_2 and $operator_2 == 22; + + + # Handle a "Top" style expression. + if ($operator_1 >= 30) { + # Remove the second expression if present. + $operator_2 = undef; + $token_2 = undef; + + # Set the active flag. + $top10_active = 1; + + if ($operator_1 == 30 or $operator_1 == 31) { + $top10_direction = 1; + } + + if ($operator_1 == 31 or $operator_1 == 33) { + $top10_percent = 1; + } + + if ($top10_direction == 1) { + $operator_1 = 6 + } + else { + $operator_1 = 3 + } + + $top10_value = $token_1; + $token_1 = 0; } - ${$self->{_str_total}}++; + $grbit |= $optimised_1 << 2; + $grbit |= $optimised_2 << 3; + $grbit |= $top10_active << 4; + $grbit |= $top10_direction << 5; + $grbit |= $top10_percent << 6; + $grbit |= $top10_value << 7; + ($doper_1, $string_1) = $self->_pack_doper($operator_1, $token_1); + ($doper_2, $string_2) = $self->_pack_doper($operator_2, $token_2); - my $header = pack("vv", $record, $length); - my $data = pack("vvvV", $row, $col, $xf, ${$self->{_str_table}}->{$str}); + my $data = pack 'v', $index; + $data .= pack 'v', $grbit; + $data .= $doper_1; + $data .= $doper_2; + $data .= $string_1; + $data .= $string_2; + + $length = length $data; + my $header = pack('vv', $record, $length); + + $self->_prepend($header, $data); +} + + +############################################################################### +# +# _pack_doper() +# +# Create a Biff Doper structure that represents a filter expression. Depending +# on the type of the token we pack an Empty, String or Number doper. +# +sub _pack_doper { + + my $self = shift; + + my $operator = $_[0]; + my $token = $_[1]; + + my $doper = ''; + my $string = ''; + + + # Return default doper for non-defined filters. + if (not defined $operator) { + return ($self->_pack_unused_doper, $string); + } + + + if ($token =~ /^blanks|nonblanks$/i) { + $doper = $self->_pack_blanks_doper($operator, $token); + } + elsif ($operator == 2 or + $token !~ /^([+-]?)(?=\d|\.\d)\d*(\.\d*)?([Ee]([+-]?\d+))?$/) + { + # Excel treats all tokens as strings if the operator is equality, =. + + $string = $token; + + my $encoding = 0; + my $length = length $string; + + # Handle utf8 strings in perl 5.8. + if ($] >= 5.008) { + require Encode; + + if (Encode::is_utf8($string)) { + $string = Encode::encode("UTF-16BE", $string); + $encoding = 1; + } + } + + $string = pack('C', $encoding) . $string; + $doper = $self->_pack_string_doper($operator, $length); + } + else { + $string = ''; + $doper = $self->_pack_number_doper($operator, $token); + } + + return ($doper, $string); +} + + +############################################################################### +# +# _pack_unused_doper() +# +# Pack an empty Doper structure. +# +sub _pack_unused_doper { + + my $self = shift; + + return pack 'C10', (0x0) x 10; +} + + +############################################################################### +# +# _pack_blanks_doper() +# +# Pack an Blanks/NonBlanks Doper structure. +# +sub _pack_blanks_doper { + + my $self = shift; + + my $operator = $_[0]; + my $token = $_[1]; + my $type; + + if ($token eq 'blanks') { + $type = 0x0C; + $operator = 2; + + } + else { + $type = 0x0E; + $operator = 5; + } + + + my $doper = pack 'CCVV', $type, # Data type + $operator, # + 0x0000, # Reserved + 0x0000; # Reserved + return $doper; +} + + +############################################################################### +# +# _pack_string_doper() +# +# Pack an string Doper structure. +# +sub _pack_string_doper { + + my $self = shift; + + my $operator = $_[0]; + my $length = $_[1]; + my $doper = pack 'CCVCCCC', 0x06, # Data type + $operator, # + 0x0000, # Reserved + $length, # String char length. + 0x0, 0x0, 0x0; # Reserved + return $doper; +} + + +############################################################################### +# +# _pack_number_doper() +# +# Pack an IEEE double number Doper structure. +# +sub _pack_number_doper { + + my $self = shift; + + my $operator = $_[0]; + my $number = $_[1]; + $number = pack 'd', $number; + $number = reverse $number if $self->{_byte_order}; + + my $doper = pack 'CC', 0x04, $operator; + $doper .= $number; + + return $doper; +} + + +# +# Methods related to comments and MSO objects. +# + + +############################################################################### +# +# _prepare_images() +# +# Turn the HoH that stores the images into an array for easier handling. +# +sub _prepare_images { + + my $self = shift; + + my $count = 0; + my @images; + + + # We sort the images by row and column but that isn't strictly required. + # + my @rows = sort {$a <=> $b} keys %{$self->{_images}}; + + for my $row (@rows) { + my @cols = sort {$a <=> $b} keys %{$self->{_images}->{$row}}; + + for my $col (@cols) { + push @images, $self->{_images}->{$row}->{$col}; + $count++; + } + } + + $self->{_images} = {}; + $self->{_images_array} = \@images; + + return $count; +} + + +############################################################################### +# +# _prepare_comments() +# +# Turn the HoH that stores the comments into an array for easier handling. +# +sub _prepare_comments { + + my $self = shift; + + my $count = 0; + my @comments; + + + # We sort the comments by row and column but that isn't strictly required. + # + my @rows = sort {$a <=> $b} keys %{$self->{_comments}}; + + for my $row (@rows) { + my @cols = sort {$a <=> $b} keys %{$self->{_comments}->{$row}}; + + for my $col (@cols) { + push @comments, $self->{_comments}->{$row}->{$col}; + $count++; + } + } + + $self->{_comments} = {}; + $self->{_comments_array} = \@comments; + + return $count; +} + + +############################################################################### +# +# _prepare_charts() +# +# Turn the HoH that stores the charts into an array for easier handling. +# +sub _prepare_charts { + + my $self = shift; + + my $count = 0; + my @charts; + + + # We sort the charts by row and column but that isn't strictly required. + # + my @rows = sort {$a <=> $b} keys %{$self->{_charts}}; + + for my $row (@rows) { + my @cols = sort {$a <=> $b} keys %{$self->{_charts}->{$row}}; + + for my $col (@cols) { + push @charts, $self->{_charts}->{$row}->{$col}; + $count++; + } + } + + $self->{_charts} = {}; + $self->{_charts_array} = \@charts; + + return $count; +} + + +############################################################################### +# +# _store_images() +# +# Store the collections of records that make up images. +# +sub _store_images { + + my $self = shift; + + my $record = 0x00EC; # Record identifier + my $length = 0x0000; # Bytes to follow + + my @ids = @{$self->{_object_ids }}; + my $spid = shift @ids; + + my @images = @{$self->{_images_array}}; + my $num_images = scalar @images; + + my $num_filters = $self->{_filter_count}; + my $num_comments = @{$self->{_comments_array}}; + my $num_charts = @{$self->{_charts_array }}; + + # Skip this if there aren't any images. + return unless $num_images; + + for my $i (0 .. $num_images-1) { + my $row = $images[$i]->[0]; + my $col = $images[$i]->[1]; + my $name = $images[$i]->[2]; + my $x_offset = $images[$i]->[3]; + my $y_offset = $images[$i]->[4]; + my $scale_x = $images[$i]->[5]; + my $scale_y = $images[$i]->[6]; + my $image_id = $images[$i]->[7]; + my $type = $images[$i]->[8]; + my $width = $images[$i]->[9]; + my $height = $images[$i]->[10]; + + $width *= $scale_x if $scale_x; + $height *= $scale_y if $scale_y; + + + # Calculate the positions of image object. + my @vertices = $self->_position_object( $col, + $row, + $x_offset, + $y_offset, + $width, + $height + ); + + if ($i == 0) { + # Write the parent MSODRAWIING record. + my $dg_length = 156 + 84*($num_images -1); + my $spgr_length = 132 + 84*($num_images -1); + + $dg_length += 120 *$num_charts; + $spgr_length += 120 *$num_charts; + + $dg_length += 96 *$num_filters; + $spgr_length += 96 *$num_filters; + + $dg_length += 128 *$num_comments; + $spgr_length += 128 *$num_comments; + + + + my $data = $self->_store_mso_dg_container($dg_length); + $data .= $self->_store_mso_dg(@ids); + $data .= $self->_store_mso_spgr_container($spgr_length); + $data .= $self->_store_mso_sp_container(40); + $data .= $self->_store_mso_spgr(); + $data .= $self->_store_mso_sp(0x0, $spid++, 0x0005); + $data .= $self->_store_mso_sp_container(76); + $data .= $self->_store_mso_sp(75, $spid++, 0x0A00); + $data .= $self->_store_mso_opt_image($image_id); + $data .= $self->_store_mso_client_anchor(2, @vertices); + $data .= $self->_store_mso_client_data(); + + $length = length $data; + my $header = pack("vv", $record, $length); + $self->_append($header, $data); + + } + else { + # Write the child MSODRAWIING record. + my $data = $self->_store_mso_sp_container(76); + $data .= $self->_store_mso_sp(75, $spid++, 0x0A00); + $data .= $self->_store_mso_opt_image($image_id); + $data .= $self->_store_mso_client_anchor(2, @vertices); + $data .= $self->_store_mso_client_data(); + + $length = length $data; + my $header = pack("vv", $record, $length); + $self->_append($header, $data); + + + } + + $self->_store_obj_image($i+1); + } + + $self->{_object_ids}->[0] = $spid; +} + + + +############################################################################### +# +# _store_charts() +# +# Store the collections of records that make up charts. +# +sub _store_charts { + + my $self = shift; + + my $record = 0x00EC; # Record identifier + my $length = 0x0000; # Bytes to follow + + my @ids = @{$self->{_object_ids}}; + my $spid = shift @ids; + + my @charts = @{$self->{_charts_array}}; + my $num_charts = scalar @charts; + + my $num_filters = $self->{_filter_count}; + my $num_comments = @{$self->{_comments_array}}; + + # Number of objects written so far. + my $num_objects = @{$self->{_images_array}}; + + # Skip this if there aren't any charts. + return unless $num_charts; + + for my $i (0 .. $num_charts-1 ) { + my $row = $charts[$i]->[0]; + my $col = $charts[$i]->[1]; + my $name = $charts[$i]->[2]; + my $x_offset = $charts[$i]->[3]; + my $y_offset = $charts[$i]->[4]; + my $scale_x = $charts[$i]->[5]; + my $scale_y = $charts[$i]->[6]; + my $width = 526; + my $height = 319; + + $width *= $scale_x if $scale_x; + $height *= $scale_y if $scale_y; + + # Calculate the positions of chart object. + my @vertices = $self->_position_object( $col, + $row, + $x_offset, + $y_offset, + $width, + $height + ); + + + if ($i == 0 and not $num_objects) { + # Write the parent MSODRAWIING record. + my $dg_length = 192 + 120*($num_charts -1); + my $spgr_length = 168 + 120*($num_charts -1); + + $dg_length += 96 *$num_filters; + $spgr_length += 96 *$num_filters; + + $dg_length += 128 *$num_comments; + $spgr_length += 128 *$num_comments; + + + my $data = $self->_store_mso_dg_container($dg_length); + $data .= $self->_store_mso_dg(@ids); + $data .= $self->_store_mso_spgr_container($spgr_length); + $data .= $self->_store_mso_sp_container(40); + $data .= $self->_store_mso_spgr(); + $data .= $self->_store_mso_sp(0x0, $spid++, 0x0005); + $data .= $self->_store_mso_sp_container(112); + $data .= $self->_store_mso_sp(201, $spid++, 0x0A00); + $data .= $self->_store_mso_opt_chart(); + $data .= $self->_store_mso_client_anchor(0, @vertices); + $data .= $self->_store_mso_client_data(); + + $length = length $data; + my $header = pack("vv", $record, $length); + $self->_append($header, $data); + + } + else { + # Write the child MSODRAWIING record. + my $data = $self->_store_mso_sp_container(112); + $data .= $self->_store_mso_sp(201, $spid++, 0x0A00); + $data .= $self->_store_mso_opt_chart(); + $data .= $self->_store_mso_client_anchor(0, @vertices); + $data .= $self->_store_mso_client_data(); + + $length = length $data; + my $header = pack("vv", $record, $length); + $self->_append($header, $data); + + + } + + $self->_store_obj_chart($num_objects+$i+1); + $self->_store_chart_binary($name); + } + + + # Simulate the EXTERNSHEET link between the chart and data using a formula + # such as '=Sheet1!A1'. + # TODO. Won't work for external data refs. Also should use a more direct + # method. + # + my $formula = "='$self->{_name}'!A1"; + $self->store_formula($formula); + + $self->{_object_ids}->[0] = $spid; +} + + +############################################################################### +# +# _store_chart_binary +# +# Add a binary chart object extracted from an Excel file. +# +sub _store_chart_binary { + + my $self = shift; + my $filename = $_[0]; + my $tmp; + + my $filehandle = FileHandle->new($filename) or + die "Couldn't open $filename in add_chart_ext(): $!.\n"; + + binmode($filehandle); + + while (read($filehandle, $tmp, 4096)) { + $self->_append($tmp); + } +} + + +############################################################################### +# +# _store_filters() +# +# Store the collections of records that make up filters. +# +sub _store_filters { + + my $self = shift; + + my $record = 0x00EC; # Record identifier + my $length = 0x0000; # Bytes to follow + + my @ids = @{$self->{_object_ids}}; + my $spid = shift @ids; + + my $filter_area = $self->{_filter_area}; + my $num_filters = $self->{_filter_count}; + + my $num_comments = @{$self->{_comments_array}}; + + # Number of objects written so far. + my $num_objects = @{$self->{_images_array}} + + @{$self->{_charts_array}}; + + # Skip this if there aren't any filters. + return unless $num_filters; - $self->_append($header, $data); - return $str_error; -} + my ($row1, $row2, $col1, $col2) = @$filter_area; + for my $i (0 .. $num_filters-1 ) { + my @vertices = ( $col1 +$i, + 0, + $row1, + 0, + $col1 +$i +1, + 0, + $row1 +1, + 0); -# -# Methods related to comments and MSO objects. -# + if ($i == 0 and not $num_objects) { + # Write the parent MSODRAWIING record. + my $dg_length = 168 + 96*($num_filters -1); + my $spgr_length = 144 + 96*($num_filters -1); -############################################################################### -# -# _prepare_comments() -# -# Turn the HoH that stores the comments into an array for easier handling. -# -sub _prepare_comments { + $dg_length += 128 *$num_comments; + $spgr_length += 128 *$num_comments; - my $self = shift; - my $count = 0; - my @comments; + my $data = $self->_store_mso_dg_container($dg_length); + $data .= $self->_store_mso_dg(@ids); + $data .= $self->_store_mso_spgr_container($spgr_length); + $data .= $self->_store_mso_sp_container(40); + $data .= $self->_store_mso_spgr(); + $data .= $self->_store_mso_sp(0x0, $spid++, 0x0005); + $data .= $self->_store_mso_sp_container(88); + $data .= $self->_store_mso_sp(201, $spid++, 0x0A00); + $data .= $self->_store_mso_opt_filter(); + $data .= $self->_store_mso_client_anchor(1, @vertices); + $data .= $self->_store_mso_client_data(); + $length = length $data; + my $header = pack("vv", $record, $length); + $self->_append($header, $data); - # We sort the comments by row and column but that isn't strictly required. - # - my @rows = sort {$a <=> $b} keys %{$self->{_comments}}; + } + else { + # Write the child MSODRAWIING record. + my $data = $self->_store_mso_sp_container(88); + $data .= $self->_store_mso_sp(201, $spid++, 0x0A00); + $data .= $self->_store_mso_opt_filter(); + $data .= $self->_store_mso_client_anchor(1, @vertices); + $data .= $self->_store_mso_client_data(); + + $length = length $data; + my $header = pack("vv", $record, $length); + $self->_append($header, $data); - for my $row (@rows) { - my @cols = sort {$a <=> $b} keys %{$self->{_comments}->{$row}}; - for my $col (@cols) { - push @comments, $self->{_comments}->{$row}->{$col}; - $count++; } + + $self->_store_obj_filter($num_objects+$i+1, $col1 +$i); } - $self->{_comments} = {}; - $self->{_comments_array} = [@comments]; - return $count; + # Simulate the EXTERNSHEET link between the filter and data using a formula + # such as '=Sheet1!A1'. + # TODO. Won't work for external data refs. Also should use a more direct + # method. + # + my $formula = "='$self->{_name}'!A1"; + $self->store_formula($formula); + + $self->{_object_ids}->[0] = $spid; } @@ -4757,6 +5609,9 @@ sub _prepare_comments { # # Store the collections of records that make up cell comments. # +# NOTE: We write the comment objects last since that makes it a little easier +# to write the NOTE records directly after the MSODRAWIING records. +# sub _store_comments { my $self = shift; @@ -4764,13 +5619,19 @@ sub _store_comments { my $record = 0x00EC; # Record identifier my $length = 0x0000; # Bytes to follow - my @ids = @{$self->{_comments_ids}}; + my @ids = @{$self->{_object_ids}}; + my $spid = shift @ids; + my @comments = @{$self->{_comments_array}}; my $num_comments = scalar @comments; - my $spid = shift @ids; - return unless $num_comments; + # Number of objects written so far. + my $num_objects = @{$self->{_images_array}} + + $self->{_filter_count} + + @{$self->{_charts_array}}; + # Skip this if there aren't any comments. + return unless $num_comments; for my $i (0 .. $num_comments-1) { @@ -4781,13 +5642,12 @@ sub _store_comments { my $visible = $comments[$i]->[6]; my $color = $comments[$i]->[7]; my @vertices = @{$comments[$i]->[8]}; - my @anchor_data = ($row, $col, @vertices); my $str_len = length $str; $str_len /= 2 if $encoding; # Num of chars not bytes. my $formats = [[0, 5], [$str_len, 0]]; - if ($i == 0) { + if ($i == 0 and not $num_objects) { # Write the parent MSODRAWIING record. my $dg_length = 200 + 128*($num_comments -1); my $spgr_length = 176 + 128*($num_comments -1); @@ -4801,7 +5661,7 @@ sub _store_comments { $data .= $self->_store_mso_sp_container(120); $data .= $self->_store_mso_sp(202, $spid++, 0x0A00); $data .= $self->_store_mso_opt(0x80, $visible, $color); - $data .= $self->_store_mso_client_anchor(@anchor_data); + $data .= $self->_store_mso_client_anchor(3, @vertices); $data .= $self->_store_mso_client_data(); $length = length $data; @@ -4813,8 +5673,8 @@ sub _store_comments { # Write the child MSODRAWIING record. my $data = $self->_store_mso_sp_container(120); $data .= $self->_store_mso_sp(202, $spid++, 0x0A00); - $data .= $self->_store_mso_opt(0x80, $visible,$color); - $data .= $self->_store_mso_client_anchor(@anchor_data); + $data .= $self->_store_mso_opt(0x80, $visible, $color); + $data .= $self->_store_mso_client_anchor(3, @vertices); $data .= $self->_store_mso_client_data(); $length = length $data; @@ -4824,7 +5684,7 @@ sub _store_comments { } - $self->_store_obj_comment($i+1); + $self->_store_obj_comment($num_objects+$i+1); $self->_store_mso_drawing_text_box(); $self->_store_txo($str_len); $self->_store_txo_continue_1($str, $encoding); @@ -4841,7 +5701,8 @@ sub _store_comments { my $author_enc = $comments[$i]->[5]; my $visible = $comments[$i]->[6]; - $self->_store_note($row, $col, $i+1, $author, $author_enc, $visible); + $self->_store_note($row, $col, $num_objects+$i+1, + $author, $author_enc, $visible); } } @@ -4985,6 +5846,7 @@ sub _store_mso_sp { # _store_mso_opt() # # Write the Escher Opt record that is part of MSODRAWING. +# TODO make this _store_mso_opt_comment. # sub _store_mso_opt { @@ -5025,6 +5887,121 @@ sub _store_mso_opt { } +############################################################################### +# +# _store_mso_opt_image() +# +# Write the Escher Opt record that is part of MSODRAWING. +# +sub _store_mso_opt_image { + + my $self = shift; + + my $type = 0xF00B; + my $version = 3; + my $instance = 3; + my $data = ''; + my $length = undef; + my $spid = $_[0]; + + $data = pack 'v', 0x4104; # Blip -> pib + $data .= pack 'V', $spid; + $data .= pack 'v', 0x01BF; # Fill Style -> fNoFillHitTest + $data .= pack 'V', 0x00010000; + $data .= pack 'v', 0x03BF; # Group Shape -> fPrint + $data .= pack 'V', 0x00080000; + + + return $self->_add_mso_generic($type, $version, $instance, $data, $length); +} + + +############################################################################### +# +# _store_mso_opt_chart() +# +# Write the Escher Opt record that is part of MSODRAWING. +# +sub _store_mso_opt_chart { + + my $self = shift; + + my $type = 0xF00B; + my $version = 3; + my $instance = 9; + my $data = ''; + my $length = undef; + + $data = pack 'v', 0x007F; # Protection -> fLockAgainstGrouping + $data .= pack 'V', 0x01040104; + + $data .= pack 'v', 0x00BF; # Text -> fFitTextToShape + $data .= pack 'V', 0x00080008; + + $data .= pack 'v', 0x0181; # Fill Style -> fillColor + $data .= pack 'V', 0x0800004E ; + + $data .= pack 'v', 0x0183; # Fill Style -> fillBackColor + $data .= pack 'V', 0x0800004D; + + $data .= pack 'v', 0x01BF; # Fill Style -> fNoFillHitTest + $data .= pack 'V', 0x00110010; + + $data .= pack 'v', 0x01C0; # Line Style -> lineColor + $data .= pack 'V', 0x0800004D; + + $data .= pack 'v', 0x01FF; # Line Style -> fNoLineDrawDash + $data .= pack 'V', 0x00080008; + + $data .= pack 'v', 0x023F; # Shadow Style -> fshadowObscured + $data .= pack 'V', 0x00020000; + + $data .= pack 'v', 0x03BF; # Group Shape -> fPrint + $data .= pack 'V', 0x00080000; + + + return $self->_add_mso_generic($type, $version, $instance, $data, $length); +} + + +############################################################################### +# +# _store_mso_opt_filter() +# +# Write the Escher Opt record that is part of MSODRAWING. +# +sub _store_mso_opt_filter { + + my $self = shift; + + my $type = 0xF00B; + my $version = 3; + my $instance = 5; + my $data = ''; + my $length = undef; + + + + $data = pack 'v', 0x007F; # Protection -> fLockAgainstGrouping + $data .= pack 'V', 0x01040104; + + $data .= pack 'v', 0x00BF; # Text -> fFitTextToShape + $data .= pack 'V', 0x00080008; + + $data .= pack 'v', 0x01BF; # Fill Style -> fNoFillHitTest + $data .= pack 'V', 0x00010000; + + $data .= pack 'v', 0x01FF; # Line Style -> fNoLineDrawDash + $data .= pack 'V', 0x00080000; + + $data .= pack 'v', 0x03BF; # Group Shape -> fPrint + $data .= pack 'V', 0x000A0000; + + + return $self->_add_mso_generic($type, $version, $instance, $data, $length); +} + + ############################################################################### # # _store_mso_client_anchor() @@ -5041,26 +6018,19 @@ sub _store_mso_client_anchor { my $data = ''; my $length = 18; - my $flag = 3; - my $row = $_[0]; - my $col = $_[1]; - - - my $col_start = $_[2]; # Col containing upper left corner of object - my $x1 = $_[3]; # Distance to left side of object + my $flag = shift; - my $row_start = $_[4]; # Row containing top left corner of object - my $y1 = $_[5]; # Distance to top of object + my $col_start = $_[0]; # Col containing upper left corner of object + my $x1 = $_[1]; # Distance to left side of object - my $col_end = $_[6]; # Col containing lower right corner of object - my $x2 = $_[7]; # Distance to right side of object + my $row_start = $_[2]; # Row containing top left corner of object + my $y1 = $_[3]; # Distance to top of object - my $row_end = $_[8]; # Row containing bottom right corner of object - my $y2 = $_[9]; # Distance to bottom of object - - my $width = $_[10]; # Width of image frame - my $height = $_[11]; # Height of image frame + my $col_end = $_[4]; # Col containing lower right corner of object + my $x2 = $_[5]; # Distance to right side of object + my $row_end = $_[6]; # Row containing bottom right corner of object + my $y2 = $_[7]; # Distance to bottom of object $data = pack "v9", $flag, $col_start, $x1, @@ -5149,6 +6119,184 @@ sub _store_obj_comment { } +############################################################################### +# +# _store_obj_image() +# +# Write the OBJ record that is part of image records. +# +sub _store_obj_image { + + my $self = shift; + + my $record = 0x005D; # Record identifier + my $length = 0x0026; # Bytes to follow + + my $obj_id = $_[0]; # Object ID number. + my $obj_type = 0x0008; # Object type (Picture). + my $data = ''; # Record data. + + my $sub_record = 0x0000; # Sub-record identifier. + my $sub_length = 0x0000; # Length of sub-record. + my $sub_data = ''; # Data of sub-record. + my $options = 0x6011; + my $reserved = 0x0000; + + # Add ftCmo (common object data) subobject + $sub_record = 0x0015; # ftCmo + $sub_length = 0x0012; + $sub_data = pack 'vvvVVV', $obj_type, $obj_id, $options, + $reserved, $reserved, $reserved; + $data = pack 'vv', $sub_record, $sub_length; + $data .= $sub_data; + + + # Add ftCf (Clipboard format) subobject + $sub_record = 0x0007; # ftCf + $sub_length = 0x0002; + $sub_data = pack 'v', 0xFFFF; + $data .= pack 'vv', $sub_record, $sub_length; + $data .= $sub_data; + + # Add ftPioGrbit (Picture option flags) subobject + $sub_record = 0x0008; # ftPioGrbit + $sub_length = 0x0002; + $sub_data = pack 'v', 0x0001; + $data .= pack 'vv', $sub_record, $sub_length; + $data .= $sub_data; + + + # Add ftEnd (end of object) subobject + $sub_record = 0x0000; # ftNts + $sub_length = 0x0000; + $data .= pack 'vv', $sub_record, $sub_length; + + + # Pack the record. + my $header = pack('vv', $record, $length); + + $self->_append($header, $data); + +} + + +############################################################################### +# +# _store_obj_chart() +# +# Write the OBJ record that is part of chart records. +# +sub _store_obj_chart { + + my $self = shift; + + my $record = 0x005D; # Record identifier + my $length = 0x001A; # Bytes to follow + + my $obj_id = $_[0]; # Object ID number. + my $obj_type = 0x0005; # Object type (chart). + my $data = ''; # Record data. + + my $sub_record = 0x0000; # Sub-record identifier. + my $sub_length = 0x0000; # Length of sub-record. + my $sub_data = ''; # Data of sub-record. + my $options = 0x6011; + my $reserved = 0x0000; + + # Add ftCmo (common object data) subobject + $sub_record = 0x0015; # ftCmo + $sub_length = 0x0012; + $sub_data = pack 'vvvVVV', $obj_type, $obj_id, $options, + $reserved, $reserved, $reserved; + $data = pack 'vv', $sub_record, $sub_length; + $data .= $sub_data; + + # Add ftEnd (end of object) subobject + $sub_record = 0x0000; # ftNts + $sub_length = 0x0000; + $data .= pack 'vv', $sub_record, $sub_length; + + + # Pack the record. + my $header = pack('vv', $record, $length); + + $self->_append($header, $data); + +} + + + + +############################################################################### +# +# _store_obj_filter() +# +# Write the OBJ record that is part of filter records. +# +sub _store_obj_filter { + + my $self = shift; + + my $record = 0x005D; # Record identifier + my $length = 0x0046; # Bytes to follow + + my $obj_id = $_[0]; # Object ID number. + my $obj_type = 0x0014; # Object type (combo box). + my $data = ''; # Record data. + + my $sub_record = 0x0000; # Sub-record identifier. + my $sub_length = 0x0000; # Length of sub-record. + my $sub_data = ''; # Data of sub-record. + my $options = 0x2101; + my $reserved = 0x0000; + + # Add ftCmo (common object data) subobject + $sub_record = 0x0015; # ftCmo + $sub_length = 0x0012; + $sub_data = pack 'vvvVVV', $obj_type, $obj_id, $options, + $reserved, $reserved, $reserved; + $data = pack 'vv', $sub_record, $sub_length; + $data .= $sub_data; + + # Add ftSbs Scroll bar subobject + $sub_record = 0x000C; # ftSbs + $sub_length = 0x0014; + $sub_data = pack 'H*', '0000000000000000640001000A00000010000100'; + $data .= pack 'vv', $sub_record, $sub_length; + $data .= $sub_data; + + + # Add ftLbsData (List box data) subobject + $sub_record = 0x0013; # ftLbsData + $sub_length = 0x1FEE; # Special case (undocumented). + + + # If the filter is active we set one of the undocumented flags. + my $col = $_[1]; + + if ($self->{_filter_cols}->{$col}) { + $sub_data = pack 'H*', '000000000100010300000A0008005700'; + } + else { + $sub_data = pack 'H*', '00000000010001030000020008005700'; + } + + $data .= pack 'vv', $sub_record, $sub_length; + $data .= $sub_data; + + + # Add ftEnd (end of object) subobject + $sub_record = 0x0000; # ftNts + $sub_length = 0x0000; + $data .= pack 'vv', $sub_record, $sub_length; + + # Pack the record. + my $header = pack('vv', $record, $length); + + $self->_append($header, $data); +} + + ############################################################################### # # _store_mso_drawing_text_box() diff --git a/t/21_escher.t b/t/21_escher.t index 2a7798c..cc12201 100644 --- a/t/21_escher.t +++ b/t/21_escher.t @@ -14,7 +14,7 @@ use strict; use Spreadsheet::WriteExcel; -use Test::More tests => 41; +use Test::More tests => 42; #use Test::More 'no_plan'; my @tests = ( @@ -133,6 +133,15 @@ my @tests = ( '0D 00 00 08 0C 00 00 08 ' . '17 00 00 08 F7 00 00 10', ], + + [ 'BstoreContainer', # Caption + 0xF001, # Type + 15, # Version + 1, # Instance + '', # Data + 163, # Length + '1F 00 01 F0 A3 00 00 00', # Target + ], ); @@ -356,13 +365,13 @@ $range = 'A1'; $caption = sprintf " \t_store_mso_client_anchor(%s)", $range; @data = $worksheet->_substitute_cellref($range); @data = $worksheet->_comment_params(@data, 'Test'); -@data = ($data[0], $data[0], @{$data[-1]}); +@data = @{$data[-1]}; $target = join ' ', qw( 00 00 10 F0 12 00 00 00 03 00 01 00 F0 00 00 00 1E 00 03 00 F0 00 04 00 78 00 ); -$result = unpack_record($worksheet->_store_mso_client_anchor(@data)); +$result = unpack_record($worksheet->_store_mso_client_anchor(3, @data)); is($result, $target, $caption); @@ -375,13 +384,13 @@ $range = 'A2'; $caption = sprintf " \t_store_mso_client_anchor(%s)", $range; @data = $worksheet->_substitute_cellref($range); @data = $worksheet->_comment_params(@data, 'Test'); -@data = ($data[0], $data[0], @{$data[-1]}); +@data = @{$data[-1]}; $target = join ' ', qw( 00 00 10 F0 12 00 00 00 03 00 01 00 F0 00 00 00 69 00 03 00 F0 00 04 00 C4 00 ); -$result = unpack_record($worksheet->_store_mso_client_anchor(@data)); +$result = unpack_record($worksheet->_store_mso_client_anchor(3, @data)); is($result, $target, $caption); @@ -394,13 +403,13 @@ $range = 'A3'; $caption = sprintf " \t_store_mso_client_anchor(%s)", $range; @data = $worksheet->_substitute_cellref($range); @data = $worksheet->_comment_params(@data, 'Test'); -@data = ($data[0], $data[0], @{$data[-1]}); +@data = @{$data[-1]}; $target = join ' ', qw( 00 00 10 F0 12 00 00 00 03 00 01 00 F0 00 01 00 69 00 03 00 F0 00 05 00 C4 00 ); -$result = unpack_record($worksheet->_store_mso_client_anchor(@data)); +$result = unpack_record($worksheet->_store_mso_client_anchor(3, @data)); is($result, $target, $caption); @@ -413,13 +422,13 @@ $range = 'A65534'; $caption = sprintf " \t_store_mso_client_anchor(%s)", $range; @data = $worksheet->_substitute_cellref($range); @data = $worksheet->_comment_params(@data, 'Test'); -@data = ($data[0], $data[0], @{$data[-1]}); +@data = @{$data[-1]}; $target = join ' ', qw( 00 00 10 F0 12 00 00 00 03 00 01 00 F0 00 F9 FF 3C 00 03 00 F0 00 FD FF 97 00 ); -$result = unpack_record($worksheet->_store_mso_client_anchor(@data)); +$result = unpack_record($worksheet->_store_mso_client_anchor(3, @data)); is($result, $target, $caption); @@ -432,13 +441,13 @@ $range = 'A65535'; $caption = sprintf " \t_store_mso_client_anchor(%s)", $range; @data = $worksheet->_substitute_cellref($range); @data = $worksheet->_comment_params(@data, 'Test'); -@data = ($data[0], $data[0], @{$data[-1]}); +@data = @{$data[-1]}; $target = join ' ', qw( 00 00 10 F0 12 00 00 00 03 00 01 00 F0 00 FA FF 3C 00 03 00 F0 00 FE FF 97 00 ); -$result = unpack_record($worksheet->_store_mso_client_anchor(@data)); +$result = unpack_record($worksheet->_store_mso_client_anchor(3, @data)); is($result, $target, $caption); @@ -451,13 +460,13 @@ $range = 'A65536'; $caption = sprintf " \t_store_mso_client_anchor(%s)", $range; @data = $worksheet->_substitute_cellref($range); @data = $worksheet->_comment_params(@data, 'Test'); -@data = ($data[0], $data[0], @{$data[-1]}); +@data = @{$data[-1]}; $target = join ' ', qw( 00 00 10 F0 12 00 00 00 03 00 01 00 F0 00 FB FF 1E 00 03 00 F0 00 FF FF 78 00 ); -$result = unpack_record($worksheet->_store_mso_client_anchor(@data)); +$result = unpack_record($worksheet->_store_mso_client_anchor(3, @data)); is($result, $target, $caption); @@ -470,13 +479,13 @@ $range = 'IT3'; $caption = sprintf " \t_store_mso_client_anchor(%s)", $range; @data = $worksheet->_substitute_cellref($range); @data = $worksheet->_comment_params(@data, 'Test'); -@data = ($data[0], $data[0], @{$data[-1]}); +@data = @{$data[-1]}; $target = join ' ', qw( 00 00 10 F0 12 00 00 00 03 00 FA 00 10 03 01 00 69 00 FC 00 10 03 05 00 C4 00 ); -$result = unpack_record($worksheet->_store_mso_client_anchor(@data)); +$result = unpack_record($worksheet->_store_mso_client_anchor(3, @data)); is($result, $target, $caption); @@ -489,13 +498,13 @@ $range = 'IU3'; $caption = sprintf " \t_store_mso_client_anchor(%s)", $range; @data = $worksheet->_substitute_cellref($range); @data = $worksheet->_comment_params(@data, 'Test'); -@data = ($data[0], $data[0], @{$data[-1]}); +@data = @{$data[-1]}; $target = join ' ', qw( 00 00 10 F0 12 00 00 00 03 00 FB 00 10 03 01 00 69 00 FD 00 10 03 05 00 C4 00 ); -$result = unpack_record($worksheet->_store_mso_client_anchor(@data)); +$result = unpack_record($worksheet->_store_mso_client_anchor(3, @data)); is($result, $target, $caption); @@ -508,13 +517,13 @@ $range = 'IV3'; $caption = sprintf " \t_store_mso_client_anchor(%s)", $range; @data = $worksheet->_substitute_cellref($range); @data = $worksheet->_comment_params(@data, 'Test'); -@data = ($data[0], $data[0], @{$data[-1]}); +@data = @{$data[-1]}; $target = join ' ', qw( 00 00 10 F0 12 00 00 00 03 00 FC 00 10 03 01 00 69 00 FE 00 10 03 05 00 C4 00 ); -$result = unpack_record($worksheet->_store_mso_client_anchor(@data)); +$result = unpack_record($worksheet->_store_mso_client_anchor(3, @data)); is($result, $target, $caption); @@ -528,14 +537,14 @@ $range = 'A3'; $caption = sprintf " \t_store_mso_client_anchor(%s). Cell offsets changed.", $range; @data = $worksheet->_substitute_cellref($range); @data = $worksheet->_comment_params(@data, 'Test', x_offset => 18, y_offset => 9); -@data = ($data[0], $data[0], @{$data[-1]}); +@data = @{$data[-1]}; $target = join ' ', qw( 00 00 10 F0 12 00 00 00 03 00 01 00 20 01 01 00 88 00 03 00 20 01 05 00 E2 00 ); -$result = unpack_record($worksheet->_store_mso_client_anchor(@data)); +$result = unpack_record($worksheet->_store_mso_client_anchor(3, @data)); is($result, $target, $caption); @@ -549,14 +558,14 @@ $range = 'A3'; $caption = sprintf " \t_store_mso_client_anchor(%s). Dimensions changed.", $range; @data = $worksheet->_substitute_cellref($range); @data = $worksheet->_comment_params(@data, 'Test', x_scale => 3, y_scale => 2); -@data = ($data[0], $data[0], @{$data[-1]}); +@data = @{$data[-1]}; $target = join ' ', qw( 00 00 10 F0 12 00 00 00 03 00 01 00 F0 00 01 00 69 00 07 00 F0 00 0A 00 1E 00 ); -$result = unpack_record($worksheet->_store_mso_client_anchor(@data)); +$result = unpack_record($worksheet->_store_mso_client_anchor(3, @data)); is($result, $target, $caption); @@ -570,14 +579,14 @@ $range = 'A3'; $caption = sprintf " \t_store_mso_client_anchor(%s). Dimensions changed.", $range; @data = $worksheet->_substitute_cellref($range); @data = $worksheet->_comment_params(@data, 'Test', width => 385, height => 149); -@data = ($data[0], $data[0], @{$data[-1]}); +@data = @{$data[-1]}; $target = join ' ', qw( 00 00 10 F0 12 00 00 00 03 00 01 00 F0 00 01 00 69 00 07 00 F0 00 0A 00 1E 00 ); -$result = unpack_record($worksheet->_store_mso_client_anchor(@data)); +$result = unpack_record($worksheet->_store_mso_client_anchor(3, @data)); is($result, $target, $caption); @@ -592,14 +601,14 @@ $worksheet->set_column('G:G', 20); $caption = sprintf " \t_store_mso_client_anchor(%s). Col width changed.", $range; @data = $worksheet->_substitute_cellref($range); @data = $worksheet->_comment_params(@data, 'Test'); -@data = ($data[0], $data[0], @{$data[-1]}); +@data = @{$data[-1]}; $target = join ' ', qw( 00 00 10 F0 12 00 00 00 03 00 06 00 6A 00 01 00 69 00 06 00 F2 03 05 00 C4 00 ); -$result = unpack_record($worksheet->_store_mso_client_anchor(@data)); +$result = unpack_record($worksheet->_store_mso_client_anchor(3, @data)); is($result, $target, $caption); @@ -614,14 +623,14 @@ $worksheet->set_column('L:O', 4); $caption = sprintf " \t_store_mso_client_anchor(%s). Col width changed.", $range; @data = $worksheet->_substitute_cellref($range); @data = $worksheet->_comment_params(@data, 'Test'); -@data = ($data[0], $data[0], @{$data[-1]}); +@data = @{$data[-1]}; $target = join ' ', qw( 00 00 10 F0 12 00 00 00 03 00 0B 00 D1 01 01 00 69 00 0F 00 B0 00 05 00 C4 00 ); -$result = unpack_record($worksheet->_store_mso_client_anchor(@data)); +$result = unpack_record($worksheet->_store_mso_client_anchor(3, @data)); is($result, $target, $caption); @@ -639,14 +648,14 @@ $worksheet->set_row(8, 6); $caption = sprintf " \t_store_mso_client_anchor(%s). Row height changed.", $range; @data = $worksheet->_substitute_cellref($range); @data = $worksheet->_comment_params(@data, 'Test'); -@data = ($data[0], $data[0], @{$data[-1]}); +@data = @{$data[-1]}; $target = join ' ', qw( 00 00 10 F0 12 00 00 00 03 00 01 00 F0 00 04 00 69 00 03 00 F0 00 0A 00 E2 00 ); -$result = unpack_record($worksheet->_store_mso_client_anchor(@data)); +$result = unpack_record($worksheet->_store_mso_client_anchor(3, @data)); is($result, $target, $caption); @@ -661,14 +670,14 @@ $worksheet->set_row(14, 60); $caption = sprintf " \t_store_mso_client_anchor(%s). Row height changed.", $range; @data = $worksheet->_substitute_cellref($range); @data = $worksheet->_comment_params(@data, 'Test'); -@data = ($data[0], $data[0], @{$data[-1]}); +@data = @{$data[-1]}; $target = join ' ', qw( 00 00 10 F0 12 00 00 00 03 00 01 00 F0 00 0D 00 69 00 03 00 F0 00 0E 00 CD 00 ); -$result = unpack_record($worksheet->_store_mso_client_anchor(@data)); +$result = unpack_record($worksheet->_store_mso_client_anchor(3, @data)); is($result, $target, $caption); @@ -737,17 +746,3 @@ unlink $test_file; __END__ - - - -0 - -93 00 0B F0 36 00 00 00 -80 00 E8 1E 5C 01 BF 00 08 00 08 00 -58 01 00 00 00 00 81 01 50 00 00 08 83 01 50 00 -00 08 BF 01 10 00 11 00 01 02 00 00 00 00 3F 02 -03 00 03 00 BF 03 02 00 0A 00 - -00 00 10 F0 12 00 -00 00 03 00 01 00 F0 00 01 00 69 00 03 00 F0 00 -05 00 C4 00 00 00 11 F0 00 00 00 00 diff --git a/t/22_mso_drawing_group.t b/t/22_mso_drawing_group.t index 1aca710..1ddc576 100644 --- a/t/22_mso_drawing_group.t +++ b/t/22_mso_drawing_group.t @@ -74,7 +74,7 @@ $caption .= ' (params)'; ); for my $sheet ($workbook->sheets()) { - push @result_ids, @{$sheet->{_comments_ids}}; + push @result_ids, @{$sheet->{_object_ids}}; } is_deeply(\@result_ids, \@target_ids , $caption); @@ -117,7 +117,7 @@ $caption .= ' (params)'; ); for my $sheet ($workbook->sheets()) { - push @result_ids, @{$sheet->{_comments_ids}}; + push @result_ids, @{$sheet->{_object_ids}}; } is_deeply(\@result_ids, \@target_ids , $caption); @@ -160,7 +160,7 @@ $caption .= ' (params)'; ); for my $sheet ($workbook->sheets()) { - push @result_ids, @{$sheet->{_comments_ids}}; + push @result_ids, @{$sheet->{_object_ids}}; } is_deeply(\@result_ids, \@target_ids , $caption); @@ -203,7 +203,7 @@ $caption .= ' (params)'; ); for my $sheet ($workbook->sheets()) { - push @result_ids, @{$sheet->{_comments_ids}}; + push @result_ids, @{$sheet->{_object_ids}}; } is_deeply(\@result_ids, \@target_ids , $caption); @@ -247,7 +247,7 @@ $caption .= ' (params)'; ); for my $sheet ($workbook->sheets()) { - push @result_ids, @{$sheet->{_comments_ids}}; + push @result_ids, @{$sheet->{_object_ids}}; } is_deeply(\@result_ids, \@target_ids , $caption); @@ -291,7 +291,7 @@ $caption .= ' (params)'; ); for my $sheet ($workbook->sheets()) { - push @result_ids, @{$sheet->{_comments_ids}}; + push @result_ids, @{$sheet->{_object_ids}}; } is_deeply(\@result_ids, \@target_ids , $caption); @@ -340,7 +340,7 @@ $caption .= ' (params)'; ); for my $sheet ($workbook->sheets()) { - push @result_ids, @{$sheet->{_comments_ids}}; + push @result_ids, @{$sheet->{_object_ids}}; } is_deeply(\@result_ids, \@target_ids , $caption); @@ -389,7 +389,7 @@ $caption .= ' (params)'; ); for my $sheet ($workbook->sheets()) { - push @result_ids, @{$sheet->{_comments_ids}}; + push @result_ids, @{$sheet->{_object_ids}}; } is_deeply(\@result_ids, \@target_ids , $caption); @@ -438,7 +438,7 @@ $caption .= ' (params)'; ); for my $sheet ($workbook->sheets()) { - push @result_ids, @{$sheet->{_comments_ids}}; + push @result_ids, @{$sheet->{_object_ids}}; } is_deeply(\@result_ids, \@target_ids , $caption); @@ -487,7 +487,7 @@ $caption .= ' (params)'; ); for my $sheet ($workbook->sheets()) { - push @result_ids, @{$sheet->{_comments_ids}}; + push @result_ids, @{$sheet->{_object_ids}}; } is_deeply(\@result_ids, \@target_ids , $caption); @@ -537,7 +537,7 @@ $caption .= ' (params)'; ); for my $sheet ($workbook->sheets()) { - push @result_ids, @{$sheet->{_comments_ids}}; + push @result_ids, @{$sheet->{_object_ids}}; } is_deeply(\@result_ids, \@target_ids , $caption); @@ -586,7 +586,7 @@ $caption .= ' (params)'; ); for my $sheet ($workbook->sheets()) { - push @result_ids, @{$sheet->{_comments_ids}}; + push @result_ids, @{$sheet->{_object_ids}}; } is_deeply(\@result_ids, \@target_ids , $caption); @@ -639,7 +639,7 @@ $caption .= ' (params)'; ); for my $sheet ($workbook->sheets()) { - push @result_ids, @{$sheet->{_comments_ids}}; + push @result_ids, @{$sheet->{_object_ids}}; } is_deeply(\@result_ids, \@target_ids , $caption); @@ -692,7 +692,7 @@ $caption .= ' (params)'; ); for my $sheet ($workbook->sheets()) { - push @result_ids, @{$sheet->{_comments_ids}}; + push @result_ids, @{$sheet->{_object_ids}}; } is_deeply(\@result_ids, \@target_ids , $caption); @@ -746,7 +746,7 @@ $caption .= ' (params)'; ); for my $sheet ($workbook->sheets()) { - push @result_ids, @{$sheet->{_comments_ids}}; + push @result_ids, @{$sheet->{_object_ids}}; } is_deeply(\@result_ids, \@target_ids , $caption); @@ -800,7 +800,7 @@ $caption .= ' (params)'; ); for my $sheet ($workbook->sheets()) { - push @result_ids, @{$sheet->{_comments_ids}}; + push @result_ids, @{$sheet->{_object_ids}}; } is_deeply(\@result_ids, \@target_ids , $caption); @@ -860,7 +860,7 @@ $caption .= ' (params)'; ); for my $sheet ($workbook->sheets()) { - push @result_ids, @{$sheet->{_comments_ids}}; + push @result_ids, @{$sheet->{_object_ids}}; } is_deeply(\@result_ids, \@target_ids , $caption); diff --git a/t/26_autofilter.t b/t/26_autofilter.t new file mode 100644 index 0000000..94bb8e1 --- /dev/null +++ b/t/26_autofilter.t @@ -0,0 +1,348 @@ +#!/usr/bin/perl -w + +############################################################################### +# +# A test for Spreadsheet::WriteExcel. +# +# Tests for the internal methods used to write the AUTOFILTER record. +# +# reverse('©'), August 2007, John McNamara, jmcnamara@cpan.org +# + + +use strict; + +use Spreadsheet::WriteExcel; +use Test::More tests => 29; +#use Test::More 'no_plan'; + + +############################################################################### +# +# Tests setup +# +my $test_file = "temp_test_file.xls"; +my $workbook = Spreadsheet::WriteExcel->new($test_file); +my $worksheet = $workbook->add_worksheet(); + + +############################################################################### +# +# Test cases. +# +my @tests = ( + { + 'column' => 0, + 'expression' => 'x = Blanks', + 'data' => [qw( + 9E 00 18 00 00 00 84 32 0C 02 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 + + )], + }, + { + 'column' => 1, + 'expression' => 'x = Nonblanks', + 'data' => [qw( + 9E 00 18 00 01 00 84 32 0E 05 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 + + )], + }, + { + 'column' => 2, + 'expression' => 'x > 1.001', + 'data' => [qw( + 9E 00 18 00 02 00 80 32 04 04 6A BC 74 93 18 04 + F0 3F 00 00 00 00 00 00 00 00 00 00 + + )], + }, + { + 'column' => 3, + 'expression' => 'x >= 1.001', + 'data' => [qw( + 9E 00 18 00 03 00 80 32 04 06 6A BC 74 93 18 04 + F0 3F 00 00 00 00 00 00 00 00 00 00 + + )], + }, + { + 'column' => 4, + 'expression' => 'x < 1.001', + 'data' => [qw( + 9E 00 18 00 04 00 80 32 04 01 6A BC 74 93 18 04 + F0 3F 00 00 00 00 00 00 00 00 00 00 + + )], + }, + { + 'column' => 5, + 'expression' => 'x <= 1.001', + 'data' => [qw( + 9E 00 18 00 05 00 80 32 04 03 6A BC 74 93 18 04 + F0 3F 00 00 00 00 00 00 00 00 00 00 + + )], + }, + { + 'column' => 6, + 'expression' => 'x > 1.001 and x <= 5.001', + 'data' => [qw( + 9E 00 18 00 06 00 80 32 04 04 6A BC 74 93 18 04 + F0 3F 04 03 1B 2F DD 24 06 01 14 40 + + )], + }, + { + 'column' => 7, + 'expression' => 'x > 1.001 or x <= 5.001', + 'data' => [qw( + 9E 00 18 00 07 00 81 32 04 04 6A BC 74 93 18 04 + F0 3F 04 03 1B 2F DD 24 06 01 14 40 + + )], + }, + { + 'column' => 8, + 'expression' => 'x <> 2.001', + 'data' => [qw( + 9E 00 18 00 08 00 80 32 04 05 35 5E BA 49 0C 02 + 00 40 00 00 00 00 00 00 00 00 00 00 + + )], + }, + { + 'column' => 9, + 'expression' => 'x = 1.001', + 'data' => [qw( + 9E 00 1E 00 09 00 84 32 06 02 00 00 00 00 05 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 31 2E 30 + 30 31 + + )], + }, + { + 'column' => 10, + 'expression' => 'x = West', + 'data' => [qw( + 9E 00 1D 00 0A 00 84 32 06 02 00 00 00 00 04 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 57 65 73 + 74 + + )], + }, + { + 'column' => 11, + 'expression' => 'x = East', + 'data' => [qw( + 9E 00 1D 00 0B 00 84 32 06 02 00 00 00 00 04 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 45 61 73 + 74 + + )], + }, + { + 'column' => 12, + 'expression' => 'x <> West', + 'data' => [qw( + 9E 00 1D 00 0C 00 80 32 06 05 00 00 00 00 04 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 57 65 73 + 74 + + )], + }, + { + 'column' => 13, + 'expression' => 'x =~ b*', + 'data' => [qw( + 9E 00 1B 00 0D 00 80 32 06 02 00 00 00 00 02 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 62 2A + + )], + }, + { + 'column' => 14, + 'expression' => 'x !~ b*', + 'data' => [qw( + 9E 00 1B 00 0E 00 80 32 06 05 00 00 00 00 02 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 62 2A + + )], + }, + { + 'column' => 15, + 'expression' => 'x =~ *b', + 'data' => [qw( + 9E 00 1B 00 0F 00 80 32 06 02 00 00 00 00 02 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 2A 62 + + )], + }, + { + 'column' => 16, + 'expression' => 'x !~ *b', + 'data' => [qw( + 9E 00 1B 00 10 00 80 32 06 05 00 00 00 00 02 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 2A 62 + + )], + }, + { + 'column' => 17, + 'expression' => 'x =~ *b*', + 'data' => [qw( + 9E 00 1C 00 11 00 80 32 06 02 00 00 00 00 03 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 2A 62 2A + + )], + }, + { + 'column' => 18, + 'expression' => 'x !~ *b*', + 'data' => [qw( + 9E 00 1C 00 12 00 80 32 06 05 00 00 00 00 03 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 2A 62 2A + + )], + }, + { + 'column' => 19, + 'expression' => 'x = fo?', + 'data' => [qw( + 9E 00 1C 00 13 00 80 32 06 02 00 00 00 00 03 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 66 6F 3F + + )], + }, + { + 'column' => 20, + 'expression' => 'x = fo~?', + 'data' => [qw( + 9E 00 1D 00 14 00 80 32 06 02 00 00 00 00 04 00 + 00 00 00 00 00 00 00 00 00 00 00 00 00 66 6F 7E + 3F + + )], + }, + { + 'column' => 21, + 'expression' => 'x = East and x = West', + 'data' => [qw( + 9E 00 22 00 15 00 8C 32 06 02 00 00 00 00 04 00 + 00 00 06 02 00 00 00 00 04 00 00 00 00 45 61 73 + 74 00 57 65 73 74 + + )], + }, + { + 'column' => 22, + 'expression' => 'top 10 items', + 'data' => [qw( + 9E 00 18 00 16 00 30 05 04 06 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 + + )], + }, + { + 'column' => 23, + 'expression' => 'top 10 %', + 'data' => [qw( + 9E 00 18 00 17 00 70 05 04 06 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 + + )], + }, + { + 'column' => 24, + 'expression' => 'bottom 10 items', + 'data' => [qw( + 9E 00 18 00 18 00 10 05 04 03 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 + + )], + }, + { + 'column' => 25, + 'expression' => 'bottom 10 %', + 'data' => [qw( + 9E 00 18 00 19 00 50 05 04 03 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 + + )], + }, + { + 'column' => 26, + 'expression' => 'top 5 items', + 'data' => [qw( + 9E 00 18 00 1A 00 B0 02 04 06 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 + + )], + }, + { + 'column' => 27, + 'expression' => 'top 100 items', + 'data' => [qw( + 9E 00 18 00 1B 00 30 32 04 06 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 + + )], + }, + { + 'column' => 28, + 'expression' => 'top 101 items', + 'data' => [qw( + 9E 00 18 00 1C 00 B0 32 04 06 00 00 00 00 00 00 + 00 00 00 00 00 00 00 00 00 00 00 00 + + )], + }, + +); + + +############################################################################### +# +# Run tests. +# +for my $test (@tests) { + + my $column = $test->{column}; + my $expression = $test->{expression}; + my @tokens = $worksheet->_extract_filter_tokens($expression); + @tokens = $worksheet->_parse_filter_expression($expression, @tokens); + + my $result = $worksheet->_store_autofilter($column , @tokens); + + my $target = join " ", @{$test->{data}}; + + my $caption = " \tfilter_column($column, '$expression')"; + + $result = unpack_record($result); + is($result, $target, $caption); + +} + +# +# Helper functions. +# + +############################################################################### +# +# Unpack the binary data into a format suitable for printing in tests. +# +sub unpack_record { + return join ' ', map {sprintf "%02X", $_} unpack "C*", $_[0]; +} + + +# Cleanup +$workbook->close(); +unlink $test_file; + + +__END__ + + + diff --git a/t/27_autofilter.t b/t/27_autofilter.t new file mode 100644 index 0000000..85ff685 --- /dev/null +++ b/t/27_autofilter.t @@ -0,0 +1,145 @@ +#!/usr/bin/perl -w + +############################################################################### +# +# A test for Spreadsheet::WriteExcel. +# +# Tests for the token extraction method used to parse autofilter expressions. +# +# reverse('©'), August 2007, John McNamara, jmcnamara@cpan.org +# + + +use strict; + +use Spreadsheet::WriteExcel; +use Test::More tests => 18; +#use Test::More 'no_plan'; + + +############################################################################### +# +# Tests setup +# +my $test_file = "temp_test_file.xls"; +my $workbook = Spreadsheet::WriteExcel->new($test_file); +my $worksheet = $workbook->add_worksheet(); + + +############################################################################### +# +# Test cases structured as [$input, [@expected_output]] +# +my @tests = ( + [ + undef, + [], + ], + + [ + '', + [], + ], + + [ + '0 < 2000', + [0, '<', 2000], + ], + + [ + 'x < 2000', + ['x', '<', 2000], + ], + + [ + 'x > 2000', + ['x', '>', 2000], + ], + + [ + 'x == 2000', + ['x', '==', 2000], + ], + + [ + 'x > 2000 and x < 5000', + ['x', '>', 2000, 'and', 'x', '<', 5000], + ], + + [ + 'x = "foo"', + ['x', '=', 'foo'], + ], + + [ + 'x = foo', + ['x', '=', 'foo'], + ], + + [ + 'x = "foo bar"', + ['x', '=', 'foo bar'], + ], + + [ + 'x = "foo "" bar"', + ['x', '=', 'foo " bar'], + ], + + [ + 'x = "foo bar" or x = "bar foo"', + ['x', '=', 'foo bar', 'or', 'x', '=', 'bar foo'], + ], + + [ + 'x = "foo "" bar" or x = "bar "" foo"', + ['x', '=', 'foo " bar', 'or', 'x', '=', 'bar " foo'], + ], + + [ + 'x = """"""""', + ['x', '=', '"""'], + ], + + [ + 'x = Blanks', + ['x', '=', 'Blanks'], + ], + + [ + 'x = NonBlanks', + ['x', '=', 'NonBlanks'], + ], + + [ + 'top 10 %', + ['top', 10, '%'], + ], + + [ + 'top 10 items', + ['top', 10, 'items'], + ], + +); + + +############################################################################### +# +# Run the test cases. +# +for my $aref (@tests) { + my $expression = $aref->[0]; + my $expected = $aref->[1]; + my @results = $worksheet->_extract_filter_tokens($expression); + + my $testname = $expression || 'none'; + + is_deeply(\@results, $expected, " \t" . $testname); +} + +# Cleanup +$workbook->close(); +unlink $test_file; + +__END__ diff --git a/t/28_autofilter.t b/t/28_autofilter.t new file mode 100644 index 0000000..fe185dd --- /dev/null +++ b/t/28_autofilter.t @@ -0,0 +1,176 @@ +#!/usr/bin/perl -w + +############################################################################### +# +# A test for Spreadsheet::WriteExcel. +# +# Tests for the token parsing methods used to parse autofilter expressions. +# +# reverse('©'), August 2007, John McNamara, jmcnamara@cpan.org +# + +use strict; + +use Spreadsheet::WriteExcel; +use Test::More tests => 24; +#use Test::More 'no_plan'; + + +############################################################################### +# +# Tests setup +# +my $test_file = "temp_test_file.xls"; +my $workbook = Spreadsheet::WriteExcel->new($test_file); +my $worksheet = $workbook->add_worksheet(); + + +############################################################################### +# +# Test cases structured as [$input, [@expected_output]] +# +my @tests = ( + + [ + 'x = 2000', + [2, 2000], + ], + + [ + 'x == 2000', + [2, 2000], + ], + + [ + 'x =~ 2000', + [2, 2000], + ], + + [ + 'x eq 2000', + [2, 2000], + ], + + [ + 'x <> 2000', + [5, 2000], + ], + + [ + 'x != 2000', + [5, 2000], + ], + + [ + 'x ne 2000', + [5, 2000], + ], + + [ + 'x !~ 2000', + [5, 2000], + ], + + [ + 'x > 2000', + [4, 2000], + ], + + [ + 'x < 2000', + [1, 2000], + ], + + [ + 'x >= 2000', + [6, 2000], + ], + + [ + 'x <= 2000', + [3, 2000], + ], + + [ + 'x > 2000 and x < 5000', + [4, 2000, 0, 1, 5000], + ], + + [ + 'x > 2000 && x < 5000', + [4, 2000, 0, 1, 5000], + ], + + [ + 'x > 2000 or x < 5000', + [4, 2000, 1, 1, 5000], + ], + + [ + 'x > 2000 || x < 5000', + [4, 2000, 1, 1, 5000], + ], + + [ + 'x = Blanks', + [2, 'blanks'], + ], + + [ + 'x = NonBlanks', + [2, 'nonblanks'], + ], + + [ + 'x <> Blanks', + [2, 'nonblanks'], + ], + + [ + 'x <> NonBlanks', + [2, 'blanks'], + ], + + [ + 'Top 10 Items', + [30, 10], + ], + + [ + 'Top 20 %', + [31, 20], + ], + + [ + 'Bottom 5 Items', + [32, 5], + ], + + [ + 'Bottom 101 %', + [33, 101], + ], + + +); + + +############################################################################### +# +# Run the test cases. +# +for my $aref (@tests) { + my $expression = $aref->[0]; + my $expected = $aref->[1]; + my @tokens = $worksheet->_extract_filter_tokens($expression); + my @results = $worksheet->_parse_filter_expression($expression, @tokens); + + my $testname = $expression || 'none'; + + is_deeply(\@results, $expected, " \t" . $testname); +} + + +# Cleanup +$workbook->close(); +unlink $test_file; \ No newline at end of file