lib/writeexcel/workbook.rb in writeexcel-0.6.4 vs lib/writeexcel/workbook.rb in writeexcel-0.6.5

- old
+ new

@@ -24,22 +24,27 @@ class Workbook < BIFFWriter require 'writeexcel/properties' require 'writeexcel/helper' - NonAscii = /[^!"#\$%&'\(\)\*\+,\-\.\/\:\;<=>\?@0-9A-Za-z_\[\\\]\{\}^` ~\0\n]/ - - BOF = 11 # :nodoc: + BOF = 12 # :nodoc: EOF = 4 # :nodoc: SheetName = "Sheet" # :nodoc: + private + + attr_accessor :add_doc_properties #:nodoc: + attr_reader :formats, :defined_names #:nodoc: + + public + # - # file is a filename (as string) or io object where to out spreadsheet data. - # you can set default format of workbook using default_formats. + # _file_ is a filename (as string) or io object where to out spreadsheet data. + # you can set default format of workbook using _default_formats_. # # A new Excel workbook is created using the new() constructor which accepts - # either a filename or a filehandle as a parameter. The following example + # either a filename or an IO object as a parameter. The following example # creates a new Excel file based on a filename: # # workbook = WriteExcel.new('filename.xls') # worksheet = workbook.add_worksheet # worksheet.write(0, 0, 'Hi Excel!') @@ -72,12 +77,10 @@ @file = file @default_formats = default_formats @parser = Writeexcel::Formula.new(@byte_order) @tempdir = nil @date_1904 = false - @sheet = - @selected = 0 @xf_index = 0 @fileclosed = false @biffsize = 0 @sheet_name = "Sheet" @@ -86,11 +89,10 @@ @chart_count = 0 @url_format = '' @codepage = 0x04E4 @country = 1 @worksheets = [] - @sheetnames = [] @formats = [] @palette = [] @biff_only = 0 @internal_fh = 0 @@ -115,11 +117,10 @@ @mso_size = 0 @hideobj = 0 @compatibility = 0 - @add_doc_properties = 0 @summary = '' @doc_summary = '' @localtime = Time.now @defined_names = [] @@ -163,11 +164,11 @@ get_checksum_method end ############################################################################### # - # _get_checksum_method. + # get_checksum_method. # # Check for modules available to calculate image checksum. Excel uses MD4 but # MD5 will also work. # # ------- cxn03651 add ------- @@ -180,29 +181,17 @@ # # Calls finalization methods and explicitly close the OLEwriter files # handle. # - # In general your Excel file will be closed automatically when your program - # ends or when the Workbook object goes out of scope, however the close method - # can be used to explicitly close an Excel file. - # - # workbook.close - # # 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 # it to an email. # # 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(). # - # The return value of close() is the same as that returned by perl when it - # closes the file created by new(). This allows you to handle error conditions - # in the usual way: - # - # $workbook.close() or die "Error closing file: $!"; - # def close return if @fileclosed # Prevent close() from being called twice. @fileclosed = true store_workbook @@ -243,15 +232,11 @@ # def sheets(*args) if args.empty? @worksheets else - ary = [] - args.each do |i| - ary << @worksheets[i] - end - ary + args.collect{|i| @worksheets[i] } end end # # Add a new worksheet to the Excel workbook. @@ -264,11 +249,11 @@ # worksheet1 = workbook.add_worksheet # Sheet1 # worksheet2 = workbook.add_worksheet('Foglio2') # Foglio2 # worksheet3 = workbook.add_worksheet('Data') # Data # worksheet4 = workbook.add_worksheet # Sheet4 # - # If sheetname is not specified the default Excel convention will be followed, + # If _sheetname_ is not specified the default Excel convention will be followed, # i.e. Sheet1, Sheet2, etc. The utf_16_be parameter is optional, see below. # # The worksheet name must be a valid Excel worksheet name, i.e. it cannot # contain any of the following characters, [ ] : * ? / \ # and it must be less than 32 characters. In addition, you cannot use the same, @@ -306,95 +291,97 @@ nil, # Palette. Not used yet. See add_chart(). @sinfo, ] worksheet = Writeexcel::Worksheet.new(*init_data) @worksheets[index] = worksheet # Store ref for iterator - @sheetnames[index] = name # Store EXTERNSHEET names @parser.set_ext_sheets(name, index) # Store names in Formula.rb worksheet end - ############################################################################### # - # add_chart(params) - # # Create a chart for embedding or as as new sheet. # # This method is use to create a new chart either as a standalone worksheet # (the default) or as an embeddable object that can be inserted into a # worksheet via the insert_chart() Worksheet method. # # chart = workbook.add_chart(:type => 'Chart::Column') # # The properties that can be set are: # - # :type (required) - # :name (optional) - # :embedded (optional) + # :type (required) + # :name (optional) + # :encoding (optional) + # :embedded (optional) # - # * type + # * type # - # This is a required parameter. It defines the type of chart that will be created. + # This is a required parameter. It defines the type of chart that will be created. # - # chart = workbook.add_chart(:type => 'Chart::Line') + # chart = workbook.add_chart(:type => 'Chart::Line') # - # The available types are: + # The available types are: # - # 'Chart::Column' - # 'Chart::Bar' - # 'Chart::Line' - # 'Chart::Area' - # 'Chart::Pie' - # 'Chart::Scatter' - # 'Chart::Stock' + # 'Chart::Column' + # 'Chart::Bar' + # 'Chart::Line' + # 'Chart::Area' + # 'Chart::Pie' + # 'Chart::Scatter' + # 'Chart::Stock' # - # * :name + # * :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 - # charts. + # 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 + # charts. # - # chart = workbook.add_chart( - # :type => 'Chart::Line', - # :name => 'Results Chart' - # ) + # chart = workbook.add_chart( + # :type => 'Chart::Line', + # :name => 'Results Chart' + # ) # - # * :embedded + # * :encoding # - # Specifies 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. + # if :name is UTF-16BE format, pass 1 as :encoding. # - # chart = workbook.add_chart(:type => 'Chart::Line', :embedded => 1) + # * :embedded # - # # Configure the chart. - # ... + # Specifies 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. # - # # Insert the chart into the a worksheet. - # worksheet.insert_chart('E2', chart) + # chart = workbook.add_chart(:type => 'Chart::Line', :embedded => 1) # + # # Configure the chart. + # ... + # + # # Insert the chart into the a worksheet. + # worksheet.insert_chart('E2', chart) + # # See WriteExcel::Chart for details on how to configure the - # chart object once it is created. See also the chart_*.pl programs in the + # chart object once it is created. See also the chart_*.rb programs in the # examples directory of the distro. # - def add_chart(params) + def add_chart(properties) name = '' encoding = 0 index = @worksheets.size # Type must be specified so we can create the required chart instance. - type = params[:type] - print "Must define chart type in add_chart()" if type.nil? + type = properties[:type] + raise "Must define chart type in add_chart()" if type.nil? # Ensure that the chart defaults to non embedded. - embedded = params[:embedded] + embedded = properties[:embedded] # Check the worksheet name for non-embedded charts. unless embedded - name, encoding = check_sheetname(params[:name], params[:name_encoding], 1) + name, encoding = + check_sheetname(properties[:name], properties[:encoding], true) end init_data = [ name, index, @@ -410,25 +397,21 @@ chart = Writeexcel::Chart.factory(type, *init_data) # If the chart isn't embedded let the workbook control it. if !embedded @worksheets[index] = chart # Store ref for iterator - @sheetnames[index] = name # Store EXTERNSHEET names else # Set index to 0 so that the activate() and set_first_sheet() methods # point back to the first worksheet if used for embedded charts. chart.index = 0 chart.set_embedded_config_data end chart end - ############################################################################### # - # add_chart_ext($filename, $name) - # # Add an externally created chart. # # This method is use to include externally generated charts in a WriteExcel # file. # @@ -452,54 +435,74 @@ @sinfo ] chart = Writeexcel::Chart.factory(self, type, init_data) @worksheets[index] = chart # Store ref for iterator - @sheetnames[index] = name # Store EXTERNSHEET names chart end ############################################################################### # - # _check_sheetname($name, $encoding) + # 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 = 0) #:nodoc: + def check_sheetname(name, encoding = 0, chart = nil) #:nodoc: encoding ||= 0 - limit = encoding != 0 ? 62 : 31 - invalid_char = %r![\[\]:*?/\\]! # Increment the Sheet/Chart number used for default sheet names below. - if chart != 0 + 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 != 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 encoding == 1 && (name.bytesize % 2 != 0) + 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. @@ -510,36 +513,25 @@ raise 'Invalid character []:*?/\\ in worksheet name: ' + name end str = $~.post_match end end + end + private :check_sheetname_valid_chars - # Handle utf8 strings - ruby_18 do - if name =~ NonAscii - name = utf8_to_16be(name) - encoding = 1 - end - end - ruby_19 do - if name.encoding == Encoding::UTF_8 - name = utf8_to_16be(name) - encoding = 1 - 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 encoding into account. - # + # 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 = 0; + 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) @@ -567,13 +559,12 @@ end if error raise "Worksheet name '#{name}', with case ignored, is already in use" end end - [name, encoding] end - private :check_sheetname + 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. @@ -582,15 +573,15 @@ # format2 = workbook.add_format # Set properties later # # See the "CELL FORMATTING" section for more details about Format properties and how to set them. # def add_format(*args) - formats = {} - args.each { |arg| formats = formats.merge(arg) } - format = Writeexcel::Format.new(@xf_index, @default_formats.merge(formats)) + fmts = {} + args.each { |arg| fmts = fmts.merge(arg) } + format = Writeexcel::Format.new(@xf_index, @default_formats.merge(fmts)) @xf_index += 1 - @formats.push format # Store format reference + formats.push format # Store format reference format end # # Set the compatibility mode. @@ -817,11 +808,10 @@ [0x99, 0x33, 0x00, 0x00], # 60 [0x99, 0x33, 0x66, 0x00], # 61 [0x33, 0x33, 0x99, 0x00], # 62 [0x33, 0x33, 0x33, 0x00] # 63 ] - 0 end private :set_palette_xl97 # # Change the default temp directory @@ -946,20 +936,20 @@ tokens.collect! { |t| t.gsub(/_range3d/, '_range3dR') } # Parse the tokens into a formula string. formula = parser.parse_tokens(tokens) - @defined_names.push( + defined_names.push( { :name => name, :encoding => encoding, :sheet_index => sheet_index, :formula => formula } ) - index = @defined_names.size + index = defined_names.size parser.set_ext_name(name, index) end # @@ -1018,111 +1008,115 @@ return -1 if !params.respond_to?(:to_hash) || params.empty? params.each do |k, v| params[k] = convert_to_ascii_if_ascii(v) if v.respond_to?(:to_str) end - # List of valid input parameters. - 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 - } # Check for valid input parameters. - params.each_key do |k| - unless properties.has_key?(k) - raise "Unknown parameter '#{k}' in set_properties()"; - end - end + check_valid_params_for_properties(params) # Set the creation time unless specified by the user. - unless params.has_key?(:created) - params[:created] = @localtime - end + params[:created] = @localtime unless params.has_key?(:created) # # Create the SummaryInformation property set. # # Get the codepage of the strings in the property set. - strings = ["title", "subject", "author", "keywords", "comments", "last_author"] - params[:codepage] = get_property_set_codepage(params, strings) + properties = [:title, :subject, :author, :keywords, :comments, :last_author] + params[:codepage] = get_property_set_codepage(params, properties) # Create an array of property set values. - property_sets = [] - strings.unshift("codepage") - strings.push("created") - strings.each do |string| - property = string.to_sym - property_sets.push(property_set(properties, property, params)) if params[property] - end + properties.unshift(:codepage) + properties.push(:created) # Pack the property sets. - @summary = create_summary_property_set(property_sets) + @summary = + create_summary_property_set(property_sets(properties, params)) # # Create the DocSummaryInformation property set. # # Get the codepage of the strings in the property set. - strings = ["category", "manager", "company"] - params[:codepage] = get_property_set_codepage(params, strings) + properties = [:category, :manager, :company] + params[:codepage] = get_property_set_codepage(params, properties) # Create an array of property set values. - property_sets = [] + properties.unshift(:codepage) - [:codepage, :category, :manager, :company].each do |property| - property_sets.push(property_set(properties, property, params)) if params[property] - end - # Pack the property sets. - @doc_summary = create_doc_summary_property_set(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 = 1 + add_doc_properties = true end - def property_set(properties, property, params) - [ properties[property][0], properties[property][1], params[property] ] + 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' ], + :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 + } + 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_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, strings) #:nodoc: + def get_property_set_codepage(params, properties) #:nodoc: # Allow for manually marked utf8 strings. - unless params[:utf8].nil? - return 0xFDE9 - else - strings.each do |string| - next unless params.has_key?(string.to_sym) - ruby_18 { return 0xFDE9 if params[string.to_sym] =~ NonAscii } - ruby_19 { return 0xFDE9 if params[string.to_sym].encoding == Encoding::UTF_8 } - end - return 0x04E4; # Default codepage, Latin 1. + return 0xFDE9 unless params[:utf8].nil? + properties.each do |property| + 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() + # store_workbook() # # Assemble worksheets into a workbook and send the BIFF data to an OLE # storage. # def store_workbook #:nodoc: @@ -1166,11 +1160,11 @@ sheet.encoding ) end # NOTE: If any records are added between here and EOF the - # _calc_sheet_offsets() should be updated to include the new length. + # calc_sheet_offsets() should be updated to include the new length. store_country if @ext_refs.keys.size != 0 store_supbook store_externsheet store_names @@ -1211,20 +1205,20 @@ @localtime = val end ############################################################################### # - # _store_ole_filie() + # 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: maxsize = 7_087_104 # maxsize = 1 - if @add_doc_properties == 0 && @biffsize <= maxsize + if !add_doc_properties && @biffsize <= maxsize # Write the OLE file using OLEwriter if data <= 7MB ole = OLEWriter.new(@fh_out) # Write the BIFF data without the OLE container for testing. ole.biff_only = @biff_only @@ -1264,11 +1258,11 @@ streams = [] streams << workbook # Create the properties streams, if any. - if @add_doc_properties != 0 + 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) @@ -1291,18 +1285,15 @@ end private :store_ole_filie ############################################################################### # - # _calc_sheet_offsets() + # calc_sheet_offsets() # # Calculate Worksheet BOF offsets records for use in the BOUNDSHEET records. # def calc_sheet_offsets #:nodoc: - _bof = 12 - _eof = 4 - offset = @datasize # Add the length of the COUNTRY record offset += 8 @@ -1314,20 +1305,20 @@ # Add the length of the SUPBOOK, EXTERNSHEET and NAME records offset += calculate_extern_sizes # Add the length of the MSODRAWINGGROUP records including an extra 4 bytes - # for any CONTINUE headers. See _add_mso_drawing_group_continue(). + # for any CONTINUE headers. See add_mso_drawing_group_continue(). mso_size = @mso_size mso_size += 4 * Integer((mso_size -1) / Float(@limit)) offset += mso_size @worksheets.each do |sheet| - offset += _bof + sheet.name.bytesize + offset += BOF + sheet.name.bytesize end - offset += _eof + offset += EOF @worksheets.each do |sheet| sheet.offset = offset sheet.close offset += sheet.datasize end @@ -1336,11 +1327,11 @@ end private :calc_sheet_offsets ############################################################################### # - # _calc_mso_sizes() + # 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. @@ -1417,11 +1408,11 @@ end private :calc_mso_sizes ############################################################################### # - # _process_images() + # 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. @@ -1552,14 +1543,14 @@ end private :process_images ############################################################################### # - # _image_checksum() + # 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 + # 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 @@ -1578,11 +1569,11 @@ end private :image_checksum ############################################################################### # - # _process_png() + # process_png() # # Extract width and height information from a PNG file. # def process_png(data) #:nodoc: type = 6 # Excel Blip type (MSOBLIPTYPE). @@ -1593,11 +1584,11 @@ end private :process_png ############################################################################### # - # _process_bmp() + # process_bmp() # # Extract width and height information from a BMP file. # # Most of these checks came from the old Worksheet::_process_bitmap() method. # @@ -1642,11 +1633,11 @@ end private :process_bmp ############################################################################### # - # _process_jpg() + # process_jpg() # # Extract width and height information from a JPEG file. # def process_jpg(data, filename) # :nodoc: type = 5 # Excel Blip type (MSOBLIPTYPE). @@ -1681,16 +1672,16 @@ [type, width, height] end ############################################################################### # - # _store_all_fonts() + # store_all_fonts() # # Store the Excel FONT records. # def store_all_fonts #:nodoc: - format = @formats[15] # The default cell format. + format = formats[15] # The default cell format. font = format.get_font # Fonts are 0-indexed. According to the SDK there is no index 4, (0..3).each do append(font) @@ -1749,11 +1740,11 @@ fonts[key] = 0 # Index of the default font # Fonts that are marked as '_font_only' are always stored. These are used # mainly for charts and may not have an associated XF record. - @formats.each do |fmt| + formats.each do |fmt| key = fmt.get_font_key if fmt.font_only == 0 and !fonts[key].nil? # FONT has already been used fmt.font_index = fonts[key] else @@ -1772,22 +1763,22 @@ end private :store_all_fonts ############################################################################### # - # _store_all_num_formats() + # 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 # Iterate through the XF objects and write a FORMAT record if it isn't a # built-in format type and if the FORMAT string hasn't already been used. # - @formats.each do |format| + formats.each do |format| num_format = format.num_format encoding = format.num_format_enc # Check if $num_format is an index to a built-in format. # Also check for a string of zeros, which is a valid format string @@ -1811,25 +1802,25 @@ end private :store_all_num_formats ############################################################################### # - # _store_all_xfs() + # store_all_xfs() # # Write all XF records. # def store_all_xfs #:nodoc: - @formats.each do |format| + formats.each do |format| xf = format.get_xf append(xf) end end private :store_all_xfs ############################################################################### # - # _store_all_styles() + # store_all_styles() # # Write all STYLE records. # def store_all_styles #:nodoc: # Excel adds the built-in styles in alphabetical order. @@ -1856,72 +1847,82 @@ end private :store_all_styles ############################################################################### # - # _store_names() + # 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| + defined_names.each do |defined_name| store_name( defined_name[:name], defined_name[:encoding], defined_name[:sheet_index], defined_name[:formula] ) end # Sort the worksheets into alphabetical order by name. This is a # requirement for some non-English language Excel patch levels. - worksheets = @worksheets.sort_by{ |x| x.name } + sorted_worksheets = @worksheets.sort_by{ |x| x.name } # Create the autofilter NAME records - worksheets.each do |worksheet| + create_autofilter_name_records(sorted_worksheets) + + # Create the print area NAME records + create_print_area_name_records(sorted_worksheets) + + # Create the print title NAME records + create_print_title_name_records(sorted_worksheets) + end + + def create_autofilter_name_records(sorted_worksheets) #:nodoc: + sorted_worksheets.each do |worksheet| index = worksheet.index - key = "#{index}:#{index}" - ref = @ext_refs[key] # Write a Name record if Autofilter has been defined if worksheet.filter_count != 0 store_name_short( worksheet.index, 0x0D, # NAME type = Filter Database - ref, + @ext_refs["#{index}:#{index}"], worksheet.filter_area[0], worksheet.filter_area[1], worksheet.filter_area[2], worksheet.filter_area[3], 1 # Hidden ) end end + end + private :create_autofilter_name_records - # Create the print area NAME records - worksheets.each do |worksheet| + def create_print_area_name_records(sorted_worksheets) #:nodoc: + sorted_worksheets.each do |worksheet| index = worksheet.index - key = "#{index}:#{index}" - ref = @ext_refs[key] # Write a Name record if the print area has been defined if !worksheet.print_rowmin.nil? store_name_short( worksheet.index, 0x06, # NAME type = Print_Area - ref, + @ext_refs["#{index}:#{index}"], worksheet.print_rowmin, worksheet.print_rowmax, worksheet.print_colmin, worksheet.print_colmax ) end end + end + private :create_print_area_name_records - # Create the print title NAME records - worksheets.each do |worksheet| + def create_print_title_name_records(sorted_worksheets) #:nodoc: + sorted_worksheets.each do |worksheet| index = worksheet.index rowmin = worksheet.title_rowmin rowmax = worksheet.title_rowmax colmin = worksheet.title_colmin colmax = worksheet.title_colmax @@ -1968,21 +1969,22 @@ else # Nothing left to do end end end + private :create_print_title_name_records ############################################################################### ############################################################################### # # BIFF RECORDS # ############################################################################### # - # _store_window1() + # store_window1() # # Write Excel BIFF WINDOW1 record. # def store_window1 #:nodoc: record = 0x003D # Record identifier @@ -2012,11 +2014,11 @@ end private :store_window1 ############################################################################### # - # _store_boundsheet() + # 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 @@ -2044,11 +2046,11 @@ end private :store_boundsheet ############################################################################### # - # _store_style() + # store_style() # type = $_[0] # Built-in style # xf_index = $_[1] # Index to style XF # # Write Excel BIFF STYLE records. # @@ -2067,11 +2069,11 @@ end private :store_style ############################################################################### # - # _store_num_format() + # 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. @@ -2084,22 +2086,14 @@ cch = format.bytesize ruby_19 { format = convert_to_ascii_if_ascii(format) } # Handle utf8 strings - ruby_18 do - if format =~ NonAscii - format = utf8_to_16be(format) - encoding = 1 - end + if is_utf8?(format) + format = utf8_to_16be(format) + encoding = 1 end - ruby_19 do - if format.encoding == Encoding::UTF_8 - format = utf8_to_16be(format) - encoding = 1 - end - end # Handle Unicode format strings. if encoding == 1 raise "Uneven number of bytes in Unicode font name" if cch % 2 != 0 cch /= 2 if encoding != 0 @@ -2123,11 +2117,11 @@ end private :store_num_format ############################################################################### # - # _store_1904() + # store_1904() # # Write Excel 1904 record to indicate the date system in use. # def store_1904 #:nodoc: record = 0x0022 # Record identifier @@ -2142,11 +2136,11 @@ end private :store_1904 ############################################################################### # - # _store_supbook() + # 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: @@ -2163,11 +2157,11 @@ end private :store_supbook ############################################################################### # - # _store_externsheet() + # 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. # @@ -2196,12 +2190,12 @@ # # Store the NAME record used for storing the print area, repeat rows, repeat # columns, autofilters and defined names. # - # TODO. This is a more generic version that will replace _store_name_short() - # and _store_name_long(). + # TODO. This is a more generic version that will replace store_name_short() + # and store_name_long(). # def store_name(name, encoding, sheet_index, formula) # :nodoc: ruby_19 { formula = convert_to_ascii_if_ascii(formula) } record = 0x0018 # Record identifier @@ -2246,11 +2240,11 @@ append(header, data) end ############################################################################### # - # _store_name_short() + # store_name_short() # index = shift # Sheet index # type = shift # ext_ref = shift # TODO # rowmin = $_[0] # Start row # rowmax = $_[1] # End row @@ -2307,11 +2301,11 @@ end private :store_name_short ############################################################################### # - # _store_name_long() + # 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 @@ -2319,11 +2313,11 @@ # 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. + # 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. ;-) # def store_name_long(index, type, ext_ref, rowmin, rowmax, colmin, colmax) #:nodoc: record = 0x0018 # Record identifier length = 0x002a # Number of bytes to follow @@ -2383,11 +2377,11 @@ end private :store_name_long ############################################################################### # - # _store_palette() + # store_palette() # # Stores the PALETTE biff record. # def store_palette #:nodoc: record = 0x0092 # Record identifier @@ -2406,11 +2400,11 @@ end private :store_palette ############################################################################### # - # _store_codepage() + # store_codepage() # # Stores the CODEPAGE biff record. # def store_codepage #:nodoc: record = 0x0042 # Record identifier @@ -2421,11 +2415,11 @@ end private :store_codepage ############################################################################### # - # _store_country() + # store_country() # # Stores the COUNTRY biff record. # def store_country #:nodoc: record = 0x008C # Record identifier @@ -2437,11 +2431,11 @@ end private :store_country ############################################################################### # - # _store_hideobj() + # store_hideobj() # # Stores the HIDEOBJ biff record. # def store_hideobj #:nodoc: record = 0x008D # Record identifier @@ -2450,38 +2444,38 @@ store_common(record, length, hide) end private :store_hideobj - def store_common(record, length, *data) + 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() + # 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 length = 0 index = 0 - unless @defined_names.empty? + unless defined_names.empty? index = 0 key = "#{index}:#{index}" add_ext_refs(ext_refs, key) unless ext_refs.has_key?(key) end - @defined_names.each do |defined_name| + defined_names.each do |defined_name| length += 19 + defined_name[:name].bytesize + defined_name[:formula].bytesize end @worksheets.each do |worksheet| @@ -2533,30 +2527,30 @@ length += 6 * (1 + ext_ref_count) length end - def add_ext_refs(ext_refs, key) + def add_ext_refs(ext_refs, key) #:nodoc: ext_refs[key] = ext_refs.keys.size end private :add_ext_refs ############################################################################### # - # _calculate_shared_string_sizes() + # 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 - # the _add_continue() method in BIFFwriter.pm. Thus we have to make two passes + # the add_continue() method in BIFFwriter.pm. Thus we have to make two passes # through the strings data. The first is to calculate the required block sizes - # and the second, in _store_shared_strings(), is to write the actual strings. + # and the second, in store_shared_strings(), is to write the actual strings. # The first pass through the data is also used to calculate the size of the SST # and CONTINUE records for use in setting the BOUNDSHEET record offsets. The - # downside of this is that the same algorithm repeated in _store_shared_strings. + # downside of this is that the same algorithm repeated in store_shared_strings. # def calculate_shared_string_sizes #:nodoc: strings = Array.new(@sinfo[:str_unique]) @sinfo[:str_table].each_key do |key| @@ -2568,16 +2562,16 @@ # Iterate through the strings to calculate the CONTINUE block sizes. # # The SST blocks requires a specialised CONTINUE block, so we have to # ensure that the maximum data block size is less than the limit used by - # _add_continue() in BIFFwriter.pm. For simplicity we use the same size + # add_continue() in BIFFwriter.pm. For simplicity we use the same size # for the SST and CONTINUE records: # 8228 : Maximum Excel97 block size # -4 : Length of block header # -8 : Length of additional SST header information - # -8 : Arbitrary number to keep within _add_continue() limit + # -8 : Arbitrary number to keep within add_continue() limit # = 8208 # continue_limit = 8208 block_length = 0 written = 0 @@ -2603,11 +2597,11 @@ # written in more than one CONTINUE record. encoding = string.unpack("xx C")[0] split_string = 0 while block_length >= continue_limit header_length, space_remaining, align, split_string = - _split_string_setup(encoding, split_string, continue_limit, written, continue) + split_string_setup(encoding, split_string, continue_limit, written, continue) if space_remaining > header_length # Write as much as possible of the string in the current block written += space_remaining @@ -2665,11 +2659,11 @@ length end private :calculate_shared_string_sizes - def _split_string_setup(encoding, split_string, continue_limit, written, continue) + 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 space_remaining = continue_limit - written - continue @@ -2695,19 +2689,19 @@ split_string = 1 end end [header_length, space_remaining, align, split_string] end - private :_split_string_setup + private :split_string_setup ############################################################################### # - # _store_shared_strings() + # store_shared_strings() # # Write all of the workbooks strings into an indexed array. # - # See the comments in _calculate_shared_string_sizes() for more information. + # 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. # In order to do this we first identify the first string in an EXTSST bucket # and then store its global and local offset within the SST table. The offset # occurs wherever the start of the bucket string is written out via append(). @@ -2724,11 +2718,11 @@ block_length = 0 written = 0 continue = 0 # The SST and CONTINUE block sizes have been pre-calculated by - # _calculate_shared_string_sizes() + # calculate_shared_string_sizes() block_sizes = @str_block_sizes # The SST record is required even if it contains no strings. Thus we will # always have a length # @@ -2783,11 +2777,11 @@ # written in more than one CONTINUE record. encoding = string.unpack("xx C")[0] split_string = 0 while block_length >= continue_limit header_length, space_remaining, align, split_string = - _split_string_setup(encoding, split_string, continue_limit, written, continue) + split_string_setup(encoding, split_string, continue_limit, written, continue) if space_remaining > header_length # Write as much as possible of the string in the current block tmp = string[0, space_remaining] @@ -2855,11 +2849,11 @@ end private :store_shared_strings ############################################################################### # - # _calculate_extsst_size + # 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 @@ -2882,13 +2876,13 @@ 6 + 8 * buckets end ############################################################################### # - # _store_extsst + # store_extsst # - # Write EXTSST table using the offsets calculated in _store_shared_strings(). + # Write EXTSST table using the offsets calculated in store_shared_strings(). # def store_extsst #:nodoc: offsets = @extsst_offsets bucket_size = @extsst_bucket_size @@ -2910,11 +2904,11 @@ # Methods related to comments and MSO objects. # ############################################################################### # - # _add_mso_drawing_group() + # 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: @@ -2941,11 +2935,11 @@ end private :add_mso_drawing_group ############################################################################### # - # _add_mso_drawing_group_continue() + # add_mso_drawing_group_continue() # # 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. @@ -2960,11 +2954,11 @@ limit = 8228 -4 mso_group = 0x00EB # Record identifier continue = 0x003C # Record identifier block_count = 1 - # Ignore the base class _add_continue() method. + # Ignore the base class add_continue() method. @ignore_continue = 1 # Case 1 above. Just return the data as it is. if data.bytesize <= limit append(data) @@ -2993,25 +2987,25 @@ # Last CONTINUE block for remaining data. Case 2 and 3 above. header = [continue, data.bytesize].pack("vv") append(header, data) - # Turn the base class _add_continue() method back on. + # Turn the base class add_continue() method back on. @ignore_continue = 0 end private :add_mso_drawing_group_continue - def devide_string(string, nth) + 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() + # store_mso_dgg_container() # # Write the Escher DggContainer record that is part of MSODRAWINGGROUP. # def store_mso_dgg_container #:nodoc: type = 0xF000 @@ -3024,11 +3018,11 @@ end private :store_mso_dgg_container ############################################################################### # - # _store_mso_dgg() + # store_mso_dgg() # my $max_spid = $_[0]; # my $num_clusters = $_[1]; # my $shapes_saved = $_[2]; # my $drawings_saved = $_[3]; # my $clusters = $_[4]; @@ -3056,11 +3050,11 @@ end private :store_mso_dgg ############################################################################### # - # _store_mso_bstore_container() + # 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 @@ -3075,11 +3069,11 @@ end private :store_mso_bstore_container ############################################################################### # - # _store_mso_images() + # store_mso_images() # ref_count = $_[0] # image_type = $_[1] # image = $_[2] # size = $_[3] # checksum1 = $_[4] @@ -3107,11 +3101,11 @@ end private :store_mso_images ############################################################################### # - # _store_mso_blip_store_entry() + # store_mso_blip_store_entry() # ref_count = $_[0] # image_type = $_[1] # size = $_[2] # checksum1 = $_[3] # @@ -3138,11 +3132,11 @@ end private :store_mso_blip_store_entry ############################################################################### # - # _store_mso_blip() + # store_mso_blip() # image_type = $_[0] # image_data = $_[1] # size = $_[2] # checksum1 = $_[3] # checksum2 = $_[4] @@ -3170,11 +3164,11 @@ end private :store_mso_blip ############################################################################### # - # _store_mso_opt() + # store_mso_opt() # # Write the Escher Opt record that is part of MSODRAWINGGROUP. # def store_mso_opt #:nodoc: type = 0xF00B @@ -3189,11 +3183,11 @@ end private :store_mso_opt ############################################################################### # - # _store_mso_split_menu_colors() + # store_mso_split_menu_colors() # # Write the Escher SplitMenuColors record that is part of MSODRAWINGGROUP. # def store_mso_split_menu_colors #:nodoc: type = 0xF11E @@ -3206,10 +3200,11 @@ add_mso_generic(type, version, instance, data, length) end private :store_mso_split_menu_colors - def cleanup + def cleanup #:nodoc: super sheets.each { |sheet| sheet.cleanup } end + private :cleanup end