spreadsheet 1.3.3 → 1.3.4

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