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