lib/writeexcel/workbook.rb in writeexcel-0.6.6 vs lib/writeexcel/workbook.rb in writeexcel-0.6.7
- old
+ new
@@ -9,11 +9,10 @@
# 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 'digest/md5'
require 'nkf'
require 'writeexcel/biffwriter'
require 'writeexcel/worksheet'
require 'writeexcel/chart'
require 'writeexcel/format'
@@ -24,13 +23,15 @@
class Workbook < BIFFWriter
require 'writeexcel/properties'
require 'writeexcel/helper'
+ attr_reader :url_format, :parser, :tempdir, :date_1904
+ attr_reader :compatibility, :palette, :sinfo
+
BOF = 12 # :nodoc:
EOF = 4 # :nodoc:
- SheetName = "Sheet" # :nodoc:
#
# _file_ is a filename (as string) or io object where to out spreadsheet data.
# you can set default format of workbook using _default_formats_.
#
@@ -40,11 +41,11 @@
#
# workbook = WriteExcel.new('filename.xls')
# worksheet = workbook.add_worksheet
# worksheet.write(0, 0, 'Hi Excel!')
#
- # Here are some other examples of using new() with filenames:
+ # Here are some other examples of using new():
#
# workbook1 = WriteExcel.new(filename)
# workbook2 = WriteExcel.new('/tmp/filename.xls')
# workbook3 = WriteExcel.new("c:\\tmp\\filename.xls")
# workbook4 = WriteExcel.new('c:\tmp\filename.xls')
@@ -55,34 +56,34 @@
#
# The new() constructor returns a WriteExcel object that you can use to add
# worksheets and store data.
#
# If the file cannot be created, due to file permissions or some other reason,
- # new will return undef. Therefore, it is good practice to check the return
- # value of new before proceeding.
+ # new will raise Exception Errno::EXXX.
#
- # workbook = WriteExcel.new('protected.xls')
- # die "Problems creating new Excel file:" if workbook.nil?
+ # You can also pass a valid IO object to the new() constructor.:
+ # require 'stringio'
#
- # You can also pass a valid IO object to the new() constructor.
+ # io = StringIO.new
+ # workbook = WriteExcel.new(io) # After workbook.close, you can get excel data as io.string
#
+ # And, you can also pass default format properties.
+ #
+ # workbook = WriteExcel.new(filename, :font => 'Courier New', :size => 11)
+ #
def initialize(file, default_formats = {})
super()
@file = file
@default_formats = default_formats
@parser = Writeexcel::Formula.new(@byte_order)
@tempdir = nil
@date_1904 = false
@selected = 0
@xf_index = 0
- @fileclosed = false
@biffsize = 0
- @sheet_name = "Sheet"
- @chart_name = "Chart"
@sheet_count = 0
@chart_count = 0
- @url_format = ''
@codepage = 0x04E4
@country = 1
@worksheets = []
@formats = []
@palette = []
@@ -107,41 +108,19 @@
@ext_refs = {}
@mso_clusters = []
@mso_size = 0
- @hideobj = 0
- @compatibility = 0
+ @hideobj = false
@summary = ''
@doc_summary = ''
@localtime = Time.now
@defined_names = []
- # Add the in-built style formats and the default cell format.
- add_format(:type => 1) # 0 Normal
- add_format(:type => 1) # 1 RowLevel 1
- add_format(:type => 1) # 2 RowLevel 2
- add_format(:type => 1) # 3 RowLevel 3
- add_format(:type => 1) # 4 RowLevel 4
- add_format(:type => 1) # 5 RowLevel 5
- add_format(:type => 1) # 6 RowLevel 6
- add_format(:type => 1) # 7 RowLevel 7
- add_format(:type => 1) # 8 ColLevel 1
- add_format(:type => 1) # 9 ColLevel 2
- add_format(:type => 1) # 10 ColLevel 3
- add_format(:type => 1) # 11 ColLevel 4
- add_format(:type => 1) # 12 ColLevel 5
- add_format(:type => 1) # 13 ColLevel 6
- add_format(:type => 1) # 14 ColLevel 7
- add_format(default_formats) # 15 Cell XF
- add_format(:type => 1, :num_format => 0x2B) # 16 Comma
- add_format(:type => 1, :num_format => 0x29) # 17 Comma[0]
- add_format(:type => 1, :num_format => 0x2C) # 18 Currency
- add_format(:type => 1, :num_format => 0x2A) # 19 Currency[0]
- add_format(:type => 1, :num_format => 0x09) # 20 Percent
+ setup_built_in_formats(default_formats)
# Add the default format for hyperlinks
@url_format = add_format(:color => 'blue', :underline => 1)
if file.respond_to?(:to_str) && file != ''
@@ -151,30 +130,13 @@
@fh_out = file
end
# Set colour palette.
set_palette_xl97
-
- get_checksum_method
end
- ###############################################################################
#
- # get_checksum_method.
- #
- # Check for modules available to calculate image checksum. Excel uses MD4 but
- # MD5 will also work.
- #
- # ------- cxn03651 add -------
- # md5 can use in ruby. so, @checksum_method is always 3.
-
- def get_checksum_method #:nodoc:
- @checksum_method = 3
- end
- private :get_checksum_method
-
- #
# Calls finalization methods and explicitly close the OLEwriter files
# handle.
#
# An explicit close() is required if the file must be closed prior to performing
# some external action on it such as copying it, reading its size or attaching
@@ -182,11 +144,11 @@
#
# In general, if you create a file with a size of 0 bytes or you fail to create
# a file you need to call close().
#
def close
- return if @fileclosed # Prevent close() from being called twice.
+ return if fileclosed? # Prevent close() from being called twice.
@fileclosed = true
store_workbook
cleanup
end
@@ -232,11 +194,11 @@
end
#
# Add a new worksheet to the Excel workbook.
#
- # if _sheetname_ is UTF-16BE format, pass 1 as _encoding_.
+ # if _sheetname_ is UTF-16BE format, pass true as _name_utf16be_.
#
# At least one worksheet should be added to a new workbook. A worksheet is
# used to write data into cells:
#
# worksheet1 = workbook.add_worksheet # Sheet1
@@ -257,34 +219,22 @@
# worksheet = workbook.add_worksheet("γ·γΌγε")
#
# UTF-16BE worksheet names using an additional optional parameter:
#
# name = [0x263a].pack('n')
- # worksheet = workbook.add_worksheet(name, 1) # Smiley
+ # worksheet = workbook.add_worksheet(name, true) # Smiley
#
- def add_worksheet(sheetname = '', encoding = 0)
+ def add_worksheet(sheetname = '', name_utf16be = false)
index = @worksheets.size
- name, encoding = check_sheetname(sheetname, encoding)
+ name, name_utf16be = check_sheetname(sheetname, name_utf16be)
- # Porters take note, the following scheme of passing references to Workbook
- # data (in the \$self->{_foo} cases) instead of a reference to the Workbook
- # itself is a workaround to avoid circular references between Workbook and
- # Worksheet objects. Feel free to implement this in any way the suits your
- # language.
- #
init_data = [
+ self,
name,
index,
- encoding,
- @url_format,
- @parser,
- @tempdir,
- @date_1904,
- @compatibility,
- nil, # Palette. Not used yet. See add_chart().
- @sinfo,
+ name_utf16be
]
worksheet = Writeexcel::Worksheet.new(*init_data)
@worksheets[index] = worksheet # Store ref for iterator
@parser.set_ext_sheets(name, index) # Store names in Formula.rb
worksheet
@@ -301,14 +251,14 @@
#
# The properties that can be set are:
#
# :type (required)
# :name (optional)
- # :encoding (optional)
+ # :name_utf16be (optional)
# :embedded (optional)
#
- # * type
+ # * :type
#
# This is a required parameter. It defines the type of chart that will be created.
#
# chart = workbook.add_chart(:type => 'Chart::Line')
#
@@ -325,29 +275,34 @@
# * :name
#
# Set the name for the chart sheet. The name property is optional and
# if it isn't supplied will default to Chart1 .. n. The name must be
# a valid Excel worksheet name. See add_worksheet() for more details
- # on valid sheet names. The name property can be omitted for embedded
+ # on valid sheet names. The :name property can be omitted for embedded
# charts.
#
# chart = workbook.add_chart(
# :type => 'Chart::Line',
# :name => 'Results Chart'
# )
#
+ # * :name_utf16be
+ #
+ # if :name is UTF-16BE format, pass true as :name_utf16be
+ #
# * :encoding
#
# if :name is UTF-16BE format, pass 1 as :encoding.
+ # This key is obsolete in v0.7 or later. Use :name_utf16be instead.
#
# * :embedded
#
- # Specifies that the Chart object will be inserted in a worksheet via
+ # Specifies true that the Chart object will be inserted in a worksheet via
# the insert_chart() Worksheet method. It is an error to try insert a
# Chart that doesn't have this flag set.
#
- # chart = workbook.add_chart(:type => 'Chart::Line', :embedded => 1)
+ # chart = workbook.add_chart(:type => 'Chart::Line', :embedded => true)
#
# # Configure the chart.
# ...
#
# # Insert the chart into the a worksheet.
@@ -357,11 +312,11 @@
# chart object once it is created. See also the chart_*.rb programs in the
# examples directory of the distro.
#
def add_chart(properties)
name = ''
- encoding = 0
+ name_utf16be = false
index = @worksheets.size
# Type must be specified so we can create the required chart instance.
type = properties[:type]
raise "Must define chart type in add_chart()" if type.nil?
@@ -369,25 +324,20 @@
# Ensure that the chart defaults to non embedded.
embedded = properties[:embedded]
# Check the worksheet name for non-embedded charts.
unless embedded
- name, encoding =
- check_sheetname(properties[:name], properties[:encoding], true)
+ properties[:name_utf16be] = true if properties[:encoding] == 1
+ name, name_utf16be =
+ check_sheetname(properties[:name], properties[:name_utf16be], true)
end
init_data = [
+ self,
name,
index,
- encoding,
- @url_format,
- @parser,
- @tempdir,
- @date_1904 ? 1 : 0,
- @compatibility,
- @palette,
- @sinfo
+ name_utf16be
]
chart = Writeexcel::Chart.factory(type, *init_data)
# If the chart isn't embedded let the workbook control it.
if !embedded
@@ -412,154 +362,30 @@
#
# This feature is semi-deprecated in favour of the "native" charts created
# using add_chart(). Read external_charts.txt in the external_charts
# directory of the distro for a full explanation.
#
- def add_chart_ext(filename, name, encoding = 0)
+ def add_chart_ext(filename, chartname, name_utf16be = false)
index = @worksheets.size
type = 'extarnal'
- name, encoding = check_sheetname(name, encoding)
+ name, name_utf16be = check_sheetname(chartname, name_utf16be)
init_data = [
filename,
name,
index,
- encoding,
+ name_utf16be,
@sinfo
]
chart = Writeexcel::Chart.factory(self, type, init_data)
@worksheets[index] = chart # Store ref for iterator
chart
end
- ###############################################################################
#
- # check_sheetname(name, encoding)
- #
- # Check for valid worksheet names. We check the length, if it contains any
- # invalid characters and if the name is unique in the workbook.
- #
- def check_sheetname(name, encoding = 0, chart = nil) #:nodoc:
- encoding ||= 0
-
- # Increment the Sheet/Chart number used for default sheet names below.
- if chart
- @chart_count += 1
- else
- @sheet_count += 1
- end
-
- # Supply default Sheet/Chart name if none has been defined.
- if name.nil? || name == ""
- encoding = 0
- if chart
- name = @chart_name + @chart_count.to_s
- else
- name = @sheet_name + @sheet_count.to_s
- end
- end
-
- ruby_19 { name = convert_to_ascii_if_ascii(name) }
- check_sheetname_length(name, encoding)
- check_sheetname_even(name) if encoding == 1
- check_sheetname_valid_chars(name, encoding)
-
- # Handle utf8 strings
- if is_utf8?(name)
- name = utf8_to_16be(name)
- encoding = 1
- end
-
- check_sheetname_uniq(name, encoding)
- [name, encoding]
- end
- private :check_sheetname
-
- def check_sheetname_length(name, encoding) #:nodoc:
- # Check that sheetname is <= 31 (1 or 2 byte chars). Excel limit.
- limit = encoding != 0 ? 62 : 31
- raise "Sheetname $name must be <= 31 chars" if name.bytesize > limit
- end
- private :check_sheetname_length
-
- def check_sheetname_even(name) #:nodoc:
- # Check that Unicode sheetname has an even number of bytes
- if (name.bytesize % 2 != 0)
- raise "Odd number of bytes in Unicode worksheet name: #{name}"
- end
- end
- private :check_sheetname_even
-
- def check_sheetname_valid_chars(name, encoding) #:nodoc:
- # Check that sheetname doesn't contain any invalid characters
- invalid_char = %r![\[\]:*?/\\]!
- if encoding != 1 && name =~ invalid_char
- # Check ASCII names
- raise "Invalid character []:*?/\\ in worksheet name: #{name}"
- else
- # Extract any 8bit clean chars from the UTF16 name and validate them.
- str = name.dup
- while str =~ /../m
- hi, lo = $~[0].unpack('aa')
- if hi == "\0" and lo =~ invalid_char
- raise 'Invalid character []:*?/\\ in worksheet name: ' + name
- end
- str = $~.post_match
- end
- end
- end
- private :check_sheetname_valid_chars
-
- # Check that the worksheet name doesn't already exist since this is a fatal
- # error in Excel 97. The check must also exclude case insensitive matches
- # since the names 'Sheet1' and 'sheet1' are equivalent. The tests also have
- # to take the encoding into account.
- #
- def check_sheetname_uniq(name, encoding) #:nodoc:
- @worksheets.each do |worksheet|
- name_a = name
- encd_a = encoding
- name_b = worksheet.name
- encd_b = worksheet.encoding
- error = false
-
- if encd_a == 0 and encd_b == 0
- error = (name_a.downcase == name_b.downcase)
- elsif encd_a == 0 and encd_b == 1
- name_a = ascii_to_16be(name_a)
- error = (name_a.downcase == name_b.downcase)
- elsif encd_a == 1 and encd_b == 0
- name_b = ascii_to_16be(name_b)
- error = (name_a.downcase == name_b.downcase)
- elsif encd_a == 1 and encd_b == 1
- # TODO : not converted yet.
-
- # 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.
- #
- # Strip out the high bytes (funkily).
- # 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);
- # }
- end
- if error
- raise "Worksheet name '#{name}', with case ignored, is already in use"
- end
- end
- end
- private :check_sheetname_uniq
-
- #
# The add_format method can be used to create new Format objects which are
# used to apply formatting to a cell. You can either define the properties
# at creation time via a hash of property values or later via method calls.
#
# format1 = workbook.add_format(props) # Set properties at creation
@@ -623,11 +449,11 @@
#
# This requires additional data to be stored in memory until the file is
# about to be written. This incurs a memory and speed penalty and may not be
# suitable for very large files.
#
- def compatibility_mode(mode = 1)
+ def compatibility_mode(mode = true)
unless sheets.empty?
raise "compatibility_mode() must be called before add_worksheet()"
end
@compatibility = mode
end
@@ -643,24 +469,28 @@
# the other.
#
# WriteExcel stores dates in the 1900 format by default. If you wish to
# change this you can call the set_1904() workbook method. You can query
# the current value by calling the get_1904() workbook method. This returns
- # 0 for 1900 and 1 for 1904.
+ # false for 1900 and true for 1904.
#
# See also "DATES AND TIME IN EXCEL" for more information about working
# with Excel's date system.
#
# In general you probably won't need to use set_1904().
#
def set_1904(mode = true)
unless sheets.empty?
raise "set_1904() must be called before add_worksheet()"
end
- @date_1904 = mode
+ @date_1904 = (!mode || mode == 0) ? false : true
end
+ def get_1904
+ @date_1904
+ end
+
#
# Change the RGB components of the elements in the colour palette.
#
# The set_custom_color() method can be used to override one of the built-in
# palette values with a more suitable colour.
@@ -708,11 +538,11 @@
# :bg_color => $ferrari,
# :pattern => 1,
# :border => 1
# )
#
- def set_custom_color(index = nil, red = nil, green = nil, blue = nil)
+ def set_custom_color(index, red = nil, green = nil, blue = nil)
# Match a HTML #xxyyzz style parameter
if !red.nil? && red =~ /^#(\w\w)(\w\w)(\w\w)/
red = $1.hex
green = $2.hex
blue = $3.hex
@@ -736,82 +566,13 @@
@palette[index] = [red, green, blue, 0]
index + 8
end
- ###############################################################################
#
- # set_palette_xl97()
- #
- # Sets the colour palette to the Excel 97+ default.
- #
- def set_palette_xl97 #:nodoc:
- @palette = [
- [0x00, 0x00, 0x00, 0x00], # 8
- [0xff, 0xff, 0xff, 0x00], # 9
- [0xff, 0x00, 0x00, 0x00], # 10
- [0x00, 0xff, 0x00, 0x00], # 11
- [0x00, 0x00, 0xff, 0x00], # 12
- [0xff, 0xff, 0x00, 0x00], # 13
- [0xff, 0x00, 0xff, 0x00], # 14
- [0x00, 0xff, 0xff, 0x00], # 15
- [0x80, 0x00, 0x00, 0x00], # 16
- [0x00, 0x80, 0x00, 0x00], # 17
- [0x00, 0x00, 0x80, 0x00], # 18
- [0x80, 0x80, 0x00, 0x00], # 19
- [0x80, 0x00, 0x80, 0x00], # 20
- [0x00, 0x80, 0x80, 0x00], # 21
- [0xc0, 0xc0, 0xc0, 0x00], # 22
- [0x80, 0x80, 0x80, 0x00], # 23
- [0x99, 0x99, 0xff, 0x00], # 24
- [0x99, 0x33, 0x66, 0x00], # 25
- [0xff, 0xff, 0xcc, 0x00], # 26
- [0xcc, 0xff, 0xff, 0x00], # 27
- [0x66, 0x00, 0x66, 0x00], # 28
- [0xff, 0x80, 0x80, 0x00], # 29
- [0x00, 0x66, 0xcc, 0x00], # 30
- [0xcc, 0xcc, 0xff, 0x00], # 31
- [0x00, 0x00, 0x80, 0x00], # 32
- [0xff, 0x00, 0xff, 0x00], # 33
- [0xff, 0xff, 0x00, 0x00], # 34
- [0x00, 0xff, 0xff, 0x00], # 35
- [0x80, 0x00, 0x80, 0x00], # 36
- [0x80, 0x00, 0x00, 0x00], # 37
- [0x00, 0x80, 0x80, 0x00], # 38
- [0x00, 0x00, 0xff, 0x00], # 39
- [0x00, 0xcc, 0xff, 0x00], # 40
- [0xcc, 0xff, 0xff, 0x00], # 41
- [0xcc, 0xff, 0xcc, 0x00], # 42
- [0xff, 0xff, 0x99, 0x00], # 43
- [0x99, 0xcc, 0xff, 0x00], # 44
- [0xff, 0x99, 0xcc, 0x00], # 45
- [0xcc, 0x99, 0xff, 0x00], # 46
- [0xff, 0xcc, 0x99, 0x00], # 47
- [0x33, 0x66, 0xff, 0x00], # 48
- [0x33, 0xcc, 0xcc, 0x00], # 49
- [0x99, 0xcc, 0x00, 0x00], # 50
- [0xff, 0xcc, 0x00, 0x00], # 51
- [0xff, 0x99, 0x00, 0x00], # 52
- [0xff, 0x66, 0x00, 0x00], # 53
- [0x66, 0x66, 0x99, 0x00], # 54
- [0x96, 0x96, 0x96, 0x00], # 55
- [0x00, 0x33, 0x66, 0x00], # 56
- [0x33, 0x99, 0x66, 0x00], # 57
- [0x00, 0x33, 0x00, 0x00], # 58
- [0x33, 0x33, 0x00, 0x00], # 59
- [0x99, 0x33, 0x00, 0x00], # 60
- [0x99, 0x33, 0x66, 0x00], # 61
- [0x33, 0x33, 0x99, 0x00], # 62
- [0x33, 0x33, 0x33, 0x00] # 63
- ]
- end
- private :set_palette_xl97
-
- #
# Change the default temp directory
#
- #
# For speed and efficiency WriteExcel stores worksheet data in temporary
# files prior to assembling the final workbook.
#
# If WriteExcel is unable to create these temporary files it will store
# the required data in memory. This can be slow for large files.
@@ -962,18 +723,18 @@
# :comments => 'Created with Ruby and WriteExcel',
# )
#
# The properties that can be set are:
#
- # * title
- # * subject
- # * author
- # * manager
- # * company
- # * category
- # * keywords
- # * comments
+ # * :title
+ # * :subject
+ # * :author
+ # * :manager
+ # * :company
+ # * :category
+ # * :keywords
+ # * :comments
#
# User defined properties are not supported due to effort required.
#
# You can also pass UTF-8 strings as properties.
#
@@ -988,11 +749,11 @@
# consider adding a comment such as the following when using document
# properties:
#
# workbook.set_properties(
# ...,
- # :comments => 'Created with Ruby and WriteExcel',
+ # :comments => 'Created with Ruby and writeexcel',
# ...,
# )
#
# See also the properties.rb program in the examples directory of the distro.
#
@@ -1040,25 +801,272 @@
# Pack the property sets.
@doc_summary =
create_doc_summary_property_set(property_sets(properties, params))
# Set a flag for when the files is written.
- add_doc_properties = true
+ @add_doc_properties = true
end
+ def str_unique=(val) # :nodoc:
+ @sinfo[:str_unique] = val
+ end
+
+ def extsst_buckets # :nodoc:
+ @extsst_buckets
+ end
+
+ def extsst_bucket_size # :nodoc:
+ @extsst_bucket_size
+ end
+
+ def biff_only=(val) # :nodoc:
+ @biff_only = val
+ end
+
+ def summary # :nodoc:
+ @summary
+ end
+
+ def localtime=(val) # :nodoc:
+ @localtime = val
+ end
+
+ #==========================================
+ # Internal method
+ #==========================================
+
+ private
+
+ # Add the in-built style formats and the default cell format.
+ def setup_built_in_formats(default_formats) # :nodoc:
+ add_format(:type => 1) # 0 Normal
+ add_format(:type => 1) # 1 RowLevel 1
+ add_format(:type => 1) # 2 RowLevel 2
+ add_format(:type => 1) # 3 RowLevel 3
+ add_format(:type => 1) # 4 RowLevel 4
+ add_format(:type => 1) # 5 RowLevel 5
+ add_format(:type => 1) # 6 RowLevel 6
+ add_format(:type => 1) # 7 RowLevel 7
+ add_format(:type => 1) # 8 ColLevel 1
+ add_format(:type => 1) # 9 ColLevel 2
+ add_format(:type => 1) # 10 ColLevel 3
+ add_format(:type => 1) # 11 ColLevel 4
+ add_format(:type => 1) # 12 ColLevel 5
+ add_format(:type => 1) # 13 ColLevel 6
+ add_format(:type => 1) # 14 ColLevel 7
+ add_format(default_formats) # 15 Cell XF
+ add_format(:type => 1, :num_format => 0x2B) # 16 Comma
+ add_format(:type => 1, :num_format => 0x29) # 17 Comma[0]
+ add_format(:type => 1, :num_format => 0x2C) # 18 Currency
+ add_format(:type => 1, :num_format => 0x2A) # 19 Currency[0]
+ add_format(:type => 1, :num_format => 0x09) # 20 Percent
+ end
+
+ def fileclosed?
+ @fileclosed || false
+ end
+
+ #
+ # Check for valid worksheet names. We check the length, if it contains any
+ # invalid characters and if the name is unique in the workbook.
+ #
+ def check_sheetname(name, name_utf16be = false, chart = nil) #:nodoc:
+ # name_utf16be parameter may set 0/1 in v0.6.6 or earlier
+ name_utf16be = false if name_utf16be == 0
+ name_utf16be = true if name_utf16be == 1
+
+ increment_sheet_chart_count(chart)
+ if name.nil? || name == ""
+ name_utf16be = false
+ name = default_sheet_chart_name(chart)
+ end
+
+ ruby_19 { name = convert_to_ascii_if_ascii(name) }
+ check_sheetname_length(name, name_utf16be)
+ check_sheetname_even(name) if name_utf16be
+ check_sheetname_valid_chars(name, name_utf16be)
+
+ # Handle utf8 strings
+ if is_utf8?(name)
+ name = utf8_to_16be(name)
+ name_utf16be = true
+ end
+
+ check_sheetname_uniq(name, name_utf16be)
+ [name, name_utf16be]
+ end
+
+ # Increment the Sheet/Chart number used for default sheet names below.
+ def increment_sheet_chart_count(chart)
+ if chart
+ @chart_count += 1
+ else
+ @sheet_count += 1
+ end
+ end
+
+ # Supply default Sheet/Chart name if none has been defined.
+ def default_sheet_chart_name(chart)
+ if chart
+ "Chart#{@chart_count}"
+ else
+ "Sheet#{@sheet_count}"
+ end
+ end
+
+ def check_sheetname_length(name, name_utf16be) #:nodoc:
+ # Check that sheetname is <= 31 (1 or 2 byte chars). Excel limit.
+ limit = name_utf16be ? 62 : 31
+ raise "Sheetname $name must be <= 31 chars" if name.bytesize > limit
+ end
+
+ def check_sheetname_even(name) #:nodoc:
+ # Check that Unicode sheetname has an even number of bytes
+ if (name.bytesize % 2 != 0)
+ raise "Odd number of bytes in Unicode worksheet name: #{name}"
+ end
+ end
+
+ def check_sheetname_valid_chars(name, name_utf16be) #:nodoc:
+ # Check that sheetname doesn't contain any invalid characters
+ invalid_char = %r![\[\]:*?/\\]!
+ if !name_utf16be && name =~ invalid_char
+ # Check ASCII names
+ raise "Invalid character []:*?/\\ in worksheet name: #{name}"
+ else
+ # Extract any 8bit clean chars from the UTF16 name and validate them.
+ str = name.dup
+ while str =~ /../m
+ hi, lo = $~[0].unpack('aa')
+ if hi == "\0" and lo =~ invalid_char
+ raise 'Invalid character []:*?/\\ in worksheet name: ' + name
+ end
+ str = $~.post_match
+ end
+ end
+ end
+
+ # Check that the worksheet name doesn't already exist since this is a fatal
+ # error in Excel 97. The check must also exclude case insensitive matches
+ # since the names 'Sheet1' and 'sheet1' are equivalent. The tests also have
+ # to take the name_utf16be into account.
+ #
+ def check_sheetname_uniq(name, name_utf16be) #:nodoc:
+ @worksheets.each do |worksheet|
+ name_a = name
+ encd_a = name_utf16be
+ name_b = worksheet.name
+ encd_b = worksheet.is_name_utf16be?
+ error = false
+
+ if !encd_a and !encd_b
+ error = (name_a.downcase == name_b.downcase)
+ elsif !encd_a and encd_b
+ name_a = ascii_to_16be(name_a)
+ error = (name_a.downcase == name_b.downcase)
+ elsif encd_a and !encd_b
+ name_b = ascii_to_16be(name_b)
+ error = (name_a.downcase == name_b.downcase)
+ elsif encd_a and encd_b
+ error = (name_a.downcase == name_b.downcase)
+ # TODO : not converted yet.
+
+ # 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.
+ #
+ # Strip out the high bytes (funkily).
+ # 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);
+ # }
+ end
+ if error
+ raise "Worksheet name '#{name}', with case ignored, is already in use"
+ end
+ end
+ end
+
+ #
+ # Sets the colour palette to the Excel 97+ default.
+ #
+ def set_palette_xl97 #:nodoc:
+ @palette = [
+ [0x00, 0x00, 0x00, 0x00], # 8
+ [0xff, 0xff, 0xff, 0x00], # 9
+ [0xff, 0x00, 0x00, 0x00], # 10
+ [0x00, 0xff, 0x00, 0x00], # 11
+ [0x00, 0x00, 0xff, 0x00], # 12
+ [0xff, 0xff, 0x00, 0x00], # 13
+ [0xff, 0x00, 0xff, 0x00], # 14
+ [0x00, 0xff, 0xff, 0x00], # 15
+ [0x80, 0x00, 0x00, 0x00], # 16
+ [0x00, 0x80, 0x00, 0x00], # 17
+ [0x00, 0x00, 0x80, 0x00], # 18
+ [0x80, 0x80, 0x00, 0x00], # 19
+ [0x80, 0x00, 0x80, 0x00], # 20
+ [0x00, 0x80, 0x80, 0x00], # 21
+ [0xc0, 0xc0, 0xc0, 0x00], # 22
+ [0x80, 0x80, 0x80, 0x00], # 23
+ [0x99, 0x99, 0xff, 0x00], # 24
+ [0x99, 0x33, 0x66, 0x00], # 25
+ [0xff, 0xff, 0xcc, 0x00], # 26
+ [0xcc, 0xff, 0xff, 0x00], # 27
+ [0x66, 0x00, 0x66, 0x00], # 28
+ [0xff, 0x80, 0x80, 0x00], # 29
+ [0x00, 0x66, 0xcc, 0x00], # 30
+ [0xcc, 0xcc, 0xff, 0x00], # 31
+ [0x00, 0x00, 0x80, 0x00], # 32
+ [0xff, 0x00, 0xff, 0x00], # 33
+ [0xff, 0xff, 0x00, 0x00], # 34
+ [0x00, 0xff, 0xff, 0x00], # 35
+ [0x80, 0x00, 0x80, 0x00], # 36
+ [0x80, 0x00, 0x00, 0x00], # 37
+ [0x00, 0x80, 0x80, 0x00], # 38
+ [0x00, 0x00, 0xff, 0x00], # 39
+ [0x00, 0xcc, 0xff, 0x00], # 40
+ [0xcc, 0xff, 0xff, 0x00], # 41
+ [0xcc, 0xff, 0xcc, 0x00], # 42
+ [0xff, 0xff, 0x99, 0x00], # 43
+ [0x99, 0xcc, 0xff, 0x00], # 44
+ [0xff, 0x99, 0xcc, 0x00], # 45
+ [0xcc, 0x99, 0xff, 0x00], # 46
+ [0xff, 0xcc, 0x99, 0x00], # 47
+ [0x33, 0x66, 0xff, 0x00], # 48
+ [0x33, 0xcc, 0xcc, 0x00], # 49
+ [0x99, 0xcc, 0x00, 0x00], # 50
+ [0xff, 0xcc, 0x00, 0x00], # 51
+ [0xff, 0x99, 0x00, 0x00], # 52
+ [0xff, 0x66, 0x00, 0x00], # 53
+ [0x66, 0x66, 0x99, 0x00], # 54
+ [0x96, 0x96, 0x96, 0x00], # 55
+ [0x00, 0x33, 0x66, 0x00], # 56
+ [0x33, 0x99, 0x66, 0x00], # 57
+ [0x00, 0x33, 0x00, 0x00], # 58
+ [0x33, 0x33, 0x00, 0x00], # 59
+ [0x99, 0x33, 0x00, 0x00], # 60
+ [0x99, 0x33, 0x66, 0x00], # 61
+ [0x33, 0x33, 0x99, 0x00], # 62
+ [0x33, 0x33, 0x33, 0x00] # 63
+ ]
+ end
+
def property_set(property, params) #:nodoc:
valid_properties[property][0..1] + [params[property]]
end
- private :property_set
def property_sets(properties, params) #:nodoc:
properties.select { |property| params[property.to_sym] }.
collect do |property|
property_set(property.to_sym, params)
end
end
- private :property_sets
# List of valid input parameters.
def valid_properties #:nodoc:
{
:codepage => [0x0001, 'VT_I2' ],
@@ -1073,25 +1081,20 @@
:manager => [0x000E, 'VT_LPSTR' ],
:company => [0x000F, 'VT_LPSTR' ],
:utf8 => 1
}
end
- private :valid_properties
def check_valid_params_for_properties(params) #:nodoc:
params.each_key do |k|
unless valid_properties.has_key?(k)
raise "Unknown parameter '#{k}' in set_properties()"
end
end
end
- private :check_valid_params_for_properties
- ###############################################################################
#
- # get_property_set_codepage()
- #
# Get the character codepage used by the strings in a property set. If one of
# the strings used is utf8 then the codepage is marked as utf8. Otherwise
# Latin 1 is used (although in our case this is limited to 7bit ASCII).
#
def get_property_set_codepage(params, properties) #:nodoc:
@@ -1101,16 +1104,12 @@
next unless params.has_key?(property.to_sym)
return 0xFDE9 if is_utf8?(params[property.to_sym])
end
return 0x04E4 # Default codepage, Latin 1.
end
- private :get_property_set_codepage
- ###############################################################################
#
- # store_workbook()
- #
# Assemble worksheets into a workbook and send the BIFF data to an OLE
# storage.
#
def store_workbook #:nodoc:
# Add a default worksheet if non have been added.
@@ -1122,11 +1121,11 @@
# Ensure that at least one worksheet has been selected.
@worksheets[0].select if @sinfo[:activesheet] == 0
# Calculate the number of selected sheet tabs and set the active sheet.
@worksheets.each do |sheet|
- @selected += 1 if sheet.selected != 0
+ @selected += 1 if sheet.selected?
sheet.active = 1 if sheet.index == @sinfo[:activesheet]
end
# Add Workbook globals
store_bof(0x0005)
@@ -1143,17 +1142,11 @@
# Calculate the offsets required by the BOUNDSHEET records
calc_sheet_offsets
# Add BOUNDSHEET records.
@worksheets.each do |sheet|
- store_boundsheet(
- sheet.name,
- sheet.offset,
- sheet.sheet_type,
- sheet.hidden,
- sheet.encoding
- )
+ store_boundsheet(sheet)
end
# NOTE: If any records are added between here and EOF the
# calc_sheet_offsets() should be updated to include the new length.
store_country
@@ -1168,122 +1161,98 @@
# End Workbook globals
store_eof
# Store the workbook in an OLE container
- store_ole_filie
+ store_ole_file
end
- private :store_workbook
- def str_unique=(val) # :nodoc:
- @sinfo[:str_unique] = val
- end
-
- def extsst_buckets # :nodoc:
- @extsst_buckets
- end
-
- def extsst_bucket_size # :nodoc:
- @extsst_bucket_size
- end
-
- def biff_only=(val) # :nodoc:
- @biff_only = val
- end
-
- def summary # :nodoc:
- @summary
- end
-
- def localtime=(val) # :nodoc:
- @localtime = val
- end
-
- ###############################################################################
#
- # store_ole_filie()
- #
# Store the workbook in an OLE container using the default handler or using
# OLE::Storage_Lite if the workbook data is > ~ 7MB.
#
- def store_ole_filie #:nodoc:
+ def store_ole_file #:nodoc:
maxsize = 7_087_104
# maxsize = 1
if !add_doc_properties && @biffsize <= maxsize
- # Write the OLE file using OLEwriter if data <= 7MB
- ole = OLEWriter.new(@fh_out)
+ store_through_ole_writer
+ else
+ store_through_ole_storage_lite
+ end
+ end
- # Write the BIFF data without the OLE container for testing.
- ole.biff_only = @biff_only
+ def store_through_ole_writer
+ # Write the OLE file using OLEwriter if data <= 7MB
+ ole = OLEWriter.new(@fh_out)
- # Indicate that we created the filehandle and want to close it.
- ole.internal_fh = @internal_fh
+ # Write the BIFF data without the OLE container for testing.
+ ole.biff_only = @biff_only
- ole.set_size(@biffsize)
- ole.write_header
+ # Indicate that we created the filehandle and want to close it.
+ ole.internal_fh = @internal_fh
- while tmp = get_data
+ ole.set_size(@biffsize)
+ ole.write_header
+
+ while tmp = get_data
+ ole.write(tmp)
+ end
+
+ @worksheets.each do |worksheet|
+ while tmp = worksheet.get_data
ole.write(tmp)
end
+ end
- @worksheets.each do |worksheet|
- while tmp = worksheet.get_data
- ole.write(tmp)
- end
- end
+ return ole.close
+ end
- return ole.close
- else
- # Create the Workbook stream.
- stream = 'Workbook'.unpack('C*').pack('v*')
- workbook = OLEStorageLitePPSFile.new(stream)
- workbook.set_file # use tempfile
+ def store_through_ole_storage_lite
+ # Create the Workbook stream.
+ stream = 'Workbook'.unpack('C*').pack('v*')
+ workbook = OLEStorageLitePPSFile.new(stream)
+ workbook.set_file # use tempfile
- while tmp = get_data
+ while tmp = get_data
+ workbook.append(tmp)
+ end
+
+ @worksheets.each do |worksheet|
+ while tmp = worksheet.get_data
workbook.append(tmp)
end
+ end
- @worksheets.each do |worksheet|
- while tmp = worksheet.get_data
- workbook.append(tmp)
- end
- end
+ streams = []
+ streams << workbook
- streams = []
- streams << workbook
-
- # Create the properties streams, if any.
- if add_doc_properties
- stream = "\5SummaryInformation".unpack('C*').pack('v*')
- summary = OLEStorageLitePPSFile.new(stream, @summary)
- streams << summary
- stream = "\5DocumentSummaryInformation".unpack('C*').pack('v*')
- summary = OLEStorageLitePPSFile.new(stream, @doc_summary)
- streams << summary
- end
- # Create the OLE root document and add the substreams.
- localtime = @localtime.to_a[0..5]
- localtime[4] -= 1 # month
- localtime[5] -= 1900
- ole_root = OLEStorageLitePPSRoot.new(
- localtime,
- localtime,
- streams
- )
- ole_root.save(@file)
-
- # Close the filehandle if it was created internally.
- return @fh_out.close if @internal_fh != 0
+ # Create the properties streams, if any.
+ if add_doc_properties
+ stream = "\5SummaryInformation".unpack('C*').pack('v*')
+ summary = OLEStorageLitePPSFile.new(stream, @summary)
+ streams << summary
+ stream = "\5DocumentSummaryInformation".unpack('C*').pack('v*')
+ summary = OLEStorageLitePPSFile.new(stream, @doc_summary)
+ streams << summary
end
+ # Create the OLE root document and add the substreams.
+ localtime = @localtime.to_a[0..5]
+ localtime[4] -= 1 # month
+ localtime[5] -= 1900
+ ole_root = OLEStorageLitePPSRoot.new(
+ localtime,
+ localtime,
+ streams
+ )
+ ole_root.save(@file)
+
+ # Close the filehandle if it was created internally.
+ return @fh_out.close if @internal_fh != 0
end
- private :store_ole_filie
- ###############################################################################
#
- # calc_sheet_offsets()
- #
# Calculate Worksheet BOF offsets records for use in the BOUNDSHEET records.
#
def calc_sheet_offsets #:nodoc:
offset = @datasize
@@ -1316,16 +1285,12 @@
offset += sheet.datasize
end
@biffsize = offset
end
- private :calc_sheet_offsets
- ###############################################################################
#
- # calc_mso_sizes()
- #
# Calculate the MSODRAWINGGROUP sizes and the indexes of the Worksheet
# MSODRAWING records.
#
# In the following SPID is shape id, according to Escher nomenclature.
#
@@ -1346,25 +1311,24 @@
# Iterate through the worksheets, calculate the MSODRAWINGGROUP parameters
# and space required to store the record and the MSODRAWING parameters
# required by each worksheet.
#
@worksheets.each do |sheet|
- next unless sheet.sheet_type == 0x0000
+ next unless sheet.type == 0x0000
num_images = sheet.num_images
- image_mso_size = sheet.image_mso_size
num_comments = sheet.prepare_comments
num_charts = sheet.prepare_charts
num_filters = sheet.filter_count
next if num_images + num_comments + num_charts + num_filters == 0
# Include 1 parent MSODRAWING shape, per sheet, in the shape count.
num_shapes = 1 + num_images + num_comments +
num_charts + num_filters
shapes_saved += num_shapes
- mso_size += image_mso_size
+ mso_size += sheet.image_mso_size
# Add a drawing object for each sheet with comments.
drawings_saved += 1
# For each sheet start the spids at the next 1024 interval.
@@ -1397,16 +1361,12 @@
@mso_clusters = [
max_spid, num_clusters, shapes_saved,
drawings_saved, clusters
]
end
- private :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.
#
@@ -1421,256 +1381,63 @@
previous_images = []
image_id = 1
images_size = 0
@worksheets.each do |sheet|
- next unless sheet.sheet_type == 0x0000
+ next unless sheet.type == 0x0000
next if sheet.prepare_images == 0
num_images = 0
image_mso_size = 0
sheet.images_array.each do |image|
- filename = image[2]
num_images += 1
-
- #
- # 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 images_seen[filename].nil?
+ unless images_seen[image.filename]
# TODO should also match seen images based on checksum.
+ image.id = image_id
+ image.ref_count = 1
+ image.import
- # Open the image file and import the data.
- fh = open(filename, "rb")
- raise "Couldn't import #{filename}: #{$!}" unless fh
-
- # Slurp the file into a string and do some size calcs.
- # my $data = do {local $/; <$fh>};
- data = fh.read
- size = data.bytesize
- checksum1 = image_checksum(data, image_id)
- checksum2 = checksum1
- ref_count = 1
-
- # Process the image and extract dimensions.
- # Test for PNGs...
- if data.unpack('x A3')[0] == 'PNG'
- type, width, height = process_png(data)
- # Test for JFIF and Exif JPEGs...
- elsif ( data.unpack('n')[0] == 0xFFD8 &&
- (data.unpack('x6 A4')[0] == 'JFIF' ||
- data.unpack('x6 A4')[0] == 'Exif')
- )
- type, width, height = process_jpg(data, filename)
- # Test for BMPs...
- elsif data.unpack('A2')[0] == 'BM'
- type, width, height = process_bmp(data, filename)
- # The 14 byte header of the BMP is stripped off.
- data[0, 13] = ''
-
- # A checksum of the new image data is also required.
- checksum2 = image_checksum(data, image_id, image_id)
-
- # Adjust size -14 (header) + 16 (extra checksum).
- size += 2
- else
- raise "Unsupported image format for file: #{filename}\n"
- end
-
- # Push the new data back into the Worksheet array;
- image.push(image_id, type, width, height)
-
# Also store new data for use in duplicate images.
- previous_images.push([image_id, type, width, height])
+ previous_images.push(image)
# Store information required by the Workbook.
- image_data.push([ref_count, type, data, size,
- checksum1, checksum2])
+ image_data.push(image)
# Keep track of overall data size.
- images_size += size +61 # Size for bstore container.
- image_mso_size += size +69 # Size for dgg container.
+ images_size += image.size + 61 # Size for bstore container.
+ image_mso_size += image.size + 69 # Size for dgg container.
- images_seen[filename] = image_id
+ images_seen[image.filename] = image_id
image_id += 1
- fh.close
else
# We've processed this file already.
- index = images_seen[filename] -1
+ index = images_seen[image.filename] -1
# Increase image reference count.
- image_data[index][0] += 1
+ image_data[index].ref_count += 1
# Add previously calculated data back onto the Worksheet array.
- # $image_id, $type, $width, $height
- a_ref = sheet.images_array[index]
- image.concat(previous_images[index])
+ # image_id, type, width, height
+ image.id = previous_images[index].id
+ image.type = previous_images[index].type
+ image.width = previous_images[index].width
+ image.height = previous_images[index].height
end
end
# Store information required by the Worksheet.
sheet.num_images = num_images
sheet.image_mso_size = image_mso_size
-
end
-
# Store information required by the Workbook.
@images_size = images_size
@images_data = image_data # Store the data for MSODRAWINGGROUP.
end
- private :process_images
- ###############################################################################
#
- # 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.
- #
- def image_checksum(data, index1, index2 = 0) #:nodoc:
- if @checksum_method == 1
- # Digest::MD4
- # return Digest::MD4::md4_hex($data);
- elsif @checksum_method == 2
- # Digest::Perl::MD4
- # return Digest::Perl::MD4::md4_hex($data);
- elsif @checksum_method == 3
- # Digest::MD5
- return Digest::MD5.hexdigest(data)
- else
- # Default
- return sprintf('%016X%016X', index2, index1)
- end
- end
- private :image_checksum
-
- ###############################################################################
- #
- # process_png()
- #
- # Extract width and height information from a PNG file.
- #
- def process_png(data) #:nodoc:
- type = 6 # Excel Blip type (MSOBLIPTYPE).
- width = data[16, 4].unpack("N")[0]
- height = data[20, 4].unpack("N")[0]
-
- [type, width, height]
- end
- private :process_png
-
- ###############################################################################
- #
- # process_bmp()
- #
- # Extract width and height information from a BMP file.
- #
- # Most of these checks came from the old Worksheet::_process_bitmap() method.
- #
- def process_bmp(data, filename) #:nodoc:
- type = 7 # Excel Blip type (MSOBLIPTYPE).
-
- # Check that the file is big enough to be a bitmap.
- if data.bytesize <= 0x36
- raise "#{filename} doesn't contain enough data."
- end
-
- # Read the bitmap width and height. Verify the sizes.
- width, height = data.unpack("x18 V2")
-
- if width > 0xFFFF
- raise "#{filename}: largest image width #{width} supported is 65k."
- end
-
- if height > 0xFFFF
- raise "#{filename}: largest image height supported is 65k."
- end
-
- # Read the bitmap planes and bpp data. Verify them.
- planes, bitcount = data.unpack("x26 v2")
-
- if bitcount != 24
- raise "#{filename} isn't a 24bit true color bitmap."
- end
-
- if planes != 1
- raise "#{filename}: only 1 plane supported in bitmap image."
- end
-
- # Read the bitmap compression. Verify compression.
- compression = data.unpack("x30 V")
-
- if compression != 0
- raise "#{filename}: compression not supported in bitmap image."
- end
-
- [type, width, height]
- end
- private :process_bmp
-
- ###############################################################################
- #
- # process_jpg()
- #
- # Extract width and height information from a JPEG file.
- #
- def process_jpg(data, filename) # :nodoc:
- type = 5 # Excel Blip type (MSOBLIPTYPE).
-
- offset = 2
- data_length = data.bytesize
-
- # Search through the image data to find the 0xFFC0 marker. The height and
- # width are contained in the data for that sub element.
- while offset < data_length
- marker = data[offset, 2].unpack("n")
- marker = marker[0]
- length = data[offset+2, 2].unpack("n")
- length = length[0]
-
- if marker == 0xFFC0 || marker == 0xFFC2
- height = data[offset+5, 2].unpack("n")
- height = height[0]
- width = data[offset+7, 2].unpack("n")
- width = width[0]
- break
- end
-
- offset += length + 2
- break if marker == 0xFFDA
- end
-
- if height.nil?
- raise "#{filename}: no size data found in jpeg image.\n"
- end
-
- [type, width, height]
- end
-
- ###############################################################################
- #
- # store_all_fonts()
- #
# Store the Excel FONT records.
#
def store_all_fonts #:nodoc:
format = formats[15] # The default cell format.
font = format.get_font
@@ -1752,16 +1519,12 @@
font = fmt.get_font
append(font)
end
end
end
- private :store_all_fonts
- ###############################################################################
#
- # store_all_num_formats()
- #
# Store user defined numerical formats i.e. FORMAT records
#
def store_all_num_formats #:nodoc:
num_formats = {}
index = 164 # User defined FORMAT records start from 0xA4
@@ -1791,30 +1554,22 @@
store_num_format(num_format, index, encoding)
index += 1
end
end
end
- private :store_all_num_formats
- ###############################################################################
#
- # store_all_xfs()
- #
# Write all XF records.
#
def store_all_xfs #:nodoc:
formats.each do |format|
xf = format.get_xf
append(xf)
end
end
- private :store_all_xfs
- ###############################################################################
#
- # store_all_styles()
- #
# Write all STYLE records.
#
def store_all_styles #:nodoc:
# Excel adds the built-in styles in alphabetical order.
built_ins = [
@@ -1836,16 +1591,12 @@
xf_index = aref[1]
store_style(type, xf_index)
end
end
- private :store_all_styles
- ###############################################################################
#
- # store_names()
- #
# Write the NAME record to define the print area and the repeat rows and cols.
#
def store_names # :nodoc:
# Create the user defined names.
defined_names.each do |defined_name|
@@ -1888,11 +1639,10 @@
1 # Hidden
)
end
end
end
- private :create_autofilter_name_records
def create_print_area_name_records(sorted_worksheets) #:nodoc:
sorted_worksheets.each do |worksheet|
index = worksheet.index
@@ -1908,11 +1658,10 @@
worksheet.print_colmax
)
end
end
end
- private :create_print_area_name_records
def create_print_title_name_records(sorted_worksheets) #:nodoc:
sorted_worksheets.each do |worksheet|
index = worksheet.index
rowmin = worksheet.title_rowmin
@@ -1962,23 +1711,19 @@
else
# Nothing left to do
end
end
end
- private :create_print_title_name_records
###############################################################################
###############################################################################
#
# BIFF RECORDS
#
- ###############################################################################
#
- # store_window1()
- #
# Write Excel BIFF WINDOW1 record.
#
def store_window1 #:nodoc:
record = 0x003D # Record identifier
length = 0x0012 # Number of bytes to follow
@@ -2003,24 +1748,27 @@
ctabsel, tab_ratio
].pack("vvvvvvvvv")
append(header, data)
end
- private :store_window1
- ###############################################################################
#
- # store_boundsheet()
- # my $sheetname = $_[0]; # Worksheet name
- # my $offset = $_[1]; # Location of worksheet BOF
- # my $type = $_[2]; # Worksheet type
- # my $hidden = $_[3]; # Worksheet hidden flag
- # my $encoding = $_[4]; # Sheet name encoding
- #
# Writes Excel BIFF BOUNDSHEET record.
#
- def store_boundsheet(sheetname, offset, type, hidden, encoding) #:nodoc:
+ # sheetname # Worksheet name
+ # offset # Location of worksheet BOF
+ # type # Worksheet type
+ # hidden # Worksheet hidden flag
+ # encoding # Sheet name encoding
+ #
+ def store_boundsheet(sheet) #:nodoc:
+ sheetname = sheet.name
+ offset = sheet.offset
+ type = sheet.type
+ hidden = sheet.hidden? ? 1 : 0
+ encoding = sheet.is_name_utf16be? ? 1 : 0
+
record = 0x0085 # Record identifier
length = 0x08 + sheetname.bytesize # Number of bytes to follow
cch = sheetname.bytesize # Length of sheet name
@@ -2035,20 +1783,17 @@
header = [record, length].pack("vv")
data = [offset, grbit, cch, encoding].pack("VvCC")
append(header, data, sheetname)
end
- private :store_boundsheet
- ###############################################################################
#
- # store_style()
- # type = $_[0] # Built-in style
- # xf_index = $_[1] # Index to style XF
- #
# Write Excel BIFF STYLE records.
#
+ # type # Built-in style
+ # xf_index # Index to style XF
+ #
def store_style(type, xf_index) #:nodoc:
record = 0x0293 # Record identifier
length = 0x0004 # Bytes to follow
level = 0xff # Outline style level
@@ -2058,21 +1803,18 @@
header = [record, length].pack("vv")
data = [xf_index, type, level].pack("vCC")
append(header, data)
end
- private :store_style
- ###############################################################################
#
- # store_num_format()
- # my $format = $_[0]; # Custom format string
- # my $ifmt = $_[1]; # Format index code
- # my $encoding = $_[2]; # Char encoding for format string
- #
# Writes Excel FORMAT record for non "built-in" numerical formats.
#
+ # format # Custom format string
+ # ifmt # Format index code
+ # encoding # Char encoding for format string
+ #
def store_num_format(format, ifmt, encoding) #:nodoc:
format = format.to_s unless format.respond_to?(:to_str)
record = 0x041E # Record identifier
# length # Number of bytes to follow
# Char length of format string
@@ -2106,16 +1848,12 @@
header = [record, length].pack("vv")
data = [ifmt, cch, encoding].pack("vvC")
append(header, data, format)
end
- private :store_num_format
- ###############################################################################
#
- # store_1904()
- #
# Write Excel 1904 record to indicate the date system in use.
#
def store_1904 #:nodoc:
record = 0x0022 # Record identifier
length = 0x0002 # Bytes to follow
@@ -2125,16 +1863,12 @@
header = [record, length].pack("vv")
data = [f1904].pack("v")
append(header, data)
end
- private :store_1904
- ###############################################################################
#
- # store_supbook()
- #
# Write BIFF record SUPBOOK to indicate that the workbook contains external
# references, in our case, formula, print area and print title refs.
#
def store_supbook #:nodoc:
record = 0x01AE # Record identifier
@@ -2146,16 +1880,12 @@
header = [record, length].pack("vv")
data = [tabs, virt_path].pack("vv")
append(header, data)
end
- private :store_supbook
- ###############################################################################
#
- # store_externsheet()
- #
# Writes the Excel BIFF EXTERNSHEET record. These references are used by
# formulas. TODO NAME record is required to define the print area and the
# repeat rows and columns.
#
def store_externsheet # :nodoc:
@@ -2231,26 +1961,23 @@
header = [record, data.bytesize].pack("vv")
append(header, data)
end
- ###############################################################################
#
- # store_name_short()
- # index = shift # Sheet index
- # type = shift
- # ext_ref = shift # TODO
- # rowmin = $_[0] # Start row
- # rowmax = $_[1] # End row
- # colmin = $_[2] # Start column
- # colmax = $_[3] # end column
- # hidden = $_[4] # Name is hidden
- #
- #
# Store the NAME record in the short format that is used for storing the print
# area, repeat rows only and repeat columns only.
#
+ # index # Sheet index
+ # type
+ # ext_ref # TODO
+ # rowmin # Start row
+ # rowmax # End row
+ # colmin # Start column
+ # colmax # end column
+ # hidden # Name is hidden
+ #
def store_name_short(index, type, ext_ref, rowmin, rowmax, colmin, colmax, hidden = nil) #:nodoc:
record = 0x0018 # Record identifier
length = 0x001b # Number of bytes to follow
grbit = 0x0020 # Option flags
@@ -2290,29 +2017,25 @@
data += [colmin].pack("v")
data += [colmax].pack("v")
append(header, data)
end
- private :store_name_short
- ###############################################################################
#
- # store_name_long()
- # my $index = shift; # Sheet index
- # my $type = shift;
- # my $ext_ref = shift; # TODO
- # my $rowmin = $_[0]; # Start row
- # my $rowmax = $_[1]; # End row
- # my $colmin = $_[2]; # Start column
- # my $colmax = $_[3]; # end column
- #
- #
# Store the NAME record in the long format that is used for storing the repeat
# rows and columns when both are specified. This share a lot of code with
# store_name_short() but we use a separate method to keep the code clean.
# Code abstraction for reuse can be carried too far, and I should know. ;-)
#
+ # index # Sheet index
+ # type
+ # ext_ref # TODO
+ # rowmin # Start row
+ # rowmax # End row
+ # colmin # Start column
+ # colmax # end column
+ #
def store_name_long(index, type, ext_ref, rowmin, rowmax, colmin, colmax) #:nodoc:
record = 0x0018 # Record identifier
length = 0x002a # Number of bytes to follow
grbit = 0x0020 # Option flags
@@ -2366,16 +2089,12 @@
# End of data
data += [0x10].pack("C")
append(header, data)
end
- private :store_name_long
- ###############################################################################
#
- # store_palette()
- #
# Stores the PALETTE biff record.
#
def store_palette #:nodoc:
record = 0x0092 # Record identifier
length = 2 + 4 * @palette.size # Number of bytes to follow
@@ -2389,70 +2108,53 @@
header = [record, length, ccv].pack("vvv")
append(header, data)
end
- private :store_palette
- ###############################################################################
#
- # store_codepage()
- #
# Stores the CODEPAGE biff record.
#
def store_codepage #:nodoc:
record = 0x0042 # Record identifier
length = 0x0002 # Number of bytes to follow
cv = @codepage # The code page
store_common(record, length, cv)
end
- private :store_codepage
- ###############################################################################
#
- # store_country()
- #
# Stores the COUNTRY biff record.
#
def store_country #:nodoc:
record = 0x008C # Record identifier
length = 0x0004 # Number of bytes to follow
country_default = @country
country_win_ini = @country
store_common(record, length, country_default, country_win_ini)
end
- private :store_country
- ###############################################################################
#
- # store_hideobj()
- #
# Stores the HIDEOBJ biff record.
#
def store_hideobj #:nodoc:
record = 0x008D # Record identifier
length = 0x0002 # Number of bytes to follow
- hide = @hideobj # Option to hide objects
+ hide = @hideobj ? 1 : 0 # Option to hide objects
store_common(record, length, hide)
end
- private :store_hideobj
def store_common(record, length, *data) #:nodoc:
header = [record, length].pack("vv")
add_data = [*data].pack("v*")
append(header, add_data)
end
- private :store_common
- ###############################################################################
#
- # calculate_extern_sizes()
- #
# We need to calculate the space required by the SUPBOOK, EXTERNSHEET and NAME
# records so that it can be added to the BOUNDSHEET offsets.
#
def calculate_extern_sizes # :nodoc:
ext_refs = @parser.get_ext_sheets
@@ -2523,16 +2225,12 @@
end
def add_ext_refs(ext_refs, key) #:nodoc:
ext_refs[key] = ext_refs.keys.size
end
- private :add_ext_refs
- ###############################################################################
#
- # calculate_shared_string_sizes()
- #
# Handling of the SST continue blocks is complicated by the need to include an
# additional continuation byte depending on whether the string is split between
# blocks or whether it starts at the beginning of the block. (There are also
# additional complications that will arise later when/if Rich Strings are
# supported). As such we cannot use the simple CONTINUE mechanism provided by
@@ -2650,11 +2348,10 @@
length += 4 + block_sizes.shift # CONTINUEs
end
length
end
- private :calculate_shared_string_sizes
def split_string_setup(encoding, split_string, continue_limit, written, continue) #:nodoc:
# We need to avoid the case where a string is continued in the first
# n bytes that contain the string header information.
header_length = 3 # Min string + header size -1
@@ -2682,16 +2379,12 @@
split_string = 1
end
end
[header_length, space_remaining, align, split_string]
end
- private :split_string_setup
- ###############################################################################
#
- # store_shared_strings()
- #
# Write all of the workbooks strings into an indexed array.
#
# See the comments in calculate_shared_string_sizes() for more information.
#
# We also use this routine to record the offsets required by the EXTSST table.
@@ -2838,16 +2531,12 @@
written = 0
end
end
end
end
- private :store_shared_strings
- ###############################################################################
#
- # calculate_extsst_size
- #
# The number of buckets used in the EXTSST is between 0 and 128. The number of
# strings per bucket (bucket size) has a minimum value of 8 and a theoretical
# maximum of 2^16. For "number of strings" < 1024 there is a constant bucket
# size of 8. The following algorithm generates the same size/bucket ratio
# as Excel.
@@ -2867,14 +2556,11 @@
@extsst_bucket_size = bucket_size
6 + 8 * buckets
end
- ###############################################################################
#
- # store_extsst
- #
# Write EXTSST table using the offsets calculated in store_shared_strings().
#
def store_extsst #:nodoc:
offsets = @extsst_offsets
bucket_size = @extsst_bucket_size
@@ -2889,20 +2575,16 @@
data += [offset[0], offset[1], 0].pack('Vvv')
end
append(header, data)
end
- private :store_extsst
#
# Methods related to comments and MSO objects.
#
- ###############################################################################
#
- # add_mso_drawing_group()
- #
# Write the MSODRAWINGGROUP record that keeps track of the Escher drawing
# objects in the file such as images, comments and filters.
#
def add_mso_drawing_group #:nodoc:
return unless @mso_size != 0
@@ -2912,11 +2594,11 @@
data = store_mso_dgg_container
data += store_mso_dgg(*@mso_clusters)
data += store_mso_bstore_container
@images_data.each do |image|
- data += store_mso_images(*image)
+ data += store_mso_images(image)
end
data += store_mso_opt
data += store_mso_split_menu_colors
length = data.bytesize
@@ -2924,18 +2606,14 @@
add_mso_drawing_group_continue(header + data)
header + data # For testing only.
end
- private :add_mso_drawing_group
- ###############################################################################
#
- # add_mso_drawing_group_continue()
+ # See first BIFFwriter#add_continue() method.
#
- # See first the 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:
#
@@ -2983,23 +2661,18 @@
append(header, data)
# Turn the base class add_continue() method back on.
@ignore_continue = 0
end
- private :add_mso_drawing_group_continue
def devide_string(string, nth) #:nodoc:
first_string = string[0, nth]
latter_string = string[nth, string.size - nth]
[first_string, latter_string]
end
- private :devide_string
- ###############################################################################
#
- # store_mso_dgg_container()
- #
# Write the Escher DggContainer record that is part of MSODRAWINGGROUP.
#
def store_mso_dgg_container #:nodoc:
type = 0xF000
version = 15
@@ -3007,21 +2680,12 @@
data = ''
length = @mso_size -12 # -4 (biff header) -8 (for this).
add_mso_generic(type, version, instance, data, length)
end
- private :store_mso_dgg_container
- ###############################################################################
#
- # store_mso_dgg()
- # my $max_spid = $_[0];
- # my $num_clusters = $_[1];
- # my $shapes_saved = $_[2];
- # my $drawings_saved = $_[3];
- # my $clusters = $_[4];
- #
# Write the Escher Dgg record that is part of MSODRAWINGGROUP.
#
def store_mso_dgg(max_spid, num_clusters, shapes_saved, drawings_saved, clusters) #:nodoc:
type = 0xF006
version = 0
@@ -3039,16 +2703,12 @@
data += [drawing_id, shape_ids_used].pack("VV")
end
add_mso_generic(type, version, instance, data, length)
end
- private :store_mso_dgg
- ###############################################################################
#
- # store_mso_bstore_container()
- #
# Write the Escher BstoreContainer record that is part of MSODRAWINGGROUP.
#
def store_mso_bstore_container #:nodoc:
return '' if @images_size == 0
@@ -3058,52 +2718,27 @@
data = ''
length = @images_size +8 *instance
add_mso_generic(type, version, instance, data, length)
end
- private :store_mso_bstore_container
- ###############################################################################
#
- # store_mso_images()
- # ref_count = $_[0]
- # image_type = $_[1]
- # image = $_[2]
- # size = $_[3]
- # checksum1 = $_[4]
- # checksum2 = $_[5]
- #
# Write the Escher BstoreContainer record that is part of MSODRAWINGGROUP.
#
- def store_mso_images(ref_count, image_type, image, size, checksum1, checksum2) #:nodoc:
+ def store_mso_images(image)
blip_store_entry = store_mso_blip_store_entry(
- ref_count,
- image_type,
- size,
- checksum1
+ image.ref_count, image.type, image.size, image.checksum1
)
blip = store_mso_blip(
- image_type,
- image,
- size,
- checksum1,
- checksum2
+ image.type, image.data, image.size, image.checksum1, image.checksum2
)
blip_store_entry + blip
end
- private :store_mso_images
- ###############################################################################
#
- # store_mso_blip_store_entry()
- # ref_count = $_[0]
- # image_type = $_[1]
- # size = $_[2]
- # checksum1 = $_[3]
- #
# Write the Escher BlipStoreEntry record that is part of MSODRAWINGGROUP.
#
def store_mso_blip_store_entry(ref_count, image_type, size, checksum1) #:nodoc:
type = 0xF007
version = 2
@@ -3121,21 +2756,12 @@
[0x00].pack('C') + # Unused
[0x00].pack('C') # Unused
add_mso_generic(type, version, instance, data, length)
end
- private :store_mso_blip_store_entry
- ###############################################################################
#
- # store_mso_blip()
- # image_type = $_[0]
- # image_data = $_[1]
- # size = $_[2]
- # checksum1 = $_[3]
- # checksum2 = $_[4]
- #
# Write the Escher Blip record that is part of MSODRAWINGGROUP.
#
def store_mso_blip(image_type, image_data, size, checksum1, checksum2) #:nodoc:
instance = 0x046A if image_type == 5 # JPG
instance = 0x06E0 if image_type == 6 # PNG
@@ -3153,16 +2779,12 @@
[0xFF].pack('C') + # Tag
image_data # Image
add_mso_generic(type, version, instance, data, length)
end
- private :store_mso_blip
- ###############################################################################
#
- # store_mso_opt()
- #
# Write the Escher Opt record that is part of MSODRAWINGGROUP.
#
def store_mso_opt #:nodoc:
type = 0xF00B
version = 3
@@ -3172,16 +2794,12 @@
data = ['BF0008000800810109000008C0014000'+'0008'].pack("H*")
add_mso_generic(type, version, instance, data, length)
end
- private :store_mso_opt
- ###############################################################################
#
- # store_mso_split_menu_colors()
- #
# Write the Escher SplitMenuColors record that is part of MSODRAWINGGROUP.
#
def store_mso_split_menu_colors #:nodoc:
type = 0xF11E
version = 0
@@ -3191,25 +2809,17 @@
data = ['0D0000080C00000817000008F7000010'].pack("H*")
add_mso_generic(type, version, instance, data, length)
end
- private :store_mso_split_menu_colors
def cleanup #:nodoc:
super
sheets.each { |sheet| sheet.cleanup }
end
- private :cleanup
- private
-
def add_doc_properties
- @add_doc_properties
- end
-
- def add_doc_properties=(val)
- @add_doc_properties = val
+ @add_doc_properties ||= false
end
def formats
@formats
end