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,650 +1,687 @@
1
- require 'spreadsheet/excel/internals'
2
- require 'spreadsheet/writer'
3
- require 'spreadsheet/excel/writer/biff8'
4
- require 'spreadsheet/excel/writer/format'
5
- require 'spreadsheet/excel/writer/worksheet'
6
- require 'ole/storage'
1
+ require "spreadsheet/excel/internals"
2
+ require "spreadsheet/writer"
3
+ require "spreadsheet/excel/writer/biff8"
4
+ require "spreadsheet/excel/writer/format"
5
+ require "spreadsheet/excel/writer/worksheet"
6
+ require "ole/storage"
7
7
 
8
8
  module Spreadsheet
9
9
  module Excel
10
10
  module Writer
11
- ##
12
- # Writer class for Excel Workbooks. Most write_* method correspond to an
13
- # Excel-Record/Opcode. Designed to be able to write several Workbooks in
14
- # parallel (just because I can't imagine why you would want to do that
15
- # doesn't mean it shouldn't be possible ;). You should not need to call any of
16
- # its methods directly. If you think you do, look at #write_workbook
17
- class Workbook < Spreadsheet::Writer
18
- include Spreadsheet::Excel::Writer::Biff8
19
- include Spreadsheet::Excel::Internals
20
- attr_reader :fonts, :date_base
21
- def initialize *args
22
- super
23
- @biff_version = 0x0600
24
- @bof = 0x0809
25
- @build_id = 3515
26
- @build_year = 1996
27
- @bof_types = {
28
- :globals => 0x0005,
29
- :visual_basic => 0x0006,
30
- :worksheet => 0x0010,
31
- :chart => 0x0020,
32
- :macro_sheet => 0x0040,
33
- :workspace => 0x0100,
34
- }
35
- @worksheets = {}
36
- @sst = {}
37
- @recordsize_limit = 8224
38
- @fonts = {}
39
- @formats = {}
40
- @number_formats = {}
41
- end
42
- def cleanup workbook
43
- worksheets(workbook).each do |worksheet|
44
- @sst.delete worksheet
45
- end
46
- @fonts.delete workbook
47
- @formats.delete workbook
48
- @number_formats.delete workbook
49
- @worksheets.delete workbook
50
- end
51
- def collect_formats workbook, opts={}
52
- # The default cell format is always present in an Excel file, described by
53
- # the XF record with the fixed index 15 (0-based). By default, it uses the
54
- # worksheet/workbook default cell style, described by the very first XF
55
- # record (index 0).
56
- formats = []
57
- unless opts[:existing_document]
58
- 15.times do
59
- formats.push Format.new(self, workbook, workbook.default_format,
60
- :type => :style)
61
- end
62
- formats.push Format.new(self, workbook)
63
- end
64
- workbook.formats.each do |fmt|
65
- formats.push Format.new(self, workbook, fmt)
66
- end
67
- @formats[workbook] = {
68
- :writers => [],
69
- :xf_indexes => {}
70
- }
71
- formats.each_with_index do |fmt, idx|
72
- @formats[workbook][:writers] << fmt
73
- @formats[workbook][:xf_indexes][fmt.format] ||= idx
74
- end
75
- end
76
- def complete_sst_update? workbook
77
- stored = workbook.sst.collect do |entry| entry.content end
78
- num_total = 0
79
- current = worksheets(workbook).inject(Hash.new(0)) do |memo, worksheet|
80
- worksheet.strings.each do |k,v|
81
- memo[k] += v
82
- num_total += v
83
- end
84
- memo
85
- end
86
- current.delete ''
87
- if !stored.empty? && stored.all?{|x| current.include?(x) }
88
- ## if all previously stored strings are still needed, we don't have to
89
- # rewrite all cells because the sst-index of such string does not change.
90
- additions = current.keys - stored
91
- [:partial_update, num_total, stored + additions]
92
- else
93
- [:complete_update, num_total, current.keys]
94
- end
95
- end
96
- def font_index workbook, font_key
97
- idx = @fonts[workbook][font_key] || 0
98
- ## this appears to be undocumented: the first 4 fonts seem to be accessed
99
- # with a 0-based index, but all subsequent font indices are 1-based.
100
- idx > 3 ? idx.next : idx
101
- end
102
- def number_format_index workbook, format
103
- @number_formats[workbook][format] || 0
104
- end
105
- def sanitize_worksheets sheets
106
- return sheets if sheets.empty?
107
- found_selected = false
108
- sheets.each do |sheet|
109
- found_selected ||= sheet.selected
110
- sheet.format_dates!
111
- end
112
- unless found_selected
113
- sheets.first.selected = true
114
- end
115
- sheets
116
- end
117
- def worksheets workbook
118
- @worksheets[workbook] ||= workbook.worksheets.collect do |worksheet|
119
- Excel::Writer::Worksheet.new self, worksheet
120
- end
121
- end
122
- def write_bof workbook, writer, type
123
- data = [
124
- @biff_version, # BIFF version (always 0x0600 for BIFF8)
125
- @bof_types[type], # Type of the following data:
126
- # 0x0005 = Workbook globals
127
- # 0x0006 = Visual Basic module
128
- # 0x0010 = Worksheet
129
- # 0x0020 = Chart
130
- # 0x0040 = Macro sheet
131
- # 0x0100 = Workspace file
132
- @build_id, # Build identifier
133
- @build_year, # Build year
134
- 0x000, # File history flags
135
- 0x006, # Lowest Excel version that can read
136
- # all records in this file
137
- ]
138
- write_op writer, @bof, data.pack("v4V2")
139
- end
140
- def write_bookbool workbook, writer
141
- write_placeholder writer, 0x00da
142
- end
143
- def write_boundsheets workbook, writer, offset
144
- worksheets = worksheets(workbook)
145
- worksheets.each do |worksheet|
146
- # account for boundsheet-entry
147
- offset += worksheet.boundsheet_size
148
- end
149
- worksheets.each do |worksheet|
150
- visibility = SEITILIBISIV_TEEHSKROW[worksheet.worksheet.visibility]
151
- data = [
152
- offset, # Absolute stream position of the BOF record of the sheet
153
- # represented by this record. This field is never encrypted
154
- # in protected files.
155
- visibility, # Visibility: 0x00 = Visible
156
- # 0x01 = Hidden
157
- # 0x02 = Strong hidden (see below)
158
- 0x00, # Sheet type: 0x00 = Worksheet
159
- # 0x02 = Chart
160
- # 0x06 = Visual Basic module
161
- ]
162
- write_op writer, 0x0085, data.pack("VC2"), worksheet.name
163
- offset += worksheet.size
164
- end
165
- end
166
- ##
167
- def write_datemode workbook, writer
168
- mode = @date_base.year == 1899 ? 0x00 : 0x01
169
- data = [
170
- mode, # 0 = Base date is 1899-Dec-31
11
+ ##
12
+ # Writer class for Excel Workbooks. Most write_* method correspond to an
13
+ # Excel-Record/Opcode. Designed to be able to write several Workbooks in
14
+ # parallel (just because I can't imagine why you would want to do that
15
+ # doesn't mean it shouldn't be possible ;). You should not need to call any of
16
+ # its methods directly. If you think you do, look at #write_workbook
17
+ class Workbook < Spreadsheet::Writer
18
+ include Spreadsheet::Excel::Writer::Biff8
19
+ include Spreadsheet::Excel::Internals
20
+
21
+ attr_reader :fonts, :date_base
22
+ def initialize *args
23
+ super
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
+ @worksheets = {}
37
+ @sst = {}
38
+ @recordsize_limit = 8224
39
+ @fonts = {}
40
+ @formats = {}
41
+ @number_formats = {}
42
+ end
43
+
44
+ def cleanup workbook
45
+ worksheets(workbook).each do |worksheet|
46
+ @sst.delete worksheet
47
+ end
48
+ @fonts.delete workbook
49
+ @formats.delete workbook
50
+ @number_formats.delete workbook
51
+ @worksheets.delete workbook
52
+ end
53
+
54
+ def collect_formats workbook, opts = {}
55
+ # The default cell format is always present in an Excel file, described by
56
+ # the XF record with the fixed index 15 (0-based). By default, it uses the
57
+ # worksheet/workbook default cell style, described by the very first XF
58
+ # record (index 0).
59
+ formats = []
60
+ unless opts[:existing_document]
61
+ 15.times do
62
+ formats.push Format.new(self, workbook, workbook.default_format,
63
+ type: :style)
64
+ end
65
+ formats.push Format.new(self, workbook)
66
+ end
67
+ workbook.formats.each do |fmt|
68
+ formats.push Format.new(self, workbook, fmt)
69
+ end
70
+ @formats[workbook] = {
71
+ writers: [],
72
+ xf_indexes: {}
73
+ }
74
+ formats.each_with_index do |fmt, idx|
75
+ @formats[workbook][:writers] << fmt
76
+ @formats[workbook][:xf_indexes][fmt.format] ||= idx
77
+ end
78
+ end
79
+
80
+ def complete_sst_update? workbook
81
+ stored = workbook.sst.collect { |entry| entry.content }
82
+ num_total = 0
83
+ current = worksheets(workbook).each_with_object(Hash.new(0)) do |worksheet, memo|
84
+ worksheet.strings.each do |k, v|
85
+ memo[k] += v
86
+ num_total += v
87
+ end
88
+ end
89
+ current.delete ""
90
+ if !stored.empty? && stored.all? { |x| current.include?(x) }
91
+ ## if all previously stored strings are still needed, we don't have to
92
+ # rewrite all cells because the sst-index of such string does not change.
93
+ additions = current.keys - stored
94
+ [:partial_update, num_total, stored + additions]
95
+ else
96
+ [:complete_update, num_total, current.keys]
97
+ end
98
+ end
99
+
100
+ def font_index workbook, font_key
101
+ idx = @fonts[workbook][font_key] || 0
102
+ ## this appears to be undocumented: the first 4 fonts seem to be accessed
103
+ # with a 0-based index, but all subsequent font indices are 1-based.
104
+ (idx > 3) ? idx.next : idx
105
+ end
106
+
107
+ def number_format_index workbook, format
108
+ @number_formats[workbook][format] || 0
109
+ end
110
+
111
+ def sanitize_worksheets sheets
112
+ return sheets if sheets.empty?
113
+ found_selected = false
114
+ sheets.each do |sheet|
115
+ found_selected ||= sheet.selected
116
+ sheet.format_dates!
117
+ end
118
+ unless found_selected
119
+ sheets.first.selected = true
120
+ end
121
+ sheets
122
+ end
123
+
124
+ def worksheets workbook
125
+ @worksheets[workbook] ||= workbook.worksheets.collect do |worksheet|
126
+ Excel::Writer::Worksheet.new self, worksheet
127
+ end
128
+ end
129
+
130
+ def write_bof workbook, writer, type
131
+ data = [
132
+ @biff_version, # BIFF version (always 0x0600 for BIFF8)
133
+ @bof_types[type], # Type of the following data:
134
+ # 0x0005 = Workbook globals
135
+ # 0x0006 = Visual Basic module
136
+ # 0x0010 = Worksheet
137
+ # 0x0020 = Chart
138
+ # 0x0040 = Macro sheet
139
+ # 0x0100 = Workspace file
140
+ @build_id, # Build identifier
141
+ @build_year, # Build year
142
+ 0x000, # File history flags
143
+ 0x006 # Lowest Excel version that can read
144
+ # all records in this file
145
+ ]
146
+ write_op writer, @bof, data.pack("v4V2")
147
+ end
148
+
149
+ def write_bookbool workbook, writer
150
+ write_placeholder writer, 0x00da
151
+ end
152
+
153
+ def write_boundsheets workbook, writer, offset
154
+ worksheets = worksheets(workbook)
155
+ worksheets.each do |worksheet|
156
+ # account for boundsheet-entry
157
+ offset += worksheet.boundsheet_size
158
+ end
159
+ worksheets.each do |worksheet|
160
+ visibility = SEITILIBISIV_TEEHSKROW[worksheet.worksheet.visibility]
161
+ data = [
162
+ offset, # Absolute stream position of the BOF record of the sheet
163
+ # represented by this record. This field is never encrypted
164
+ # in protected files.
165
+ visibility, # Visibility: 0x00 = Visible
166
+ # 0x01 = Hidden
167
+ # 0x02 = Strong hidden (see below)
168
+ 0x00 # Sheet type: 0x00 = Worksheet
169
+ # 0x02 = Chart
170
+ # 0x06 = Visual Basic module
171
+ ]
172
+ write_op writer, 0x0085, data.pack("VC2"), worksheet.name
173
+ offset += worksheet.size
174
+ end
175
+ end
176
+
177
+ ##
178
+ def write_datemode workbook, writer
179
+ mode = (@date_base.year == 1899) ? 0x00 : 0x01
180
+ data = [
181
+ mode # 0 = Base date is 1899-Dec-31
171
182
  # (the cell value 1 represents 1900-Jan-01)
172
183
  # 1 = Base date is 1904-Jan-01
173
184
  # (the cell value 1 represents 1904-Jan-02)
174
- ]
175
- write_op writer, 0x0022, data.pack('v')
176
- end
177
- def write_dsf workbook, writer
178
- data = [
179
- 0x00, # 0 = Only the BIFF8 “Workbook” stream is present
185
+ ]
186
+ write_op writer, 0x0022, data.pack("v")
187
+ end
188
+
189
+ def write_dsf workbook, writer
190
+ data = [
191
+ 0x00 # 0 = Only the BIFF8 “Workbook” stream is present
180
192
  # 1 = Additional BIFF5/BIFF7 “Book” stream is in the file
181
- ]
182
- write_op writer, 0x0161, data.pack('v')
183
- end
184
- def write_encoding workbook, writer
185
- enc = workbook.encoding || 'UTF-16LE'
186
- if RUBY_VERSION >= '1.9' && enc.is_a?(Encoding)
187
- enc = enc.name.upcase
188
- end
189
- cp = SEGAPEDOC.fetch(enc) do
190
- raise Spreadsheet::Errors::UnknownCodepage, "Invalid or Unknown Codepage '#{enc}'"
191
- end
192
- write_op writer, 0x0042, [cp].pack('v')
193
- end
194
- def write_eof workbook, writer
195
- write_op writer, 0x000a
196
- end
197
- def write_extsst workbook, offsets, writer
198
- header = [SST_CHUNKSIZE].pack('v')
199
- data = offsets.collect do |pair| pair.push(0).pack('Vv2') end
200
- write_op writer, 0x00ff, header, data
201
- end
202
- def write_font workbook, writer, font
203
- # TODO: Colors/Palette index
204
- size = font.size * TWIPS
205
- color = SEDOC_ROLOC[font.color] || SEDOC_ROLOC[:text]
206
- weight = FONT_WEIGHTS.fetch(font.weight, font.weight)
207
- weight = [[weight, 1000].min, 100].max
208
- esc = SEPYT_TNEMEPACSE.fetch(font.escapement, 0)
209
- underline = SEPYT_ENILREDNU.fetch(font.underline, 0)
210
- family = SEILIMAF_TNOF.fetch(font.family, 0)
211
- encoding = SGNIDOCNE_TNOF.fetch(font.encoding, 0)
212
- options = 0
213
- options |= 0x0001 if weight > 600
214
- options |= 0x0002 if font.italic?
215
- options |= 0x0004 if underline > 0
216
- options |= 0x0008 if font.strikeout?
217
- options |= 0x0010 if font.outline?
218
- options |= 0x0020 if font.shadow?
219
- data = [
220
- size, # Height of the font (in twips = 1/20 of a point)
221
- options, # Option flags:
222
- # Bit Mask Contents
223
- # 0 0x0001 1 = Characters are bold (redundant, see below)
224
- # 1 0x0002 1 = Characters are italic
225
- # 2 0x0004 1 = Characters are underlined (redundant)
226
- # 3 0x0008 1 = Characters are struck out
227
- # 4 0x0010 1 = Characters are outlined (djberger)
228
- # 5 0x0020 1 = Characters are shadowed (djberger)
229
- color, # Palette index (➜ 6.70)
230
- weight, # Font weight (100-1000). Standard values are
231
- # 0x0190 (400) for normal text and
232
- # 0x02bc (700) for bold text.
233
- esc, # Escapement type: 0x0000 = None
234
- # 0x0001 = Superscript
235
- # 0x0002 = Subscript
236
- underline,# Underline type: 0x00 = None
237
- # 0x01 = Single
238
- # 0x02 = Double
239
- # 0x21 = Single accounting
240
- # 0x22 = Double accounting
241
- family, # Font family: 0x00 = None (unknown or don't care)
242
- # 0x01 = Roman (variable width, serifed)
243
- # 0x02 = Swiss (variable width, sans-serifed)
244
- # 0x03 = Modern (fixed width,
245
- # serifed or sans-serifed)
246
- # 0x04 = Script (cursive)
247
- # 0x05 = Decorative (specialised,
248
- # e.g. Old English, Fraktur)
249
- encoding, # Character set: 0x00 = 0 = ANSI Latin
250
- # 0x01 = 1 = System default
251
- # 0x02 = 2 = Symbol
252
- # 0x4d = 77 = Apple Roman
253
- # 0x80 = 128 = ANSI Japanese Shift-JIS
254
- # 0x81 = 129 = ANSI Korean (Hangul)
255
- # 0x82 = 130 = ANSI Korean (Johab)
256
- # 0x86 = 134 = ANSI Chinese Simplified GBK
257
- # 0x88 = 136 = ANSI Chinese Traditional BIG5
258
- # 0xa1 = 161 = ANSI Greek
259
- # 0xa2 = 162 = ANSI Turkish
260
- # 0xa3 = 163 = ANSI Vietnamese
261
- # 0xb1 = 177 = ANSI Hebrew
262
- # 0xb2 = 178 = ANSI Arabic
263
- # 0xba = 186 = ANSI Baltic
264
- # 0xcc = 204 = ANSI Cyrillic
265
- # 0xde = 222 = ANSI Thai
266
- # 0xee = 238 = ANSI Latin II (Central European)
267
- # 0xff = 255 = OEM Latin I
268
- ]
269
- name = unicode_string font.name # Font name: Unicode string,
270
- # 8-bit string length (➜ 3.4)
271
- write_op writer, opcode(:font), data.pack(binfmt(:font)), name
272
- end
273
- def write_fonts workbook, writer
274
- fonts = @fonts[workbook] = {}
275
- @formats[workbook][:writers].map{|format| format.font }.compact.uniq.each do |font|
276
- unless fonts.include?(font.key)
277
- fonts.store font.key, fonts.size
278
- write_font workbook, writer, font
279
- end
280
- end
281
- end
282
- def write_formats workbook, writer
283
- # From BIFF5 on, the built-in number formats will be omitted. The built-in
284
- # formats are dependent on the current regional settings of the operating
285
- # system. BUILTIN_FORMATS shows which number formats are used by
286
- # default in a US-English environment. All indexes from 0 to 163 are
287
- # reserved for built-in formats.
288
- # The first user-defined format starts at 164 (0xa4).
289
- formats = @number_formats[workbook] = {}
290
- BUILTIN_FORMATS.each do |idx, str|
291
- formats.store client(str, 'UTF-8'), idx
292
- end
293
- ## Ensure at least a 'GENERAL' format is written
294
- formats.delete client('GENERAL', 'UTF-8')
295
- idx = 0xa4
296
- workbook.formats.each do |fmt|
297
- str = fmt.number_format
298
- unless formats[str]
299
- formats.store str, idx
300
- # Number format string (Unicode string, 16-bit string length, ➜ 3.4)
301
- write_op writer, opcode(:format), [idx].pack('v'), unicode_string(str, 2)
302
- idx += 1
303
- end
304
- end
305
- end
306
- ##
307
- # Write a new Excel file.
308
- def write_from_scratch workbook, io
309
- sanitize_worksheets workbook.worksheets
310
- collect_formats workbook
311
- sheets = worksheets workbook
312
- buffer1 = StringIO.new ''.dup
313
- # ● BOF Type = workbook globals (➜ 6.8)
314
- write_bof workbook, buffer1, :globals
315
- # ○ File Protection Block ➜ 4.19
316
- # ○ WRITEACCESS User name (BIFF3-BIFF8, ➜ 5.112)
317
- # ○ FILESHARING File sharing options (BIFF3-BIFF8, ➜ 5.44)
318
- # ○ CODEPAGE ➜ 6.17
319
- write_encoding workbook, buffer1
320
- # ○ DSF ➜ 6.32
321
- write_dsf workbook, buffer1
322
- # ○ TABID
323
- write_tabid workbook, buffer1
324
- # ○ FNGROUPCOUNT
325
- # ○ Workbook Protection Block ➜ 4.18
326
- # ○ WINDOWPROTECT Window settings: 1 = protected (➜ 5.111)
327
- # ○ PROTECT Cell contents: 1 = protected (➜ 5.82)
328
- write_protect workbook, buffer1
329
- # ○ OBJECTPROTECT Embedded objects: 1 = protected (➜ 5.72)
330
- # ○ PASSWORD Hash value of the password; 0 = No password (➜ 5.76)
331
- write_password workbook, buffer1
332
- # ○ BACKUP ➜ 5.5
333
- # ○ HIDEOBJ ➜ 5.56
334
- # ● WINDOW1 ➜ 5.109
335
- write_window1 workbook, buffer1
336
- # ○ DATEMODE ➜ 5.28
337
- write_datemode workbook, buffer1
338
- # ○ PRECISION ➜ 5.79
339
- write_precision workbook, buffer1
340
- # ○ REFRESHALL
341
- write_refreshall workbook, buffer1
342
- # ○ BOOKBOOL ➜ 5.9
343
- write_bookbool workbook, buffer1
344
- # ●● FONT ➜ 5.45
345
- write_fonts workbook, buffer1
346
- # ○○ FORMAT ➜ 5.49
347
- write_formats workbook, buffer1
348
- # ●● XF ➜ 5.115
349
- write_xfs workbook, buffer1
350
- # ●● STYLE ➜ 5.103
351
- write_styles workbook, buffer1
352
- # ○ PALETTE ➜ 5.74
353
- write_palette workbook, buffer1
354
- # ○ USESELFS ➜ 5.106
355
- buffer1.rewind
356
- # ●● BOUNDSHEET ➜ 5.95
357
- buffer2 = StringIO.new ''.dup
358
- # ○ COUNTRY ➜ 5.22
359
- # ○ Link Table ➜ 4.10.3
360
- # ○○ NAME ➜ 6.66
361
- # ○ Shared String Table ➜ 4.11
362
- # ● SST ➜ 5.100
363
- # ● EXTSST ➜ 5.42
364
- write_sst workbook, buffer2, buffer1.size
365
- # ● EOF ➜ 5.37
366
- write_eof workbook, buffer2
367
- buffer2.rewind
368
- # worksheet data can only be assembled after write_sst
369
- sheets.each do |worksheet| worksheet.write_from_scratch end
370
- Ole::Storage.open io do |ole|
371
- ole.file.open 'Workbook', 'w' do |writer|
372
- writer.write buffer1.read
373
- write_boundsheets workbook, writer, buffer1.size + buffer2.size
374
- writer.write buffer2.read
375
- sheets.each do |worksheet|
376
- writer.write worksheet.data
193
+ ]
194
+ write_op writer, 0x0161, data.pack("v")
377
195
  end
