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.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/lib/parseexcel/parseexcel.rb +66 -58
  3. data/lib/parseexcel/parser.rb +1 -1
  4. data/lib/parseexcel.rb +1 -1
  5. data/lib/spreadsheet/column.rb +11 -9
  6. data/lib/spreadsheet/compatibility.rb +3 -1
  7. data/lib/spreadsheet/datatypes.rb +149 -147
  8. data/lib/spreadsheet/encodings.rb +20 -16
  9. data/lib/spreadsheet/errors.rb +2 -2
  10. data/lib/spreadsheet/excel/error.rb +23 -22
  11. data/lib/spreadsheet/excel/internals/biff5.rb +11 -11
  12. data/lib/spreadsheet/excel/internals/biff8.rb +13 -13
  13. data/lib/spreadsheet/excel/internals.rb +451 -451
  14. data/lib/spreadsheet/excel/offset.rb +32 -31
  15. data/lib/spreadsheet/excel/password_hash.rb +18 -18
  16. data/lib/spreadsheet/excel/reader/biff5.rb +34 -35
  17. data/lib/spreadsheet/excel/reader/biff8.rb +234 -222
  18. data/lib/spreadsheet/excel/reader.rb +1320 -1274
  19. data/lib/spreadsheet/excel/rgb.rb +91 -91
  20. data/lib/spreadsheet/excel/row.rb +99 -91
  21. data/lib/spreadsheet/excel/sst_entry.rb +40 -38
  22. data/lib/spreadsheet/excel/workbook.rb +86 -76
  23. data/lib/spreadsheet/excel/worksheet.rb +125 -107
  24. data/lib/spreadsheet/excel/writer/biff8.rb +56 -55
  25. data/lib/spreadsheet/excel/writer/format.rb +273 -256
  26. data/lib/spreadsheet/excel/writer/n_worksheet.rb +837 -798
  27. data/lib/spreadsheet/excel/writer/workbook.rb +671 -635
  28. data/lib/spreadsheet/excel/writer/worksheet.rb +898 -861
  29. data/lib/spreadsheet/excel/writer.rb +1 -1
  30. data/lib/spreadsheet/excel.rb +18 -11
  31. data/lib/spreadsheet/font.rb +30 -26
  32. data/lib/spreadsheet/format.rb +74 -59
  33. data/lib/spreadsheet/link.rb +7 -5
  34. data/lib/spreadsheet/note.rb +6 -6
  35. data/lib/spreadsheet/noteObject.rb +5 -5
  36. data/lib/spreadsheet/row.rb +33 -23
  37. data/lib/spreadsheet/version.rb +1 -1
  38. data/lib/spreadsheet/workbook.rb +27 -13
  39. data/lib/spreadsheet/worksheet.rb +102 -68
  40. data/lib/spreadsheet/writer.rb +3 -0
  41. data/lib/spreadsheet.rb +12 -15
  42. data/test/excel/reader.rb +8 -8
  43. data/test/excel/row.rb +35 -31
  44. data/test/excel/writer/workbook.rb +18 -16
  45. data/test/excel/writer/worksheet.rb +10 -8
  46. data/test/font.rb +44 -32
  47. data/test/format.rb +38 -33
  48. data/test/integration.rb +627 -598
  49. data/test/row.rb +5 -3
  50. data/test/suite.rb +7 -7
  51. data/test/workbook.rb +15 -14
  52. data/test/workbook_protection.rb +5 -5
  53. data/test/worksheet.rb +36 -34
  54. metadata +48 -6
