lib/writeexcel/biffwriter.rb in writeexcel-0.1.0 vs lib/writeexcel/biffwriter.rb in writeexcel-0.3.0
- old
+ new
@@ -1,260 +1,273 @@
-#
-# BIFFwriter - An abstract base class for Excel workbooks and worksheets.
-#
-#
-# Used in conjunction with Spreadsheet::WriteExcel
-#
-# Copyright 2000-2008, John McNamara, jmcnamara@cpan.org
-#
-# original written in Perl by John McNamara
-# converted to Ruby by Hideo Nakamura, cxn03651@msj.biglobe.ne.jp
-#
-
-
-require 'tempfile'
-
-class BIFFWriter
-
- BIFF_Version = 0x0600
- BigEndian = [1].pack("I") == [1].pack("N")
-
- attr_reader :byte_order, :data, :datasize
-
- ######################################################################
- # The args here aren't used by BIFFWriter, but they are needed by its
- # subclasses. I don't feel like creating multiple constructors.
- ######################################################################
-
- def initialize
- set_byte_order
- @data = ''
- @datasize = 0
- @limit = 8224
- @ignore_continue = 0
-
- # Open a tmp file to store the majority of the Worksheet data. If this fails,
- # for example due to write permissions, store the data in memory. This can be
- # slow for large files.
- @filehandle = Tempfile.new('spreadsheetwriteexcel')
- @filehandle.binmode
-
- # failed. store temporary data in memory.
- @using_tmpfile = @filehandle ? true : false
-
- end
-
- ###############################################################################
- #
- # _set_byte_order()
- #
- # Determine the byte order and store it as class data to avoid
- # recalculating it for each call to new().
- #
- def set_byte_order
- # Check if "pack" gives the required IEEE 64bit float
- teststr = [1.2345].pack("d")
- hexdata = [0x8D, 0x97, 0x6E, 0x12, 0x83, 0xC0, 0xF3, 0x3F]
- number = hexdata.pack("C8")
-
- if number == teststr
- @byte_order = 0 # Little Endian
- elsif number == teststr.reverse
- @byte_order = 1 # Big Endian
- else
- # Give up. I'll fix this in a later version.
- raise( "Required floating point format not supported " +
- "on this platform. See the portability section " +
- "of the documentation."
- )
- end
- end
-
- ###############################################################################
- #
- # _prepend($data)
- #
- # General storage function
- #
- def prepend(*args)
- d = args.join
- d = add_continue(d) if d.length > @limit
-
- @datasize += d.length
- @data = d + @data
-
-#print "prepend\n"
-#print d.unpack('C*').map! {|c| sprintf("%02X", c) }.join(' ') + "\n\n"
- return d
- end
-
- ###############################################################################
- #
- # _append($data)
- #
- # General storage function
- #
- def append(*args)
- d = args.join
- # Add CONTINUE records if necessary
- d = add_continue(d) if d.length > @limit
- if @using_tmpfile
- @filehandle.write d
- @datasize += d.length
- else
- @datasize += d.length
- @data = @data + d
- end
-#print "apend\n"
-#print d.unpack('C*').map! {|c| sprintf("%02X", c) }.join(' ') + "\n\n"
- return d
- end
-
- ###############################################################################
- #
- # get_data().
- #
- # Retrieves data from memory in one chunk, or from disk in $buffer
- # sized chunks.
- #
- def get_data
- buflen = 4096
-
- # Return data stored in memory
- unless @data.nil?
- tmp = @data
- @data = nil
- if @using_tmpfile
- @filehandle.open
- @filehandle.binmode
- end
- return tmp
- end
-
- # Return data stored on disk
- if @using_tmpfile
- return @filehandle.read(buflen)
- end
-
- # No data to return
- return nil
- end
-
- ###############################################################################
- #
- # _store_bof($type)
- #
- # $type = 0x0005, Workbook
- # $type = 0x0010, Worksheet
- #
- # Writes Excel BOF record to indicate the beginning of a stream or
- # sub-stream in the BIFF file.
- #
- def store_bof(type = 0x0005)
- record = 0x0809 # Record identifier
- length = 0x0010 # Number of bytes to follow
-
- # 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.
- build = 0x0DBB
- year = 0x07CC
-
- bfh = 0x00000041
- sfo = 0x00000006
-
- header = [record,length].pack("vv")
- data = [BIFF_Version,type,build,year,bfh,sfo].pack("vvvvVV")
-
- prepend(header, data)
- end
-
- ###############################################################################
- #
- # _store_eof()
- #
- # Writes Excel EOF record to indicate the end of a BIFF stream.
- #
- def store_eof
- record = 0x000A
- length = 0x0000
- header = [record,length].pack("vv")
-
- append(header)
- end
-
- ###############################################################################
- #
- # _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.
- #
- def add_continue(data)
- record = 0x003C # Record identifier
-
- # Skip this if another method handles the continue blocks.
- return data if @ignore_continue != 0
-
- # The first 2080/8224 bytes remain intact. However, we have to change
- # the length field of the record.
- #
-
- # in perl
- # $tmp = substr($data, 0, $limit, "");
- if data.length > @limit
- tmp = data[0, @limit]
- data[0, @limit] = ''
- else
- tmp = data.dup
- data = ''
- end
-
- tmp[2, 2] = [@limit-4].pack('v')
-
- # Strip out chunks of 2080/8224 bytes +4 for the header.
- while (data.length > @limit)
- header = [record, @limit].pack("vv")
- tmp = tmp + header + data[0, @limit]
- data[0, @limit] = ''
- end
-
- # Mop up the last of the data
- header = [record, data.length].pack("vv")
- tmp = tmp + header + data
-
- return tmp
- end
-
- ###############################################################################
- #
- # _add_mso_generic()
- # my $type = $_[0];
- # my $version = $_[1];
- # my $instance = $_[2];
- # my $data = $_[3];
- #
- # 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.
- #
- def add_mso_generic(type, version, instance, data, length = nil)
- length = length.nil? ? data.length : length
-
- # The header contains version and instance info packed into 2 bytes.
- header = version | (instance << 4)
-
- record = [header, type, length].pack('vvV') + data
-
- return record
- end
-
-end
+#
+# BIFFwriter - An abstract base class for Excel workbooks and worksheets.
+#
+#
+# Used in conjunction with WriteExcel
+#
+# Copyright 2000-2010, John McNamara, jmcnamara@cpan.org
+#
+# original written in Perl by John McNamara
+# converted to Ruby by Hideo Nakamura, cxn03651@msj.biglobe.ne.jp
+#
+
+
+require 'tempfile'
+
+class BIFFWriter #:nodoc:
+
+ BIFF_Version = 0x0600
+ BigEndian = [1].pack("I") == [1].pack("N")
+
+ attr_reader :byte_order, :data, :datasize
+
+ ######################################################################
+ # The args here aren't used by BIFFWriter, but they are needed by its
+ # subclasses. I don't feel like creating multiple constructors.
+ ######################################################################
+
+ def initialize
+ set_byte_order
+ @data = ''
+ @datasize = 0
+ @limit = 8224
+ @ignore_continue = 0
+
+ # Open a tmp file to store the majority of the Worksheet data. If this fails,
+ # for example due to write permissions, store the data in memory. This can be
+ # slow for large files.
+ @filehandle = Tempfile.new('spreadsheetwriteexcel')
+ @filehandle.binmode
+
+ # failed. store temporary data in memory.
+ @using_tmpfile = @filehandle ? true : false
+
+ end
+
+ ###############################################################################
+ #
+ # _set_byte_order()
+ #
+ # Determine the byte order and store it as class data to avoid
+ # recalculating it for each call to new().
+ #
+ def set_byte_order
+ # Check if "pack" gives the required IEEE 64bit float
+ teststr = [1.2345].pack("d")
+ hexdata = [0x8D, 0x97, 0x6E, 0x12, 0x83, 0xC0, 0xF3, 0x3F]
+ number = hexdata.pack("C8")
+
+ if number == teststr
+ @byte_order = 0 # Little Endian
+ elsif number == teststr.reverse
+ @byte_order = 1 # Big Endian
+ else
+ # Give up. I'll fix this in a later version.
+ raise( "Required floating point format not supported " +
+ "on this platform. See the portability section " +
+ "of the documentation."
+ )
+ end
+ end
+
+ ###############################################################################
+ #
+ # _prepend($data)
+ #
+ # General storage function
+ #
+ def prepend(*args)
+ d = args.join
+ d = add_continue(d) if d.length > @limit
+
+ @datasize += d.length
+ @data = d + @data
+
+ print "prepend\n" if defined?($debug)
+ print d.unpack('C*').map! {|c| sprintf("%02X", c) }.join(' ') + "\n\n" if defined?($debug)
+ return d
+ end
+
+ ###############################################################################
+ #
+ # _append($data)
+ #
+ # General storage function
+ #
+ def append(*args)
+ d = args.join
+ # Add CONTINUE records if necessary
+ d = add_continue(d) if d.length > @limit
+ if @using_tmpfile
+ @filehandle.write d
+ @datasize += d.length
+ else
+ @datasize += d.length
+ @data = @data + d
+ end
+
+ print "append\n" if defined?($debug)
+ print d.unpack('C*').map! {|c| sprintf("%02X", c) }.join(' ') + "\n\n" if defined?($debug)
+ return d
+ end
+
+ ###############################################################################
+ #
+ # get_data().
+ #
+ # Retrieves data from memory in one chunk, or from disk in $buffer
+ # sized chunks.
+ #
+ def get_data
+ buflen = 4096
+
+ # Return data stored in memory
+ unless @data.nil?
+ tmp = @data
+ @data = nil
+ if @using_tmpfile
+ @filehandle.open
+ @filehandle.binmode
+ end
+ return tmp
+ end
+
+ # Return data stored on disk
+ if @using_tmpfile
+ return @filehandle.read(buflen)
+ end
+
+ # No data to return
+ return nil
+ end
+
+ ###############################################################################
+ #
+ # _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.
+ #
+ def store_bof(type = 0x0005)
+ record = 0x0809 # Record identifier
+ length = 0x0010 # Number of bytes to follow
+
+ # 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.
+ build = 0x0DBB
+ year = 0x07CC
+
+ bfh = 0x00000041
+ sfo = 0x00000006
+
+ header = [record,length].pack("vv")
+ data = [BIFF_Version,type,build,year,bfh,sfo].pack("vvvvVV")
+
+ print "store_bof in #{__FILE__}\n" if defined?($debug)
+ prepend(header, data)
+ end
+
+ ###############################################################################
+ #
+ # _store_eof()
+ #
+ # Writes Excel EOF record to indicate the end of a BIFF stream.
+ #
+ def store_eof
+ record = 0x000A
+ length = 0x0000
+ header = [record,length].pack("vv")
+
+ print "store_eof in #{__FILE__}\n" if defined?($debug)
+ append(header)
+ end
+
+ ###############################################################################
+ #
+ # _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.
+ #
+ def add_continue(data)
+ record = 0x003C # Record identifier
+
+ # Skip this if another method handles the continue blocks.
+ return data if @ignore_continue != 0
+
+ # The first 2080/8224 bytes remain intact. However, we have to change
+ # the length field of the record.
+ #
+
+ # in perl
+ # $tmp = substr($data, 0, $limit, "");
+ if data.length > @limit
+ tmp = data[0, @limit]
+ data[0, @limit] = ''
+ else
+ tmp = data.dup
+ data = ''
+ end
+
+ tmp[2, 2] = [@limit-4].pack('v')
+
+ # Strip out chunks of 2080/8224 bytes +4 for the header.
+ while (data.length > @limit)
+ header = [record, @limit].pack("vv")
+ tmp = tmp + header + data[0, @limit]
+ data[0, @limit] = ''
+ end
+
+ # Mop up the last of the data
+ header = [record, data.length].pack("vv")
+ tmp = tmp + header + data
+
+ return tmp
+ end
+
+ ###############################################################################
+ #
+ # _add_mso_generic()
+ # my $type = $_[0];
+ # my $version = $_[1];
+ # my $instance = $_[2];
+ # my $data = $_[3];
+ #
+ # 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.
+ #
+ def add_mso_generic(type, version, instance, data, length = nil)
+ length = length.nil? ? data.length : length
+
+ # The header contains version and instance info packed into 2 bytes.
+ header = version | (instance << 4)
+
+ record = [header, type, length].pack('vvV') + data
+
+ return record
+ end
+
+ def not_using_tmpfile
+ @filehandle.close(true) if @filehandle
+ @filehandle = nil
+ @using_tmpfile = nil
+ end
+
+ def clear_data_for_test # :nodoc:
+ @data = ''
+ end
+end