378
- end
379
- end
380
- end
381
- def write_op writer, op, *args
382
- data = args.join
383
- limited = data.slice!(0...@recordsize_limit)
384
- writer.write [op,limited.size].pack("v2")
385
- writer.write limited
386
- data
387
- end
388
- def write_password workbook, writer
389
- write_placeholder writer, 0x0013
390
- end
391
- def write_placeholder writer, op, value=0x0000, fmt='v'
392
- write_op writer, op, [value].pack(fmt)
393
- end
394
- def write_precision workbook, writer
395
- # 0 = Use displayed values; 1 = Use real cell values
396
- write_placeholder writer, 0x000e, 0x0001
397
- end
398
- def write_protect workbook, writer
399
- write_placeholder writer, 0x0012
400
- end
401
- def write_refreshall workbook, writer
402
- write_placeholder writer, 0x01b7
403
- end
404
- def write_sst workbook, writer, offset
405
- # Offset Size Contents
406
- # 0 4 Total number of strings in the workbook (see below)
407
- # 4 4 Number of following strings (nm)
408
- # 8 var. List of nm Unicode strings, 16-bit string length (➜ 3.4)
409
- num_total = 0
410
- strings = worksheets(workbook).inject(Hash.new(0)) do |memo, worksheet|
411
- worksheet.strings.each do |k,v|
412
- memo[k] += v
413
- num_total += v
414
- end
415
- memo
416
- end
417
- _write_sst workbook, writer, offset, num_total, strings.keys
418
- end
419
- def _write_sst workbook, writer, offset, total, strings
420
- sst = {}
421
- worksheets(workbook).each do |worksheet|
422
- offset += worksheet.boundsheet_size
423
- @sst[worksheet] = sst
424
- end
425
- sst_size = strings.size
426
- data = [total, sst_size].pack 'V2'
427
- op = 0x00fc
428
- wide = 0
429
- offsets = []
430
- strings.each_with_index do |string, idx|
431
- sst.store string, idx
432
- op_offset = data.size + 4
433
- if idx % SST_CHUNKSIZE == 0
434
- offsets.push [offset + writer.pos + op_offset, op_offset]
435
- end
436
- header, packed, next_wide = _unicode_string string, 2
437
- # the first few bytes (header + first character) must not be split
438
- must_fit = header.size + wide + 1
439
- while data.size + must_fit > @recordsize_limit
440
- op, data, wide = write_string_part writer, op, data, wide
441
- end
442
- wide = next_wide
443
- data << header << packed
444
- end
445
- until data.empty?
446
- op, data, wide = write_string_part writer, op, data, wide
447
- end
448
- write_extsst workbook, offsets, writer
449
- end
450
- def write_sst_changes workbook, writer, offset, total, strings
451
- _write_sst workbook, writer, offset, total, strings
452
- end
453
- def write_string_part writer, op, data, wide
454
- bef = data.size
455
- ## if we're writing wide characters, we need to make sure we don't cut
456
- # characters in half
457
- if wide > 0 && data.size > @recordsize_limit
458
- remove = @recordsize_limit - bef
459
- remove -= remove % 2
460
- rest = data.slice!(remove..-1)
461
- write_op writer, op, data
462
- data = rest
463
- else
464
- data = write_op writer, op, data
465
- end
466
- op = 0x003c
467
- # Unicode strings are split in a special way. At the beginning of each
468
- # CONTINUE record the option flags byte is repeated. Only the
469
- # character size flag will be set in this flags byte, the Rich-Text
470
- # flag and the Far-East flag are set to zero.
471
- unless data.empty?
472
- if wide == 1
473
- # check if we can compress the rest of the string
474
- data, wide = compress_unicode_string data
475
- end
476
- data = [wide].pack('C') << data
477
- end
478
- [op, data, wide]
479
- end
480
- def write_styles workbook, writer
481
- # TODO: Style implementation. The following is simply a standard builtin
482
- # style.
483
- # TODO: User defined styles
484
- data = [
485
- 0x8000, # Bit Mask Contents
486
- # 11- 0 0x0fff Index to style XF record (➜ 6.115)
487
- # 15 0x8000 Always 1 for built-in styles
488
- 0x00, # Identifier of the built-in cell style:
489
- # 0x00 = Normal
490
- # 0x01 = RowLevel_lv (see next field)
491
- # 0x02 = ColLevel_lv (see next field)
492
- # 0x03 = Comma
493
- # 0x04 = Currency
494
- # 0x05 = Percent
495
- # 0x06 = Comma [0] (BIFF4-BIFF8)
496
- # 0x07 = Currency [0] (BIFF4-BIFF8)
497
- # 0x08 = Hyperlink (BIFF8)
498
- # 0x09 = Followed Hyperlink (BIFF8)
499
- 0xff, # Level for RowLevel or ColLevel style (zero-based, lv),
500
- # 0xff otherwise
501
- # The RowLevel and ColLevel styles specify the formatting of
502
- # subtotal cells in a specific outline level. The level is
503
- # specified by the last field in the STYLE record. Valid values
504
- # are 0…6 for the outline levels 1…7.
505
- ]
506
- write_op writer, 0x0293, data.pack('vC2')
507
- end
508
- def write_palette workbook, writer
509
- data = default_palette
510
196
 
