spreadsheet 1.3.3 → 1.3.5

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 (65) hide show
  1. checksums.yaml +4 -4
  2. data/bin/irb +29 -0
  3. data/bin/racc +29 -0
  4. data/bin/rdbg +29 -0
  5. data/bin/rdoc +29 -0
  6. data/bin/ri +29 -0
  7. data/bin/rubocop +29 -0
  8. data/bin/ruby-parse +29 -0
  9. data/bin/ruby-rewrite +29 -0
  10. data/bin/standardrb +29 -0
  11. data/bin/test-unit +29 -0
  12. data/lib/parseexcel/parseexcel.rb +66 -58
  13. data/lib/parseexcel/parser.rb +1 -1
  14. data/lib/parseexcel.rb +1 -1
  15. data/lib/spreadsheet/column.rb +13 -10
  16. data/lib/spreadsheet/compatibility.rb +3 -1
  17. data/lib/spreadsheet/datatypes.rb +150 -147
  18. data/lib/spreadsheet/encodings.rb +20 -16
  19. data/lib/spreadsheet/errors.rb +2 -2
  20. data/lib/spreadsheet/excel/error.rb +23 -22
  21. data/lib/spreadsheet/excel/internals/biff5.rb +11 -11
  22. data/lib/spreadsheet/excel/internals/biff8.rb +13 -13
  23. data/lib/spreadsheet/excel/internals.rb +451 -451
  24. data/lib/spreadsheet/excel/offset.rb +34 -31
  25. data/lib/spreadsheet/excel/password_hash.rb +18 -18
  26. data/lib/spreadsheet/excel/reader/biff5.rb +34 -35
  27. data/lib/spreadsheet/excel/reader/biff8.rb +235 -222
  28. data/lib/spreadsheet/excel/reader.rb +1331 -1274
  29. data/lib/spreadsheet/excel/rgb.rb +91 -91
  30. data/lib/spreadsheet/excel/row.rb +99 -91
  31. data/lib/spreadsheet/excel/sst_entry.rb +41 -38
  32. data/lib/spreadsheet/excel/workbook.rb +87 -76
  33. data/lib/spreadsheet/excel/worksheet.rb +126 -107
  34. data/lib/spreadsheet/excel/writer/biff8.rb +57 -55
  35. data/lib/spreadsheet/excel/writer/format.rb +274 -256
  36. data/lib/spreadsheet/excel/writer/n_worksheet.rb +838 -798
  37. data/lib/spreadsheet/excel/writer/workbook.rb +672 -635
  38. data/lib/spreadsheet/excel/writer/worksheet.rb +899 -861
  39. data/lib/spreadsheet/excel/writer.rb +1 -1
  40. data/lib/spreadsheet/excel.rb +19 -12
  41. data/lib/spreadsheet/font.rb +31 -26
  42. data/lib/spreadsheet/format.rb +75 -59
  43. data/lib/spreadsheet/link.rb +8 -5
  44. data/lib/spreadsheet/note.rb +7 -6
  45. data/lib/spreadsheet/noteObject.rb +6 -5
  46. data/lib/spreadsheet/row.rb +35 -24
  47. data/lib/spreadsheet/version.rb +1 -1
  48. data/lib/spreadsheet/workbook.rb +28 -13
  49. data/lib/spreadsheet/worksheet.rb +103 -68
  50. data/lib/spreadsheet/writer.rb +3 -0
  51. data/lib/spreadsheet.rb +12 -15
  52. data/test/data/test_row_record_empty_range.xls +0 -0
  53. data/test/excel/reader.rb +8 -8
  54. data/test/excel/row.rb +35 -31
  55. data/test/excel/writer/workbook.rb +18 -16
  56. data/test/excel/writer/worksheet.rb +10 -8
  57. data/test/font.rb +44 -32
  58. data/test/format.rb +38 -33
  59. data/test/integration.rb +641 -598
  60. data/test/row.rb +5 -3
  61. data/test/suite.rb +7 -7
  62. data/test/workbook.rb +15 -14
  63. data/test/workbook_protection.rb +5 -5
  64. data/test/worksheet.rb +36 -34
  65. metadata +59 -6
