ricardoo27-writeexcel 0.6.12.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.document +5 -0
- data/.gitattributes +1 -0
- data/README.rdoc +136 -0
- data/Rakefile +52 -0
- data/VERSION +1 -0
- data/charts/chartex.rb +316 -0
- data/charts/demo1.rb +46 -0
- data/charts/demo101.bin +0 -0
- data/charts/demo2.rb +65 -0
- data/charts/demo201.bin +0 -0
- data/charts/demo3.rb +117 -0
- data/charts/demo301.bin +0 -0
- data/charts/demo4.rb +119 -0
- data/charts/demo401.bin +0 -0
- data/charts/demo5.rb +48 -0
- data/charts/demo501.bin +0 -0
- data/examples/a_simple.rb +43 -0
- data/examples/autofilter.rb +265 -0
- data/examples/bigfile.rb +30 -0
- data/examples/chart_area.rb +121 -0
- data/examples/chart_bar.rb +120 -0
- data/examples/chart_column.rb +120 -0
- data/examples/chart_line.rb +120 -0
- data/examples/chart_pie.rb +108 -0
- data/examples/chart_scatter.rb +121 -0
- data/examples/chart_stock.rb +148 -0
- data/examples/chess.rb +142 -0
- data/examples/colors.rb +129 -0
- data/examples/comments1.rb +27 -0
- data/examples/comments2.rb +352 -0
- data/examples/copyformat.rb +52 -0
- data/examples/data_validate.rb +279 -0
- data/examples/date_time.rb +87 -0
- data/examples/defined_name.rb +32 -0
- data/examples/demo.rb +124 -0
- data/examples/diag_border.rb +36 -0
- data/examples/formats.rb +490 -0
- data/examples/formula_result.rb +30 -0
- data/examples/header.rb +137 -0
- data/examples/hide_sheet.rb +29 -0
- data/examples/hyperlink.rb +43 -0
- data/examples/images.rb +63 -0
- data/examples/indent.rb +31 -0
- data/examples/merge1.rb +40 -0
- data/examples/merge2.rb +45 -0
- data/examples/merge3.rb +66 -0
- data/examples/merge4.rb +83 -0
- data/examples/merge5.rb +80 -0
- data/examples/merge6.rb +67 -0
- data/examples/outline.rb +255 -0
- data/examples/outline_collapsed.rb +209 -0
- data/examples/panes.rb +113 -0
- data/examples/password_protection.rb +33 -0
- data/examples/properties.rb +34 -0
- data/examples/properties_jp.rb +33 -0
- data/examples/protection.rb +47 -0
- data/examples/regions.rb +53 -0
- data/examples/repeat.rb +43 -0
- data/examples/republic.png +0 -0
- data/examples/right_to_left.rb +27 -0
- data/examples/row_wrap.rb +53 -0
- data/examples/set_first_sheet.rb +14 -0
- data/examples/stats.rb +74 -0
- data/examples/stocks.rb +81 -0
- data/examples/store_formula.rb +15 -0
- data/examples/tab_colors.rb +31 -0
- data/examples/utf8.rb +15 -0
- data/examples/write_arrays.rb +83 -0
- data/html/en/doc_en.html +5946 -0
- data/html/images/a_simple.jpg +0 -0
- data/html/images/area1.jpg +0 -0
- data/html/images/bar1.jpg +0 -0
- data/html/images/chart_area.xls +0 -0
- data/html/images/column1.jpg +0 -0
- data/html/images/data_validation.jpg +0 -0
- data/html/images/line1.jpg +0 -0
- data/html/images/pie1.jpg +0 -0
- data/html/images/regions.jpg +0 -0
- data/html/images/scatter1.jpg +0 -0
- data/html/images/stats.jpg +0 -0
- data/html/images/stock1.jpg +0 -0
- data/html/images/stocks.jpg +0 -0
- data/html/index.html +16 -0
- data/html/style.css +433 -0
- data/lib/writeexcel.rb +1159 -0
- data/lib/writeexcel/biffwriter.rb +223 -0
- data/lib/writeexcel/caller_info.rb +12 -0
- data/lib/writeexcel/cell_range.rb +332 -0
- data/lib/writeexcel/chart.rb +1968 -0
- data/lib/writeexcel/charts/area.rb +154 -0
- data/lib/writeexcel/charts/bar.rb +177 -0
- data/lib/writeexcel/charts/column.rb +156 -0
- data/lib/writeexcel/charts/external.rb +66 -0
- data/lib/writeexcel/charts/line.rb +154 -0
- data/lib/writeexcel/charts/pie.rb +169 -0
- data/lib/writeexcel/charts/scatter.rb +192 -0
- data/lib/writeexcel/charts/stock.rb +213 -0
- data/lib/writeexcel/col_info.rb +87 -0
- data/lib/writeexcel/colors.rb +68 -0
- data/lib/writeexcel/comments.rb +460 -0
- data/lib/writeexcel/compatibility.rb +65 -0
- data/lib/writeexcel/convert_date_time.rb +117 -0
- data/lib/writeexcel/data_validations.rb +370 -0
- data/lib/writeexcel/debug_info.rb +41 -0
- data/lib/writeexcel/embedded_chart.rb +35 -0
- data/lib/writeexcel/excelformula.y +139 -0
- data/lib/writeexcel/excelformulaparser.rb +587 -0
- data/lib/writeexcel/format.rb +1575 -0
- data/lib/writeexcel/formula.rb +987 -0
- data/lib/writeexcel/helper.rb +78 -0
- data/lib/writeexcel/image.rb +218 -0
- data/lib/writeexcel/olewriter.rb +305 -0
- data/lib/writeexcel/outline.rb +24 -0
- data/lib/writeexcel/properties.rb +242 -0
- data/lib/writeexcel/shared_string_table.rb +153 -0
- data/lib/writeexcel/storage_lite.rb +984 -0
- data/lib/writeexcel/workbook.rb +2478 -0
- data/lib/writeexcel/worksheet.rb +6925 -0
- data/lib/writeexcel/worksheets.rb +25 -0
- data/lib/writeexcel/write_file.rb +63 -0
- data/test/excelfile/Chart1.xls +0 -0
- data/test/excelfile/Chart2.xls +0 -0
- data/test/excelfile/Chart3.xls +0 -0
- data/test/excelfile/Chart4.xls +0 -0
- data/test/excelfile/Chart5.xls +0 -0
- data/test/helper.rb +31 -0
- data/test/perl_output/Chart1.xls.data +0 -0
- data/test/perl_output/Chart2.xls.data +0 -0
- data/test/perl_output/Chart3.xls.data +0 -0
- data/test/perl_output/Chart4.xls.data +0 -0
- data/test/perl_output/Chart5.xls.data +0 -0
- data/test/perl_output/README +31 -0
- data/test/perl_output/a_simple.xls +0 -0
- data/test/perl_output/autofilter.xls +0 -0
- data/test/perl_output/biff_add_continue_testdata +0 -0
- data/test/perl_output/chart_area.xls +0 -0
- data/test/perl_output/chart_bar.xls +0 -0
- data/test/perl_output/chart_column.xls +0 -0
- data/test/perl_output/chart_line.xls +0 -0
- data/test/perl_output/chess.xls +0 -0
- data/test/perl_output/colors.xls +0 -0
- data/test/perl_output/comments0.xls +0 -0
- data/test/perl_output/comments1.xls +0 -0
- data/test/perl_output/comments2.xls +0 -0
- data/test/perl_output/data_validate.xls +0 -0
- data/test/perl_output/date_time.xls +0 -0
- data/test/perl_output/defined_name.xls +0 -0
- data/test/perl_output/demo.xls +0 -0
- data/test/perl_output/demo101.bin +0 -0
- data/test/perl_output/demo201.bin +0 -0
- data/test/perl_output/demo301.bin +0 -0
- data/test/perl_output/demo401.bin +0 -0
- data/test/perl_output/demo501.bin +0 -0
- data/test/perl_output/diag_border.xls +0 -0
- data/test/perl_output/f_font_biff +0 -0
- data/test/perl_output/f_font_key +1 -0
- data/test/perl_output/f_xf_biff +0 -0
- data/test/perl_output/file_font_biff +0 -0
- data/test/perl_output/file_font_key +1 -0
- data/test/perl_output/file_xf_biff +0 -0
- data/test/perl_output/formula_result.xls +0 -0
- data/test/perl_output/headers.xls +0 -0
- data/test/perl_output/hidden.xls +0 -0
- data/test/perl_output/hide_zero.xls +0 -0
- data/test/perl_output/hyperlink.xls +0 -0
- data/test/perl_output/images.xls +0 -0
- data/test/perl_output/indent.xls +0 -0
- data/test/perl_output/merge1.xls +0 -0
- data/test/perl_output/merge2.xls +0 -0
- data/test/perl_output/merge3.xls +0 -0
- data/test/perl_output/merge4.xls +0 -0
- data/test/perl_output/merge5.xls +0 -0
- data/test/perl_output/merge6.xls +0 -0
- data/test/perl_output/ole_write_header +0 -0
- data/test/perl_output/outline.xls +0 -0
- data/test/perl_output/outline_collapsed.xls +0 -0
- data/test/perl_output/panes.xls +0 -0
- data/test/perl_output/password_protection.xls +0 -0
- data/test/perl_output/protection.xls +0 -0
- data/test/perl_output/regions.xls +0 -0
- data/test/perl_output/right_to_left.xls +0 -0
- data/test/perl_output/set_first_sheet.xls +0 -0
- data/test/perl_output/stats.xls +0 -0
- data/test/perl_output/stocks.xls +0 -0
- data/test/perl_output/store_formula.xls +0 -0
- data/test/perl_output/tab_colors.xls +0 -0
- data/test/perl_output/unicode_cyrillic.xls +0 -0
- data/test/perl_output/utf8.xls +0 -0
- data/test/perl_output/workbook1.xls +0 -0
- data/test/perl_output/workbook2.xls +0 -0
- data/test/perl_output/ws_colinfo +1 -0
- data/test/perl_output/ws_store_colinfo +0 -0
- data/test/perl_output/ws_store_dimensions +0 -0
- data/test/perl_output/ws_store_filtermode +0 -0
- data/test/perl_output/ws_store_filtermode_off +0 -0
- data/test/perl_output/ws_store_filtermode_on +0 -0
- data/test/perl_output/ws_store_selection +0 -0
- data/test/perl_output/ws_store_window2 +1 -0
- data/test/republic.png +0 -0
- data/test/test_00_IEEE_double.rb +13 -0
- data/test/test_01_add_worksheet.rb +10 -0
- data/test/test_02_merge_formats.rb +49 -0
- data/test/test_04_dimensions.rb +388 -0
- data/test/test_05_rows.rb +175 -0
- data/test/test_06_extsst.rb +74 -0
- data/test/test_11_date_time.rb +475 -0
- data/test/test_12_date_only.rb +525 -0
- data/test/test_13_date_seconds.rb +477 -0
- data/test/test_21_escher.rb +624 -0
- data/test/test_22_mso_drawing_group.rb +741 -0
- data/test/test_23_note.rb +57 -0
- data/test/test_24_txo.rb +74 -0
- data/test/test_25_position_object.rb +80 -0
- data/test/test_26_autofilter.rb +309 -0
- data/test/test_27_autofilter.rb +126 -0
- data/test/test_28_autofilter.rb +156 -0
- data/test/test_29_process_jpg.rb +670 -0
- data/test/test_30_validation_dval.rb +74 -0
- data/test/test_31_validation_dv_strings.rb +123 -0
- data/test/test_32_validation_dv_formula.rb +203 -0
- data/test/test_40_property_types.rb +188 -0
- data/test/test_41_properties.rb +235 -0
- data/test/test_42_set_properties.rb +434 -0
- data/test/test_50_name_stored.rb +295 -0
- data/test/test_51_name_print_area.rb +353 -0
- data/test/test_52_name_print_titles.rb +450 -0
- data/test/test_53_autofilter.rb +199 -0
- data/test/test_60_chart_generic.rb +574 -0
- data/test/test_61_chart_subclasses.rb +84 -0
- data/test/test_62_chart_formats.rb +268 -0
- data/test/test_63_chart_area_formats.rb +645 -0
- data/test/test_biff.rb +71 -0
- data/test/test_big_workbook.rb +17 -0
- data/test/test_compatibility.rb +12 -0
- data/test/test_example_match.rb +3246 -0
- data/test/test_format.rb +1189 -0
- data/test/test_formula.rb +61 -0
- data/test/test_ole.rb +102 -0
- data/test/test_storage_lite.rb +116 -0
- data/test/test_workbook.rb +146 -0
- data/test/test_worksheet.rb +106 -0
- data/utils/add_magic_comment.rb +80 -0
- data/writeexcel.gemspec +278 -0
- data/writeexcel.rdoc +1425 -0
- metadata +292 -0
|
@@ -0,0 +1,2478 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
###############################################################################
|
|
3
|
+
#
|
|
4
|
+
# Workbook - A writer class for Excel Workbooks.
|
|
5
|
+
#
|
|
6
|
+
#
|
|
7
|
+
# Used in conjunction with WriteExcel
|
|
8
|
+
#
|
|
9
|
+
# Copyright 2000-2010, John McNamara, jmcnamara@cpan.org
|
|
10
|
+
#
|
|
11
|
+
# original written in Perl by John McNamara
|
|
12
|
+
# converted to Ruby by Hideo Nakamura, cxn03651@msj.biglobe.ne.jp
|
|
13
|
+
#
|
|
14
|
+
require 'nkf'
|
|
15
|
+
require 'forwardable'
|
|
16
|
+
require 'writeexcel/biffwriter'
|
|
17
|
+
require 'writeexcel/worksheet'
|
|
18
|
+
require 'writeexcel/chart'
|
|
19
|
+
require 'writeexcel/format'
|
|
20
|
+
require 'writeexcel/formula'
|
|
21
|
+
require 'writeexcel/olewriter'
|
|
22
|
+
require 'writeexcel/storage_lite'
|
|
23
|
+
require 'writeexcel/compatibility'
|
|
24
|
+
require 'writeexcel/shared_string_table'
|
|
25
|
+
require 'writeexcel/worksheets'
|
|
26
|
+
|
|
27
|
+
class Workbook < BIFFWriter
|
|
28
|
+
require 'writeexcel/properties'
|
|
29
|
+
require 'writeexcel/helper'
|
|
30
|
+
|
|
31
|
+
extend Forwardable
|
|
32
|
+
|
|
33
|
+
attr_reader :url_format, :parser, :tempdir, :date_1904
|
|
34
|
+
attr_reader :compatibility, :palette
|
|
35
|
+
attr_reader :ext_refs
|
|
36
|
+
attr_reader :worksheets
|
|
37
|
+
|
|
38
|
+
BOF = 12 # :nodoc:
|
|
39
|
+
EOF = 4 # :nodoc:
|
|
40
|
+
|
|
41
|
+
#
|
|
42
|
+
# _file_ is a filename (as string) or io object where to out spreadsheet data.
|
|
43
|
+
# you can set default format of workbook using _default_formats_.
|
|
44
|
+
#
|
|
45
|
+
# A new Excel workbook is created using the new() constructor which accepts
|
|
46
|
+
# either a filename or an IO object as a parameter. The following example
|
|
47
|
+
# creates a new Excel file based on a filename:
|
|
48
|
+
#
|
|
49
|
+
# workbook = WriteExcel.new('filename.xls')
|
|
50
|
+
# worksheet = workbook.add_worksheet
|
|
51
|
+
# worksheet.write(0, 0, 'Hi Excel!')
|
|
52
|
+
#
|
|
53
|
+
# Here are some other examples of using new():
|
|
54
|
+
#
|
|
55
|
+
# workbook1 = WriteExcel.new(filename)
|
|
56
|
+
# workbook2 = WriteExcel.new('/tmp/filename.xls')
|
|
57
|
+
# workbook3 = WriteExcel.new("c:\\tmp\\filename.xls")
|
|
58
|
+
# workbook4 = WriteExcel.new('c:\tmp\filename.xls')
|
|
59
|
+
#
|
|
60
|
+
# The last two examples demonstrates how to create a file on DOS or
|
|
61
|
+
# Windows where it is necessary to either escape the directory
|
|
62
|
+
# separator \ or to use single quotes to ensure that it isn't interpolated.
|
|
63
|
+
#
|
|
64
|
+
# The new() constructor returns a WriteExcel object that you can use to add
|
|
65
|
+
# worksheets and store data.
|
|
66
|
+
#
|
|
67
|
+
# If the file cannot be created, due to file permissions or some other reason,
|
|
68
|
+
# new will raise Exception Errno::EXXX.
|
|
69
|
+
#
|
|
70
|
+
# You can also pass a valid IO object to the new() constructor.:
|
|
71
|
+
# require 'stringio'
|
|
72
|
+
#
|
|
73
|
+
# io = StringIO.new
|
|
74
|
+
# workbook = WriteExcel.new(io) # After workbook.close, you can get excel data as io.string
|
|
75
|
+
#
|
|
76
|
+
# And, you can also pass default format properties.
|
|
77
|
+
#
|
|
78
|
+
# workbook = WriteExcel.new(filename, :font => 'Courier New', :size => 11)
|
|
79
|
+
#
|
|
80
|
+
def initialize(file, default_formats = {})
|
|
81
|
+
super()
|
|
82
|
+
@file = file
|
|
83
|
+
@default_formats = default_formats
|
|
84
|
+
@parser = Writeexcel::Formula.new(@byte_order)
|
|
85
|
+
@tempdir = nil
|
|
86
|
+
@date_1904 = false
|
|
87
|
+
@xf_index = 0
|
|
88
|
+
@biffsize = 0
|
|
89
|
+
@sheet_count = 0
|
|
90
|
+
@chart_count = 0
|
|
91
|
+
@codepage = 0x04E4
|
|
92
|
+
@country = 1
|
|
93
|
+
@worksheets = Worksheets.new
|
|
94
|
+
@formats = []
|
|
95
|
+
@palette = []
|
|
96
|
+
@biff_only = 0
|
|
97
|
+
|
|
98
|
+
@internal_fh = 0
|
|
99
|
+
@fh_out = ""
|
|
100
|
+
|
|
101
|
+
@shared_string_table = SharedStringTable.new
|
|
102
|
+
@extsst_offsets = [] # array of [global_offset, local_offset]
|
|
103
|
+
@extsst_buckets = 0
|
|
104
|
+
@extsst_bucket_size = 0
|
|
105
|
+
|
|
106
|
+
@ext_refs = {}
|
|
107
|
+
|
|
108
|
+
@mso_clusters = []
|
|
109
|
+
@mso_size = 0
|
|
110
|
+
|
|
111
|
+
@hideobj = false
|
|
112
|
+
|
|
113
|
+
@summary = ''
|
|
114
|
+
@doc_summary = ''
|
|
115
|
+
@localtime = Time.now
|
|
116
|
+
|
|
117
|
+
@defined_names = []
|
|
118
|
+
|
|
119
|
+
setup_built_in_formats(default_formats)
|
|
120
|
+
|
|
121
|
+
# Add the default format for hyperlinks
|
|
122
|
+
@url_format = add_format(:color => 'blue', :underline => 1)
|
|
123
|
+
|
|
124
|
+
if file.respond_to?(:to_str) && file != ''
|
|
125
|
+
@fh_out = open(file, "wb")
|
|
126
|
+
@internal_fh = 1
|
|
127
|
+
else
|
|
128
|
+
@fh_out = file
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# Set colour palette.
|
|
132
|
+
set_palette_xl97
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# return custom format from array at index
|
|
136
|
+
def format(index)
|
|
137
|
+
@formats[22+index]
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
#
|
|
141
|
+
# Calls finalization methods and explicitly close the OLEwriter files
|
|
142
|
+
# handle.
|
|
143
|
+
#
|
|
144
|
+
# An explicit close() is required if the file must be closed prior to performing
|
|
145
|
+
# some external action on it such as copying it, reading its size or attaching
|
|
146
|
+
# it to an email.
|
|
147
|
+
#
|
|
148
|
+
# In general, if you create a file with a size of 0 bytes or you fail to create
|
|
149
|
+
# a file you need to call close().
|
|
150
|
+
#
|
|
151
|
+
def close
|
|
152
|
+
return if fileclosed? # Prevent close() from being called twice.
|
|
153
|
+
|
|
154
|
+
@fileclosed = true
|
|
155
|
+
store_workbook
|
|
156
|
+
cleanup
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# get array of Worksheet objects
|
|
160
|
+
#
|
|
161
|
+
# :call-seq:
|
|
162
|
+
# sheets -> array of all Wordsheet object
|
|
163
|
+
# sheets(1, 3, 4) -> array of spcified Worksheet object.
|
|
164
|
+
#
|
|
165
|
+
# The sheets() method returns a array, or a sliced array, of the worksheets
|
|
166
|
+
# in a workbook.
|
|
167
|
+
#
|
|
168
|
+
# If no arguments are passed the method returns a list of all the worksheets
|
|
169
|
+
# in the workbook. This is useful if you want to repeat an operation on each
|
|
170
|
+
# worksheet:
|
|
171
|
+
#
|
|
172
|
+
# workbook.sheets.each do |worksheet|
|
|
173
|
+
# print worksheet.get_name
|
|
174
|
+
# end
|
|
175
|
+
#
|
|
176
|
+
# You can also specify a slice list to return one or more worksheet objects:
|
|
177
|
+
#
|
|
178
|
+
# worksheet = workbook.sheets(0)
|
|
179
|
+
# worksheet.write('A1', 'Hello')
|
|
180
|
+
#
|
|
181
|
+
# you can write the above example as:
|
|
182
|
+
#
|
|
183
|
+
# workbook.sheets(0).write('A1', 'Hello')
|
|
184
|
+
#
|
|
185
|
+
# The following example returns the first and last worksheet in a workbook:
|
|
186
|
+
#
|
|
187
|
+
# workbook.sheets(0, -1).each do |sheet|
|
|
188
|
+
# # Do something
|
|
189
|
+
# end
|
|
190
|
+
#
|
|
191
|
+
def sheets(*args)
|
|
192
|
+
if args.empty?
|
|
193
|
+
@worksheets
|
|
194
|
+
else
|
|
195
|
+
args.collect{|i| @worksheets[i] }
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
#
|
|
200
|
+
# Add a new worksheet to the Excel workbook.
|
|
201
|
+
#
|
|
202
|
+
# if _sheetname_ is UTF-16BE format, pass true as _name_utf16be_.
|
|
203
|
+
#
|
|
204
|
+
# At least one worksheet should be added to a new workbook. A worksheet is
|
|
205
|
+
# used to write data into cells:
|
|
206
|
+
#
|
|
207
|
+
# worksheet1 = workbook.add_worksheet # Sheet1
|
|
208
|
+
# worksheet2 = workbook.add_worksheet('Foglio2') # Foglio2
|
|
209
|
+
# worksheet3 = workbook.add_worksheet('Data') # Data
|
|
210
|
+
# worksheet4 = workbook.add_worksheet # Sheet4
|
|
211
|
+
#
|
|
212
|
+
# If _sheetname_ is not specified the default Excel convention will be followed,
|
|
213
|
+
# i.e. Sheet1, Sheet2, etc. The utf_16_be parameter is optional, see below.
|
|
214
|
+
#
|
|
215
|
+
# The worksheet name must be a valid Excel worksheet name, i.e. it cannot
|
|
216
|
+
# contain any of the following characters, [ ] : * ? / \
|
|
217
|
+
# and it must be less than 32 characters. In addition, you cannot use the same,
|
|
218
|
+
# case insensitive, _sheetname_ for more than one worksheet.
|
|
219
|
+
#
|
|
220
|
+
# This method will also handle strings in UTF-8 format.
|
|
221
|
+
#
|
|
222
|
+
# worksheet = workbook.add_worksheet("シート名")
|
|
223
|
+
#
|
|
224
|
+
# UTF-16BE worksheet names using an additional optional parameter:
|
|
225
|
+
#
|
|
226
|
+
# name = [0x263a].pack('n')
|
|
227
|
+
# worksheet = workbook.add_worksheet(name, true) # Smiley
|
|
228
|
+
#
|
|
229
|
+
def add_worksheet(sheetname = '', name_utf16be = false)
|
|
230
|
+
name, name_utf16be = check_sheetname(sheetname, name_utf16be)
|
|
231
|
+
|
|
232
|
+
init_data = [
|
|
233
|
+
self,
|
|
234
|
+
name,
|
|
235
|
+
name_utf16be
|
|
236
|
+
]
|
|
237
|
+
worksheet = Writeexcel::Worksheet.new(*init_data)
|
|
238
|
+
@worksheets << worksheet # Store ref for iterator
|
|
239
|
+
@parser.set_ext_sheets(name, worksheet.index) # Store names in Formula.rb
|
|
240
|
+
worksheet
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
#
|
|
244
|
+
# Create a chart for embedding or as as new sheet.
|
|
245
|
+
#
|
|
246
|
+
# This method is use to create a new chart either as a standalone worksheet
|
|
247
|
+
# (the default) or as an embeddable object that can be inserted into a
|
|
248
|
+
# worksheet via the insert_chart() Worksheet method.
|
|
249
|
+
#
|
|
250
|
+
# chart = workbook.add_chart(:type => 'Chart::Column')
|
|
251
|
+
#
|
|
252
|
+
# The properties that can be set are:
|
|
253
|
+
#
|
|
254
|
+
# :type (required)
|
|
255
|
+
# :name (optional)
|
|
256
|
+
# :name_utf16be (optional)
|
|
257
|
+
# :embedded (optional)
|
|
258
|
+
#
|
|
259
|
+
# * :type
|
|
260
|
+
#
|
|
261
|
+
# This is a required parameter. It defines the type of chart that will be created.
|
|
262
|
+
#
|
|
263
|
+
# chart = workbook.add_chart(:type => 'Chart::Line')
|
|
264
|
+
#
|
|
265
|
+
# The available types are:
|
|
266
|
+
#
|
|
267
|
+
# 'Chart::Column'
|
|
268
|
+
# 'Chart::Bar'
|
|
269
|
+
# 'Chart::Line'
|
|
270
|
+
# 'Chart::Area'
|
|
271
|
+
# 'Chart::Pie'
|
|
272
|
+
# 'Chart::Scatter'
|
|
273
|
+
# 'Chart::Stock'
|
|
274
|
+
#
|
|
275
|
+
# * :name
|
|
276
|
+
#
|
|
277
|
+
# Set the name for the chart sheet. The name property is optional and
|
|
278
|
+
# if it isn't supplied will default to Chart1 .. n. The name must be
|
|
279
|
+
# a valid Excel worksheet name. See add_worksheet() for more details
|
|
280
|
+
# on valid sheet names. The :name property can be omitted for embedded
|
|
281
|
+
# charts.
|
|
282
|
+
#
|
|
283
|
+
# chart = workbook.add_chart(
|
|
284
|
+
# :type => 'Chart::Line',
|
|
285
|
+
# :name => 'Results Chart'
|
|
286
|
+
# )
|
|
287
|
+
#
|
|
288
|
+
# * :name_utf16be
|
|
289
|
+
#
|
|
290
|
+
# if :name is UTF-16BE format, pass true as :name_utf16be
|
|
291
|
+
#
|
|
292
|
+
# * :encoding
|
|
293
|
+
#
|
|
294
|
+
# if :name is UTF-16BE format, pass 1 as :encoding.
|
|
295
|
+
# This key is obsolete in v0.7 or later. Use :name_utf16be instead.
|
|
296
|
+
#
|
|
297
|
+
# * :embedded
|
|
298
|
+
#
|
|
299
|
+
# Specifies true that the Chart object will be inserted in a worksheet via
|
|
300
|
+
# the insert_chart() Worksheet method. It is an error to try insert a
|
|
301
|
+
# Chart that doesn't have this flag set.
|
|
302
|
+
#
|
|
303
|
+
# chart = workbook.add_chart(:type => 'Chart::Line', :embedded => true)
|
|
304
|
+
#
|
|
305
|
+
# # Configure the chart.
|
|
306
|
+
# ...
|
|
307
|
+
#
|
|
308
|
+
# # Insert the chart into the a worksheet.
|
|
309
|
+
# worksheet.insert_chart('E2', chart)
|
|
310
|
+
#
|
|
311
|
+
# See WriteExcel::Chart for details on how to configure the
|
|
312
|
+
# chart object once it is created. See also the chart_*.rb programs in the
|
|
313
|
+
# examples directory of the distro.
|
|
314
|
+
#
|
|
315
|
+
def add_chart(properties)
|
|
316
|
+
name = ''
|
|
317
|
+
name_utf16be = false
|
|
318
|
+
|
|
319
|
+
# Type must be specified so we can create the required chart instance.
|
|
320
|
+
type = properties[:type]
|
|
321
|
+
raise "Must define chart type in add_chart()" if type.nil?
|
|
322
|
+
|
|
323
|
+
# Ensure that the chart defaults to non embedded.
|
|
324
|
+
embedded = properties[:embedded]
|
|
325
|
+
|
|
326
|
+
# Check the worksheet name for non-embedded charts.
|
|
327
|
+
unless embedded
|
|
328
|
+
properties[:name_utf16be] = true if properties[:encoding] == 1
|
|
329
|
+
name, name_utf16be =
|
|
330
|
+
check_sheetname(properties[:name], properties[:name_utf16be], true)
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
init_data = [
|
|
334
|
+
self,
|
|
335
|
+
name,
|
|
336
|
+
name_utf16be
|
|
337
|
+
]
|
|
338
|
+
|
|
339
|
+
chart = Writeexcel::Chart.factory(type, *init_data)
|
|
340
|
+
# If the chart isn't embedded let the workbook control it.
|
|
341
|
+
if !embedded
|
|
342
|
+
@worksheets << chart # Store ref for iterator
|
|
343
|
+
else
|
|
344
|
+
chart.set_embedded_config_data
|
|
345
|
+
end
|
|
346
|
+
chart
|
|
347
|
+
end
|
|
348
|
+
|
|
349
|
+
#
|
|
350
|
+
# Add an externally created chart.
|
|
351
|
+
#
|
|
352
|
+
# This method is use to include externally generated charts in a WriteExcel
|
|
353
|
+
# file.
|
|
354
|
+
#
|
|
355
|
+
# chart = workbook.add_chart_ext('chart01.bin', 'Chart1')
|
|
356
|
+
#
|
|
357
|
+
# This feature is semi-deprecated in favour of the "native" charts created
|
|
358
|
+
# using add_chart(). Read external_charts.txt in the external_charts
|
|
359
|
+
# directory of the distro for a full explanation.
|
|
360
|
+
#
|
|
361
|
+
def add_chart_ext(filename, chartname, name_utf16be = false)
|
|
362
|
+
type = 'extarnal'
|
|
363
|
+
|
|
364
|
+
name, name_utf16be = check_sheetname(chartname, name_utf16be)
|
|
365
|
+
|
|
366
|
+
init_data = [
|
|
367
|
+
filename,
|
|
368
|
+
name,
|
|
369
|
+
name_utf16be
|
|
370
|
+
]
|
|
371
|
+
|
|
372
|
+
chart = Writeexcel::Chart.factory(self, type, init_data)
|
|
373
|
+
@worksheets << chart # Store ref for iterator
|
|
374
|
+
chart
|
|
375
|
+
end
|
|
376
|
+
|
|
377
|
+
#
|
|
378
|
+
# The add_format method can be used to create new Format objects which are
|
|
379
|
+
# used to apply formatting to a cell. You can either define the properties
|
|
380
|
+
# at creation time via a hash of property values or later via method calls.
|
|
381
|
+
#
|
|
382
|
+
# format1 = workbook.add_format(props) # Set properties at creation
|
|
383
|
+
# format2 = workbook.add_format # Set properties later
|
|
384
|
+
#
|
|
385
|
+
# See the "CELL FORMATTING" section for more details about Format properties and how to set them.
|
|
386
|
+
#
|
|
387
|
+
def add_format(*args)
|
|
388
|
+
fmts = {}
|
|
389
|
+
args.each { |arg| fmts = fmts.merge(arg) }
|
|
390
|
+
format = Writeexcel::Format.new(@xf_index, @default_formats.merge(fmts))
|
|
391
|
+
@xf_index += 1
|
|
392
|
+
@formats.push format # Store format reference
|
|
393
|
+
format
|
|
394
|
+
end
|
|
395
|
+
|
|
396
|
+
#
|
|
397
|
+
# Set the compatibility mode.
|
|
398
|
+
#
|
|
399
|
+
# This method is used to improve compatibility with third party
|
|
400
|
+
# applications that read Excel files.
|
|
401
|
+
#
|
|
402
|
+
# workbook.compatibility_mode
|
|
403
|
+
#
|
|
404
|
+
# An Excel file is comprised of binary records that describe properties of
|
|
405
|
+
# a spreadsheet. Excel is reasonably liberal about this and, outside of a
|
|
406
|
+
# core subset, it doesn't require every possible record to be present when
|
|
407
|
+
# it reads a file. This is also true of Gnumeric and OpenOffice.Org Calc.
|
|
408
|
+
#
|
|
409
|
+
# WriteExcel takes advantage of this fact to omit some records in order to
|
|
410
|
+
# minimise the amount of data stored in memory and to simplify and speed up
|
|
411
|
+
# the writing of files. However, some third party applications that read
|
|
412
|
+
# Excel files often expect certain records to be present. In
|
|
413
|
+
# "compatibility mode" WriteExcel writes these records and tries to be as
|
|
414
|
+
# close to an Excel generated file as possible.
|
|
415
|
+
#
|
|
416
|
+
# Applications that require compatibility_mode() are Apache POI,
|
|
417
|
+
# Apple Numbers, and Quickoffice on Nokia, Palm and other devices. You should
|
|
418
|
+
# also use compatibility_mode() if your Excel file will be used as an external
|
|
419
|
+
# data source by another Excel file.
|
|
420
|
+
#
|
|
421
|
+
# If you encounter other situations that require compatibility_mode(),
|
|
422
|
+
# please let me know.
|
|
423
|
+
#
|
|
424
|
+
# It should be noted that compatibility_mode() requires additional data to be
|
|
425
|
+
# stored in memory and additional processing. This incurs a memory and speed
|
|
426
|
+
# penalty and may not be suitable for very large files (>20MB).
|
|
427
|
+
#
|
|
428
|
+
# You must call compatibility_mode() before calling add_worksheet().
|
|
429
|
+
#
|
|
430
|
+
#
|
|
431
|
+
# Excel doesn't require every possible Biff record to be present in a file.
|
|
432
|
+
# In particular if the indexing records INDEX, ROW and DBCELL aren't present
|
|
433
|
+
# it just ignores the fact and reads the cells anyway. This is also true of
|
|
434
|
+
# the EXTSST record. Gnumeric and OOo also take this approach. This allows
|
|
435
|
+
# WriteExcel to ignore these records in order to minimise the amount of data
|
|
436
|
+
# stored in memory. However, other third party applications that read Excel
|
|
437
|
+
# files often expect these records to be present. In "compatibility mode"
|
|
438
|
+
# WriteExcel writes these records and tries to be as close to an Excel
|
|
439
|
+
# generated file as possible.
|
|
440
|
+
#
|
|
441
|
+
# This requires additional data to be stored in memory until the file is
|
|
442
|
+
# about to be written. This incurs a memory and speed penalty and may not be
|
|
443
|
+
# suitable for very large files.
|
|
444
|
+
#
|
|
445
|
+
def compatibility_mode(mode = true)
|
|
446
|
+
unless sheets.empty?
|
|
447
|
+
raise "compatibility_mode() must be called before add_worksheet()"
|
|
448
|
+
end
|
|
449
|
+
@compatibility = mode
|
|
450
|
+
end
|
|
451
|
+
|
|
452
|
+
#
|
|
453
|
+
# Set the date system: false = 1900 (the default), true = 1904
|
|
454
|
+
#
|
|
455
|
+
# Excel stores dates as real numbers where the integer part stores the
|
|
456
|
+
# number of days since the epoch and the fractional part stores the
|
|
457
|
+
# percentage of the day. The epoch can be either 1900 or 1904. Excel for
|
|
458
|
+
# Windows uses 1900 and Excel for Macintosh uses 1904. However, Excel on
|
|
459
|
+
# either platform will convert automatically between one system and
|
|
460
|
+
# the other.
|
|
461
|
+
#
|
|
462
|
+
# WriteExcel stores dates in the 1900 format by default. If you wish to
|
|
463
|
+
# change this you can call the set_1904() workbook method. You can query
|
|
464
|
+
# the current value by calling the get_1904() workbook method. This returns
|
|
465
|
+
# false for 1900 and true for 1904.
|
|
466
|
+
#
|
|
467
|
+
# See also "DATES AND TIME IN EXCEL" for more information about working
|
|
468
|
+
# with Excel's date system.
|
|
469
|
+
#
|
|
470
|
+
# In general you probably won't need to use set_1904().
|
|
471
|
+
#
|
|
472
|
+
def set_1904(mode = true)
|
|
473
|
+
unless sheets.empty?
|
|
474
|
+
raise "set_1904() must be called before add_worksheet()"
|
|
475
|
+
end
|
|
476
|
+
@date_1904 = (!mode || mode == 0) ? false : true
|
|
477
|
+
end
|
|
478
|
+
|
|
479
|
+
def get_1904
|
|
480
|
+
@date_1904
|
|
481
|
+
end
|
|
482
|
+
|
|
483
|
+
#
|
|
484
|
+
# Change the RGB components of the elements in the colour palette.
|
|
485
|
+
#
|
|
486
|
+
# The set_custom_color() method can be used to override one of the built-in
|
|
487
|
+
# palette values with a more suitable colour.
|
|
488
|
+
#
|
|
489
|
+
# The value for _index_ should be in the range 8..63, see "COLOURS IN EXCEL".
|
|
490
|
+
#
|
|
491
|
+
# The default named colours use the following indices:
|
|
492
|
+
#
|
|
493
|
+
# 8 => black
|
|
494
|
+
# 9 => white
|
|
495
|
+
# 10 => red
|
|
496
|
+
# 11 => lime
|
|
497
|
+
# 12 => blue
|
|
498
|
+
# 13 => yellow
|
|
499
|
+
# 14 => magenta
|
|
500
|
+
# 15 => cyan
|
|
501
|
+
# 16 => brown
|
|
502
|
+
# 17 => green
|
|
503
|
+
# 18 => navy
|
|
504
|
+
# 20 => purple
|
|
505
|
+
# 22 => silver
|
|
506
|
+
# 23 => gray
|
|
507
|
+
# 33 => pink
|
|
508
|
+
# 53 => orange
|
|
509
|
+
#
|
|
510
|
+
# A new colour is set using its RGB (red green blue) components. The red,
|
|
511
|
+
# green and blue values must be in the range 0..255. You can determine the
|
|
512
|
+
# required values in Excel using the Tools->Options->Colors->Modify dialog.
|
|
513
|
+
#
|
|
514
|
+
# The set_custom_color() workbook method can also be used with a HTML style
|
|
515
|
+
# #rrggbb hex value:
|
|
516
|
+
#
|
|
517
|
+
# workbook.set_custom_color(40, 255, 102, 0 ) # Orange
|
|
518
|
+
# workbook.set_custom_color(40, 0xFF, 0x66, 0x00) # Same thing
|
|
519
|
+
# workbook.set_custom_color(40, '#FF6600' ) # Same thing
|
|
520
|
+
#
|
|
521
|
+
# font = workbook.add_format(:color => 40) # Use the modified colour
|
|
522
|
+
#
|
|
523
|
+
# The return value from set_custom_color() is the index of the colour that
|
|
524
|
+
# was changed:
|
|
525
|
+
#
|
|
526
|
+
# ferrari = workbook.set_custom_color(40, 216, 12, 12)
|
|
527
|
+
#
|
|
528
|
+
# format = workbook.add_format(
|
|
529
|
+
# :bg_color => $ferrari,
|
|
530
|
+
# :pattern => 1,
|
|
531
|
+
# :border => 1
|
|
532
|
+
# )
|
|
533
|
+
#
|
|
534
|
+
def set_custom_color(index, red = nil, green = nil, blue = nil)
|
|
535
|
+
# Match a HTML #xxyyzz style parameter
|
|
536
|
+
if !red.nil? && red =~ /^#(\w\w)(\w\w)(\w\w)/
|
|
537
|
+
red = $1.hex
|
|
538
|
+
green = $2.hex
|
|
539
|
+
blue = $3.hex
|
|
540
|
+
end
|
|
541
|
+
|
|
542
|
+
# Check that the colour index is the right range
|
|
543
|
+
if index < 8 || index > 64
|
|
544
|
+
raise "Color index #{index} outside range: 8 <= index <= 64"
|
|
545
|
+
end
|
|
546
|
+
|
|
547
|
+
# Check that the colour components are in the right range
|
|
548
|
+
if (red < 0 || red > 255) ||
|
|
549
|
+
(green < 0 || green > 255) ||
|
|
550
|
+
(blue < 0 || blue > 255)
|
|
551
|
+
raise "Color component outside range: 0 <= color <= 255"
|
|
552
|
+
end
|
|
553
|
+
|
|
554
|
+
index -=8 # Adjust colour index (wingless dragonfly)
|
|
555
|
+
|
|
556
|
+
# Set the RGB value
|
|
557
|
+
@palette[index] = [red, green, blue, 0]
|
|
558
|
+
|
|
559
|
+
index + 8
|
|
560
|
+
end
|
|
561
|
+
|
|
562
|
+
#
|
|
563
|
+
# Change the default temp directory
|
|
564
|
+
#
|
|
565
|
+
# For speed and efficiency WriteExcel stores worksheet data in temporary
|
|
566
|
+
# files prior to assembling the final workbook.
|
|
567
|
+
#
|
|
568
|
+
# If WriteExcel is unable to create these temporary files it will store
|
|
569
|
+
# the required data in memory. This can be slow for large files.
|
|
570
|
+
#
|
|
571
|
+
# The problem occurs mainly with IIS on Windows although it could feasibly
|
|
572
|
+
# occur on Unix systems as well. The problem generally occurs because the
|
|
573
|
+
# default temp file directory is defined as C:/ or some other directory that
|
|
574
|
+
# IIS doesn't provide write access to.
|
|
575
|
+
#
|
|
576
|
+
# To check if this might be a problem on a particular system you can run a
|
|
577
|
+
# simple test program with -w or use warnings. This will generate a warning
|
|
578
|
+
# if the module cannot create the required temporary files:
|
|
579
|
+
#
|
|
580
|
+
# #!/usr/bin/ruby -w
|
|
581
|
+
#
|
|
582
|
+
# require 'WriteExcel'
|
|
583
|
+
#
|
|
584
|
+
# workbook = WriteExcel.new('test.xls')
|
|
585
|
+
# worksheet = workbook.add_worksheet
|
|
586
|
+
# workbook.close
|
|
587
|
+
#
|
|
588
|
+
# To avoid this problem the set_tempdir() method can be used to specify a
|
|
589
|
+
# directory that is accessible for the creation of temporary files.
|
|
590
|
+
#
|
|
591
|
+
# Even if the default temporary file directory is accessible you may wish
|
|
592
|
+
# to specify an alternative location for security or maintenance reasons:
|
|
593
|
+
#
|
|
594
|
+
# workbook.set_tempdir('/tmp/writeexcel')
|
|
595
|
+
# workbook.set_tempdir('c:\windows\temp\writeexcel')
|
|
596
|
+
#
|
|
597
|
+
# The directory for the temporary file must exist, set_tempdir() will not
|
|
598
|
+
# create a new directory.
|
|
599
|
+
#
|
|
600
|
+
# One disadvantage of using the set_tempdir() method is that on some Windows
|
|
601
|
+
# systems it will limit you to approximately 800 concurrent tempfiles. This
|
|
602
|
+
# means that a single program running on one of these systems will be limited
|
|
603
|
+
# to creating a total of 800 workbook and worksheet objects. You can run
|
|
604
|
+
# multiple, non-concurrent programs to work around this if necessary.
|
|
605
|
+
#
|
|
606
|
+
def set_tempdir(dir = '')
|
|
607
|
+
raise "#{dir} is not a valid directory" if dir != '' && !FileTest.directory?(dir)
|
|
608
|
+
raise "set_tempdir must be called before add_worksheet" unless sheets.empty?
|
|
609
|
+
|
|
610
|
+
@tempdir = dir
|
|
611
|
+
end
|
|
612
|
+
|
|
613
|
+
#
|
|
614
|
+
# The default code page or character set used by WriteExcel is ANSI. This is
|
|
615
|
+
# also the default used by Excel for Windows. Occasionally however it may be
|
|
616
|
+
# necessary to change the code page via the set_codepage() method.
|
|
617
|
+
#
|
|
618
|
+
# Changing the code page may be required if your are using WriteExcel on the
|
|
619
|
+
# Macintosh and you are using characters outside the ASCII 128 character set:
|
|
620
|
+
#
|
|
621
|
+
# workbook.set_codepage(1) # ANSI, MS Windows
|
|
622
|
+
# workbook.set_codepage(2) # Apple Macintosh
|
|
623
|
+
#
|
|
624
|
+
# The set_codepage() method is rarely required.
|
|
625
|
+
#
|
|
626
|
+
def set_codepage(type = 1)
|
|
627
|
+
if type == 2
|
|
628
|
+
@codepage = 0x8000
|
|
629
|
+
else
|
|
630
|
+
@codepage = 0x04E4
|
|
631
|
+
end
|
|
632
|
+
end
|
|
633
|
+
|
|
634
|
+
#
|
|
635
|
+
# store the country code.
|
|
636
|
+
#
|
|
637
|
+
# Some non-english versions of Excel may need this set to some value other
|
|
638
|
+
# than 1 = "United States". In general the country code is equal to the
|
|
639
|
+
# international dialling code.
|
|
640
|
+
#
|
|
641
|
+
def set_country(code = 1)
|
|
642
|
+
@country = code
|
|
643
|
+
end
|
|
644
|
+
|
|
645
|
+
#
|
|
646
|
+
# This method is used to defined a name that can be used to represent a
|
|
647
|
+
# value, a single cell or a range of cells in a workbook.
|
|
648
|
+
#
|
|
649
|
+
# workbook.define_name('Exchange_rate', '=0.96')
|
|
650
|
+
# workbook.define_name('Sales', '=Sheet1!$G$1:$H$10')
|
|
651
|
+
# workbook.define_name('Sheet2!Sales', '=Sheet2!$G$1:$G$10')
|
|
652
|
+
#
|
|
653
|
+
# See the defined_name.rb program in the examples dir of the distro.
|
|
654
|
+
#
|
|
655
|
+
# Note: This currently a beta feature. More documentation and examples
|
|
656
|
+
# will be added.
|
|
657
|
+
#
|
|
658
|
+
def define_name(name, formula, encoding = 0)
|
|
659
|
+
sheet_index = 0
|
|
660
|
+
full_name = name.downcase
|
|
661
|
+
|
|
662
|
+
if name =~ /^(.*)!(.*)$/
|
|
663
|
+
sheetname = $1
|
|
664
|
+
name = $2
|
|
665
|
+
sheet_index = 1 + @parser.get_sheet_index(sheetname)
|
|
666
|
+
end
|
|
667
|
+
|
|
668
|
+
# Strip the = sign at the beginning of the formula string
|
|
669
|
+
formula = formula.sub(/^=/, '')
|
|
670
|
+
|
|
671
|
+
# Parse the formula using the parser in Formula.pm
|
|
672
|
+
parser = @parser
|
|
673
|
+
|
|
674
|
+
# In order to raise formula errors from the point of view of the calling
|
|
675
|
+
# program we use an eval block and re-raise the error from here.
|
|
676
|
+
#
|
|
677
|
+
tokens = parser.parse_formula(formula)
|
|
678
|
+
|
|
679
|
+
# Force 2d ranges to be a reference class.
|
|
680
|
+
tokens.collect! { |t| t.gsub(/_ref3d/, '_ref3dR') }
|
|
681
|
+
tokens.collect! { |t| t.gsub(/_range3d/, '_range3dR') }
|
|
682
|
+
|
|
683
|
+
# Parse the tokens into a formula string.
|
|
684
|
+
formula = parser.parse_tokens(tokens)
|
|
685
|
+
|
|
686
|
+
defined_names.push(
|
|
687
|
+
{
|
|
688
|
+
:name => name,
|
|
689
|
+
:encoding => encoding,
|
|
690
|
+
:sheet_index => sheet_index,
|
|
691
|
+
:formula => formula
|
|
692
|
+
}
|
|
693
|
+
)
|
|
694
|
+
|
|
695
|
+
index = defined_names.size
|
|
696
|
+
|
|
697
|
+
parser.set_ext_name(name, index)
|
|
698
|
+
end
|
|
699
|
+
|
|
700
|
+
#
|
|
701
|
+
# Set the document properties such as Title, Author etc. These are written to
|
|
702
|
+
# property sets in the OLE container.
|
|
703
|
+
#
|
|
704
|
+
# The set_properties method can be used to set the document properties of
|
|
705
|
+
# the Excel file created by WriteExcel. These properties are visible when you
|
|
706
|
+
# use the File->Properties menu option in Excel and are also available to
|
|
707
|
+
# external applications that read or index windows files.
|
|
708
|
+
#
|
|
709
|
+
# The properties should be passed as a hash of values as follows:
|
|
710
|
+
#
|
|
711
|
+
# workbook.set_properties(
|
|
712
|
+
# :title => 'This is an example spreadsheet',
|
|
713
|
+
# :author => 'cxn03651',
|
|
714
|
+
# :comments => 'Created with Ruby and WriteExcel',
|
|
715
|
+
# )
|
|
716
|
+
#
|
|
717
|
+
# The properties that can be set are:
|
|
718
|
+
#
|
|
719
|
+
# * :title
|
|
720
|
+
# * :subject
|
|
721
|
+
# * :author
|
|
722
|
+
# * :manager
|
|
723
|
+
# * :company
|
|
724
|
+
# * :category
|
|
725
|
+
# * :keywords
|
|
726
|
+
# * :comments
|
|
727
|
+
#
|
|
728
|
+
# User defined properties are not supported due to effort required.
|
|
729
|
+
#
|
|
730
|
+
# You can also pass UTF-8 strings as properties.
|
|
731
|
+
#
|
|
732
|
+
# workbook.set_properties(
|
|
733
|
+
# :subject => "住所録"
|
|
734
|
+
# )
|
|
735
|
+
#
|
|
736
|
+
# Usually WriteExcel allows you to use UTF-16. However, document properties
|
|
737
|
+
# don't support UTF-16 for these type of strings.
|
|
738
|
+
#
|
|
739
|
+
# In order to promote the usefulness of Ruby and the WriteExcel module
|
|
740
|
+
# consider adding a comment such as the following when using document
|
|
741
|
+
# properties:
|
|
742
|
+
#
|
|
743
|
+
# workbook.set_properties(
|
|
744
|
+
# ...,
|
|
745
|
+
# :comments => 'Created with Ruby and writeexcel',
|
|
746
|
+
# ...,
|
|
747
|
+
# )
|
|
748
|
+
#
|
|
749
|
+
# See also the properties.rb program in the examples directory of the distro.
|
|
750
|
+
#
|
|
751
|
+
def set_properties(params)
|
|
752
|
+
# Ignore if no args were passed.
|
|
753
|
+
return -1 if !params.respond_to?(:to_hash) || params.empty?
|
|
754
|
+
|
|
755
|
+
params.each do |k, v|
|
|
756
|
+
params[k] = convert_to_ascii_if_ascii(v) if v.respond_to?(:to_str)
|
|
757
|
+
end
|
|
758
|
+
|
|
759
|
+
# Check for valid input parameters.
|
|
760
|
+
check_valid_params_for_properties(params)
|
|
761
|
+
|
|
762
|
+
# Set the creation time unless specified by the user.
|
|
763
|
+
params[:created] = @localtime unless params.has_key?(:created)
|
|
764
|
+
|
|
765
|
+
#
|
|
766
|
+
# Create the SummaryInformation property set.
|
|
767
|
+
#
|
|
768
|
+
|
|
769
|
+
# Get the codepage of the strings in the property set.
|
|
770
|
+
properties = [:title, :subject, :author, :keywords, :comments, :last_author]
|
|
771
|
+
params[:codepage] = get_property_set_codepage(params, properties)
|
|
772
|
+
|
|
773
|
+
# Create an array of property set values.
|
|
774
|
+
properties.unshift(:codepage)
|
|
775
|
+
properties.push(:created)
|
|
776
|
+
|
|
777
|
+
# Pack the property sets.
|
|
778
|
+
@summary =
|
|
779
|
+
create_summary_property_set(property_sets(properties, params))
|
|
780
|
+
|
|
781
|
+
#
|
|
782
|
+
# Create the DocSummaryInformation property set.
|
|
783
|
+
#
|
|
784
|
+
|
|
785
|
+
# Get the codepage of the strings in the property set.
|
|
786
|
+
properties = [:category, :manager, :company]
|
|
787
|
+
params[:codepage] = get_property_set_codepage(params, properties)
|
|
788
|
+
|
|
789
|
+
# Create an array of property set values.
|
|
790
|
+
properties.unshift(:codepage)
|
|
791
|
+
|
|
792
|
+
# Pack the property sets.
|
|
793
|
+
@doc_summary =
|
|
794
|
+
create_doc_summary_property_set(property_sets(properties, params))
|
|
795
|
+
|
|
796
|
+
# Set a flag for when the files is written.
|
|
797
|
+
@add_doc_properties = true
|
|
798
|
+
end
|
|
799
|
+
|
|
800
|
+
def extsst_buckets # :nodoc:
|
|
801
|
+
@extsst_buckets
|
|
802
|
+
end
|
|
803
|
+
|
|
804
|
+
def extsst_bucket_size # :nodoc:
|
|
805
|
+
@extsst_bucket_size
|
|
806
|
+
end
|
|
807
|
+
|
|
808
|
+
def biff_only=(val) # :nodoc:
|
|
809
|
+
@biff_only = val
|
|
810
|
+
end
|
|
811
|
+
|
|
812
|
+
def summary # :nodoc:
|
|
813
|
+
@summary
|
|
814
|
+
end
|
|
815
|
+
|
|
816
|
+
def localtime=(val) # :nodoc:
|
|
817
|
+
@localtime = val
|
|
818
|
+
end
|
|
819
|
+
|
|
820
|
+
def update_str_table(str)
|
|
821
|
+
@shared_string_table << str
|
|
822
|
+
end
|
|
823
|
+
|
|
824
|
+
#==========================================
|
|
825
|
+
# Internal method
|
|
826
|
+
#==========================================
|
|
827
|
+
|
|
828
|
+
private
|
|
829
|
+
|
|
830
|
+
# Add the in-built style formats and the default cell format.
|
|
831
|
+
def setup_built_in_formats(default_formats) # :nodoc:
|
|
832
|
+
add_format(:type => 1) # 0 Normal
|
|
833
|
+
add_format(:type => 1) # 1 RowLevel 1
|
|
834
|
+
add_format(:type => 1) # 2 RowLevel 2
|
|
835
|
+
add_format(:type => 1) # 3 RowLevel 3
|
|
836
|
+
add_format(:type => 1) # 4 RowLevel 4
|
|
837
|
+
add_format(:type => 1) # 5 RowLevel 5
|
|
838
|
+
add_format(:type => 1) # 6 RowLevel 6
|
|
839
|
+
add_format(:type => 1) # 7 RowLevel 7
|
|
840
|
+
add_format(:type => 1) # 8 ColLevel 1
|
|
841
|
+
add_format(:type => 1) # 9 ColLevel 2
|
|
842
|
+
add_format(:type => 1) # 10 ColLevel 3
|
|
843
|
+
add_format(:type => 1) # 11 ColLevel 4
|
|
844
|
+
add_format(:type => 1) # 12 ColLevel 5
|
|
845
|
+
add_format(:type => 1) # 13 ColLevel 6
|
|
846
|
+
add_format(:type => 1) # 14 ColLevel 7
|
|
847
|
+
add_format(default_formats) # 15 Cell XF
|
|
848
|
+
add_format(:type => 1, :num_format => 0x2B) # 16 Comma
|
|
849
|
+
add_format(:type => 1, :num_format => 0x29) # 17 Comma[0]
|
|
850
|
+
add_format(:type => 1, :num_format => 0x2C) # 18 Currency
|
|
851
|
+
add_format(:type => 1, :num_format => 0x2A) # 19 Currency[0]
|
|
852
|
+
add_format(:type => 1, :num_format => 0x09) # 20 Percent
|
|
853
|
+
end
|
|
854
|
+
|
|
855
|
+
def fileclosed?
|
|
856
|
+
@fileclosed || false
|
|
857
|
+
end
|
|
858
|
+
|
|
859
|
+
#
|
|
860
|
+
# Check for valid worksheet names. We check the length, if it contains any
|
|
861
|
+
# invalid characters and if the name is unique in the workbook.
|
|
862
|
+
#
|
|
863
|
+
def check_sheetname(name, name_utf16be = false, chart = nil) #:nodoc:
|
|
864
|
+
# name_utf16be parameter may set 0/1 in v0.6.6 or earlier
|
|
865
|
+
name_utf16be = false if name_utf16be == 0
|
|
866
|
+
name_utf16be = true if name_utf16be == 1
|
|
867
|
+
|
|
868
|
+
increment_sheet_chart_count(chart)
|
|
869
|
+
if name.nil? || name == ""
|
|
870
|
+
name_utf16be = false
|
|
871
|
+
name = default_sheet_chart_name(chart)
|
|
872
|
+
end
|
|
873
|
+
|
|
874
|
+
ruby_19 { name = convert_to_ascii_if_ascii(name) }
|
|
875
|
+
check_sheetname_length(name, name_utf16be)
|
|
876
|
+
check_sheetname_even(name) if name_utf16be
|
|
877
|
+
check_sheetname_valid_chars(name, name_utf16be)
|
|
878
|
+
|
|
879
|
+
# Handle utf8 strings
|
|
880
|
+
if is_utf8?(name)
|
|
881
|
+
name = utf8_to_16be(name)
|
|
882
|
+
name_utf16be = true
|
|
883
|
+
end
|
|
884
|
+
|
|
885
|
+
check_sheetname_uniq(name, name_utf16be)
|
|
886
|
+
[name, name_utf16be]
|
|
887
|
+
end
|
|
888
|
+
|
|
889
|
+
# Increment the Sheet/Chart number used for default sheet names below.
|
|
890
|
+
def increment_sheet_chart_count(chart)
|
|
891
|
+
if chart
|
|
892
|
+
@chart_count += 1
|
|
893
|
+
else
|
|
894
|
+
@sheet_count += 1
|
|
895
|
+
end
|
|
896
|
+
end
|
|
897
|
+
|
|
898
|
+
# Supply default Sheet/Chart name if none has been defined.
|
|
899
|
+
def default_sheet_chart_name(chart)
|
|
900
|
+
if chart
|
|
901
|
+
"Chart#{@chart_count}"
|
|
902
|
+
else
|
|
903
|
+
"Sheet#{@sheet_count}"
|
|
904
|
+
end
|
|
905
|
+
end
|
|
906
|
+
|
|
907
|
+
def check_sheetname_length(name, name_utf16be) #:nodoc:
|
|
908
|
+
# Check that sheetname is <= 31 (1 or 2 byte chars). Excel limit.
|
|
909
|
+
limit = name_utf16be ? 62 : 100 #31 Increased length limit of sheet name to 100
|
|
910
|
+
raise "Sheetname $name must be <= 100 chars" if name.bytesize > limit
|
|
911
|
+
end
|
|
912
|
+
|
|
913
|
+
def check_sheetname_even(name) #:nodoc:
|
|
914
|
+
# Check that Unicode sheetname has an even number of bytes
|
|
915
|
+
if (name.bytesize % 2 != 0)
|
|
916
|
+
raise "Odd number of bytes in Unicode worksheet name: #{name}"
|
|
917
|
+
end
|
|
918
|
+
end
|
|
919
|
+
|
|
920
|
+
def check_sheetname_valid_chars(name, name_utf16be) #:nodoc:
|
|
921
|
+
# Check that sheetname doesn't contain any invalid characters
|
|
922
|
+
invalid_char = %r![\[\]:*?/\\]!
|
|
923
|
+
if !name_utf16be && name =~ invalid_char
|
|
924
|
+
# Check ASCII names
|
|
925
|
+
raise "Invalid character []:*?/\\ in worksheet name: #{name}"
|
|
926
|
+
else
|
|
927
|
+
# Extract any 8bit clean chars from the UTF16 name and validate them.
|
|
928
|
+
str = name.dup
|
|
929
|
+
while str =~ /../m
|
|
930
|
+
hi, lo = $~[0].unpack('aa')
|
|
931
|
+
if hi == "\0" and lo =~ invalid_char
|
|
932
|
+
raise 'Invalid character []:*?/\\ in worksheet name: ' + name
|
|
933
|
+
end
|
|
934
|
+
str = $~.post_match
|
|
935
|
+
end
|
|
936
|
+
end
|
|
937
|
+
end
|
|
938
|
+
|
|
939
|
+
# Check that the worksheet name doesn't already exist since this is a fatal
|
|
940
|
+
# error in Excel 97. The check must also exclude case insensitive matches
|
|
941
|
+
# since the names 'Sheet1' and 'sheet1' are equivalent. The tests also have
|
|
942
|
+
# to take the name_utf16be into account.
|
|
943
|
+
#
|
|
944
|
+
def check_sheetname_uniq(name, name_utf16be) #:nodoc:
|
|
945
|
+
@worksheets.each do |worksheet|
|
|
946
|
+
name_a = name
|
|
947
|
+
encd_a = name_utf16be
|
|
948
|
+
name_b = worksheet.name
|
|
949
|
+
encd_b = worksheet.is_name_utf16be?
|
|
950
|
+
error = false
|
|
951
|
+
|
|
952
|
+
if !encd_a and !encd_b
|
|
953
|
+
error = (name_a.downcase == name_b.downcase)
|
|
954
|
+
elsif !encd_a and encd_b
|
|
955
|
+
name_a = ascii_to_16be(name_a)
|
|
956
|
+
error = (name_a.downcase == name_b.downcase)
|
|
957
|
+
elsif encd_a and !encd_b
|
|
958
|
+
name_b = ascii_to_16be(name_b)
|
|
959
|
+
error = (name_a.downcase == name_b.downcase)
|
|
960
|
+
elsif encd_a and encd_b
|
|
961
|
+
error = (name_a.downcase == name_b.downcase)
|
|
962
|
+
# TODO : not converted yet.
|
|
963
|
+
|
|
964
|
+
# We can't easily do a case insensitive test of the UTF16 names.
|
|
965
|
+
# As a special case we check if all of the high bytes are nulls and
|
|
966
|
+
# then do an ASCII style case insensitive test.
|
|
967
|
+
#
|
|
968
|
+
# Strip out the high bytes (funkily).
|
|
969
|
+
# my $hi_a = grep {ord} $name_a =~ /(.)./sg;
|
|
970
|
+
# my $hi_b = grep {ord} $name_b =~ /(.)./sg;
|
|
971
|
+
#
|
|
972
|
+
# if ($hi_a or $hi_b) {
|
|
973
|
+
# $error = 1 if $name_a eq $name_b;
|
|
974
|
+
# }
|
|
975
|
+
# else {
|
|
976
|
+
# $error = 1 if lc($name_a) eq lc($name_b);
|
|
977
|
+
# }
|
|
978
|
+
end
|
|
979
|
+
if error
|
|
980
|
+
raise "Worksheet name '#{name}', with case ignored, is already in use"
|
|
981
|
+
end
|
|
982
|
+
end
|
|
983
|
+
end
|
|
984
|
+
|
|
985
|
+
#
|
|
986
|
+
# Sets the colour palette to the Excel 97+ default.
|
|
987
|
+
#
|
|
988
|
+
def set_palette_xl97 #:nodoc:
|
|
989
|
+
@palette = [
|
|
990
|
+
[0x00, 0x00, 0x00, 0x00], # 8
|
|
991
|
+
[0xff, 0xff, 0xff, 0x00], # 9
|
|
992
|
+
[0xff, 0x00, 0x00, 0x00], # 10
|
|
993
|
+
[0x00, 0xff, 0x00, 0x00], # 11
|
|
994
|
+
[0x00, 0x00, 0xff, 0x00], # 12
|
|
995
|
+
[0xff, 0xff, 0x00, 0x00], # 13
|
|
996
|
+
[0xff, 0x00, 0xff, 0x00], # 14
|
|
997
|
+
[0x00, 0xff, 0xff, 0x00], # 15
|
|
998
|
+
[0x80, 0x00, 0x00, 0x00], # 16
|
|
999
|
+
[0x00, 0x80, 0x00, 0x00], # 17
|
|
1000
|
+
[0x00, 0x00, 0x80, 0x00], # 18
|
|
1001
|
+
[0x80, 0x80, 0x00, 0x00], # 19
|
|
1002
|
+
[0x80, 0x00, 0x80, 0x00], # 20
|
|
1003
|
+
[0x00, 0x80, 0x80, 0x00], # 21
|
|
1004
|
+
[0xc0, 0xc0, 0xc0, 0x00], # 22
|
|
1005
|
+
[0x80, 0x80, 0x80, 0x00], # 23
|
|
1006
|
+
[0x99, 0x99, 0xff, 0x00], # 24
|
|
1007
|
+
[0x99, 0x33, 0x66, 0x00], # 25
|
|
1008
|
+
[0xff, 0xff, 0xcc, 0x00], # 26
|
|
1009
|
+
[0xcc, 0xff, 0xff, 0x00], # 27
|
|
1010
|
+
[0x66, 0x00, 0x66, 0x00], # 28
|
|
1011
|
+
[0xff, 0x80, 0x80, 0x00], # 29
|
|
1012
|
+
[0x00, 0x66, 0xcc, 0x00], # 30
|
|
1013
|
+
[0xcc, 0xcc, 0xff, 0x00], # 31
|
|
1014
|
+
[0x00, 0x00, 0x80, 0x00], # 32
|
|
1015
|
+
[0xff, 0x00, 0xff, 0x00], # 33
|
|
1016
|
+
[0xff, 0xff, 0x00, 0x00], # 34
|
|
1017
|
+
[0x00, 0xff, 0xff, 0x00], # 35
|
|
1018
|
+
[0x80, 0x00, 0x80, 0x00], # 36
|
|
1019
|
+
[0x80, 0x00, 0x00, 0x00], # 37
|
|
1020
|
+
[0x00, 0x80, 0x80, 0x00], # 38
|
|
1021
|
+
[0x00, 0x00, 0xff, 0x00], # 39
|
|
1022
|
+
[0x00, 0xcc, 0xff, 0x00], # 40
|
|
1023
|
+
[0xcc, 0xff, 0xff, 0x00], # 41
|
|
1024
|
+
[0xcc, 0xff, 0xcc, 0x00], # 42
|
|
1025
|
+
[0xff, 0xff, 0x99, 0x00], # 43
|
|
1026
|
+
[0x99, 0xcc, 0xff, 0x00], # 44
|
|
1027
|
+
[0xff, 0x99, 0xcc, 0x00], # 45
|
|
1028
|
+
[0xcc, 0x99, 0xff, 0x00], # 46
|
|
1029
|
+
[0xff, 0xcc, 0x99, 0x00], # 47
|
|
1030
|
+
[0x33, 0x66, 0xff, 0x00], # 48
|
|
1031
|
+
[0x33, 0xcc, 0xcc, 0x00], # 49
|
|
1032
|
+
[0x99, 0xcc, 0x00, 0x00], # 50
|
|
1033
|
+
[0xff, 0xcc, 0x00, 0x00], # 51
|
|
1034
|
+
[0xff, 0x99, 0x00, 0x00], # 52
|
|
1035
|
+
[0xff, 0x66, 0x00, 0x00], # 53
|
|
1036
|
+
[0x66, 0x66, 0x99, 0x00], # 54
|
|
1037
|
+
[0x96, 0x96, 0x96, 0x00], # 55
|
|
1038
|
+
[0x00, 0x33, 0x66, 0x00], # 56
|
|
1039
|
+
[0x33, 0x99, 0x66, 0x00], # 57
|
|
1040
|
+
[0x00, 0x33, 0x00, 0x00], # 58
|
|
1041
|
+
[0x33, 0x33, 0x00, 0x00], # 59
|
|
1042
|
+
[0x99, 0x33, 0x00, 0x00], # 60
|
|
1043
|
+
[0x99, 0x33, 0x66, 0x00], # 61
|
|
1044
|
+
[0x33, 0x33, 0x99, 0x00], # 62
|
|
1045
|
+
[0x33, 0x33, 0x33, 0x00] # 63
|
|
1046
|
+
]
|
|
1047
|
+
end
|
|
1048
|
+
|
|
1049
|
+
def property_set(property, params) #:nodoc:
|
|
1050
|
+
valid_properties[property][0..1] + [params[property]]
|
|
1051
|
+
end
|
|
1052
|
+
|
|
1053
|
+
def property_sets(properties, params) #:nodoc:
|
|
1054
|
+
properties.select { |property| params[property.to_sym] }.
|
|
1055
|
+
collect do |property|
|
|
1056
|
+
property_set(property.to_sym, params)
|
|
1057
|
+
end
|
|
1058
|
+
end
|
|
1059
|
+
|
|
1060
|
+
# List of valid input parameters.
|
|
1061
|
+
def valid_properties #:nodoc:
|
|
1062
|
+
{
|
|
1063
|
+
:codepage => [0x0001, 'VT_I2' ],
|
|
1064
|
+
:title => [0x0002, 'VT_LPSTR' ],
|
|
1065
|
+
:subject => [0x0003, 'VT_LPSTR' ],
|
|
1066
|
+
:author => [0x0004, 'VT_LPSTR' ],
|
|
1067
|
+
:keywords => [0x0005, 'VT_LPSTR' ],
|
|
1068
|
+
:comments => [0x0006, 'VT_LPSTR' ],
|
|
1069
|
+
:last_author => [0x0008, 'VT_LPSTR' ],
|
|
1070
|
+
:created => [0x000C, 'VT_FILETIME'],
|
|
1071
|
+
:category => [0x0002, 'VT_LPSTR' ],
|
|
1072
|
+
:manager => [0x000E, 'VT_LPSTR' ],
|
|
1073
|
+
:company => [0x000F, 'VT_LPSTR' ],
|
|
1074
|
+
:utf8 => 1
|
|
1075
|
+
}
|
|
1076
|
+
end
|
|
1077
|
+
|
|
1078
|
+
def check_valid_params_for_properties(params) #:nodoc:
|
|
1079
|
+
params.each_key do |k|
|
|
1080
|
+
unless valid_properties.has_key?(k)
|
|
1081
|
+
raise "Unknown parameter '#{k}' in set_properties()"
|
|
1082
|
+
end
|
|
1083
|
+
end
|
|
1084
|
+
end
|
|
1085
|
+
|
|
1086
|
+
#
|
|
1087
|
+
# Get the character codepage used by the strings in a property set. If one of
|
|
1088
|
+
# the strings used is utf8 then the codepage is marked as utf8. Otherwise
|
|
1089
|
+
# Latin 1 is used (although in our case this is limited to 7bit ASCII).
|
|
1090
|
+
#
|
|
1091
|
+
def get_property_set_codepage(params, properties) #:nodoc:
|
|
1092
|
+
# Allow for manually marked utf8 strings.
|
|
1093
|
+
return 0xFDE9 unless params[:utf8].nil?
|
|
1094
|
+
properties.each do |property|
|
|
1095
|
+
next unless params.has_key?(property.to_sym)
|
|
1096
|
+
return 0xFDE9 if is_utf8?(params[property.to_sym])
|
|
1097
|
+
end
|
|
1098
|
+
return 0x04E4 # Default codepage, Latin 1.
|
|
1099
|
+
end
|
|
1100
|
+
|
|
1101
|
+
#
|
|
1102
|
+
# Assemble worksheets into a workbook and send the BIFF data to an OLE
|
|
1103
|
+
# storage.
|
|
1104
|
+
#
|
|
1105
|
+
def store_workbook #:nodoc:
|
|
1106
|
+
# Add a default worksheet if non have been added.
|
|
1107
|
+
add_worksheet if @worksheets.empty?
|
|
1108
|
+
|
|
1109
|
+
# Calculate size required for MSO records and update worksheets.
|
|
1110
|
+
calc_mso_sizes
|
|
1111
|
+
|
|
1112
|
+
# Ensure that at least one worksheet has been selected.
|
|
1113
|
+
unless @worksheets.activesheet
|
|
1114
|
+
@worksheets.activesheet = @worksheets.first
|
|
1115
|
+
@worksheets.activesheet.select
|
|
1116
|
+
end
|
|
1117
|
+
|
|
1118
|
+
# Add Workbook globals
|
|
1119
|
+
add_workbook_globals
|
|
1120
|
+
|
|
1121
|
+
# Calculate the offsets required by the BOUNDSHEET records
|
|
1122
|
+
calc_sheet_offsets
|
|
1123
|
+
|
|
1124
|
+
# Add BOUNDSHEET records.
|
|
1125
|
+
@worksheets.each do |sheet|
|
|
1126
|
+
store_boundsheet(sheet)
|
|
1127
|
+
end
|
|
1128
|
+
|
|
1129
|
+
# NOTE: If any records are added between here and EOF the
|
|
1130
|
+
# calc_sheet_offsets() should be updated to include the new length.
|
|
1131
|
+
store_country
|
|
1132
|
+
if @ext_refs.keys.size != 0
|
|
1133
|
+
store_supbook
|
|
1134
|
+
store_externsheet
|
|
1135
|
+
store_names
|
|
1136
|
+
end
|
|
1137
|
+
add_mso_drawing_group
|
|
1138
|
+
store_shared_strings
|
|
1139
|
+
store_extsst
|
|
1140
|
+
|
|
1141
|
+
# End Workbook globals
|
|
1142
|
+
store_eof
|
|
1143
|
+
|
|
1144
|
+
# Store the workbook in an OLE container
|
|
1145
|
+
store_ole_file
|
|
1146
|
+
end
|
|
1147
|
+
|
|
1148
|
+
def add_workbook_globals
|
|
1149
|
+
store_bof(0x0005)
|
|
1150
|
+
store_codepage
|
|
1151
|
+
store_window1
|
|
1152
|
+
store_hideobj
|
|
1153
|
+
store_1904
|
|
1154
|
+
store_all_fonts
|
|
1155
|
+
store_all_num_formats
|
|
1156
|
+
store_all_xfs
|
|
1157
|
+
store_all_styles
|
|
1158
|
+
store_palette
|
|
1159
|
+
end
|
|
1160
|
+
|
|
1161
|
+
#
|
|
1162
|
+
# Store the workbook in an OLE container using the default handler or using
|
|
1163
|
+
# OLE::Storage_Lite if the workbook data is > ~ 7MB.
|
|
1164
|
+
#
|
|
1165
|
+
def store_ole_file #:nodoc:
|
|
1166
|
+
maxsize = 7_087_104
|
|
1167
|
+
# maxsize = 1
|
|
1168
|
+
|
|
1169
|
+
if !add_doc_properties && @biffsize <= maxsize
|
|
1170
|
+
store_through_ole_writer
|
|
1171
|
+
else
|
|
1172
|
+
store_through_ole_storage_lite
|
|
1173
|
+
end
|
|
1174
|
+
end
|
|
1175
|
+
|
|
1176
|
+
def store_through_ole_writer
|
|
1177
|
+
# Write the OLE file using OLEwriter if data <= 7MB
|
|
1178
|
+
ole = OLEWriter.new(@fh_out)
|
|
1179
|
+
|
|
1180
|
+
# Write the BIFF data without the OLE container for testing.
|
|
1181
|
+
ole.biff_only = @biff_only
|
|
1182
|
+
|
|
1183
|
+
# Indicate that we created the filehandle and want to close it.
|
|
1184
|
+
ole.internal_fh = @internal_fh
|
|
1185
|
+
|
|
1186
|
+
ole.set_size(@biffsize)
|
|
1187
|
+
ole.write_header
|
|
1188
|
+
|
|
1189
|
+
while tmp = get_data
|
|
1190
|
+
ole.write(tmp)
|
|
1191
|
+
end
|
|
1192
|
+
|
|
1193
|
+
@worksheets.each do |worksheet|
|
|
1194
|
+
while tmp = worksheet.get_data
|
|
1195
|
+
ole.write(tmp)
|
|
1196
|
+
end
|
|
1197
|
+
end
|
|
1198
|
+
|
|
1199
|
+
return ole.close
|
|
1200
|
+
end
|
|
1201
|
+
|
|
1202
|
+
def store_through_ole_storage_lite
|
|
1203
|
+
# Create the Workbook stream.
|
|
1204
|
+
stream = 'Workbook'.unpack('C*').pack('v*')
|
|
1205
|
+
workbook = OLEStorageLitePPSFile.new(stream)
|
|
1206
|
+
workbook.set_file # use tempfile
|
|
1207
|
+
|
|
1208
|
+
while tmp = get_data
|
|
1209
|
+
workbook.append(tmp)
|
|
1210
|
+
end
|
|
1211
|
+
|
|
1212
|
+
@worksheets.each do |worksheet|
|
|
1213
|
+
while tmp = worksheet.get_data
|
|
1214
|
+
workbook.append(tmp)
|
|
1215
|
+
end
|
|
1216
|
+
end
|
|
1217
|
+
|
|
1218
|
+
streams = []
|
|
1219
|
+
streams << workbook
|
|
1220
|
+
|
|
1221
|
+
# Create the properties streams, if any.
|
|
1222
|
+
if add_doc_properties
|
|
1223
|
+
stream = "\5SummaryInformation".unpack('C*').pack('v*')
|
|
1224
|
+
summary = OLEStorageLitePPSFile.new(stream, @summary)
|
|
1225
|
+
streams << summary
|
|
1226
|
+
stream = "\5DocumentSummaryInformation".unpack('C*').pack('v*')
|
|
1227
|
+
summary = OLEStorageLitePPSFile.new(stream, @doc_summary)
|
|
1228
|
+
streams << summary
|
|
1229
|
+
end
|
|
1230
|
+
# Create the OLE root document and add the substreams.
|
|
1231
|
+
localtime = @localtime.to_a[0..5]
|
|
1232
|
+
localtime[4] -= 1 # month
|
|
1233
|
+
localtime[5] -= 1900
|
|
1234
|
+
ole_root = OLEStorageLitePPSRoot.new(
|
|
1235
|
+
localtime,
|
|
1236
|
+
localtime,
|
|
1237
|
+
streams
|
|
1238
|
+
)
|
|
1239
|
+
ole_root.save(@file)
|
|
1240
|
+
|
|
1241
|
+
# Close the filehandle if it was created internally.
|
|
1242
|
+
return @fh_out.close if @internal_fh != 0
|
|
1243
|
+
end
|
|
1244
|
+
|
|
1245
|
+
#
|
|
1246
|
+
# Calculate Worksheet BOF offsets records for use in the BOUNDSHEET records.
|
|
1247
|
+
#
|
|
1248
|
+
def calc_sheet_offsets #:nodoc:
|
|
1249
|
+
offset = @datasize
|
|
1250
|
+
|
|
1251
|
+
# Add the length of the COUNTRY record
|
|
1252
|
+
offset += 8
|
|
1253
|
+
|
|
1254
|
+
# Add the length of the SST and associated CONTINUEs
|
|
1255
|
+
offset += calculate_shared_string_sizes
|
|
1256
|
+
|
|
1257
|
+
# Add the length of the EXTSST record.
|
|
1258
|
+
offset += calculate_extsst_size(@shared_string_table.str_unique)
|
|
1259
|
+
|
|
1260
|
+
# Add the length of the SUPBOOK, EXTERNSHEET and NAME records
|
|
1261
|
+
offset += calculate_extern_sizes
|
|
1262
|
+
|
|
1263
|
+
# Add the length of the MSODRAWINGGROUP records including an extra 4 bytes
|
|
1264
|
+
# for any CONTINUE headers. See add_mso_drawing_group_continue().
|
|
1265
|
+
mso_size = @mso_size
|
|
1266
|
+
mso_size += 4 * Integer((mso_size -1) / Float(@limit))
|
|
1267
|
+
offset += mso_size
|
|
1268
|
+
|
|
1269
|
+
@worksheets.each do |sheet|
|
|
1270
|
+
offset += BOF + sheet.name.bytesize
|
|
1271
|
+
end
|
|
1272
|
+
|
|
1273
|
+
offset += EOF
|
|
1274
|
+
@worksheets.each do |sheet|
|
|
1275
|
+
sheet.offset = offset
|
|
1276
|
+
sheet.close
|
|
1277
|
+
offset += sheet.datasize
|
|
1278
|
+
end
|
|
1279
|
+
|
|
1280
|
+
@biffsize = offset
|
|
1281
|
+
end
|
|
1282
|
+
|
|
1283
|
+
#
|
|
1284
|
+
# Calculate the MSODRAWINGGROUP sizes and the indexes of the Worksheet
|
|
1285
|
+
# MSODRAWING records.
|
|
1286
|
+
#
|
|
1287
|
+
# In the following SPID is shape id, according to Escher nomenclature.
|
|
1288
|
+
#
|
|
1289
|
+
def calc_mso_sizes #:nodoc:
|
|
1290
|
+
mso_size = 0 # Size of the MSODRAWINGGROUP record
|
|
1291
|
+
max_spid = 1024 # spidMax
|
|
1292
|
+
drawings_saved = 0 # cdgSaved
|
|
1293
|
+
clusters = []
|
|
1294
|
+
|
|
1295
|
+
process_images
|
|
1296
|
+
|
|
1297
|
+
# Add Bstore container size if there are images.
|
|
1298
|
+
mso_size += 8 unless @images_data.empty?
|
|
1299
|
+
|
|
1300
|
+
# Iterate through the worksheets, calculate the MSODRAWINGGROUP parameters
|
|
1301
|
+
# and space required to store the record and the MSODRAWING parameters
|
|
1302
|
+
# required by each worksheet.
|
|
1303
|
+
#
|
|
1304
|
+
@worksheets.each do |sheet|
|
|
1305
|
+
next unless sheet.type == 0x0000
|
|
1306
|
+
next if sheet.num_shapes == 1
|
|
1307
|
+
|
|
1308
|
+
mso_size, drawings_saved, max_spid, start_spid =
|
|
1309
|
+
sheet.push_object_ids(mso_size, drawings_saved, max_spid, start_spid, clusters)
|
|
1310
|
+
end
|
|
1311
|
+
|
|
1312
|
+
|
|
1313
|
+
# Calculate the MSODRAWINGGROUP size if we have stored some shapes.
|
|
1314
|
+
mso_size += 86 if mso_size != 0 # Smallest size is 86+8=94
|
|
1315
|
+
|
|
1316
|
+
shapes_saved = sheets.collect { |sheet| sheet.num_shapes }.
|
|
1317
|
+
select { |num_shapes| num_shapes > 1 }.
|
|
1318
|
+
inject(0) { |result, num_shapes| result + num_shapes }
|
|
1319
|
+
|
|
1320
|
+
@mso_size = mso_size
|
|
1321
|
+
@mso_clusters = [
|
|
1322
|
+
max_spid, clusters.size + 1, shapes_saved,
|
|
1323
|
+
drawings_saved, clusters
|
|
1324
|
+
]
|
|
1325
|
+
end
|
|
1326
|
+
|
|
1327
|
+
#
|
|
1328
|
+
# We need to process each image in each worksheet and extract information.
|
|
1329
|
+
# Some of this information is stored and used in the Workbook and some is
|
|
1330
|
+
# passed back into each Worksheet. The overall size for the image related
|
|
1331
|
+
# BIFF structures in the Workbook is calculated here.
|
|
1332
|
+
#
|
|
1333
|
+
# MSO size = 8 bytes for bstore_container +
|
|
1334
|
+
# 44 bytes for blip_store_entry +
|
|
1335
|
+
# 25 bytes for blip
|
|
1336
|
+
# = 77 + image size.
|
|
1337
|
+
#
|
|
1338
|
+
def process_images #:nodoc:
|
|
1339
|
+
images_seen = {}
|
|
1340
|
+
image_data = []
|
|
1341
|
+
previous_images = []
|
|
1342
|
+
image_id = 1
|
|
1343
|
+
images_size = 0
|
|
1344
|
+
|
|
1345
|
+
@worksheets.each do |sheet|
|
|
1346
|
+
next unless sheet.type == 0x0000
|
|
1347
|
+
next if sheet.images_size == 0
|
|
1348
|
+
|
|
1349
|
+
num_images = 0
|
|
1350
|
+
image_mso_size = 0
|
|
1351
|
+
|
|
1352
|
+
sheet.images_array.each do |image|
|
|
1353
|
+
num_images += 1
|
|
1354
|
+
unless images_seen[image.filename]
|
|
1355
|
+
# TODO should also match seen images based on checksum.
|
|
1356
|
+
image.id = image_id
|
|
1357
|
+
image.ref_count = 1
|
|
1358
|
+
image.import
|
|
1359
|
+
|
|
1360
|
+
# Also store new data for use in duplicate images.
|
|
1361
|
+
previous_images.push(image)
|
|
1362
|
+
|
|
1363
|
+
# Store information required by the Workbook.
|
|
1364
|
+
image_data.push(image)
|
|
1365
|
+
|
|
1366
|
+
# Keep track of overall data size.
|
|
1367
|
+
images_size += image.size + 61 # Size for bstore container.
|
|
1368
|
+
image_mso_size += image.size + 69 # Size for dgg container.
|
|
1369
|
+
|
|
1370
|
+
images_seen[image.filename] = image_id
|
|
1371
|
+
image_id += 1
|
|
1372
|
+
else
|
|
1373
|
+
# We've processed this file already.
|
|
1374
|
+
index = images_seen[image.filename] -1
|
|
1375
|
+
|
|
1376
|
+
# Increase image reference count.
|
|
1377
|
+
image_data[index].ref_count += 1
|
|
1378
|
+
|
|
1379
|
+
# Add previously calculated data back onto the Worksheet array.
|
|
1380
|
+
# image_id, type, width, height
|
|
1381
|
+
image.id = previous_images[index].id
|
|
1382
|
+
image.type = previous_images[index].type
|
|
1383
|
+
image.width = previous_images[index].width
|
|
1384
|
+
image.height = previous_images[index].height
|
|
1385
|
+
end
|
|
1386
|
+
end
|
|
1387
|
+
|
|
1388
|
+
# Store information required by the Worksheet.
|
|
1389
|
+
sheet.num_images = num_images
|
|
1390
|
+
sheet.image_mso_size = image_mso_size
|
|
1391
|
+
end
|
|
1392
|
+
|
|
1393
|
+
# Store information required by the Workbook.
|
|
1394
|
+
@images_size = images_size
|
|
1395
|
+
@images_data = image_data # Store the data for MSODRAWINGGROUP.
|
|
1396
|
+
end
|
|
1397
|
+
|
|
1398
|
+
#
|
|
1399
|
+
# Store the Excel FONT records.
|
|
1400
|
+
#
|
|
1401
|
+
def store_all_fonts #:nodoc:
|
|
1402
|
+
format = @formats[15] # The default cell format.
|
|
1403
|
+
font = format.get_font
|
|
1404
|
+
|
|
1405
|
+
# Fonts are 0-indexed. According to the SDK there is no index 4,
|
|
1406
|
+
4.times { append(font) }
|
|
1407
|
+
|
|
1408
|
+
# Add the default fonts for charts and comments. This aren't connected
|
|
1409
|
+
# to XF formats. Note, the font size, and some other properties of
|
|
1410
|
+
# chart fonts are set in the FBI record of the chart.
|
|
1411
|
+
|
|
1412
|
+
# Index 5. Axis numbers.
|
|
1413
|
+
tmp_format = Writeexcel::Format.new(
|
|
1414
|
+
nil,
|
|
1415
|
+
:font_only => 1
|
|
1416
|
+
)
|
|
1417
|
+
append(tmp_format.get_font)
|
|
1418
|
+
|
|
1419
|
+
# Index 6. Series names.
|
|
1420
|
+
tmp_format = Writeexcel::Format.new(
|
|
1421
|
+
nil,
|
|
1422
|
+
:font_only => 1
|
|
1423
|
+
)
|
|
1424
|
+
append(tmp_format.get_font)
|
|
1425
|
+
|
|
1426
|
+
# Index 7. Title.
|
|
1427
|
+
tmp_format = Writeexcel::Format.new(
|
|
1428
|
+
nil,
|
|
1429
|
+
:font_only => 1,
|
|
1430
|
+
:bold => 1
|
|
1431
|
+
)
|
|
1432
|
+
append(tmp_format.get_font)
|
|
1433
|
+
|
|
1434
|
+
# Index 8. Axes.
|
|
1435
|
+
tmp_format = Writeexcel::Format.new(
|
|
1436
|
+
nil,
|
|
1437
|
+
:font_only => 1,
|
|
1438
|
+
:bold => 1
|
|
1439
|
+
)
|
|
1440
|
+
append(tmp_format.get_font)
|
|
1441
|
+
|
|
1442
|
+
# Index 9. Comments.
|
|
1443
|
+
tmp_format = Writeexcel::Format.new(
|
|
1444
|
+
nil,
|
|
1445
|
+
:font_only => 1,
|
|
1446
|
+
:font => 'Tahoma',
|
|
1447
|
+
:size => 8
|
|
1448
|
+
)
|
|
1449
|
+
append(tmp_format.get_font)
|
|
1450
|
+
|
|
1451
|
+
# Iterate through the XF objects and write a FONT record if it isn't the
|
|
1452
|
+
# same as the default FONT and if it hasn't already been used.
|
|
1453
|
+
#
|
|
1454
|
+
fonts = {}
|
|
1455
|
+
index = 10 # The first user defined FONT
|
|
1456
|
+
|
|
1457
|
+
key = format.get_font_key # The default font for cell formats.
|
|
1458
|
+
fonts[key] = 0 # Index of the default font
|
|
1459
|
+
|
|
1460
|
+
# Fonts that are marked as '_font_only' are always stored. These are used
|
|
1461
|
+
# mainly for charts and may not have an associated XF record.
|
|
1462
|
+
|
|
1463
|
+
@formats.each do |fmt|
|
|
1464
|
+
key = fmt.get_font_key
|
|
1465
|
+
if fmt.font_only == 0 and !fonts[key].nil?
|
|
1466
|
+
# FONT has already been used
|
|
1467
|
+
fmt.font_index = fonts[key]
|
|
1468
|
+
else
|
|
1469
|
+
# Add a new FONT record
|
|
1470
|
+
|
|
1471
|
+
if fmt.font_only == 0
|
|
1472
|
+
fonts[key] = index
|
|
1473
|
+
end
|
|
1474
|
+
|
|
1475
|
+
fmt.font_index = index
|
|
1476
|
+
index += 1
|
|
1477
|
+
font = fmt.get_font
|
|
1478
|
+
append(font)
|
|
1479
|
+
end
|
|
1480
|
+
end
|
|
1481
|
+
end
|
|
1482
|
+
|
|
1483
|
+
#
|
|
1484
|
+
# Store user defined numerical formats i.e. FORMAT records
|
|
1485
|
+
#
|
|
1486
|
+
def store_all_num_formats #:nodoc:
|
|
1487
|
+
num_formats = {}
|
|
1488
|
+
index = 164 # User defined FORMAT records start from 0xA4
|
|
1489
|
+
|
|
1490
|
+
# Iterate through the XF objects and write a FORMAT record if it isn't a
|
|
1491
|
+
# built-in format type and if the FORMAT string hasn't already been used.
|
|
1492
|
+
#
|
|
1493
|
+
@formats.each do |format|
|
|
1494
|
+
num_format = format.num_format
|
|
1495
|
+
encoding = format.num_format_enc
|
|
1496
|
+
|
|
1497
|
+
# Check if $num_format is an index to a built-in format.
|
|
1498
|
+
# Also check for a string of zeros, which is a valid format string
|
|
1499
|
+
# but would evaluate to zero.
|
|
1500
|
+
#
|
|
1501
|
+
unless num_format.to_s =~ /^0+\d/
|
|
1502
|
+
next if num_format.to_s =~ /^\d+$/ # built-in
|
|
1503
|
+
end
|
|
1504
|
+
|
|
1505
|
+
if num_formats[num_format]
|
|
1506
|
+
# FORMAT has already been used
|
|
1507
|
+
format.num_format = num_formats[num_format]
|
|
1508
|
+
else
|
|
1509
|
+
# Add a new FORMAT
|
|
1510
|
+
num_formats[num_format] = index
|
|
1511
|
+
format.num_format = index
|
|
1512
|
+
store_num_format(num_format, index, encoding)
|
|
1513
|
+
index += 1
|
|
1514
|
+
end
|
|
1515
|
+
end
|
|
1516
|
+
end
|
|
1517
|
+
|
|
1518
|
+
#
|
|
1519
|
+
# Write all XF records.
|
|
1520
|
+
#
|
|
1521
|
+
def store_all_xfs #:nodoc:
|
|
1522
|
+
@formats.each do |format|
|
|
1523
|
+
xf = format.get_xf
|
|
1524
|
+
append(xf)
|
|
1525
|
+
end
|
|
1526
|
+
end
|
|
1527
|
+
|
|
1528
|
+
#
|
|
1529
|
+
# Write all STYLE records.
|
|
1530
|
+
#
|
|
1531
|
+
def store_all_styles #:nodoc:
|
|
1532
|
+
# Excel adds the built-in styles in alphabetical order.
|
|
1533
|
+
built_ins = [
|
|
1534
|
+
[0x03, 16], # Comma
|
|
1535
|
+
[0x06, 17], # Comma[0]
|
|
1536
|
+
[0x04, 18], # Currency
|
|
1537
|
+
[0x07, 19], # Currency[0]
|
|
1538
|
+
[0x00, 0], # Normal
|
|
1539
|
+
[0x05, 20] # Percent
|
|
1540
|
+
|
|
1541
|
+
# We don't deal with these styles yet.
|
|
1542
|
+
#[0x08, 21], # Hyperlink
|
|
1543
|
+
#[0x02, 8], # ColLevel_n
|
|
1544
|
+
#[0x01, 1], # RowLevel_n
|
|
1545
|
+
]
|
|
1546
|
+
|
|
1547
|
+
built_ins.each do |aref|
|
|
1548
|
+
type = aref[0]
|
|
1549
|
+
xf_index = aref[1]
|
|
1550
|
+
|
|
1551
|
+
store_style(type, xf_index)
|
|
1552
|
+
end
|
|
1553
|
+
end
|
|
1554
|
+
|
|
1555
|
+
#
|
|
1556
|
+
# Write the NAME record to define the print area and the repeat rows and cols.
|
|
1557
|
+
#
|
|
1558
|
+
def store_names # :nodoc:
|
|
1559
|
+
# Create the user defined names.
|
|
1560
|
+
defined_names.each do |defined_name|
|
|
1561
|
+
store_name(
|
|
1562
|
+
defined_name[:name],
|
|
1563
|
+
defined_name[:encoding],
|
|
1564
|
+
defined_name[:sheet_index],
|
|
1565
|
+
defined_name[:formula]
|
|
1566
|
+
)
|
|
1567
|
+
end
|
|
1568
|
+
|
|
1569
|
+
# Sort the worksheets into alphabetical order by name. This is a
|
|
1570
|
+
# requirement for some non-English language Excel patch levels.
|
|
1571
|
+
sorted_worksheets = @worksheets.sort_by{ |x| x.name }
|
|
1572
|
+
|
|
1573
|
+
# Create the autofilter NAME records
|
|
1574
|
+
create_autofilter_name_records(sorted_worksheets)
|
|
1575
|
+
|
|
1576
|
+
# Create the print area NAME records
|
|
1577
|
+
create_print_area_name_records(sorted_worksheets)
|
|
1578
|
+
|
|
1579
|
+
# Create the print title NAME records
|
|
1580
|
+
create_print_title_name_records(sorted_worksheets)
|
|
1581
|
+
end
|
|
1582
|
+
|
|
1583
|
+
def create_autofilter_name_records(sorted_worksheets) #:nodoc:
|
|
1584
|
+
sorted_worksheets.each do |worksheet|
|
|
1585
|
+
append(*worksheet.autofilter_name_record_short(true))
|
|
1586
|
+
end
|
|
1587
|
+
end
|
|
1588
|
+
|
|
1589
|
+
def create_print_area_name_records(sorted_worksheets) #:nodoc:
|
|
1590
|
+
sorted_worksheets.each do |worksheet|
|
|
1591
|
+
append(*worksheet.print_area_name_record_short)
|
|
1592
|
+
end
|
|
1593
|
+
end
|
|
1594
|
+
|
|
1595
|
+
def create_print_title_name_records(sorted_worksheets) #:nodoc:
|
|
1596
|
+
sorted_worksheets.each do |worksheet|
|
|
1597
|
+
# Determine if row + col, row, col or nothing has been defined
|
|
1598
|
+
# and write the appropriate record
|
|
1599
|
+
#
|
|
1600
|
+
if worksheet.title_range.row_min && worksheet.title_range.col_min
|
|
1601
|
+
# Row and column titles have been defined.
|
|
1602
|
+
# Row title has been defined.
|
|
1603
|
+
append(*worksheet.print_title_name_record_long)
|
|
1604
|
+
elsif worksheet.title_range.row_min
|
|
1605
|
+
# Row title has been defined.
|
|
1606
|
+
append(*worksheet.print_title_name_record_short)
|
|
1607
|
+
elsif worksheet.title_range.col_min
|
|
1608
|
+
# Column title has been defined.
|
|
1609
|
+
append(*worksheet.print_title_name_record_short)
|
|
1610
|
+
else
|
|
1611
|
+
# Nothing left to do
|
|
1612
|
+
end
|
|
1613
|
+
end
|
|
1614
|
+
end
|
|
1615
|
+
|
|
1616
|
+
###############################################################################
|
|
1617
|
+
###############################################################################
|
|
1618
|
+
#
|
|
1619
|
+
# BIFF RECORDS
|
|
1620
|
+
#
|
|
1621
|
+
|
|
1622
|
+
|
|
1623
|
+
#
|
|
1624
|
+
# Write Excel BIFF WINDOW1 record.
|
|
1625
|
+
#
|
|
1626
|
+
def store_window1 #:nodoc:
|
|
1627
|
+
record = 0x003D # Record identifier
|
|
1628
|
+
length = 0x0012 # Number of bytes to follow
|
|
1629
|
+
|
|
1630
|
+
x_pos = 0x0000 # Horizontal position of window
|
|
1631
|
+
y_pos = 0x0000 # Vertical position of window
|
|
1632
|
+
dx_win = 0x355C # Width of window
|
|
1633
|
+
dy_win = 0x30ED # Height of window
|
|
1634
|
+
|
|
1635
|
+
grbit = 0x0038 # Option flags
|
|
1636
|
+
ctabsel = @worksheets.selected_count # Number of workbook tabs selected
|
|
1637
|
+
tab_ratio = 0x0258 # Tab to scrollbar ratio
|
|
1638
|
+
|
|
1639
|
+
tab_cur = @worksheets.activesheet_index # Active worksheet
|
|
1640
|
+
tab_first = @worksheets.firstsheet_index # 1st displayed worksheet
|
|
1641
|
+
header = [record, length].pack("vv")
|
|
1642
|
+
data = [
|
|
1643
|
+
x_pos, y_pos, dx_win, dy_win,
|
|
1644
|
+
grbit,
|
|
1645
|
+
tab_cur, tab_first,
|
|
1646
|
+
ctabsel, tab_ratio
|
|
1647
|
+
].pack("vvvvvvvvv")
|
|
1648
|
+
|
|
1649
|
+
append(header, data)
|
|
1650
|
+
end
|
|
1651
|
+
|
|
1652
|
+
#
|
|
1653
|
+
# Writes Excel BIFF BOUNDSHEET record.
|
|
1654
|
+
#
|
|
1655
|
+
# sheetname # Worksheet name
|
|
1656
|
+
# offset # Location of worksheet BOF
|
|
1657
|
+
# type # Worksheet type
|
|
1658
|
+
# hidden # Worksheet hidden flag
|
|
1659
|
+
# encoding # Sheet name encoding
|
|
1660
|
+
#
|
|
1661
|
+
def store_boundsheet(sheet) #:nodoc:
|
|
1662
|
+
append(sheet.boundsheet)
|
|
1663
|
+
end
|
|
1664
|
+
|
|
1665
|
+
#
|
|
1666
|
+
# Write Excel BIFF STYLE records.
|
|
1667
|
+
#
|
|
1668
|
+
# type # Built-in style
|
|
1669
|
+
# xf_index # Index to style XF
|
|
1670
|
+
#
|
|
1671
|
+
def store_style(type, xf_index) #:nodoc:
|
|
1672
|
+
record = 0x0293 # Record identifier
|
|
1673
|
+
length = 0x0004 # Bytes to follow
|
|
1674
|
+
|
|
1675
|
+
level = 0xff # Outline style level
|
|
1676
|
+
|
|
1677
|
+
xf_index |= 0x8000 # Add flag to indicate built-in style.
|
|
1678
|
+
|
|
1679
|
+
header = [record, length].pack("vv")
|
|
1680
|
+
data = [xf_index, type, level].pack("vCC")
|
|
1681
|
+
|
|
1682
|
+
append(header, data)
|
|
1683
|
+
end
|
|
1684
|
+
|
|
1685
|
+
#
|
|
1686
|
+
# Writes Excel FORMAT record for non "built-in" numerical formats.
|
|
1687
|
+
#
|
|
1688
|
+
# format # Custom format string
|
|
1689
|
+
# ifmt # Format index code
|
|
1690
|
+
# encoding # Char encoding for format string
|
|
1691
|
+
#
|
|
1692
|
+
def store_num_format(format, ifmt, encoding) #:nodoc:
|
|
1693
|
+
format = format.to_s unless format.respond_to?(:to_str)
|
|
1694
|
+
record = 0x041E # Record identifier
|
|
1695
|
+
# length # Number of bytes to follow
|
|
1696
|
+
# Char length of format string
|
|
1697
|
+
cch = format.bytesize
|
|
1698
|
+
|
|
1699
|
+
ruby_19 { format = convert_to_ascii_if_ascii(format) }
|
|
1700
|
+
|
|
1701
|
+
# Handle utf8 strings
|
|
1702
|
+
if is_utf8?(format)
|
|
1703
|
+
format = utf8_to_16be(format)
|
|
1704
|
+
encoding = 1
|
|
1705
|
+
end
|
|
1706
|
+
|
|
1707
|
+
# Handle Unicode format strings.
|
|
1708
|
+
if encoding == 1
|
|
1709
|
+
#raise "Uneven number of bytes in Unicode font name" if cch % 2 != 0
|
|
1710
|
+
#cch /= 2 if encoding != 0
|
|
1711
|
+
format = utf16be_to_16le(format)
|
|
1712
|
+
end
|
|
1713
|
+
|
|
1714
|
+
=begin
|
|
1715
|
+
# Special case to handle Euro symbol, 0x80, in non-Unicode strings.
|
|
1716
|
+
if encoding == 0 and format =~ /\x80/
|
|
1717
|
+
format = format.unpack('C*').pack('v*')
|
|
1718
|
+
format.gsub!(/\x80\x00/, "\xAC\x20")
|
|
1719
|
+
encoding = 1
|
|
1720
|
+
end
|
|
1721
|
+
=end
|
|
1722
|
+
length = 0x05 + format.bytesize
|
|
1723
|
+
|
|
1724
|
+
header = [record, length].pack("vv")
|
|
1725
|
+
data = [ifmt, cch, encoding].pack("vvC")
|
|
1726
|
+
|
|
1727
|
+
append(header, data, format)
|
|
1728
|
+
end
|
|
1729
|
+
|
|
1730
|
+
#
|
|
1731
|
+
# Write Excel 1904 record to indicate the date system in use.
|
|
1732
|
+
#
|
|
1733
|
+
def store_1904 #:nodoc:
|
|
1734
|
+
record = 0x0022 # Record identifier
|
|
1735
|
+
length = 0x0002 # Bytes to follow
|
|
1736
|
+
|
|
1737
|
+
f1904 = @date_1904 ? 1 : 0 # Flag for 1904 date system
|
|
1738
|
+
|
|
1739
|
+
header = [record, length].pack("vv")
|
|
1740
|
+
data = [f1904].pack("v")
|
|
1741
|
+
|
|
1742
|
+
append(header, data)
|
|
1743
|
+
end
|
|
1744
|
+
|
|
1745
|
+
#
|
|
1746
|
+
# Write BIFF record SUPBOOK to indicate that the workbook contains external
|
|
1747
|
+
# references, in our case, formula, print area and print title refs.
|
|
1748
|
+
#
|
|
1749
|
+
def store_supbook #:nodoc:
|
|
1750
|
+
record = 0x01AE # Record identifier
|
|
1751
|
+
length = 0x0004 # Number of bytes to follow
|
|
1752
|
+
|
|
1753
|
+
tabs = @worksheets.size # Number of worksheets
|
|
1754
|
+
virt_path = 0x0401 # Encoded workbook filename
|
|
1755
|
+
|
|
1756
|
+
header = [record, length].pack("vv")
|
|
1757
|
+
data = [tabs, virt_path].pack("vv")
|
|
1758
|
+
|
|
1759
|
+
append(header, data)
|
|
1760
|
+
end
|
|
1761
|
+
|
|
1762
|
+
#
|
|
1763
|
+
# Writes the Excel BIFF EXTERNSHEET record. These references are used by
|
|
1764
|
+
# formulas. TODO NAME record is required to define the print area and the
|
|
1765
|
+
# repeat rows and columns.
|
|
1766
|
+
#
|
|
1767
|
+
def store_externsheet # :nodoc:
|
|
1768
|
+
record = 0x0017 # Record identifier
|
|
1769
|
+
|
|
1770
|
+
# Get the external refs
|
|
1771
|
+
ext = @ext_refs.keys.sort
|
|
1772
|
+
|
|
1773
|
+
# Change the external refs from stringified "1:1" to [1, 1]
|
|
1774
|
+
ext.map! {|e| e.split(/:/).map! {|v| v.to_i} }
|
|
1775
|
+
|
|
1776
|
+
cxti = ext.size # Number of Excel XTI structures
|
|
1777
|
+
rgxti = '' # Array of XTI structures
|
|
1778
|
+
|
|
1779
|
+
# Write the XTI structs
|
|
1780
|
+
ext.each do |e|
|
|
1781
|
+
rgxti += [0, e[0], e[1]].pack("vvv")
|
|
1782
|
+
end
|
|
1783
|
+
|
|
1784
|
+
data = [cxti].pack("v") + rgxti
|
|
1785
|
+
header = [record, data.bytesize].pack("vv")
|
|
1786
|
+
|
|
1787
|
+
append(header, data)
|
|
1788
|
+
end
|
|
1789
|
+
|
|
1790
|
+
#
|
|
1791
|
+
# Store the NAME record used for storing the print area, repeat rows, repeat
|
|
1792
|
+
# columns, autofilters and defined names.
|
|
1793
|
+
#
|
|
1794
|
+
# TODO. This is a more generic version that will replace store_name_short()
|
|
1795
|
+
# and store_name_long().
|
|
1796
|
+
#
|
|
1797
|
+
def store_name(name, encoding, sheet_index, formula) # :nodoc:
|
|
1798
|
+
ruby_19 { formula = convert_to_ascii_if_ascii(formula) }
|
|
1799
|
+
|
|
1800
|
+
record = 0x0018 # Record identifier
|
|
1801
|
+
|
|
1802
|
+
text_length = name.bytesize
|
|
1803
|
+
formula_length = formula.bytesize
|
|
1804
|
+
|
|
1805
|
+
# UTF-16 string length is in characters not bytes.
|
|
1806
|
+
text_length /= 2 if encoding != 0
|
|
1807
|
+
|
|
1808
|
+
grbit = 0x0000 # Option flags
|
|
1809
|
+
shortcut = 0x00 # Keyboard shortcut
|
|
1810
|
+
ixals = 0x0000 # Unused index.
|
|
1811
|
+
menu_length = 0x00 # Length of cust menu text
|
|
1812
|
+
desc_length = 0x00 # Length of description text
|
|
1813
|
+
help_length = 0x00 # Length of help topic text
|
|
1814
|
+
status_length = 0x00 # Length of status bar text
|
|
1815
|
+
|
|
1816
|
+
# Set grbit built-in flag and the hidden flag for autofilters.
|
|
1817
|
+
if text_length == 1
|
|
1818
|
+
grbit = 0x0020 if name.ord == 0x06 # Print area
|
|
1819
|
+
grbit = 0x0020 if name.ord == 0x07 # Print titles
|
|
1820
|
+
grbit = 0x0021 if name.ord == 0x0D # Autofilter
|
|
1821
|
+
end
|
|
1822
|
+
|
|
1823
|
+
data = [grbit].pack("v")
|
|
1824
|
+
data += [shortcut].pack("C")
|
|
1825
|
+
data += [text_length].pack("C")
|
|
1826
|
+
data += [formula_length].pack("v")
|
|
1827
|
+
data += [ixals].pack("v")
|
|
1828
|
+
data += [sheet_index].pack("v")
|
|
1829
|
+
data += [menu_length].pack("C")
|
|
1830
|
+
data += [desc_length].pack("C")
|
|
1831
|
+
data += [help_length].pack("C")
|
|
1832
|
+
data += [status_length].pack("C")
|
|
1833
|
+
data += [encoding].pack("C")
|
|
1834
|
+
data += name
|
|
1835
|
+
data += formula
|
|
1836
|
+
|
|
1837
|
+
header = [record, data.bytesize].pack("vv")
|
|
1838
|
+
|
|
1839
|
+
append(header, data)
|
|
1840
|
+
end
|
|
1841
|
+
|
|
1842
|
+
#
|
|
1843
|
+
# Stores the PALETTE biff record.
|
|
1844
|
+
#
|
|
1845
|
+
def store_palette #:nodoc:
|
|
1846
|
+
record = 0x0092 # Record identifier
|
|
1847
|
+
length = 2 + 4 * @palette.size # Number of bytes to follow
|
|
1848
|
+
ccv = @palette.size # Number of RGB values to follow
|
|
1849
|
+
data = '' # The RGB data
|
|
1850
|
+
|
|
1851
|
+
# Pack the RGB data
|
|
1852
|
+
@palette.each do |p|
|
|
1853
|
+
data += p.pack('CCCC')
|
|
1854
|
+
end
|
|
1855
|
+
|
|
1856
|
+
header = [record, length, ccv].pack("vvv")
|
|
1857
|
+
|
|
1858
|
+
append(header, data)
|
|
1859
|
+
end
|
|
1860
|
+
|
|
1861
|
+
#
|
|
1862
|
+
# Stores the CODEPAGE biff record.
|
|
1863
|
+
#
|
|
1864
|
+
def store_codepage #:nodoc:
|
|
1865
|
+
record = 0x0042 # Record identifier
|
|
1866
|
+
length = 0x0002 # Number of bytes to follow
|
|
1867
|
+
cv = @codepage # The code page
|
|
1868
|
+
|
|
1869
|
+
store_common(record, length, cv)
|
|
1870
|
+
end
|
|
1871
|
+
|
|
1872
|
+
#
|
|
1873
|
+
# Stores the COUNTRY biff record.
|
|
1874
|
+
#
|
|
1875
|
+
def store_country #:nodoc:
|
|
1876
|
+
record = 0x008C # Record identifier
|
|
1877
|
+
length = 0x0004 # Number of bytes to follow
|
|
1878
|
+
country_default = @country
|
|
1879
|
+
country_win_ini = @country
|
|
1880
|
+
|
|
1881
|
+
store_common(record, length, country_default, country_win_ini)
|
|
1882
|
+
end
|
|
1883
|
+
|
|
1884
|
+
#
|
|
1885
|
+
# Stores the HIDEOBJ biff record.
|
|
1886
|
+
#
|
|
1887
|
+
def store_hideobj #:nodoc:
|
|
1888
|
+
record = 0x008D # Record identifier
|
|
1889
|
+
length = 0x0002 # Number of bytes to follow
|
|
1890
|
+
hide = @hideobj ? 1 : 0 # Option to hide objects
|
|
1891
|
+
|
|
1892
|
+
store_common(record, length, hide)
|
|
1893
|
+
end
|
|
1894
|
+
|
|
1895
|
+
def store_common(record, length, *data) #:nodoc:
|
|
1896
|
+
header = [record, length].pack("vv")
|
|
1897
|
+
add_data = [*data].pack("v*")
|
|
1898
|
+
|
|
1899
|
+
append(header, add_data)
|
|
1900
|
+
end
|
|
1901
|
+
|
|
1902
|
+
#
|
|
1903
|
+
# We need to calculate the space required by the SUPBOOK, EXTERNSHEET and NAME
|
|
1904
|
+
# records so that it can be added to the BOUNDSHEET offsets.
|
|
1905
|
+
#
|
|
1906
|
+
def calculate_extern_sizes # :nodoc:
|
|
1907
|
+
ext_refs = @parser.get_ext_sheets
|
|
1908
|
+
length = 0
|
|
1909
|
+
index = 0
|
|
1910
|
+
|
|
1911
|
+
unless defined_names.empty?
|
|
1912
|
+
index = 0
|
|
1913
|
+
key = "#{index}:#{index}"
|
|
1914
|
+
|
|
1915
|
+
add_ext_refs(ext_refs, key) unless ext_refs.has_key?(key)
|
|
1916
|
+
end
|
|
1917
|
+
|
|
1918
|
+
defined_names.each do |defined_name|
|
|
1919
|
+
length += 19 + defined_name[:name].bytesize + defined_name[:formula].bytesize
|
|
1920
|
+
end
|
|
1921
|
+
|
|
1922
|
+
@worksheets.each do |worksheet|
|
|
1923
|
+
key = "#{index}:#{index}"
|
|
1924
|
+
index += 1
|
|
1925
|
+
|
|
1926
|
+
# Add area NAME records
|
|
1927
|
+
#
|
|
1928
|
+
if worksheet.print_range.row_min
|
|
1929
|
+
add_ext_refs(ext_refs, key) unless ext_refs[key]
|
|
1930
|
+
length += 31
|
|
1931
|
+
end
|
|
1932
|
+
|
|
1933
|
+
# Add title NAME records
|
|
1934
|
+
#
|
|
1935
|
+
if worksheet.title_range.row_min && worksheet.title_range.col_min
|
|
1936
|
+
add_ext_refs(ext_refs, key) unless ext_refs[key]
|
|
1937
|
+
length += 46
|
|
1938
|
+
elsif worksheet.title_range.row_min || worksheet.title_range.col_min
|
|
1939
|
+
add_ext_refs(ext_refs, key) unless ext_refs[key]
|
|
1940
|
+
length += 31
|
|
1941
|
+
else
|
|
1942
|
+
# TODO, may need this later.
|
|
1943
|
+
end
|
|
1944
|
+
|
|
1945
|
+
# Add Autofilter NAME records
|
|
1946
|
+
#
|
|
1947
|
+
unless worksheet.filter_count == 0
|
|
1948
|
+
add_ext_refs(ext_refs, key) unless ext_refs[key]
|
|
1949
|
+
length += 31
|
|
1950
|
+
end
|
|
1951
|
+
end
|
|
1952
|
+
|
|
1953
|
+
# Update the ref counts.
|
|
1954
|
+
ext_ref_count = ext_refs.keys.size
|
|
1955
|
+
@ext_refs = ext_refs
|
|
1956
|
+
|
|
1957
|
+
# If there are no external refs then we don't write, SUPBOOK, EXTERNSHEET
|
|
1958
|
+
# and NAME. Therefore the length is 0.
|
|
1959
|
+
|
|
1960
|
+
return length = 0 if ext_ref_count == 0
|
|
1961
|
+
|
|
1962
|
+
# The SUPBOOK record is 8 bytes
|
|
1963
|
+
length += 8
|
|
1964
|
+
|
|
1965
|
+
# The EXTERNSHEET record is 6 bytes + 6 bytes for each external ref
|
|
1966
|
+
length += 6 * (1 + ext_ref_count)
|
|
1967
|
+
|
|
1968
|
+
length
|
|
1969
|
+
end
|
|
1970
|
+
|
|
1971
|
+
def add_ext_refs(ext_refs, key) #:nodoc:
|
|
1972
|
+
ext_refs[key] = ext_refs.keys.size
|
|
1973
|
+
end
|
|
1974
|
+
|
|
1975
|
+
#
|
|
1976
|
+
# Handling of the SST continue blocks is complicated by the need to include an
|
|
1977
|
+
# additional continuation byte depending on whether the string is split between
|
|
1978
|
+
# blocks or whether it starts at the beginning of the block. (There are also
|
|
1979
|
+
# additional complications that will arise later when/if Rich Strings are
|
|
1980
|
+
# supported). As such we cannot use the simple CONTINUE mechanism provided by
|
|
1981
|
+
# the add_continue() method in BIFFwriter.pm. Thus we have to make two passes
|
|
1982
|
+
# through the strings data. The first is to calculate the required block sizes
|
|
1983
|
+
# and the second, in store_shared_strings(), is to write the actual strings.
|
|
1984
|
+
# The first pass through the data is also used to calculate the size of the SST
|
|
1985
|
+
# and CONTINUE records for use in setting the BOUNDSHEET record offsets. The
|
|
1986
|
+
# downside of this is that the same algorithm repeated in store_shared_strings.
|
|
1987
|
+
#
|
|
1988
|
+
def calculate_shared_string_sizes #:nodoc:
|
|
1989
|
+
str_block_sizes = @shared_string_table.block_sizes
|
|
1990
|
+
|
|
1991
|
+
# Calculate the total length of the SST and associated CONTINUEs (if any).
|
|
1992
|
+
# The SST record will have a length even if it contains no strings.
|
|
1993
|
+
# This length is required to set the offsets in the BOUNDSHEET records since
|
|
1994
|
+
# they must be written before the SST records
|
|
1995
|
+
#
|
|
1996
|
+
# when array = [a, b, c] # array not empty?
|
|
1997
|
+
# length = 12 + a + 4 + b + 4 + c = 12 + a + b + c + 4 * (array.size - 1)
|
|
1998
|
+
#
|
|
1999
|
+
length = str_block_sizes.inject(12){|result, item| result + item}
|
|
2000
|
+
length += 4 * (str_block_sizes.size - 1) unless str_block_sizes.empty?
|
|
2001
|
+
|
|
2002
|
+
length
|
|
2003
|
+
end
|
|
2004
|
+
|
|
2005
|
+
def self.split_string_setup(encoding, split_string, continue_limit, written, continue) #:nodoc:
|
|
2006
|
+
# We need to avoid the case where a string is continued in the first
|
|
2007
|
+
# n bytes that contain the string header information.
|
|
2008
|
+
header_length = 3 # Min string + header size -1
|
|
2009
|
+
space_remaining = continue_limit - written - continue
|
|
2010
|
+
|
|
2011
|
+
# Unicode data should only be split on char (2 byte) boundaries.
|
|
2012
|
+
# Therefore, in some cases we need to reduce the amount of available
|
|
2013
|
+
# space by 1 byte to ensure the correct alignment.
|
|
2014
|
+
align = 0
|
|
2015
|
+
|
|
2016
|
+
# Only applies to Unicode strings
|
|
2017
|
+
if encoding == 1
|
|
2018
|
+
# Min string + header size -1
|
|
2019
|
+
header_length = 4
|
|
2020
|
+
if space_remaining > header_length
|
|
2021
|
+
# String contains 3 byte header => split on odd boundary
|
|
2022
|
+
if split_string == 0 and space_remaining % 2 != 1
|
|
2023
|
+
space_remaining -= 1
|
|
2024
|
+
align = 1
|
|
2025
|
+
# Split section without header => split on even boundary
|
|
2026
|
+
elsif split_string != 0 and space_remaining % 2 == 1
|
|
2027
|
+
space_remaining -= 1
|
|
2028
|
+
align = 1
|
|
2029
|
+
end
|
|
2030
|
+
split_string = 1
|
|
2031
|
+
end
|
|
2032
|
+
end
|
|
2033
|
+
[header_length, space_remaining, align, split_string]
|
|
2034
|
+
end
|
|
2035
|
+
|
|
2036
|
+
def_delegator self, :split_string_setup
|
|
2037
|
+
|
|
2038
|
+
#
|
|
2039
|
+
# Write all of the workbooks strings into an indexed array.
|
|
2040
|
+
#
|
|
2041
|
+
# See the comments in calculate_shared_string_sizes() for more information.
|
|
2042
|
+
#
|
|
2043
|
+
# We also use this routine to record the offsets required by the EXTSST table.
|
|
2044
|
+
# In order to do this we first identify the first string in an EXTSST bucket
|
|
2045
|
+
# and then store its global and local offset within the SST table. The offset
|
|
2046
|
+
# occurs wherever the start of the bucket string is written out via append().
|
|
2047
|
+
#
|
|
2048
|
+
def store_shared_strings #:nodoc:
|
|
2049
|
+
record = 0x00FC # Record identifier
|
|
2050
|
+
length = 0x0008 # Number of bytes to follow
|
|
2051
|
+
total = 0x0000
|
|
2052
|
+
|
|
2053
|
+
# Iterate through the strings to calculate the CONTINUE block sizes
|
|
2054
|
+
continue_limit = 8208
|
|
2055
|
+
block_length = 0
|
|
2056
|
+
written = 0
|
|
2057
|
+
continue = 0
|
|
2058
|
+
|
|
2059
|
+
# The SST and CONTINUE block sizes have been pre-calculated by
|
|
2060
|
+
# calculate_shared_string_sizes()
|
|
2061
|
+
block_sizes = @shared_string_table.block_sizes
|
|
2062
|
+
|
|
2063
|
+
# The SST record is required even if it contains no strings. Thus we will
|
|
2064
|
+
# always have a length
|
|
2065
|
+
#
|
|
2066
|
+
if block_sizes.size != 0
|
|
2067
|
+
length = 8 + block_sizes.shift
|
|
2068
|
+
else
|
|
2069
|
+
# No strings
|
|
2070
|
+
length = 8
|
|
2071
|
+
end
|
|
2072
|
+
|
|
2073
|
+
# Initialise variables used to track EXTSST bucket offsets.
|
|
2074
|
+
extsst_str_num = -1
|
|
2075
|
+
sst_block_start = @datasize
|
|
2076
|
+
|
|
2077
|
+
# Write the SST block header information
|
|
2078
|
+
header = [record, length].pack("vv")
|
|
2079
|
+
data = [@shared_string_table.str_total, @shared_string_table.str_unique].pack("VV")
|
|
2080
|
+
append(header, data)
|
|
2081
|
+
|
|
2082
|
+
# Iterate through the strings and write them out
|
|
2083
|
+
return if @shared_string_table.strings.empty?
|
|
2084
|
+
@shared_string_table.strings.each do |string|
|
|
2085
|
+
|
|
2086
|
+
string_length = string.bytesize
|
|
2087
|
+
|
|
2088
|
+
# Check if the string is at the start of a EXTSST bucket.
|
|
2089
|
+
extsst_str_num += 1
|
|
2090
|
+
# Used to track EXTSST bucket offsets.
|
|
2091
|
+
bucket_string = (extsst_str_num % @extsst_bucket_size == 0)
|
|
2092
|
+
|
|
2093
|
+
# Block length is the total length of the strings that will be
|
|
2094
|
+
# written out in a single SST or CONTINUE block.
|
|
2095
|
+
#
|
|
2096
|
+
block_length += string_length
|
|
2097
|
+
|
|
2098
|
+
# We can write the string if it doesn't cross a CONTINUE boundary
|
|
2099
|
+
if block_length < continue_limit
|
|
2100
|
+
|
|
2101
|
+
# Store location of EXTSST bucket string.
|
|
2102
|
+
if bucket_string
|
|
2103
|
+
@extsst_offsets.push([@datasize, @datasize - sst_block_start])
|
|
2104
|
+
bucket_string = false
|
|
2105
|
+
end
|
|
2106
|
+
|
|
2107
|
+
append(string)
|
|
2108
|
+
written += string_length
|
|
2109
|
+
next
|
|
2110
|
+
end
|
|
2111
|
+
|
|
2112
|
+
# Deal with the cases where the next string to be written will exceed
|
|
2113
|
+
# the CONTINUE boundary. If the string is very long it may need to be
|
|
2114
|
+
# written in more than one CONTINUE record.
|
|
2115
|
+
encoding = string.unpack("xx C")[0]
|
|
2116
|
+
split_string = 0
|
|
2117
|
+
while block_length >= continue_limit
|
|
2118
|
+
header_length, space_remaining, align, split_string =
|
|
2119
|
+
split_string_setup(encoding, split_string, continue_limit, written, continue)
|
|
2120
|
+
|
|
2121
|
+
if space_remaining > header_length
|
|
2122
|
+
# Write as much as possible of the string in the current block
|
|
2123
|
+
tmp = string[0, space_remaining]
|
|
2124
|
+
|
|
2125
|
+
# Store location of EXTSST bucket string.
|
|
2126
|
+
if bucket_string
|
|
2127
|
+
@extsst_offsets.push([@datasize, @datasize - sst_block_start])
|
|
2128
|
+
bucket_string = false
|
|
2129
|
+
end
|
|
2130
|
+
|
|
2131
|
+
append(tmp)
|
|
2132
|
+
|
|
2133
|
+
# The remainder will be written in the next block(s)
|
|
2134
|
+
string = string[space_remaining .. string.length-1]
|
|
2135
|
+
|
|
2136
|
+
# Reduce the current block length by the amount written
|
|
2137
|
+
block_length -= continue_limit -continue -align
|
|
2138
|
+
|
|
2139
|
+
# If the current string was split then the next CONTINUE block
|
|
2140
|
+
# should have the string continue flag (grbit) set unless the
|
|
2141
|
+
# split string fits exactly into the remaining space.
|
|
2142
|
+
#
|
|
2143
|
+
if block_length > 0
|
|
2144
|
+
continue = 1
|
|
2145
|
+
else
|
|
2146
|
+
continue = 0
|
|
2147
|
+
end
|
|
2148
|
+
else
|
|
2149
|
+
# Not enough space to start the string in the current block
|
|
2150
|
+
block_length -= continue_limit -space_remaining -continue
|
|
2151
|
+
continue = 0
|
|
2152
|
+
end
|
|
2153
|
+
|
|
2154
|
+
# Write the CONTINUE block header
|
|
2155
|
+
if block_sizes.size != 0
|
|
2156
|
+
sst_block_start= @datasize # Reset EXTSST offset.
|
|
2157
|
+
|
|
2158
|
+
record = 0x003C
|
|
2159
|
+
length = block_sizes.shift
|
|
2160
|
+
|
|
2161
|
+
header = [record, length].pack("vv")
|
|
2162
|
+
header += [encoding].pack("C") if continue != 0
|
|
2163
|
+
|
|
2164
|
+
append(header)
|
|
2165
|
+
end
|
|
2166
|
+
|
|
2167
|
+
# If the string (or substr) is small enough we can write it in the
|
|
2168
|
+
# new CONTINUE block. Else, go through the loop again to write it in
|
|
2169
|
+
# one or more CONTINUE blocks
|
|
2170
|
+
#
|
|
2171
|
+
if block_length < continue_limit
|
|
2172
|
+
|
|
2173
|
+
# Store location of EXTSST bucket string.
|
|
2174
|
+
if bucket_string
|
|
2175
|
+
@extsst_offsets.push([@datasize, @datasize - sst_block_start])
|
|
2176
|
+
bucket_string = false
|
|
2177
|
+
end
|
|
2178
|
+
append(string)
|
|
2179
|
+
|
|
2180
|
+
written = block_length
|
|
2181
|
+
else
|
|
2182
|
+
written = 0
|
|
2183
|
+
end
|
|
2184
|
+
end
|
|
2185
|
+
end
|
|
2186
|
+
end
|
|
2187
|
+
|
|
2188
|
+
#
|
|
2189
|
+
# The number of buckets used in the EXTSST is between 0 and 128. The number of
|
|
2190
|
+
# strings per bucket (bucket size) has a minimum value of 8 and a theoretical
|
|
2191
|
+
# maximum of 2^16. For "number of strings" < 1024 there is a constant bucket
|
|
2192
|
+
# size of 8. The following algorithm generates the same size/bucket ratio
|
|
2193
|
+
# as Excel.
|
|
2194
|
+
#
|
|
2195
|
+
def calculate_extsst_size(str_unique) #:nodoc:
|
|
2196
|
+
unique_strings = str_unique
|
|
2197
|
+
|
|
2198
|
+
if unique_strings < 1024
|
|
2199
|
+
bucket_size = 8
|
|
2200
|
+
else
|
|
2201
|
+
bucket_size = 1 + Integer(unique_strings / 128.0)
|
|
2202
|
+
end
|
|
2203
|
+
|
|
2204
|
+
buckets = Integer((unique_strings + bucket_size -1) / Float(bucket_size))
|
|
2205
|
+
|
|
2206
|
+
@extsst_buckets = buckets
|
|
2207
|
+
@extsst_bucket_size = bucket_size
|
|
2208
|
+
|
|
2209
|
+
6 + 8 * buckets
|
|
2210
|
+
end
|
|
2211
|
+
|
|
2212
|
+
#
|
|
2213
|
+
# Write EXTSST table using the offsets calculated in store_shared_strings().
|
|
2214
|
+
#
|
|
2215
|
+
def store_extsst #:nodoc:
|
|
2216
|
+
offsets = @extsst_offsets
|
|
2217
|
+
bucket_size = @extsst_bucket_size
|
|
2218
|
+
|
|
2219
|
+
record = 0x00FF # Record identifier
|
|
2220
|
+
length = 2 + 8 * offsets.size # Bytes to follow
|
|
2221
|
+
|
|
2222
|
+
header = [record, length].pack('vv')
|
|
2223
|
+
data = [bucket_size].pack('v')
|
|
2224
|
+
|
|
2225
|
+
offsets.each do |offset|
|
|
2226
|
+
data += [offset[0], offset[1], 0].pack('Vvv')
|
|
2227
|
+
end
|
|
2228
|
+
|
|
2229
|
+
append(header, data)
|
|
2230
|
+
end
|
|
2231
|
+
|
|
2232
|
+
#
|
|
2233
|
+
# Methods related to comments and MSO objects.
|
|
2234
|
+
#
|
|
2235
|
+
|
|
2236
|
+
#
|
|
2237
|
+
# Write the MSODRAWINGGROUP record that keeps track of the Escher drawing
|
|
2238
|
+
# objects in the file such as images, comments and filters.
|
|
2239
|
+
#
|
|
2240
|
+
def add_mso_drawing_group #:nodoc:
|
|
2241
|
+
return unless @mso_size != 0
|
|
2242
|
+
|
|
2243
|
+
record = 0x00EB # Record identifier
|
|
2244
|
+
length = 0x0000 # Number of bytes to follow
|
|
2245
|
+
|
|
2246
|
+
data = store_mso_dgg_container
|
|
2247
|
+
data += store_mso_dgg(*@mso_clusters)
|
|
2248
|
+
data += store_mso_bstore_container
|
|
2249
|
+
@images_data.each do |image|
|
|
2250
|
+
data += store_mso_images(image)
|
|
2251
|
+
end
|
|
2252
|
+
data += store_mso_opt
|
|
2253
|
+
data += store_mso_split_menu_colors
|
|
2254
|
+
|
|
2255
|
+
length = data.bytesize
|
|
2256
|
+
header = [record, length].pack("vv")
|
|
2257
|
+
|
|
2258
|
+
add_mso_drawing_group_continue(header + data)
|
|
2259
|
+
|
|
2260
|
+
header + data # For testing only.
|
|
2261
|
+
end
|
|
2262
|
+
|
|
2263
|
+
#
|
|
2264
|
+
# See first BIFFwriter#add_continue() method.
|
|
2265
|
+
#
|
|
2266
|
+
# Add specialised CONTINUE headers to large MSODRAWINGGROUP data block.
|
|
2267
|
+
# We use the Excel 97 max block size of 8228 - 4 bytes for the header = 8224.
|
|
2268
|
+
#
|
|
2269
|
+
# The structure depends on the size of the data block:
|
|
2270
|
+
#
|
|
2271
|
+
# Case 1: <= 8224 bytes 1 MSODRAWINGGROUP
|
|
2272
|
+
# Case 2: <= 2*8224 bytes 1 MSODRAWINGGROUP + 1 CONTINUE
|
|
2273
|
+
# Case 3: > 2*8224 bytes 2 MSODRAWINGGROUP + n CONTINUE
|
|
2274
|
+
#
|
|
2275
|
+
def add_mso_drawing_group_continue(data) #:nodoc:
|
|
2276
|
+
limit = 8228 -4
|
|
2277
|
+
mso_group = 0x00EB # Record identifier
|
|
2278
|
+
continue = 0x003C # Record identifier
|
|
2279
|
+
block_count = 1
|
|
2280
|
+
|
|
2281
|
+
# Ignore the base class add_continue() method.
|
|
2282
|
+
@ignore_continue = true
|
|
2283
|
+
|
|
2284
|
+
# Case 1 above. Just return the data as it is.
|
|
2285
|
+
if data.bytesize <= limit
|
|
2286
|
+
append(data)
|
|
2287
|
+
return
|
|
2288
|
+
end
|
|
2289
|
+
|
|
2290
|
+
# Change length field of the first MSODRAWINGGROUP block. Case 2 and 3.
|
|
2291
|
+
tmp, data = devide_string(data, limit + 4)
|
|
2292
|
+
tmp[2, 2] = [limit].pack('v')
|
|
2293
|
+
append(tmp)
|
|
2294
|
+
|
|
2295
|
+
# Add MSODRAWINGGROUP and CONTINUE blocks for Case 3 above.
|
|
2296
|
+
while data.bytesize > limit
|
|
2297
|
+
if block_count == 1
|
|
2298
|
+
# Add extra MSODRAWINGGROUP block header.
|
|
2299
|
+
header = [mso_group, limit].pack("vv")
|
|
2300
|
+
block_count += 1
|
|
2301
|
+
else
|
|
2302
|
+
# Add normal CONTINUE header.
|
|
2303
|
+
header = [continue, limit].pack("vv")
|
|
2304
|
+
end
|
|
2305
|
+
|
|
2306
|
+
tmp, data = devide_string(data, limit)
|
|
2307
|
+
append(header, tmp)
|
|
2308
|
+
end
|
|
2309
|
+
|
|
2310
|
+
# Last CONTINUE block for remaining data. Case 2 and 3 above.
|
|
2311
|
+
header = [continue, data.bytesize].pack("vv")
|
|
2312
|
+
append(header, data)
|
|
2313
|
+
|
|
2314
|
+
# Turn the base class add_continue() method back on.
|
|
2315
|
+
@ignore_continue = false
|
|
2316
|
+
end
|
|
2317
|
+
|
|
2318
|
+
def devide_string(string, nth) #:nodoc:
|
|
2319
|
+
first_string = string[0, nth]
|
|
2320
|
+
latter_string = string[nth, string.size - nth]
|
|
2321
|
+
[first_string, latter_string]
|
|
2322
|
+
end
|
|
2323
|
+
|
|
2324
|
+
#
|
|
2325
|
+
# Write the Escher DggContainer record that is part of MSODRAWINGGROUP.
|
|
2326
|
+
#
|
|
2327
|
+
def store_mso_dgg_container #:nodoc:
|
|
2328
|
+
type = 0xF000
|
|
2329
|
+
version = 15
|
|
2330
|
+
instance = 0
|
|
2331
|
+
data = ''
|
|
2332
|
+
length = @mso_size -12 # -4 (biff header) -8 (for this).
|
|
2333
|
+
|
|
2334
|
+
add_mso_generic(type, version, instance, data, length)
|
|
2335
|
+
end
|
|
2336
|
+
|
|
2337
|
+
#
|
|
2338
|
+
# Write the Escher Dgg record that is part of MSODRAWINGGROUP.
|
|
2339
|
+
#
|
|
2340
|
+
def store_mso_dgg(max_spid, num_clusters, shapes_saved, drawings_saved, clusters) #:nodoc:
|
|
2341
|
+
type = 0xF006
|
|
2342
|
+
version = 0
|
|
2343
|
+
instance = 0
|
|
2344
|
+
data = ''
|
|
2345
|
+
length = nil # Calculate automatically.
|
|
2346
|
+
|
|
2347
|
+
data = [max_spid, num_clusters,
|
|
2348
|
+
shapes_saved, drawings_saved].pack("VVVV")
|
|
2349
|
+
|
|
2350
|
+
clusters.each do |aref|
|
|
2351
|
+
drawing_id = aref[0]
|
|
2352
|
+
shape_ids_used = aref[1]
|
|
2353
|
+
|
|
2354
|
+
data += [drawing_id, shape_ids_used].pack("VV")
|
|
2355
|
+
end
|
|
2356
|
+
|
|
2357
|
+
add_mso_generic(type, version, instance, data, length)
|
|
2358
|
+
end
|
|
2359
|
+
|
|
2360
|
+
#
|
|
2361
|
+
# Write the Escher BstoreContainer record that is part of MSODRAWINGGROUP.
|
|
2362
|
+
#
|
|
2363
|
+
def store_mso_bstore_container #:nodoc:
|
|
2364
|
+
return '' if @images_size == 0
|
|
2365
|
+
|
|
2366
|
+
type = 0xF001
|
|
2367
|
+
version = 15
|
|
2368
|
+
instance = @images_data.size # Number of images.
|
|
2369
|
+
data = ''
|
|
2370
|
+
length = @images_size +8 *instance
|
|
2371
|
+
|
|
2372
|
+
add_mso_generic(type, version, instance, data, length)
|
|
2373
|
+
end
|
|
2374
|
+
|
|
2375
|
+
#
|
|
2376
|
+
# Write the Escher BstoreContainer record that is part of MSODRAWINGGROUP.
|
|
2377
|
+
#
|
|
2378
|
+
def store_mso_images(image)
|
|
2379
|
+
blip_store_entry = store_mso_blip_store_entry(
|
|
2380
|
+
image.ref_count, image.type, image.size, image.checksum1
|
|
2381
|
+
)
|
|
2382
|
+
|
|
2383
|
+
blip = store_mso_blip(
|
|
2384
|
+
image.type, image.data, image.size, image.checksum1, image.checksum2
|
|
2385
|
+
)
|
|
2386
|
+
|
|
2387
|
+
blip_store_entry + blip
|
|
2388
|
+
end
|
|
2389
|
+
|
|
2390
|
+
#
|
|
2391
|
+
# Write the Escher BlipStoreEntry record that is part of MSODRAWINGGROUP.
|
|
2392
|
+
#
|
|
2393
|
+
def store_mso_blip_store_entry(ref_count, image_type, size, checksum1) #:nodoc:
|
|
2394
|
+
type = 0xF007
|
|
2395
|
+
version = 2
|
|
2396
|
+
instance = image_type
|
|
2397
|
+
length = size +61
|
|
2398
|
+
data = [image_type].pack('C') + # Win32
|
|
2399
|
+
[image_type].pack('C') + # Mac
|
|
2400
|
+
[checksum1].pack('H*') + # Uid checksum
|
|
2401
|
+
[0xFF].pack('v') + # Tag
|
|
2402
|
+
[size +25].pack('V') + # Next Blip size
|
|
2403
|
+
[ref_count].pack('V') + # Image ref count
|
|
2404
|
+
[0x00000000].pack('V') + # File offset
|
|
2405
|
+
[0x00].pack('C') + # Usage
|
|
2406
|
+
[0x00].pack('C') + # Name length
|
|
2407
|
+
[0x00].pack('C') + # Unused
|
|
2408
|
+
[0x00].pack('C') # Unused
|
|
2409
|
+
|
|
2410
|
+
add_mso_generic(type, version, instance, data, length)
|
|
2411
|
+
end
|
|
2412
|
+
|
|
2413
|
+
#
|
|
2414
|
+
# Write the Escher Blip record that is part of MSODRAWINGGROUP.
|
|
2415
|
+
#
|
|
2416
|
+
def store_mso_blip(image_type, image_data, size, checksum1, checksum2) #:nodoc:
|
|
2417
|
+
instance = 0x046A if image_type == 5 # JPG
|
|
2418
|
+
instance = 0x06E0 if image_type == 6 # PNG
|
|
2419
|
+
instance = 0x07A9 if image_type == 7 # BMP
|
|
2420
|
+
|
|
2421
|
+
# BMPs contain an extra checksum for the stripped data.
|
|
2422
|
+
if image_type == 7
|
|
2423
|
+
checksum1 = checksum2 + checksum1
|
|
2424
|
+
end
|
|
2425
|
+
|
|
2426
|
+
type = 0xF018 + image_type
|
|
2427
|
+
version = 0x0000
|
|
2428
|
+
length = size +17
|
|
2429
|
+
data = [checksum1].pack('H*') + # Uid checksum
|
|
2430
|
+
[0xFF].pack('C') + # Tag
|
|
2431
|
+
image_data # Image
|
|
2432
|
+
|
|
2433
|
+
add_mso_generic(type, version, instance, data, length)
|
|
2434
|
+
end
|
|
2435
|
+
|
|
2436
|
+
#
|
|
2437
|
+
# Write the Escher Opt record that is part of MSODRAWINGGROUP.
|
|
2438
|
+
#
|
|
2439
|
+
def store_mso_opt #:nodoc:
|
|
2440
|
+
type = 0xF00B
|
|
2441
|
+
version = 3
|
|
2442
|
+
instance = 3
|
|
2443
|
+
data = ''
|
|
2444
|
+
length = 18
|
|
2445
|
+
|
|
2446
|
+
data = ['BF0008000800810109000008C0014000'+'0008'].pack("H*")
|
|
2447
|
+
|
|
2448
|
+
add_mso_generic(type, version, instance, data, length)
|
|
2449
|
+
end
|
|
2450
|
+
|
|
2451
|
+
#
|
|
2452
|
+
# Write the Escher SplitMenuColors record that is part of MSODRAWINGGROUP.
|
|
2453
|
+
#
|
|
2454
|
+
def store_mso_split_menu_colors #:nodoc:
|
|
2455
|
+
type = 0xF11E
|
|
2456
|
+
version = 0
|
|
2457
|
+
instance = 4
|
|
2458
|
+
data = ''
|
|
2459
|
+
length = 16
|
|
2460
|
+
|
|
2461
|
+
data = ['0D0000080C00000817000008F7000010'].pack("H*")
|
|
2462
|
+
|
|
2463
|
+
add_mso_generic(type, version, instance, data, length)
|
|
2464
|
+
end
|
|
2465
|
+
|
|
2466
|
+
def cleanup #:nodoc:
|
|
2467
|
+
super
|
|
2468
|
+
sheets.each { |sheet| sheet.cleanup }
|
|
2469
|
+
end
|
|
2470
|
+
|
|
2471
|
+
def add_doc_properties
|
|
2472
|
+
@add_doc_properties ||= false
|
|
2473
|
+
end
|
|
2474
|
+
|
|
2475
|
+
def defined_names
|
|
2476
|
+
@defined_names
|
|
2477
|
+
end
|
|
2478
|
+
end
|