511
- workbook.palette.each do |idx, color|
512
- idx = SEDOC_ROLOC[idx] - 8 if idx.kind_of? Symbol
513
- raise "Undefined color index: #{idx}" unless data[idx]
514
- data[idx] = color
515
- end
197
+ def write_encoding workbook, writer
198
+ enc = workbook.encoding || "UTF-16LE"
199
+ if RUBY_VERSION >= "1.9" && enc.is_a?(Encoding)
200
+ enc = enc.name.upcase
201
+ end
202
+ cp = SEGAPEDOC.fetch(enc) do
203
+ raise Spreadsheet::Errors::UnknownCodepage, "Invalid or Unknown Codepage '#{enc}'"
204
+ end
205
+ write_op writer, 0x0042, [cp].pack("v")
206
+ end
516
207
 
517
- writer.write [opcode(:palette), 2 + 4 * data.size, data.size].pack('v3')
518
- writer.write data.collect { |c| c.push(0).pack('C4') }.join
519
- end
520
- def write_tabid workbook, writer
521
- write_op writer, 0x013d, [1].pack('v')
522
- end
523
- def write_window1 workbook, writer
524
- selected = workbook.worksheets.find do |sheet| sheet.selected end
525
- actidx = workbook.worksheets.index selected
526
- data = [
527
- 0x0000, # Horizontal position of the document window
528
- # (in twips = 1/20 of a point)
529
- 0x0000, # Vertical position of the document window
530
- # (in twips = 1/20 of a point)
531
- 0x4000, # Width of the document window (in twips = 1/20 of a point)
532
- 0x2000, # Height of the document window (in twips = 1/20 of a point)
533
- 0x0038, # Option flags:
534
- # Bit Mask Contents
535
- # 0 0x0001 0 = Window is visible
536
- # 1 = Window is hidden
537
- # 1 0x0002 0 = Window is open
538
- # 1 = Window is minimised
539
- # 3 0x0008 0 = Horizontal scroll bar hidden
540
- # 1 = Horizontal scroll bar visible
541
- # 4 0x0010 0 = Vertical scroll bar hidden
542
- # 1 = Vertical scroll bar visible
543
- # 5 0x0020 0 = Worksheet tab bar hidden
544
- # 1 = Worksheet tab bar visible
545
- actidx, # Index to active (displayed) worksheet
546
- 0x0000, # Index of first visible tab in the worksheet tab bar
547
- 0x0001, # Number of selected worksheets
548
- # (highlighted in the worksheet tab bar)
549
- 0x00e5, # Width of worksheet tab bar (in 1/1000 of window width).
550
- # The remaining space is used by the horizontal scrollbar.
551
- ]
552
- write_op writer, 0x003d, data.pack('v*')
553
- end
554
- ##
555
- # The main writer method. Calls #write_from_scratch.
556
- def write_workbook workbook, io
557
- unless workbook.is_a?(Excel::Workbook) && workbook.io
558
- @date_base = Date.new 1899, 12, 31
559
- write_from_scratch workbook, io
560
- else
561
- @date_base = workbook.date_base
562
- if workbook.changes.empty?
563
- super
564
- else
565
- @date_base = Date.new 1899, 12, 31
566
- write_from_scratch workbook, io
208
+ def write_eof workbook, writer
209
+ write_op writer, 0x000a
210
+ end
211
+
212
+ def write_extsst workbook, offsets, writer
213
+ header = [SST_CHUNKSIZE].pack("v")
214
+ data = offsets.collect { |pair| pair.push(0).pack("Vv2") }
215
+ write_op writer, 0x00ff, header, data
216
+ end
217
+
218
+ def write_font workbook, writer, font
219
+ # TODO: Colors/Palette index
220
+ size = font.size * TWIPS
221
+ color = SEDOC_ROLOC[font.color] || SEDOC_ROLOC[:text]
222
+ weight = FONT_WEIGHTS.fetch(font.weight, font.weight)
223
+ weight = weight.clamp(100, 1000)
224
+ esc = SEPYT_TNEMEPACSE.fetch(font.escapement, 0)
225
+ underline = SEPYT_ENILREDNU.fetch(font.underline, 0)
226
+ family = SEILIMAF_TNOF.fetch(font.family, 0)
227
+ encoding = SGNIDOCNE_TNOF.fetch(font.encoding, 0)
228
+ options = 0
229
+ options |= 0x0001 if weight > 600
230
+ options |= 0x0002 if font.italic?
231
+ options |= 0x0004 if underline > 0
232
+ options |= 0x0008 if font.strikeout?
233
+ options |= 0x0010 if font.outline?
234
+ options |= 0x0020 if font.shadow?
235
+ data = [
236
+ size, # Height of the font (in twips = 1/20 of a point)
237
+ options, # Option flags:
238
+ # Bit Mask Contents
239
+ # 0 0x0001 1 = Characters are bold (redundant, see below)
240
+ # 1 0x0002 1 = Characters are italic
241
+ # 2 0x0004 1 = Characters are underlined (redundant)
242
+ # 3 0x0008 1 = Characters are struck out
243
+ # 4 0x0010 1 = Characters are outlined (djberger)
244
+ # 5 0x0020 1 = Characters are shadowed (djberger)
245
+ color, # Palette index (➜ 6.70)
246
+ weight, # Font weight (100-1000). Standard values are
247
+ # 0x0190 (400) for normal text and
248
+ # 0x02bc (700) for bold text.
249
+ esc, # Escapement type: 0x0000 = None
250
+ # 0x0001 = Superscript
251
+ # 0x0002 = Subscript
252
+ underline, # Underline type: 0x00 = None
253
+ # 0x01 = Single
254
+ # 0x02 = Double
255
+ # 0x21 = Single accounting
256
+ # 0x22 = Double accounting
257
+ family, # Font family: 0x00 = None (unknown or don't care)
258
+ # 0x01 = Roman (variable width, serifed)
259
+ # 0x02 = Swiss (variable width, sans-serifed)
260
+ # 0x03 = Modern (fixed width,
261
+ # serifed or sans-serifed)
262
+ # 0x04 = Script (cursive)
263
+ # 0x05 = Decorative (specialised,
264
+ # e.g. Old English, Fraktur)
265
+ encoding # Character set: 0x00 = 0 = ANSI Latin
266
+ # 0x01 = 1 = System default
267
+ # 0x02 = 2 = Symbol
268
+ # 0x4d = 77 = Apple Roman
269
+ # 0x80 = 128 = ANSI Japanese Shift-JIS
270
+ # 0x81 = 129 = ANSI Korean (Hangul)
271
+ # 0x82 = 130 = ANSI Korean (Johab)
272
+ # 0x86 = 134 = ANSI Chinese Simplified GBK
273
+ # 0x88 = 136 = ANSI Chinese Traditional BIG5
274
+ # 0xa1 = 161 = ANSI Greek
275
+ # 0xa2 = 162 = ANSI Turkish
276
+ # 0xa3 = 163 = ANSI Vietnamese
277
+ # 0xb1 = 177 = ANSI Hebrew
278
+ # 0xb2 = 178 = ANSI Arabic
279
+ # 0xba = 186 = ANSI Baltic
280
+ # 0xcc = 204 = ANSI Cyrillic
281
+ # 0xde = 222 = ANSI Thai
282
+ # 0xee = 238 = ANSI Latin II (Central European)
283
+ # 0xff = 255 = OEM Latin I
284
+ ]
285
+ name = unicode_string font.name # Font name: Unicode string,
286
+ # 8-bit string length (➜ 3.4)
287
+ write_op writer, opcode(:font), data.pack(binfmt(:font)), name
288
+ end
289
+
290
+ def write_fonts workbook, writer
291
+ fonts = @fonts[workbook] = {}
292
+ @formats[workbook][:writers].map { |format| format.font }.compact.uniq.each do |font|
293
+ unless fonts.include?(font.key)
294
+ fonts.store font.key, fonts.size
295
+ write_font workbook, writer, font
296
+ end
297
+ end
298
+ end
299
+
300
+ def write_formats workbook, writer
301
+ # From BIFF5 on, the built-in number formats will be omitted. The built-in
302
+ # formats are dependent on the current regional settings of the operating
303
+ # system. BUILTIN_FORMATS shows which number formats are used by
304
+ # default in a US-English environment. All indexes from 0 to 163 are
305
+ # reserved for built-in formats.
306
+ # The first user-defined format starts at 164 (0xa4).
307
+ formats = @number_formats[workbook] = {}
308
+ BUILTIN_FORMATS.each do |idx, str|
309
+ formats.store client(str, "UTF-8"), idx
310
+ end
311
+ ## Ensure at least a 'GENERAL' format is written
312
+ formats.delete client("GENERAL", "UTF-8")
313
+ idx = 0xa4
314
+ workbook.formats.each do |fmt|
315
+ str = fmt.number_format
316
+ unless formats[str]
317
+ formats.store str, idx
318
+ # Number format string (Unicode string, 16-bit string length, ➜ 3.4)
319
+ write_op writer, opcode(:format), [idx].pack("v"), unicode_string(str, 2)
320
+ idx += 1
321
+ end
322
+ end
323
+ end
324
+
325
+ ##
326
+ # Write a new Excel file.
327
+ def write_from_scratch workbook, io
328
+ sanitize_worksheets workbook.worksheets
329
+ collect_formats workbook
330
+ sheets = worksheets workbook
331
+ buffer1 = StringIO.new "".dup
332
+ # ● BOF Type = workbook globals (➜ 6.8)
333
+ write_bof workbook, buffer1, :globals
334
+ # ○ File Protection Block ➜ 4.19
335
+ # ○ WRITEACCESS User name (BIFF3-BIFF8, ➜ 5.112)
336
+ # ○ FILESHARING File sharing options (BIFF3-BIFF8, ➜ 5.44)
337
+ # ○ CODEPAGE ➜ 6.17
338
+ write_encoding workbook, buffer1
339
+ # ○ DSF ➜ 6.32
340
+ write_dsf workbook, buffer1
341
+ # ○ TABID
342
+ write_tabid workbook, buffer1
343
+ # ○ FNGROUPCOUNT
344
+ # ○ Workbook Protection Block ➜ 4.18
345
+ # ○ WINDOWPROTECT Window settings: 1 = protected (➜ 5.111)
346
+ # ○ PROTECT Cell contents: 1 = protected (➜ 5.82)
347
+ write_protect workbook, buffer1
348
+ # ○ OBJECTPROTECT Embedded objects: 1 = protected (➜ 5.72)
349
+ # ○ PASSWORD Hash value of the password; 0 = No password (➜ 5.76)
350
+ write_password workbook, buffer1
351
+ # ○ BACKUP ➜ 5.5
352
+ # ○ HIDEOBJ ➜ 5.56
353
+ # ● WINDOW1 ➜ 5.109
354
+ write_window1 workbook, buffer1
355
+ # ○ DATEMODE ➜ 5.28
356
+ write_datemode workbook, buffer1
357
+ # ○ PRECISION ➜ 5.79
358
+ write_precision workbook, buffer1
359
+ # ○ REFRESHALL
360
+ write_refreshall workbook, buffer1
361
+ # ○ BOOKBOOL ➜ 5.9
362
+ write_bookbool workbook, buffer1
363
+ # ●● FONT ➜ 5.45
364
+ write_fonts workbook, buffer1
365
+ # ○○ FORMAT ➜ 5.49
366
+ write_formats workbook, buffer1
367
+ # ●● XF ➜ 5.115
368
+ write_xfs workbook, buffer1
369
+ # ●● STYLE ➜ 5.103
370
+ write_styles workbook, buffer1
371
+ # ○ PALETTE ➜ 5.74
372
+ write_palette workbook, buffer1
373
+ # ○ USESELFS ➜ 5.106
374
+ buffer1.rewind
375
+ # ●● BOUNDSHEET ➜ 5.95
376
+ buffer2 = StringIO.new "".dup
377
+ # ○ COUNTRY ➜ 5.22
378
+ # ○ Link Table ➜ 4.10.3
379
+ # ○○ NAME ➜ 6.66
380
+ # ○ Shared String Table ➜ 4.11
381
+ # ● SST ➜ 5.100
382
+ # ● EXTSST ➜ 5.42
383
+ write_sst workbook, buffer2, buffer1.size
384
+ # ● EOF ➜ 5.37
385
+ write_eof workbook, buffer2
386
+ buffer2.rewind
387
+ # worksheet data can only be assembled after write_sst
388
+ sheets.each { |worksheet| worksheet.write_from_scratch }
389
+ Ole::Storage.open io do |ole|
390
+ ole.file.open "Workbook", "w" do |writer|
391
+ writer.write buffer1.read
392
+ write_boundsheets workbook, writer, buffer1.size + buffer2.size
393
+ writer.write buffer2.read
394
+ sheets.each do |worksheet|
395
+ writer.write worksheet.data
396
+ end
397
+ end
398
+ end
399
+ end
400
+
401
+ def write_op writer, op, *args
402
+ data = args.join
403
+ limited = data.slice!(0...@recordsize_limit)
404
+ writer.write [op, limited.size].pack("v2")
405
+ writer.write limited
406
+ data
407
+ end
408
+
409
+ def write_password workbook, writer
410
+ write_placeholder writer, 0x0013
411
+ end
412
+
413
+ def write_placeholder writer, op, value = 0x0000, fmt = "v"
414
+ write_op writer, op, [value].pack(fmt)
415
+ end
416
+
417
+ def write_precision workbook, writer
418
+ # 0 = Use displayed values; 1 = Use real cell values
419
+ write_placeholder writer, 0x000e, 0x0001
420
+ end
421
+
422
+ def write_protect workbook, writer
423
+ write_placeholder writer, 0x0012
424
+ end
425
+
426
+ def write_refreshall workbook, writer
427
+ write_placeholder writer, 0x01b7
428
+ end
429
+
430
+ def write_sst workbook, writer, offset
431
+ # Offset Size Contents
432
+ # 0 4 Total number of strings in the workbook (see below)
433
+ # 4 4 Number of following strings (nm)
434
+ # 8 var. List of nm Unicode strings, 16-bit string length (➜ 3.4)
435
+ num_total = 0
436
+ strings = worksheets(workbook).each_with_object(Hash.new(0)) do |worksheet, memo|
437
+ worksheet.strings.each do |k, v|
438
+ memo[k] += v
439
+ num_total += v
440
+ end
441
+ end
442
+ _write_sst workbook, writer, offset, num_total, strings.keys
443
+ end
444
+
445
+ def _write_sst workbook, writer, offset, total, strings
446
+ sst = {}
447
+ worksheets(workbook).each do |worksheet|
448
+ offset += worksheet.boundsheet_size
449
+ @sst[worksheet] = sst
450
+ end
451
+ sst_size = strings.size
452
+ data = [total, sst_size].pack "V2"
453
+ op = 0x00fc
454
+ wide = 0
455
+ offsets = []
456
+ strings.each_with_index do |string, idx|
457
+ sst.store string, idx
458
+ op_offset = data.size + 4
459
+ if idx % SST_CHUNKSIZE == 0
460
+ offsets.push [offset + writer.pos + op_offset, op_offset]
461
+ end
462
+ header, packed, next_wide = _unicode_string string, 2
463
+ # the first few bytes (header + first character) must not be split
464
+ must_fit = header.size + wide + 1
465
+ while data.size + must_fit > @recordsize_limit
466
+ op, data, wide = write_string_part writer, op, data, wide
467
+ end
468
+ wide = next_wide
469
+ data << header << packed
470
+ end
471
+ until data.empty?
472
+ op, data, wide = write_string_part writer, op, data, wide
473
+ end
474
+ write_extsst workbook, offsets, writer
475
+ end
476
+
477
+ def write_sst_changes workbook, writer, offset, total, strings
478
+ _write_sst workbook, writer, offset, total, strings
479
+ end
480
+
481
+ def write_string_part writer, op, data, wide
482
+ bef = data.size
483
+ ## if we're writing wide characters, we need to make sure we don't cut
484
+ # characters in half
485
+ if wide > 0 && data.size > @recordsize_limit
486
+ remove = @recordsize_limit - bef
487
+ remove -= remove % 2
488
+ rest = data.slice!(remove..-1)
489
+ write_op writer, op, data
490
+ data = rest
491
+ else
492
+ data = write_op writer, op, data
493
+ end
494
+ op = 0x003c
495
+ # Unicode strings are split in a special way. At the beginning of each
496
+ # CONTINUE record the option flags byte is repeated. Only the
497
+ # character size flag will be set in this flags byte, the Rich-Text
498
+ # flag and the Far-East flag are set to zero.
499
+ unless data.empty?
500
+ if wide == 1
501
+ # check if we can compress the rest of the string
502
+ data, wide = compress_unicode_string data
503
+ end
504
+ data = [wide].pack("C") << data
505
+ end
506
+ [op, data, wide]
507
+ end
508
+
509
+ def write_styles workbook, writer
510
+ # TODO: Style implementation. The following is simply a standard builtin
511
+ # style.
512
+ # TODO: User defined styles
513
+ data = [
514
+ 0x8000, # Bit Mask Contents
515
+ # 11- 0 0x0fff Index to style XF record (➜ 6.115)
516
+ # 15 0x8000 Always 1 for built-in styles
517
+ 0x00, # Identifier of the built-in cell style:
518
+ # 0x00 = Normal
519
+ # 0x01 = RowLevel_lv (see next field)
520
+ # 0x02 = ColLevel_lv (see next field)
521
+ # 0x03 = Comma
522
+ # 0x04 = Currency
523
+ # 0x05 = Percent
524
+ # 0x06 = Comma [0] (BIFF4-BIFF8)
525
+ # 0x07 = Currency [0] (BIFF4-BIFF8)
526
+ # 0x08 = Hyperlink (BIFF8)
527
+ # 0x09 = Followed Hyperlink (BIFF8)
528
+ 0xff # Level for RowLevel or ColLevel style (zero-based, lv),
529
+ # 0xff otherwise
530
+ # The RowLevel and ColLevel styles specify the formatting of
531
+ # subtotal cells in a specific outline level. The level is
532
+ # specified by the last field in the STYLE record. Valid values
533
+ # are 0…6 for the outline levels 1…7.
534
+ ]
535
+ write_op writer, 0x0293, data.pack("vC2")
536
+ end
537
+
538
+ def write_palette workbook, writer
539
+ data = default_palette
540
+
541
+ workbook.palette.each do |idx, color|
542
+ idx = SEDOC_ROLOC[idx] - 8 if idx.is_a? Symbol
543
+ raise "Undefined color index: #{idx}" unless data[idx]
544
+ data[idx] = color
545
+ end
546
+
547
+ writer.write [opcode(:palette), 2 + 4 * data.size, data.size].pack("v3")
548
+ writer.write data.collect { |c| c.push(0).pack("C4") }.join
549
+ end
550
+
551
+ def write_tabid workbook, writer
552
+ write_op writer, 0x013d, [1].pack("v")
553
+ end
554
+
555
+ def write_window1 workbook, writer
556
+ selected = workbook.worksheets.find { |sheet| sheet.selected }
557
+ actidx = workbook.worksheets.index selected
558
+ data = [
559
+ 0x0000, # Horizontal position of the document window
560
+ # (in twips = 1/20 of a point)
561
+ 0x0000, # Vertical position of the document window
562
+ # (in twips = 1/20 of a point)
563
+ 0x4000, # Width of the document window (in twips = 1/20 of a point)
564
+ 0x2000, # Height of the document window (in twips = 1/20 of a point)
565
+ 0x0038, # Option flags:
566
+ # Bit Mask Contents
567
+ # 0 0x0001 0 = Window is visible
568
+ # 1 = Window is hidden
569
+ # 1 0x0002 0 = Window is open
570
+ # 1 = Window is minimised
571
+ # 3 0x0008 0 = Horizontal scroll bar hidden
572
+ # 1 = Horizontal scroll bar visible
573
+ # 4 0x0010 0 = Vertical scroll bar hidden
574
+ # 1 = Vertical scroll bar visible
575
+ # 5 0x0020 0 = Worksheet tab bar hidden
576
+ # 1 = Worksheet tab bar visible
577
+ actidx, # Index to active (displayed) worksheet
578
+ 0x0000, # Index of first visible tab in the worksheet tab bar
579
+ 0x0001, # Number of selected worksheets
580
+ # (highlighted in the worksheet tab bar)
581
+ 0x00e5 # Width of worksheet tab bar (in 1/1000 of window width).
582
+ # The remaining space is used by the horizontal scrollbar.
583
+ ]
584
+ write_op writer, 0x003d, data.pack("v*")
585
+ end
586
+
587
+ ##
588
+ # The main writer method. Calls #write_from_scratch.
589
+ def write_workbook workbook, io
590
+ if workbook.is_a?(Excel::Workbook) && workbook.io
591
+ @date_base = workbook.date_base
592
+ if workbook.changes.empty?
593
+ super
594
+ else
595
+ @date_base = Date.new 1899, 12, 31
596
+ write_from_scratch workbook, io
597
+ end
598
+ else
599
+ @date_base = Date.new 1899, 12, 31
600
+ write_from_scratch workbook, io
601
+ end
602
+ ensure
603
+ cleanup workbook
604
+ end
605
+
606
+ def write_xfs workbook, writer
607
+ # The default cell format is always present in an Excel file, described by
608
+ # the XF record with the fixed index 15 (0-based). By default, it uses the
609
+ # worksheet/workbook default cell style, described by the very first XF
610
+ # record (index 0).
611
+ @formats[workbook][:writers].each { |fmt| fmt.write_xf writer }
612
+ end
613
+
614
+ def sst_index worksheet, str
615
+ @sst[worksheet][str]
616
+ end
617
+
618
+ def xf_index workbook, format
619
+ @formats[workbook][:xf_indexes][format] || 0
620
+ end
621
+
622
+ ##
623
+ # Returns Excel 97+ default colour palette.
624
+ def default_palette
625
+ [
626
+ [0x00, 0x00, 0x00],
627
+ [0xff, 0xff, 0xff],
628
+ [0xff, 0x00, 0x00],
629
+ [0x00, 0xff, 0x00],
630
+ [0x00, 0x00, 0xff],
631
+ [0xff, 0xff, 0x00],
632
+ [0xff, 0x00, 0xff],
633
+ [0x00, 0xff, 0xff],
634
+ [0x80, 0x00, 0x00],
635
+ [0x00, 0x80, 0x00],
636
+ [0x00, 0x00, 0x80],
637
+ [0x80, 0x80, 0x00],
638
+ [0x80, 0x00, 0x80],
639
+ [0x00, 0x80, 0x80],
640
+ [0xc0, 0xc0, 0xc0],
641
+ [0x80, 0x80, 0x80],
642
+ [0x99, 0x99, 0xff],
643
+ [0x99, 0x33, 0x66],
644
+ [0xff, 0xff, 0xcc],
645
+ [0xcc, 0xff, 0xff],
646
+ [0x66, 0x00, 0x66],
647
+ [0xff, 0x80, 0x80],
648
+ [0x00, 0x66, 0xcc],
649
+ [0xcc, 0xcc, 0xff],
650
+ [0x00, 0x00, 0x80],
651
+ [0xff, 0x00, 0xff],
652
+ [0xff, 0xff, 0x00],
653
+ [0x00, 0xff, 0xff],
654
+ [0x80, 0x00, 0x80],
655
+ [0x80, 0x00, 0x00],
656
+ [0x00, 0x80, 0x80],
657
+ [0x00, 0x00, 0xff],
658
+ [0x00, 0xcc, 0xff],
659
+ [0xcc, 0xff, 0xff],
660
+ [0xcc, 0xff, 0xcc],
661
+ [0xff, 0xff, 0x99],
662
+ [0x99, 0xcc, 0xff],
663
+ [0xff, 0x99, 0xcc],
664
+ [0xcc, 0x99, 0xff],
665
+ [0xff, 0xcc, 0x99],
666
+ [0x33, 0x66, 0xff],
667
+ [0x33, 0xcc, 0xcc],
668
+ [0x99, 0xcc, 0x00],
669
+ [0xff, 0xcc, 0x00],
670
+ [0xff, 0x99, 0x00],
671
+ [0xff, 0x66, 0x00],
672
+ [0x66, 0x66, 0x99],
673
+ [0x96, 0x96, 0x96],
674
+ [0x00, 0x33, 0x66],
675
+ [0x33, 0x99, 0x66],
676
+ [0x00, 0x33, 0x00],
677
+ [0x33, 0x33, 0x00],
678
+ [0x99, 0x33, 0x00],
679
+ [0x99, 0x33, 0x66],
680
+ [0x33, 0x33, 0x99],
681
+ [0x33, 0x33, 0x33]
682
+ ]
683
+ end
567
684
  end