@@ -1,883 +1,920 @@
1
- require 'stringio'
2
- require 'spreadsheet/excel/writer/biff8'
3
- require 'spreadsheet/excel/internals'
4
- require 'spreadsheet/excel/internals/biff8'
5
- require 'bigdecimal'
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
- include Spreadsheet::Excel::Writer::Biff8
17
- include Spreadsheet::Excel::Internals
18
- include Spreadsheet::Excel::Internals::Biff8
19
- attr_reader :worksheet
20
- def initialize workbook, worksheet
21
- @workbook = workbook
22
- @worksheet = worksheet
23
- @io = StringIO.new ''.dup
24
- @biff_version = 0x0600
25
- @bof = 0x0809
26
- @build_id = 3515
27
- @build_year = 1996
28
- @bof_types = {
29
- :globals => 0x0005,
30
- :visual_basic => 0x0006,
31
- :worksheet => 0x0010,
32
- :chart => 0x0020,
33
- :macro_sheet => 0x0040,
34
- :workspace => 0x0100,
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
- end
281
- end
282
- end
283
- def write_defaultrowheight
284
- data = [
285
- 0x00, # Option flags:
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
- 0xf2, # Default height for unused rows, in twips = 1/20 of a point
292
- ]
293
- write_op 0x0225, data.pack('v2')
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
- def write_merged_cells
798
- return unless @worksheet.merged_cells.any?
799
- # FIXME standards say the record is limited by 1027 records at once
800
- # And no CONTINUE is supported
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
- merge_cells = @worksheet.merged_cells.dup
803
- while (window = merge_cells.slice!(0...1027)).any?
804
- count = window.size
805
- data = ([count] + window.flatten).pack('v2v*')
806
- write_op opcode(:mergedcells), data
807
- end
808
- end
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
- def write_pagesetup
811
- return unless @worksheet.pagesetup
812
- data = @worksheet.pagesetup[:orig_data].dup
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
- if @worksheet.pagesetup[:adjust_to]
818
- data[1] = @worksheet.pagesetup[:adjust_to]
819
- end
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
- write_op opcode(:pagesetup), data.pack(binfmt(:pagesetup))
822
- end
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
- def write_margins
825
- @worksheet.margins.each do |key, value|
826
- next unless [:left, :top, :right, :bottom].include?(key)
827
- write_op opcode(:"#{key}margin"), [value].pack(binfmt(:margin))
828
- end
829
- end
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
- def write_proctection
832
- return unless @worksheet.protected?
833
- # ○ PROTECT Worksheet contents: 1 = protected (➜ 5.82)
834
- write_op opcode(:protect), [1].pack('v')
835
- # OBJECTPROTECT Embedded objects: 1 = protected (➜ 5.72)
836
- # ○ SCENPROTECT Scenarios: 1 = protected (➜ 5.91)
837
- # ○ PASSWORD Hash value of the password; 0 = no password (➜ 5.76)
838
- write_op opcode(:password), [@worksheet.password_hash].pack('v')
839
- end
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
- def write_wsbool
842
- bits = [
843
- # Bit Mask Contents
844
- 1, # 0 0x0001 0 = Do not show automatic page breaks
845
- # 1 = Show automatic page breaks
846
- 0, # 4 0x0010 0 = Standard sheet
847
- # 1 = Dialogue sheet (BIFF5-BIFF8)
848
- 0, # 5 0x0020 0 = No automatic styles in outlines
849
- # 1 = Apply automatic styles to outlines
850
- 1, # 6 0x0040 0 = Outline buttons above outline group
851
- # 1 = Outline buttons below outline group
852
- 1, # 7 0x0080 0 = Outline buttons left of outline group
853
- # 1 = Outline buttons right of outline group
854
- 0, # 8 0x0100 0 = Scale printout in percent (➜ 6.89)
855
- # 1 = Fit printout to number of pages (➜ 6.89)
856
- 0, # 9 0x0200 0 = Save external linked values
857
- # (BIFF3-BIFF4 only, ➜ 5.10)
858
- # 1 = Do not save external linked values
859
- # (BIFF3-BIFF4 only, 5.10)
860
- 1, # 10 0x0400 0 = Do not show row outline symbols
861
- # 1 = Show row outline symbols
862
- 0, # 11 0x0800 0 = Do not show column outline symbols
863
- # 1 = Show column outline symbols
864
- 0, # 13-12 0x3000 These flags specify the arrangement of windows.
865
- # They are stored in BIFF4 only.
866
- # 00 = Arrange windows tiled
867
- # 01 = Arrange windows horizontal
868
- 0, # 10 = Arrange windows vertical
869
- # 11 = Arrange windows cascaded
870
- # The following flags are valid for BIFF4-BIFF8 only:
871
- 0, # 14 0x4000 0 = Standard expression evaluation
872
- # 1 = Alternative expression evaluation
873
- 0, # 15 0x8000 0 = Standard formula entries
874
- # 1 = Alternative formula entries
875
- ]
876
- weights = [4,5,6,7,8,9,10,11,12,13,14,15]
877
- value = bits.inject do |a, b| a | (b << weights.shift) end
878
- write_op 0x0081, [value].pack('v')
879
- end
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