keeguon-spreadsheet 0.9.3

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