318 lines
7.2 KiB
Perl
318 lines
7.2 KiB
Perl
package Spreadsheet::WriteExcel::BIFFwriter;
|
|
|
|
###############################################################################
|
|
#
|
|
# BIFFwriter - An abstract base class for Excel workbooks and worksheets.
|
|
#
|
|
#
|
|
# Used in conjunction with Spreadsheet::WriteExcel
|
|
#
|
|
# Copyright 2000-2010, John McNamara, jmcnamara@cpan.org
|
|
#
|
|
# Documentation after __END__
|
|
#
|
|
|
|
use Exporter;
|
|
use strict;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
use vars qw($VERSION @ISA);
|
|
@ISA = qw(Exporter);
|
|
|
|
$VERSION = '2.40';
|
|
|
|
###############################################################################
|
|
#
|
|
# Class data.
|
|
#
|
|
my $byte_order = '';
|
|
my $BIFF_version = 0x0600;
|
|
|
|
|
|
###############################################################################
|
|
#
|
|
# new()
|
|
#
|
|
# Constructor
|
|
#
|
|
sub new {
|
|
|
|
my $class = $_[0];
|
|
|
|
my $self = {
|
|
_byte_order => '',
|
|
_data => '',
|
|
_datasize => 0,
|
|
_limit => 8224,
|
|
_ignore_continue => 0,
|
|
};
|
|
|
|
bless $self, $class;
|
|
$self->_set_byte_order();
|
|
return $self;
|
|
}
|
|
|
|
|
|
###############################################################################
|
|
#
|
|
# _set_byte_order()
|
|
#
|
|
# Determine the byte order and store it as class data to avoid
|
|
# recalculating it for each call to new().
|
|
#
|
|
sub _set_byte_order {
|
|
|
|
my $self = shift;
|
|
|
|
if ($byte_order eq ''){
|
|
# Check if "pack" gives the required IEEE 64bit float
|
|
my $teststr = pack "d", 1.2345;
|
|
my @hexdata =(0x8D, 0x97, 0x6E, 0x12, 0x83, 0xC0, 0xF3, 0x3F);
|
|
my $number = pack "C8", @hexdata;
|
|
|
|
if ($number eq $teststr) {
|
|
$byte_order = 0; # Little Endian
|
|
}
|
|
elsif ($number eq reverse($teststr)){
|
|
$byte_order = 1; # Big Endian
|
|
}
|
|
else {
|
|
# Give up. I'll fix this in a later version.
|
|
croak ( "Required floating point format not supported " .
|
|
"on this platform. See the portability section " .
|
|
"of the documentation."
|
|
);
|
|
}
|
|
}
|
|
$self->{_byte_order} = $byte_order;
|
|
}
|
|
|
|
|
|
###############################################################################
|
|
#
|
|
# _prepend($data)
|
|
#
|
|
# General storage function
|
|
#
|
|
sub _prepend {
|
|
|
|
my $self = shift;
|
|
my $data = join('', @_);
|
|
|
|
$data = $self->_add_continue($data) if length($data) > $self->{_limit};
|
|
|
|
$self->{_data} = $data . $self->{_data};
|
|
$self->{_datasize} += length($data);
|
|
|
|
return $data;
|
|
}
|
|
|
|
|
|
###############################################################################
|
|
#
|
|
# _append($data)
|
|
#
|
|
# General storage function
|
|
#
|
|
sub _append {
|
|
|
|
my $self = shift;
|
|
my $data = join('', @_);
|
|
|
|
$data = $self->_add_continue($data) if length($data) > $self->{_limit};
|
|
|
|
$self->{_data} = $self->{_data} . $data;
|
|
$self->{_datasize} += length($data);
|
|
|
|
return $data;
|
|
}
|
|
|
|
|
|
###############################################################################
|
|
#
|
|
# _store_bof($type)
|
|
#
|
|
# $type = 0x0005, Workbook
|
|
# $type = 0x0010, Worksheet
|
|
# $type = 0x0020, Chart
|
|
#
|
|
# Writes Excel BOF record to indicate the beginning of a stream or
|
|
# sub-stream in the BIFF file.
|
|
#
|
|
sub _store_bof {
|
|
|
|
my $self = shift;
|
|
my $record = 0x0809; # Record identifier
|
|
my $length = 0x0010; # Number of bytes to follow
|
|
|
|
my $version = $BIFF_version;
|
|
my $type = $_[0];
|
|
|
|
# According to the SDK $build and $year should be set to zero.
|
|
# However, this throws a warning in Excel 5. So, use these
|
|
# magic numbers.
|
|
my $build = 0x0DBB;
|
|
my $year = 0x07CC;
|
|
|
|
my $bfh = 0x00000041;
|
|
my $sfo = 0x00000006;
|
|
|
|
my $header = pack("vv", $record, $length);
|
|
my $data = pack("vvvvVV", $version, $type, $build, $year, $bfh, $sfo);
|
|
|
|
$self->_prepend($header, $data);
|
|
}
|
|
|
|
|
|
###############################################################################
|
|
#
|
|
# _store_eof()
|
|
#
|
|
# Writes Excel EOF record to indicate the end of a BIFF stream.
|
|
#
|
|
sub _store_eof {
|
|
|
|
my $self = shift;
|
|
my $record = 0x000A; # Record identifier
|
|
my $length = 0x0000; # Number of bytes to follow
|
|
|
|
my $header = pack("vv", $record, $length);
|
|
|
|
$self->_append($header);
|
|
}
|
|
|
|
|
|
###############################################################################
|
|
#
|
|
# _add_continue()
|
|
#
|
|
# Excel limits the size of BIFF records. In Excel 5 the limit is 2084 bytes. In
|
|
# Excel 97 the limit is 8228 bytes. Records that are longer than these limits
|
|
# must be split up into CONTINUE blocks.
|
|
#
|
|
# 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 $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.
|
|
#
|
|
$tmp = substr($data, 0, $limit, "");
|
|
substr($tmp, 2, 2, pack("v", $limit-4));
|
|
|
|
# Strip out chunks of 2080/8224 bytes +4 for the header.
|
|
while (length($data) > $limit) {
|
|
$header = pack("vv", $record, $limit);
|
|
$tmp .= $header;
|
|
$tmp .= substr($data, 0, $limit, "");
|
|
}
|
|
|
|
# Mop up the last of the data
|
|
$header = pack("vv", $record, length($data));
|
|
$tmp .= $header;
|
|
$tmp .= $data;
|
|
|
|
return $tmp ;
|
|
}
|
|
|
|
|
|
###############################################################################
|
|
#
|
|
# _add_mso_generic()
|
|
#
|
|
# Create a mso structure that is part of an Escher drawing object. These are
|
|
# are used for images, comments and filters. This generic method is used by
|
|
# other methods to create specific mso records.
|
|
#
|
|
# Returns the packed record.
|
|
#
|
|
sub _add_mso_generic {
|
|
|
|
my $self = shift;
|
|
my $type = $_[0];
|
|
my $version = $_[1];
|
|
my $instance = $_[2];
|
|
my $data = $_[3];
|
|
my $length = defined $_[4] ? $_[4] : length($data);
|
|
|
|
# The header contains version and instance info packed into 2 bytes.
|
|
my $header = $version | ($instance << 4);
|
|
|
|
my $record = pack "vvV", $header, $type, $length;
|
|
$record .= $data;
|
|
|
|
return $record;
|
|
}
|
|
|
|
|
|
###############################################################################
|
|
#
|
|
# For debugging
|
|
#
|
|
sub _hexout {
|
|
|
|
my $self = shift;
|
|
|
|
print +(caller(1))[3], "\n";
|
|
|
|
my $data = join '', @_;
|
|
|
|
my @bytes = unpack("H*", $data) =~ /../g;
|
|
|
|
while (@bytes > 16) {
|
|
print join " ", splice @bytes, 0, 16;
|
|
print "\n";
|
|
}
|
|
print join " ", @bytes, "\n\n";
|
|
}
|
|
|
|
|
|
|
|
1;
|
|
|
|
|
|
__END__
|
|
|
|
=encoding latin1
|
|
|
|
=head1 NAME
|
|
|
|
BIFFwriter - An abstract base class for Excel workbooks and worksheets.
|
|
|
|
=head1 SYNOPSIS
|
|
|
|
See the documentation for Spreadsheet::WriteExcel
|
|
|
|
=head1 DESCRIPTION
|
|
|
|
This module is used in conjunction with Spreadsheet::WriteExcel.
|
|
|
|
=head1 AUTHOR
|
|
|
|
John McNamara jmcnamara@cpan.org
|
|
|
|
=head1 COPYRIGHT
|
|
|
|
Copyright MM-MMX, John McNamara.
|
|
|
|
All Rights Reserved. This module is free software. It may be used, redistributed and/or modified under the same terms as Perl itself.
|