568
685
  end
569
- ensure
570
- cleanup workbook
571
- end
572
- def write_xfs workbook, writer
573
- # The default cell format is always present in an Excel file, described by
574
- # the XF record with the fixed index 15 (0-based). By default, it uses the
575
- # worksheet/workbook default cell style, described by the very first XF
576
- # record (index 0).
577
- @formats[workbook][:writers].each do |fmt| fmt.write_xf writer end
578
- end
579
- def sst_index worksheet, str
580
- @sst[worksheet][str]
581
- end
582
- def xf_index workbook, format
583
- @formats[workbook][:xf_indexes][format] || 0
584
- end
585
- ##
586
- # Returns Excel 97+ default colour palette.
587
- def default_palette
588
- [
589
- [0x00, 0x00, 0x00],
590
- [0xff, 0xff, 0xff],
591
- [0xff, 0x00, 0x00],
592
- [0x00, 0xff, 0x00],
593
- [0x00, 0x00, 0xff],
594
- [0xff, 0xff, 0x00],
595
- [0xff, 0x00, 0xff],
596
- [0x00, 0xff, 0xff],
597
- [0x80, 0x00, 0x00],
598
- [0x00, 0x80, 0x00],
599
- [0x00, 0x00, 0x80],
600
- [0x80, 0x80, 0x00],
601
- [0x80, 0x00, 0x80],
602
- [0x00, 0x80, 0x80],
603
- [0xc0, 0xc0, 0xc0],
604
- [0x80, 0x80, 0x80],
605
- [0x99, 0x99, 0xff],
606
- [0x99, 0x33, 0x66],
607
- [0xff, 0xff, 0xcc],
608
- [0xcc, 0xff, 0xff],
609
- [0x66, 0x00, 0x66],
610
- [0xff, 0x80, 0x80],
611
- [0x00, 0x66, 0xcc],
612
- [0xcc, 0xcc, 0xff],
613
- [0x00, 0x00, 0x80],
614
- [0xff, 0x00, 0xff],
615
- [0xff, 0xff, 0x00],
616
- [0x00, 0xff, 0xff],
617
- [0x80, 0x00, 0x80],
618
- [0x80, 0x00, 0x00],
619
- [0x00, 0x80, 0x80],
620
- [0x00, 0x00, 0xff],
621
- [0x00, 0xcc, 0xff],
622
- [0xcc, 0xff, 0xff],
623
- [0xcc, 0xff, 0xcc],
624
- [0xff, 0xff, 0x99],
625
- [0x99, 0xcc, 0xff],
626
- [0xff, 0x99, 0xcc],
627
- [0xcc, 0x99, 0xff],
628
- [0xff, 0xcc, 0x99],
629
- [0x33, 0x66, 0xff],
630
- [0x33, 0xcc, 0xcc],
631
- [0x99, 0xcc, 0x00],
632
- [0xff, 0xcc, 0x00],
633
- [0xff, 0x99, 0x00],
634
- [0xff, 0x66, 0x00],
635
- [0x66, 0x66, 0x99],
636
- [0x96, 0x96, 0x96],
637
- [0x00, 0x33, 0x66],
638
- [0x33, 0x99, 0x66],
639
- [0x00, 0x33, 0x00],
640
- [0x33, 0x33, 0x00],
641
- [0x99, 0x33, 0x00],
642
- [0x99, 0x33, 0x66],
643
- [0x33, 0x33, 0x99],
644
- [0x33, 0x33, 0x33]
645
- ]
646
- end
647
- end
648
- end
649
686
  end
650
687
  end