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