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,883 +1,920 @@
|
|
1
|
-
require
|
2
|
-
require
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require
|
6
|
-
require 'rational' #for Ruby 1.8.x
|
1
|
+
require "stringio"
|
2
|
+
require "spreadsheet/excel/writer/biff8"
|
3
|
+
require "spreadsheet/excel/internals"
|
4
|
+
require "spreadsheet/excel/internals/biff8"
|
5
|
+
require "bigdecimal"
|
7
6
|
|
8
7
|
module Spreadsheet
|
9
8
|
module Excel
|
10
9
|
module Writer
|
11
|
-
##
|
12
|
-
# Writer class for Excel Worksheets. Most write_* method correspond to an
|
13
|
-
# Excel-Record/Opcode. You should not need to call any of its methods directly.
|
14
|
-
# If you think you do, look at #write_worksheet
|
15
|
-
class Worksheet
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
end
|
37
|
-
##
|
38
|
-
# The number of bytes needed to write a Boundsheet record for this Worksheet
|
39
|
-
# Used by Writer::Worksheet to calculate various offsets.
|
40
|
-
def boundsheet_size
|
41
|
-
name.size + 10
|
42
|
-
end
|
43
|
-
def data
|
44
|
-
@io.rewind
|
45
|
-
@io.read
|
46
|
-
end
|
47
|
-
def encode_date date
|
48
|
-
return date if date.is_a? Numeric
|
49
|
-
if date.is_a? Time
|
50
|
-
date = DateTime.new date.year, date.month, date.day,
|
51
|
-
date.hour, date.min, date.sec
|
52
|
-
end
|
53
|
-
base = @workbook.date_base
|
54
|
-
value = date - base
|
55
|
-
if LEAP_ERROR > base
|
56
|
-
value += 1
|
57
|
-
end
|
58
|
-
value
|
59
|
-
end
|
60
|
-
def encode_rk value
|
61
|
-
# Bit Mask Contents
|
62
|
-
# 0 0x00000001 0 = Value not changed 1 = Value is multiplied by 100
|
63
|
-
# 1 0x00000002 0 = Floating-point value 1 = Signed integer value
|
64
|
-
# 31-2 0xFFFFFFFC Encoded value
|
65
|
-
cent = 0
|
66
|
-
int = 2
|
67
|
-
higher = value * 100
|
68
|
-
if (higher.is_a?(Rational) or higher.is_a?(BigDecimal) or higher.is_a?(Float)) && higher < 0xfffffffc
|
69
|
-
cent = 1
|
70
|
-
if higher == higher.to_i
|
71
|
-
value = higher.to_i
|
72
|
-
else
|
73
|
-
value = higher
|
74
|
-
end
|
75
|
-
end
|
76
|
-
if value.is_a?(Integer)
|
77
|
-
## although not documented as signed, 'V' appears to correctly pack
|
78
|
-
# negative numbers.
|
79
|
-
value <<= 2
|
80
|
-
else
|
81
|
-
# FIXME: precision of small numbers
|
82
|
-
int = 0
|
83
|
-
value, = [value].pack(EIGHT_BYTE_DOUBLE).unpack('x4V')
|
84
|
-
value &= 0xfffffffc
|
85
|
-
end
|
86
|
-
value | cent | int
|
87
|
-
end
|
88
|
-
def name
|
89
|
-
unicode_string @worksheet.name
|
90
|
-
end
|
91
|
-
def need_number? cell
|
92
|
-
if cell.is_a?(Numeric) && cell.abs > 0x1fffffff
|
93
|
-
true
|
94
|
-
elsif cell.is_a?(Rational) or ((cell.is_a?(BigDecimal) or cell.is_a?(Float)) and not cell.nan?)
|
95
|
-
higher = cell * 100
|
96
|
-
if higher == higher.to_i
|
97
|
-
need_number? higher.to_i
|
98
|
-
else
|
99
|
-
test1, test2 = [cell * 100].pack(EIGHT_BYTE_DOUBLE).unpack('V2')
|
100
|
-
test1 > 0 || need_number?(test2)
|
101
|
-
end
|
102
|
-
else
|
103
|
-
false
|
104
|
-
end
|
105
|
-
end
|
106
|
-
def row_blocks
|
107
|
-
# All cells in an Excel document are divided into blocks of 32 consecutive
|
108
|
-
# rows, called Row Blocks. The first Row Block starts with the first used
|
109
|
-
# row in that sheet. Inside each Row Block there will occur ROW records
|
110
|
-
# describing the properties of the rows, and cell records with all the cell
|
111
|
-
# contents in this Row Block.
|
112
|
-
blocks = []
|
113
|
-
@worksheet.reject do |row| row.empty? end.each_with_index do |row, idx|
|
114
|
-
blocks << [] if idx % 32 == 0
|
115
|
-
blocks.last << row
|
116
|
-
end
|
117
|
-
blocks
|
118
|
-
end
|
119
|
-
def size
|
120
|
-
@io.size
|
121
|
-
end
|
122
|
-
def strings
|
123
|
-
@worksheet.inject(Hash.new(0)) do |memo, row|
|
124
|
-
row.each do |cell|
|
125
|
-
memo[cell] += 1 if (cell.is_a?(String) && !cell.empty?)
|
126
|
-
end
|
127
|
-
memo
|
128
|
-
end
|
129
|
-
end
|
130
|
-
##
|
131
|
-
# Write a blank cell
|
132
|
-
def write_blank row, idx
|
133
|
-
write_cell :blank, row, idx
|
134
|
-
end
|
135
|
-
def write_bof
|
136
|
-
data = [
|
137
|
-
@biff_version, # BIFF version (always 0x0600 for BIFF8)
|
138
|
-
0x0010, # Type of the following data:
|
139
|
-
# 0x0005 = Workbook globals
|
140
|
-
# 0x0006 = Visual Basic module
|
141
|
-
# 0x0010 = Worksheet
|
142
|
-
# 0x0020 = Chart
|
143
|
-
# 0x0040 = Macro sheet
|
144
|
-
# 0x0100 = Workspace file
|
145
|
-
@build_id, # Build identifier
|
146
|
-
@build_year, # Build year
|
147
|
-
0x000, # File history flags
|
148
|
-
0x006, # Lowest Excel version that can read
|
149
|
-
# all records in this file
|
150
|
-
]
|
151
|
-
write_op @bof, data.pack("v4V2")
|
152
|
-
end
|
153
|
-
##
|
154
|
-
# Write a cell with a Boolean or Error value
|
155
|
-
def write_boolerr row, idx
|
156
|
-
value = row[idx]
|
157
|
-
type = 0
|
158
|
-
numval = 0
|
159
|
-
if value.is_a? Error
|
160
|
-
type = 1
|
161
|
-
numval = value.code
|
162
|
-
elsif value
|
163
|
-
numval = 1
|
164
|
-
end
|
165
|
-
data = [
|
166
|
-
numval, # Boolean or error value (type depends on the following byte)
|
167
|
-
type # 0 = Boolean value; 1 = Error code
|
168
|
-
]
|
169
|
-
write_cell :boolerr, row, idx, *data
|
170
|
-
end
|
171
|
-
def write_calccount
|
172
|
-
count = 100 # Maximum number of iterations allowed in circular references
|
173
|
-
write_op 0x000c, [count].pack('v')
|
174
|
-
end
|
175
|
-
def write_cell type, row, idx, *args
|
176
|
-
xf_idx = @workbook.xf_index @worksheet.workbook, row.format(idx)
|
177
|
-
data = [
|
178
|
-
row.idx, # Index to row
|
179
|
-
idx, # Index to column
|
180
|
-
xf_idx, # Index to XF record (➜ 6.115)
|
181
|
-
].concat args
|
182
|
-
write_op opcode(type), data.pack(binfmt(type))
|
183
|
-
end
|
184
|
-
def write_cellblocks row
|
185
|
-
# BLANK ➜ 6.7
|
186
|
-
# BOOLERR ➜ 6.10
|
187
|
-
# INTEGER ➜ 6.56 (BIFF2 only)
|
188
|
-
# LABEL ➜ 6.59 (BIFF2-BIFF7)
|
189
|
-
# LABELSST ➜ 6.61 (BIFF8 only)
|
190
|
-
# MULBLANK ➜ 6.64 (BIFF5-BIFF8)
|
191
|
-
# MULRK ➜ 6.65 (BIFF5-BIFF8)
|
192
|
-
# NUMBER ➜ 6.68
|
193
|
-
# RK ➜ 6.82 (BIFF3-BIFF8)
|
194
|
-
# RSTRING ➜ 6.84 (BIFF5/BIFF7)
|
195
|
-
multiples, first_idx = nil
|
196
|
-
row = row.formatted
|
197
|
-
row.each_with_index do |cell, idx|
|
198
|
-
cell = nil if cell == ''
|
199
|
-
## it appears that there are limitations to RK precision, both for
|
200
|
-
# Integers and Floats, that lie well below 2^30 significant bits, or
|
201
|
-
# Ruby's Bignum threshold. In that case we'll just write a Number
|
202
|
-
# record
|
203
|
-
need_number = need_number? cell
|
204
|
-
if multiples && (!multiples.last.is_a?(cell.class) || need_number)
|
205
|
-
write_multiples row, first_idx, multiples
|
206
|
-
multiples, first_idx = nil
|
207
|
-
end
|
208
|
-
nxt = idx + 1
|
209
|
-
case cell
|
210
|
-
when NilClass
|
211
|
-
if multiples
|
212
|
-
multiples.push cell
|
213
|
-
elsif nxt < row.size && row[nxt].nil?
|
214
|
-
multiples = [cell]
|
215
|
-
first_idx = idx
|
216
|
-
else
|
217
|
-
write_blank row, idx
|
218
|
-
end
|
219
|
-
when TrueClass, FalseClass, Error
|
220
|
-
write_boolerr row, idx
|
221
|
-
when String
|
222
|
-
write_labelsst row, idx
|
223
|
-
when Numeric
|
224
|
-
## RK encodes Floats with 30 significant bits, which is a bit more than
|
225
|
-
# 10^9. Not sure what is a good rule of thumb here, but it seems that
|
226
|
-
# Decimal Numbers with more than 4 significant digits are not represented
|
227
|
-
# with sufficient precision by RK
|
228
|
-
if need_number
|
229
|
-
write_number row, idx
|
230
|
-
elsif multiples
|
231
|
-
multiples.push cell
|
232
|
-
elsif nxt < row.size && row[nxt].is_a?(Numeric)
|
233
|
-
multiples = [cell]
|
234
|
-
first_idx = idx
|
235
|
-
else
|
236
|
-
write_rk row, idx
|
237
|
-
end
|
238
|
-
when Formula
|
239
|
-
write_formula row, idx
|
240
|
-
when Date, Time
|
241
|
-
write_number row, idx
|
242
|
-
end
|
243
|
-
end
|
244
|
-
write_multiples row, first_idx, multiples if multiples
|
245
|
-
end
|
246
|
-
def write_colinfo bunch
|
247
|
-
col = bunch.first
|
248
|
-
width = col.width.to_f * 256
|
249
|
-
xf_idx = @workbook.xf_index @worksheet.workbook, col.default_format
|
250
|
-
opts = 0
|
251
|
-
opts |= 0x0001 if col.hidden?
|
252
|
-
opts |= col.outline_level.to_i << 8
|
253
|
-
opts |= 0x1000 if col.collapsed?
|
254
|
-
data = [
|
255
|
-
col.idx, # Index to first column in the range
|
256
|
-
bunch.last.idx, # Index to last column in the range
|
257
|
-
width.to_i, # Width of the columns in 1/256 of the width of the zero
|
258
|
-
# character, using default font (first FONT record in the
|
259
|
-
# file)
|
260
|
-
xf_idx.to_i, # Index to XF record (➜ 6.115) for default column formatting
|
261
|
-
opts, # Option flags:
|
262
|
-
# Bits Mask Contents
|
263
|
-
# 0 0x0001 1 = Columns are hidden
|
264
|
-
# 10-8 0x0700 Outline level of the columns
|
265
|
-
# (0 = no outline)
|
266
|
-
# 12 0x1000 1 = Columns are collapsed
|
267
|
-
]
|
268
|
-
write_op opcode(:colinfo), data.pack(binfmt(:colinfo))
|
269
|
-
end
|
270
|
-
def write_colinfos
|
271
|
-
cols = @worksheet.columns
|
272
|
-
bunch = []
|
273
|
-
cols.each_with_index do |column, idx|
|
274
|
-
if column
|
275
|
-
bunch << column
|
276
|
-
if cols[idx.next] != column
|
277
|
-
write_colinfo bunch
|
278
|
-
bunch.clear
|
10
|
+
##
|
11
|
+
# Writer class for Excel Worksheets. Most write_* method correspond to an
|
12
|
+
# Excel-Record/Opcode. You should not need to call any of its methods directly.
|
13
|
+
# If you think you do, look at #write_worksheet
|
14
|
+
class Worksheet
|
15
|
+
include Spreadsheet::Excel::Writer::Biff8
|
16
|
+
include Spreadsheet::Excel::Internals
|
17
|
+
include Spreadsheet::Excel::Internals::Biff8
|
18
|
+
attr_reader :worksheet
|
19
|
+
def initialize workbook, worksheet
|
20
|
+
@workbook = workbook
|
21
|
+
@worksheet = worksheet
|
22
|
+
@io = StringIO.new "".dup
|
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
|
+
}
|
279
35
|
end
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
36
|
+
|
37
|
+
##
|
38
|
+
# The number of bytes needed to write a Boundsheet record for this Worksheet
|
39
|
+
# Used by Writer::Worksheet to calculate various offsets.
|
40
|
+
def boundsheet_size
|
41
|
+
name.size + 10
|
42
|
+
end
|
43
|
+
|
44
|
+
def data
|
45
|
+
@io.rewind
|
46
|
+
@io.read
|
47
|
+
end
|
48
|
+
|
49
|
+
def encode_date date
|
50
|
+
return date if date.is_a? Numeric
|
51
|
+
if date.is_a? Time
|
52
|
+
date = DateTime.new date.year, date.month, date.day,
|
53
|
+
date.hour, date.min, date.sec
|
54
|
+
end
|
55
|
+
base = @workbook.date_base
|
56
|
+
value = date - base
|
57
|
+
if base < LEAP_ERROR
|
58
|
+
value += 1
|
59
|
+
end
|
60
|
+
value
|
61
|
+
end
|
62
|
+
|
63
|
+
def encode_rk value
|
64
|
+
# Bit Mask Contents
|
65
|
+
# 0 0x00000001 0 = Value not changed 1 = Value is multiplied by 100
|
66
|
+
# 1 0x00000002 0 = Floating-point value 1 = Signed integer value
|
67
|
+
# 31-2 0xFFFFFFFC Encoded value
|
68
|
+
cent = 0
|
69
|
+
int = 2
|
70
|
+
higher = value * 100
|
71
|
+
if (higher.is_a?(Rational) || higher.is_a?(BigDecimal) || higher.is_a?(Float)) && higher < 0xfffffffc
|
72
|
+
cent = 1
|
73
|
+
value = if higher == higher.to_i
|
74
|
+
higher.to_i
|
75
|
+
else
|
76
|
+
higher
|
77
|
+
end
|
78
|
+
end
|
79
|
+
if value.is_a?(Integer)
|
80
|
+
## although not documented as signed, 'V' appears to correctly pack
|
81
|
+
# negative numbers.
|
82
|
+
value <<= 2
|
83
|
+
else
|
84
|
+
# FIXME: precision of small numbers
|
85
|
+
int = 0
|
86
|
+
value, = [value].pack(EIGHT_BYTE_DOUBLE).unpack("x4V")
|
87
|
+
value &= 0xfffffffc
|
88
|
+
end
|
89
|
+
value | cent | int
|
90
|
+
end
|
91
|
+
|
92
|
+
def name
|
93
|
+
unicode_string @worksheet.name
|
94
|
+
end
|
95
|
+
|
96
|
+
def need_number? cell
|
97
|
+
if cell.is_a?(Numeric) && cell.abs > 0x1fffffff
|
98
|
+
true
|
99
|
+
elsif cell.is_a?(Rational) || ((cell.is_a?(BigDecimal) || cell.is_a?(Float)) && !cell.nan?)
|
100
|
+
higher = cell * 100
|
101
|
+
if higher == higher.to_i
|
102
|
+
need_number? higher.to_i
|
103
|
+
else
|
104
|
+
test1, test2 = [cell * 100].pack(EIGHT_BYTE_DOUBLE).unpack("V2")
|
105
|
+
test1 > 0 || need_number?(test2)
|
106
|
+
end
|
107
|
+
else
|
108
|
+
false
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def row_blocks
|
113
|
+
# All cells in an Excel document are divided into blocks of 32 consecutive
|
114
|
+
# rows, called Row Blocks. The first Row Block starts with the first used
|
115
|
+
# row in that sheet. Inside each Row Block there will occur ROW records
|
116
|
+
# describing the properties of the rows, and cell records with all the cell
|
117
|
+
# contents in this Row Block.
|
118
|
+
blocks = []
|
119
|
+
@worksheet.reject { |row| row.empty? }.each_with_index do |row, idx|
|
120
|
+
blocks << [] if idx % 32 == 0
|
121
|
+
blocks.last << row
|
122
|
+
end
|
123
|
+
blocks
|
124
|
+
end
|
125
|
+
|
126
|
+
def size
|
127
|
+
@io.size
|
128
|
+
end
|
129
|
+
|
130
|
+
def strings
|
131
|
+
@worksheet.each_with_object(Hash.new(0)) do |row, memo|
|
132
|
+
row.each do |cell|
|
133
|
+
memo[cell] += 1 if cell.is_a?(String) && !cell.empty?
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
##
|
139
|
+
# Write a blank cell
|
140
|
+
def write_blank row, idx
|
141
|
+
write_cell :blank, row, idx
|
142
|
+
end
|
143
|
+
|
144
|
+
def write_bof
|
145
|
+
data = [
|
146
|
+
@biff_version, # BIFF version (always 0x0600 for BIFF8)
|
147
|
+
0x0010, # Type of the following data:
|
148
|
+
# 0x0005 = Workbook globals
|
149
|
+
# 0x0006 = Visual Basic module
|
150
|
+
# 0x0010 = Worksheet
|
151
|
+
# 0x0020 = Chart
|
152
|
+
# 0x0040 = Macro sheet
|
153
|
+
# 0x0100 = Workspace file
|
154
|
+
@build_id, # Build identifier
|
155
|
+
@build_year, # Build year
|
156
|
+
0x000, # File history flags
|
157
|
+
0x006 # Lowest Excel version that can read
|
158
|
+
# all records in this file
|
159
|
+
]
|
160
|
+
write_op @bof, data.pack("v4V2")
|
161
|
+
end
|
162
|
+
|
163
|
+
##
|
164
|
+
# Write a cell with a Boolean || Error value
|
165
|
+
def write_boolerr row, idx
|
166
|
+
value = row[idx]
|
167
|
+
type = 0
|
168
|
+
numval = 0
|
169
|
+
if value.is_a? Error
|
170
|
+
type = 1
|
171
|
+
numval = value.code
|
172
|
+
elsif value
|
173
|
+
numval = 1
|
174
|
+
end
|
175
|
+
data = [
|
176
|
+
numval, # Boolean || error value (type depends on the following byte)
|
177
|
+
type # 0 = Boolean value; 1 = Error code
|
178
|
+
]
|
179
|
+
write_cell :boolerr, row, idx, *data
|
180
|
+
end
|
181
|
+
|
182
|
+
def write_calccount
|
183
|
+
count = 100 # Maximum number of iterations allowed in circular references
|
184
|
+
write_op 0x000c, [count].pack("v")
|
185
|
+
end
|
186
|
+
|
187
|
+
def write_cell type, row, idx, *args
|
188
|
+
xf_idx = @workbook.xf_index @worksheet.workbook, row.format(idx)
|
189
|
+
data = [
|
190
|
+
row.idx, # Index to row
|
191
|
+
idx, # Index to column
|
192
|
+
xf_idx # Index to XF record (➜ 6.115)
|
193
|
+
].concat args
|
194
|
+
write_op opcode(type), data.pack(binfmt(type))
|
195
|
+
end
|
196
|
+
|
197
|
+
def write_cellblocks row
|
198
|
+
# BLANK ➜ 6.7
|
199
|
+
# BOOLERR ➜ 6.10
|
200
|
+
# INTEGER ➜ 6.56 (BIFF2 only)
|
201
|
+
# LABEL ➜ 6.59 (BIFF2-BIFF7)
|
202
|
+
# LABELSST ➜ 6.61 (BIFF8 only)
|
203
|
+
# MULBLANK ➜ 6.64 (BIFF5-BIFF8)
|
204
|
+
# MULRK ➜ 6.65 (BIFF5-BIFF8)
|
205
|
+
# NUMBER ➜ 6.68
|
206
|
+
# RK ➜ 6.82 (BIFF3-BIFF8)
|
207
|
+
# RSTRING ➜ 6.84 (BIFF5/BIFF7)
|
208
|
+
multiples, first_idx = nil
|
209
|
+
row = row.formatted
|
210
|
+
row.each_with_index do |cell, idx|
|
211
|
+
cell = nil if cell == ""
|
212
|
+
## it appears that there are limitations to RK precision, both for
|
213
|
+
# Integers and Floats, that lie well below 2^30 significant bits, or
|
214
|
+
# Ruby's Bignum threshold. In that case we'll just write a Number
|
215
|
+
# record
|
216
|
+
need_number = need_number? cell
|
217
|
+
if multiples && (!multiples.last.is_a?(cell.class) || need_number)
|
218
|
+
write_multiples row, first_idx, multiples
|
219
|
+
multiples, first_idx = nil
|
220
|
+
end
|
221
|
+
nxt = idx + 1
|
222
|
+
case cell
|
223
|
+
when NilClass
|
224
|
+
if multiples
|
225
|
+
multiples.push cell
|
226
|
+
elsif nxt < row.size && row[nxt].nil?
|
227
|
+
multiples = [cell]
|
228
|
+
first_idx = idx
|
229
|
+
else
|
230
|
+
write_blank row, idx
|
231
|
+
end
|
232
|
+
when TrueClass, FalseClass, Error
|
233
|
+
write_boolerr row, idx
|
234
|
+
when String
|
235
|
+
write_labelsst row, idx
|
236
|
+
when Numeric
|
237
|
+
## RK encodes Floats with 30 significant bits, which is a bit more than
|
238
|
+
# 10^9. Not sure what is a good rule of thumb here, but it seems that
|
239
|
+
# Decimal Numbers with more than 4 significant digits are not represented
|
240
|
+
# with sufficient precision by RK
|
241
|
+
if need_number
|
242
|
+
write_number row, idx
|
243
|
+
elsif multiples
|
244
|
+
multiples.push cell
|
245
|
+
elsif nxt < row.size && row[nxt].is_a?(Numeric)
|
246
|
+
multiples = [cell]
|
247
|
+
first_idx = idx
|
248
|
+
else
|
249
|
+
write_rk row, idx
|
250
|
+
end
|
251
|
+
when Formula
|
252
|
+
write_formula row, idx
|
253
|
+
when Date, Time
|
254
|
+
write_number row, idx
|
255
|
+
end
|
256
|
+
end
|
257
|
+
write_multiples row, first_idx, multiples if multiples
|
258
|
+
end
|
259
|
+
|
260
|
+
def write_colinfo bunch
|
261
|
+
col = bunch.first
|
262
|
+
width = col.width.to_f * 256
|
263
|
+
xf_idx = @workbook.xf_index @worksheet.workbook, col.default_format
|
264
|
+
opts = 0
|
265
|
+
opts |= 0x0001 if col.hidden?
|
266
|
+
opts |= col.outline_level.to_i << 8
|
267
|
+
opts |= 0x1000 if col.collapsed?
|
268
|
+
data = [
|
269
|
+
col.idx, # Index to first column in the range
|
270
|
+
bunch.last.idx, # Index to last column in the range
|
271
|
+
width.to_i, # Width of the columns in 1/256 of the width of the zero
|
272
|
+
# character, using default font (first FONT record in the
|
273
|
+
# file)
|
274
|
+
xf_idx.to_i, # Index to XF record (➜ 6.115) for default column formatting
|
275
|
+
opts # Option flags:
|
276
|
+
# Bits Mask Contents
|
277
|
+
# 0 0x0001 1 = Columns are hidden
|
278
|
+
# 10-8 0x0700 Outline level of the columns
|
279
|
+
# (0 = no outline)
|
280
|
+
# 12 0x1000 1 = Columns are collapsed
|
281
|
+
]
|
282
|
+
write_op opcode(:colinfo), data.pack(binfmt(:colinfo))
|
283
|
+
end
|
284
|
+
|
285
|
+
def write_colinfos
|
286
|
+
cols = @worksheet.columns
|
287
|
+
bunch = []
|
288
|
+
cols.each_with_index do |column, idx|
|
289
|
+
if column
|
290
|
+
bunch << column
|
291
|
+
if cols[idx.next] != column
|
292
|
+
write_colinfo bunch
|
293
|
+
bunch.clear
|
294
|
+
end
|
295
|
+
end
|
296
|
+
end
|
297
|
+
end
|
298
|
+
|
299
|
+
def write_defaultrowheight
|
300
|
+
data = [
|
301
|
+
0x00, # Option flags:
|
286
302
|
# Bit Mask Contents
|
287
303
|
# 0 0x01 1 = Row height and default font height do not match
|
288
304
|
# 1 0x02 1 = Row is hidden
|
289
305
|
# 2 0x04 1 = Additional space above the row
|
290
306
|
# 3 0x08 1 = Additional space below the row
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
end
|
295
|
-
def write_defcolwidth
|
296
|
-
# Offset Size Contents
|
297
|
-
# 0 2 Column width in characters, using the width of the zero
|
298
|
-
# character from default font (first FONT record in the
|
299
|
-
# file). Excel adds some extra space to the default width,
|
300
|
-
# depending on the default font and default font size. The
|
301
|
-
# algorithm how to exactly calculate the resulting column
|
302
|
-
# width is not known.
|
303
|
-
#
|
304
|
-
# Example: The default width of 8 set in this record results
|
305
|
-
# in a column width of 8.43 using Arial font with a size of
|
306
|
-
# 10 points.
|
307
|
-
write_op 0x0055, [8].pack('v')
|
308
|
-
end
|
309
|
-
def write_dimensions
|
310
|
-
# Offset Size Contents
|
311
|
-
# 0 4 Index to first used row
|
312
|
-
# 4 4 Index to last used row, increased by 1
|
313
|
-
# 8 2 Index to first used column
|
314
|
-
# 10 2 Index to last used column, increased by 1
|
315
|
-
# 12 2 Not used
|
316
|
-
write_op 0x0200, @worksheet.dimensions.pack(binfmt(:dimensions))
|
317
|
-
end
|
318
|
-
def write_eof
|
319
|
-
write_op 0x000a
|
320
|
-
end
|
321
|
-
##
|
322
|
-
# Write a cell with a Formula. May write an additional String record depending
|
323
|
-
# on the stored result of the Formula.
|
324
|
-
def write_formula row, idx
|
325
|
-
xf_idx = @workbook.xf_index @worksheet.workbook, row.format(idx)
|
326
|
-
cell = row[idx]
|
327
|
-
data1 = [
|
328
|
-
row.idx, # Index to row
|
329
|
-
idx, # Index to column
|
330
|
-
xf_idx, # Index to XF record (➜ 6.115)
|
331
|
-
].pack 'v3'
|
332
|
-
data2 = nil
|
333
|
-
case value = cell.value
|
334
|
-
when Numeric # IEEE 754 floating-point value (64-bit double precision)
|
335
|
-
data2 = [value].pack EIGHT_BYTE_DOUBLE
|
336
|
-
when String
|
337
|
-
data2 = [
|
338
|
-
0x00, # (identifier for a string value)
|
339
|
-
0xffff, #
|
340
|
-
].pack 'Cx5v'
|
341
|
-
when true, false
|
342
|
-
value = value ? 1 : 0
|
343
|
-
data2 = [
|
344
|
-
0x01, # (identifier for a Boolean value)
|
345
|
-
value, # 0 = FALSE, 1 = TRUE
|
346
|
-
0xffff, #
|
347
|
-
].pack 'CxCx3v'
|
348
|
-
when Error
|
349
|
-
data2 = [
|
350
|
-
0x02, # (identifier for an error value)
|
351
|
-
value.code, # Error code
|
352
|
-
0xffff, #
|
353
|
-
].pack 'CxCx3v'
|
354
|
-
when nil
|
355
|
-
data2 = [
|
356
|
-
0x03, # (identifier for an empty cell)
|
357
|
-
0xffff, #
|
358
|
-
].pack 'Cx5v'
|
359
|
-
else
|
360
|
-
data2 = [
|
361
|
-
0x02, # (identifier for an error value)
|
362
|
-
0x2a, # Error code: #N/A! Argument or function not available
|
363
|
-
0xffff, #
|
364
|
-
].pack 'CxCx3v'
|
365
|
-
end
|
366
|
-
opts = 0x03
|
367
|
-
opts |= 0x08 if cell.shared
|
368
|
-
data3 = [
|
369
|
-
opts # Option flags:
|
370
|
-
# Bit Mask Contents
|
371
|
-
# 0 0x0001 1 = Recalculate always
|
372
|
-
# 1 0x0002 1 = Calculate on open
|
373
|
-
# 3 0x0008 1 = Part of a shared formula
|
374
|
-
].pack 'vx4'
|
375
|
-
write_op opcode(:formula), data1, data2, data3, cell.data
|
376
|
-
if cell.value.is_a?(String)
|
377
|
-
write_op opcode(:string), unicode_string(cell.value, 2)
|
378
|
-
end
|
379
|
-
end
|
380
|
-
##
|
381
|
-
# Write a new Worksheet.
|
382
|
-
def write_from_scratch
|
383
|
-
# ● BOF Type = worksheet (➜ 5.8)
|
384
|
-
write_bof
|
385
|
-
# ○ UNCALCED ➜ 5.105
|
386
|
-
# ○ INDEX ➜ 4.7 (Row Blocks), ➜ 5.59
|
387
|
-
# ○ Calculation Settings Block ➜ 4.3
|
388
|
-
write_calccount
|
389
|
-
write_refmode
|
390
|
-
write_iteration
|
391
|
-
write_saverecalc
|
392
|
-
# ○ PRINTHEADERS ➜ 5.81
|
393
|
-
# ○ PRINTGRIDLINES ➜ 5.80
|
394
|
-
# ○ GRIDSET ➜ 5.52
|
395
|
-
# ○ GUTS ➜ 5.53
|
396
|
-
write_guts
|
397
|
-
# ○ DEFAULTROWHEIGHT ➜ 5.31
|
398
|
-
write_defaultrowheight
|
399
|
-
# ○ WSBOOL ➜ 5.113
|
400
|
-
write_wsbool
|
401
|
-
# ○ Page Settings Block ➜ 4.4
|
402
|
-
# ○ Worksheet Protection Block ➜ 4.18
|
403
|
-
write_proctection
|
404
|
-
# ○ DEFCOLWIDTH ➜ 5.32
|
405
|
-
write_defcolwidth
|
406
|
-
# ○○ COLINFO ➜ 5.18
|
407
|
-
write_colinfos
|
408
|
-
# ○ SORT ➜ 5.99
|
409
|
-
# ● DIMENSIONS ➜ 5.35
|
410
|
-
write_dimensions
|
411
|
-
# ○○ Row Blocks ➜ 4.7
|
412
|
-
write_rows
|
413
|
-
# ● Worksheet View Settings Block ➜ 4.5
|
414
|
-
# ● WINDOW2 ➜ 5.110
|
415
|
-
write_window2
|
416
|
-
# ○ SCL ➜ 5.92 (BIFF4-BIFF8 only)
|
417
|
-
# ○ PANE ➜ 5.75
|
418
|
-
# ○○ SELECTION ➜ 5.93
|
419
|
-
# ○ STANDARDWIDTH ➜ 5.101
|
420
|
-
# ○○ MERGEDCELLS ➜ 5.67
|
421
|
-
write_merged_cells
|
422
|
-
# ○ LABELRANGES ➜ 5.64
|
423
|
-
# ○ PHONETIC ➜ 5.77
|
424
|
-
# ○ Conditional Formatting Table ➜ 4.12
|
425
|
-
# ○ Hyperlink Table ➜ 4.13
|
426
|
-
write_pagesetup
|
427
|
-
write_margins
|
428
|
-
write_hyperlink_table
|
429
|
-
# ○ Data Validity Table ➜ 4.14
|
430
|
-
# ○ SHEETLAYOUT ➜ 5.96 (BIFF8X only)
|
431
|
-
# ○ SHEETPROTECTION Additional protection, ➜ 5.98 (BIFF8X only)
|
432
|
-
# ○ RANGEPROTECTION Additional protection, ➜ 5.84 (BIFF8X only)
|
433
|
-
# ● EOF ➜ 5.36
|
434
|
-
write_eof
|
435
|
-
end
|
436
|
-
##
|
437
|
-
# Write record that contains information about the layout of outline symbols.
|
438
|
-
def write_guts
|
439
|
-
# find the maximum outline_level in rows and columns
|
440
|
-
row_outline_level = 0
|
441
|
-
col_outline_level = 0
|
442
|
-
if(row = @worksheet.rows.select{|x| x!=nil}.max{|a,b| a.outline_level <=> b.outline_level})
|
443
|
-
row_outline_level = row.outline_level
|
444
|
-
end
|
445
|
-
if(col = @worksheet.columns.select{|x| x!=nil}.max{|a,b| a.outline_level <=> b.outline_level})
|
446
|
-
col_outline_level = col.outline_level
|
447
|
-
end
|
448
|
-
# set data
|
449
|
-
data = [
|
450
|
-
0, # Width of the area to display row outlines (left of the sheet), in pixel
|
451
|
-
0, # Height of the area to display column outlines (above the sheet), in pixel
|
452
|
-
row_outline_level+1, # Number of visible row outline levels (used row levels+1; or 0,if not used)
|
453
|
-
col_outline_level+1 # Number of visible column outline levels (used column levels+1; or 0,if not used)
|
454
|
-
]
|
455
|
-
# write record
|
456
|
-
write_op opcode(:guts), data.pack('v4')
|
457
|
-
end
|
458
|
-
def write_hlink row, col, link
|
459
|
-
# FIXME: only Hyperlinks are supported at present.
|
460
|
-
cell_range = [
|
461
|
-
row, row, # Cell range address of all cells containing this hyperlink
|
462
|
-
col, col, # (➜ 3.13.1)
|
463
|
-
].pack 'v4'
|
464
|
-
guid = [
|
465
|
-
# GUID of StdLink:
|
466
|
-
# D0 C9 EA 79 F9 BA CE 11 8C 82 00 AA 00 4B A9 0B
|
467
|
-
# (79EAC9D0-BAF9-11CE-8C82-00AA004BA90B)
|
468
|
-
"d0c9ea79f9bace118c8200aa004ba90b",
|
469
|
-
].pack 'H32'
|
470
|
-
opts = 0x01
|
471
|
-
opts |= 0x02
|
472
|
-
opts |= 0x14 unless link == link.url
|
473
|
-
opts |= 0x08 if link.fragment
|
474
|
-
opts |= 0x80 if link.target_frame
|
475
|
-
# TODO: UNC support
|
476
|
-
options = [
|
477
|
-
2, # Unknown value: 0x00000002
|
478
|
-
opts, # Option flags
|
479
|
-
# Bit Mask Contents
|
480
|
-
# 0 0x00000001 0 = No link extant
|
481
|
-
# 1 = File link or URL
|
482
|
-
# 1 0x00000002 0 = Relative file path
|
483
|
-
# 1 = Absolute path or URL
|
484
|
-
# 2 and 4 0x00000014 0 = No description
|
485
|
-
# 1 (both bits) = Description
|
486
|
-
# 3 0x00000008 0 = No text mark
|
487
|
-
# 1 = Text mark
|
488
|
-
# 7 0x00000080 0 = No target frame
|
489
|
-
# 1 = Target frame
|
490
|
-
# 8 0x00000100 0 = File link or URL
|
491
|
-
# 1 = UNC path (incl. server name)
|
492
|
-
|
493
|
-
].pack('V2')
|
494
|
-
tail = []
|
495
|
-
## call internal to get the correct internal encoding in Ruby 1.9
|
496
|
-
nullstr = internal "\000"
|
497
|
-
unless link == link.url
|
498
|
-
desc = internal(link).dup << nullstr
|
499
|
-
tail.push [desc.size / 2].pack('V'), desc
|
500
|
-
end
|
501
|
-
if link.target_frame
|
502
|
-
frme = internal(link.target_frame).dup << nullstr
|
503
|
-
tail.push [frme.size / 2].pack('V'), frme
|
504
|
-
end
|
505
|
-
url = internal(link.url).dup << nullstr
|
506
|
-
tail.push [
|
507
|
-
# 6.53.2 Hyperlink containing a URL (Uniform Resource Locator)
|
508
|
-
# These data fields occur for links which are not local files or files
|
509
|
-
# in the local network (for instance HTTP and FTP links and e-mail
|
510
|
-
# addresses). The lower 9 bits of the option flags field must be
|
511
|
-
# 0.x00x.xx112 (x means optional, depending on hyperlink content). The
|
512
|
-
# GUID could be used to distinguish a URL from a file link.
|
513
|
-
# GUID of URL Moniker:
|
514
|
-
# E0 C9 EA 79 F9 BA CE 11 8C 82 00 AA 00 4B A9 0B
|
515
|
-
# (79EAC9E0-BAF9-11CE-8C82-00AA004BA90B)
|
516
|
-
'e0c9ea79f9bace118c8200aa004ba90b',
|
517
|
-
url.size # Size of character array of the URL, including trailing zero
|
518
|
-
# word (us). There are us/2-1 characters in the following
|
519
|
-
# string.
|
520
|
-
].pack('H32V'), url
|
521
|
-
if link.fragment
|
522
|
-
frag = internal(link.fragment).dup << nullstr
|
523
|
-
tail.push [frag.size / 2].pack('V'), frag
|
524
|
-
end
|
525
|
-
write_op opcode(:hlink), cell_range, guid, options, *tail
|
526
|
-
end
|
527
|
-
def write_hyperlink_table
|
528
|
-
# TODO: theoretically it's possible to write fewer records by combining
|
529
|
-
# identical neighboring links in cell-ranges
|
530
|
-
@worksheet.each do |row|
|
531
|
-
row.each_with_index do |cell, idx|
|
532
|
-
if cell.is_a? Link
|
533
|
-
write_hlink row.idx, idx, cell
|
307
|
+
0xf2 # Default height for unused rows, in twips = 1/20 of a point
|
308
|
+
]
|
309
|
+
write_op 0x0225, data.pack("v2")
|
534
310
|
end
|
535
|
-
end
|
536
|
-
end
|
537
|
-
end
|
538
|
-
def write_iteration
|
539
|
-
its = 0 # 0 = Iterations off; 1 = Iterations on
|
540
|
-
write_op 0x0011, [its].pack('v')
|
541
|
-
end
|
542
|
-
##
|
543
|
-
# Write a cell with a String value. The String must have been stored in the
|
544
|
-
# Shared String Table.
|
545
|
-
def write_labelsst row, idx
|
546
|
-
write_cell :labelsst, row, idx, @workbook.sst_index(self, row[idx])
|
547
|
-
end
|
548
|
-
##
|
549
|
-
# Write multiple consecutive blank cells.
|
550
|
-
def write_mulblank row, idx, multiples
|
551
|
-
data = [
|
552
|
-
row.idx, # Index to row
|
553
|
-
idx, # Index to first column (fc)
|
554
|
-
]
|
555
|
-
# List of nc=lc-fc+1 16-bit indexes to XF records (➜ 6.115)
|
556
|
-
multiples.each_with_index do |blank, cell_idx|
|
557
|
-
xf_idx = @workbook.xf_index @worksheet.workbook, row.format(idx + cell_idx)
|
558
|
-
data.push xf_idx
|
559
|
-
end
|
560
|
-
# Index to last column (lc)
|
561
|
-
data.push idx + multiples.size - 1
|
562
|
-
write_op opcode(:mulblank), data.pack('v*')
|
563
|
-
end
|
564
|
-
##
|
565
|
-
# Write multiple consecutive cells with RK values (see #write_rk)
|
566
|
-
def write_mulrk row, idx, multiples
|
567
|
-
fmt = ['v2']
|
568
|
-
data = [
|
569
|
-
row.idx, # Index to row
|
570
|
-
idx, # Index to first column (fc)
|
571
|
-
]
|
572
|
-
# List of nc=lc-fc+1 16-bit indexes to XF records (➜ 6.115)
|
573
|
-
multiples.each_with_index do |cell, cell_idx|
|
574
|
-
xf_idx = @workbook.xf_index @worksheet.workbook, row.format(idx + cell_idx)
|
575
|
-
data.push xf_idx, encode_rk(cell)
|
576
|
-
fmt << 'vV'
|
577
|
-
end
|
578
|
-
# Index to last column (lc)
|
579
|
-
data.push idx + multiples.size - 1
|
580
|
-
write_op opcode(:mulrk), data.pack((fmt << 'v').join)
|
581
|
-
end
|
582
|
-
def write_multiples row, idx, multiples
|
583
|
-
case multiples.last
|
584
|
-
when NilClass
|
585
|
-
write_mulblank row, idx, multiples
|
586
|
-
when Numeric
|
587
|
-
if multiples.size > 1
|
588
|
-
write_mulrk row, idx, multiples
|
589
|
-
else
|
590
|
-
write_rk row, idx
|
591
|
-
end
|
592
|
-
end
|
593
|
-
end
|
594
|
-
##
|
595
|
-
# Write a cell with a 64-bit double precision Float value
|
596
|
-
def write_number row, idx
|
597
|
-
# Offset Size Contents
|
598
|
-
# 0 2 Index to row
|
599
|
-
# 2 2 Index to column
|
600
|
-
# 4 2 Index to XF record (➜ 6.115)
|
601
|
-
# 6 8 IEEE 754 floating-point value (64-bit double precision)
|
602
|
-
value = row[idx]
|
603
|
-
case value
|
604
|
-
when Date, Time
|
605
|
-
value = encode_date(value)
|
606
|
-
end
|
607
|
-
write_cell :number, row, idx, value
|
608
|
-
end
|
609
|
-
def write_op op, *args
|
610
|
-
data = args.join
|
611
|
-
@io.write [op,data.size].pack("v2")
|
612
|
-
@io.write data
|
613
|
-
end
|
614
|
-
def write_refmode
|
615
|
-
# • The “RC” mode uses numeric indexes for rows and columns, for example
|
616
|
-
# “R(1)C(-1)”, or “R1C1:R2C2”.
|
617
|
-
# • The “A1” mode uses characters for columns and numbers for rows, for
|
618
|
-
# example “B1”, or “$A$1:$B$2”.
|
619
|
-
mode = 1 # 0 = RC mode; 1 = A1 mode
|
620
|
-
write_op 0x000f, [mode].pack('v')
|
621
|
-
end
|
622
|
-
##
|
623
|
-
# Write a cell with a Numeric or Date value.
|
624
|
-
def write_rk row, idx
|
625
|
-
write_cell :rk, row, idx, encode_rk(row[idx])
|
626
|
-
end
|
627
|
-
def write_row row
|
628
|
-
# Offset Size Contents
|
629
|
-
# 0 2 Index of this row
|
630
|
-
# 2 2 Index to column of the first cell which
|
631
|
-
# is described by a cell record
|
632
|
-
# 4 2 Index to column of the last cell which is
|
633
|
-
# described by a cell record, increased by 1
|
634
|
-
# 6 2 Bit Mask Contents
|
635
|
-
# 14-0 0x7fff Height of the row, in twips = 1/20 of a point
|
636
|
-
# 15 0x8000 0 = Row has custom height;
|
637
|
-
# 1 = Row has default height
|
638
|
-
# 8 2 Not used
|
639
|
-
# 10 2 In BIFF3-BIFF4 this field contains a relative offset to
|
640
|
-
# calculate stream position of the first cell record for this
|
641
|
-
# row (➜ 5.7.1). In BIFF5-BIFF8 this field is not used
|
642
|
-
# anymore, but the DBCELL record (➜ 6.26) instead.
|
643
|
-
# 12 4 Option flags and default row formatting:
|
644
|
-
# Bit Mask Contents
|
645
|
-
# 2-0 0x00000007 Outline level of the row
|
646
|
-
# 4 0x00000010 1 = Outline group starts or ends here
|
647
|
-
# (depending on where the outline
|
648
|
-
# buttons are located, see WSBOOL
|
649
|
-
# record, ➜ 6.113), and is collapsed
|
650
|
-
# 5 0x00000020 1 = Row is hidden (manually, or by a
|
651
|
-
# filter or outline group)
|
652
|
-
# 6 0x00000040 1 = Row height and default font height
|
653
|
-
# do not match
|
654
|
-
# 7 0x00000080 1 = Row has explicit default format (fl)
|
655
|
-
# 8 0x00000100 Always 1
|
656
|
-
# 27-16 0x0fff0000 If fl = 1: Index to default XF record
|
657
|
-
# (➜ 6.115)
|
658
|
-
# 28 0x10000000 1 = Additional space above the row.
|
659
|
-
# This flag is set, if the upper
|
660
|
-
# border of at least one cell in this
|
661
|
-
# row or if the lower border of at
|
662
|
-
# least one cell in the row above is
|
663
|
-
# formatted with a thick line style.
|
664
|
-
# Thin and medium line styles are not
|
665
|
-
# taken into account.
|
666
|
-
# 29 0x20000000 1 = Additional space below the row.
|
667
|
-
# This flag is set, if the lower
|
668
|
-
# border of at least one cell in this
|
669
|
-
# row or if the upper border of at
|
670
|
-
# least one cell in the row below is
|
671
|
-
# formatted with a medium or thick
|
672
|
-
# line style. Thin line styles are
|
673
|
-
# not taken into account.
|
674
|
-
height = row.height || ROW_HEIGHT
|
675
|
-
opts = row.outline_level & 0x00000007
|
676
|
-
opts |= 0x00000010 if row.collapsed?
|
677
|
-
opts |= 0x00000020 if row.hidden?
|
678
|
-
opts |= 0x00000040 if height != ROW_HEIGHT
|
679
|
-
if fmt = row.default_format
|
680
|
-
xf_idx = @workbook.xf_index @worksheet.workbook, fmt
|
681
|
-
opts |= 0x00000080
|
682
|
-
opts |= xf_idx << 16
|
683
|
-
end
|
684
|
-
opts |= 0x00000100
|
685
|
-
height = if height == ROW_HEIGHT
|
686
|
-
(height * TWIPS).to_i | 0x8000
|
687
|
-
else
|
688
|
-
height * TWIPS
|
689
|
-
end
|
690
|
-
|
691
|
-
attrs = [
|
692
|
-
row.idx,
|
693
|
-
row.first_used,
|
694
|
-
row.first_unused,
|
695
|
-
height,
|
696
|
-
opts]
|
697
|
-
|
698
|
-
return if attrs.any?(&:nil?)
|
699
|
-
|
700
|
-
# TODO: Row spacing
|
701
|
-
data = attrs.pack binfmt(:row)
|
702
|
-
write_op opcode(:row), data
|
703
|
-
end
|
704
|
-
def write_rowblock block
|
705
|
-
# ●● ROW Properties of the used rows
|
706
|
-
# ○○ Cell Block(s) Cell records for all used cells
|
707
|
-
# ○ DBCELL Stream offsets to the cell records of each row
|
708
|
-
block.each do |row|
|
709
|
-
write_row row
|
710
|
-
end
|
711
|
-
block.each do |row|
|
712
|
-
write_cellblocks row
|
713
|
-
end
|
714
|
-
end
|
715
|
-
def write_rows
|
716
|
-
row_blocks.each do |block|
|
717
|
-
write_rowblock block
|
718
|
-
end
|
719
|
-
end
|
720
|
-
def write_saverecalc
|
721
|
-
# 0 = Do not recalculate; 1 = Recalculate before saving the document
|
722
|
-
write_op 0x005f, [1].pack('v')
|
723
|
-
end
|
724
|
-
def write_window2
|
725
|
-
# This record contains additional settings for the document window
|
726
|
-
# (BIFF2-BIFF4) or for the window of a specific worksheet (BIFF5-BIFF8).
|
727
|
-
# It is part of the Sheet View Settings Block (➜ 4.5).
|
728
|
-
# Offset Size Contents
|
729
|
-
# 0 2 Option flags:
|
730
|
-
# Bits Mask Contents
|
731
|
-
# 0 0x0001 0 = Show formula results
|
732
|
-
# 1 = Show formulas
|
733
|
-
# 1 0x0002 0 = Do not show grid lines
|
734
|
-
# 1 = Show grid lines
|
735
|
-
# 2 0x0004 0 = Do not show sheet headers
|
736
|
-
# 1 = Show sheet headers
|
737
|
-
# 3 0x0008 0 = Panes are not frozen
|
738
|
-
# 1 = Panes are frozen (freeze)
|
739
|
-
# 4 0x0010 0 = Show zero values as empty cells
|
740
|
-
# 1 = Show zero values
|
741
|
-
# 5 0x0020 0 = Manual grid line colour
|
742
|
-
# 1 = Automatic grid line colour
|
743
|
-
# 6 0x0040 0 = Columns from left to right
|
744
|
-
# 1 = Columns from right to left
|
745
|
-
# 7 0x0080 0 = Do not show outline symbols
|
746
|
-
# 1 = Show outline symbols
|
747
|
-
# 8 0x0100 0 = Keep splits if pane freeze is removed
|
748
|
-
# 1 = Remove splits if pane freeze is removed
|
749
|
-
# 9 0x0200 0 = Sheet not selected
|
750
|
-
# 1 = Sheet selected (BIFF5-BIFF8)
|
751
|
-
# 10 0x0400 0 = Sheet not active
|
752
|
-
# 1 = Sheet active (BIFF5-BIFF8)
|
753
|
-
# 11 0x0800 0 = Show in normal view
|
754
|
-
# 1 = Show in page break preview (BIFF8)
|
755
|
-
# 2 2 Index to first visible row
|
756
|
-
# 4 2 Index to first visible column
|
757
|
-
# 6 2 Colour index of grid line colour (➜ 5.74).
|
758
|
-
# Note that in BIFF2-BIFF5 an RGB colour is written instead.
|
759
|
-
# 8 2 Not used
|
760
|
-
# 10 2 Cached magnification factor in page break preview (in percent)
|
761
|
-
# 0 = Default (60%)
|
762
|
-
# 12 2 Cached magnification factor in normal view (in percent)
|
763
|
-
# 0 = Default (100%)
|
764
|
-
# 14 4 Not used
|
765
|
-
flags = 0x0536 # Show grid lines, sheet headers, zero values. Automatic
|
766
|
-
# grid line colour, Remove slits if pane freeze is removed,
|
767
|
-
# Sheet is active.
|
768
|
-
if @worksheet.selected
|
769
|
-
flags |= 0x0200
|
770
|
-
end
|
771
|
-
flags |= 0x0080 # Show outline symbols,
|
772
|
-
# but if [Row|Column]#outline_level = 0 the symbols are not shown.
|
773
|
-
|
774
|
-
if @worksheet.has_frozen_panel?
|
775
|
-
# See:
|
776
|
-
# OpenOffice.org's Documentation of the Microsoft Excel File FormatExcel
|
777
|
-
# Versions 2, 3, 4, 5, 95, 97, 2000, XP, 2003
|
778
|
-
# https://www.openoffice.org/sc/excelfileformat.pdf
|
779
|
-
# section 5.110.2
|
780
|
-
flags |= 0x0008 #Panes are frozen (freeze)
|
781
|
-
end
|
782
|
-
data = [ flags, 0, 0, 0, 0, 0 ].pack binfmt(:window2)
|
783
|
-
|
784
|
-
write_op opcode(:window2), data
|
785
|
-
if @worksheet.has_frozen_panel?
|
786
|
-
# See:
|
787
|
-
# OpenOffice.org's Documentation of the Microsoft Excel File FormatExcel
|
788
|
-
# Versions 2, 3, 4, 5, 95, 97, 2000, XP, 2003
|
789
|
-
# https://www.openoffice.org/sc/excelfileformat.pdf
|
790
|
-
# section 5.75
|
791
|
-
pane_data = [@worksheet.froze_left, @worksheet.froze_top, @worksheet.froze_top, @worksheet.froze_left, 3].pack binfmt(:colinfo)
|
792
|
-
write_op opcode(:pane), pane_data
|
793
|
-
end
|
794
|
-
|
795
|
-
end
|
796
311
|
|
797
|
-
|
798
|
-
|
799
|
-
|
800
|
-
|
312
|
+
def write_defcolwidth
|
313
|
+
# Offset Size Contents
|
314
|
+
# 0 2 Column width in characters, using the width of the zero
|
315
|
+
# character from default font (first FONT record in the
|
316
|
+
# file). Excel adds some extra space to the default width,
|
317
|
+
# depending on the default font and default font size. The
|
318
|
+
# algorithm how to exactly calculate the resulting column
|
319
|
+
# width is not known.
|
320
|
+
#
|
321
|
+
# Example: The default width of 8 set in this record results
|
322
|
+
# in a column width of 8.43 using Arial font with a size of
|
323
|
+
# 10 points.
|
324
|
+
write_op 0x0055, [8].pack("v")
|
325
|
+
end
|
801
326
|
|
802
|
-
|
803
|
-
|
804
|
-
|
805
|
-
|
806
|
-
|
807
|
-
|
808
|
-
|
327
|
+
def write_dimensions
|
328
|
+
# Offset Size Contents
|
329
|
+
# 0 4 Index to first used row
|
330
|
+
# 4 4 Index to last used row, increased by 1
|
331
|
+
# 8 2 Index to first used column
|
332
|
+
# 10 2 Index to last used column, increased by 1
|
333
|
+
# 12 2 Not used
|
334
|
+
write_op 0x0200, @worksheet.dimensions.pack(binfmt(:dimensions))
|
335
|
+
end
|
809
336
|
|
810
|
-
|
811
|
-
|
812
|
-
|
813
|
-
if @worksheet.pagesetup[:orientation]
|
814
|
-
data[5] = @worksheet.pagesetup[:orientation] == :landscape ? 0 : 2
|
815
|
-
end
|
337
|
+
def write_eof
|
338
|
+
write_op 0x000a
|
339
|
+
end
|
816
340
|
|
817
|
-
|
818
|
-
|
819
|
-
|
341
|
+
##
|
342
|
+
# Write a cell with a Formula. May write an additional String record depending
|
343
|
+
# on the stored result of the Formula.
|
344
|
+
def write_formula row, idx
|
345
|
+
xf_idx = @workbook.xf_index @worksheet.workbook, row.format(idx)
|
346
|
+
cell = row[idx]
|
347
|
+
data1 = [
|
348
|
+
row.idx, # Index to row
|
349
|
+
idx, # Index to column
|
350
|
+
xf_idx # Index to XF record (➜ 6.115)
|
351
|
+
].pack "v3"
|
352
|
+
data2 = nil
|
353
|
+
case value = cell.value
|
354
|
+
when Numeric # IEEE 754 floating-point value (64-bit double precision)
|
355
|
+
data2 = [value].pack EIGHT_BYTE_DOUBLE
|
356
|
+
when String
|
357
|
+
data2 = [
|
358
|
+
0x00, # (identifier for a string value)
|
359
|
+
0xffff
|
360
|
+
].pack "Cx5v"
|
361
|
+
when true, false
|
362
|
+
value = value ? 1 : 0
|
363
|
+
data2 = [
|
364
|
+
0x01, # (identifier for a Boolean value)
|
365
|
+
value, # 0 = FALSE, 1 = TRUE
|
366
|
+
0xffff
|
367
|
+
].pack "CxCx3v"
|
368
|
+
when Error
|
369
|
+
data2 = [
|
370
|
+
0x02, # (identifier for an error value)
|
371
|
+
value.code, # Error code
|
372
|
+
0xffff
|
373
|
+
].pack "CxCx3v"
|
374
|
+
when nil
|
375
|
+
data2 = [
|
376
|
+
0x03, # (identifier for an empty cell)
|
377
|
+
0xffff
|
378
|
+
].pack "Cx5v"
|
379
|
+
else
|
380
|
+
data2 = [
|
381
|
+
0x02, # (identifier for an error value)
|
382
|
+
0x2a, # Error code: #N/A! Argument || function not available
|
383
|
+
0xffff
|
384
|
+
].pack "CxCx3v"
|
385
|
+
end
|
386
|
+
opts = 0x03
|
387
|
+
opts |= 0x08 if cell.shared
|
388
|
+
data3 = [
|
389
|
+
opts # Option flags:
|
390
|
+
# Bit Mask Contents
|
391
|
+
# 0 0x0001 1 = Recalculate always
|
392
|
+
# 1 0x0002 1 = Calculate on open
|
393
|
+
# 3 0x0008 1 = Part of a shared formula
|
394
|
+
].pack "vx4"
|
395
|
+
write_op opcode(:formula), data1, data2, data3, cell.data
|
396
|
+
if cell.value.is_a?(String)
|
397
|
+
write_op opcode(:string), unicode_string(cell.value, 2)
|
398
|
+
end
|
399
|
+
end
|
820
400
|
|
821
|
-
|
822
|
-
|
401
|
+
##
|
402
|
+
# Write a new Worksheet.
|
403
|
+
def write_from_scratch
|
404
|
+
# ● BOF Type = worksheet (➜ 5.8)
|
405
|
+
write_bof
|
406
|
+
# ○ UNCALCED ➜ 5.105
|
407
|
+
# ○ INDEX ➜ 4.7 (Row Blocks), ➜ 5.59
|
408
|
+
# ○ Calculation Settings Block ➜ 4.3
|
409
|
+
write_calccount
|
410
|
+
write_refmode
|
411
|
+
write_iteration
|
412
|
+
write_saverecalc
|
413
|
+
# ○ PRINTHEADERS ➜ 5.81
|
414
|
+
# ○ PRINTGRIDLINES ➜ 5.80
|
415
|
+
# ○ GRIDSET ➜ 5.52
|
416
|
+
# ○ GUTS ➜ 5.53
|
417
|
+
write_guts
|
418
|
+
# ○ DEFAULTROWHEIGHT ➜ 5.31
|
419
|
+
write_defaultrowheight
|
420
|
+
# ○ WSBOOL ➜ 5.113
|
421
|
+
write_wsbool
|
422
|
+
# ○ Page Settings Block ➜ 4.4
|
423
|
+
# ○ Worksheet Protection Block ➜ 4.18
|
424
|
+
write_proctection
|
425
|
+
# ○ DEFCOLWIDTH ➜ 5.32
|
426
|
+
write_defcolwidth
|
427
|
+
# ○○ COLINFO ➜ 5.18
|
428
|
+
write_colinfos
|
429
|
+
# ○ SORT ➜ 5.99
|
430
|
+
# ● DIMENSIONS ➜ 5.35
|
431
|
+
write_dimensions
|
432
|
+
# ○○ Row Blocks ➜ 4.7
|
433
|
+
write_rows
|
434
|
+
# ● Worksheet View Settings Block ➜ 4.5
|
435
|
+
# ● WINDOW2 ➜ 5.110
|
436
|
+
write_window2
|
437
|
+
# ○ SCL ➜ 5.92 (BIFF4-BIFF8 only)
|
438
|
+
# ○ PANE ➜ 5.75
|
439
|
+
# ○○ SELECTION ➜ 5.93
|
440
|
+
# ○ STANDARDWIDTH ➜ 5.101
|
441
|
+
# ○○ MERGEDCELLS ➜ 5.67
|
442
|
+
write_merged_cells
|
443
|
+
# ○ LABELRANGES ➜ 5.64
|
444
|
+
# ○ PHONETIC ➜ 5.77
|
445
|
+
# ○ Conditional Formatting Table ➜ 4.12
|
446
|
+
# ○ Hyperlink Table ➜ 4.13
|
447
|
+
write_pagesetup
|
448
|
+
write_margins
|
449
|
+
write_hyperlink_table
|
450
|
+
# ○ Data Validity Table ➜ 4.14
|
451
|
+
# ○ SHEETLAYOUT ➜ 5.96 (BIFF8X only)
|
452
|
+
# ○ SHEETPROTECTION Additional protection, ➜ 5.98 (BIFF8X only)
|
453
|
+
# ○ RANGEPROTECTION Additional protection, ➜ 5.84 (BIFF8X only)
|
454
|
+
# ● EOF ➜ 5.36
|
455
|
+
write_eof
|
456
|
+
end
|
823
457
|
|
824
|
-
|
825
|
-
|
826
|
-
|
827
|
-
|
828
|
-
|
829
|
-
|
458
|
+
##
|
459
|
+
# Write record that contains information about the layout of outline symbols.
|
460
|
+
def write_guts
|
461
|
+
# find the maximum outline_level in rows and columns
|
462
|
+
row_outline_level = 0
|
463
|
+
col_outline_level = 0
|
464
|
+
if (row = @worksheet.rows.select { |x| !x.nil? }.max_by(&:outline_level))
|
465
|
+
row_outline_level = row.outline_level
|
466
|
+
end
|
467
|
+
if (col = @worksheet.columns.select { |x| !x.nil? }.max_by(&:outline_level))
|
468
|
+
col_outline_level = col.outline_level
|
469
|
+
end
|
470
|
+
# set data
|
471
|
+
data = [
|
472
|
+
0, # Width of the area to display row outlines (left of the sheet), in pixel
|
473
|
+
0, # Height of the area to display column outlines (above the sheet), in pixel
|
474
|
+
row_outline_level + 1, # Number of visible row outline levels (used row levels+1; || 0,if not used)
|
475
|
+
col_outline_level + 1 # Number of visible column outline levels (used column levels+1; || 0,if not used)
|
476
|
+
]
|
477
|
+
# write record
|
478
|
+
write_op opcode(:guts), data.pack("v4")
|
479
|
+
end
|
830
480
|
|
831
|
-
|
832
|
-
|
833
|
-
|
834
|
-
|
835
|
-
|
836
|
-
|
837
|
-
|
838
|
-
|
839
|
-
|
481
|
+
def write_hlink row, col, link
|
482
|
+
# FIXME: only Hyperlinks are supported at present.
|
483
|
+
cell_range = [
|
484
|
+
row, row, # Cell range address of all cells containing this hyperlink
|
485
|
+
col, col # (➜ 3.13.1)
|
486
|
+
].pack "v4"
|
487
|
+
guid = [
|
488
|
+
# GUID of StdLink:
|
489
|
+
# D0 C9 EA 79 F9 BA CE 11 8C 82 00 AA 00 4B A9 0B
|
490
|
+
# (79EAC9D0-BAF9-11CE-8C82-00AA004BA90B)
|
491
|
+
"d0c9ea79f9bace118c8200aa004ba90b"
|
492
|
+
].pack "H32"
|
493
|
+
opts = 0x01
|
494
|
+
opts |= 0x02
|
495
|
+
opts |= 0x14 unless link == link.url
|
496
|
+
opts |= 0x08 if link.fragment
|
497
|
+
opts |= 0x80 if link.target_frame
|
498
|
+
# TODO: UNC support
|
499
|
+
options = [
|
500
|
+
2, # Unknown value: 0x00000002
|
501
|
+
opts # Option flags
|
502
|
+
# Bit Mask Contents
|
503
|
+
# 0 0x00000001 0 = No link extant
|
504
|
+
# 1 = File link || URL
|
505
|
+
# 1 0x00000002 0 = Relative file path
|
506
|
+
# 1 = Absolute path || URL
|
507
|
+
# 2 and 4 0x00000014 0 = No description
|
508
|
+
# 1 (both bits) = Description
|
509
|
+
# 3 0x00000008 0 = No text mark
|
510
|
+
# 1 = Text mark
|
511
|
+
# 7 0x00000080 0 = No target frame
|
512
|
+
# 1 = Target frame
|
513
|
+
# 8 0x00000100 0 = File link || URL
|
514
|
+
# 1 = UNC path (incl. server name)
|
515
|
+
].pack("V2")
|
516
|
+
tail = []
|
517
|
+
## call internal to get the correct internal encoding in Ruby 1.9
|
518
|
+
nullstr = internal "\000"
|
519
|
+
unless link == link.url
|
520
|
+
desc = internal(link).dup << nullstr
|
521
|
+
tail.push [desc.size / 2].pack("V"), desc
|
522
|
+
end
|
523
|
+
if link.target_frame
|
524
|
+
frme = internal(link.target_frame).dup << nullstr
|
525
|
+
tail.push [frme.size / 2].pack("V"), frme
|
526
|
+
end
|
527
|
+
url = internal(link.url).dup << nullstr
|
528
|
+
tail.push [
|
529
|
+
# 6.53.2 Hyperlink containing a URL (Uniform Resource Locator)
|
530
|
+
# These data fields occur for links which are not local files || files
|
531
|
+
# in the local network (for instance HTTP and FTP links and e-mail
|
532
|
+
# addresses). The lower 9 bits of the option flags field must be
|
533
|
+
# 0.x00x.xx112 (x means optional, depending on hyperlink content). The
|
534
|
+
# GUID could be used to distinguish a URL from a file link.
|
535
|
+
# GUID of URL Moniker:
|
536
|
+
# E0 C9 EA 79 F9 BA CE 11 8C 82 00 AA 00 4B A9 0B
|
537
|
+
# (79EAC9E0-BAF9-11CE-8C82-00AA004BA90B)
|
538
|
+
"e0c9ea79f9bace118c8200aa004ba90b",
|
539
|
+
url.size # Size of character array of the URL, including trailing zero
|
540
|
+
# word (us). There are us/2-1 characters in the following
|
541
|
+
# string.
|
542
|
+
].pack("H32V"), url
|
543
|
+
if link.fragment
|
544
|
+
frag = internal(link.fragment).dup << nullstr
|
545
|
+
tail.push [frag.size / 2].pack("V"), frag
|
546
|
+
end
|
547
|
+
write_op opcode(:hlink), cell_range, guid, options, *tail
|
548
|
+
end
|
840
549
|
|
841
|
-
|
842
|
-
|
843
|
-
|
844
|
-
|
845
|
-
|
846
|
-
|
847
|
-
|
848
|
-
|
849
|
-
|
850
|
-
|
851
|
-
|
852
|
-
|
853
|
-
|
854
|
-
|
855
|
-
|
856
|
-
|
857
|
-
|
858
|
-
|
859
|
-
|
860
|
-
|
861
|
-
|
862
|
-
|
863
|
-
|
864
|
-
|
865
|
-
|
866
|
-
|
867
|
-
|
868
|
-
|
869
|
-
|
870
|
-
|
871
|
-
|
872
|
-
|
873
|
-
|
874
|
-
|
875
|
-
|
876
|
-
|
877
|
-
|
878
|
-
|
879
|
-
|
880
|
-
end
|
550
|
+
def write_hyperlink_table
|
551
|
+
# TODO: theoretically it's possible to write fewer records by combining
|
552
|
+
# identical neighboring links in cell-ranges
|
553
|
+
@worksheet.each do |row|
|
554
|
+
row.each_with_index do |cell, idx|
|
555
|
+
if cell.is_a? Link
|
556
|
+
write_hlink row.idx, idx, cell
|
557
|
+
end
|
558
|
+
end
|
559
|
+
end
|
560
|
+
end
|
561
|
+
|
562
|
+
def write_iteration
|
563
|
+
its = 0 # 0 = Iterations off; 1 = Iterations on
|
564
|
+
write_op 0x0011, [its].pack("v")
|
565
|
+
end
|
566
|
+
|
567
|
+
##
|
568
|
+
# Write a cell with a String value. The String must have been stored in the
|
569
|
+
# Shared String Table.
|
570
|
+
def write_labelsst row, idx
|
571
|
+
write_cell :labelsst, row, idx, @workbook.sst_index(self, row[idx])
|
572
|
+
end
|
573
|
+
|
574
|
+
##
|
575
|
+
# Write multiple consecutive blank cells.
|
576
|
+
def write_mulblank row, idx, multiples
|
577
|
+
data = [
|
578
|
+
row.idx, # Index to row
|
579
|
+
idx # Index to first column (fc)
|
580
|
+
]
|
581
|
+
# List of nc=lc-fc+1 16-bit indexes to XF records (➜ 6.115)
|
582
|
+
multiples.each_with_index do |blank, cell_idx|
|
583
|
+
xf_idx = @workbook.xf_index @worksheet.workbook, row.format(idx + cell_idx)
|
584
|
+
data.push xf_idx
|
585
|
+
end
|
586
|
+
# Index to last column (lc)
|
587
|
+
data.push idx + multiples.size - 1
|
588
|
+
write_op opcode(:mulblank), data.pack("v*")
|
589
|
+
end
|
590
|
+
|
591
|
+
##
|
592
|
+
# Write multiple consecutive cells with RK values (see #write_rk)
|
593
|
+
def write_mulrk row, idx, multiples
|
594
|
+
fmt = ["v2"]
|
595
|
+
data = [
|
596
|
+
row.idx, # Index to row
|
597
|
+
idx # Index to first column (fc)
|
598
|
+
]
|
599
|
+
# List of nc=lc-fc+1 16-bit indexes to XF records (➜ 6.115)
|
600
|
+
multiples.each_with_index do |cell, cell_idx|
|
601
|
+
xf_idx = @workbook.xf_index @worksheet.workbook, row.format(idx + cell_idx)
|
602
|
+
data.push xf_idx, encode_rk(cell)
|
603
|
+
fmt << "vV"
|
604
|
+
end
|
605
|
+
# Index to last column (lc)
|
606
|
+
data.push idx + multiples.size - 1
|
607
|
+
write_op opcode(:mulrk), data.pack((fmt << "v").join)
|
608
|
+
end
|
609
|
+
|
610
|
+
def write_multiples row, idx, multiples
|
611
|
+
case multiples.last
|
612
|
+
when NilClass
|
613
|
+
write_mulblank row, idx, multiples
|
614
|
+
when Numeric
|
615
|
+
if multiples.size > 1
|
616
|
+
write_mulrk row, idx, multiples
|
617
|
+
else
|
618
|
+
write_rk row, idx
|
619
|
+
end
|
620
|
+
end
|
621
|
+
end
|
622
|
+
|
623
|
+
##
|
624
|
+
# Write a cell with a 64-bit double precision Float value
|
625
|
+
def write_number row, idx
|
626
|
+
# Offset Size Contents
|
627
|
+
# 0 2 Index to row
|
628
|
+
# 2 2 Index to column
|
629
|
+
# 4 2 Index to XF record (➜ 6.115)
|
630
|
+
# 6 8 IEEE 754 floating-point value (64-bit double precision)
|
631
|
+
value = row[idx]
|
632
|
+
case value
|
633
|
+
when Date, Time
|
634
|
+
value = encode_date(value)
|
635
|
+
end
|
636
|
+
write_cell :number, row, idx, value
|
637
|
+
end
|
638
|
+
|
639
|
+
def write_op op, *args
|
640
|
+
data = args.join
|
641
|
+
@io.write [op, data.size].pack("v2")
|
642
|
+
@io.write data
|
643
|
+
end
|
644
|
+
|
645
|
+
def write_refmode
|
646
|
+
# • The “RC” mode uses numeric indexes for rows and columns, for example
|
647
|
+
# “R(1)C(-1)”, || “R1C1:R2C2”.
|
648
|
+
# • The “A1” mode uses characters for columns and numbers for rows, for
|
649
|
+
# example “B1”, || “$A$1:$B$2”.
|
650
|
+
mode = 1 # 0 = RC mode; 1 = A1 mode
|
651
|
+
write_op 0x000f, [mode].pack("v")
|
652
|
+
end
|
653
|
+
|
654
|
+
##
|
655
|
+
# Write a cell with a Numeric || Date value.
|
656
|
+
def write_rk row, idx
|
657
|
+
write_cell :rk, row, idx, encode_rk(row[idx])
|
658
|
+
end
|
659
|
+
|
660
|
+
def write_row row
|
661
|
+
# Offset Size Contents
|
662
|
+
# 0 2 Index of this row
|
663
|
+
# 2 2 Index to column of the first cell which
|
664
|
+
# is described by a cell record
|
665
|
+
# 4 2 Index to column of the last cell which is
|
666
|
+
# described by a cell record, increased by 1
|
667
|
+
# 6 2 Bit Mask Contents
|
668
|
+
# 14-0 0x7fff Height of the row, in twips = 1/20 of a point
|
669
|
+
# 15 0x8000 0 = Row has custom height;
|
670
|
+
# 1 = Row has default height
|
671
|
+
# 8 2 Not used
|
672
|
+
# 10 2 In BIFF3-BIFF4 this field contains a relative offset to
|
673
|
+
# calculate stream position of the first cell record for this
|
674
|
+
# row (➜ 5.7.1). In BIFF5-BIFF8 this field is not used
|
675
|
+
# anymore, but the DBCELL record (➜ 6.26) instead.
|
676
|
+
# 12 4 Option flags and default row formatting:
|
677
|
+
# Bit Mask Contents
|
678
|
+
# 2-0 0x00000007 Outline level of the row
|
679
|
+
# 4 0x00000010 1 = Outline group starts || ends here
|
680
|
+
# (depending on where the outline
|
681
|
+
# buttons are located, see WSBOOL
|
682
|
+
# record, ➜ 6.113), and is collapsed
|
683
|
+
# 5 0x00000020 1 = Row is hidden (manually, || by a
|
684
|
+
# filter || outline group)
|
685
|
+
# 6 0x00000040 1 = Row height and default font height
|
686
|
+
# do not match
|
687
|
+
# 7 0x00000080 1 = Row has explicit default format (fl)
|
688
|
+
# 8 0x00000100 Always 1
|
689
|
+
# 27-16 0x0fff0000 If fl = 1: Index to default XF record
|
690
|
+
# (➜ 6.115)
|
691
|
+
# 28 0x10000000 1 = Additional space above the row.
|
692
|
+
# This flag is set, if the upper
|
693
|
+
# border of at least one cell in this
|
694
|
+
# row || if the lower border of at
|
695
|
+
# least one cell in the row above is
|
696
|
+
# formatted with a thick line style.
|
697
|
+
# Thin and medium line styles are not
|
698
|
+
# taken into account.
|
699
|
+
# 29 0x20000000 1 = Additional space below the row.
|
700
|
+
# This flag is set, if the lower
|
701
|
+
# border of at least one cell in this
|
702
|
+
# row || if the upper border of at
|
703
|
+
# least one cell in the row below is
|
704
|
+
# formatted with a medium || thick
|
705
|
+
# line style. Thin line styles are
|
706
|
+
# not taken into account.
|
707
|
+
height = row.height || ROW_HEIGHT
|
708
|
+
opts = row.outline_level & 0x00000007
|
709
|
+
opts |= 0x00000010 if row.collapsed?
|
710
|
+
opts |= 0x00000020 if row.hidden?
|
711
|
+
opts |= 0x00000040 if height != ROW_HEIGHT
|
712
|
+
if (fmt = row.default_format)
|
713
|
+
xf_idx = @workbook.xf_index @worksheet.workbook, fmt
|
714
|
+
opts |= 0x00000080
|
715
|
+
opts |= xf_idx << 16
|
716
|
+
end
|
717
|
+
opts |= 0x00000100
|
718
|
+
height = if height == ROW_HEIGHT
|
719
|
+
(height * TWIPS).to_i | 0x8000
|
720
|
+
else
|
721
|
+
height * TWIPS
|
722
|
+
end
|
723
|
+
|
724
|
+
attrs = [
|
725
|
+
row.idx,
|
726
|
+
row.first_used,
|
727
|
+
row.first_unused,
|
728
|
+
height,
|
729
|
+
opts
|
730
|
+
]
|
731
|
+
|
732
|
+
return if attrs.any?(&:nil?)
|
733
|
+
|
734
|
+
# TODO: Row spacing
|
735
|
+
data = attrs.pack binfmt(:row)
|
736
|
+
write_op opcode(:row), data
|
737
|
+
end
|
738
|
+
|
739
|
+
def write_rowblock block
|
740
|
+
# ●● ROW Properties of the used rows
|
741
|
+
# ○○ Cell Block(s) Cell records for all used cells
|
742
|
+
# ○ DBCELL Stream offsets to the cell records of each row
|
743
|
+
block.each do |row|
|
744
|
+
write_row row
|
745
|
+
end
|
746
|
+
block.each do |row|
|
747
|
+
write_cellblocks row
|
748
|
+
end
|
749
|
+
end
|
750
|
+
|
751
|
+
def write_rows
|
752
|
+
row_blocks.each do |block|
|
753
|
+
write_rowblock block
|
754
|
+
end
|
755
|
+
end
|
756
|
+
|
757
|
+
def write_saverecalc
|
758
|
+
# 0 = Do not recalculate; 1 = Recalculate before saving the document
|
759
|
+
write_op 0x005f, [1].pack("v")
|
760
|
+
end
|
761
|
+
|
762
|
+
def write_window2
|
763
|
+
# This record contains additional settings for the document window
|
764
|
+
# (BIFF2-BIFF4) || for the window of a specific worksheet (BIFF5-BIFF8).
|
765
|
+
# It is part of the Sheet View Settings Block (➜ 4.5).
|
766
|
+
# Offset Size Contents
|
767
|
+
# 0 2 Option flags:
|
768
|
+
# Bits Mask Contents
|
769
|
+
# 0 0x0001 0 = Show formula results
|
770
|
+
# 1 = Show formulas
|
771
|
+
# 1 0x0002 0 = Do not show grid lines
|
772
|
+
# 1 = Show grid lines
|
773
|
+
# 2 0x0004 0 = Do not show sheet headers
|
774
|
+
# 1 = Show sheet headers
|
775
|
+
# 3 0x0008 0 = Panes are not frozen
|
776
|
+
# 1 = Panes are frozen (freeze)
|
777
|
+
# 4 0x0010 0 = Show zero values as empty cells
|
778
|
+
# 1 = Show zero values
|
779
|
+
# 5 0x0020 0 = Manual grid line colour
|
780
|
+
# 1 = Automatic grid line colour
|
781
|
+
# 6 0x0040 0 = Columns from left to right
|
782
|
+
# 1 = Columns from right to left
|
783
|
+
# 7 0x0080 0 = Do not show outline symbols
|
784
|
+
# 1 = Show outline symbols
|
785
|
+
# 8 0x0100 0 = Keep splits if pane freeze is removed
|
786
|
+
# 1 = Remove splits if pane freeze is removed
|
787
|
+
# 9 0x0200 0 = Sheet not selected
|
788
|
+
# 1 = Sheet selected (BIFF5-BIFF8)
|
789
|
+
# 10 0x0400 0 = Sheet not active
|
790
|
+
# 1 = Sheet active (BIFF5-BIFF8)
|
791
|
+
# 11 0x0800 0 = Show in normal view
|
792
|
+
# 1 = Show in page break preview (BIFF8)
|
793
|
+
# 2 2 Index to first visible row
|
794
|
+
# 4 2 Index to first visible column
|
795
|
+
# 6 2 Colour index of grid line colour (➜ 5.74).
|
796
|
+
# Note that in BIFF2-BIFF5 an RGB colour is written instead.
|
797
|
+
# 8 2 Not used
|
798
|
+
# 10 2 Cached magnification factor in page break preview (in percent)
|
799
|
+
# 0 = Default (60%)
|
800
|
+
# 12 2 Cached magnification factor in normal view (in percent)
|
801
|
+
# 0 = Default (100%)
|
802
|
+
# 14 4 Not used
|
803
|
+
flags = 0x0536 # Show grid lines, sheet headers, zero values. Automatic
|
804
|
+
# grid line colour, Remove slits if pane freeze is removed,
|
805
|
+
# Sheet is active.
|
806
|
+
if @worksheet.selected
|
807
|
+
flags |= 0x0200
|
808
|
+
end
|
809
|
+
flags |= 0x0080 # Show outline symbols,
|
810
|
+
# but if [Row|Column]#outline_level = 0 the symbols are not shown.
|
811
|
+
|
812
|
+
if @worksheet.has_frozen_panel?
|
813
|
+
# See:
|
814
|
+
# OpenOffice.org's Documentation of the Microsoft Excel File FormatExcel
|
815
|
+
# Versions 2, 3, 4, 5, 95, 97, 2000, XP, 2003
|
816
|
+
# https://www.openoffice.org/sc/excelfileformat.pdf
|
817
|
+
# section 5.110.2
|
818
|
+
flags |= 0x0008 # Panes are frozen (freeze)
|
819
|
+
end
|
820
|
+
data = [flags, 0, 0, 0, 0, 0].pack binfmt(:window2)
|
821
|
+
|
822
|
+
write_op opcode(:window2), data
|
823
|
+
if @worksheet.has_frozen_panel?
|
824
|
+
# See:
|
825
|
+
# OpenOffice.org's Documentation of the Microsoft Excel File FormatExcel
|
826
|
+
# Versions 2, 3, 4, 5, 95, 97, 2000, XP, 2003
|
827
|
+
# https://www.openoffice.org/sc/excelfileformat.pdf
|
828
|
+
# section 5.75
|
829
|
+
pane_data = [@worksheet.froze_left, @worksheet.froze_top, @worksheet.froze_top, @worksheet.froze_left, 3].pack binfmt(:colinfo)
|
830
|
+
write_op opcode(:pane), pane_data
|
831
|
+
end
|
832
|
+
end
|
833
|
+
|
834
|
+
def write_merged_cells
|
835
|
+
return unless @worksheet.merged_cells.any?
|
836
|
+
# FIXME standards say the record is limited by 1027 records at once
|
837
|
+
# And no CONTINUE is supported
|
838
|
+
|
839
|
+
merge_cells = @worksheet.merged_cells.dup
|
840
|
+
while (window = merge_cells.slice!(0...1027)).any?
|
841
|
+
count = window.size
|
842
|
+
data = ([count] + window.flatten).pack("v2v*")
|
843
|
+
write_op opcode(:mergedcells), data
|
844
|
+
end
|
845
|
+
end
|
846
|
+
|
847
|
+
def write_pagesetup
|
848
|
+
return unless @worksheet.pagesetup
|
849
|
+
data = @worksheet.pagesetup[:orig_data].dup
|
850
|
+
if @worksheet.pagesetup[:orientation]
|
851
|
+
data[5] = (@worksheet.pagesetup[:orientation] == :landscape) ? 0 : 2
|
852
|
+
end
|
853
|
+
|
854
|
+
if @worksheet.pagesetup[:adjust_to]
|
855
|
+
data[1] = @worksheet.pagesetup[:adjust_to]
|
856
|
+
end
|
857
|
+
|
858
|
+
write_op opcode(:pagesetup), data.pack(binfmt(:pagesetup))
|
859
|
+
end
|
860
|
+
|
861
|
+
def write_margins
|
862
|
+
@worksheet.margins.each do |key, value|
|
863
|
+
next unless [:left, :top, :right, :bottom].include?(key)
|
864
|
+
write_op opcode(:"#{key}margin"), [value].pack(binfmt(:margin))
|
865
|
+
end
|
866
|
+
end
|
867
|
+
|
868
|
+
def write_proctection
|
869
|
+
return unless @worksheet.protected?
|
870
|
+
# ○ PROTECT Worksheet contents: 1 = protected (➜ 5.82)
|
871
|
+
write_op opcode(:protect), [1].pack("v")
|
872
|
+
# ○ OBJECTPROTECT Embedded objects: 1 = protected (➜ 5.72)
|
873
|
+
# ○ SCENPROTECT Scenarios: 1 = protected (➜ 5.91)
|
874
|
+
# ○ PASSWORD Hash value of the password; 0 = no password (➜ 5.76)
|
875
|
+
write_op opcode(:password), [@worksheet.password_hash].pack("v")
|
876
|
+
end
|
877
|
+
|
878
|
+
def write_wsbool
|
879
|
+
bits = [
|
880
|
+
# Bit Mask Contents
|
881
|
+
1, # 0 0x0001 0 = Do not show automatic page breaks
|
882
|
+
# 1 = Show automatic page breaks
|
883
|
+
0, # 4 0x0010 0 = Standard sheet
|
884
|
+
# 1 = Dialogue sheet (BIFF5-BIFF8)
|
885
|
+
0, # 5 0x0020 0 = No automatic styles in outlines
|
886
|
+
# 1 = Apply automatic styles to outlines
|
887
|
+
1, # 6 0x0040 0 = Outline buttons above outline group
|
888
|
+
# 1 = Outline buttons below outline group
|
889
|
+
1, # 7 0x0080 0 = Outline buttons left of outline group
|
890
|
+
# 1 = Outline buttons right of outline group
|
891
|
+
0, # 8 0x0100 0 = Scale printout in percent (➜ 6.89)
|
892
|
+
# 1 = Fit printout to number of pages (➜ 6.89)
|
893
|
+
0, # 9 0x0200 0 = Save external linked values
|
894
|
+
# (BIFF3-BIFF4 only, ➜ 5.10)
|
895
|
+
# 1 = Do not save external linked values
|
896
|
+
# (BIFF3-BIFF4 only, ➜ 5.10)
|
897
|
+
1, # 10 0x0400 0 = Do not show row outline symbols
|
898
|
+
# 1 = Show row outline symbols
|
899
|
+
0, # 11 0x0800 0 = Do not show column outline symbols
|
900
|
+
# 1 = Show column outline symbols
|
901
|
+
0, # 13-12 0x3000 These flags specify the arrangement of windows.
|
902
|
+
# They are stored in BIFF4 only.
|
903
|
+
# 00 = Arrange windows tiled
|
904
|
+
# 01 = Arrange windows horizontal
|
905
|
+
0, # 10 = Arrange windows vertical
|
906
|
+
# 11 = Arrange windows cascaded
|
907
|
+
# The following flags are valid for BIFF4-BIFF8 only:
|
908
|
+
0, # 14 0x4000 0 = Standard expression evaluation
|
909
|
+
# 1 = Alternative expression evaluation
|
910
|
+
0 # 15 0x8000 0 = Standard formula entries
|
911
|
+
# 1 = Alternative formula entries
|
912
|
+
]
|
913
|
+
weights = [4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
|
914
|
+
value = bits.inject { |a, b| a | (b << weights.shift) }
|
915
|
+
write_op 0x0081, [value].pack("v")
|
916
|
+
end
|
917
|
+
end
|
881
918
|
end
|
882
919
|
end
|
883
920
|
end
|