lib/writeexcel/workbook.rb in writeexcel-0.3.5 vs lib/writeexcel/workbook.rb in writeexcel-0.4.0

- old
+ new

@@ -1,5 +1,6 @@ +# -*- coding: utf-8 -*- ############################################################################### # # Workbook - A writer class for Excel Workbooks. # # @@ -8,10 +9,12 @@ # 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/olewriter' require 'writeexcel/formula' require 'writeexcel/format' require 'writeexcel/worksheet' @@ -22,19 +25,21 @@ require 'writeexcel/charts/external' require 'writeexcel/charts/line' require 'writeexcel/charts/pie' require 'writeexcel/charts/scatter' require 'writeexcel/charts/stock' -require 'writeexcel/properties' -require 'digest/md5' require 'writeexcel/storage_lite' +require 'writeexcel/compatibility' class Workbook < BIFFWriter + require 'writeexcel/properties' + require 'writeexcel/helper' + private :convert_to_ascii_if_ascii + BOF = 11 # :nodoc: EOF = 4 # :nodoc: SheetName = "Sheet" # :nodoc: - NonAscii = /[^!"#\$%&'\(\)\*\+,\-\.\/\:\;<=>\?@0-9A-Za-z_\[\\\]^` ~\0\n]/ # :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. # @@ -206,11 +211,12 @@ # def close return if @fileclosed # Prevent close() from being called twice. @fileclosed = true - return store_workbook + store_workbook + cleanup end # get array of Worksheet objects # # :call-seq: @@ -487,15 +493,17 @@ else name = @sheet_name + @sheet_count.to_s end end + name = convert_to_ascii_if_ascii(name) + # Check that sheetname is <= 31 (1 or 2 byte chars). Excel limit. - raise "Sheetname $name must be <= 31 chars" if name.length > limit + raise "Sheetname $name must be <= 31 chars" if name.bytesize > limit # Check that Unicode sheetname has an even number of bytes - if encoding == 1 && (name.length % 2 != 0) + if encoding == 1 && (name.bytesize % 2 != 0) raise "Odd number of bytes in Unicode worksheet name: #{name}" end # Check that sheetname doesn't contain any invalid characters if encoding != 1 && name =~ invalid_char @@ -512,12 +520,13 @@ str = $~.post_match end end # Handle utf8 strings - if name =~ NonAscii + if name.encoding == Encoding::UTF_8 name = NKF.nkf('-w16B0 -m0 -W', name) + name.force_encoding('UTF-16BE') encoding = 1 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 @@ -748,11 +757,11 @@ index -=8 # Adjust colour index (wingless dragonfly) # Set the RGB value @palette[index] = [red, green, blue, 0] - return index + 8 + index + 8 end ############################################################################### # # set_palette_xl97() @@ -816,11 +825,11 @@ [0x99, 0x33, 0x00, 0x00], # 60 [0x99, 0x33, 0x66, 0x00], # 61 [0x33, 0x33, 0x99, 0x00], # 62 [0x33, 0x33, 0x33, 0x00] # 63 ] - return 0 + 0 end private :set_palette_xl97 # # Change the default temp directory @@ -954,11 +963,11 @@ :sheet_index => sheet_index, :formula => formula } ) - index = @defined_names.length + index = @defined_names.size parser.set_ext_name(name, index) end # @@ -1014,10 +1023,13 @@ # def set_properties(params) # Ignore if no args were passed. return -1 if !params.kind_of?(Hash) || params.empty? + params.each do |k, v| + params[k] = convert_to_ascii_if_ascii(v) if v.kind_of?(String) + end # List of valid input parameters. properties = { :codepage => [0x0001, 'VT_I2' ], :title => [0x0002, 'VT_LPSTR' ], :subject => [0x0003, 'VT_LPSTR' ], @@ -1110,11 +1122,11 @@ unless params[:utf8].nil? return 0xFDE9 else strings.each do |string| next unless params.has_key?(string.to_sym) - return 0xFDE9 if params[string.to_sym] =~ NonAscii + return 0xFDE9 if params[string.to_sym].encoding == Encoding::UTF_8 end return 0x04E4; # Default codepage, Latin 1. end end private :get_property_set_codepage @@ -1246,12 +1258,10 @@ end end return ole.close else - # Write the OLE file using ruby-ole if data > 7MB - # Create the Workbook stream. stream = 'Workbook'.unpack('C*').pack('v*') workbook = OLEStorageLitePPSFile.new(stream) workbook.set_file # use tempfile @@ -1325,11 +1335,11 @@ mso_size = @mso_size mso_size += 4 * Integer((mso_size -1) / Float(@limit)) offset += mso_size @worksheets.each do |sheet| - offset += _bof + sheet.name.length + offset += _bof + sheet.name.bytesize end offset += _eof @worksheets.each do |sheet| sheet.offset = offset @@ -1481,11 +1491,11 @@ 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.length + size = data.bytesize checksum1 = image_checksum(data, image_id) checksum2 = checksum1 ref_count = 1 # Process the image and extract dimensions. @@ -1592,11 +1602,11 @@ 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] - return [type, width, height] + [type, width, height] end private :process_png ############################################################################### # @@ -1608,11 +1618,11 @@ # 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.length <= 0x36 + 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") @@ -1641,11 +1651,11 @@ if compression != 0 raise "#{filename}: compression not supported in bitmap image." end - return [type, width, height] + [type, width, height] end private :process_bmp ############################################################################### # @@ -1655,11 +1665,11 @@ # def process_jpg(data, filename) # :nodoc: type = 5 # Excel Blip type (MSOBLIPTYPE). offset = 2; - data_length = data.length + 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") @@ -1673,11 +1683,11 @@ width = data[offset+7, 2].unpack("n") width = width[0] break end - offset = offset + length + 2 + offset += length + 2 break if marker == 0xFFDA end if height.nil? raise "#{filename}: no size data found in jpeg image.\n" @@ -1760,25 +1770,25 @@ 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 |format| - key = format.get_font_key - if format.font_only == 0 and !fonts[key].nil? + @formats.each do |fmt| + key = fmt.get_font_key + if fmt.font_only == 0 and !fonts[key].nil? # FONT has already been used - format.font_index = fonts[key] + fmt.font_index = fonts[key] else # Add a new FONT record - if format.font_only == 0 + if fmt.font_only == 0 fonts[key] = index end - format.font_index = index + fmt.font_index = index index += 1 - font = format.get_font + font = fmt.get_font print "#{__FILE__}(#{__LINE__}) \n" if defined?($debug) append(font) end end end @@ -2036,14 +2046,14 @@ # my $encoding = $_[4]; # Sheet name encoding # # Writes Excel BIFF BOUNDSHEET record. # def store_boundsheet(sheetname, offset, type, hidden, encoding) #:nodoc: - record = 0x0085 # Record identifier - length = 0x08 + sheetname.length # Number of bytes to follow + record = 0x0085 # Record identifier + length = 0x08 + sheetname.bytesize # Number of bytes to follow - cch = sheetname.length # Length of sheet name + cch = sheetname.bytesize # Length of sheet name grbit = type | hidden # Character length is num of chars not num of bytes cch /= 2 if encoding != 0 @@ -2095,34 +2105,38 @@ def store_num_format(format, ifmt, encoding) #:nodoc: format = format.to_s unless format.kind_of?(String) record = 0x041E # Record identifier # length # Number of bytes to follow # Char length of format string - cch = format.length + cch = format.bytesize + format = convert_to_ascii_if_ascii(format) + # Handle utf8 strings - if format =~ NonAscii + if format.encoding == Encoding::UTF_8 format = NKF.nkf('-w16B0 -m0 -W', format) + format.force_encoding('UTF-16BE') encoding = 1 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 format = format.unpack('n*').pack('v*') end +=begin # Special case to handle Euro symbol, 0x80, in non-Unicode strings. if encoding == 0 and format =~ /\x80/ format = format.unpack('C*').pack('v*') format.gsub!(/\x80\x00/, "\xAC\x20") encoding = 1 end +=end + length = 0x05 + format.bytesize - length = 0x05 + format.length - header = [record, length].pack("vv") data = [ifmt, cch, encoding].pack("vvC") print "#{__FILE__}(#{__LINE__}) \n" if defined?($debug) append(header, data, format) @@ -2185,22 +2199,22 @@ # Get the external refs ext_refs = @ext_refs ext = ext_refs.keys.sort # Change the external refs from stringified "1:1" to [1, 1] - ext.map! {|e| e.split(/:/).map! {|e| e.to_i} } + ext.map! {|e| e.split(/:/).map! {|v| v.to_i} } cxti = ext.size # Number of Excel XTI structures rgxti = '' # Array of XTI structures # Write the XTI structs ext.each do |e| - rgxti = rgxti + [0, e[0], e[1]].pack("vvv") + rgxti += [0, e[0], e[1]].pack("vvv") end data = [cxti].pack("v") + rgxti - header = [record, data.length].pack("vv") + header = [record, data.bytesize].pack("vv") print "#{__FILE__}(#{__LINE__}) \n" if defined?($debug) append(header, data) end @@ -2210,14 +2224,16 @@ # # 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: + formula = convert_to_ascii_if_ascii(formula) + record = 0x0018 # Record identifier - text_length = name.length - formula_length = formula.length + text_length = name.bytesize + formula_length = formula.bytesize # UTF-16 string length is in characters not bytes. text_length /= 2 if encoding != 0 grbit = 0x0000 # Option flags @@ -2228,13 +2244,13 @@ help_length = 0x00 # Length of help topic text status_length = 0x00 # Length of status bar text # Set grbit built-in flag and the hidden flag for autofilters. if text_length == 1 - grbit = 0x0020 if name[0] == 0x06 # Print area - grbit = 0x0020 if name[0] == 0x07 # Print titles - grbit = 0x0021 if name[0] == 0x0D # Autofilter + grbit = 0x0020 if name.ord == 0x06 # Print area + grbit = 0x0020 if name.ord == 0x07 # Print titles + grbit = 0x0021 if name.ord == 0x0D # Autofilter end data = [grbit].pack("v") data += [shortcut].pack("C") data += [text_length].pack("C") @@ -2247,11 +2263,11 @@ data += [status_length].pack("C") data += [encoding].pack("C") data += name data += formula - header = [record, data.length].pack("vv") + header = [record, data.bytesize].pack("vv") print "#{__FILE__}(#{__LINE__}) \n" if defined?($debug) append(header, data) end @@ -2278,11 +2294,11 @@ grbit = 0x0020 # Option flags chKey = 0x00 # Keyboard shortcut cch = 0x01 # Length of text name cce = 0x000b # Length of text definition unknown01 = 0x0000 # - ixals = index +1 # Sheet index + ixals = index + 1 # Sheet index unknown02 = 0x00 # cchCustMenu = 0x00 # Length of cust menu text cchDescription = 0x00 # Length of description text cchHelptopic = 0x00 # Length of help topic text cchStatustext = 0x00 # Length of status bar text @@ -2341,11 +2357,11 @@ grbit = 0x0020 # Option flags chKey = 0x00 # Keyboard shortcut cch = 0x01 # Length of text name cce = 0x001a # Length of text definition unknown01 = 0x0000 # - ixals = index +1 # Sheet index + ixals = index + 1 # Sheet index unknown02 = 0x00 # cchCustMenu = 0x00 # Length of cust menu text cchDescription = 0x00 # Length of description text cchHelptopic = 0x00 # Length of help topic text cchStatustext = 0x00 # Length of status bar text @@ -2406,11 +2422,11 @@ ccv = @palette.size # Number of RGB values to follow data = '' # The RGB data # Pack the RGB data @palette.each do |p| - data = data + p.pack('CCCC') + data += p.pack('CCCC') end header = [record, length, ccv].pack("vvv") print "#{__FILE__}(#{__LINE__}) \n" if defined?($debug) @@ -2503,11 +2519,11 @@ ext_ref_count += 1 end end @defined_names.each do |defined_name| - length += 19 + defined_name[:name].length + defined_name[:formula].length + length += 19 + defined_name[:name].bytesize + defined_name[:formula].bytesize end @worksheets.each do |worksheet| rowmin = worksheet.title_rowmin @@ -2569,11 +2585,11 @@ length += 8 # The EXTERNSHEET record is 6 bytes + 6 bytes for each external ref length += 6 * (1 + ext_ref_count) - return length + length end ############################################################################### # # _calculate_shared_string_sizes() @@ -2618,11 +2634,11 @@ block_sizes = [] continue = 0 strings.each do |string| - string_length = string.length + string_length = string.bytesize encoding = string.unpack("xx C")[0] split_string = 0 # Block length is the total length of the strings that will be # written out in a single SST or CONTINUE block. @@ -2728,11 +2744,11 @@ length += block_sizes.shift unless block_sizes.empty? # SST while !block_sizes.empty? do length += 4 + block_sizes.shift # CONTINUEs end - return length + length end private :calculate_shared_string_sizes ############################################################################### # @@ -2786,11 +2802,11 @@ # Iterate through the strings and write them out return if strings.empty? strings.each do |string| - string_length = string.length + string_length = string.bytesize encoding = string.unpack("xx C")[0] split_string = 0 bucket_string = 0 # Used to track EXTSST bucket offsets. # Check if the string is at the start of a EXTSST bucket. @@ -2904,11 +2920,11 @@ record = 0x003C length = block_sizes.shift header = [record, length].pack("vv") - header = header + [encoding].pack("C") if continue != 0 + header += [encoding].pack("C") if continue != 0 print "#{__FILE__}(#{__LINE__}) \n" if defined?($debug) append(header) end @@ -2961,11 +2977,11 @@ buckets = Integer((unique_strings + bucket_size -1) / Float(bucket_size)) @extsst_buckets = buckets @extsst_bucket_size = bucket_size - return 6 + 8 * buckets + 6 + 8 * buckets end ############################################################################### # # _store_extsst @@ -2981,11 +2997,11 @@ header = [record, length].pack('vv') data = [bucket_size].pack('v') offsets.each do |offset| - data = data + [offset[0], offset[1], 0].pack('Vvv') + data += [offset[0], offset[1], 0].pack('Vvv') end print "#{__FILE__}(#{__LINE__}) \n" if defined?($debug) append(header, data) end @@ -3007,24 +3023,24 @@ record = 0x00EB # Record identifier length = 0x0000 # Number of bytes to follow data = store_mso_dgg_container - data = data + store_mso_dgg(*@mso_clusters) - data = data + store_mso_bstore_container + data += store_mso_dgg(*@mso_clusters) + data += store_mso_bstore_container @images_data.each do |image| - data = data + store_mso_images(*image) + data += store_mso_images(*image) end - data = data + store_mso_opt - data = data + store_mso_split_menu_colors + data += store_mso_opt + data += store_mso_split_menu_colors - length = data.length + length = data.bytesize header = [record, length].pack("vv") add_mso_drawing_group_continue(header + data) - return header + data # For testing only. + header + data # For testing only. end private :add_mso_drawing_group ############################################################################### # @@ -3049,11 +3065,11 @@ # Ignore the base class _add_continue() method. @ignore_continue = 1 # Case 1 above. Just return the data as it is. - if data.length <= limit + if data.bytesize <= limit print "#{__FILE__}(#{__LINE__}) \n" if defined?($debug) append(data) return end @@ -3063,11 +3079,11 @@ tmp[2, 2] = [limit].pack('v') print "#{__FILE__}(#{__LINE__}) \n" if defined?($debug) append(tmp) # Add MSODRAWINGGROUP and CONTINUE blocks for Case 3 above. - while data.length > limit + while data.bytesize > limit if block_count == 1 # Add extra MSODRAWINGGROUP block header. header = [mso_group, limit].pack("vv") block_count += 1 else @@ -3080,11 +3096,11 @@ print "#{__FILE__}(#{__LINE__}) \n" if defined?($debug) append(header, tmp) end # Last CONTINUE block for remaining data. Case 2 and 3 above. - header = [continue, data.length].pack("vv") + header = [continue, data.bytesize].pack("vv") print "#{__FILE__}(#{__LINE__}) \n" if defined?($debug) append(header, data) # Turn the base class _add_continue() method back on. @ignore_continue = 0 @@ -3102,11 +3118,11 @@ version = 15 instance = 0 data = '' length = @mso_size -12 # -4 (biff header) -8 (for this). - return add_mso_generic(type, version, instance, data, length) + add_mso_generic(type, version, instance, data, length) end private :store_mso_dgg_container ############################################################################### # @@ -3131,14 +3147,14 @@ clusters.each do |aref| drawing_id = aref[0] shape_ids_used = aref[1] - data = data + [drawing_id, shape_ids_used].pack("VV") + data += [drawing_id, shape_ids_used].pack("VV") end - return add_mso_generic(type, version, instance, data, length) + add_mso_generic(type, version, instance, data, length) end private :store_mso_dgg ############################################################################### # @@ -3153,11 +3169,11 @@ version = 15 instance = @images_data.size # Number of images. data = '' length = @images_size +8 *instance - return add_mso_generic(type, version, instance, data, length) + add_mso_generic(type, version, instance, data, length) end private :store_mso_bstore_container ############################################################################### # @@ -3185,11 +3201,11 @@ size, checksum1, checksum2 ) - return blip_store_entry + blip + blip_store_entry + blip end private :store_mso_images ############################################################################### # @@ -3216,11 +3232,11 @@ [0x00].pack('C') + # Usage [0x00].pack('C') + # Name length [0x00].pack('C') + # Unused [0x00].pack('C') # Unused - return add_mso_generic(type, version, instance, data, length) + add_mso_generic(type, version, instance, data, length) end private :store_mso_blip_store_entry ############################################################################### # @@ -3248,11 +3264,11 @@ length = size +17 data = [checksum1].pack('H*') + # Uid checksum [0xFF].pack('C') + # Tag image_data # Image - return add_mso_generic(type, version, instance, data, length) + add_mso_generic(type, version, instance, data, length) end private :store_mso_blip ############################################################################### # @@ -3267,11 +3283,11 @@ data = '' length = 18 data = ['BF0008000800810109000008C0014000'+'0008'].pack("H*") - return add_mso_generic(type, version, instance, data, length) + add_mso_generic(type, version, instance, data, length) end private :store_mso_opt ############################################################################### # @@ -3286,9 +3302,14 @@ data = '' length = 16 data = ['0D0000080C00000817000008F7000010'].pack("H*") - return add_mso_generic(type, version, instance, data, length) + add_mso_generic(type, version, instance, data, length) end private :store_mso_split_menu_colors + + def cleanup + super + sheets.each { |sheet| sheet.cleanup } + end end