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