@@ -1,813 +1,853 @@
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
+
18
+ attr_reader :worksheet
19
+ def initialize workbook, worksheet
20
+ @workbook = workbook
21
+ @worksheet = worksheet
22
+ @io = StringIO.new ""
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
+ }
277
35
  end
278
- end
279
- end
280
- end
281
- def write_defaultrowheight
282
- data = [
283
- 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?(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?(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 or 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 or 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:
284
302
  # Bit Mask Contents
285
303
  # 0 0x01 1 = Row height and default font height do not match
286
304
  # 1 0x02 1 = Row is hidden
287
305
  # 2 0x04 1 = Additional space above the row
288
306
  # 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
307
+ 0xf2 # Default height for unused rows, in twips = 1/20 of a point
308
+ ]
309
+ write_op 0x0225, data.pack("v2")
310
+ end
311
+
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
326
+
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
336
+
337
+ def write_eof
338
+ write_op 0x000a
339
+ end
340
+
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 or 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
400
+
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
+ # ○ DEFAULTROWHEIGHT5.31
419
+ write_defaultrowheight
420
+ # ○ WSBOOL ➜ 5.113
421
+ write_wsbool
422
+ # ○ Page Settings Block ➜ 4.4
423
+ # ○ Worksheet Protection Block 4.18
424
+ # DEFCOLWIDTH ➜ 5.32
425
+ write_defcolwidth
426
+ # ○○ COLINFO5.18
427
+ write_colinfos
428
+ # SORT5.99
429
+ # ● DIMENSIONS ➜ 5.35
430
+ write_dimensions
431
+ # ○○ Row Blocks 4.7
432
+ write_rows
433
+ # ● Worksheet View Settings Block 4.5
434
+ # WINDOW2 ➜ 5.110
435
+ write_window2
436
+ # ○ SCL ➜ 5.92 (BIFF4-BIFF8 only)
437
+ # ○ PANE ➜ 5.75
438
+ # ○○ SELECTION5.93
439
+ # ○ STANDARDWIDTH5.101
440
+ # ○○ MERGEDCELLS ➜ 5.67
441
+ # ○ LABELRANGES5.64
442
+ # ○ PHONETIC ➜ 5.77
443
+ # ○ Conditional Formatting Table4.12
444
+ # ○ Hyperlink Table4.13
445
+ write_hyperlink_table
446
+ # ○ Data Validity Table ➜ 4.14
447
+ # ○ SHEETLAYOUT ➜ 5.96 (BIFF8X only)
448
+ # ○ SHEETPROTECTION Additional protection, ➜ 5.98 (BIFF8X only)
449
+ # ○ RANGEPROTECTION Additional protection, 5.84 (BIFF8X only)
450
+ # ● EOF ➜ 5.36
451
+ write_eof
452
+ end
453
+
454
+ ##
455
+ # Write record that contains information about the layout of outline symbols.
456
+ def write_guts
457
+ # find the maximum outline_level in rows and columns
458
+ row_outline_level = 0
459
+ col_outline_level = 0
460
+ if (row = @worksheet.rows.select { |x| !x.nil? }.max_by(&:outline_level))
461
+ row_outline_level = row.outline_level
462
+ end
463
+ if (col = @worksheet.columns.select { |x| !x.nil? }.max_by(&:outline_level))
464
+ col_outline_level = col.outline_level
465
+ end
466
+ # set data
467
+ data = [
468
+ 0, # Width of the area to display row outlines (left of the sheet), in pixel
469
+ 0, # Height of the area to display column outlines (above the sheet), in pixel
470
+ row_outline_level + 1, # Number of visible row outline levels (used row levels+1; or 0,if not used)
471
+ col_outline_level + 1 # Number of visible column outline levels (used column levels+1; or 0,if not used)
472
+ ]
473
+ # write record
474
+ write_op opcode(:guts), data.pack("v4")
475
+ end
476
+
477
+ def write_hlink row, col, link
478
+ # FIXME: only Hyperlinks are supported at present.
479
+ cell_range = [
480
+ row, row, # Cell range address of all cells containing this hyperlink
481
+ col, col # (➜ 3.13.1)
482
+ ].pack "v4"
483
+ guid = [
484
+ # GUID of StdLink:
485
+ # D0 C9 EA 79 F9 BA CE 11 8C 82 00 AA 00 4B A9 0B
486
+ # (79EAC9D0-BAF9-11CE-8C82-00AA004BA90B)
487
+ "d0c9ea79f9bace118c8200aa004ba90b"
488
+ ].pack "H32"
489
+ opts = 0x01
490
+ opts |= 0x02
491
+ opts |= 0x14 unless link == link.url
492
+ opts |= 0x08 if link.fragment
493
+ opts |= 0x80 if link.target_frame
494
+ # TODO: UNC support
495
+ options = [
496
+ 2, # Unknown value: 0x00000002
497
+ opts # Option flags
498
+ # Bit Mask Contents
499
+ # 0 0x00000001 0 = No link extant
500
+ # 1 = File link or URL
501
+ # 1 0x00000002 0 = Relative file path
502
+ # 1 = Absolute path or URL
503
+ # 2 and 4 0x00000014 0 = No description
504
+ # 1 (both bits) = Description
505
+ # 3 0x00000008 0 = No text mark
506
+ # 1 = Text mark
507
+ # 7 0x00000080 0 = No target frame
508
+ # 1 = Target frame
509
+ # 8 0x00000100 0 = File link or URL
510
+ # 1 = UNC path (incl. server name)
511
+ ].pack("V2")
512
+ tail = []
513
+ ## call internal to get the correct internal encoding in Ruby 1.9
514
+ nullstr = internal "\000"
515
+ unless link == link.url
516
+ desc = internal(link).dup << nullstr
517
+ tail.push [desc.size / 2].pack("V"), desc
518
+ end
519
+ if link.target_frame
520
+ frme = internal(link.target_frame).dup << nullstr
521
+ tail.push [frme.size / 2].pack("V"), frme
522
+ end
523
+ url = internal(link.url).dup << nullstr
524
+ tail.push [
525
+ # 6.53.2 Hyperlink containing a URL (Uniform Resource Locator)
526
+ # These data fields occur for links which are not local files or files
527
+ # in the local network (for instance HTTP and FTP links and e-mail
528
+ # addresses). The lower 9 bits of the option flags field must be
529
+ # 0.x00x.xx112 (x means optional, depending on hyperlink content). The
530
+ # GUID could be used to distinguish a URL from a file link.
531
+ # GUID of URL Moniker:
532
+ # E0 C9 EA 79 F9 BA CE 11 8C 82 00 AA 00 4B A9 0B
533
+ # (79EAC9E0-BAF9-11CE-8C82-00AA004BA90B)
534
+ "e0c9ea79f9bace118c8200aa004ba90b",
535
+ url.size # Size of character array of the URL, including trailing zero
536
+ # word (us). There are us/2-1 characters in the following
537
+ # string.
538
+ ].pack("H32V"), url
539
+ if link.fragment
540
+ frag = internal(link.fragment).dup << nullstr
541
+ tail.push [frag.size / 2].pack("V"), frag
542
+ end
543
+ write_op opcode(:hlink), cell_range, guid, options, *tail
544
+ end
545
+
546
+ def write_hyperlink_table
547
+ # TODO: theoretically it's possible to write fewer records by combining
548
+ # identical neighboring links in cell-ranges
549
+ @worksheet.each do |row|
550
+ row.each_with_index do |cell, idx|
551
+ if cell.is_a? Link
552
+ write_hlink row.idx, idx, cell
553
+ end
554
+ end
555
+ end
556
+ end
557
+
558
+ def write_iteration
559
+ its = 0 # 0 = Iterations off; 1 = Iterations on
560
+ write_op 0x0011, [its].pack("v")
561
+ end
562
+
563
+ ##
564
+ # Write a cell with a String value. The String must have been stored in the
565
+ # Shared String Table.
566
+ def write_labelsst row, idx
567
+ write_cell :labelsst, row, idx, @workbook.sst_index(self, row[idx])
568
+ end
569
+
570
+ ##
571
+ # Write multiple consecutive blank cells.
572
+ def write_mulblank row, idx, multiples
573
+ data = [
574
+ row.idx, # Index to row
575
+ idx # Index to first column (fc)
576
+ ]
577
+ # List of nc=lc-fc+1 16-bit indexes to XF records (➜ 6.115)
578
+ multiples.each_with_index do |blank, cell_idx|
579
+ xf_idx = @workbook.xf_index @worksheet.workbook, row.format(idx + cell_idx)
580
+ data.push xf_idx
581
+ end
582
+ # Index to last column (lc)
583
+ data.push idx + multiples.size - 1
584
+ write_op opcode(:mulblank), data.pack("v*")
585
+ end
586
+
587
+ ##
588
+ # Write multiple consecutive cells with RK values (see #write_rk)
589
+ def write_mulrk row, idx, multiples
590
+ fmt = "v2"
591
+ data = [
592
+ row.idx, # Index to row
593
+ idx # Index to first column (fc)
594
+ ]
595
+ # List of nc=lc-fc+1 16-bit indexes to XF records (➜ 6.115)
596
+ multiples.each_with_index do |cell, cell_idx|
597
+ xf_idx = @workbook.xf_index @worksheet.workbook, row.format(idx + cell_idx)
598
+ data.push xf_idx, encode_rk(cell)
599
+ fmt << "vV"
600
+ end
601
+ # Index to last column (lc)
602
+ data.push idx + multiples.size - 1
603
+ write_op opcode(:mulrk), data.pack(fmt << "v")
604
+ end
605
+
606
+ def write_multiples row, idx, multiples
607
+ case multiples.last
608
+ when NilClass
609
+ write_mulblank row, idx, multiples
610
+ when Numeric
611
+ if multiples.size > 1
612
+ write_mulrk row, idx, multiples
613
+ else
614
+ write_rk row, idx
615
+ end
616
+ end
617
+ end
618
+
619
+ ##
620
+ # Write a cell with a 64-bit double precision Float value
621
+ def write_number row, idx
622
+ # Offset Size Contents
623
+ # 0 2 Index to row
624
+ # 2 2 Index to column
625
+ # 4 2 Index to XF record (➜ 6.115)
626
+ # 6 8 IEEE 754 floating-point value (64-bit double precision)
627
+ value = row[idx]
628
+ case value
629
+ when Date, Time
630
+ value = encode_date(value)
631
+ end
632
+ write_cell :number, row, idx, value
633
+ end
634
+
635
+ def write_op op, *args
636
+ data = args.join
637
+ @io.write [op, data.size].pack("v2")
638
+ @io.write data
639
+ end
640
+
641
+ def write_refmode
642
+ # • The “RC” mode uses numeric indexes for rows and columns, for example
643
+ # “R(1)C(-1)”, or “R1C1:R2C2”.
644
+ # • The “A1” mode uses characters for columns and numbers for rows, for
645
+ # example “B1”, or “$A$1:$B$2”.
646
+ mode = 1 # 0 = RC mode; 1 = A1 mode
647
+ write_op 0x000f, [mode].pack("v")
648
+ end
649
+
650
+ ##
651
+ # Write a cell with a Numeric or Date value.
652
+ def write_rk row, idx
653
+ write_cell :rk, row, idx, encode_rk(row[idx])
654
+ end
655
+
656
+ def write_row row
657
+ # Offset Size Contents
658
+ # 0 2 Index of this row
659
+ # 2 2 Index to column of the first cell which
660
+ # is described by a cell record
661
+ # 4 2 Index to column of the last cell which is
662
+ # described by a cell record, increased by 1
663
+ # 6 2 Bit Mask Contents
664
+ # 14-0 0x7fff Height of the row, in twips = 1/20 of a point
665
+ # 15 0x8000 0 = Row has custom height;
666
+ # 1 = Row has default height
667
+ # 8 2 Not used
668
+ # 10 2 In BIFF3-BIFF4 this field contains a relative offset to
669
+ # calculate stream position of the first cell record for this
670
+ # row (➜ 5.7.1). In BIFF5-BIFF8 this field is not used
671
+ # anymore, but the DBCELL record (➜ 6.26) instead.
672
+ # 12 4 Option flags and default row formatting:
673
+ # Bit Mask Contents
674
+ # 2-0 0x00000007 Outline level of the row
675
+ # 4 0x00000010 1 = Outline group starts or ends here
676
+ # (depending on where the outline
677
+ # buttons are located, see WSBOOL
678
+ # record, ➜ 6.113), and is collapsed
679
+ # 5 0x00000020 1 = Row is hidden (manually, or by a
680
+ # filter or outline group)
681
+ # 6 0x00000040 1 = Row height and default font height
682
+ # do not match
683
+ # 7 0x00000080 1 = Row has explicit default format (fl)
684
+ # 8 0x00000100 Always 1
685
+ # 27-16 0x0fff0000 If fl = 1: Index to default XF record
686
+ # (➜ 6.115)
687
+ # 28 0x10000000 1 = Additional space above the row.
688
+ # This flag is set, if the upper
689
+ # border of at least one cell in this
690
+ # row or if the lower border of at
691
+ # least one cell in the row above is
692
+ # formatted with a thick line style.
693
+ # Thin and medium line styles are not
694
+ # taken into account.
695
+ # 29 0x20000000 1 = Additional space below the row.
696
+ # This flag is set, if the lower
697
+ # border of at least one cell in this
698
+ # row or if the upper border of at
699
+ # least one cell in the row below is
700
+ # formatted with a medium or thick
701
+ # line style. Thin line styles are
702
+ # not taken into account.
703
+ height = row.height || ROW_HEIGHT
704
+ opts = row.outline_level & 0x00000007
705
+ opts |= 0x00000010 if row.collapsed?
706
+ opts |= 0x00000020 if row.hidden?
707
+ opts |= 0x00000040 if height != ROW_HEIGHT
708
+ if (fmt = row.default_format)
709
+ xf_idx = @workbook.xf_index @worksheet.workbook, fmt
710
+ opts |= 0x00000080
711
+ opts |= xf_idx << 16
712
+ end
713
+ opts |= 0x00000100
714
+ height = if height == ROW_HEIGHT
715
+ (height * TWIPS).to_i | 0x8000
716
+ else
717
+ height * TWIPS
718
+ end
719
+
720
+ attrs = [
721
+ row.idx,
722
+ row.first_used,
723
+ row.first_unused,
724
+ height,
725
+ opts
726
+ ]
727
+
728
+ return if attrs.any?(&:nil?)
729
+
730
+ # TODO: Row spacing
731
+ data = attrs.pack binfmt(:row)
732
+ write_op opcode(:row), data
733
+ end
734
+
735
+ def write_rowblock block
736
+ # ●● ROW Properties of the used rows
737
+ # ○○ Cell Block(s) Cell records for all used cells
738
+ # ○ DBCELL Stream offsets to the cell records of each row
739
+ block.each do |row|
740
+ write_row row
741
+ end
742
+ block.each do |row|
743
+ write_cellblocks row
744
+ end
745
+ end
746
+
747
+ def write_rows
748
+ row_blocks.each do |block|
749
+ write_rowblock block
750
+ end
751
+ end
752
+
753
+ def write_saverecalc
754
+ # 0 = Do not recalculate; 1 = Recalculate before saving the document
755
+ write_op 0x005f, [1].pack("v")
756
+ end
757
+
758
+ def write_window2
759
+ # This record contains additional settings for the document window
760
+ # (BIFF2-BIFF4) or for the window of a specific worksheet (BIFF5-BIFF8).
761
+ # It is part of the Sheet View Settings Block (➜ 4.5).
762
+ # Offset Size Contents
763
+ # 0 2 Option flags:
764
+ # Bits Mask Contents
765
+ # 0 0x0001 0 = Show formula results
766
+ # 1 = Show formulas
767
+ # 1 0x0002 0 = Do not show grid lines
768
+ # 1 = Show grid lines
769
+ # 2 0x0004 0 = Do not show sheet headers
770
+ # 1 = Show sheet headers
771
+ # 3 0x0008 0 = Panes are not frozen
772
+ # 1 = Panes are frozen (freeze)
773
+ # 4 0x0010 0 = Show zero values as empty cells
774
+ # 1 = Show zero values
775
+ # 5 0x0020 0 = Manual grid line colour
776
+ # 1 = Automatic grid line colour
777
+ # 6 0x0040 0 = Columns from left to right
778
+ # 1 = Columns from right to left
779
+ # 7 0x0080 0 = Do not show outline symbols
780
+ # 1 = Show outline symbols
781
+ # 8 0x0100 0 = Keep splits if pane freeze is removed
782
+ # 1 = Remove splits if pane freeze is removed
783
+ # 9 0x0200 0 = Sheet not selected
784
+ # 1 = Sheet selected (BIFF5-BIFF8)
785
+ # 10 0x0400 0 = Sheet not active
786
+ # 1 = Sheet active (BIFF5-BIFF8)
787
+ # 11 0x0800 0 = Show in normal view
788
+ # 1 = Show in page break preview (BIFF8)
789
+ # 2 2 Index to first visible row
790
+ # 4 2 Index to first visible column
791
+ # 6 2 Colour index of grid line colour (➜ 5.74).
792
+ # Note that in BIFF2-BIFF5 an RGB colour is written instead.
793
+ # 8 2 Not used
794
+ # 10 2 Cached magnification factor in page break preview (in percent)
795
+ # 0 = Default (60%)
796
+ # 12 2 Cached magnification factor in normal view (in percent)
797
+ # 0 = Default (100%)
798
+ # 14 4 Not used
799
+ flags = 0x0536 # Show grid lines, sheet headers, zero values. Automatic
800
+ # grid line colour, Remove slits if pane freeze is removed,
801
+ # Sheet is active.
802
+ if @worksheet.selected
803
+ flags |= 0x0200
804
+ end
805
+ flags |= 0x0080 # Show outline symbols,
806
+ # but if [Row|Column]#outline_level = 0 the symbols are not shown.
807
+ data = [flags, 0, 0, 0, 0, 0].pack binfmt(:window2)
808
+ write_op opcode(:window2), data
809
+ end
810
+
811
+ def write_wsbool
812
+ bits = [
813
+ # Bit Mask Contents
814
+ 1, # 0 0x0001 0 = Do not show automatic page breaks
815
+ # 1 = Show automatic page breaks
816
+ 0, # 4 0x0010 0 = Standard sheet
817
+ # 1 = Dialogue sheet (BIFF5-BIFF8)
818
+ 0, # 5 0x0020 0 = No automatic styles in outlines
819
+ # 1 = Apply automatic styles to outlines
820
+ 1, # 6 0x0040 0 = Outline buttons above outline group
821
+ # 1 = Outline buttons below outline group
822
+ 1, # 7 0x0080 0 = Outline buttons left of outline group
823
+ # 1 = Outline buttons right of outline group
824
+ 0, # 8 0x0100 0 = Scale printout in percent (➜ 6.89)
825
+ # 1 = Fit printout to number of pages (➜ 6.89)
826
+ 0, # 9 0x0200 0 = Save external linked values
827
+ # (BIFF3-BIFF4 only, ➜ 5.10)
828
+ # 1 = Do not save external linked values
829
+ # (BIFF3-BIFF4 only, ➜ 5.10)
830
+ 1, # 10 0x0400 0 = Do not show row outline symbols
831
+ # 1 = Show row outline symbols
832
+ 0, # 11 0x0800 0 = Do not show column outline symbols
833
+ # 1 = Show column outline symbols
834
+ 0, # 13-12 0x3000 These flags specify the arrangement of windows.
835
+ # They are stored in BIFF4 only.
836
+ # 00 = Arrange windows tiled
837
+ # 01 = Arrange windows horizontal
838
+ 0, # 10 = Arrange windows vertical
839
+ # 11 = Arrange windows cascaded
840
+ # The following flags are valid for BIFF4-BIFF8 only:
841
+ 0, # 14 0x4000 0 = Standard expression evaluation
842
+ # 1 = Alternative expression evaluation
843
+ 0 # 15 0x8000 0 = Standard formula entries
844
+ # 1 = Alternative formula entries
845
+ ]
846
+ weights = [4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
847
+ value = bits.inject { |a, b| a | (b << weights.shift) }
848
+ write_op 0x0081, [value].pack("v")
529
849
  end
530
850
  end
531
851
  end
532
852
  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
853
  end