use
vars
qw($VERSION @ISA)
;
@ISA
=
qw(Spreadsheet::WriteExcel::BIFFwriter Exporter)
;
$VERSION
=
'2.40'
;
sub
new {
my
$class
=
shift
;
my
$self
= Spreadsheet::WriteExcel::BIFFwriter->new();
my
$byte_order
=
$self
->{_byte_order};
my
$parser
= Spreadsheet::WriteExcel::Formula->new(
$byte_order
);
$self
->{_filename} =
$_
[0] ||
''
;
$self
->{_parser} =
$parser
;
$self
->{_tempdir} =
undef
;
$self
->{_1904} = 0;
$self
->{_activesheet} = 0;
$self
->{_firstsheet} = 0;
$self
->{_selected} = 0;
$self
->{_xf_index} = 0;
$self
->{_fileclosed} = 0;
$self
->{_biffsize} = 0;
$self
->{_sheet_name} =
'Sheet'
;
$self
->{_chart_name} =
'Chart'
;
$self
->{_sheet_count} = 0;
$self
->{_chart_count} = 0;
$self
->{_url_format} =
''
;
$self
->{_codepage} = 0x04E4;
$self
->{_country} = 1;
$self
->{_worksheets} = [];
$self
->{_sheetnames} = [];
$self
->{_formats} = [];
$self
->{_palette} = [];
$self
->{_using_tmpfile} = 1;
$self
->{_filehandle} =
""
;
$self
->{_temp_file} =
""
;
$self
->{_internal_fh} = 0;
$self
->{_fh_out} =
""
;
$self
->{_str_total} = 0;
$self
->{_str_unique} = 0;
$self
->{_str_table} = {};
$self
->{_str_array} = [];
$self
->{_str_block_sizes} = [];
$self
->{_extsst_offsets} = [];
$self
->{_extsst_buckets} = 0;
$self
->{_extsst_bucket_size} = 0;
$self
->{_ext_ref_count} = 0;
$self
->{_ext_refs} = {};
$self
->{_mso_clusters} = [];
$self
->{_mso_size} = 0;
$self
->{_hideobj} = 0;
$self
->{_compatibility} = 0;
$self
->{_add_doc_properties} = 0;
$self
->{_localtime} = [
localtime
()];
$self
->{_defined_names} = [];
bless
$self
,
$class
;
$self
->add_format(
type
=> 1);
$self
->add_format(
type
=> 1);
$self
->add_format(
type
=> 1);
$self
->add_format(
type
=> 1);
$self
->add_format(
type
=> 1);
$self
->add_format(
type
=> 1);
$self
->add_format(
type
=> 1);
$self
->add_format(
type
=> 1);
$self
->add_format(
type
=> 1);
$self
->add_format(
type
=> 1);
$self
->add_format(
type
=> 1);
$self
->add_format(
type
=> 1);
$self
->add_format(
type
=> 1);
$self
->add_format(
type
=> 1);
$self
->add_format(
type
=> 1);
$self
->add_format();
$self
->add_format(
type
=> 1,
num_format
=> 0x2B);
$self
->add_format(
type
=> 1,
num_format
=> 0x29);
$self
->add_format(
type
=> 1,
num_format
=> 0x2C);
$self
->add_format(
type
=> 1,
num_format
=> 0x2A);
$self
->add_format(
type
=> 1,
num_format
=> 0x09);
$self
->{_url_format} =
$self
->add_format(
color
=>
'blue'
,
underline
=> 1);
if
(not
ref
$self
->{_filename} and
$self
->{_filename} eq
''
) {
carp
'Filename required by Spreadsheet::WriteExcel->new()'
;
return
undef
;
}
if
(not
ref
$self
->{_filename}) {
my
$fh
= FileHandle->new(
'>'
.
$self
->{_filename});
if
(not
defined
$fh
) {
carp
"Can't open "
.
$self
->{_filename} .
". It may be in use or protected"
;
return
undef
;
}
binmode
(
$fh
);
$self
->{_internal_fh} = 1;
$self
->{_fh_out} =
$fh
;
}
else
{
$self
->{_internal_fh} = 0;
$self
->{_fh_out} =
$self
->{_filename};
}
$self
->set_palette_xl97();
require
Encode
if
$] >= 5.008;
$self
->_initialize();
$self
->_get_checksum_method();
return
$self
;
}
sub
_initialize {
my
$self
=
shift
;
my
$fh
;
my
$tmp_dir
;
if
(
defined
$self
->{_tempdir}) {
die
"The File::Temp module must be installed in order "
.
"to call set_tempdir().\n"
if
$@;
eval
{
$fh
= File::Temp::tempfile(
DIR
=>
$self
->{_tempdir}) };
$tmp_dir
=
$self
->{_tempdir} || File::Spec->tmpdir
if
not
$fh
;
}
else
{
$fh
= IO::File->new_tmpfile();
$tmp_dir
=
"POSIX::tmpnam() directory"
if
not
$fh
;
}
if
(
$fh
) {
binmode
(
$fh
);
$self
->{_filehandle} =
$fh
;
}
else
{
$self
->{_using_tmpfile} = 0;
if
($^W) {
my
$dir
=
$self
->{_tempdir} || File::Spec->tmpdir();
warn
"Unable to create temp files in $tmp_dir. Data will be "
.
"stored in memory. Refer to set_tempdir() in the "
.
"Spreadsheet::WriteExcel documentation.\n"
;
}
}
}
sub
_get_checksum_method {
my
$self
=
shift
;
if
(not $@) {
$self
->{_checksum_method} = 1;
return
;
}
if
(not $@) {
$self
->{_checksum_method} = 2;
return
;
}
if
(not $@) {
$self
->{_checksum_method} = 3;
return
;
}
$self
->{_checksum_method} = 0;
}
sub
_append {
my
$self
=
shift
;
my
$data
=
''
;
if
(
$self
->{_using_tmpfile}) {
$data
=
join
(
''
,
@_
);
$data
=
$self
->_add_continue(
$data
)
if
length
(
$data
) >
$self
->{_limit};
local
$\ =
undef
;
print
{
$self
->{_filehandle}}
$data
;
$self
->{_datasize} +=
length
(
$data
);
}
else
{
$data
=
$self
->SUPER::_append(
@_
);
}
return
$data
;
}
sub
get_data {
my
$self
=
shift
;
my
$buffer
= 4096;
my
$tmp
;
if
(
defined
$self
->{_data}) {
$tmp
=
$self
->{_data};
$self
->{_data} =
undef
;
my
$fh
=
$self
->{_filehandle};
seek
(
$fh
, 0, 0)
if
$self
->{_using_tmpfile};
return
$tmp
;
}
if
(
$self
->{_using_tmpfile}) {
return
$tmp
if
read
(
$self
->{_filehandle},
$tmp
,
$buffer
);
}
return
undef
;
}
sub
close
{
my
$self
=
shift
;
return
if
$self
->{_fileclosed};
$self
->{_fileclosed} = 1;
return
$self
->_store_workbook();
}
sub
DESTROY {
my
$self
=
shift
;
local
($@, $!, $^E, $?);
$self
->
close
()
if
not
$self
->{_fileclosed};
}
sub
sheets {
my
$self
=
shift
;
if
(
@_
) {
return
@{
$self
->{_worksheets}}[
@_
];
}
else
{
return
@{
$self
->{_worksheets}};
}
}
sub
worksheets {
my
$self
=
shift
;
return
$self
->{_worksheets};
}
sub
add_worksheet {
my
$self
=
shift
;
my
$index
= @{
$self
->{_worksheets}};
my
(
$name
,
$encoding
) =
$self
->_check_sheetname(
$_
[0],
$_
[1]);
my
@init_data
= (
$name
,
$index
,
$encoding
,
\
$self
->{_activesheet},
\
$self
->{_firstsheet},
$self
->{_url_format},
$self
->{_parser},
$self
->{_tempdir},
\
$self
->{_str_total},
\
$self
->{_str_unique},
\
$self
->{_str_table},
$self
->{_1904},
$self
->{_compatibility},
undef
,
);
my
$worksheet
= Spreadsheet::WriteExcel::Worksheet->new(
@init_data
);
$self
->{_worksheets}->[
$index
] =
$worksheet
;
$self
->{_sheetnames}->[
$index
] =
$name
;
$self
->{_parser}->set_ext_sheets(
$name
,
$index
);
return
$worksheet
;
}
*addworksheet
=
*add_worksheet
;
sub
add_chart {
my
$self
=
shift
;
my
%arg
=
@_
;
my
$name
=
''
;
my
$encoding
= 0;
my
$index
= @{
$self
->{_worksheets} };
my
$type
=
$arg
{type};
if
( !
defined
$type
) {
croak
"Must define chart type in add_chart()"
;
}
my
$embedded
=
$arg
{embedded} ||= 0;
if
( !
$embedded
) {
(
$name
,
$encoding
) =
$self
->_check_sheetname(
$arg
{name},
$arg
{name_encoding}, 1 );
}
my
@init_data
= (
$name
,
$index
,
$encoding
,
\
$self
->{_activesheet},
\
$self
->{_firstsheet},
$self
->{_url_format},
$self
->{_parser},
$self
->{_tempdir},
\
$self
->{_str_total},
\
$self
->{_str_unique},
\
$self
->{_str_table},
$self
->{_1904},
$self
->{_compatibility},
$self
->{_palette},
);
my
$chart
= Spreadsheet::WriteExcel::Chart->factory(
$type
,
@init_data
);
if
( !
$embedded
) {
$self
->{_worksheets}->[
$index
] =
$chart
;
$self
->{_sheetnames}->[
$index
] =
$name
;
}
else
{
$chart
->{_index} = 0;
$chart
->_set_embedded_config_data();
}
return
$chart
;
}
sub
add_chart_ext {
my
$self
=
shift
;
my
$filename
=
$_
[0];
my
$index
= @{
$self
->{_worksheets}};
my
$type
=
'external'
;
my
(
$name
,
$encoding
) =
$self
->_check_sheetname(
$_
[1],
$_
[2]);
my
@init_data
= (
$filename
,
$name
,
$index
,
$encoding
,
\
$self
->{_activesheet},
\
$self
->{_firstsheet},
);
my
$chart
= Spreadsheet::WriteExcel::Chart->factory(
$type
,
@init_data
);
$self
->{_worksheets}->[
$index
] =
$chart
;
$self
->{_sheetnames}->[
$index
] =
$name
;
return
$chart
;
}
sub
_check_sheetname {
my
$self
=
shift
;
my
$name
=
$_
[0] ||
""
;
my
$encoding
=
$_
[1] || 0;
my
$chart
=
$_
[2] || 0;
my
$limit
=
$encoding
? 62 : 31;
my
$invalid_char
=
qr([\[\]:*?/\\])
;
if
(
$chart
) {
$self
->{_chart_count}++;
}
else
{
$self
->{_sheet_count}++;
}
if
(
$name
eq
""
) {
$encoding
= 0;
if
(
$chart
) {
$name
=
$self
->{_chart_name} .
$self
->{_chart_count};
}
else
{
$name
=
$self
->{_sheet_name} .
$self
->{_sheet_count};
}
}
croak
"Sheetname $name must be <= 31 chars"
if
length
$name
>
$limit
;
croak
'Odd number of bytes in Unicode worksheet name:'
.
$name
if
$encoding
== 1 and
length
(
$name
) % 2;
if
(
$encoding
!= 1 and
$name
=~
$invalid_char
) {
croak
'Invalid character []:*?/\\ in worksheet name: '
.
$name
;
}
else
{
for
my
$wchar
(
$name
=~ /../sg) {
my
(
$hi
,
$lo
) =
unpack
"aa"
,
$wchar
;
if
(
$hi
eq
"\0"
and
$lo
=~
$invalid_char
) {
croak
'Invalid character []:*?/\\ in worksheet name: '
.
$name
;
}
}
}
if
($] >= 5.008) {
if
(Encode::is_utf8(
$name
)) {
$name
= Encode::encode(
"UTF-16BE"
,
$name
);
$encoding
= 1;
}
}
foreach
my
$worksheet
(@{
$self
->{_worksheets}}) {
my
$name_a
=
$name
;
my
$encd_a
=
$encoding
;
my
$name_b
=
$worksheet
->{_name};
my
$encd_b
=
$worksheet
->{_encoding};
my
$error
= 0;
if
(
$encd_a
== 0 and
$encd_b
== 0) {
$error
= 1
if
lc
(
$name_a
) eq
lc
(
$name_b
);
}
elsif
(
$encd_a
== 0 and
$encd_b
== 1) {
$name_a
=
pack
"n*"
,
unpack
"C*"
,
$name_a
;
$error
= 1
if
lc
(
$name_a
) eq
lc
(
$name_b
);
}
elsif
(
$encd_a
== 1 and
$encd_b
== 0) {
$name_b
=
pack
"n*"
,
unpack
"C*"
,
$name_b
;
$error
= 1
if
lc
(
$name_a
) eq
lc
(
$name_b
);
}
elsif
(
$encd_a
== 1 and
$encd_b
== 1) {
if
($] >= 5.008) {
$name_a
= Encode::decode(
"UTF-16BE"
,
$name_a
);
$name_b
= Encode::decode(
"UTF-16BE"
,
$name_b
);
$error
= 1
if
lc
(
$name_a
) eq
lc
(
$name_b
);
}
else
{
my
$hi_a
=
grep
{
ord
}
$name_a
=~ /(.)./sg;
my
$hi_b
=
grep
{
ord
}
$name_b
=~ /(.)./sg;
if
(
$hi_a
or
$hi_b
) {
$error
= 1
if
$name_a
eq
$name_b
;
}
else
{
$error
= 1
if
lc
(
$name_a
) eq
lc
(
$name_b
);
}
}
}
if
(
$error
) {
croak
"Worksheet name '$name', with case ignored, "
.
"is already in use"
;
}
}
return
(
$name
,
$encoding
);
}
sub
add_format {
my
$self
=
shift
;
my
$format
= Spreadsheet::WriteExcel::Format->new(
$self
->{_xf_index},
@_
);
$self
->{_xf_index} += 1;
push
@{
$self
->{_formats}},
$format
;
return
$format
;
}
*addformat
=
*add_format
;
sub
compatibility_mode {
my
$self
=
shift
;
croak
"compatibility_mode() must be called before add_worksheet()"
if
$self
->sheets();
if
(
defined
(
$_
[0])) {
$self
->{_compatibility} =
$_
[0];
}
else
{
$self
->{_compatibility} = 1;
}
}
sub
set_1904 {
my
$self
=
shift
;
croak
"set_1904() must be called before add_worksheet()"
if
$self
->sheets();
if
(
defined
(
$_
[0])) {
$self
->{_1904} =
$_
[0];
}
else
{
$self
->{_1904} = 1;
}
}
sub
get_1904 {
my
$self
=
shift
;
return
$self
->{_1904};
}
sub
set_custom_color {
my
$self
=
shift
;
if
(
defined
$_
[1] and
$_
[1] =~ /^
@_
= (
$_
[0],
hex
$1,
hex
$2,
hex
$3);
}
my
$index
=
$_
[0] || 0;
my
$red
=
$_
[1] || 0;
my
$green
=
$_
[2] || 0;
my
$blue
=
$_
[3] || 0;
my
$aref
=
$self
->{_palette};
if
(
$index
< 8 or
$index
> 64) {
carp
"Color index $index outside range: 8 <= index <= 64"
;
return
0;
}
if
( (
$red
< 0 or
$red
> 255) ||
(
$green
< 0 or
$green
> 255) ||
(
$blue
< 0 or
$blue
> 255) )
{
carp
"Color component outside range: 0 <= color <= 255"
;
return
0;
}
$index
-=8;
$aref
->[
$index
] = [
$red
,
$green
,
$blue
, 0];
return
$index
+8;
}
sub
set_palette_xl97 {
my
$self
=
shift
;
$self
->{_palette} = [
[0x00, 0x00, 0x00, 0x00],
[0xff, 0xff, 0xff, 0x00],
[0xff, 0x00, 0x00, 0x00],
[0x00, 0xff, 0x00, 0x00],
[0x00, 0x00, 0xff, 0x00],
[0xff, 0xff, 0x00, 0x00],
[0xff, 0x00, 0xff, 0x00],
[0x00, 0xff, 0xff, 0x00],
[0x80, 0x00, 0x00, 0x00],
[0x00, 0x80, 0x00, 0x00],
[0x00, 0x00, 0x80, 0x00],
[0x80, 0x80, 0x00, 0x00],
[0x80, 0x00, 0x80, 0x00],
[0x00, 0x80, 0x80, 0x00],
[0xc0, 0xc0, 0xc0, 0x00],
[0x80, 0x80, 0x80, 0x00],
[0x99, 0x99, 0xff, 0x00],
[0x99, 0x33, 0x66, 0x00],
[0xff, 0xff, 0xcc, 0x00],
[0xcc, 0xff, 0xff, 0x00],
[0x66, 0x00, 0x66, 0x00],
[0xff, 0x80, 0x80, 0x00],
[0x00, 0x66, 0xcc, 0x00],
[0xcc, 0xcc, 0xff, 0x00],
[0x00, 0x00, 0x80, 0x00],
[0xff, 0x00, 0xff, 0x00],
[0xff, 0xff, 0x00, 0x00],
[0x00, 0xff, 0xff, 0x00],
[0x80, 0x00, 0x80, 0x00],
[0x80, 0x00, 0x00, 0x00],
[0x00, 0x80, 0x80, 0x00],
[0x00, 0x00, 0xff, 0x00],
[0x00, 0xcc, 0xff, 0x00],
[0xcc, 0xff, 0xff, 0x00],
[0xcc, 0xff, 0xcc, 0x00],
[0xff, 0xff, 0x99, 0x00],
[0x99, 0xcc, 0xff, 0x00],
[0xff, 0x99, 0xcc, 0x00],
[0xcc, 0x99, 0xff, 0x00],
[0xff, 0xcc, 0x99, 0x00],
[0x33, 0x66, 0xff, 0x00],
[0x33, 0xcc, 0xcc, 0x00],
[0x99, 0xcc, 0x00, 0x00],
[0xff, 0xcc, 0x00, 0x00],
[0xff, 0x99, 0x00, 0x00],
[0xff, 0x66, 0x00, 0x00],
[0x66, 0x66, 0x99, 0x00],
[0x96, 0x96, 0x96, 0x00],
[0x00, 0x33, 0x66, 0x00],
[0x33, 0x99, 0x66, 0x00],
[0x00, 0x33, 0x00, 0x00],
[0x33, 0x33, 0x00, 0x00],
[0x99, 0x33, 0x00, 0x00],
[0x99, 0x33, 0x66, 0x00],
[0x33, 0x33, 0x99, 0x00],
[0x33, 0x33, 0x33, 0x00],
];
return
0;
}
sub
set_tempdir {
my
$self
=
shift
;
my
$dir
=
shift
||
''
;
croak
"$dir is not a valid directory"
if
$dir
ne
''
and not -d
$dir
;
croak
"set_tempdir must be called before add_worksheet"
if
$self
->sheets();
$self
->{_tempdir} =
$dir
;
}
sub
set_codepage {
my
$self
=
shift
;
my
$codepage
=
$_
[0] || 1;
$codepage
= 0x04E4
if
$codepage
== 1;
$codepage
= 0x8000
if
$codepage
== 2;
$self
->{_codepage} =
$codepage
;
}
sub
set_country {
my
$self
=
shift
;
$self
->{_country} =
$_
[0] || 1;
}
sub
define_name {
my
$self
=
shift
;
my
$name
=
shift
;
my
$formula
=
shift
;
my
$encoding
=
shift
|| 0;
my
$sheet_index
= 0;
my
@tokens
;
my
$full_name
=
$name
;
if
(
$name
=~ /^(.*)!(.*)$/) {
my
$sheetname
= $1;
$name
= $2;
$sheet_index
= 1 +
$self
->{_parser}->_get_sheet_index(
$sheetname
);
}
$formula
=~ s(^=)();
my
$parser
=
$self
->{_parser};
eval
{
@tokens
=
$parser
->parse_formula(
$formula
) };
if
($@) {
$@ =~ s/\n$//;
croak $@;
}
s/_ref3d/_ref3dR/
for
@tokens
;
s/_range3d/_range3dR/
for
@tokens
;
$formula
=
$parser
->parse_tokens(
@tokens
);
$full_name
=
lc
$full_name
;
push
@{
$self
->{_defined_names}}, {
name
=>
$name
,
encoding
=>
$encoding
,
sheet_index
=>
$sheet_index
,
formula
=>
$formula
,
};
my
$index
=
scalar
@{
$self
->{_defined_names}};
$parser
->set_ext_name(
$name
,
$index
);
}
sub
set_properties {
my
$self
=
shift
;
my
%param
;
return
-1
unless
@_
;
if
(
ref
$_
[0] eq
'HASH'
) {
%param
= %{
$_
[0]};
}
else
{
%param
=
@_
;
}
my
%properties
= (
codepage
=> [0x0001,
'VT_I2'
],
title
=> [0x0002,
'VT_LPSTR'
],
subject
=> [0x0003,
'VT_LPSTR'
],
author
=> [0x0004,
'VT_LPSTR'
],
keywords
=> [0x0005,
'VT_LPSTR'
],
comments
=> [0x0006,
'VT_LPSTR'
],
last_author
=> [0x0008,
'VT_LPSTR'
],
created
=> [0x000C,
'VT_FILETIME'
],
category
=> [0x0002,
'VT_LPSTR'
],
manager
=> [0x000E,
'VT_LPSTR'
],
company
=> [0x000F,
'VT_LPSTR'
],
utf8
=> 1,
);
for
my
$parameter
(
keys
%param
) {
if
(not
exists
$properties
{
$parameter
}) {
carp
"Unknown parameter '$parameter' in set_properties()"
;
return
-1;
}
}
if
(!
exists
$param
{created}){
$param
{created} =
$self
->{_localtime};
}
my
@strings
=
qw(title subject author keywords comments last_author)
;
$param
{codepage} =
$self
->_get_property_set_codepage(\
%param
,
\
@strings
);
my
@property_sets
;
for
my
$property
(
qw(codepage title subject author
keywords comments last_author created)
)
{
if
(
exists
$param
{
$property
} &&
defined
$param
{
$property
}) {
push
@property_sets
, [
$properties
{
$property
}->[0],
$properties
{
$property
}->[1],
$param
{
$property
}
];
}
}
$self
->{summary} = create_summary_property_set(\
@property_sets
);
@strings
=
qw(category manager company)
;
$param
{codepage} =
$self
->_get_property_set_codepage(\
%param
,
\
@strings
);
@property_sets
= ();
for
my
$property
(
qw(codepage category manager company)
)
{
if
(
exists
$param
{
$property
} &&
defined
$param
{
$property
}) {
push
@property_sets
, [
$properties
{
$property
}->[0],
$properties
{
$property
}->[1],
$param
{
$property
}
];
}
}
$self
->{doc_summary} = create_doc_summary_property_set(\
@property_sets
);
$self
->{_add_doc_properties} = 1;
}
sub
_get_property_set_codepage {
my
$self
=
shift
;
my
$params
=
$_
[0];
my
$strings
=
$_
[1];
return
0xFDE9
if
defined
$params
->{utf8};
if
($] >= 5.008) {
for
my
$string
(@{
$strings
}) {
next
unless
exists
$params
->{
$string
};
return
0xFDE9
if
Encode::is_utf8(
$params
->{
$string
});
}
}
return
0x04E4;
}
sub
_store_workbook {
my
$self
=
shift
;
$self
->add_worksheet()
if
not @{
$self
->{_worksheets}};
$self
->_calc_mso_sizes();
if
(
$self
->{_activesheet} == 0) {
@{
$self
->{_worksheets}}[0]->{_selected} = 1;
@{
$self
->{_worksheets}}[0]->{_hidden} = 0;
}
foreach
my
$sheet
(@{
$self
->{_worksheets}}) {
$self
->{_selected}++
if
$sheet
->{_selected};
$sheet
->{_active} = 1
if
$sheet
->{_index} ==
$self
->{_activesheet};
}
$self
->_store_bof(0x0005);
$self
->_store_codepage();
$self
->_store_window1();
$self
->_store_hideobj();
$self
->_store_1904();
$self
->_store_all_fonts();
$self
->_store_all_num_formats();
$self
->_store_all_xfs();
$self
->_store_all_styles();
$self
->_store_palette();
$self
->_calc_sheet_offsets();
foreach
my
$sheet
(@{
$self
->{_worksheets}}) {
$self
->_store_boundsheet(
$sheet
->{_name},
$sheet
->{_offset},
$sheet
->{_sheet_type},
$sheet
->{_hidden},
$sheet
->{_encoding});
}
$self
->_store_country();
if
(
$self
->{_ext_ref_count}) {
$self
->_store_supbook();
$self
->_store_externsheet();
$self
->_store_names();
}
$self
->_add_mso_drawing_group();
$self
->_store_shared_strings();
$self
->_store_extsst();
$self
->_store_eof();
return
$self
->_store_OLE_file();
}
sub
_store_OLE_file {
my
$self
=
shift
;
my
$maxsize
= 7_087_104;
if
(!
$self
->{_add_doc_properties} &&
$self
->{_biffsize} <=
$maxsize
) {
my
$OLE
= Spreadsheet::WriteExcel::OLEwriter->new(
$self
->{_fh_out});
$OLE
->{_biff_only} =
$self
->{_biff_only};
$OLE
->{_internal_fh} =
$self
->{_internal_fh};
$OLE
->set_size(
$self
->{_biffsize});
$OLE
->write_header();
while
(
my
$tmp
=
$self
->get_data()) {
$OLE
->
write
(
$tmp
);
}
foreach
my
$worksheet
(@{
$self
->{_worksheets}}) {
while
(
my
$tmp
=
$worksheet
->get_data()) {
$OLE
->
write
(
$tmp
);
}
}
return
$OLE
->
close
();
}
else
{
if
(not $@) {
local
$\ =
undef
;
my
@streams
;
my
$stream
=
pack
'v*'
,
unpack
'C*'
,
'Workbook'
;
my
$workbook
= OLE::Storage_Lite::PPS::File->newFile(
$stream
);
while
(
my
$tmp
=
$self
->get_data()) {
$workbook
->append(
$tmp
);
}
foreach
my
$worksheet
(@{
$self
->{_worksheets}}) {
while
(
my
$tmp
=
$worksheet
->get_data()) {
$workbook
->append(
$tmp
);
}
}
push
@streams
,
$workbook
;
if
(
$self
->{_add_doc_properties}) {
my
$stream
;
my
$summary
;
$stream
=
pack
'v*'
,
unpack
'C*'
,
"\5SummaryInformation"
;
$summary
=
$self
->{summary};
$summary
= OLE::Storage_Lite::PPS::File->new(
$stream
,
$summary
);
push
@streams
,
$summary
;
$stream
=
pack
'v*'
,
unpack
'C*'
,
"\5DocumentSummaryInformation"
;
$summary
=
$self
->{doc_summary};
$summary
= OLE::Storage_Lite::PPS::File->new(
$stream
,
$summary
);
push
@streams
,
$summary
;
}
my
@localtime
= @{
$self
->{_localtime} };
splice
(
@localtime
, 6);
my
$ole_root
= OLE::Storage_Lite::PPS::Root->new(\
@localtime
,
\
@localtime
,
\
@streams
);
$ole_root
->save(
$self
->{_filename});
return
CORE::
close
(
$self
->{_fh_out})
if
$self
->{_internal_fh};
}
else
{
$! = 27;
croak
"Maximum Spreadsheet::WriteExcel filesize, $maxsize bytes, "
.
"exceeded. To create files bigger than this limit please "
.
"install OLE::Storage_Lite\n"
;
}
}
}
sub
_calc_sheet_offsets {
my
$self
=
shift
;
my
$BOF
= 12;
my
$EOF
= 4;
my
$offset
=
$self
->{_datasize};
$offset
+= 8;
$offset
+=
$self
->_calculate_shared_string_sizes();
$offset
+=
$self
->_calculate_extsst_size();
$offset
+=
$self
->_calculate_extern_sizes();
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});
}
$offset
+=
$EOF
;
foreach
my
$sheet
(@{
$self
->{_worksheets}}) {
$sheet
->{_offset} =
$offset
;
$sheet
->_close();
$offset
+=
$sheet
->{_datasize};
}
$self
->{_biffsize} =
$offset
;
}
sub
_calc_mso_sizes {
my
$self
=
shift
;
my
$mso_size
= 0;
my
$start_spid
= 1024;
my
$max_spid
= 1024;
my
$num_clusters
= 1;
my
$shapes_saved
= 0;
my
$drawings_saved
= 0;
my
@clusters
= ();
$self
->_process_images();
$mso_size
+= 8
if
@{
$self
->{_images_data}};
foreach
my
$sheet
(@{
$self
->{_worksheets}}) {
next
unless
$sheet
->{_sheet_type} == 0x0000;
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
;
my
$num_shapes
= 1 +
$num_images
+
$num_comments
+
$num_charts
+
$num_filters
;
$shapes_saved
+=
$num_shapes
;
$mso_size
+=
$image_mso_size
;
$drawings_saved
++;
$max_spid
= 1024 * (1 +
int
((
$max_spid
-1)/1024));
$start_spid
=
$max_spid
;
$max_spid
+=
$num_shapes
;
for
(
my
$i
=
$num_shapes
;
$i
> 0;
$i
-= 1024) {
$num_clusters
+= 1;
$mso_size
+= 8;
my
$size
=
$i
> 1024 ? 1024 :
$i
;
push
@clusters
, [
$drawings_saved
,
$size
];
}
$sheet
->{_object_ids} = [
$start_spid
,
$drawings_saved
,
$num_shapes
,
$max_spid
-1];
}
$mso_size
+= 86
if
$mso_size
;
$self
->{_mso_size} =
$mso_size
;
$self
->{_mso_clusters} = [
$max_spid
,
$num_clusters
,
$shapes_saved
,
$drawings_saved
, [
@clusters
]
];
}
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
$sheet
->{_sheet_type} == 0x0000;
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
++;
if
(not
exists
$images_seen
{
$filename
}) {
my
$fh
= FileHandle->new(
$filename
);
croak
"Couldn't import $filename: $!"
unless
defined
$fh
;
binmode
$fh
;
my
$data
=
do
{
local
$/; <
$fh
>};
my
$size
=
length
$data
;
my
$checksum1
=
$self
->_image_checksum(
$data
,
$image_id
);
my
$checksum2
=
$checksum1
;
my
$ref_count
= 1;
my
(
$type
,
$width
,
$height
);
if
(
unpack
(
'x A3'
,
$data
) eq
'PNG'
) {
(
$type
,
$width
,
$height
) =
$self
->_process_png(
$data
);
}
elsif
( (
unpack
(
'n'
,
$data
) == 0xFFD8) &&
( (
unpack
(
'x6 A4'
,
$data
) eq
'JFIF'
) ||
(
unpack
(
'x6 A4'
,
$data
) eq
'Exif'
)
)
)
{
(
$type
,
$width
,
$height
) =
$self
->_process_jpg(
$data
,
$filename
);
}
elsif
(
unpack
(
'A2'
,
$data
) eq
'BM'
) {
(
$type
,
$width
,
$height
) =
$self
->_process_bmp(
$data
,
$filename
);
$data
=
substr
$data
, 14;
$checksum2
=
$self
->_image_checksum(
$data
,
$image_id
,
$image_id
);
$size
+= 2;
}
else
{
croak
"Unsupported image format for file: $filename\n"
;
}
push
@$image_ref
,
$image_id
,
$type
,
$width
,
$height
;
push
@previous_images
, [
$image_id
,
$type
,
$width
,
$height
];
push
@image_data
, [
$ref_count
,
$type
,
$data
,
$size
,
$checksum1
,
$checksum2
];
$images_size
+=
$size
+61;
$image_mso_size
+=
$size
+69;
$images_seen
{
$filename
} =
$image_id
++;
$fh
->
close
;
}
else
{
my
$index
=
$images_seen
{
$filename
} -1;
$image_data
[
$index
]->[0]++;
my
$a_ref
=
$sheet
->{_images_array}->[
$index
];
push
@$image_ref
, @{
$previous_images
[
$index
]};
}
}
$sheet
->{_num_images} =
$num_images
;
$sheet
->{_image_mso_size} =
$image_mso_size
;
}
$self
->{_images_size} =
$images_size
;
$self
->{_images_data} = \
@image_data
;
}
sub
_image_checksum {
my
$self
=
shift
;
my
$data
=
$_
[0];
my
$index1
=
$_
[1];
my
$index2
=
$_
[2] || 0;
if
(
$self
->{_checksum_method} == 1) {
return
Digest::MD4::md4_hex(
$data
);
}
elsif
(
$self
->{_checksum_method} == 2) {
return
Digest::Perl::MD4::md4_hex(
$data
);
}
elsif
(
$self
->{_checksum_method} == 3) {
return
Digest::MD5::md5_hex(
$data
);
}
else
{
return
sprintf
'%016X%016X'
,
$index2
,
$index1
;
}
}
sub
_process_png {
my
$self
=
shift
;
my
$type
= 6;
my
$width
=
unpack
"N"
,
substr
$_
[0], 16, 4;
my
$height
=
unpack
"N"
,
substr
$_
[0], 20, 4;
return
(
$type
,
$width
,
$height
);
}
sub
_process_bmp {
my
$self
=
shift
;
my
$data
=
$_
[0];
my
$filename
=
$_
[1];
my
$type
= 7;
if
(
length
$data
<= 0x36) {
croak
"$filename doesn't contain enough data."
;
}
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."
;
}
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."
;
}
my
$compression
=
unpack
"x30 V"
,
$data
;
if
(
$compression
!= 0) {
croak
"$filename: compression not supported in bitmap image."
;
}
return
(
$type
,
$width
,
$height
);
}
sub
_process_jpg {
my
$self
=
shift
;
my
$data
=
$_
[0];
my
$filename
=
$_
[1];
my
$type
= 5;
my
$width
;
my
$height
;
my
$offset
= 2;
my
$data_length
=
length
$data
;
while
(
$offset
<
$data_length
) {
my
$marker
=
unpack
"n"
,
substr
$data
,
$offset
, 2;
my
$length
=
unpack
"n"
,
substr
$data
,
$offset
+2, 2;
if
(
$marker
== 0xFFC0 ||
$marker
== 0xFFC2) {
$height
=
unpack
"n"
,
substr
$data
,
$offset
+5, 2;
$width
=
unpack
"n"
,
substr
$data
,
$offset
+7, 2;
last
;
}
$offset
=
$offset
+
$length
+ 2;
last
if
$marker
== 0xFFDA;
}
if
(not
defined
$height
) {
croak
"$filename: no size data found in jpeg image.\n"
;
}
return
(
$type
,
$width
,
$height
);
}
sub
_store_all_fonts {
my
$self
=
shift
;
my
$format
=
$self
->{_formats}->[15];
my
$font
=
$format
->get_font();
for
(0..3) {
$self
->_append(
$font
);
}
my
$tmp_format
;
$tmp_format
= Spreadsheet::WriteExcel::Format->new(
undef
,
font_only
=> 1,
);
$self
->_append(
$tmp_format
->get_font() );
$tmp_format
= Spreadsheet::WriteExcel::Format->new(
undef
,
font_only
=> 1,
);
$self
->_append(
$tmp_format
->get_font() );
$tmp_format
= Spreadsheet::WriteExcel::Format->new(
undef
,
font_only
=> 1,
bold
=> 1,
);
$self
->_append(
$tmp_format
->get_font() );
$tmp_format
= Spreadsheet::WriteExcel::Format->new(
undef
,
font_only
=> 1,
bold
=> 1,
);
$self
->_append(
$tmp_format
->get_font() );
$tmp_format
= Spreadsheet::WriteExcel::Format->new(
undef
,
font_only
=> 1,
font
=>
'Tahoma'
,
size
=> 8,
);
$self
->_append(
$tmp_format
->get_font() );
my
%fonts
;
my
$key
;
my
$index
= 10;
$key
=
$format
->get_font_key();
$fonts
{
$key
} = 0;
foreach
$format
(@{
$self
->{_formats}}) {
$key
=
$format
->get_font_key();
if
(not
$format
->{_font_only} and
exists
$fonts
{
$key
}) {
$format
->{_font_index} =
$fonts
{
$key
};
}
else
{
if
(not
$format
->{_font_only}) {
$fonts
{
$key
} =
$index
;
}
$format
->{_font_index} =
$index
;
$index
++;
$font
=
$format
->get_font();
$self
->_append(
$font
);
}
}
}
sub
_store_all_num_formats {
my
$self
=
shift
;
my
%num_formats
;
my
@num_formats
;
my
$num_format
;
my
$index
= 164;
foreach
my
$format
(@{
$self
->{_formats}}) {
my
$num_format
=
$format
->{_num_format};
my
$encoding
=
$format
->{_num_format_enc};
if
(
$num_format
!~ m/^0+\d/) {
next
if
$num_format
=~ m/^\d+$/;
}
if
(
exists
(
$num_formats
{
$num_format
})) {
$format
->{_num_format} =
$num_formats
{
$num_format
};
}
else
{
$num_formats
{
$num_format
} =
$index
;
$format
->{_num_format} =
$index
;
$self
->_store_num_format(
$num_format
,
$index
,
$encoding
);
$index
++;
}
}
}
sub
_store_all_xfs {
my
$self
=
shift
;
foreach
my
$format
(@{
$self
->{_formats}}) {
my
$xf
=
$format
->get_xf();
$self
->_append(
$xf
);
}
}
sub
_store_all_styles {
my
$self
=
shift
;
my
@built_ins
= (
[0x03, 16],
[0x06, 17],
[0x04, 18],
[0x07, 19],
[0x00, 0],
[0x05, 20],
);
for
my
$aref
(
@built_ins
) {
my
$type
=
$aref
->[0];
my
$xf_index
=
$aref
->[1];
$self
->_store_style(
$type
,
$xf_index
);
}
}
sub
_store_names {
my
$self
=
shift
;
my
$index
;
my
%ext_refs
= %{
$self
->{_ext_refs}};
for
my
$defined_name
(@{
$self
->{_defined_names}}) {
$self
->_store_name(
$defined_name
->{name},
$defined_name
->{encoding},
$defined_name
->{sheet_index},
$defined_name
->{formula},
);
}
my
@worksheets
= @{
$self
->{_worksheets}};
@worksheets
=
sort
{
$a
->{_name} cmp
$b
->{_name} }
@worksheets
;
foreach
my
$worksheet
(
@worksheets
) {
$index
=
$worksheet
->{_index};
my
$key
=
"$index:$index"
;
my
$ref
=
$ext_refs
{
$key
};
if
(
$worksheet
->{_filter_count}) {
$self
->_store_name_short(
$worksheet
->{_index},
0x0D,
$ref
,
$worksheet
->{_filter_area}->[0],
$worksheet
->{_filter_area}->[1],
$worksheet
->{_filter_area}->[2],
$worksheet
->{_filter_area}->[3],
1,
);
}
}
foreach
my
$worksheet
(
@worksheets
) {
$index
=
$worksheet
->{_index};
my
$key
=
"$index:$index"
;
my
$ref
=
$ext_refs
{
$key
};
if
(
defined
$worksheet
->{_print_rowmin}) {
$self
->_store_name_short(
$worksheet
->{_index},
0x06,
$ref
,
$worksheet
->{_print_rowmin},
$worksheet
->{_print_rowmax},
$worksheet
->{_print_colmin},
$worksheet
->{_print_colmax}
);
}
}
foreach
my
$worksheet
(
@worksheets
) {
$index
=
$worksheet
->{_index};
my
$rowmin
=
$worksheet
->{_title_rowmin};
my
$rowmax
=
$worksheet
->{_title_rowmax};
my
$colmin
=
$worksheet
->{_title_colmin};
my
$colmax
=
$worksheet
->{_title_colmax};
my
$key
=
"$index:$index"
;
my
$ref
=
$ext_refs
{
$key
};
if
(
defined
$rowmin
&&
defined
$colmin
) {
$self
->_store_name_long(
$worksheet
->{_index},
0x07,
$ref
,
$rowmin
,
$rowmax
,
$colmin
,
$colmax
);
}
elsif
(
defined
$rowmin
) {
$self
->_store_name_short(
$worksheet
->{_index},
0x07,
$ref
,
$rowmin
,
$rowmax
,
0x00,
0xff
);
}
elsif
(
defined
$colmin
) {
$self
->_store_name_short(
$worksheet
->{_index},
0x07,
$ref
,
0x0000,
0xffff,
$colmin
,
$colmax
);
}
else
{
}
}
}
sub
_store_window1 {
my
$self
=
shift
;
my
$record
= 0x003D;
my
$length
= 0x0012;
my
$xWn
= 0x0000;
my
$yWn
= 0x0000;
my
$dxWn
= 0x355C;
my
$dyWn
= 0x30ED;
my
$grbit
= 0x0038;
my
$ctabsel
=
$self
->{_selected};
my
$wTabRatio
= 0x0258;
my
$itabFirst
=
$self
->{_firstsheet};
my
$itabCur
=
$self
->{_activesheet};
my
$header
=
pack
(
"vv"
,
$record
,
$length
);
my
$data
=
pack
(
"vvvvvvvvv"
,
$xWn
,
$yWn
,
$dxWn
,
$dyWn
,
$grbit
,
$itabCur
,
$itabFirst
,
$ctabsel
,
$wTabRatio
);
$self
->_append(
$header
,
$data
);
}
sub
_store_boundsheet {
my
$self
=
shift
;
my
$record
= 0x0085;
my
$length
= 0x08 +
length
(
$_
[0]);
my
$sheetname
=
$_
[0];
my
$offset
=
$_
[1];
my
$type
=
$_
[2];
my
$hidden
=
$_
[3];
my
$encoding
=
$_
[4];
my
$cch
=
length
(
$sheetname
);
my
$grbit
=
$type
|
$hidden
;
$cch
/= 2
if
$encoding
;
$sheetname
=
pack
'n*'
,
unpack
'v*'
,
$sheetname
if
$encoding
;
my
$header
=
pack
(
"vv"
,
$record
,
$length
);
my
$data
=
pack
(
"VvCC"
,
$offset
,
$grbit
,
$cch
,
$encoding
);
$self
->_append(
$header
,
$data
,
$sheetname
);
}
sub
_store_style {
my
$self
=
shift
;
my
$record
= 0x0293;
my
$length
= 0x0004;
my
$type
=
$_
[0];
my
$xf_index
=
$_
[1];
my
$level
= 0xff;
$xf_index
|= 0x8000;
my
$header
=
pack
(
"vv"
,
$record
,
$length
);
my
$data
=
pack
(
"vCC"
,
$xf_index
,
$type
,
$level
);
$self
->_append(
$header
,
$data
);
}
sub
_store_num_format {
my
$self
=
shift
;
my
$record
= 0x041E;
my
$length
;
my
$format
=
$_
[0];
my
$ifmt
=
$_
[1];
my
$encoding
=
$_
[2];
if
($] >= 5.008) {
if
(Encode::is_utf8(
$format
)) {
$format
= Encode::encode(
"UTF-16BE"
,
$format
);
$encoding
= 1;
}
}
my
$cch
=
length
$format
;
if
(
$encoding
== 1) {
croak
"Uneven number of bytes in Unicode font name"
if
$cch
% 2;
$cch
/= 2
if
$encoding
;
$format
=
pack
'v*'
,
unpack
'n*'
,
$format
;
}
if
(
$encoding
== 0 and
$format
=~ /\x80/) {
$format
=
pack
'v*'
,
unpack
'C*'
,
$format
;
$format
=~ s/\x80\x00/\xAC\x20/g;
$encoding
= 1;
}
$length
= 0x05 +
length
$format
;
my
$header
=
pack
(
"vv"
,
$record
,
$length
);
my
$data
=
pack
(
"vvC"
,
$ifmt
,
$cch
,
$encoding
);
$self
->_append(
$header
,
$data
,
$format
);
}
sub
_store_1904 {
my
$self
=
shift
;
my
$record
= 0x0022;
my
$length
= 0x0002;
my
$f1904
=
$self
->{_1904};
my
$header
=
pack
(
"vv"
,
$record
,
$length
);
my
$data
=
pack
(
"v"
,
$f1904
);
$self
->_append(
$header
,
$data
);
}
sub
_store_supbook {
my
$self
=
shift
;
my
$record
= 0x01AE;
my
$length
= 0x0004;
my
$ctabs
= @{
$self
->{_worksheets}};
my
$StVirtPath
= 0x0401;
my
$header
=
pack
(
"vv"
,
$record
,
$length
);
my
$data
=
pack
(
"vv"
,
$ctabs
,
$StVirtPath
);
$self
->_append(
$header
,
$data
);
}
sub
_store_externsheet {
my
$self
=
shift
;
my
$record
= 0x0017;
my
$length
;
my
%ext_refs
= %{
$self
->{_ext_refs}};
my
@ext_refs
=
sort
{
$ext_refs
{
$a
} <=>
$ext_refs
{
$b
}}
keys
%ext_refs
;
foreach
my
$ref
(
@ext_refs
) {
$ref
= [
split
/:/,
$ref
];
}
my
$cxti
=
scalar
@ext_refs
;
my
$rgxti
=
''
;
foreach
my
$ext_ref
(
@ext_refs
) {
$rgxti
.=
pack
(
"vvv"
, 0,
$ext_ref
->[0],
$ext_ref
->[1])
}
my
$data
=
pack
(
"v"
,
$cxti
) .
$rgxti
;
my
$header
=
pack
(
"vv"
,
$record
,
length
$data
);
$self
->_append(
$header
,
$data
);
}
sub
_store_name {
my
$self
=
shift
;
my
$record
= 0x0018;
my
$length
;
my
$name
=
shift
;
my
$encoding
=
shift
;
my
$sheet_index
=
shift
;
my
$formula
=
shift
;
my
$text_length
=
length
$name
;
my
$formula_length
=
length
$formula
;
$text_length
/= 2
if
$encoding
;
my
$grbit
= 0x0000;
my
$shortcut
= 0x00;
my
$ixals
= 0x0000;
my
$menu_length
= 0x00;
my
$desc_length
= 0x00;
my
$help_length
= 0x00;
my
$status_length
= 0x00;
if
(
$text_length
== 1) {
$grbit
= 0x0020
if
ord
$name
== 0x06;
$grbit
= 0x0020
if
ord
$name
== 0x07;
$grbit
= 0x0021
if
ord
$name
== 0x0D;
}
my
$data
=
pack
"v"
,
$grbit
;
$data
.=
pack
"C"
,
$shortcut
;
$data
.=
pack
"C"
,
$text_length
;
$data
.=
pack
"v"
,
$formula_length
;
$data
.=
pack
"v"
,
$ixals
;
$data
.=
pack
"v"
,
$sheet_index
;
$data
.=
pack
"C"
,
$menu_length
;
$data
.=
pack
"C"
,
$desc_length
;
$data
.=
pack
"C"
,
$help_length
;
$data
.=
pack
"C"
,
$status_length
;
$data
.=
pack
"C"
,
$encoding
;
$data
.=
$name
;
$data
.=
$formula
;
my
$header
=
pack
"vv"
,
$record
,
length
$data
;
$self
->_append(
$header
,
$data
);
}
sub
_store_name_short {
my
$self
=
shift
;
my
$record
= 0x0018;
my
$length
= 0x001b;
my
$index
=
shift
;
my
$type
=
shift
;
my
$ext_ref
=
shift
;
my
$grbit
= 0x0020;
my
$chKey
= 0x00;
my
$cch
= 0x01;
my
$cce
= 0x000b;
my
$unknown01
= 0x0000;
my
$ixals
=
$index
+1;
my
$unknown02
= 0x00;
my
$cchCustMenu
= 0x00;
my
$cchDescription
= 0x00;
my
$cchHelptopic
= 0x00;
my
$cchStatustext
= 0x00;
my
$rgch
=
$type
;
my
$unknown03
= 0x3b;
my
$rowmin
=
$_
[0];
my
$rowmax
=
$_
[1];
my
$colmin
=
$_
[2];
my
$colmax
=
$_
[3];
my
$hidden
=
$_
[4];
$grbit
= 0x0021
if
$hidden
;
my
$header
=
pack
(
"vv"
,
$record
,
$length
);
my
$data
=
pack
(
"v"
,
$grbit
);
$data
.=
pack
(
"C"
,
$chKey
);
$data
.=
pack
(
"C"
,
$cch
);
$data
.=
pack
(
"v"
,
$cce
);
$data
.=
pack
(
"v"
,
$unknown01
);
$data
.=
pack
(
"v"
,
$ixals
);
$data
.=
pack
(
"C"
,
$unknown02
);
$data
.=
pack
(
"C"
,
$cchCustMenu
);
$data
.=
pack
(
"C"
,
$cchDescription
);
$data
.=
pack
(
"C"
,
$cchHelptopic
);
$data
.=
pack
(
"C"
,
$cchStatustext
);
$data
.=
pack
(
"C"
,
$rgch
);
$data
.=
pack
(
"C"
,
$unknown03
);
$data
.=
pack
(
"v"
,
$ext_ref
);
$data
.=
pack
(
"v"
,
$rowmin
);
$data
.=
pack
(
"v"
,
$rowmax
);
$data
.=
pack
(
"v"
,
$colmin
);
$data
.=
pack
(
"v"
,
$colmax
);
$self
->_append(
$header
,
$data
);
}
sub
_store_name_long {
my
$self
=
shift
;
my
$record
= 0x0018;
my
$length
= 0x002a;
my
$index
=
shift
;
my
$type
=
shift
;
my
$ext_ref
=
shift
;
my
$grbit
= 0x0020;
my
$chKey
= 0x00;
my
$cch
= 0x01;
my
$cce
= 0x001a;
my
$unknown01
= 0x0000;
my
$ixals
=
$index
+1;
my
$unknown02
= 0x00;
my
$cchCustMenu
= 0x00;
my
$cchDescription
= 0x00;
my
$cchHelptopic
= 0x00;
my
$cchStatustext
= 0x00;
my
$rgch
=
$type
;
my
$unknown03
= 0x29;
my
$unknown04
= 0x0017;
my
$unknown05
= 0x3b;
my
$rowmin
=
$_
[0];
my
$rowmax
=
$_
[1];
my
$colmin
=
$_
[2];
my
$colmax
=
$_
[3];
my
$header
=
pack
(
"vv"
,
$record
,
$length
);
my
$data
=
pack
(
"v"
,
$grbit
);
$data
.=
pack
(
"C"
,
$chKey
);
$data
.=
pack
(
"C"
,
$cch
);
$data
.=
pack
(
"v"
,
$cce
);
$data
.=
pack
(
"v"
,
$unknown01
);
$data
.=
pack
(
"v"
,
$ixals
);
$data
.=
pack
(
"C"
,
$unknown02
);
$data
.=
pack
(
"C"
,
$cchCustMenu
);
$data
.=
pack
(
"C"
,
$cchDescription
);
$data
.=
pack
(
"C"
,
$cchHelptopic
);
$data
.=
pack
(
"C"
,
$cchStatustext
);
$data
.=
pack
(
"C"
,
$rgch
);
$data
.=
pack
(
"C"
,
$unknown03
);
$data
.=
pack
(
"v"
,
$unknown04
);
$data
.=
pack
(
"C"
,
$unknown05
);
$data
.=
pack
(
"v"
,
$ext_ref
);
$data
.=
pack
(
"v"
, 0x0000);
$data
.=
pack
(
"v"
, 0xffff);
$data
.=
pack
(
"v"
,
$colmin
);
$data
.=
pack
(
"v"
,
$colmax
);
$data
.=
pack
(
"C"
,
$unknown05
);
$data
.=
pack
(
"v"
,
$ext_ref
);
$data
.=
pack
(
"v"
,
$rowmin
);
$data
.=
pack
(
"v"
,
$rowmax
);
$data
.=
pack
(
"v"
, 0x00);
$data
.=
pack
(
"v"
, 0xff);
$data
.=
pack
(
"C"
, 0x10);
$self
->_append(
$header
,
$data
);
}
sub
_store_palette {
my
$self
=
shift
;
my
$aref
=
$self
->{_palette};
my
$record
= 0x0092;
my
$length
= 2 + 4 *
@$aref
;
my
$ccv
=
@$aref
;
my
$data
;
$data
.=
pack
"CCCC"
,
@$_
for
@$aref
;
my
$header
=
pack
(
"vvv"
,
$record
,
$length
,
$ccv
);
$self
->_append(
$header
,
$data
);
}
sub
_store_codepage {
my
$self
=
shift
;
my
$record
= 0x0042;
my
$length
= 0x0002;
my
$cv
=
$self
->{_codepage};
my
$header
=
pack
(
"vv"
,
$record
,
$length
);
my
$data
=
pack
(
"v"
,
$cv
);
$self
->_append(
$header
,
$data
);
}
sub
_store_country {
my
$self
=
shift
;
my
$record
= 0x008C;
my
$length
= 0x0004;
my
$country_default
=
$self
->{_country};
my
$country_win_ini
=
$self
->{_country};
my
$header
=
pack
(
"vv"
,
$record
,
$length
);
my
$data
=
pack
(
"vv"
,
$country_default
,
$country_win_ini
);
$self
->_append(
$header
,
$data
);
}
sub
_store_hideobj {
my
$self
=
shift
;
my
$record
= 0x008D;
my
$length
= 0x0002;
my
$hide
=
$self
->{_hideobj};
my
$header
=
pack
(
"vv"
,
$record
,
$length
);
my
$data
=
pack
(
"v"
,
$hide
);
$self
->_append(
$header
,
$data
);
}
sub
_calculate_extern_sizes {
my
$self
=
shift
;
my
%ext_refs
=
$self
->{_parser}->get_ext_sheets();
my
$ext_ref_count
=
scalar
keys
%ext_refs
;
my
$length
= 0;
my
$index
= 0;
if
(@{
$self
->{_defined_names}}) {
my
$index
= 0;
my
$key
=
"$index:$index"
;
if
(not
exists
$ext_refs
{
$key
}) {
$ext_refs
{
$key
} =
$ext_ref_count
++;
}
}
for
my
$defined_name
(@{
$self
->{_defined_names}}) {
$length
+= 19
+
length
(
$defined_name
->{name})
+
length
(
$defined_name
->{formula});
}
foreach
my
$worksheet
(@{
$self
->{_worksheets}}) {
my
$rowmin
=
$worksheet
->{_title_rowmin};
my
$colmin
=
$worksheet
->{_title_colmin};
my
$filter
=
$worksheet
->{_filter_count};
my
$key
=
"$index:$index"
;
$index
++;
if
(
defined
$worksheet
->{_print_rowmin}) {
$ext_refs
{
$key
} =
$ext_ref_count
++
if
not
exists
$ext_refs
{
$key
};
$length
+= 31 ;
}
if
(
defined
$rowmin
and
defined
$colmin
) {
$ext_refs
{
$key
} =
$ext_ref_count
++
if
not
exists
$ext_refs
{
$key
};
$length
+= 46;
}
elsif
(
defined
$rowmin
or
defined
$colmin
) {
$ext_refs
{
$key
} =
$ext_ref_count
++
if
not
exists
$ext_refs
{
$key
};
$length
+= 31;
}
else
{
}
if
(
$filter
) {
$ext_refs
{
$key
} =
$ext_ref_count
++
if
not
exists
$ext_refs
{
$key
};
$length
+= 31;
}
}
$self
->{_ext_ref_count} =
$ext_ref_count
;
$self
->{_ext_refs} = {
%ext_refs
};
return
$length
= 0
if
$ext_ref_count
== 0;
$length
+= 8;
$length
+= 6 * (1 +
$ext_ref_count
);
return
$length
;
}
sub
_calculate_shared_string_sizes {
my
$self
=
shift
;
my
@strings
;
$#strings
=
$self
->{_str_unique} -1; # Pre-extend array
while
(
my
$key
=
each
%{
$self
->{_str_table}}) {
$strings
[
$self
->{_str_table}->{
$key
}] =
$key
;
}
$self
->{_str_table} =
undef
;
$self
->{_str_array} = [
@strings
];
my
$continue_limit
= 8208;
my
$block_length
= 0;
my
$written
= 0;
my
@block_sizes
;
my
$continue
= 0;
for
my
$string
(
@strings
) {
my
$string_length
=
length
$string
;
my
$encoding
=
unpack
"xx C"
,
$string
;
my
$split_string
= 0;
$block_length
+=
$string_length
;
if
(
$block_length
<
$continue_limit
) {
$written
+=
$string_length
;
next
;
}
while
(
$block_length
>=
$continue_limit
) {
my
$header_length
= 3;
my
$space_remaining
=
$continue_limit
-
$written
-
$continue
;
my
$align
= 0;
if
(
$encoding
== 1) {
$header_length
= 4;
if
(
$space_remaining
>
$header_length
) {
if
(not
$split_string
and
$space_remaining
% 2 != 1) {
$space_remaining
--;
$align
= 1;
}
elsif
(
$split_string
and
$space_remaining
% 2 == 1) {
$space_remaining
--;
$align
= 1;
}
$split_string
= 1;
}
}
if
(
$space_remaining
>
$header_length
) {
$written
+=
$space_remaining
;
$block_length
-=
$continue_limit
-
$continue
-
$align
;
push
@block_sizes
,
$continue_limit
-
$align
;
if
(
$block_length
> 0) {
$continue
= 1;
}
else
{
$continue
= 0;
}
}
else
{
push
@block_sizes
,
$written
+
$continue
;
$block_length
-=
$continue_limit
-
$space_remaining
-
$continue
;
$continue
= 0;
}
if
(
$block_length
<
$continue_limit
) {
$written
=
$block_length
;
}
else
{
$written
= 0;
}
}
}
push
@block_sizes
,
$written
+
$continue
if
$written
+
$continue
;
$self
->{_str_block_sizes} = [
@block_sizes
];
my
$length
= 12;
$length
+=
shift
@block_sizes
if
@block_sizes
;
$length
+= 4 +
shift
@block_sizes
while
@block_sizes
;
return
$length
;
}
sub
_store_shared_strings {
my
$self
=
shift
;
my
@strings
= @{
$self
->{_str_array}};
my
$record
= 0x00FC;
my
$length
= 0x0008;
my
$total
= 0x0000;
my
$continue_limit
= 8208;
my
$block_length
= 0;
my
$written
= 0;
my
$continue
= 0;
my
@block_sizes
= @{
$self
->{_str_block_sizes}};
if
(
@block_sizes
) {
$length
= 8 +
shift
@block_sizes
;
}
else
{
$length
= 8;
}
my
$extsst_str_num
= -1;
my
$sst_block_start
=
$self
->{_datasize};
my
$header
=
pack
(
"vv"
,
$record
,
$length
);
my
$data
=
pack
(
"VV"
,
$self
->{_str_total},
$self
->{_str_unique});
$self
->_append(
$header
,
$data
);
for
my
$string
(
@strings
) {
my
$string_length
=
length
$string
;
my
$encoding
=
unpack
"xx C"
,
$string
;
my
$split_string
= 0;
my
$bucket_string
= 0;
if
(++
$extsst_str_num
%
$self
->{_extsst_bucket_size} == 0) {
$bucket_string
= 1;
}
$block_length
+=
$string_length
;
if
(
$block_length
<
$continue_limit
) {
if
(
$bucket_string
) {
my
$global_offset
=
$self
->{_datasize};
my
$local_offset
=
$self
->{_datasize} -
$sst_block_start
;
push
@{
$self
->{_extsst_offsets}}, [
$global_offset
,
$local_offset
];
$bucket_string
= 0;
}
$self
->_append(
$string
);
$written
+=
$string_length
;
next
;
}
while
(
$block_length
>=
$continue_limit
) {
my
$header_length
= 3;
my
$space_remaining
=
$continue_limit
-
$written
-
$continue
;
my
$align
= 0;
if
(
$encoding
== 1) {
$header_length
= 4;
if
(
$space_remaining
>
$header_length
) {
if
(not
$split_string
and
$space_remaining
% 2 != 1) {
$space_remaining
--;
$align
= 1;
}
elsif
(
$split_string
and
$space_remaining
% 2 == 1) {
$space_remaining
--;
$align
= 1;
}
$split_string
= 1;
}
}
if
(
$space_remaining
>
$header_length
) {
my
$tmp
=
substr
$string
, 0,
$space_remaining
;
if
(
$bucket_string
) {
my
$global_offset
=
$self
->{_datasize};
my
$local_offset
=
$self
->{_datasize} -
$sst_block_start
;
push
@{
$self
->{_extsst_offsets}}, [
$global_offset
,
$local_offset
];
$bucket_string
= 0;
}
$self
->_append(
$tmp
);
$string
=
substr
$string
,
$space_remaining
;
$block_length
-=
$continue_limit
-
$continue
-
$align
;
if
(
$block_length
> 0) {
$continue
= 1;
}
else
{
$continue
= 0;
}
}
else
{
$block_length
-=
$continue_limit
-
$space_remaining
-
$continue
;
$continue
= 0;
}
if
(
@block_sizes
) {
$sst_block_start
=
$self
->{_datasize};
$record
= 0x003C;
$length
=
shift
@block_sizes
;
$header
=
pack
(
"vv"
,
$record
,
$length
);
$header
.=
pack
(
"C"
,
$encoding
)
if
$continue
;
$self
->_append(
$header
);
}
if
(
$block_length
<
$continue_limit
) {
if
(
$bucket_string
) {
my
$global_offset
=
$self
->{_datasize};
my
$local_offset
=
$self
->{_datasize} -
$sst_block_start
;
push
@{
$self
->{_extsst_offsets}}, [
$global_offset
,
$local_offset
];
$bucket_string
= 0;
}
$self
->_append(
$string
);
$written
=
$block_length
;
}
else
{
$written
= 0;
}
}
}
}
sub
_calculate_extsst_size {
my
$self
=
shift
;
my
$unique_strings
=
$self
->{_str_unique};
my
$bucket_size
;
my
$buckets
;
if
(
$unique_strings
< 1024) {
$bucket_size
= 8;
}
else
{
$bucket_size
= 1 +
int
(
$unique_strings
/ 128);
}
$buckets
=
int
((
$unique_strings
+
$bucket_size
-1) /
$bucket_size
);
$self
->{_extsst_buckets} =
$buckets
;
$self
->{_extsst_bucket_size} =
$bucket_size
;
return
6 + 8 *
$buckets
;
}
sub
_store_extsst {
my
$self
=
shift
;
my
@offsets
= @{
$self
->{_extsst_offsets}};
my
$bucket_size
=
$self
->{_extsst_bucket_size};
my
$record
= 0x00FF;
my
$length
= 2 + 8 *
@offsets
;
my
$header
=
pack
'vv'
,
$record
,
$length
;
my
$data
=
pack
'v'
,
$bucket_size
,;
for
my
$offset
(
@offsets
) {
$data
.=
pack
'Vvv'
,
$offset
->[0],
$offset
->[1], 0;
}
$self
->_append(
$header
,
$data
);
}
sub
_add_mso_drawing_group {
my
$self
=
shift
;
return
unless
$self
->{_mso_size};
my
$record
= 0x00EB;
my
$length
= 0x0000;
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
->_add_mso_drawing_group_continue(
$header
.
$data
);
return
$header
.
$data
;
}
sub
_add_mso_drawing_group_continue {
my
$self
=
shift
;
my
$data
=
$_
[0];
my
$limit
= 8228 -4;
my
$mso_group
= 0x00EB;
my
$continue
= 0x003C;
my
$block_count
= 1;
my
$header
;
my
$tmp
;
$self
->{_ignore_continue} = 1;
if
(
length
$data
<=
$limit
) {
$self
->_append(
$data
);
return
;
}
$tmp
=
substr
(
$data
, 0,
$limit
+4,
""
);
substr
(
$tmp
, 2, 2,
pack
(
"v"
,
$limit
));
$self
->_append(
$tmp
);
while
(
length
(
$data
) >
$limit
) {
if
(
$block_count
== 1) {
$header
=
pack
(
"vv"
,
$mso_group
,
$limit
);
$block_count
++;
}
else
{
$header
=
pack
(
"vv"
,
$continue
,
$limit
);
}
$tmp
=
substr
(
$data
, 0,
$limit
,
""
);
$self
->_append(
$header
,
$tmp
);
}
$header
=
pack
(
"vv"
,
$continue
,
length
(
$data
));
$self
->_append(
$header
,
$data
);
$self
->{_ignore_continue} = 0;
}
sub
_store_mso_dgg_container {
my
$self
=
shift
;
my
$type
= 0xF000;
my
$version
= 15;
my
$instance
= 0;
my
$data
=
''
;
my
$length
=
$self
->{_mso_size} -12;
return
$self
->_add_mso_generic(
$type
,
$version
,
$instance
,
$data
,
$length
);
}
sub
_store_mso_dgg {
my
$self
=
shift
;
my
$type
= 0xF006;
my
$version
= 0;
my
$instance
= 0;
my
$data
=
''
;
my
$length
=
undef
;
my
$max_spid
=
$_
[0];
my
$num_clusters
=
$_
[1];
my
$shapes_saved
=
$_
[2];
my
$drawings_saved
=
$_
[3];
my
$clusters
=
$_
[4];
$data
=
pack
"VVVV"
,
$max_spid
,
$num_clusters
,
$shapes_saved
,
$drawings_saved
;
for
my
$aref
(
@$clusters
) {
my
$drawing_id
=
$aref
->[0];
my
$shape_ids_used
=
$aref
->[1];
$data
.=
pack
"VV"
,
$drawing_id
,
$shape_ids_used
;
}
return
$self
->_add_mso_generic(
$type
,
$version
,
$instance
,
$data
,
$length
);
}
sub
_store_mso_bstore_container {
my
$self
=
shift
;
return
''
unless
$self
->{_images_size};
my
$type
= 0xF001;
my
$version
= 15;
my
$instance
= @{
$self
->{_images_data}};
my
$data
=
''
;
my
$length
=
$self
->{_images_size} +8
*$instance
;
return
$self
->_add_mso_generic(
$type
,
$version
,
$instance
,
$data
,
$length
);
}
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
;
}
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
)
.
pack
(
'C'
,
$image_type
)
.
pack
(
'H*'
,
$checksum1
)
.
pack
(
'v'
, 0xFF)
.
pack
(
'V'
,
$size
+25)
.
pack
(
'V'
,
$ref_count
)
.
pack
(
'V'
, 0x00000000)
.
pack
(
'C'
, 0x00)
.
pack
(
'C'
, 0x00)
.
pack
(
'C'
, 0x00)
.
pack
(
'C'
, 0x00)
;
return
$self
->_add_mso_generic(
$type
,
$version
,
$instance
,
$data
,
$length
);
}
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
= 0x046A
if
$image_type
== 5;
$instance
= 0x06E0
if
$image_type
== 6;
$instance
= 0x07A9
if
$image_type
== 7;
if
(
$image_type
== 7) {
$checksum1
=
$checksum2
.
$checksum1
;
}
my
$type
= 0xF018 +
$image_type
;
my
$version
= 0x0000;
my
$length
=
$size
+17;
my
$data
=
pack
(
'H*'
,
$checksum1
)
.
pack
(
'C'
, 0xFF)
.
$image_data
;
return
$self
->_add_mso_generic(
$type
,
$version
,
$instance
,
$data
,
$length
);
}
sub
_store_mso_opt {
my
$self
=
shift
;
my
$type
= 0xF00B;
my
$version
= 3;
my
$instance
= 3;
my
$data
=
''
;
my
$length
= 18;
$data
=
pack
"H*"
,
'BF0008000800810109000008C0014000'
.
'0008'
;
return
$self
->_add_mso_generic(
$type
,
$version
,
$instance
,
$data
,
$length
);
}
sub
_store_mso_split_menu_colors {
my
$self
=
shift
;
my
$type
= 0xF11E;
my
$version
= 0;
my
$instance
= 4;
my
$data
=
''
;
my
$length
= 16;
$data
=
pack
"H*"
,
'0D0000080C00000817000008F7000010'
;
return
$self
->_add_mso_generic(
$type
,
$version
,
$instance
,
$data
,
$length
);
}
1;