spreadsheet 1.3.3 → 1.3.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/parseexcel/parseexcel.rb +66 -58
- data/lib/parseexcel/parser.rb +1 -1
- data/lib/parseexcel.rb +1 -1
- data/lib/spreadsheet/column.rb +11 -9
- data/lib/spreadsheet/compatibility.rb +3 -1
- data/lib/spreadsheet/datatypes.rb +149 -147
- data/lib/spreadsheet/encodings.rb +20 -16
- data/lib/spreadsheet/errors.rb +2 -2
- data/lib/spreadsheet/excel/error.rb +23 -22
- data/lib/spreadsheet/excel/internals/biff5.rb +11 -11
- data/lib/spreadsheet/excel/internals/biff8.rb +13 -13
- data/lib/spreadsheet/excel/internals.rb +451 -451
- data/lib/spreadsheet/excel/offset.rb +32 -31
- data/lib/spreadsheet/excel/password_hash.rb +18 -18
- data/lib/spreadsheet/excel/reader/biff5.rb +34 -35
- data/lib/spreadsheet/excel/reader/biff8.rb +234 -222
- data/lib/spreadsheet/excel/reader.rb +1320 -1274
- data/lib/spreadsheet/excel/rgb.rb +91 -91
- data/lib/spreadsheet/excel/row.rb +99 -91
- data/lib/spreadsheet/excel/sst_entry.rb +40 -38
- data/lib/spreadsheet/excel/workbook.rb +86 -76
- data/lib/spreadsheet/excel/worksheet.rb +125 -107
- data/lib/spreadsheet/excel/writer/biff8.rb +56 -55
- data/lib/spreadsheet/excel/writer/format.rb +273 -256
- data/lib/spreadsheet/excel/writer/n_worksheet.rb +837 -798
- data/lib/spreadsheet/excel/writer/workbook.rb +671 -635
- data/lib/spreadsheet/excel/writer/worksheet.rb +898 -861
- data/lib/spreadsheet/excel/writer.rb +1 -1
- data/lib/spreadsheet/excel.rb +18 -11
- data/lib/spreadsheet/font.rb +30 -26
- data/lib/spreadsheet/format.rb +74 -59
- data/lib/spreadsheet/link.rb +7 -5
- data/lib/spreadsheet/note.rb +6 -6
- data/lib/spreadsheet/noteObject.rb +5 -5
- data/lib/spreadsheet/row.rb +33 -23
- data/lib/spreadsheet/version.rb +1 -1
- data/lib/spreadsheet/workbook.rb +27 -13
- data/lib/spreadsheet/worksheet.rb +102 -68
- data/lib/spreadsheet/writer.rb +3 -0
- data/lib/spreadsheet.rb +12 -15
- data/test/excel/reader.rb +8 -8
- data/test/excel/row.rb +35 -31
- data/test/excel/writer/workbook.rb +18 -16
- data/test/excel/writer/worksheet.rb +10 -8
- data/test/font.rb +44 -32
- data/test/format.rb +38 -33
- data/test/integration.rb +627 -598
- data/test/row.rb +5 -3
- data/test/suite.rb +7 -7
- data/test/workbook.rb +15 -14
- data/test/workbook_protection.rb +5 -5
- data/test/worksheet.rb +36 -34
- metadata +48 -6
@@ -1,650 +1,686 @@
|
|
1
|
-
require
|
2
|
-
require
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require
|
6
|
-
require
|
1
|
+
require "spreadsheet/excel/internals"
|
2
|
+
require "spreadsheet/writer"
|
3
|
+
require "spreadsheet/excel/writer/biff8"
|
4
|
+
require "spreadsheet/excel/writer/format"
|
5
|
+
require "spreadsheet/excel/writer/worksheet"
|
6
|
+
require "ole/storage"
|
7
7
|
|
8
8
|
module Spreadsheet
|
9
9
|
module Excel
|
10
10
|
module Writer
|
11
|
-
##
|
12
|
-
# Writer class for Excel Workbooks. Most write_* method correspond to an
|
13
|
-
# Excel-Record/Opcode. Designed to be able to write several Workbooks in
|
14
|
-
# parallel (just because I can't imagine why you would want to do that
|
15
|
-
# doesn't mean it shouldn't be possible ;). You should not need to call any of
|
16
|
-
# its methods directly. If you think you do, look at #write_workbook
|
17
|
-
class Workbook < Spreadsheet::Writer
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
11
|
+
##
|
12
|
+
# Writer class for Excel Workbooks. Most write_* method correspond to an
|
13
|
+
# Excel-Record/Opcode. Designed to be able to write several Workbooks in
|
14
|
+
# parallel (just because I can't imagine why you would want to do that
|
15
|
+
# doesn't mean it shouldn't be possible ;). You should not need to call any of
|
16
|
+
# its methods directly. If you think you do, look at #write_workbook
|
17
|
+
class Workbook < Spreadsheet::Writer
|
18
|
+
include Spreadsheet::Excel::Writer::Biff8
|
19
|
+
include Spreadsheet::Excel::Internals
|
20
|
+
attr_reader :fonts, :date_base
|
21
|
+
def initialize *args
|
22
|
+
super
|
23
|
+
@biff_version = 0x0600
|
24
|
+
@bof = 0x0809
|
25
|
+
@build_id = 3515
|
26
|
+
@build_year = 1996
|
27
|
+
@bof_types = {
|
28
|
+
globals: 0x0005,
|
29
|
+
visual_basic: 0x0006,
|
30
|
+
worksheet: 0x0010,
|
31
|
+
chart: 0x0020,
|
32
|
+
macro_sheet: 0x0040,
|
33
|
+
workspace: 0x0100
|
34
|
+
}
|
35
|
+
@worksheets = {}
|
36
|
+
@sst = {}
|
37
|
+
@recordsize_limit = 8224
|
38
|
+
@fonts = {}
|
39
|
+
@formats = {}
|
40
|
+
@number_formats = {}
|
41
|
+
end
|
42
|
+
|
43
|
+
def cleanup workbook
|
44
|
+
worksheets(workbook).each do |worksheet|
|
45
|
+
@sst.delete worksheet
|
46
|
+
end
|
47
|
+
@fonts.delete workbook
|
48
|
+
@formats.delete workbook
|
49
|
+
@number_formats.delete workbook
|
50
|
+
@worksheets.delete workbook
|
51
|
+
end
|
52
|
+
|
53
|
+
def collect_formats workbook, opts = {}
|
54
|
+
# The default cell format is always present in an Excel file, described by
|
55
|
+
# the XF record with the fixed index 15 (0-based). By default, it uses the
|
56
|
+
# worksheet/workbook default cell style, described by the very first XF
|
57
|
+
# record (index 0).
|
58
|
+
formats = []
|
59
|
+
unless opts[:existing_document]
|
60
|
+
15.times do
|
61
|
+
formats.push Format.new(self, workbook, workbook.default_format,
|
62
|
+
type: :style)
|
63
|
+
end
|
64
|
+
formats.push Format.new(self, workbook)
|
65
|
+
end
|
66
|
+
workbook.formats.each do |fmt|
|
67
|
+
formats.push Format.new(self, workbook, fmt)
|
68
|
+
end
|
69
|
+
@formats[workbook] = {
|
70
|
+
writers: [],
|
71
|
+
xf_indexes: {}
|
72
|
+
}
|
73
|
+
formats.each_with_index do |fmt, idx|
|
74
|
+
@formats[workbook][:writers] << fmt
|
75
|
+
@formats[workbook][:xf_indexes][fmt.format] ||= idx
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def complete_sst_update? workbook
|
80
|
+
stored = workbook.sst.collect { |entry| entry.content }
|
81
|
+
num_total = 0
|
82
|
+
current = worksheets(workbook).each_with_object(Hash.new(0)) do |worksheet, memo|
|
83
|
+
worksheet.strings.each do |k, v|
|
84
|
+
memo[k] += v
|
85
|
+
num_total += v
|
86
|
+
end
|
87
|
+
end
|
88
|
+
current.delete ""
|
89
|
+
if !stored.empty? && stored.all? { |x| current.include?(x) }
|
90
|
+
## if all previously stored strings are still needed, we don't have to
|
91
|
+
# rewrite all cells because the sst-index of such string does not change.
|
92
|
+
additions = current.keys - stored
|
93
|
+
[:partial_update, num_total, stored + additions]
|
94
|
+
else
|
95
|
+
[:complete_update, num_total, current.keys]
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def font_index workbook, font_key
|
100
|
+
idx = @fonts[workbook][font_key] || 0
|
101
|
+
## this appears to be undocumented: the first 4 fonts seem to be accessed
|
102
|
+
# with a 0-based index, but all subsequent font indices are 1-based.
|
103
|
+
(idx > 3) ? idx.next : idx
|
104
|
+
end
|
105
|
+
|
106
|
+
def number_format_index workbook, format
|
107
|
+
@number_formats[workbook][format] || 0
|
108
|
+
end
|
109
|
+
|
110
|
+
def sanitize_worksheets sheets
|
111
|
+
return sheets if sheets.empty?
|
112
|
+
found_selected = false
|
113
|
+
sheets.each do |sheet|
|
114
|
+
found_selected ||= sheet.selected
|
115
|
+
sheet.format_dates!
|
116
|
+
end
|
117
|
+
unless found_selected
|
118
|
+
sheets.first.selected = true
|
119
|
+
end
|
120
|
+
sheets
|
121
|
+
end
|
122
|
+
|
123
|
+
def worksheets workbook
|
124
|
+
@worksheets[workbook] ||= workbook.worksheets.collect do |worksheet|
|
125
|
+
Excel::Writer::Worksheet.new self, worksheet
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def write_bof workbook, writer, type
|
130
|
+
data = [
|
131
|
+
@biff_version, # BIFF version (always 0x0600 for BIFF8)
|
132
|
+
@bof_types[type], # Type of the following data:
|
133
|
+
# 0x0005 = Workbook globals
|
134
|
+
# 0x0006 = Visual Basic module
|
135
|
+
# 0x0010 = Worksheet
|
136
|
+
# 0x0020 = Chart
|
137
|
+
# 0x0040 = Macro sheet
|
138
|
+
# 0x0100 = Workspace file
|
139
|
+
@build_id, # Build identifier
|
140
|
+
@build_year, # Build year
|
141
|
+
0x000, # File history flags
|
142
|
+
0x006 # Lowest Excel version that can read
|
143
|
+
# all records in this file
|
144
|
+
]
|
145
|
+
write_op writer, @bof, data.pack("v4V2")
|
146
|
+
end
|
147
|
+
|
148
|
+
def write_bookbool workbook, writer
|
149
|
+
write_placeholder writer, 0x00da
|
150
|
+
end
|
151
|
+
|
152
|
+
def write_boundsheets workbook, writer, offset
|
153
|
+
worksheets = worksheets(workbook)
|
154
|
+
worksheets.each do |worksheet|
|
155
|
+
# account for boundsheet-entry
|
156
|
+
offset += worksheet.boundsheet_size
|
157
|
+
end
|
158
|
+
worksheets.each do |worksheet|
|
159
|
+
visibility = SEITILIBISIV_TEEHSKROW[worksheet.worksheet.visibility]
|
160
|
+
data = [
|
161
|
+
offset, # Absolute stream position of the BOF record of the sheet
|
162
|
+
# represented by this record. This field is never encrypted
|
163
|
+
# in protected files.
|
164
|
+
visibility, # Visibility: 0x00 = Visible
|
165
|
+
# 0x01 = Hidden
|
166
|
+
# 0x02 = Strong hidden (see below)
|
167
|
+
0x00 # Sheet type: 0x00 = Worksheet
|
168
|
+
# 0x02 = Chart
|
169
|
+
# 0x06 = Visual Basic module
|
170
|
+
]
|
171
|
+
write_op writer, 0x0085, data.pack("VC2"), worksheet.name
|
172
|
+
offset += worksheet.size
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
##
|
177
|
+
def write_datemode workbook, writer
|
178
|
+
mode = (@date_base.year == 1899) ? 0x00 : 0x01
|
179
|
+
data = [
|
180
|
+
mode # 0 = Base date is 1899-Dec-31
|
171
181
|
# (the cell value 1 represents 1900-Jan-01)
|
172
182
|
# 1 = Base date is 1904-Jan-01
|
173
183
|
# (the cell value 1 represents 1904-Jan-02)
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
184
|
+
]
|
185
|
+
write_op writer, 0x0022, data.pack("v")
|
186
|
+
end
|
187
|
+
|
188
|
+
def write_dsf workbook, writer
|
189
|
+
data = [
|
190
|
+
0x00 # 0 = Only the BIFF8 “Workbook” stream is present
|
180
191
|
# 1 = Additional BIFF5/BIFF7 “Book” stream is in the file
|
181
|
-
|
182
|
-
|
183
|
-
end
|
184
|
-
def write_encoding workbook, writer
|
185
|
-
enc = workbook.encoding || 'UTF-16LE'
|
186
|
-
if RUBY_VERSION >= '1.9' && enc.is_a?(Encoding)
|
187
|
-
enc = enc.name.upcase
|
188
|
-
end
|
189
|
-
cp = SEGAPEDOC.fetch(enc) do
|
190
|
-
raise Spreadsheet::Errors::UnknownCodepage, "Invalid or Unknown Codepage '#{enc}'"
|
191
|
-
end
|
192
|
-
write_op writer, 0x0042, [cp].pack('v')
|
193
|
-
end
|
194
|
-
def write_eof workbook, writer
|
195
|
-
write_op writer, 0x000a
|
196
|
-
end
|
197
|
-
def write_extsst workbook, offsets, writer
|
198
|
-
header = [SST_CHUNKSIZE].pack('v')
|
199
|
-
data = offsets.collect do |pair| pair.push(0).pack('Vv2') end
|
200
|
-
write_op writer, 0x00ff, header, data
|
201
|
-
end
|
202
|
-
def write_font workbook, writer, font
|
203
|
-
# TODO: Colors/Palette index
|
204
|
-
size = font.size * TWIPS
|
205
|
-
color = SEDOC_ROLOC[font.color] || SEDOC_ROLOC[:text]
|
206
|
-
weight = FONT_WEIGHTS.fetch(font.weight, font.weight)
|
207
|
-
weight = [[weight, 1000].min, 100].max
|
208
|
-
esc = SEPYT_TNEMEPACSE.fetch(font.escapement, 0)
|
209
|
-
underline = SEPYT_ENILREDNU.fetch(font.underline, 0)
|
210
|
-
family = SEILIMAF_TNOF.fetch(font.family, 0)
|
211
|
-
encoding = SGNIDOCNE_TNOF.fetch(font.encoding, 0)
|
212
|
-
options = 0
|
213
|
-
options |= 0x0001 if weight > 600
|
214
|
-
options |= 0x0002 if font.italic?
|
215
|
-
options |= 0x0004 if underline > 0
|
216
|
-
options |= 0x0008 if font.strikeout?
|
217
|
-
options |= 0x0010 if font.outline?
|
218
|
-
options |= 0x0020 if font.shadow?
|
219
|
-
data = [
|
220
|
-
size, # Height of the font (in twips = 1/20 of a point)
|
221
|
-
options, # Option flags:
|
222
|
-
# Bit Mask Contents
|
223
|
-
# 0 0x0001 1 = Characters are bold (redundant, see below)
|
224
|
-
# 1 0x0002 1 = Characters are italic
|
225
|
-
# 2 0x0004 1 = Characters are underlined (redundant)
|
226
|
-
# 3 0x0008 1 = Characters are struck out
|
227
|
-
# 4 0x0010 1 = Characters are outlined (djberger)
|
228
|
-
# 5 0x0020 1 = Characters are shadowed (djberger)
|
229
|
-
color, # Palette index (➜ 6.70)
|
230
|
-
weight, # Font weight (100-1000). Standard values are
|
231
|
-
# 0x0190 (400) for normal text and
|
232
|
-
# 0x02bc (700) for bold text.
|
233
|
-
esc, # Escapement type: 0x0000 = None
|
234
|
-
# 0x0001 = Superscript
|
235
|
-
# 0x0002 = Subscript
|
236
|
-
underline,# Underline type: 0x00 = None
|
237
|
-
# 0x01 = Single
|
238
|
-
# 0x02 = Double
|
239
|
-
# 0x21 = Single accounting
|
240
|
-
# 0x22 = Double accounting
|
241
|
-
family, # Font family: 0x00 = None (unknown or don't care)
|
242
|
-
# 0x01 = Roman (variable width, serifed)
|
243
|
-
# 0x02 = Swiss (variable width, sans-serifed)
|
244
|
-
# 0x03 = Modern (fixed width,
|
245
|
-
# serifed or sans-serifed)
|
246
|
-
# 0x04 = Script (cursive)
|
247
|
-
# 0x05 = Decorative (specialised,
|
248
|
-
# e.g. Old English, Fraktur)
|
249
|
-
encoding, # Character set: 0x00 = 0 = ANSI Latin
|
250
|
-
# 0x01 = 1 = System default
|
251
|
-
# 0x02 = 2 = Symbol
|
252
|
-
# 0x4d = 77 = Apple Roman
|
253
|
-
# 0x80 = 128 = ANSI Japanese Shift-JIS
|
254
|
-
# 0x81 = 129 = ANSI Korean (Hangul)
|
255
|
-
# 0x82 = 130 = ANSI Korean (Johab)
|
256
|
-
# 0x86 = 134 = ANSI Chinese Simplified GBK
|
257
|
-
# 0x88 = 136 = ANSI Chinese Traditional BIG5
|
258
|
-
# 0xa1 = 161 = ANSI Greek
|
259
|
-
# 0xa2 = 162 = ANSI Turkish
|
260
|
-
# 0xa3 = 163 = ANSI Vietnamese
|
261
|
-
# 0xb1 = 177 = ANSI Hebrew
|
262
|
-
# 0xb2 = 178 = ANSI Arabic
|
263
|
-
# 0xba = 186 = ANSI Baltic
|
264
|
-
# 0xcc = 204 = ANSI Cyrillic
|
265
|
-
# 0xde = 222 = ANSI Thai
|
266
|
-
# 0xee = 238 = ANSI Latin II (Central European)
|
267
|
-
# 0xff = 255 = OEM Latin I
|
268
|
-
]
|
269
|
-
name = unicode_string font.name # Font name: Unicode string,
|
270
|
-
# 8-bit string length (➜ 3.4)
|
271
|
-
write_op writer, opcode(:font), data.pack(binfmt(:font)), name
|
272
|
-
end
|
273
|
-
def write_fonts workbook, writer
|
274
|
-
fonts = @fonts[workbook] = {}
|
275
|
-
@formats[workbook][:writers].map{|format| format.font }.compact.uniq.each do |font|
|
276
|
-
unless fonts.include?(font.key)
|
277
|
-
fonts.store font.key, fonts.size
|
278
|
-
write_font workbook, writer, font
|
279
|
-
end
|
280
|
-
end
|
281
|
-
end
|
282
|
-
def write_formats workbook, writer
|
283
|
-
# From BIFF5 on, the built-in number formats will be omitted. The built-in
|
284
|
-
# formats are dependent on the current regional settings of the operating
|
285
|
-
# system. BUILTIN_FORMATS shows which number formats are used by
|
286
|
-
# default in a US-English environment. All indexes from 0 to 163 are
|
287
|
-
# reserved for built-in formats.
|
288
|
-
# The first user-defined format starts at 164 (0xa4).
|
289
|
-
formats = @number_formats[workbook] = {}
|
290
|
-
BUILTIN_FORMATS.each do |idx, str|
|
291
|
-
formats.store client(str, 'UTF-8'), idx
|
292
|
-
end
|
293
|
-
## Ensure at least a 'GENERAL' format is written
|
294
|
-
formats.delete client('GENERAL', 'UTF-8')
|
295
|
-
idx = 0xa4
|
296
|
-
workbook.formats.each do |fmt|
|
297
|
-
str = fmt.number_format
|
298
|
-
unless formats[str]
|
299
|
-
formats.store str, idx
|
300
|
-
# Number format string (Unicode string, 16-bit string length, ➜ 3.4)
|
301
|
-
write_op writer, opcode(:format), [idx].pack('v'), unicode_string(str, 2)
|
302
|
-
idx += 1
|
303
|
-
end
|
304
|
-
end
|
305
|
-
end
|
306
|
-
##
|
307
|
-
# Write a new Excel file.
|
308
|
-
def write_from_scratch workbook, io
|
309
|
-
sanitize_worksheets workbook.worksheets
|
310
|
-
collect_formats workbook
|
311
|
-
sheets = worksheets workbook
|
312
|
-
buffer1 = StringIO.new ''.dup
|
313
|
-
# ● BOF Type = workbook globals (➜ 6.8)
|
314
|
-
write_bof workbook, buffer1, :globals
|
315
|
-
# ○ File Protection Block ➜ 4.19
|
316
|
-
# ○ WRITEACCESS User name (BIFF3-BIFF8, ➜ 5.112)
|
317
|
-
# ○ FILESHARING File sharing options (BIFF3-BIFF8, ➜ 5.44)
|
318
|
-
# ○ CODEPAGE ➜ 6.17
|
319
|
-
write_encoding workbook, buffer1
|
320
|
-
# ○ DSF ➜ 6.32
|
321
|
-
write_dsf workbook, buffer1
|
322
|
-
# ○ TABID
|
323
|
-
write_tabid workbook, buffer1
|
324
|
-
# ○ FNGROUPCOUNT
|
325
|
-
# ○ Workbook Protection Block ➜ 4.18
|
326
|
-
# ○ WINDOWPROTECT Window settings: 1 = protected (➜ 5.111)
|
327
|
-
# ○ PROTECT Cell contents: 1 = protected (➜ 5.82)
|
328
|
-
write_protect workbook, buffer1
|
329
|
-
# ○ OBJECTPROTECT Embedded objects: 1 = protected (➜ 5.72)
|
330
|
-
# ○ PASSWORD Hash value of the password; 0 = No password (➜ 5.76)
|
331
|
-
write_password workbook, buffer1
|
332
|
-
# ○ BACKUP ➜ 5.5
|
333
|
-
# ○ HIDEOBJ ➜ 5.56
|
334
|
-
# ● WINDOW1 ➜ 5.109
|
335
|
-
write_window1 workbook, buffer1
|
336
|
-
# ○ DATEMODE ➜ 5.28
|
337
|
-
write_datemode workbook, buffer1
|
338
|
-
# ○ PRECISION ➜ 5.79
|
339
|
-
write_precision workbook, buffer1
|
340
|
-
# ○ REFRESHALL
|
341
|
-
write_refreshall workbook, buffer1
|
342
|
-
# ○ BOOKBOOL ➜ 5.9
|
343
|
-
write_bookbool workbook, buffer1
|
344
|
-
# ●● FONT ➜ 5.45
|
345
|
-
write_fonts workbook, buffer1
|
346
|
-
# ○○ FORMAT ➜ 5.49
|
347
|
-
write_formats workbook, buffer1
|
348
|
-
# ●● XF ➜ 5.115
|
349
|
-
write_xfs workbook, buffer1
|
350
|
-
# ●● STYLE ➜ 5.103
|
351
|
-
write_styles workbook, buffer1
|
352
|
-
# ○ PALETTE ➜ 5.74
|
353
|
-
write_palette workbook, buffer1
|
354
|
-
# ○ USESELFS ➜ 5.106
|
355
|
-
buffer1.rewind
|
356
|
-
# ●● BOUNDSHEET ➜ 5.95
|
357
|
-
buffer2 = StringIO.new ''.dup
|
358
|
-
# ○ COUNTRY ➜ 5.22
|
359
|
-
# ○ Link Table ➜ 4.10.3
|
360
|
-
# ○○ NAME ➜ 6.66
|
361
|
-
# ○ Shared String Table ➜ 4.11
|
362
|
-
# ● SST ➜ 5.100
|
363
|
-
# ● EXTSST ➜ 5.42
|
364
|
-
write_sst workbook, buffer2, buffer1.size
|
365
|
-
# ● EOF ➜ 5.37
|
366
|
-
write_eof workbook, buffer2
|
367
|
-
buffer2.rewind
|
368
|
-
# worksheet data can only be assembled after write_sst
|
369
|
-
sheets.each do |worksheet| worksheet.write_from_scratch end
|
370
|
-
Ole::Storage.open io do |ole|
|
371
|
-
ole.file.open 'Workbook', 'w' do |writer|
|
372
|
-
writer.write buffer1.read
|
373
|
-
write_boundsheets workbook, writer, buffer1.size + buffer2.size
|
374
|
-
writer.write buffer2.read
|
375
|
-
sheets.each do |worksheet|
|
376
|
-
writer.write worksheet.data
|
192
|
+
]
|
193
|
+
write_op writer, 0x0161, data.pack("v")
|
377
194
|
end
|
378
|
-
end
|
379
|
-
end
|
380
|
-
end
|
381
|
-
def write_op writer, op, *args
|
382
|
-
data = args.join
|
383
|
-
limited = data.slice!(0...@recordsize_limit)
|
384
|
-
writer.write [op,limited.size].pack("v2")
|
385
|
-
writer.write limited
|
386
|
-
data
|
387
|
-
end
|
388
|
-
def write_password workbook, writer
|
389
|
-
write_placeholder writer, 0x0013
|
390
|
-
end
|
391
|
-
def write_placeholder writer, op, value=0x0000, fmt='v'
|
392
|
-
write_op writer, op, [value].pack(fmt)
|
393
|
-
end
|
394
|
-
def write_precision workbook, writer
|
395
|
-
# 0 = Use displayed values; 1 = Use real cell values
|
396
|
-
write_placeholder writer, 0x000e, 0x0001
|
397
|
-
end
|
398
|
-
def write_protect workbook, writer
|
399
|
-
write_placeholder writer, 0x0012
|
400
|
-
end
|
401
|
-
def write_refreshall workbook, writer
|
402
|
-
write_placeholder writer, 0x01b7
|
403
|
-
end
|
404
|
-
def write_sst workbook, writer, offset
|
405
|
-
# Offset Size Contents
|
406
|
-
# 0 4 Total number of strings in the workbook (see below)
|
407
|
-
# 4 4 Number of following strings (nm)
|
408
|
-
# 8 var. List of nm Unicode strings, 16-bit string length (➜ 3.4)
|
409
|
-
num_total = 0
|
410
|
-
strings = worksheets(workbook).inject(Hash.new(0)) do |memo, worksheet|
|
411
|
-
worksheet.strings.each do |k,v|
|
412
|
-
memo[k] += v
|
413
|
-
num_total += v
|
414
|
-
end
|
415
|
-
memo
|
416
|
-
end
|
417
|
-
_write_sst workbook, writer, offset, num_total, strings.keys
|
418
|
-
end
|
419
|
-
def _write_sst workbook, writer, offset, total, strings
|
420
|
-
sst = {}
|
421
|
-
worksheets(workbook).each do |worksheet|
|
422
|
-
offset += worksheet.boundsheet_size
|
423
|
-
@sst[worksheet] = sst
|
424
|
-
end
|
425
|
-
sst_size = strings.size
|
426
|
-
data = [total, sst_size].pack 'V2'
|
427
|
-
op = 0x00fc
|
428
|
-
wide = 0
|
429
|
-
offsets = []
|
430
|
-
strings.each_with_index do |string, idx|
|
431
|
-
sst.store string, idx
|
432
|
-
op_offset = data.size + 4
|
433
|
-
if idx % SST_CHUNKSIZE == 0
|
434
|
-
offsets.push [offset + writer.pos + op_offset, op_offset]
|
435
|
-
end
|
436
|
-
header, packed, next_wide = _unicode_string string, 2
|
437
|
-
# the first few bytes (header + first character) must not be split
|
438
|
-
must_fit = header.size + wide + 1
|
439
|
-
while data.size + must_fit > @recordsize_limit
|
440
|
-
op, data, wide = write_string_part writer, op, data, wide
|
441
|
-
end
|
442
|
-
wide = next_wide
|
443
|
-
data << header << packed
|
444
|
-
end
|
445
|
-
until data.empty?
|
446
|
-
op, data, wide = write_string_part writer, op, data, wide
|
447
|
-
end
|
448
|
-
write_extsst workbook, offsets, writer
|
449
|
-
end
|
450
|
-
def write_sst_changes workbook, writer, offset, total, strings
|
451
|
-
_write_sst workbook, writer, offset, total, strings
|
452
|
-
end
|
453
|
-
def write_string_part writer, op, data, wide
|
454
|
-
bef = data.size
|
455
|
-
## if we're writing wide characters, we need to make sure we don't cut
|
456
|
-
# characters in half
|
457
|
-
if wide > 0 && data.size > @recordsize_limit
|
458
|
-
remove = @recordsize_limit - bef
|
459
|
-
remove -= remove % 2
|
460
|
-
rest = data.slice!(remove..-1)
|
461
|
-
write_op writer, op, data
|
462
|
-
data = rest
|
463
|
-
else
|
464
|
-
data = write_op writer, op, data
|
465
|
-
end
|
466
|
-
op = 0x003c
|
467
|
-
# Unicode strings are split in a special way. At the beginning of each
|
468
|
-
# CONTINUE record the option flags byte is repeated. Only the
|
469
|
-
# character size flag will be set in this flags byte, the Rich-Text
|
470
|
-
# flag and the Far-East flag are set to zero.
|
471
|
-
unless data.empty?
|
472
|
-
if wide == 1
|
473
|
-
# check if we can compress the rest of the string
|
474
|
-
data, wide = compress_unicode_string data
|
475
|
-
end
|
476
|
-
data = [wide].pack('C') << data
|
477
|
-
end
|
478
|
-
[op, data, wide]
|
479
|
-
end
|
480
|
-
def write_styles workbook, writer
|
481
|
-
# TODO: Style implementation. The following is simply a standard builtin
|
482
|
-
# style.
|
483
|
-
# TODO: User defined styles
|
484
|
-
data = [
|
485
|
-
0x8000, # Bit Mask Contents
|
486
|
-
# 11- 0 0x0fff Index to style XF record (➜ 6.115)
|
487
|
-
# 15 0x8000 Always 1 for built-in styles
|
488
|
-
0x00, # Identifier of the built-in cell style:
|
489
|
-
# 0x00 = Normal
|
490
|
-
# 0x01 = RowLevel_lv (see next field)
|
491
|
-
# 0x02 = ColLevel_lv (see next field)
|
492
|
-
# 0x03 = Comma
|
493
|
-
# 0x04 = Currency
|
494
|
-
# 0x05 = Percent
|
495
|
-
# 0x06 = Comma [0] (BIFF4-BIFF8)
|
496
|
-
# 0x07 = Currency [0] (BIFF4-BIFF8)
|
497
|
-
# 0x08 = Hyperlink (BIFF8)
|
498
|
-
# 0x09 = Followed Hyperlink (BIFF8)
|
499
|
-
0xff, # Level for RowLevel or ColLevel style (zero-based, lv),
|
500
|
-
# 0xff otherwise
|
501
|
-
# The RowLevel and ColLevel styles specify the formatting of
|
502
|
-
# subtotal cells in a specific outline level. The level is
|
503
|
-
# specified by the last field in the STYLE record. Valid values
|
504
|
-
# are 0…6 for the outline levels 1…7.
|
505
|
-
]
|
506
|
-
write_op writer, 0x0293, data.pack('vC2')
|
507
|
-
end
|
508
|
-
def write_palette workbook, writer
|
509
|
-
data = default_palette
|
510
195
|
|
511
|
-
|
512
|
-
|
513
|
-
|
514
|
-
|
515
|
-
|
196
|
+
def write_encoding workbook, writer
|
197
|
+
enc = workbook.encoding || "UTF-16LE"
|
198
|
+
if RUBY_VERSION >= "1.9" && enc.is_a?(Encoding)
|
199
|
+
enc = enc.name.upcase
|
200
|
+
end
|
201
|
+
cp = SEGAPEDOC.fetch(enc) do
|
202
|
+
raise Spreadsheet::Errors::UnknownCodepage, "Invalid or Unknown Codepage '#{enc}'"
|
203
|
+
end
|
204
|
+
write_op writer, 0x0042, [cp].pack("v")
|
205
|
+
end
|
516
206
|
|
517
|
-
|
518
|
-
|
519
|
-
|
520
|
-
|
521
|
-
|
522
|
-
|
523
|
-
|
524
|
-
|
525
|
-
|
526
|
-
|
527
|
-
|
528
|
-
|
529
|
-
|
530
|
-
|
531
|
-
|
532
|
-
|
533
|
-
|
534
|
-
|
535
|
-
|
536
|
-
|
537
|
-
|
538
|
-
|
539
|
-
|
540
|
-
|
541
|
-
|
542
|
-
|
543
|
-
|
544
|
-
|
545
|
-
|
546
|
-
|
547
|
-
|
548
|
-
|
549
|
-
|
550
|
-
|
551
|
-
|
552
|
-
|
553
|
-
|
554
|
-
|
555
|
-
|
556
|
-
|
557
|
-
|
558
|
-
|
559
|
-
|
560
|
-
|
561
|
-
|
562
|
-
|
563
|
-
|
564
|
-
|
565
|
-
|
566
|
-
|
207
|
+
def write_eof workbook, writer
|
208
|
+
write_op writer, 0x000a
|
209
|
+
end
|
210
|
+
|
211
|
+
def write_extsst workbook, offsets, writer
|
212
|
+
header = [SST_CHUNKSIZE].pack("v")
|
213
|
+
data = offsets.collect { |pair| pair.push(0).pack("Vv2") }
|
214
|
+
write_op writer, 0x00ff, header, data
|
215
|
+
end
|
216
|
+
|
217
|
+
def write_font workbook, writer, font
|
218
|
+
# TODO: Colors/Palette index
|
219
|
+
size = font.size * TWIPS
|
220
|
+
color = SEDOC_ROLOC[font.color] || SEDOC_ROLOC[:text]
|
221
|
+
weight = FONT_WEIGHTS.fetch(font.weight, font.weight)
|
222
|
+
weight = weight.clamp(100, 1000)
|
223
|
+
esc = SEPYT_TNEMEPACSE.fetch(font.escapement, 0)
|
224
|
+
underline = SEPYT_ENILREDNU.fetch(font.underline, 0)
|
225
|
+
family = SEILIMAF_TNOF.fetch(font.family, 0)
|
226
|
+
encoding = SGNIDOCNE_TNOF.fetch(font.encoding, 0)
|
227
|
+
options = 0
|
228
|
+
options |= 0x0001 if weight > 600
|
229
|
+
options |= 0x0002 if font.italic?
|
230
|
+
options |= 0x0004 if underline > 0
|
231
|
+
options |= 0x0008 if font.strikeout?
|
232
|
+
options |= 0x0010 if font.outline?
|
233
|
+
options |= 0x0020 if font.shadow?
|
234
|
+
data = [
|
235
|
+
size, # Height of the font (in twips = 1/20 of a point)
|
236
|
+
options, # Option flags:
|
237
|
+
# Bit Mask Contents
|
238
|
+
# 0 0x0001 1 = Characters are bold (redundant, see below)
|
239
|
+
# 1 0x0002 1 = Characters are italic
|
240
|
+
# 2 0x0004 1 = Characters are underlined (redundant)
|
241
|
+
# 3 0x0008 1 = Characters are struck out
|
242
|
+
# 4 0x0010 1 = Characters are outlined (djberger)
|
243
|
+
# 5 0x0020 1 = Characters are shadowed (djberger)
|
244
|
+
color, # Palette index (➜ 6.70)
|
245
|
+
weight, # Font weight (100-1000). Standard values are
|
246
|
+
# 0x0190 (400) for normal text and
|
247
|
+
# 0x02bc (700) for bold text.
|
248
|
+
esc, # Escapement type: 0x0000 = None
|
249
|
+
# 0x0001 = Superscript
|
250
|
+
# 0x0002 = Subscript
|
251
|
+
underline, # Underline type: 0x00 = None
|
252
|
+
# 0x01 = Single
|
253
|
+
# 0x02 = Double
|
254
|
+
# 0x21 = Single accounting
|
255
|
+
# 0x22 = Double accounting
|
256
|
+
family, # Font family: 0x00 = None (unknown or don't care)
|
257
|
+
# 0x01 = Roman (variable width, serifed)
|
258
|
+
# 0x02 = Swiss (variable width, sans-serifed)
|
259
|
+
# 0x03 = Modern (fixed width,
|
260
|
+
# serifed or sans-serifed)
|
261
|
+
# 0x04 = Script (cursive)
|
262
|
+
# 0x05 = Decorative (specialised,
|
263
|
+
# e.g. Old English, Fraktur)
|
264
|
+
encoding # Character set: 0x00 = 0 = ANSI Latin
|
265
|
+
# 0x01 = 1 = System default
|
266
|
+
# 0x02 = 2 = Symbol
|
267
|
+
# 0x4d = 77 = Apple Roman
|
268
|
+
# 0x80 = 128 = ANSI Japanese Shift-JIS
|
269
|
+
# 0x81 = 129 = ANSI Korean (Hangul)
|
270
|
+
# 0x82 = 130 = ANSI Korean (Johab)
|
271
|
+
# 0x86 = 134 = ANSI Chinese Simplified GBK
|
272
|
+
# 0x88 = 136 = ANSI Chinese Traditional BIG5
|
273
|
+
# 0xa1 = 161 = ANSI Greek
|
274
|
+
# 0xa2 = 162 = ANSI Turkish
|
275
|
+
# 0xa3 = 163 = ANSI Vietnamese
|
276
|
+
# 0xb1 = 177 = ANSI Hebrew
|
277
|
+
# 0xb2 = 178 = ANSI Arabic
|
278
|
+
# 0xba = 186 = ANSI Baltic
|
279
|
+
# 0xcc = 204 = ANSI Cyrillic
|
280
|
+
# 0xde = 222 = ANSI Thai
|
281
|
+
# 0xee = 238 = ANSI Latin II (Central European)
|
282
|
+
# 0xff = 255 = OEM Latin I
|
283
|
+
]
|
284
|
+
name = unicode_string font.name # Font name: Unicode string,
|
285
|
+
# 8-bit string length (➜ 3.4)
|
286
|
+
write_op writer, opcode(:font), data.pack(binfmt(:font)), name
|
287
|
+
end
|
288
|
+
|
289
|
+
def write_fonts workbook, writer
|
290
|
+
fonts = @fonts[workbook] = {}
|
291
|
+
@formats[workbook][:writers].map { |format| format.font }.compact.uniq.each do |font|
|
292
|
+
unless fonts.include?(font.key)
|
293
|
+
fonts.store font.key, fonts.size
|
294
|
+
write_font workbook, writer, font
|
295
|
+
end
|
296
|
+
end
|
297
|
+
end
|
298
|
+
|
299
|
+
def write_formats workbook, writer
|
300
|
+
# From BIFF5 on, the built-in number formats will be omitted. The built-in
|
301
|
+
# formats are dependent on the current regional settings of the operating
|
302
|
+
# system. BUILTIN_FORMATS shows which number formats are used by
|
303
|
+
# default in a US-English environment. All indexes from 0 to 163 are
|
304
|
+
# reserved for built-in formats.
|
305
|
+
# The first user-defined format starts at 164 (0xa4).
|
306
|
+
formats = @number_formats[workbook] = {}
|
307
|
+
BUILTIN_FORMATS.each do |idx, str|
|
308
|
+
formats.store client(str, "UTF-8"), idx
|
309
|
+
end
|
310
|
+
## Ensure at least a 'GENERAL' format is written
|
311
|
+
formats.delete client("GENERAL", "UTF-8")
|
312
|
+
idx = 0xa4
|
313
|
+
workbook.formats.each do |fmt|
|
314
|
+
str = fmt.number_format
|
315
|
+
unless formats[str]
|
316
|
+
formats.store str, idx
|
317
|
+
# Number format string (Unicode string, 16-bit string length, ➜ 3.4)
|
318
|
+
write_op writer, opcode(:format), [idx].pack("v"), unicode_string(str, 2)
|
319
|
+
idx += 1
|
320
|
+
end
|
321
|
+
end
|
322
|
+
end
|
323
|
+
|
324
|
+
##
|
325
|
+
# Write a new Excel file.
|
326
|
+
def write_from_scratch workbook, io
|
327
|
+
sanitize_worksheets workbook.worksheets
|
328
|
+
collect_formats workbook
|
329
|
+
sheets = worksheets workbook
|
330
|
+
buffer1 = StringIO.new "".dup
|
331
|
+
# ● BOF Type = workbook globals (➜ 6.8)
|
332
|
+
write_bof workbook, buffer1, :globals
|
333
|
+
# ○ File Protection Block ➜ 4.19
|
334
|
+
# ○ WRITEACCESS User name (BIFF3-BIFF8, ➜ 5.112)
|
335
|
+
# ○ FILESHARING File sharing options (BIFF3-BIFF8, ➜ 5.44)
|
336
|
+
# ○ CODEPAGE ➜ 6.17
|
337
|
+
write_encoding workbook, buffer1
|
338
|
+
# ○ DSF ➜ 6.32
|
339
|
+
write_dsf workbook, buffer1
|
340
|
+
# ○ TABID
|
341
|
+
write_tabid workbook, buffer1
|
342
|
+
# ○ FNGROUPCOUNT
|
343
|
+
# ○ Workbook Protection Block ➜ 4.18
|
344
|
+
# ○ WINDOWPROTECT Window settings: 1 = protected (➜ 5.111)
|
345
|
+
# ○ PROTECT Cell contents: 1 = protected (➜ 5.82)
|
346
|
+
write_protect workbook, buffer1
|
347
|
+
# ○ OBJECTPROTECT Embedded objects: 1 = protected (➜ 5.72)
|
348
|
+
# ○ PASSWORD Hash value of the password; 0 = No password (➜ 5.76)
|
349
|
+
write_password workbook, buffer1
|
350
|
+
# ○ BACKUP ➜ 5.5
|
351
|
+
# ○ HIDEOBJ ➜ 5.56
|
352
|
+
# ● WINDOW1 ➜ 5.109
|
353
|
+
write_window1 workbook, buffer1
|
354
|
+
# ○ DATEMODE ➜ 5.28
|
355
|
+
write_datemode workbook, buffer1
|
356
|
+
# ○ PRECISION ➜ 5.79
|
357
|
+
write_precision workbook, buffer1
|
358
|
+
# ○ REFRESHALL
|
359
|
+
write_refreshall workbook, buffer1
|
360
|
+
# ○ BOOKBOOL ➜ 5.9
|
361
|
+
write_bookbool workbook, buffer1
|
362
|
+
# ●● FONT ➜ 5.45
|
363
|
+
write_fonts workbook, buffer1
|
364
|
+
# ○○ FORMAT ➜ 5.49
|
365
|
+
write_formats workbook, buffer1
|
366
|
+
# ●● XF ➜ 5.115
|
367
|
+
write_xfs workbook, buffer1
|
368
|
+
# ●● STYLE ➜ 5.103
|
369
|
+
write_styles workbook, buffer1
|
370
|
+
# ○ PALETTE ➜ 5.74
|
371
|
+
write_palette workbook, buffer1
|
372
|
+
# ○ USESELFS ➜ 5.106
|
373
|
+
buffer1.rewind
|
374
|
+
# ●● BOUNDSHEET ➜ 5.95
|
375
|
+
buffer2 = StringIO.new "".dup
|
376
|
+
# ○ COUNTRY ➜ 5.22
|
377
|
+
# ○ Link Table ➜ 4.10.3
|
378
|
+
# ○○ NAME ➜ 6.66
|
379
|
+
# ○ Shared String Table ➜ 4.11
|
380
|
+
# ● SST ➜ 5.100
|
381
|
+
# ● EXTSST ➜ 5.42
|
382
|
+
write_sst workbook, buffer2, buffer1.size
|
383
|
+
# ● EOF ➜ 5.37
|
384
|
+
write_eof workbook, buffer2
|
385
|
+
buffer2.rewind
|
386
|
+
# worksheet data can only be assembled after write_sst
|
387
|
+
sheets.each { |worksheet| worksheet.write_from_scratch }
|
388
|
+
Ole::Storage.open io do |ole|
|
389
|
+
ole.file.open "Workbook", "w" do |writer|
|
390
|
+
writer.write buffer1.read
|
391
|
+
write_boundsheets workbook, writer, buffer1.size + buffer2.size
|
392
|
+
writer.write buffer2.read
|
393
|
+
sheets.each do |worksheet|
|
394
|
+
writer.write worksheet.data
|
395
|
+
end
|
396
|
+
end
|
397
|
+
end
|
398
|
+
end
|
399
|
+
|
400
|
+
def write_op writer, op, *args
|
401
|
+
data = args.join
|
402
|
+
limited = data.slice!(0...@recordsize_limit)
|
403
|
+
writer.write [op, limited.size].pack("v2")
|
404
|
+
writer.write limited
|
405
|
+
data
|
406
|
+
end
|
407
|
+
|
408
|
+
def write_password workbook, writer
|
409
|
+
write_placeholder writer, 0x0013
|
410
|
+
end
|
411
|
+
|
412
|
+
def write_placeholder writer, op, value = 0x0000, fmt = "v"
|
413
|
+
write_op writer, op, [value].pack(fmt)
|
414
|
+
end
|
415
|
+
|
416
|
+
def write_precision workbook, writer
|
417
|
+
# 0 = Use displayed values; 1 = Use real cell values
|
418
|
+
write_placeholder writer, 0x000e, 0x0001
|
419
|
+
end
|
420
|
+
|
421
|
+
def write_protect workbook, writer
|
422
|
+
write_placeholder writer, 0x0012
|
423
|
+
end
|
424
|
+
|
425
|
+
def write_refreshall workbook, writer
|
426
|
+
write_placeholder writer, 0x01b7
|
427
|
+
end
|
428
|
+
|
429
|
+
def write_sst workbook, writer, offset
|
430
|
+
# Offset Size Contents
|
431
|
+
# 0 4 Total number of strings in the workbook (see below)
|
432
|
+
# 4 4 Number of following strings (nm)
|
433
|
+
# 8 var. List of nm Unicode strings, 16-bit string length (➜ 3.4)
|
434
|
+
num_total = 0
|
435
|
+
strings = worksheets(workbook).each_with_object(Hash.new(0)) do |worksheet, memo|
|
436
|
+
worksheet.strings.each do |k, v|
|
437
|
+
memo[k] += v
|
438
|
+
num_total += v
|
439
|
+
end
|
440
|
+
end
|
441
|
+
_write_sst workbook, writer, offset, num_total, strings.keys
|
442
|
+
end
|
443
|
+
|
444
|
+
def _write_sst workbook, writer, offset, total, strings
|
445
|
+
sst = {}
|
446
|
+
worksheets(workbook).each do |worksheet|
|
447
|
+
offset += worksheet.boundsheet_size
|
448
|
+
@sst[worksheet] = sst
|
449
|
+
end
|
450
|
+
sst_size = strings.size
|
451
|
+
data = [total, sst_size].pack "V2"
|
452
|
+
op = 0x00fc
|
453
|
+
wide = 0
|
454
|
+
offsets = []
|
455
|
+
strings.each_with_index do |string, idx|
|
456
|
+
sst.store string, idx
|
457
|
+
op_offset = data.size + 4
|
458
|
+
if idx % SST_CHUNKSIZE == 0
|
459
|
+
offsets.push [offset + writer.pos + op_offset, op_offset]
|
460
|
+
end
|
461
|
+
header, packed, next_wide = _unicode_string string, 2
|
462
|
+
# the first few bytes (header + first character) must not be split
|
463
|
+
must_fit = header.size + wide + 1
|
464
|
+
while data.size + must_fit > @recordsize_limit
|
465
|
+
op, data, wide = write_string_part writer, op, data, wide
|
466
|
+
end
|
467
|
+
wide = next_wide
|
468
|
+
data << header << packed
|
469
|
+
end
|
470
|
+
until data.empty?
|
471
|
+
op, data, wide = write_string_part writer, op, data, wide
|
472
|
+
end
|
473
|
+
write_extsst workbook, offsets, writer
|
474
|
+
end
|
475
|
+
|
476
|
+
def write_sst_changes workbook, writer, offset, total, strings
|
477
|
+
_write_sst workbook, writer, offset, total, strings
|
478
|
+
end
|
479
|
+
|
480
|
+
def write_string_part writer, op, data, wide
|
481
|
+
bef = data.size
|
482
|
+
## if we're writing wide characters, we need to make sure we don't cut
|
483
|
+
# characters in half
|
484
|
+
if wide > 0 && data.size > @recordsize_limit
|
485
|
+
remove = @recordsize_limit - bef
|
486
|
+
remove -= remove % 2
|
487
|
+
rest = data.slice!(remove..-1)
|
488
|
+
write_op writer, op, data
|
489
|
+
data = rest
|
490
|
+
else
|
491
|
+
data = write_op writer, op, data
|
492
|
+
end
|
493
|
+
op = 0x003c
|
494
|
+
# Unicode strings are split in a special way. At the beginning of each
|
495
|
+
# CONTINUE record the option flags byte is repeated. Only the
|
496
|
+
# character size flag will be set in this flags byte, the Rich-Text
|
497
|
+
# flag and the Far-East flag are set to zero.
|
498
|
+
unless data.empty?
|
499
|
+
if wide == 1
|
500
|
+
# check if we can compress the rest of the string
|
501
|
+
data, wide = compress_unicode_string data
|
502
|
+
end
|
503
|
+
data = [wide].pack("C") << data
|
504
|
+
end
|
505
|
+
[op, data, wide]
|
506
|
+
end
|
507
|
+
|
508
|
+
def write_styles workbook, writer
|
509
|
+
# TODO: Style implementation. The following is simply a standard builtin
|
510
|
+
# style.
|
511
|
+
# TODO: User defined styles
|
512
|
+
data = [
|
513
|
+
0x8000, # Bit Mask Contents
|
514
|
+
# 11- 0 0x0fff Index to style XF record (➜ 6.115)
|
515
|
+
# 15 0x8000 Always 1 for built-in styles
|
516
|
+
0x00, # Identifier of the built-in cell style:
|
517
|
+
# 0x00 = Normal
|
518
|
+
# 0x01 = RowLevel_lv (see next field)
|
519
|
+
# 0x02 = ColLevel_lv (see next field)
|
520
|
+
# 0x03 = Comma
|
521
|
+
# 0x04 = Currency
|
522
|
+
# 0x05 = Percent
|
523
|
+
# 0x06 = Comma [0] (BIFF4-BIFF8)
|
524
|
+
# 0x07 = Currency [0] (BIFF4-BIFF8)
|
525
|
+
# 0x08 = Hyperlink (BIFF8)
|
526
|
+
# 0x09 = Followed Hyperlink (BIFF8)
|
527
|
+
0xff # Level for RowLevel or ColLevel style (zero-based, lv),
|
528
|
+
# 0xff otherwise
|
529
|
+
# The RowLevel and ColLevel styles specify the formatting of
|
530
|
+
# subtotal cells in a specific outline level. The level is
|
531
|
+
# specified by the last field in the STYLE record. Valid values
|
532
|
+
# are 0…6 for the outline levels 1…7.
|
533
|
+
]
|
534
|
+
write_op writer, 0x0293, data.pack("vC2")
|
535
|
+
end
|
536
|
+
|
537
|
+
def write_palette workbook, writer
|
538
|
+
data = default_palette
|
539
|
+
|
540
|
+
workbook.palette.each do |idx, color|
|
541
|
+
idx = SEDOC_ROLOC[idx] - 8 if idx.is_a? Symbol
|
542
|
+
raise "Undefined color index: #{idx}" unless data[idx]
|
543
|
+
data[idx] = color
|
544
|
+
end
|
545
|
+
|
546
|
+
writer.write [opcode(:palette), 2 + 4 * data.size, data.size].pack("v3")
|
547
|
+
writer.write data.collect { |c| c.push(0).pack("C4") }.join
|
548
|
+
end
|
549
|
+
|
550
|
+
def write_tabid workbook, writer
|
551
|
+
write_op writer, 0x013d, [1].pack("v")
|
552
|
+
end
|
553
|
+
|
554
|
+
def write_window1 workbook, writer
|
555
|
+
selected = workbook.worksheets.find { |sheet| sheet.selected }
|
556
|
+
actidx = workbook.worksheets.index selected
|
557
|
+
data = [
|
558
|
+
0x0000, # Horizontal position of the document window
|
559
|
+
# (in twips = 1/20 of a point)
|
560
|
+
0x0000, # Vertical position of the document window
|
561
|
+
# (in twips = 1/20 of a point)
|
562
|
+
0x4000, # Width of the document window (in twips = 1/20 of a point)
|
563
|
+
0x2000, # Height of the document window (in twips = 1/20 of a point)
|
564
|
+
0x0038, # Option flags:
|
565
|
+
# Bit Mask Contents
|
566
|
+
# 0 0x0001 0 = Window is visible
|
567
|
+
# 1 = Window is hidden
|
568
|
+
# 1 0x0002 0 = Window is open
|
569
|
+
# 1 = Window is minimised
|
570
|
+
# 3 0x0008 0 = Horizontal scroll bar hidden
|
571
|
+
# 1 = Horizontal scroll bar visible
|
572
|
+
# 4 0x0010 0 = Vertical scroll bar hidden
|
573
|
+
# 1 = Vertical scroll bar visible
|
574
|
+
# 5 0x0020 0 = Worksheet tab bar hidden
|
575
|
+
# 1 = Worksheet tab bar visible
|
576
|
+
actidx, # Index to active (displayed) worksheet
|
577
|
+
0x0000, # Index of first visible tab in the worksheet tab bar
|
578
|
+
0x0001, # Number of selected worksheets
|
579
|
+
# (highlighted in the worksheet tab bar)
|
580
|
+
0x00e5 # Width of worksheet tab bar (in 1/1000 of window width).
|
581
|
+
# The remaining space is used by the horizontal scrollbar.
|
582
|
+
]
|
583
|
+
write_op writer, 0x003d, data.pack("v*")
|
584
|
+
end
|
585
|
+
|
586
|
+
##
|
587
|
+
# The main writer method. Calls #write_from_scratch.
|
588
|
+
def write_workbook workbook, io
|
589
|
+
if workbook.is_a?(Excel::Workbook) && workbook.io
|
590
|
+
@date_base = workbook.date_base
|
591
|
+
if workbook.changes.empty?
|
592
|
+
super
|
593
|
+
else
|
594
|
+
@date_base = Date.new 1899, 12, 31
|
595
|
+
write_from_scratch workbook, io
|
596
|
+
end
|
597
|
+
else
|
598
|
+
@date_base = Date.new 1899, 12, 31
|
599
|
+
write_from_scratch workbook, io
|
600
|
+
end
|
601
|
+
ensure
|
602
|
+
cleanup workbook
|
603
|
+
end
|
604
|
+
|
605
|
+
def write_xfs workbook, writer
|
606
|
+
# The default cell format is always present in an Excel file, described by
|
607
|
+
# the XF record with the fixed index 15 (0-based). By default, it uses the
|
608
|
+
# worksheet/workbook default cell style, described by the very first XF
|
609
|
+
# record (index 0).
|
610
|
+
@formats[workbook][:writers].each { |fmt| fmt.write_xf writer }
|
611
|
+
end
|
612
|
+
|
613
|
+
def sst_index worksheet, str
|
614
|
+
@sst[worksheet][str]
|
615
|
+
end
|
616
|
+
|
617
|
+
def xf_index workbook, format
|
618
|
+
@formats[workbook][:xf_indexes][format] || 0
|
619
|
+
end
|
620
|
+
|
621
|
+
##
|
622
|
+
# Returns Excel 97+ default colour palette.
|
623
|
+
def default_palette
|
624
|
+
[
|
625
|
+
[0x00, 0x00, 0x00],
|
626
|
+
[0xff, 0xff, 0xff],
|
627
|
+
[0xff, 0x00, 0x00],
|
628
|
+
[0x00, 0xff, 0x00],
|
629
|
+
[0x00, 0x00, 0xff],
|
630
|
+
[0xff, 0xff, 0x00],
|
631
|
+
[0xff, 0x00, 0xff],
|
632
|
+
[0x00, 0xff, 0xff],
|
633
|
+
[0x80, 0x00, 0x00],
|
634
|
+
[0x00, 0x80, 0x00],
|
635
|
+
[0x00, 0x00, 0x80],
|
636
|
+
[0x80, 0x80, 0x00],
|
637
|
+
[0x80, 0x00, 0x80],
|
638
|
+
[0x00, 0x80, 0x80],
|
639
|
+
[0xc0, 0xc0, 0xc0],
|
640
|
+
[0x80, 0x80, 0x80],
|
641
|
+
[0x99, 0x99, 0xff],
|
642
|
+
[0x99, 0x33, 0x66],
|
643
|
+
[0xff, 0xff, 0xcc],
|
644
|
+
[0xcc, 0xff, 0xff],
|
645
|
+
[0x66, 0x00, 0x66],
|
646
|
+
[0xff, 0x80, 0x80],
|
647
|
+
[0x00, 0x66, 0xcc],
|
648
|
+
[0xcc, 0xcc, 0xff],
|
649
|
+
[0x00, 0x00, 0x80],
|
650
|
+
[0xff, 0x00, 0xff],
|
651
|
+
[0xff, 0xff, 0x00],
|
652
|
+
[0x00, 0xff, 0xff],
|
653
|
+
[0x80, 0x00, 0x80],
|
654
|
+
[0x80, 0x00, 0x00],
|
655
|
+
[0x00, 0x80, 0x80],
|
656
|
+
[0x00, 0x00, 0xff],
|
657
|
+
[0x00, 0xcc, 0xff],
|
658
|
+
[0xcc, 0xff, 0xff],
|
659
|
+
[0xcc, 0xff, 0xcc],
|
660
|
+
[0xff, 0xff, 0x99],
|
661
|
+
[0x99, 0xcc, 0xff],
|
662
|
+
[0xff, 0x99, 0xcc],
|
663
|
+
[0xcc, 0x99, 0xff],
|
664
|
+
[0xff, 0xcc, 0x99],
|
665
|
+
[0x33, 0x66, 0xff],
|
666
|
+
[0x33, 0xcc, 0xcc],
|
667
|
+
[0x99, 0xcc, 0x00],
|
668
|
+
[0xff, 0xcc, 0x00],
|
669
|
+
[0xff, 0x99, 0x00],
|
670
|
+
[0xff, 0x66, 0x00],
|
671
|
+
[0x66, 0x66, 0x99],
|
672
|
+
[0x96, 0x96, 0x96],
|
673
|
+
[0x00, 0x33, 0x66],
|
674
|
+
[0x33, 0x99, 0x66],
|
675
|
+
[0x00, 0x33, 0x00],
|
676
|
+
[0x33, 0x33, 0x00],
|
677
|
+
[0x99, 0x33, 0x00],
|
678
|
+
[0x99, 0x33, 0x66],
|
679
|
+
[0x33, 0x33, 0x99],
|
680
|
+
[0x33, 0x33, 0x33]
|
681
|
+
]
|
682
|
+
end
|
567
683
|
end
|
568
684
|
end
|
569
|
-
ensure
|
570
|
-
cleanup workbook
|
571
|
-
end
|
572
|
-
def write_xfs workbook, writer
|
573
|
-
# The default cell format is always present in an Excel file, described by
|
574
|
-
# the XF record with the fixed index 15 (0-based). By default, it uses the
|
575
|
-
# worksheet/workbook default cell style, described by the very first XF
|
576
|
-
# record (index 0).
|
577
|
-
@formats[workbook][:writers].each do |fmt| fmt.write_xf writer end
|
578
|
-
end
|
579
|
-
def sst_index worksheet, str
|
580
|
-
@sst[worksheet][str]
|
581
|
-
end
|
582
|
-
def xf_index workbook, format
|
583
|
-
@formats[workbook][:xf_indexes][format] || 0
|
584
|
-
end
|
585
|
-
##
|
586
|
-
# Returns Excel 97+ default colour palette.
|
587
|
-
def default_palette
|
588
|
-
[
|
589
|
-
[0x00, 0x00, 0x00],
|
590
|
-
[0xff, 0xff, 0xff],
|
591
|
-
[0xff, 0x00, 0x00],
|
592
|
-
[0x00, 0xff, 0x00],
|
593
|
-
[0x00, 0x00, 0xff],
|
594
|
-
[0xff, 0xff, 0x00],
|
595
|
-
[0xff, 0x00, 0xff],
|
596
|
-
[0x00, 0xff, 0xff],
|
597
|
-
[0x80, 0x00, 0x00],
|
598
|
-
[0x00, 0x80, 0x00],
|
599
|
-
[0x00, 0x00, 0x80],
|
600
|
-
[0x80, 0x80, 0x00],
|
601
|
-
[0x80, 0x00, 0x80],
|
602
|
-
[0x00, 0x80, 0x80],
|
603
|
-
[0xc0, 0xc0, 0xc0],
|
604
|
-
[0x80, 0x80, 0x80],
|
605
|
-
[0x99, 0x99, 0xff],
|
606
|
-
[0x99, 0x33, 0x66],
|
607
|
-
[0xff, 0xff, 0xcc],
|
608
|
-
[0xcc, 0xff, 0xff],
|
609
|
-
[0x66, 0x00, 0x66],
|
610
|
-
[0xff, 0x80, 0x80],
|
611
|
-
[0x00, 0x66, 0xcc],
|
612
|
-
[0xcc, 0xcc, 0xff],
|
613
|
-
[0x00, 0x00, 0x80],
|
614
|
-
[0xff, 0x00, 0xff],
|
615
|
-
[0xff, 0xff, 0x00],
|
616
|
-
[0x00, 0xff, 0xff],
|
617
|
-
[0x80, 0x00, 0x80],
|
618
|
-
[0x80, 0x00, 0x00],
|
619
|
-
[0x00, 0x80, 0x80],
|
620
|
-
[0x00, 0x00, 0xff],
|
621
|
-
[0x00, 0xcc, 0xff],
|
622
|
-
[0xcc, 0xff, 0xff],
|
623
|
-
[0xcc, 0xff, 0xcc],
|
624
|
-
[0xff, 0xff, 0x99],
|
625
|
-
[0x99, 0xcc, 0xff],
|
626
|
-
[0xff, 0x99, 0xcc],
|
627
|
-
[0xcc, 0x99, 0xff],
|
628
|
-
[0xff, 0xcc, 0x99],
|
629
|
-
[0x33, 0x66, 0xff],
|
630
|
-
[0x33, 0xcc, 0xcc],
|
631
|
-
[0x99, 0xcc, 0x00],
|
632
|
-
[0xff, 0xcc, 0x00],
|
633
|
-
[0xff, 0x99, 0x00],
|
634
|
-
[0xff, 0x66, 0x00],
|
635
|
-
[0x66, 0x66, 0x99],
|
636
|
-
[0x96, 0x96, 0x96],
|
637
|
-
[0x00, 0x33, 0x66],
|
638
|
-
[0x33, 0x99, 0x66],
|
639
|
-
[0x00, 0x33, 0x00],
|
640
|
-
[0x33, 0x33, 0x00],
|
641
|
-
[0x99, 0x33, 0x00],
|
642
|
-
[0x99, 0x33, 0x66],
|
643
|
-
[0x33, 0x33, 0x99],
|
644
|
-
[0x33, 0x33, 0x33]
|
645
|
-
]
|
646
|
-
end
|
647
|
-
end
|
648
|
-
end
|
649
685
|
end
|
650
686
|
end
|