unxls 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,1267 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Unxls::Biff8::Record
4
+ using Unxls::Helpers
5
+
6
+ HEADER_SIZE = 4
7
+
8
+ attr_reader :bytes
9
+ attr_reader :params, :stream # @todo remove
10
+
11
+ # @param params [Hash]
12
+ # @option params [Integer] :id
13
+ # @option params [Integer] :pos
14
+ # @option params [Integer] :size
15
+ # @option params [Array<String>] :data
16
+ # @option params [Array<Hash>] :continue
17
+ # @param stream [Unxls::Biff8::WorkbookStream]
18
+ def initialize(params, stream)
19
+ @params = params
20
+ @stream = stream
21
+ @serial = false # set to true for serial records like Font, Format etc.
22
+ open_next_record_block
23
+ end
24
+
25
+ # @return [StringIO]
26
+ def open_next_record_block
27
+ @bytes = StringIO.new(@params[:data].shift || '')
28
+ end
29
+
30
+ # @return [Symbol, nil]
31
+ def name
32
+ self.class.name_by_id(@params[:id])
33
+ end
34
+
35
+ def end_of_data?
36
+ @params[:data].size.zero? && @bytes.eof?
37
+ end
38
+
39
+ # @param id [Integer]
40
+ # @return [Symbol]
41
+ def self.name_by_id(id)
42
+ Unxls::Biff8::Constants::RECORD_IDS[id]
43
+ end
44
+
45
+ # @return [Hash, nil]
46
+ def process
47
+ unless name
48
+ warn("Unknown record with id #{Unxls::Log.h2b(@params[:id])}, ignoring")
49
+ return
50
+ end
51
+
52
+ method_name = "r_#{name.downcase}".to_sym
53
+ result = respond_to?(method_name) ? self.send(method_name) : nil
54
+ return unless result
55
+
56
+ result[:_record] = header_data
57
+ Unxls::Log.debug(result, 'Parsed result:', :green) # @debug
58
+
59
+ result
60
+ end
61
+
62
+ # @return [Hash]
63
+ def header_data
64
+ result = {
65
+ id: @params[:id],
66
+ name: name,
67
+ pos: @params[:pos],
68
+ size: @params[:size],
69
+ }
70
+
71
+ result[:index] = collection_index if serial?
72
+
73
+ result
74
+ end
75
+
76
+ # See 2.1.4 Record, p. 57
77
+ # @param id [Integer]
78
+ def self.continue?(id)
79
+ Unxls::Biff8::Constants::CONTINUE_RECORDS.include?(name_by_id(id))
80
+ end
81
+
82
+ def serial?
83
+ @serial
84
+ end
85
+
86
+ # @return [Integer]
87
+ def collection_index
88
+ @stream.last_parsed[name] ? @stream.last_parsed[name].size : 0
89
+ end
90
+
91
+ # Record-processing methods
92
+ #
93
+ # +method naming+
94
+ # processing methods for records from the 2.4 list: r_<record name downcase>, i.e. 'r_bof'
95
+ #
96
+ # +property naming+
97
+ # orignal values named follow the specification, i.e. 'verXLHigh'
98
+ # decoded (when meaning is unclear from the value itself) values named as <original value name>_d, i.e. 'verXLHigh_d'
99
+
100
+ #
101
+ # Globals substream records
102
+ #
103
+
104
+ # 2.4.21 BOF, page 212
105
+ # The BOF record specifies the beginning of the individual substreams as specified by the workbook section. It also specifies history information for the substreams.
106
+ # @return [Hash]
107
+ def r_bof
108
+ vers, dt, rup_build, rup_year = @bytes.read(8).unpack('v4')
109
+ result = {
110
+ vers: vers, # vers (2 bytes): An unsigned integer that specifies the BIFF version of the file. The value MUST be 0x0600.
111
+ dt: dt, # dt (2 bytes): An unsigned integer that specifies the document type of the substream of records following this record.
112
+ rupBuild: rup_build, # rupBuild (2 bytes): An unsigned integer that specifies the build identifier.
113
+ rupYear: rup_year # rupYear (2 bytes): An unsigned integer that specifies the year when this BIFF version was first created. The value MUST be 0x07CC or 0x07CD.
114
+ }
115
+
116
+ result[:vers_d] = {
117
+ 0x0000 => :BIFF5, # see 5.8.2 of OpenOffice's doc, p.136
118
+ 0x0200 => :BIFF2,
119
+ 0x0300 => :BIFF3,
120
+ 0x0400 => :BIFF4,
121
+ 0x0500 => :BIFF5,
122
+ 0x0600 => :BIFF8,
123
+ }[vers]
124
+
125
+ result[:dt_d] = {
126
+ 0x0005 => :globals, # Specifies the workbook substream
127
+ 0x0006 => :vb_module, # Visual Basic module substream
128
+ 0x0010 => :dialog_or_work_sheet, # Specifies the dialog sheet substream or the worksheet substream. If fDialog flag in the WsBool record in the substream is 1, it's a dialog sheet substream
129
+ 0x0020 => :chart, # Cart sheet substream
130
+ 0x0040 => :macro, # Macro sheet substream
131
+ 0x0100 => :workspace # Workspace substream
132
+ }[dt]
133
+
134
+ attrs = Unxls::BitOps.new(@bytes.read(4).unpack('V').first)
135
+ result[:fWin] = attrs.set_at?(0) # A - fWin (1 bit): A bit that specifies whether this file was last edited on a Windows platform. The value MUST be 1.
136
+ result[:fRisc] = attrs.set_at?(1) # B - fRisc (1 bit): A bit that specifies whether the file was last edited on a RISC platform. The value MUST be 0.
137
+ result[:fBeta] = attrs.set_at?(2) # C - fBeta (1 bit): A bit that specifies whether this file was last edited by a beta version of the application. The value MUST be 0.
138
+ result[:fWinAny] = attrs.set_at?(3) # D - fWinAny (1 bit): A bit that specifies whether this file has ever been edited on a Windows platform. The value SHOULD<28> be 1.
139
+ result[:fMacAny] = attrs.set_at?(4) # E - fMacAny (1 bit): A bit that specifies whether this file has ever been edited on a Macintosh platform. The value MUST be 0.
140
+ result[:fBetaAny] = attrs.set_at?(5) # F - fBetaAny (1 bit): A bit that specifies whether this file has ever been edited by a beta version of the application. The value MUST be 0.
141
+ # 6…7 G - unused1 (2 bits): Undefined and MUST be ignored.
142
+ result[:fRiscAny] = attrs.set_at?(8) # H - fRiscAny (1 bit): A bit that specifies whether this file has ever been edited on a RISC platform. The value MUST be 0.
143
+ result[:fOOM] = attrs.set_at?(9) # I - fOOM (1 bit): A bit that specifies whether this file had an out-of-memory failure.
144
+ result[:fGlJmp] = attrs.set_at?(10) # J - fGlJmp (1 bit): A bit that specifies whether this file had an out-of-memory failure during rendering.
145
+ # 11…12 K - unused2 (2 bits): Undefined, and MUST be ignored.
146
+ result[:fFontLimit] = attrs.set_at?(13) # L - fFontLimit (1 bit): A bit that specified that whether this file hit the 255 font limit (this happens only for Excel 97)
147
+ result[:verXLHigh] = attrs.value_at(14..17) # M - verXLHigh (4 bits): An unsigned integer that specifies the highest version of the application that once saved this file.
148
+ result[:verXLHigh_d] = Unxls::Biff8::Structure._verxlhigh(result[:verXLHigh])
149
+ # 18 N - unused3 (1 bit): Undefined, and MUST be ignored.
150
+ # 19…31 reserved1 (13 bits): MUST be zero, and MUST be ignored.
151
+
152
+ attrs_raw = @bytes.read(4).unpack('V').first
153
+ attrs = Unxls::BitOps.new(attrs_raw)
154
+ result[:verLowestBiff] = attrs.value_at(0..7) # verLowestBiff (8 bits): An unsigned integer that specifies the BIFF version saved. The value MUST be 6.
155
+ result[:verLastXLSaved] = attrs.value_at(8..11) # O - verLastXLSaved (4 bits): An unsigned integer that specifies the application that saved this file most recently. The value MUST be the value of field verXLHigh or less.
156
+ result[:verLastXLSaved_d] = Unxls::Biff8::Structure._verlastxlsaved(result[:verLastXLSaved])
157
+ # 12…31 reserved2 (20 bits): MUST be zero, and MUST be ignored.
158
+
159
+ result
160
+ end
161
+
162
+ # 2.4.28 BoundSheet8, page 220
163
+ # The BoundSheet8 record specifies basic information about a sheet, including the sheet name, hidden state, and type of sheet.
164
+ # @return [Hash]
165
+ def r_boundsheet8
166
+ @serial = true
167
+
168
+ lb_ply_pos, attrs, dt = @bytes.read(6).unpack('VCC')
169
+ hs_state = Unxls::BitOps.new(attrs).value_at(0..1)
170
+
171
+ visibility = {
172
+ 0 => :visible,
173
+ 1 => :hidden,
174
+ 2 => :very_hidden # The sheet is hidden and cannot be displayed using the user interface.
175
+ }[hs_state]
176
+
177
+ # This is very similar to BOF.dt values table but still different
178
+ type = {
179
+ 0 => :dialog_or_work_sheet, # The sheet substream that starts with the BOF record specified in lbPlyPos MUST contain one WsBool record. If the fDialog field in that WsBool is 1 then the sheet is dialog sheet. Otherwise, the sheet is a worksheet.
180
+ 1 => :macro,
181
+ 2 => :chart,
182
+ 6 => :vb_module
183
+ }[dt]
184
+
185
+ {
186
+ lbPlyPos: lb_ply_pos, # lbPlyPos (4 bytes): A FilePointer as specified in [MS-OSHARED] section 2.2.1.5 that specifies the stream position of the start of the BOF record for the sheet.
187
+ hsState: hs_state, # A - hsState (2 bits): An unsigned integer that specifies the hidden state of the sheet.
188
+ hsState_d: visibility,
189
+ # unused (6 bits): Undefined and MUST be ignored.
190
+ dt: dt, # dt (8 bits): An unsigned integer that specifies the sheet type.
191
+ dt_d: type,
192
+ stName: Unxls::Biff8::Structure.shortxlunicodestring(@bytes) # stName (variable): A ShortXLUnicodeString structure that specifies the unique case-insensitive name of the sheet.
193
+ }
194
+ end
195
+
196
+ # 2.4.35 CalcPrecision, page 224
197
+ # The CalcPrecision record specifies the calculation precision mode for the workbook.
198
+ # @return [Hash]
199
+ def r_calcprecision
200
+ {
201
+ # If the value is 0, the precision as displayed mode is selected.
202
+ # If the value is 1, the precision as displayed mode is not selected.
203
+ fFullPrec: @bytes.read.unpack('v').first == 0 # fFullPrec (2 bytes): A Boolean (section 2.5.14) that specifies whether the precision as displayed mode is selected.
204
+ }
205
+ end
206
+
207
+ # 2.4.52 CodePage, page 239
208
+ # The CodePage record specifies code page information for the workbook.
209
+ # @return [Hash]
210
+ def r_codepage
211
+ cv_data = @bytes.read.unpack('v').first
212
+
213
+ {
214
+ cv: cv_data, # cv (2 bytes): An unsigned integer that specifies the workbook’s code page. The value MUST be one of the code page values specified in [CODEPG] or the special value 1200, which means that the workbook is Unicode.
215
+ cv_d: Unxls::Biff8::Constants::CODEPAGES[cv_data]
216
+ }
217
+ end
218
+
219
+ # 2.4.63 Country, page 245
220
+ # The Country record specifies locale information for a workbook.
221
+ # @return [Hash]
222
+ def r_country
223
+ i_country_def, i_country_win_ini = @bytes.read(4).unpack('vv')
224
+
225
+ {
226
+ iCountryDef: i_country_def, # iCountryDef (2 bytes): An unsigned integer that specifies the country/region code determined by the locale in effect when the workbook was saved.
227
+ iCountryDef_d: Unxls::Biff8::Constants::COUNTRIES[i_country_def],
228
+ iCountryWinIni: i_country_win_ini, # iCountryWinIni (2 bytes): An unsigned integer that specifies the system regional settings country/region code in effect when the workbook was saved.
229
+ iCountryWinIni_d: Unxls::Biff8::Constants::COUNTRIES[i_country_win_ini]
230
+ }
231
+ end
232
+
233
+ # 2.4.77 Date1904, page 257
234
+ # The Date1904 record specifies the date system that the workbook uses.
235
+ # @return [Hash]
236
+ def r_date1904
237
+ {
238
+ # 0: The workbook uses the 1900 date system. The first date of the 1900 date system is 00:00:00 on January 1, 1900, specified by a serial value of 1.
239
+ # 1: The workbook uses the 1904 date system. The first date of the 1904 date system is 00:00:00 on January 1, 1904, specified by a serial value of 0.
240
+ f1904DateSystem: @bytes.read.unpack('v').first == 1 # f1904DateSystem (2 bytes): A Boolean (section 2.5.14) that specifies the date system used in this workbook.
241
+ }
242
+ end
243
+
244
+ # 2.4.97 DXF
245
+ # The DXF record specifies a differential format.
246
+ # @return [Hash]
247
+ def r_dxf
248
+ @serial = true
249
+
250
+ frth_data = @bytes.read(12)
251
+ attrs = @bytes.read(2).unpack('v').first
252
+ attrs = Unxls::BitOps.new(attrs)
253
+
254
+ result = {
255
+ frtHeader: Unxls::Biff8::Structure.frtheader(frth_data), # frtHeader (12 bytes): An FrtHeader structure. The frtHeader.rt field MUST be 2189.
256
+ # A - unused1 (1 bit): Undefined and MUST be ignored.
257
+ fNewBorder: attrs.set_at?(1), # B - fNewBorder (1 bit): A bit that specifies whether it is possible to specify internal border formatting in xfprops. Internal border formatting is formatting that applies to borders that lie between a range of cells. Specifies that internal border formatting can be used in xfprops.
258
+ # C - unused2 (1 bit): Undefined and MUST be ignored.
259
+ # reserved (13 bits): MUST be zero, and MUST be ignored.
260
+ }
261
+
262
+ result.merge!(Unxls::Biff8::Structure.xfprops(@bytes)) # xfprops (variable): An XFProps structure that specifies the formatting properties.
263
+ end
264
+
265
+ # 2.4.117 FilePass
266
+ # The FilePass record specifies the encryption algorithm used to encrypt the workbook and the structure that is used to verify the password provided when attempting to open the workbook.
267
+ # @return [Hash]
268
+ def r_filepass
269
+ w_encryption_type = @bytes.read(2).unpack('v').first
270
+ result = { wEncryptionType: w_encryption_type } # wEncryptionType (2 bytes): A Boolean (section 2.5.14) that specifies the encryption type.
271
+
272
+ case w_encryption_type
273
+ when 0x0000
274
+ result[:_type] = :XOR
275
+ result.merge!(Unxls::Biff8::Structure.xorobfuscation(@bytes)) # encryptionInfo (variable): A variable type field. The type and meaning of this field is dictated by the value of wEncryptionType.
276
+
277
+ when 0x0001
278
+ rc4_type = @bytes.read(2).unpack('v').first
279
+ @bytes.pos -= 2
280
+
281
+ case rc4_type
282
+ when 0x0001
283
+ result[:_type] = :RC4
284
+ result.merge!(Unxls::Offcrypto.rc4encryptionheader(@bytes))
285
+
286
+ when 0x0002, 0x0003, 0x0004
287
+ result[:_type] = :CryptoAPI
288
+ result.merge!(Unxls::Offcrypto.rc4cryptoapiheader(@bytes))
289
+
290
+ else
291
+ raise("Unknown RC4 encryption header type #{Unxls::Log.h2b(rc4_type)} in FilePass record")
292
+
293
+ end
294
+
295
+ else
296
+ raise("Unknown encryption type #{Unxls::Log.h2b(w_encryption_type)} in FilePass record")
297
+
298
+ end
299
+
300
+ result
301
+ end
302
+
303
+ # 2.4.122 Font, page 298
304
+ # The Font record specifies a font and font formatting information.
305
+ # @return [Hash]
306
+ def r_font
307
+ @serial = true
308
+
309
+ dy_height, attrs, icv, bls, sss, uls, b_family, b_char_set, _ = @bytes.read(14).unpack('v5C4')
310
+ attrs = Unxls::BitOps.new(attrs)
311
+
312
+ {
313
+ dyHeight: dy_height, # dyHeight (2 bytes): An unsigned integer that specifies the height of the font in twips.
314
+ # 0 A - unused1 (1 bit): Undefined and MUST be ignored.
315
+ fItalic: attrs.set_at?(1), # B - fItalic (1 bit): A bit that specifies whether the font is italic.
316
+ # 2 C - unused2 (1 bit): Undefined and MUST be ignored.
317
+ fStrikeOut: attrs.set_at?(3), # D - fStrikeOut (1 bit): A bit that specifies whether the font has strikethrough formatting applied.
318
+
319
+ # fOutline: attrs.set_at?(4), # (Seems like Mac Excel v.1,2 only) E - fOutline (1 bit): A bit that specifies whether the font has an outline effect applied.
320
+ # fShadow: attrs.set_at?(5), # (Seems like Mac Excel v.1,2 only) F - fShadow (1 bit): A bit that specifies whether the font has a shadow effect applied.
321
+ # fCondense: attrs.set_at?(6), # (Seems like there is no such option in Excel) G - fCondense (1 bit): A bit that specifies whether the font is condensed.
322
+ # fExtend: attrs.set_at?(7), # (Seems like there is no such option in Excel) H - fExtend (1 bit): A bit that specifies whether the font is extended.
323
+
324
+ # 8…15 reserved (8 bits): MUST be zero, and MUST be ignored.
325
+
326
+ icv: icv, # icv (2 bytes): An unsigned integer that specifies the color of the font. The value SHOULD<88> be an IcvFont value.
327
+
328
+ bls: bls, # bls (2 bytes): An unsigned integer that specifies the font weight.
329
+ bls_d: Unxls::Biff8::Structure.bold(bls),
330
+
331
+ sss: sss, # sss (2 bytes): An unsigned integer that specifies whether superscript, subscript, or normal script is used.
332
+ sss_d: Unxls::Biff8::Structure.script(sss),
333
+
334
+ uls: uls, # uls (1 byte): An unsigned integer that specifies the underline style.
335
+ uls_d: Unxls::Biff8::Structure.underline(uls),
336
+
337
+ bFamily: b_family, # bFamily (1 byte): An unsigned integer that specifies the font family this font belongs to.
338
+ bFamily_d: Unxls::Biff8::Structure._font_family(b_family),
339
+
340
+ bCharSet: b_char_set, # bCharSet (1 byte): An unsigned integer that specifies the character set.
341
+ bCharSet_d: Unxls::Biff8::Structure._character_set(b_char_set),
342
+
343
+ # unused3 (1 byte): Undefined and MUST be ignored.
344
+
345
+ fontName: Unxls::Biff8::Structure.shortxlunicodestring(@bytes) # A ShortXLUnicodeString structure that specifies the name of this font.
346
+ }
347
+ end
348
+
349
+ # 2.4.126 Format, page 302
350
+ # The Format record specifies a number format.
351
+ # See 18.8.31 numFmts (Number Formats) (p. 1774), Ecma Office Open XML Part 1 - Fundamentals And Markup Language Reference.pdf
352
+ # @return [Hash]
353
+ def r_format
354
+ @serial = true
355
+
356
+ ifmt = @bytes.read(2).unpack('v').first # See 2.5.165 IFmt
357
+
358
+ {
359
+ ifmt: ifmt, # ifmt (2 bytes): An IFmt structure that specifies the identifier of the format string specified by stFormat.
360
+ stFormat: Unxls::Biff8::Structure.xlunicodestring(@bytes) # An XLUnicodeString structure that specifies the format string for this number format. The format string indicates how to format the numeric value of the cell.
361
+ }
362
+ end
363
+
364
+ # 2.4.188 Palette, page 353
365
+ # The Palette record specifies a custom color palette.
366
+ # @return [Hash]
367
+ def r_palette
368
+ ccv = @bytes.read(2).unpack('s<').first
369
+
370
+ result = {
371
+ ccv: ccv, # ccv (2 bytes): A signed integer that specifies the number of colors in the rgColor array. The value MUST be 56.
372
+ rgColor: [] # rgColor (variable): An array of LongRGB structures that specifies the colors of the color palette. The number of items in the array MUST be equal to the value specified in the ccv field.
373
+ }
374
+
375
+ ccv.times do
376
+ result[:rgColor] << Unxls::Biff8::Structure.longrgba(@bytes.read(4))
377
+ end
378
+
379
+ result
380
+ end
381
+
382
+ # 2.4.265 SST, page 419
383
+ # The SST record specifies string constants.
384
+ # @return [Hash]
385
+ def r_sst
386
+ cst_total, cst_unique = @bytes.read(8).unpack('l<l<')
387
+ result = {
388
+ cstTotal: cst_total, # cstTotal (4 bytes): A signed integer that specifies the total number of references in the workbook to the strings in the shared string table.
389
+ cstUnique: cst_unique, # cstUnique (4 bytes): A signed integer that specifies the number of unique strings in the shared string table.
390
+ rgb: [] # rgb (variable): An array of XLUnicodeRichExtendedString structures. Records in this array are unique.
391
+ }
392
+
393
+ cst_unique.times do
394
+ result[:rgb] << Unxls::Biff8::Structure.xlunicoderichextendedstring(self)
395
+ end
396
+
397
+ result
398
+ end
399
+
400
+ # 2.4.269 Style, page 426
401
+ # The Style record specifies a cell style.
402
+ # @return [Hash]
403
+ def r_style
404
+ @serial = true
405
+
406
+ attrs = @bytes.read(2).unpack('v').first
407
+ attrs = Unxls::BitOps.new(attrs)
408
+
409
+ result = {
410
+ ixfe: attrs.value_at(0..11), # ixfe (12 bits): An unsigned integer that specifies the zero-based index of the cell style XF in the collection of XF records in the Globals Substream.
411
+ # A - unused (3 bits): Undefined and MUST be ignored.
412
+ fBuiltIn: attrs.set_at?(15) # B - fBuiltIn (1 bit): A bit that specifies whether the cell style is built-in.
413
+ }
414
+
415
+ if result[:fBuiltIn]
416
+ result[:builtInData] = Unxls::Biff8::Structure.builtinstyle(@bytes.read(2)) # builtInData (2 bytes): An optional BuiltInStyle structure that specifies the built-in cell style properties.
417
+ else
418
+ result[:user] = Unxls::Biff8::Structure.xlunicodestring(@bytes).to_sym # user (variable): An optional XLUnicodeString structure that specifies the name of the user-defined cell style.
419
+ end
420
+
421
+ result
422
+ end
423
+
424
+ # 2.4.270 StyleExt, page 427
425
+ # The StyleExt record specifies additional information for a cell style.
426
+ # @return [Hash]
427
+ def r_styleext
428
+ @serial = true
429
+
430
+ result = { frtHeader: Unxls::Biff8::Structure.frtheader(@bytes.read(12)) }
431
+
432
+ attrs, i_category = @bytes.read(2).unpack('CC')
433
+ attrs = Unxls::BitOps.new(attrs)
434
+ result[:fBuiltIn] = attrs.set_at?(0) # A - fBuiltIn (1 bit): A bit that specifies if this is a built-in cell style. If the value is 1, this is a built-in cell style. This value MUST match the fBuiltIn field of the preceding Style record.
435
+ result[:fHidden] = attrs.set_at?(1) # B - fHidden (1 bit): A bit that specifies whether the cell style is not displayed in the user interface.
436
+ result[:fCustom] = attrs.set_at?(2) # C - fCustom (1 bit): A bit that specifies whether the built-in cell style was modified by the user and thus has a custom definition.
437
+ # reserved (5 bits): MUST be zero and MUST be ignored.
438
+ result[:iCategory] = i_category # iCategory (1 byte): An unsigned integer that specifies which style category (2) that this style belongs to.
439
+ result[:iCategory_d] = {
440
+ 0x00 => :'Custom style',
441
+ 0x01 => :'Good, bad, neutral style',
442
+ 0x02 => :'Data model style',
443
+ 0x03 => :'Title and heading style',
444
+ 0x04 => :'Themed cell style',
445
+ 0x05 => :'Number format style'
446
+ }[i_category]
447
+
448
+ built_in_data = @bytes.read(2)
449
+ result[:builtInData] = Unxls::Biff8::Structure.builtinstyle(built_in_data) if result[:fBuiltIn] # builtInData (2 bytes): A BuiltInStyle structure that specifies the built-in cell style properties. If fBuiltIn is 0, this field MUST be 0xFFFF and MUST be ignored. If fBuiltIn is 1, this field MUST match the builtInData field of the preceding Style record.
450
+
451
+ result[:stName] = Unxls::Biff8::Structure.lpwidestring(@bytes).to_sym # stName (variable): An LPWideString structure that specifies the name of the style to extend. MUST be less than or equal to 255 characters in length. If fBuiltIn is 0, the name specified by this field MUST match the name specified by the user field of the preceding Style record.
452
+
453
+ result.merge!(Unxls::Biff8::Structure.xfprops(@bytes)) # xfProps (variable): An XFProps structure that specifies the formatting properties.
454
+ end
455
+
456
+ # 2.4.320 TableStyle
457
+ # The TableStyle record specifies a user-defined table style and the beginning of a collection of TableStyleElement records as specified by the Globals Substream ABNF. The collection of TableStyleElement records specifies the properties of the table style.
458
+ # @return [Hash]
459
+ def r_tablestyle
460
+ @serial = true
461
+
462
+ frth_data = @bytes.read(12)
463
+ attrs, ctse, cch_name = @bytes.read(8).unpack('vVv')
464
+ attrs = Unxls::BitOps.new(attrs)
465
+
466
+ {
467
+ frtHeader: Unxls::Biff8::Structure.frtheader(frth_data), # frtHeader (12 bytes): An FrtHeader structure. The frtHeader.rt field MUST be 0x088F.
468
+ # A - reserved1 (1 bit): MUST be zero, and MUST be ignored.
469
+ fIsPivot: attrs.set_at?(1), # B - fIsPivot (1 bit): A bit that specifies whether the style can be applied to PivotTable views.
470
+ fIsTable: attrs.set_at?(2), # C - fIsTable (1 bit): A bit that specifies whether the style can be applied to tables.
471
+ # reserved2 (13 bits): MUST be zero, and MUST be ignored.
472
+ ctse: ctse, # ctse (4 bytes): An unsigned integer that specifies the count of TableStyleElement records to follow this record. MUST be less than or equal to 28.
473
+ cchName: cch_name, # cchName (2 bytes): An unsigned integer that specifies the count of characters in the rgchName field. This value MUST be less than or equal to 255 and greater than or equal to 1.
474
+ rgchName: Unxls::Biff8::Structure._read_unicodestring(@bytes, cch_name, 1) # rgchName (variable): An array of Unicode characters whose length is specified by cchName that specifies the style name.
475
+ }
476
+ end
477
+
478
+ # 2.4.321 TableStyleElement
479
+ # The TableStyleElement record specifies formatting for one element of a table style. Each table style element specifies the formatting to apply to a particular area of a table or PivotTable view when the table style is applied.
480
+ # @return [Hash]
481
+ def r_tablestyleelement
482
+ @serial = true
483
+
484
+ frth_data = @bytes.read(12)
485
+ tse_type, size, index = @bytes.read(12).unpack('V3')
486
+
487
+ {
488
+ frtHeader: Unxls::Biff8::Structure.frtheader(frth_data), # frtHeader (12 bytes): An FrtHeader structure. The frtHeader.rt field MUST be 0x0890.
489
+ tseType: tse_type, # tseType (4 bytes): An unsigned integer that specifies the area of the table or PivotTable view to which the formatting is applied.
490
+ tseType_d: Unxls::Biff8::Structure._tse_type(tse_type),
491
+ size: size, # size (4 bytes): An unsigned integer that specifies the number of rows or columns to include in a single stripe band. MUST be ignored when the value of tseType does not equal 5, 6, 7, or 8. MUST be greater than or equal to 1 and less than or equal to 9.
492
+ index: index, # index (4 bytes): A DXFId structure that specifies the DXF record that contains the differential formatting properties for this element.
493
+ _tsi: @stream.last_parsed[:TableStyle].size - 1 # Index of parent TableStyle record
494
+ }
495
+ end
496
+
497
+ # 2.4.322 TableStyles
498
+ # The TableStyles record specifies the default table and PivotTable table styles and specifies the beginning of a collection of TableStyle records as defined by the Globals Substream ABNF. The collection of TableStyle records specifies user-defined table styles.
499
+ # @return [Hash]
500
+ def r_tablestyles
501
+ frth_data = @bytes.read(12)
502
+ cts, cch_table_style, cch_pivot_style = @bytes.read(8).unpack('Vvv')
503
+
504
+ {
505
+ frtHeader: Unxls::Biff8::Structure.frtheader(frth_data),
506
+ cts: cts, # cts (4 bytes): An unsigned integer that specifies the total number of table styles in this document. This is the sum of the standard built-in table styles and all of the custom table styles.
507
+ cchDefTableStyle: cch_table_style, # cchDefTableStyle (2 bytes): An unsigned integer that specifies the count of characters in the rgchDefTableStyle field.
508
+ cchDefPivotStyle: cch_pivot_style, # cchDefPivotStyle (2 bytes): An unsigned integer that specifies the count of characters in the rgchDefPivotStyle field.
509
+ rgchDefTableStyle: Unxls::Biff8::Structure._read_unicodestring(@bytes, cch_table_style, 1), # rgchDefTableStyle (variable): An array of Unicode characters whose length is specified by cchDefTableStyle that specifies the name of the default table style. Has double-byte characters
510
+ rgchDefPivotStyle: Unxls::Biff8::Structure._read_unicodestring(@bytes, cch_pivot_style, 1), # rgchDefPivotStyle (variable): An array of Unicode characters whose length is specified by cchDefPivotStyle that specifies the name of the default PivotTable style. Has double-byte characters
511
+ }
512
+ end
513
+
514
+ # 2.4.326 Theme, page 552
515
+ # The Theme record specifies the theme in use in the document.
516
+ # @return [Hash]
517
+ def r_theme
518
+ result = { frtHeader: Unxls::Biff8::Structure.frtheader(@bytes.read(12)) } # frtHeader (12 bytes): An FrtHeader structure. The value of the frtHeader.rt field MUST be 2198.
519
+
520
+ dw_theme_version = @bytes.read(4).unpack('V').first
521
+ result[:dwThemeVersion] = dw_theme_version # dwThemeVersion (4 bytes): An unsigned integer that specifies the theme type.
522
+ result[:dwThemeVersion_d] = { 0 => :custom, 124226 => :default }[dw_theme_version]
523
+
524
+ theme_data = @bytes.read
525
+ while open_next_record_block.size > 0
526
+ blocks.io.read(12) # skip frtHeader
527
+ theme_data += blocks.io.read # rgb (variable): An optional byte stream that specifies the theme contents
528
+ end
529
+
530
+ # See 14.2.7 Theme Part (p. 135), Ecma Office Open XML Part 1 - Fundamentals And Markup Language Reference.pdf
531
+ result[:rgb_d] = {} # unzipped theme xml files
532
+ zip = Zip::InputStream.new(StringIO.new(theme_data))
533
+ while (entry = zip.get_next_entry) do
534
+ result[:rgb_d][entry.name] = entry.get_input_stream.read
535
+ end
536
+
537
+ result
538
+ end
539
+
540
+ # 2.4.353 XF, page 584
541
+ # See also 2.5.282 XFIndex
542
+ # The XF record specifies formatting properties for a cell or a cell style.
543
+ # @return [Hash]
544
+ def r_xf
545
+ @serial = true
546
+
547
+ ifnt, ifmt, attrs = @bytes.read(6).unpack('v3')
548
+ attrs = Unxls::BitOps.new(attrs)
549
+ result = {
550
+ ifnt: ifnt, # ifnt (2 bytes): A FontIndex (2.5.129) structure that specifies a Font record.
551
+ ifmt: ifmt, # ifmt (2 bytes): An IFmt (2.5.165) structure that specifies a number format identifier.
552
+ fLocked: attrs.set_at?(0), # A - fLocked (1 bit): A bit that specifies whether the locked protection property is set to true.
553
+ fHidden: attrs.set_at?(1), # B - fHidden (1 bit): A bit that specifies whether the hidden protection property is set to true.
554
+ fStyle: attrs.set_at?(2), # C - fStyle (1 bit): A bit that specifies whether this record specifies a cell XF or a cell style XF. If the value is 1, this record specifies a cell style XF.
555
+ f123Prefix: attrs.set_at?(3), # D - f123Prefix (1 bit): A bit that specifies whether prefix characters are present in the cell.
556
+ ixfParent: attrs.value_at(4..15), # ixfParent (12 bits): An unsigned integer that specifies the zero-based index of a cell style XF record in the collection of XF records in the Globals Substream that this cell format inherits properties from. Cell style XF records are the subset of XF records with an fStyle field equal to 1. See XFIndex (2.5.282) for more information about the organization of XF records in the file. If fStyle equals 1, this field SHOULD equal 0xFFF, indicating there is no inheritance from a cell style XF.
557
+ }
558
+
559
+ specifies = Unxls::Biff8::Structure._builtin_xf_description(collection_index) # See 2.5.282 XFIndex
560
+ result[:_description] = specifies if specifies
561
+
562
+ result[:_type] = result[:fStyle] ? :stylexf : :cellxf
563
+
564
+ result.merge(Unxls::Biff8::Structure.send(result[:_type], @bytes))
565
+ end
566
+
567
+ # 2.4.355 XFExt, page 585
568
+ # The XFExt record specifies a set of formatting property extensions to an XF record in this file.
569
+ # @return [Hash]
570
+ def r_xfext
571
+ @serial = true
572
+
573
+ result = { frtHeader: Unxls::Biff8::Structure.frtheader(@bytes.read(12)) }
574
+ result.merge(Unxls::Biff8::Structure._xfext(@bytes))
575
+ end
576
+
577
+ #
578
+ # Worksheet substream records
579
+ #
580
+
581
+ # 2.4.20 Blank
582
+ # The Blank record specifies an empty cell with no formula (section 2.2.2) or value.
583
+ # @return [Hash]
584
+ def r_blank
585
+ @serial = true
586
+
587
+ Unxls::Biff8::Structure.cell(@bytes.read) # cell (6 bytes): A Cell structure that specifies the cell.
588
+ end
589
+
590
+ # 2.4.24 BoolErr
591
+ # The BoolErr record specifies a cell that contains either a Boolean value or an error value.
592
+ # @return [Hash]
593
+ def r_boolerr
594
+ @serial = true
595
+
596
+ {
597
+ **Unxls::Biff8::Structure.cell(@bytes.read(6)), # cell (6 bytes): A Cell structure that specifies the cell.
598
+ bes: Unxls::Biff8::Structure.bes(@bytes.read(2)) # bes (2 bytes): A Bes structure that specifies a Boolean or an error value.
599
+ }
600
+ end
601
+
602
+ # 2.4.42 CF, page 228
603
+ # The CF record specifies a conditional formatting rule.
604
+ # @return [Hash]
605
+ def r_cf
606
+ @serial = true
607
+
608
+ ct, cp, cce1, cce2 = @bytes.read(6).unpack('CCvv')
609
+
610
+ ct_d = {
611
+ 0x01 => :cp_function_true, # Apply the conditional formatting when the comparison function specified by cp applied to the cell value, rgce1 and rgce2, evaluates to TRUE.
612
+ 0x02 => :rgce1_formula_true # Apply the conditional formatting when the formula (section 2.2.2) specified by rgce1 evaluates to TRUE.
613
+ }[ct]
614
+
615
+ {
616
+ ct: ct, # An unsigned integer that specifies the type of condition
617
+ ct_d: ct_d,
618
+ cp: cp, # An unsigned integer that specifies the comparison function used when ct is equal to 0x01. In the following table, v represents the cell value, and v1 and v2 represent the results of evaluating the formulas specified by rgce1 and rgce2.
619
+ cp_d: Unxls::Biff8::Structure._cf_cp(cp),
620
+ cce1: cce1, # An unsigned integer that specifies the size of rgce1 in bytes.
621
+ cce2: cce2, # An unsigned integer that specifies the size of rgce2 in bytes.
622
+ rgbdxf: Unxls::Biff8::Structure.dxfn(@bytes), # A DXFN structure that specifies the formatting to apply to a cell that fulfills the condition.
623
+ rgce1: Unxls::Biff8::Structure.cfparsedformulanocce(@bytes.read(cce1)), # A CFParsedFormulaNoCCE structure that specifies the first formula. If ct is equal to 0x01, this field is the first operand of the comparison. If ct is equal to 0x02, this formula is used to determine if the conditional formatting is applied.
624
+ rgce2: Unxls::Biff8::Structure.cfparsedformulanocce(@bytes.read(cce2)) # A CFParsedFormulaNoCCE structure that specifies the formula that is the second operand of the comparison if ct is equal to 0x01 and cp is either equal to 0x01 or 0x02.
625
+ }
626
+ end
627
+
628
+ # 2.4.43 CF12
629
+ # The CF12 record specifies a conditional formatting rule.
630
+ # @return [Hash]
631
+ def r_cf12
632
+ @serial = true
633
+
634
+ result = {}
635
+ result[:frtRefHeader] = Unxls::Biff8::Structure.frtrefheader(@bytes) # frtRefHeader (12 bytes): An FrtRefHeader.
636
+
637
+ ct, cp, cce1, cce2 = @bytes.read(6).unpack('CCvv')
638
+ ct_d = {
639
+ 0x01 => :cp_function_true, # Apply the conditional formatting if the comparison operation specified by cp evaluates to TRUE. rgbCT MUST be omitted.
640
+ 0x02 => :rgce1_formula_true, # Apply the conditional formatting if the formula (section 2.2.2) specified by rgce1 evaluates to TRUE. rgbCT MUST be omitted.
641
+ 0x03 => :color_scale_formatting, # Use color scale formatting. rgbCT is a CFGradient.
642
+ 0x04 => :data_bar_formatting, # Use data bar formatting. rgbCT is a CFDatabar.
643
+ 0x05 => :passes_cffilter, # Apply the conditional formatting when the cell value passes a filter specified in the rgbCT structure. rgbCT is a CFFilter.
644
+ 0x06 => :icon_set_formatting, # Use icon set formatting. rgbCT is a CFMultistate.
645
+ }[ct]
646
+
647
+ result.merge!({
648
+ ct: ct, # ct (1 byte): An unsigned integer that specifies the type of condition. This field determines the type of the rgbCT field.
649
+ ct_d: ct_d,
650
+ cp: cp, # cp (1 byte): An unsigned integer that specifies the comparison function used when ct is equal to 0x01.
651
+ cp_d: Unxls::Biff8::Structure._cf_cp(cp),
652
+ cce1: cce1, # cce1 (2 bytes): An unsigned integer that specifies the size of rgce1 in bytes.
653
+ cce2: cce2, # cce2 (2 bytes): An unsigned integer that specifies the size of rgce2 in bytes.
654
+ })
655
+
656
+ result[:dxf] = Unxls::Biff8::Structure.dxfn12(@bytes) # dxf (variable): A DXFN12 that specifies the formatting to apply to a cell that fulfills the condition.
657
+
658
+ # Rest of the record not implemented yet:
659
+ result[:rgce1] = Unxls::Biff8::Structure.cfparsedformulanocce(@bytes.read(cce1)) # rgce1 (variable): A CFParsedFormulaNoCCE that specifies the formula used to evaluate the first operand in a comparison when ct is 0x01. If ct is 0x02 rgce1 MUST be a Boolean function.
660
+ result[:rgce2] = Unxls::Biff8::Structure.cfparsedformulanocce(@bytes.read(cce2)) # rgce2 (variable): A CFParsedFormulaNoCCE that specifies the formula used to evaluate the second operand of the comparison when ct is 0x01 and cp is either 0x01 or 0x02.
661
+ result[:fmlaActive] = Unxls::Biff8::Structure.cfparsedformula(@bytes) # fmlaActive (variable): A CFParsedFormula that specifies the formula that specifies an activity condition for the color scale, data bar and icon set formatting rule types. If ct is equal to 0x03, 0x04 or 0x06, then the conditional formatting is applied if fmlaActive evaluates to TRUE.
662
+
663
+ a_e_raw, ipriority, icf_template, cb_template_parm = @bytes.read(6).unpack('CvvC')
664
+ a_e = Unxls::BitOps.new(a_e_raw)
665
+
666
+ # 0 A - unused1 (1 bit): Undefined and MUST be ignored.
667
+ result[:fStopIfTrue] = a_e.set_at?(1) # 1 B - fStopIfTrue (1 bit): A bit that specifies whether, when a cell fulfills the condition corresponding to this rule, the lower priority conditional formatting rules that apply to this cell are evaluated.
668
+ # 2…3 C - reserved1 (2 bits): MUST be zero and MUST be ignored.
669
+ # 4 D - unused2 (1 bit): Undefined and MUST be ignored.
670
+ # 5…7 E - reserved2 (3 bits): MUST be zero and MUST be ignored.
671
+
672
+ result[:ipriority] = ipriority # ipriority (2 bytes): An unsigned integer that specifies the priority of the rule. Rules that apply to the same cell are evaluated in increasing order of ipriority. MUST be unique across all CF12 records and CFExNonCF12 structures in the worksheet substream.
673
+
674
+ result[:icfTemplate] = icf_template # icfTemplate (2 bytes): An unsigned integer that specifies the template from which the rule was created.
675
+ result[:icfTemplate_d] = Unxls::Biff8::Structure._icf_template_d(icf_template)
676
+
677
+ result[:cbTemplateParm] = cb_template_parm # cbTemplateParm (1 byte): An unsigned integer that specifies the size of the rgbTemplateParms field in bytes. MUST be 16.
678
+ result[:rgbTemplateParms] = Unxls::Biff8::Structure.cfextemplateparams(@bytes.read(cb_template_parm), icf_template) # rgbTemplateParms (16 bytes): A CFExTemplateParams that specifies the parameters for the rule.
679
+
680
+ # rgbCT (variable): A field that specifies the parameters of this rule. The type of rgbCT depends on the value of ct.
681
+ result[:rgbCT] = case ct
682
+ # Apply the conditional formatting if the comparison operation specified by cp evaluates to TRUE
683
+ # Apply the conditional formatting if the formula (section 2.2.2) specified by rgce1 evaluates to TRUE.
684
+ when 0x01, 0x02 then :omitted # rgbCT MUST be omitted.
685
+ when 0x03 then Unxls::Biff8::Structure.cfgradient(@bytes) # Use color scale formatting.
686
+ when 0x04 then Unxls::Biff8::Structure.cfdatabar(@bytes) # Use data bar formatting.
687
+ when 0x05 then Unxls::Biff8::Structure.cffilter(@bytes) # Apply the conditional formatting when the cell value passes a filter specified in the rgbCT structure.
688
+ when 0x06 then Unxls::Biff8::Structure.cfmultistate(@bytes) # Use icon set formatting.
689
+ else raise "Unexpected value '#{ct}' of ct field in CF12 record"
690
+ end
691
+
692
+ result
693
+ end
694
+
695
+ # 2.4.44 CFEx
696
+ # The CFEx record extends a CondFmt.
697
+ # @return [Hash]
698
+ def r_cfex
699
+ @serial = true
700
+
701
+ result = {
702
+ frtRefHeaderU: Unxls::Biff8::Structure.frtrefheaderu(@bytes) # frtRefHeaderU (12 bytes): An FrtRefHeaderU structure.
703
+ }
704
+
705
+ f_is_cf12, n_id = @bytes.read(6).unpack('Vv')
706
+ # fIsCF12 (4 bytes): A Boolean (section 2.5.14) that specifies what type of rule this record extends. MUST be one of the following values:
707
+ # 0x00000000 – This record extends a rule specified by a CF record and MUST NOT be followed by a CF12 record.
708
+ # 0x00000001 – This record extends a rule specified by a CF12 record and MUST be followed by the CF12 record it extends.
709
+ result[:fIsCF12] = f_is_cf12
710
+ result[:nID] = n_id # nID (2 bytes): An unsigned integer that specifies which CondFmt record is being extended. It MUST be equal to the nID field of one of the CondFmt records in the Worksheet substream.
711
+
712
+ if f_is_cf12.zero?
713
+ result[:rgbContent] = Unxls::Biff8::Structure.cfexnoncf12(@bytes) # rgbContent (variable): A CFExNonCF12 structure that specifies the extensions to an existing CF record. MUST be omitted when fIsCF12 is not equal to 0x00.
714
+ end
715
+
716
+ result
717
+ end
718
+
719
+ # 2.4.53 ColInfo, page 240
720
+ # The ColInfo record specifies the column formatting for a range of columns.
721
+ # @return [Hash]
722
+ def r_colinfo
723
+ @serial = true
724
+
725
+ col_first, col_last, coldx, ixfe, attrs, _ = @bytes.read.unpack('v6')
726
+ attrs = Unxls::BitOps.new(attrs)
727
+
728
+ {
729
+ colFirst: col_first, # colFirst (2 bytes): A Col256U structure that specifies the first formatted column.
730
+ colLast: col_last, # colLast (2 bytes): A Col256U structure that specifies the last formatted column. The value MUST be greater than or equal to colFirst.
731
+ coldx: coldx, # coldx (2 bytes): An unsigned integer that specifies the column width in units of 1/256th of a character width. Character width is defined as the maximum digit width of the numbers 0, 1, 2, … 9 as rendered in the Normal style’s font.
732
+ ixfe: ixfe, # ixfe (2 bytes): An IXFCell structure that specifies the default format for the column cells.
733
+ fHidden: attrs.set_at?(0), # A - fHidden (1 bit): A bit that specifies whether the column range defined by colFirst and colLast is hidden.
734
+ fUserSet: attrs.set_at?(1), # B - fUserSet (1 bit): A bit that specifies that the column width was either manually set by the user or is different from the default column width as specified by DefColWidth. If the value is 1, the column width was manually set or is different from DefColWidth.
735
+ fBestFit: attrs.set_at?(2), # C - fBestFit (1 bit): A bit that specifies whether the column range defined by colFirst and colLast is set to "best fit." "Best fit" implies that the column width resizes based on the cell contents, and that the column width does not equal the default column width as specified by DefColWidth.
736
+ fPhonetic: attrs.set_at?(3), # D - fPhonetic (1 bit): A bit that specifies whether phonetic information is displayed by default for the column range defined by colFirst and colLast.
737
+ # 4…7 E - reserved1 (4 bits): MUST be zero, and MUST be ignored.
738
+ iOutLevel: attrs.value_at(8..10), # F - iOutLevel (3 bits): An unsigned integer that specifies the outline level of the column range defined by colFirst and colLast.
739
+ # G - unused1 (1 bit): Undefined and MUST be ignored.
740
+ fCollapsed: attrs.set_at?(12) # H - fCollapsed (1 bit): A bit that specifies whether the column range defined by colFirst and colLast is in a collapsed outline state.
741
+ # I - reserved2 (3 bits): MUST be zero, and MUST be ignored.
742
+ # unused2 (2 bytes): Undefined and MUST be ignored.
743
+ }
744
+ end
745
+
746
+ # 2.4.56 CondFmt, page 242
747
+ # @return [Hash]
748
+ def r_condfmt
749
+ @serial = true
750
+
751
+ ccf, attrs = @bytes.read(4).unpack('vv')
752
+ attrs = Unxls::BitOps.new(attrs)
753
+
754
+ {
755
+ ccf: ccf, # An unsigned integer that specifies the count of CF records that follow this record.
756
+ fToughRecalc: attrs.set_at?(0), # A bit that specifies that the appearance of the cell requires significant processing.
757
+ nID: attrs.value_at(1..15), # An unsigned integer that identifies this record. The CFEx record uses this identifier to specify which CondFmt it extends.
758
+ refBound: Unxls::Biff8::Structure.ref8u(@bytes.read(8)), # A Ref8U structure that specifies the bounds of the set of cells to which the conditional formatting rules apply.
759
+ sqref: Unxls::Biff8::Structure.sqrefu(@bytes) # A SqRefU structure that specifies the cells to which the conditional formatting rules apply.
760
+ }
761
+ end
762
+
763
+ # 2.4.57 CondFmt12, page 242
764
+ # @return [Hash]
765
+ def r_condfmt12
766
+ @serial = true
767
+
768
+ {
769
+ frtRefHeaderU: Unxls::Biff8::Structure.frtrefheaderu(@bytes), # frtRefHeaderU (12 bytes): An FrtRefHeaderU structure
770
+ mainCF: Unxls::Biff8::Structure.condfmtstructure(@bytes) # mainCF (variable): A CondFmtStructure structure that specifies properties of a set of conditional formatting rules.
771
+ }
772
+ end
773
+
774
+ # 2.4.90 Dimensions
775
+ # The Dimensions record specifies the used range of the sheet. It specifies the row and column bounds of used cells in the sheet. Used cells include all cells with formulas (section 2.2.2) or data. Used cells also include all cells with formatting applied directly to the cell. …
776
+ # @return [Hash]
777
+ def r_dimensions
778
+ rw_mic, rw_mac, col_mic, col_mac, _ = @bytes.read.unpack('VVv3')
779
+
780
+ {
781
+ rwMic: rw_mic, # rwMic (4 bytes): A RwLongU structure that specifies the first row in the sheet that contains a used cell.
782
+ rwMac: rw_mac, # rwMac (4 bytes): An unsigned integer that specifies the zero-based index of the row after the last row in the sheet that contains a used cell. MUST be less than or equal to 0x00010000. If this value is 0x00000000, no cells on the sheet are used cells.
783
+ colMic: col_mic, # colMic (2 bytes): A ColU structure that specifies the first column in the sheet that contains a used cell.
784
+ colMac: col_mac, # colMac (2 bytes): An unsigned integer that specifies the zero-based index of the column after the last column in the sheet that contains a used cell. MUST be less than or equal to 0x0100. If this value is 0x0000, no cells on the sheet are used cells.
785
+ # reserved (2 bytes): MUST be zero, and MUST be ignored.
786
+ }
787
+ end
788
+
789
+ # @todo May be Continue-ed (!!!)
790
+ # 2.4.114 Feature11
791
+ # The Feature11 record specifies specific shared feature data. The only shared feature type stored in this record is a table in a worksheet.
792
+ # @return [Hash]
793
+ def r_feature11
794
+ @serial = true
795
+
796
+ frt_ref_header_u = Unxls::Biff8::Structure.frtrefheaderu(@bytes)
797
+ isf, _, _, cref2, cb_feat_data, _ = @bytes.read(15).unpack('vCVvVv')
798
+
799
+ {
800
+ frtRefHeaderU: frt_ref_header_u, # frtRefHeaderU (12 bytes): An FrtRefHeaderU. The frtRefHeaderU.rt field MUST be 0x0872. The frtRefHeaderU.ref8 MUST refer to a range of cells associated with this record.
801
+ isf: isf, # isf (2 bytes): A SharedFeatureType enumeration that specifies the type of Shared Feature data stored in the rgbFeat field. MUST be ISFLIST.
802
+ isf_d: Unxls::Biff8::Structure.sharedfeaturetype(isf),
803
+ # reserved1 (1 byte): Reserved and MUST be zero.
804
+ # reserved2 (4 bytes): MUST be zero, and MUST be ignored.
805
+ cref2: cref2, # cref2 (2 bytes): An unsigned integer that specifies the count of Ref8U records within the refs2 field.
806
+ cbFeatData: cb_feat_data, # cbFeatData (4 bytes): An unsigned integer that specifies the size in bytes of the rgbFeat variable-size field. If the value is 0x0000, the size of the rgbFeat field is calculated by the following formula: size of rgbFeat = total size of record in bytes – size of refs2 in bytes – 27 bytes
807
+ # reserved3 (2 bytes): MUST be zero, and MUST be ignored.
808
+ refs2: cref2.times.map { Unxls::Biff8::Structure.ref8u(@bytes.read(8)) }, # refs2 (variable): An array of Ref8U structures that specifies references to ranges of cells within the worksheet associated with the feature. The count of records within this field is specified by the cref2 field.
809
+ rgbFeat: Unxls::Biff8::Structure.tablefeaturetype(@bytes) # rgbFeat (variable): A variable-size structure that contains feature specific data. The size of the structure is specified by the cbFeatData field. This field MUST contain a TableFeatureType structure.
810
+ }
811
+ end
812
+
813
+ # 2.4.115 Feature12
814
+ # The Feature12 record specifies shared feature data that is used to describe a table in a worksheet. This record is used to encapsulate a table that has properties not supported by the Feature11 record.
815
+ # @return [Hash]
816
+ alias :r_feature12 :r_feature11
817
+
818
+ # 2.4.127 Formula
819
+ # The Formula record specifies a formula (section 2.2.2) for a cell.
820
+ # @return [Hash]
821
+ def r_formula
822
+ @serial = true
823
+
824
+ result = {
825
+ **Unxls::Biff8::Structure.cell(@bytes.read(6)), # cell (6 bytes): A Cell structure that specifies a cell on the sheet.
826
+ **Unxls::Biff8::Structure.formulavalue(@bytes.read(8)) # val (8 bytes): A FormulaValue structure that specifies the value of the formula.
827
+ }
828
+
829
+ attrs = Unxls::BitOps.new(@bytes.read(2).unpack('v').first)
830
+ @bytes.read(4) # chn
831
+
832
+ result.merge!({
833
+ fAlwaysCalc: attrs.set_at?(0), # A - fAlwaysCalc (1 bit): A bit that specifies whether the formula needs to be calculated during the next recalculation.
834
+ # B - reserved1 (1 bit): MUST be zero, and MUST be ignored.
835
+ fFill: attrs.set_at?(2), # C - fFill (1 bit): A bit that specifies whether the cell has a fill alignment or a center-across-selection alignment.
836
+ fShrFmla: attrs.set_at?(3), # D - fShrFmla (1 bit): A bit that specifies whether the formula is part of a shared formula as defined in ShrFmla. If this formula is part of a shared formula, formula.rgce MUST begin with a PtgExp structure.
837
+ # E - reserved2 (1 bit): MUST be zero, and MUST be ignored.
838
+ fClearErrors: attrs.set_at?(5), # F - fClearErrors (1 bit): A bit that specifies whether the formula is excluded from formula error checking.
839
+ # reserved3 (10 bits): MUST be zero, and MUST be ignored.
840
+ chn: :not_implemented, # chn (4 bytes): A field that specifies an application-specific cache of information. This cache exists for performance reasons only, and can be rebuilt based on information stored elsewhere in the file without affecting calculation results.
841
+ formula: :not_implemented # formula (variable): A CellParsedFormula structure that specifies the formula.
842
+ })
843
+ end
844
+
845
+ # 2.4.140 HLink
846
+ # The HLink record specifies a hyperlink associated with a range of cells.
847
+ # @return [Hash]
848
+ def r_hlink
849
+ @serial = true
850
+
851
+ result = Unxls::Biff8::Structure.ref8u(@bytes.read(8)) # ref8 (8 bytes): A Ref8U structure that specifies the range of cells containing the hyperlink.
852
+ result[:hlinkClsid] = Unxls::Dtyp.guid(@bytes.read(16)) # hlinkClsid (16 bytes): A class identifier (CLSID) (see RFC 4122, https://www.rfc-editor.org/rfc/rfc4122.txt) that specifies the COM component which saved the Hyperlink Object (as defined by [MS-OSHARED] section 2.3.7.1) in hyperlink.
853
+ result[:hyperlink] = Unxls::Oshared.hyperlink(@bytes) # hyperlink (variable): A Hyperlink Object (as defined by [MS-OSHARED] section 2.3.7.1) that specifies the hyperlink and hyperlink-related information.
854
+
855
+ result
856
+ end
857
+
858
+ # 2.4.141 HLinkTooltip
859
+ # The HLinkTooltip record specifies the hyperlink ToolTip associated with a range of cells.
860
+ # @return [Hash]
861
+ def r_hlinktooltip
862
+ @serial = true
863
+
864
+ {
865
+ frtRefHeaderNoGrbit: Unxls::Biff8::Structure.frtrefheadernogrbit(@bytes), # frtRefHeaderNoGrbit (10 bytes): An FrtRefHeaderNoGrbit structure. The frtRefHeaderNoGrbit.rt field MUST be 0x0800. The frtRefHeaderNoGrbit.ref8 field MUST match a Ref8U field from an existing HLink record.
866
+ wzTooltip: Unxls::Oshared._db_zero_terminated(@bytes) # wzTooltip (variable): An array of Unicode characters that specifies the ToolTip string. String length MUST be greater than or equal to 2 and less than or equal to 256 (inclusive of null terminator) and the string MUST be null-terminated.
867
+ }
868
+ end
869
+
870
+ # 2.4.149 LabelSst
871
+ # The LabelSst record specifies a cell that contains a string.
872
+ # @return [Hash]
873
+ def r_labelsst
874
+ @serial = true
875
+
876
+ {
877
+ **Unxls::Biff8::Structure.cell(@bytes.read(6)), # cell (6 bytes): A Cell structure that specifies the cell containing the string from the shared string table.
878
+ isst: @bytes.read(4).unpack('V').first # isst (4 bytes): An unsigned integer that specifies the zero-based index of an element in the array of XLUnicodeRichExtendedString structure in the rgb field of the SST record in this Workbook Stream ABNF that specifies the string contained in the cell. MUST be greater than or equal to zero and less than the number of elements in the rgb field of the SST record.
879
+ }
880
+ end
881
+
882
+ # 2.4.157 List12
883
+ # The List12 record specifies the additional formatting information for a table. These records immediately follow a Feature11 or Feature12 record, and specify additional formatting information for the table specified by the Feature11 or Feature12 record. This record is a future record type record.
884
+ # @return [Hash]
885
+ def r_list12
886
+ @serial = true
887
+
888
+ frth_data = @bytes.read(12)
889
+ lsd, id_list = @bytes.read(6).unpack('vV')
890
+
891
+ lsd_d = {
892
+ 0x00 => :List12BlockLevel, # rgb is a List12BlockLevel structure that specifies the table block-level formatting.
893
+ 0x01 => :List12TableStyleClientInfo, # rgb is a List12TableStyleClientInfo structure that specifies the table style.
894
+ 0x02 => :List12DisplayName # rgb is a List12DisplayName structure that specifies the display name.
895
+ }[lsd]
896
+
897
+ method_name = lsd_d.to_s.downcase.to_sym
898
+
899
+ result = {
900
+ frtHeader: Unxls::Biff8::Structure.frtheader(frth_data), # frtHeader (12 bytes): An FrtHeader structure. The frtHeader.rt field MUST be 0x0890.
901
+ lsd: lsd, # lsd (2 bytes): An unsigned integer that specifies the type of data contained in the rgb field. MUST be a value specified in the table listed under rgb.
902
+ lsd_d: lsd_d,
903
+ idList: id_list, # idList (4 bytes): An unsigned integer that identifies the associated table for which this record specifies additional formatting. MUST NOT be zero. MUST be equal to the idList field of the TableFeatureType structure embedded in the associated Feature11 or Feature12 record.
904
+ }
905
+
906
+ result[:rgb] = Unxls::Biff8::Structure.send(method_name, @bytes)
907
+
908
+ result
909
+ end
910
+
911
+ # 2.4.168 MergeCells
912
+ # The MergeCells record specifies merged cells in the document. If the count of the merged cells in the document is greater than 1026, the file will contain multiple adjacent MergeCells records.
913
+ # @return [Hash]
914
+ def r_mergecells
915
+ @serial = true
916
+
917
+ result = {
918
+ cmcs: @bytes.read(2).unpack('v').first, # cmcs (2 bytes): An unsigned integer that specifies the count of Ref8 structures. MUST be less than or equal to 1026.
919
+ rgref: []
920
+ }
921
+
922
+ result[:cmcs].times do
923
+ result[:rgref] << Unxls::Biff8::Structure.ref8(@bytes.read(8)) # rgref (variable): An array of Ref8 structures. Each array element specifies a range of cells that are merged into a single merged cell. These ranges MUST NOT overlap. MUST contain the number of elements specified by cmcs.
924
+ end
925
+
926
+ result
927
+ end
928
+
929
+ # 2.4.174 MulBlank
930
+ # The MulBlank record specifies a series of blank cells in a sheet row. This record can store up to 256 IXFCell structures.
931
+ # @return [Hash]
932
+ def r_mulblank
933
+ @serial = true
934
+
935
+ rw, col_first = @bytes.read(4).unpack('vv')
936
+ rgixfe_collast_data = @bytes.read # rest of the record
937
+ col_last = rgixfe_collast_data[-2..-1].unpack('v').first # last 2 bytes
938
+ rgixfe = rgixfe_collast_data[0..-3].unpack('v*') # all except last 2 bytes
939
+
940
+ {
941
+ rw: rw, # rw (2 bytes): An Rw structure that specifies a row containing the blank cells.
942
+ colFirst: col_first, # colFirst (2 bytes): A Col structure that specifies the first column in the series of blank cells within the sheet. The value of colFirst.col MUST be less than or equal to 254.
943
+ colLast: col_last, # colLast (2 bytes): A Col structure that specifies the last column in the series of blank cells within the sheet. This colLast.col value MUST be greater than colFirst.col value.
944
+ rgixfe: rgixfe # rgixfe (variable): An array of IXFCell structures. Each element of this array contains an IXFCell structure corresponding to a blank cell in the series. The number of entries in the array MUST be equal to the value given by the following formula: Number of entries in rgixfe = (colLast.col – colFirst.col +1)
945
+ }
946
+ end
947
+
948
+ # 2.4.175 MulRk
949
+ # The MulRk record specifies a series of cells with numeric data in a sheet row. This record can store up to 256 RkRec structures.
950
+ # @return [Hash]
951
+ def r_mulrk
952
+ @serial = true
953
+
954
+ rw, col_first = @bytes.read(4).unpack('vv')
955
+ rgrkrec_collast_data = @bytes.read # rest of the record
956
+ col_last = rgrkrec_collast_data[-2..-1].unpack('v').first # last 2 bytes
957
+
958
+ result = {
959
+ rw: rw, # rw (2 bytes): An Rw structure that specifies the row containing the cells with numeric data.
960
+ colFirst: col_first, # colFirst (2 bytes): A Col structure that specifies the first column in the series of numeric cells within the sheet. The value of colFirst.col MUST be less than or equal to 254.
961
+ colLast: col_last, # colLast (2 bytes): A Col structure that specifies the last column in the set of numeric cells within the sheet. This colLast.col value MUST be greater than the colFirst.col value.
962
+ rgrkrec: [] # rgrkrec (variable): An array of RkRec structures. Each element in the array specifies an RkRec in the row. The number of entries in the array MUST be equal to the value given by the following formula: Number of entries in rgrkrec = (colLast.col – colFirst.col +1)
963
+ }
964
+
965
+ rgrkrec_array_data_io = rgrkrec_collast_data[0..-3].to_sio # all except last 2 bytes
966
+ while (rkrec = rgrkrec_array_data_io.read(6))
967
+ result[:rgrkrec] << Unxls::Biff8::Structure.rkrec(rkrec)
968
+ end
969
+
970
+ result
971
+ end
972
+
973
+ # 2.4.179 Note
974
+ # The Note record specifies a comment associated with a cell or revision information about a comment associated with a cell.
975
+ # @return [Hash]
976
+ def r_note
977
+ @serial = true
978
+
979
+ Unxls::Biff8::Structure.notesh(@bytes)
980
+ end
981
+
982
+ # 2.4.180 Number
983
+ # The Number record specifies a cell that contains a floating-point number.
984
+ # @return [Hash]
985
+ def r_number
986
+ @serial = true
987
+
988
+ {
989
+ **Unxls::Biff8::Structure.cell(@bytes.read(6)), # cell (6 bytes): A Cell structure that specifies the cell.
990
+ num: Unxls::Biff8::Structure.xnum(@bytes.read(8)) # num (8 bytes): An Xnum (section 2.5.342) value that specifies the cell value. @todo ChartNumNillable
991
+ }
992
+ end
993
+
994
+ # 2.4.181 Obj
995
+ # The Obj record specifies the properties of an object in a sheet.
996
+ # @todo May be Continued-ed (!!!)
997
+ # @return [Hash]
998
+ def r_obj
999
+ @serial = true
1000
+
1001
+ {
1002
+ cmo: Unxls::Biff8::Structure.ftcmo(@bytes.read(22)), # cmo (22 bytes): An FtCmo structure that specifies the common properties of this object.
1003
+ _other_fields: :not_implemented # @todo
1004
+ }
1005
+ end
1006
+
1007
+ # @todo May be Continue-ed (!!!)
1008
+ # 2.4.192 PhoneticInfo
1009
+ # The PhoneticInfo record specifies the default format for phonetic strings and the ranges of cells on the sheet that have phonetic strings that are visible.
1010
+ # @return [Hash]
1011
+ def r_phoneticinfo
1012
+ {
1013
+ phs: Unxls::Biff8::Structure.phs(@bytes.read(4)), # phs (4 bytes): A Phs structure that specifies the default format for phonetic strings on the sheet. When a phonetic string is entered into a cell that does not already contain a phonetic string, the default format is applied to the phonetic string.
1014
+ sqref: Unxls::Biff8::Structure.sqref(@bytes) # sqref (variable): An SqRef structure that specifies the ranges of cells on the sheet that have phonetic strings that are visible.
1015
+ }
1016
+ end
1017
+
1018
+ # 2.4.220 RK
1019
+ # The RK record specifies the numeric data contained in a single cell.
1020
+ # @return [Hash]
1021
+ def r_rk
1022
+ @serial = true
1023
+
1024
+ rw, col = @bytes.read(4).unpack('vv')
1025
+
1026
+ {
1027
+ rw: rw, # rw (2 bytes): An Rw structure that specifies a row index.
1028
+ col: col, # col (2 bytes): A Col structure that specifies a column index.
1029
+ **Unxls::Biff8::Structure.rkrec(@bytes.read(6)) # rkrec (6 bytes): An RkRec structure that specifies the numeric data for a single cell.
1030
+ }
1031
+ end
1032
+
1033
+ # 2.4.221 Row, page 377
1034
+ # The Row record specifies a single row on a sheet.
1035
+ # @return [Hash]
1036
+ def r_row
1037
+ @serial = true
1038
+
1039
+ rw, col_mic, col_mac, miy_rw, _, attrs = @bytes.read.unpack('vvvvVV')
1040
+ attrs = Unxls::BitOps.new(attrs)
1041
+
1042
+ {
1043
+ rw: rw, # rw (2 bytes): A Rw (2.5.227) structure that specifies the row index.
1044
+ colMic: col_mic, # colMic (2 bytes): An unsigned integer that specifies the zero-based index of the first column that contains a cell populated with data or formatting in the current row. MUST be less than or equal to 255.
1045
+ colMac: col_mac, # colMac (2 bytes): An unsigned integer that specifies the one-based index of the last column that contains a cell populated with data or formatting in the current row. MUST be less than or equal to 256. If colMac is equal to colMic, this record specifies a row with no CELL records.
1046
+ miyRw: miy_rw, # miyRw (2 bytes): An unsigned integer that specifies the row height in twips. If fDyZero is 1, the row is hidden and the value of miyRw specifies the original row height. MUST be greater than or equal to 2 and MUST be less than or equal to 8192.
1047
+ # reserved1 (2 bytes): MUST be zero, and MUST be ignored.
1048
+ # unused1 (2 bytes): Undefined and MUST be ignored.
1049
+ iOutLevel: attrs.value_at(0..2), # A - iOutLevel (3 bits): An unsigned integer that specifies the outline level of the row.
1050
+ # 3 B - reserved2 (1 bit): MUST be zero, and MUST be ignored.
1051
+ fCollapsed: attrs.set_at?(4), # C - fCollapsed (1 bit): A bit that specifies whether the rows that are one level of outlining deeper than the current row are included in the collapsed outline state.
1052
+ fDyZero: attrs.set_at?(5), # D - fDyZero (1 bit): A bit that specifies whether the row is hidden.
1053
+ fUnsynced: attrs.set_at?(6), # E - fUnsynced (1 bit): A bit that specifies whether the row height was manually set.
1054
+ fGhostDirty: attrs.set_at?(7), # F - fGhostDirty (1 bit): A bit that specifies whether the row was formatted.
1055
+ # 8…15 reserved3 (1 byte): MUST be 1, and MUST be ignored.
1056
+ ixfe: attrs.value_at(16..27), # index of XF record of row formatting
1057
+ fExAsc: attrs.set_at?(28), # G - fExAsc (1 bit): A bit that specifies whether any cell in the row has a thick top border, or any cell in the row directly above the current row has a thick bottom border. Thick borders are specified by the following enumeration values from BorderStyle: THICK and DOUBLE.
1058
+ fExDes: attrs.set_at?(29), # H - fExDes (1 bit): A bit that specifies whether any cell in the row has a medium or thick bottom border, or any cell in the row directly below the current row has a medium or thick top border. Thick borders are previously specified. Medium borders are specified by the following enumeration values from BorderStyle: MEDIUM, MEDIUMDASHED, MEDIUMDASHDOT, MEDIUMDASHDOTDOT, and SLANTDASHDOT.
1059
+ fPhonetic: attrs.set_at?(30), # I - fPhonetic (1 bit): A bit that specifies whether the phonetic guide feature is enabled for any cell in this row.
1060
+ # J - unused2 (1 bit): Undefined and MUST be ignored.
1061
+ }
1062
+ end
1063
+
1064
+ # @todo May be Continue-ed (!!!)
1065
+ # 2.4.268 String
1066
+ # The String record specifies the string value of a formula (section 2.2.2).
1067
+ # @return [Hash]
1068
+ def r_string
1069
+ @serial = true
1070
+
1071
+ {
1072
+ string: Unxls::Biff8::Structure.xlunicodestring(@bytes.read) # string (variable): An XLUnicodeString structure that specifies the string value of a formula (section 2.2.2). The value of string.cch MUST be less than or equal to 32767.
1073
+ }
1074
+ end
1075
+
1076
+ # 2.4.313 SxView
1077
+ # The SxView record specifies PivotTable view information and that specifies the beginning of a collection of records as defined by the Worksheet substream ABNF. The collection specifies the remainder of the PivotTable view.
1078
+ # @return [Hash]
1079
+ def r_sxview
1080
+ @serial = true
1081
+
1082
+ ref = Unxls::Biff8::Structure.ref8u(@bytes.read(8))
1083
+ rw_first_head, rw_first_data, col_first_data, i_cache, _, sxaxis4_data = @bytes.read(12).unpack('vvvs<vv')
1084
+
1085
+ result = {
1086
+ ref: ref, # ref (8 bytes): A Ref8U structure that specifies the PivotTable report body. For more information, see Location and Body.
1087
+ rwFirstHead: rw_first_head, # rwFirstHead (2 bytes): An RwU structure that specifies the first row of the row area. MUST be 1 if none of the axes are assigned in this PivotTable view. Otherwise, the value MUST be greater than or equal to ref.rwFirst.
1088
+ rwFirstData: rw_first_data, # rwFirstData (2 bytes): An RwU structure that specifies the first row of the data area. MUST be 1 if none of the axes are assigned in this PivotTable view. Otherwise, it MUST be equal to the value as specified by the following formula: rwFirstData = rwFirstHead + cDimCol
1089
+ colFirstData: col_first_data, # colFirstData (2 bytes): A ColU structure that specifies the first column of the data area. It MUST be 1 if none of the axes are assigned in this PivotTable view. Otherwise, the value MUST be greater than or equal to ref.colFirst, and if the value of cDimCol or cDimData is not zero, it MUST be less than or equal to ref.colLast.
1090
+ iCache: i_cache, # iCache (2 bytes): A signed integer that specifies the zero-based index of an SXStreamID record in the Globals Substream. See Associated PivotCache for more information. MUST be greater than or equal to zero and less than the number of SXStreamID records in the Globals Substream.
1091
+ # reserved (2 bytes): MUST be zero, and MUST be ignored.
1092
+ sxaxis4Data: Unxls::Biff8::Structure.sxaxis4data(sxaxis4_data) # sxaxis4Data (2 bytes): An SXAxis structure that specifies the default axis for the data field. Either the sxaxis4Data.sxaxisRw field MUST be 1 or the sxaxis4Data.sxaxisCol field MUST be 1. The sxaxis4Data.sxaxisPage field MUST be 0 and the sxaxis4Data.sxaxisData field MUST be 0.
1093
+ }
1094
+ ipos4_data, c_dim, c_dim_rw, c_dim_col, c_dim_pg, c_dim_data, c_rw, c_col = @bytes.read(16).unpack('s<2v3s<v2')
1095
+
1096
+ result.merge!({
1097
+ ipos4Data: ipos4_data, # ipos4Data (2 bytes): A signed integer that specifies the row or column position for the data field in the PivotTable view. The sxaxis4Data field specifies whether this is a row or column position. MUST be greater than or equal to -1 and less than or equal to 0x7FFF. A value of -1 specifies the default position.
1098
+ cDim: c_dim, # cDim (2 bytes): A signed integer that specifies the number of pivot fields in the PivotTable view. MUST equal the number of Sxvd records following this record. MUST equal the number of fields in the associated PivotCache specified by iCache.
1099
+ cDimRw: c_dim_rw, # cDimRw (2 bytes): An unsigned integer that specifies the number of fields on the row axis of the PivotTable view. MUST be less than or equal to 0x7FFF. MUST equal the number of array elements in the SxIvd record in this PivotTable view that contain row items.
1100
+ cDimCol: c_dim_col, # cDimCol (2 bytes): An unsigned integer that specifies the number of fields on the column axis of the PivotTable view. MUST be less than or equal to 0x7FFF. MUST equal the number of array elements in the SxIvd record in this PivotTable view that contain column items.
1101
+ cDimPg: c_dim_pg, # cDimPg (2 bytes): An unsigned integer that specifies the number of page fields in the PivotTable view. MUST be less than or equal to 0x7FFF. MUST equal the number of array elements in the SXPI record in this PivotTable view.
1102
+ cDimData: c_dim_data, # cDimData (2 bytes): A signed integer that specifies the number of data fields in the PivotTable view. MUST be greater than or equal to zero and less than or equal to 0x7FFF. MUST equal the number of SXDI records in this PivotTable view.
1103
+ cRw: c_rw, # cRw (2 bytes): An unsigned integer that specifies the number of pivot lines in the row area of the PivotTable view. MUST be less than or equal to 0x7FFF. MUST equal the number of array elements in the first SXLI record in this PivotTable view.
1104
+ cCol: c_col, # cCol (2 bytes): An unsigned integer that specifies the number of pivot lines in the column area of the PivotTable view. MUST equal the number of array elements in the second SXLI record in this PivotTable view.
1105
+ })
1106
+
1107
+ attrs = Unxls::BitOps.new(@bytes.read(2).unpack('v').first)
1108
+
1109
+ result.merge!({
1110
+ fRwGrand: attrs.set_at?(0), # A - fRwGrand (1 bit): A bit that specifies whether the PivotTable contains grand totals for rows. MUST be 0 if none of the axes have been assigned in this PivotTable view.
1111
+ fColGrand: attrs.set_at?(1), # B - fColGrand (1 bit): A bit that specifies whether the PivotTable contains grand totals for columns. MUST be 1 if none of the axes are assigned in this PivotTable view.
1112
+ # C - unused1 (1 bit): Undefined and MUST be ignored.
1113
+ fAutoFormat: attrs.set_at?(3), # D - fAutoFormat (1 bit): A bit that specifies whether the PivotTable has AutoFormat applied.
1114
+ fAtrNum: attrs.set_at?(4), # E - fAtrNum (1 bit): A bit that specifies whether the PivotTable has number AutoFormat applied.
1115
+ fAtrFnt: attrs.set_at?(5), # F - fAtrFnt (1 bit): A bit that specifies whether the PivotTable has font AutoFormat applied.
1116
+ fAtrAlc: attrs.set_at?(6), # G - fAtrAlc (1 bit): A bit that specifies whether the PivotTable has alignment AutoFormat applied.
1117
+ fAtrBdr: attrs.set_at?(7), # H - fAtrBdr (1 bit): A bit that specifies whether the PivotTable has border AutoFormat applied.
1118
+ fAtrPat: attrs.set_at?(8), # I - fAtrPat (1 bit): A bit that specifies whether the PivotTable has pattern AutoFormat applied.
1119
+ fAtrProc: attrs.set_at?(9), # J - fAtrProc (1 bit): A bit that specifies whether the PivotTable has width/height AutoFormat applied.
1120
+ # unused2 (6 bits): Undefined and MUST be ignored.
1121
+ })
1122
+
1123
+ itbl_auto_fmt, cch_table_name, cch_data_name = @bytes.read(6).unpack('vvv')
1124
+
1125
+ result.merge!({
1126
+ itblAutoFmt: Unxls::Biff8::Structure.autofmt8(itbl_auto_fmt), # itblAutoFmt (2 bytes): An AutoFmt8 structure that specifies the PivotTable AutoFormat. If the value of itblAutoFmt in the associated SXViewEx9 record is not 1, this field is overridden by the value of itblAutoFmt in the associated SXViewEx9.
1127
+ cchTableName: cch_table_name, # cchTableName (2 bytes): An unsigned integer that specifies the length, in characters, of stTable. MUST be greater than or equal to zero and less than or equal to 0x00FF.
1128
+ cchDataName: cch_data_name, # cchDataName (2 bytes): An unsigned integer that specifies the length, in characters of stData. MUST be greater than zero and less than or equal to 0x00FE.
1129
+ stTable: Unxls::Biff8::Structure.xlunicodestringnocch(@bytes, cch_table_name), # stTable (variable): An XLUnicodeStringNoCch structure that specifies the name of the PivotTable. The length of this field is specified by cchTableName.
1130
+ stData: Unxls::Biff8::Structure.xlunicodestringnocch(@bytes, cch_data_name) # stData (variable): An XLUnicodeStringNoCch structure that specifies the name of the data field. The length of this field is specified by cchDataName.
1131
+ })
1132
+ end
1133
+
1134
+ # 2.4.315 SXViewEx9
1135
+ # The SXViewEx9 record specifies extensions to the PivotTable view.
1136
+ # @return [Hash]
1137
+ def r_sxviewex9
1138
+ @serial = true
1139
+
1140
+ result = { frtHeader: Unxls::Biff8::Structure.frtheader(@bytes.read(8)) }
1141
+
1142
+ attrs, itbl_auto_fmt = @bytes.read(6).unpack('Vv')
1143
+ attrs = Unxls::BitOps.new(attrs)
1144
+ result.merge!({
1145
+ # C - reserved4 (1 bit): MUST be zero, and MUST be ignored.
1146
+ fPrintTitles: attrs.set_at?(1), # D - fPrintTitles (1 bit): A bit that specifies whether the print titles for the worksheet are set based on the PivotTable report. The row print titles are set to the pivot item captions on the column axis and the column print titles are set to the pivot item captions on the row axis.
1147
+ fLineMode: attrs.set_at?(2), # E - fLineMode (1 bit): A bit that specifies whether any pivot field is in outline mode. See Subtotalling for more information.
1148
+ # F - reserved5 (2 bits): MUST be zero, and MUST be ignored.
1149
+ fRepeatItemsOnEachPrintedPage: attrs.set_at?(5), # G - fRepeatItemsOnEachPrintedPage (1 bit): A bit that specifies whether pivot item captions on the row axis are repeated on each printed page for pivot fields in tabular form.
1150
+ # reserved6 (26 bits): MUST be zero, and MUST be ignored.
1151
+ itblAutoFmt: Unxls::Biff8::Structure.autofmt8(itbl_auto_fmt), # itblAutoFmt (2 bytes): An AutoFmt8 structure that specifies the PivotTable AutoFormat. If the value of this field is not 1, this field overrides the itblAutoFmt field in the previous SxView record.
1152
+ chGrand: Unxls::Biff8::Structure.xlunicodestring(@bytes) # chGrand (variable): An XLUnicodeString structure that specifies a user-entered caption to display for grand totals when the PivotTable is recalculated. The length MUST be less than or equal to 255 characters.
1153
+ })
1154
+ end
1155
+
1156
+ # 2.4.329 TxO
1157
+ # @todo Check: may be Continued-ed
1158
+ # The TxO record specifies the text in a text box or a form control.
1159
+ # @return [Hash]
1160
+ def r_txo
1161
+ @serial = true
1162
+
1163
+ attrs, rot = @bytes.read(4).unpack('vv')
1164
+ attrs = Unxls::BitOps.new(attrs)
1165
+
1166
+ # A - reserved1 (1 bit): MUST be zero, and MUST be ignored.
1167
+
1168
+ h_alignment = attrs.value_at(1..3) # B - hAlignment (3 bits): An unsigned integer that specifies the horizontal alignment.
1169
+ h_alignment_d = {
1170
+ 1 => :left,
1171
+ 2 => :centered,
1172
+ 3 => :right,
1173
+ 4 => :justify,
1174
+ 7 => :justify_distributed
1175
+ }.freeze[h_alignment]
1176
+
1177
+ v_alignment = attrs.value_at(4..6) # C - vAlignment (3 bits): An unsigned integer that specifies the vertical alignment.
1178
+ v_alignment_d = {
1179
+ 1 => :top,
1180
+ 2 => :middle,
1181
+ 3 => :bottom,
1182
+ 4 => :justify,
1183
+ 7 => :justify_distributed
1184
+ }.freeze[v_alignment]
1185
+
1186
+ result = {
1187
+ hAlignment: h_alignment,
1188
+ hAlignment_d: h_alignment_d,
1189
+ vAlignment: v_alignment,
1190
+ vAlignment_d: v_alignment_d,
1191
+ # D - reserved2 (2 bits): MUST be zero, and MUST be ignored.
1192
+ fLockText: attrs.set_at?(9), # E - fLockText (1 bit): A bit that specifies whether the text is locked.
1193
+ # F - reserved3 (4 bits): MUST be zero, and MUST be ignored.
1194
+ fJustLast: attrs.set_at?(14), # G - fJustLast (1 bit): A bit that specifies whether the justify alignment or justify distributed alignment is used on the last line of the text in specific versions of the application.
1195
+ fSecretEdit: attrs.set_at?(15), # H - fSecretEdit (1 bit): A bit that specifies whether this is a text box used for typing passwords and hiding the actual characters being typed by the user.
1196
+ }
1197
+
1198
+ result[:rot] = rot # rot (2 bytes): An unsigned integer that specifies the orientation of the text within the object boundary.
1199
+ result[:rot_d] = {
1200
+ 0 => :none, # Specifies no rotation
1201
+ 1 => :stacked, # Specifies stacked or vertical orientation
1202
+ 2 => :ccw_90, # Specifies 90-degree counter-clockwise rotation
1203
+ 3 => :cw_90, # Specifies 90-degree clockwise rotation
1204
+ }.freeze[rot]
1205
+
1206
+ control_obj_types = Set[0, 5, 7, 11, 12, 14].freeze # Group, Chart, Button, Checkbox, Radio button, Label
1207
+
1208
+ if (preceding_obj_record = @stream.last_parsed[:Obj])
1209
+ @bytes.read(6) # reserved4 (2 bytes), reserved5 (4 bytes): MUST be zero and MUST be ignored. This field MUST exist if and only if the value of cmo.ot in the preceding Obj record is not 0, 5, 7, 11, 12 or 14.
1210
+ if control_obj_types.include?(preceding_obj_record.last[:cmo][:ot])
1211
+ result[:controlInfo] = :not_implemented # controlInfo (6 bytes): An optional ControlInfo (2.5.61) structure that specifies the properties for some form controls. The field MUST exist if and only if the value of cmo.ot in the preceding Obj record is 0, 5, 7, 11, 12, or 14.
1212
+ end
1213
+ end
1214
+
1215
+ result[:cchText] = @bytes.read(2).unpack('v').first # cchText (2 bytes): An unsigned integer that specifies the number of characters in the text string contained in the Continue records immediately following this record.
1216
+ result[:cbRuns] = @bytes.read(2).unpack('v').first # cbRuns (2 bytes): An unsigned integer that specifies the number of bytes of formatting run information in the TxORuns structure contained in the Continue records following this record. If cchText is 0, this value MUST be 0. Otherwise, the value MUST be greater than or equal to 16 and MUST be a multiple of 8.
1217
+ result[:ifntEmpty] = @bytes.read(2).unpack('v').first # ifntEmpty (2 bytes): A FontIndex structure that specifies the font when the value of cchText is 0.
1218
+
1219
+ @bytes.read # skip the rest of the record
1220
+ result[:fmla] = :not_implemented # fmla (variable): An ObjFmla (2.5.187) structure that specifies the parsed expression of the formula (section 2.2.2) for the text.
1221
+
1222
+ # see 2.5.296 XLUnicodeStringNoCch
1223
+ if result[:cchText] > 0
1224
+ open_next_record_block
1225
+ high_byte = Unxls::BitOps.new(@bytes.read(1).unpack('C').first).set_at?(0)
1226
+ result[:text_string] = Unxls::Biff8::Structure._read_continued_string(self, result[:cchText], high_byte)
1227
+ end
1228
+
1229
+ # see 2.5.272 TxORuns
1230
+ num_of_formatting_runs = result[:cbRuns] / 8 - 1
1231
+ if num_of_formatting_runs > 0
1232
+ result[:formatting_runs] = []
1233
+ num_of_formatting_runs.times do
1234
+ open_next_record_block if @bytes.eof?
1235
+ result[:formatting_runs] << Unxls::Biff8::Structure.run(@bytes)
1236
+ break if end_of_data?
1237
+ end
1238
+ end
1239
+
1240
+ result
1241
+ end
1242
+
1243
+ # 2.4.351 WsBool
1244
+ # The WsBool record specifies information about a sheet.
1245
+ # @return [Hash]
1246
+ def r_wsbool
1247
+ attrs = @bytes.read.unpack('v').first
1248
+ attrs = Unxls::BitOps.new(attrs)
1249
+
1250
+ {
1251
+ fShowAutoBreaks: attrs.set_at?(0), # A - fShowAutoBreaks (1 bit): A bit that specifies whether page breaks inserted automatically are visible on the sheet.
1252
+ # B - reserved1 (3 bits): MUST be zero, and MUST be ignored.
1253
+ fDialog: attrs.set_at?(4), # C - fDialog (1 bit): A bit that specifies whether the sheet is a dialog sheet.
1254
+ fApplyStyles: attrs.set_at?(5), # D - fApplyStyles (1 bit): A bit that specifies whether to apply styles in an outline when an outline is applied.
1255
+ fRowSumsBelow: attrs.set_at?(6), # E - fRowSumsBelow (1 bit): A bit that specifies whether summary rows appear below an outline's detail rows.
1256
+ fColSumsRight: attrs.set_at?(7), # F - fColSumsRight (1 bit): A bit that specifies whether summary columns appear to the right or left of an outline's detail columns.
1257
+ fFitToPage: attrs.set_at?(8), # G - fFitToPage (1 bit): A bit that specifies whether to fit the printable contents to a single page when printing this sheet.
1258
+ # H - reserved2 (1 bit): MUST be zero, and MUST be ignored.
1259
+ # I - unused (2 bits): Undefined and MUST be ignored.
1260
+ fSyncHoriz: attrs.set_at?(12), # J - fSyncHoriz (1 bit): A bit that specifies whether horizontal scrolling is synchronized across multiple windows displaying this sheet.
1261
+ fSyncVert: attrs.set_at?(13), # K - fSyncVert (1 bit): A bit that specifies whether vertical scrolling is synchronized across multiple windows displaying this sheet.
1262
+ fAltExprEval: attrs.set_at?(14), # L - fAltExprEval (1 bit): A bit that specifies whether the sheet uses transition formula evaluation.
1263
+ fFormulaEntry: attrs.set_at?(15), # M - fAltFormulaEntry (1 bit): A bit that specifies whether the sheet uses transition formula entry.
1264
+ }
1265
+ end
1266
+
1267
+ end