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