ricardoo27-writeexcel 0.6.12.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|