unxls 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,2740 @@
1
+ # frozen_string_literal: true
2
+
3
+ # 2.5.198 Parsed Expressions
4
+ module Unxls::Biff8::ParsedExpressions
5
+ extend self
6
+
7
+ # 2.5.198.2 BErr
8
+ # @param id [Integer]
9
+ # @return [Symbol]
10
+ def berr(id)
11
+ {
12
+ 0x00 => :'NULL!',
13
+ 0x07 => :'DIV/0!',
14
+ 0x0F => :'VALUE!',
15
+ 0x17 => :'REF!',
16
+ 0x1D => :'NAME?',
17
+ 0x24 => :'NUM!',
18
+ 0x2A => :'N/A',
19
+ }[id]
20
+ end
21
+ end
22
+
23
+ # Structure-processing methods
24
+ #
25
+ # +method naming+
26
+ # processing methods for structures from the 2.5 list: <structure name downcase>, i.e. 'cellxf'
27
+ # helper methods for data structures not from the specification list: _<method name> i.e. '_encode_string'
28
+ module Unxls::Biff8::Structure
29
+ using Unxls::Helpers
30
+
31
+ extend self
32
+ extend Unxls::Biff8::ParsedExpressions
33
+
34
+ # 2.5.9 AutoFmt8
35
+ # The AutoFmt8 enumeration specifies the following auto formatting styles.
36
+ # @param value [Integer]
37
+ # @return [Symbol]
38
+ def autofmt8(value)
39
+ {
40
+ 0x0000 => :XL8_ITBLSIMPLE, # Simple
41
+ 0x0001 => :XL8_ITBLCLASSIC1, # Classic 1
42
+ 0x0002 => :XL8_ITBLCLASSIC2, # Classic 2
43
+ 0x0003 => :XL8_ITBLCLASSIC3, # Classic 3
44
+ 0x0004 => :XL8_ITBLACCOUNTING1, # Accounting 1
45
+ 0x0005 => :XL8_ITBLACCOUNTING2, # Accounting 2
46
+ 0x0006 => :XL8_ITBLACCOUNTING3, # Accounting 3
47
+ 0x0007 => :XL8_ITBLACCOUNTING4, # Accounting 4
48
+ 0x0008 => :XL8_ITBLCOLORFUL1, # Colorful 1
49
+ 0x0009 => :XL8_ITBLCOLORFUL2, # Colorful 2
50
+ 0x000A => :XL8_ITBLCOLORFUL3, # Colorful 3
51
+ 0x000B => :XL8_ITBLLIST1, # List 1
52
+ 0x000C => :XL8_ITBLLIST2, # List 2
53
+ 0x000D => :XL8_ITBLLIST3, # List 3
54
+ 0x000E => :XL8_ITBL3DEFFECTS1, # 3Deffects 1
55
+ 0x000F => :XL8_ITBL3DEFFECTS2, # 3Deffects 2
56
+ 0x0010 => :XL8_ITBLNONE_GEN, # None
57
+ 0x0011 => :XL8_ITBLJAPAN2, # Japan 2
58
+ 0x0012 => :XL8_ITBLJAPAN3, # Japan 3
59
+ 0x0013 => :XL8_ITBLJAPAN4, # Japan 4
60
+ 0x0014 => :XL8_ITBLNONE_JPN, # Japan None
61
+ 0x1000 => :XL8_ITBLREPORT1, # Report 1
62
+ 0x1001 => :XL8_ITBLREPORT2, # Report 2
63
+ 0x1002 => :XL8_ITBLREPORT3, # Report 3
64
+ 0x1003 => :XL8_ITBLREPORT4, # Report 4
65
+ 0x1004 => :XL8_ITBLREPORT5, # Report 5
66
+ 0x1005 => :XL8_ITBLREPORT6, # Report 6
67
+ 0x1006 => :XL8_ITBLREPORT7, # Report 7
68
+ 0x1007 => :XL8_ITBLREPORT8, # Report 8
69
+ 0x1008 => :XL8_ITBLREPORT9, # Report 9
70
+ 0x1009 => :XL8_ITBLREPORT10, # Report 10
71
+ 0x100A => :XL8_ITBLTABLE1, # Table 1
72
+ 0x100B => :XL8_ITBLTABLE2, # Table 2
73
+ 0x100C => :XL8_ITBLTABLE3, # Table 3
74
+ 0x100D => :XL8_ITBLTABLE4, # Table 4
75
+ 0x100E => :XL8_ITBLTABLE5, # Table 5
76
+ 0x100F => :XL8_ITBLTABLE6, # Table 6
77
+ 0x1010 => :XL8_ITBLTABLE7, # Table 7
78
+ 0x1011 => :XL8_ITBLTABLE8, # Table 8
79
+ 0x1012 => :XL8_ITBLTABLE9, # Table 9
80
+ 0x1013 => :XL8_ITBLTABLE10, # Table 10
81
+ 0x1014 => :XL8_ITBLPTCLASSIC, # Table PTClassic
82
+ 0x1015 => :XL8_ITBLPTNONE, # None
83
+ }[value]
84
+ end
85
+
86
+ # 2.5.10 Bes
87
+ # The Bes structure specifies either a Boolean (section 2.5.14) value or an error value.
88
+ # @param data [String]
89
+ # @return [Hash]
90
+ def bes(data)
91
+ b_bool_err, f_error = data.unpack('CC')
92
+
93
+ b_bool_err_d = case f_error
94
+ when 0
95
+ {
96
+ 0x00 => :False,
97
+ 0x01 => :True
98
+ }
99
+
100
+ when 1
101
+ {
102
+ 0x00 => :'#NULL!',
103
+ 0x07 => :'#DIV/0!',
104
+ 0x0F => :'#VALUE!',
105
+ 0x17 => :'#REF!',
106
+ 0x1D => :'#NAME?',
107
+ 0x24 => :'#NUM!',
108
+ 0x2A => :'#N/A',
109
+ 0x2B => :'#GETTING_DATA',
110
+ }
111
+
112
+ else
113
+ raise "Unexpected fError value #{f_error} in Bes structure"
114
+
115
+ end
116
+
117
+ {
118
+ bBoolErr: b_bool_err, # bBoolErr (1 byte): An unsigned integer that specifies either a Boolean value or an error value, depending on the value of fError.
119
+ bBoolErr_d: b_bool_err_d[b_bool_err],
120
+ fError: f_error # fError (1 byte): A Boolean that specifies whether bBoolErr contains an error code or a Boolean value.
121
+ }
122
+ end
123
+
124
+ # @param data [String]
125
+ # @return [true, false]
126
+ def _bool1b(data)
127
+ _int1b(data) == 1
128
+ end
129
+
130
+ # 2.5.11 Bold,
131
+ # Also see 2.5.248 Stxp
132
+ # @param id [Integer]
133
+ # @return [Symbol]
134
+ def bold(id)
135
+ {
136
+ 0x0190 => :BLSNORMAL, # Normal font weight
137
+ 0x02BC => :BLSBOLD, # Bold font weight
138
+ 0xFFFF => :ignored # Indicates that this specification is to be ignored
139
+ }[id]
140
+ end
141
+
142
+ # 2.5.15 BorderStyle
143
+ # @param id [Integer]
144
+ # @return [Symbol]
145
+ def borderstyle(id)
146
+ {
147
+ 0x0000 => :NONE, # No border
148
+ 0x0001 => :THIN, # Thin line
149
+ 0x0002 => :MEDIUM, # Medium line
150
+ 0x0003 => :DASHED, # Dashed line
151
+ 0x0004 => :DOTTED, # Dotted line
152
+ 0x0005 => :THICK, # Thick line
153
+ 0x0006 => :DOUBLE, # Double line
154
+ 0x0007 => :HAIR, # Hairline
155
+ 0x0008 => :MEDIUMDASHED, # Medium dashed line
156
+ 0x0009 => :DASHDOT, # Dash-dot line
157
+ 0x000A => :MEDIUMDASHDOT, # Medium dash-dot line
158
+ 0x000B => :DASHDOTDOT, # Dash-dot-dot line
159
+ 0x000C => :MEDIUMDASHDOTDOT, # Medium dash-dot-dot line
160
+ 0x000D => :SLANTEDDASHDOTDOT, # Slanted dash-dot-dot line
161
+ }[id]
162
+ end
163
+
164
+ # 2.5.16 BuiltInStyle
165
+ # The BuiltInStyle structure specifies the type of a built-in cell style. For row outline and column outline types this structure also specifies the outline level of the style.
166
+ # @param data [String]
167
+ # @return [Hash]
168
+ def builtinstyle(data)
169
+ isty_built_in, i_level = data.unpack('CC')
170
+
171
+ name_base = Unxls::Biff8::Constants::BUILTIN_STYLES[isty_built_in]
172
+ name = (1..2).include?(isty_built_in) ? "#{name_base}#{i_level + 1}".to_sym : name_base
173
+
174
+ {
175
+ istyBuiltIn: isty_built_in, # istyBuiltIn (1 byte): An unsigned integer that specifies the type of the built-in cell style.
176
+ iLevel: i_level, # iLevel (1 byte): An unsigned integer that specifies the depth level of row/column automatic outlining.
177
+ istyBuiltIn_d: name
178
+ }
179
+ end
180
+
181
+ # See 2.5.282 XFIndex, p. 885
182
+ # @param value [Integer]
183
+ # @return [Symbol]
184
+ def _builtin_xf_description(value)
185
+ {
186
+ 0 => :'Normal style', # fStyle value == 1
187
+ 1 => :'Row outline level 1', # 1
188
+ 2 => :'Row outline level 2', # 1
189
+ 3 => :'Row outline level 3', # 1
190
+ 4 => :'Row outline level 4', # 1
191
+ 5 => :'Row outline level 5', # 1
192
+ 6 => :'Row outline level 6', # 1
193
+ 7 => :'Row outline level 7', # 1
194
+ 8 => :'Column outline level 1', # 1
195
+ 9 => :'Column outline level 2', # 1
196
+ 10 => :'Column outline level 3', # 1
197
+ 11 => :'Column outline level 4', # 1
198
+ 12 => :'Column outline level 5', # 1
199
+ 13 => :'Column outline level 6', # 1
200
+ 14 => :'Column outline level 7', # 1
201
+ 15 => :'Default cell format', # 0
202
+ }[value]
203
+ end
204
+
205
+ # 2.5.17 CachedDiskHeader
206
+ # The CachedDiskHeader structure specifies the formatting information of a table column heading.
207
+ # @param io [StringIO]
208
+ # @param f_save_style_name [true, false]
209
+ # @return [Hash]
210
+ def cacheddiskheader(io, f_save_style_name)
211
+ cbdxf_hdr_disk = io.read(4).unpack('V').first
212
+
213
+ result = {
214
+ cbdxfHdrDisk: cbdxf_hdr_disk, # cbdxfHdrDisk (4 bytes): An unsigned integer that specifies the size, in bytes, of the rgHdrDisk field.
215
+ rgHdrDisk: dxfn12list(io.read(cbdxf_hdr_disk)) # rgHdrDisk (variable): A DXFN12List structure that specifies the formatting of the column heading.
216
+ }
217
+
218
+ # If present, the formatting as specified by strStyleName is applied first, before the formatting as specified by rgHdrDisk is applied.
219
+ result[:strStyleName] = xlunicodestring(io) if f_save_style_name # strStyleName (variable): An XLUnicodeString that specifies the name of the style to use for the column heading. The name of the style MUST equal the user field of a Style record in the Globals Substream ABNF, or the name of a built-in style, as specified by the BuiltInStyle record. This field is present only if the fSaveStyleName field of the containing Feat11FieldDataItem structure is set to 0x1.
220
+
221
+ result
222
+ end
223
+
224
+ # 2.5.19 Cell
225
+ # The Cell structure specifies a cell in the current sheet.
226
+ # @param data [String]
227
+ # @return [Hash]
228
+ def cell(data)
229
+ rw, col, ixfe = data.unpack('v3')
230
+
231
+ {
232
+ rw: rw, # rw (2 bytes): An Rw that specifies the row.
233
+ col: col, # col (2 bytes): A Col that specifies the column.
234
+ ixfe: ixfe # ixfe (2 bytes): An IXFCell that specifies the XF record.
235
+ }
236
+ end
237
+
238
+ # 2.5.20 CellXF, p. 597
239
+ # See also 2.5.91 DXFALC
240
+ # This structure specifies formatting properties for a cell.
241
+ # @param io [StringIO]
242
+ # @return [Hash]
243
+ def cellxf(io)
244
+ result = _xfalc(io)
245
+ result.delete(:fMergeCell)
246
+
247
+ result.merge!(_xfbdr(io))
248
+
249
+ attrs = io.read(2).unpack('v').first
250
+ attrs = Unxls::BitOps.new(attrs)
251
+ result[:icvFore] = attrs.value_at(0..6) # icvFore (7 bits): An IcvXF that specifies the foreground color of the fill pattern.
252
+ result[:icvBack] = attrs.value_at(7..13) # icvBack (7 bits): An unsigned integer that specifies the background color of the fill pattern.
253
+ result[:fsxButton] = attrs.set_at?(14) # fsxButton (1 bit): A bit that specifies whether the XF record is attached to a pivot field drop-down button.
254
+ # Q - reserved3 (1 bit): MUST be 0 and MUST be ignored.
255
+
256
+ result
257
+ end
258
+
259
+ # 2.5.27 CFExNonCF12
260
+ # The CFExNonCF12 structure specifies properties that extend a conditional formatting rule that is specified by a CF record.
261
+ # @param io [StringIO]
262
+ # @return [Hash]
263
+ def cfexnoncf12(io)
264
+ icf, cp, icf_template, ipriority, a_e_raw, f_has_dxf = io.read(8).unpack('vCCvCC')
265
+ a_e = Unxls::BitOps.new(a_e_raw)
266
+ result = {
267
+ icf: icf, # icf (2 bytes): An unsigned integer that specifies a zero-based index of a CF record in the collection of CF records directly following the CondFmt record that is referenced by the parent CFEx record with the nID field. The referenced CF specifies the conditional formatting rule to be extended.
268
+ cp: cp, # cp (1 byte): An unsigned integer that specifies the type of comparison operation to use when the ct field of the CF record referenced by the icf field of this structure is equal to 0x01.
269
+ cp_d: _cf_cp(cp),
270
+ icfTemplate: icf_template, # icfTemplate (1 byte): An unsigned integer that specifies the template from which the rule was created. MUST be the least significant byte of one of the valid values specified for the icfTemplate field in the CF12 record.
271
+ icfTemplate_d: _icf_template_d(icf_template),
272
+ 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.
273
+ fActive: a_e.set_at?(0), # A - fActive (1 bit): A bit that specifies whether the rule is active. If set to zero, the rule will be ignored.
274
+ fStopIfTrue: a_e.set_at?(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.
275
+ # C - reserved1 (1 bit): MUST be zero and MUST be ignored.
276
+ # D - unused (1 bit): Undefined and MUST be ignored.
277
+ # E - reserved2 (4 bits): MUST be zero and MUST be ignored.
278
+ fHasDXF: f_has_dxf, # fHasDXF (1 byte): A Boolean (section 2.5.14) that specifies whether cell formatting data is part of this record extension.
279
+ }
280
+
281
+ result[:dxf] = dxfn12(io) if f_has_dxf == 1
282
+
283
+ cb_template_parm = io.read(1).unpack('C').first
284
+ result[:cbTemplateParm] = cb_template_parm # cbTemplateParm (1 byte): An unsigned integer that specifies the size of the rgbTemplateParms field in bytes. MUST be 16.
285
+ result[:rgbTemplateParms] = cfextemplateparams(io.read(cb_template_parm), icf_template) # rgbTemplateParms (16 bytes): A CFExTemplateParams that specifies the parameters for the rule.
286
+
287
+ result
288
+ end
289
+
290
+ # 2.5.21 CFColor
291
+ # The CFColor structure specifies a color in conditional formatting records or in a SheetExt record.
292
+ # @param io [StringIO]
293
+ # @return [Hash]
294
+ def cfcolor(io)
295
+ xclr_type = io.read(4).unpack('V').first
296
+ type = xcolortype(xclr_type)
297
+
298
+ {
299
+ xclrType: xclr_type, # xclrType (4 bytes): An XColorType that specifies the type of color reference.
300
+ xclrType_d: type,
301
+ xclrValue: _interpret_xclr(type, io.read(4)), # xclrValue (4 bytes): A structure that specifies the color value.
302
+ numTint: xnum(io.read(8)) # numTint (8 bytes): An Xnum (section 2.5.342) that specifies the tint and shade value to be applied to the color.
303
+ }
304
+ end
305
+
306
+ # See cp value meaning in 2.4.43 CF12 and 2.4.42 CF
307
+ # @param value [Integer]
308
+ # @return [Hash]
309
+ def _cf_cp(value)
310
+ {
311
+ 0x00 => :'No comparison',
312
+ # v between v1 and v2 or strictly
313
+ # v2 is greater than or equal to v1, and v is greater than or equal to v1 and less than or equal to v2 –Or–
314
+ # v1 is greater than v2, and v is greater than or equal to v2 and less than or equal to v1
315
+ 0x01 => :'((v1 <= v2) && (v1 <= v <= v2)) || ((v2 < v1) && (v1 >= v >= v2))',
316
+ # v is not between v1 and v2 or strictly
317
+ # v2 is greater than or equal to v1, and v is less than v1 or greater than v2 –Or–
318
+ # v1 is greater than v2, and v is less than v2 or greater than v1
319
+ 0x02 => :'((v1 <= v2) && (v < v1 || v > v2)) || ((v1 > v2) && (v > v1 || v < v2))',
320
+ 0x03 => :'v == v1', # v is equal to v1
321
+ 0x04 => :'v != v1', # v is not equal to v1
322
+ 0x05 => :'v > v1', # v is greater than v1
323
+ 0x06 => :'v < v1', # v is less than v1
324
+ 0x07 => :'v >= v1', # v is greater than or equal to v1
325
+ 0x08 => :'v <= v1' # v is less than or equal to v1
326
+ }[value]
327
+ end
328
+
329
+ # 2.5.22 CFDatabar
330
+ # The CFDatabar structure specifies the parameters of a conditional formatting rule that uses data bar formatting.
331
+ # @param io [StringIO]
332
+ # @return [Hash]
333
+ def cfdatabar(io)
334
+ _, _, a_r2_raw, i_percent_min, i_percent_max = io.read(6).unpack('vCCCC')
335
+ a_r2 = Unxls::BitOps.new(a_r2_raw)
336
+
337
+ {
338
+ _type: :CFDatabar,
339
+ # unused (2 bytes): Undefined and MUST be ignored.
340
+ # reserved1 (1 byte): MUST be zero and MUST be ignored.
341
+ fRightToLeft: a_r2.set_at?(0), # A - fRightToLeft (1 bit): A bit that specifies whether the data bars are drawn starting from the right of the cell.
342
+ fShowValue: a_r2.set_at?(1), # B - fShowValue (1 bit): A bit that specifies whether the numerical value of the cell appears in the cell along with the data bar.
343
+ # reserved2 (6 bits): MUST be zero and MUST be ignored.
344
+ iPercentMin: i_percent_min, # iPercentMin (1 byte): An unsigned integer that specifies the length of a data bar, as a percentage of the cell width, that is applied to cells with values equal to the CFVO value specified by cfvoDB1.
345
+ iPercentMax: i_percent_max, # iPercentMax (1 byte): An unsigned integer that specifies the length of a data bar, as a percentage of the cell width, that is applied to cells with values equal to the CFVO value specified by cfvoDB2.
346
+ color: cfcolor(io), # color (16 bytes): A CFColor structure that specifies the color of the data bar.
347
+ cfvoDB1: cfvo(io), # cfvoDB1 (variable): A CFVO that specifies the maximum cell value that will be represented with a minimum width data bar. All cell values that are less than or equal to the CFVO value specified by this field are represented with a data bar of iPercentMin percent of the cell width.
348
+ cfvoDB2: cfvo(io) # cfvoDB2 (variable): A CFVO that specifies the minimum cell value that will be represented with a maximum width data bar. All cell values that are greater than or equal to the CFVO value specified by this field are represented with a data bar of iPercentMax percent of the cell width.
349
+ }
350
+ end
351
+
352
+ # 2.5.23 CFExAveragesTemplateParams
353
+ # This structure specifies the parameters for an above or below average conditional formatting rule in a containing CF12 record or CFExNonCF12 structure.
354
+ # @param data [String]
355
+ # @return [Hash]
356
+ def cfexaveragestemplateparams(data)
357
+ i_param, _ = data.unpack('vC*')
358
+ i_param_d = {
359
+ 0x0000 => :not_offset, # The threshold is not offset by a multiple of the standard deviation.
360
+ 0x0001 => :offset1, # The threshold is offset by 1 standard deviation.
361
+ 0x0002 => :offset2, # The threshold is offset by 2 standard deviations.
362
+ }[i_param]
363
+
364
+ {
365
+ _type: :CFExAveragesTemplateParams,
366
+ iParam: i_param, # iParam (2 bytes): An unsigned integer that specifies the number of standard deviations above or below the average for the rule.
367
+ iParam_d: i_param_d,
368
+ # reserved (14 bytes): MUST be zero and MUST be ignored.
369
+ }
370
+ end
371
+
372
+ # 2.5.24 CFExDateTemplateParams
373
+ # The CFExDateTemplateParams structure specifies parameters for the date-related conditional formatting rules specified by a CF12 record or CFExNonCF12 structure.
374
+ # @param data [String]
375
+ # @return [Hash]
376
+ def cfexdatetemplateparams(data)
377
+ date_op, _ = data.unpack('vC*')
378
+
379
+ {
380
+ _type: :CFExDateTemplateParams,
381
+ dateOp: date_op # dateOp (2 bytes): An unsigned integer that specifies the type of date comparison.
382
+ }
383
+ end
384
+
385
+ # 2.5.25 CFExDefaultTemplateParams
386
+ # This structure specifies that there are no parameters for extensions to conditional formatting rules specified by CFEx.
387
+ # @param _ [String]
388
+ # @return [Hash]
389
+ def cfexdefaulttemplateparams(_)
390
+ {
391
+ _type: :CFExDefaultTemplateParams,
392
+ }
393
+ end
394
+
395
+ # 2.5.26 CFExFilterParams
396
+ # @param data [String]
397
+ # @return [Hash]
398
+ def cfexfilterparams(data)
399
+ a_r1_raw, i_param, _ = data.unpack('CvC*')
400
+ a_r1 = Unxls::BitOps.new(a_r1_raw)
401
+
402
+ {
403
+ _type: :CFExFilterParams,
404
+ fTop: a_r1.set_at?(0), # A - fTop (1 bit): A bit that specifies whether the top or bottom items are displayed with the conditional formatting.
405
+ fPercent: a_r1.set_at?(1), # B - fPercent (1 bit): A bit that specifies whether a percentage of the top or bottom items are displayed with the conditional formatting, or whether a set number of the top or bottom items are displayed with the conditional formatting.
406
+ # reserved1 (6 bits): MUST be zero and MUST be ignored.
407
+ iParam: i_param, # iParam (2 bytes): An unsigned integer that specifies how many values are displayed with the conditional formatting. If fPercent equals 1 then this field represents a percent and MUST be less than or equal to 100. Otherwise, this field represents a set number of cells and MUST be less than or equal to 1000.
408
+ # reserved2 (13 bytes): MUST be zero and MUST be ignored.
409
+ }
410
+ end
411
+
412
+ # 2.5.28 CFExTemplateParams
413
+ # @param data [String]
414
+ # @param icf_template [Integer]
415
+ # @return [Hash]
416
+ def cfextemplateparams(data, icf_template)
417
+ _method = case icf_template
418
+ when 0x05 then :cfexfilterparams
419
+ when 0x08 then :cfextexttemplateparams
420
+ when 0x0F..0x18 then :cfexdatetemplateparams
421
+ when 0x19, 0x1A, 0x1D, 0x1E then :cfexaveragestemplateparams
422
+ else :cfexdefaulttemplateparams
423
+ end
424
+
425
+ self.send(_method, data)
426
+ end
427
+
428
+ # 2.5.29 CFExTextTemplateParams
429
+ # The CFExTextTemplateParams structure specifies parameters for text-related conditional formatting rules as specified by a CF12 record or CFExNonCF12 structure.
430
+ # @param data [String]
431
+ # @return [Hash]
432
+ def cfextexttemplateparams(data)
433
+ ctp, _ = data.unpack('vC*')
434
+ ctp_d = {
435
+ 0x0000 => :'Text contains',
436
+ 0x0001 => :'Text does not contain',
437
+ 0x0002 => :'Text begins with',
438
+ 0x0003 => :'Text ends with',
439
+ }[ctp]
440
+
441
+ {
442
+ _type: :CFExTextTemplateParams,
443
+ ctp: ctp, # ctp (2 bytes): An unsigned integer that specifies the type of text rule.
444
+ ctp_d: ctp_d,
445
+ # reserved (14 bytes): MUST be zero and MUST be ignored.
446
+ }
447
+ end
448
+
449
+ # 2.5.30 CFFilter
450
+ # The CFFilter structure specifies the parameters of a conditional formatting rule of type top N filter.
451
+ # @param io [StringIO]
452
+ # @return [Hash]
453
+ def cffilter(io)
454
+ cb_filter, _, a_r2_raw, i_param = io.read(6).unpack('vCCv')
455
+ a_r2 = Unxls::BitOps.new(a_r2_raw)
456
+
457
+ {
458
+ _type: :CFFilter,
459
+ cbFilter: cb_filter, # cbFilter (2 bytes): An unsigned integer that specifies the size of the structure in bytes, excluding the cbFilter field itself.
460
+ fTop: a_r2.set_at?(0), # A - fTop (1 bit): A bit that specifies whether the top or bottom items are displayed with the conditional formatting.
461
+ fPercent: a_r2.set_at?(1), # B - fPercent (1 bit): A bit that specifies whether a percentage of top or bottom items are displayed with the conditional formatting, or a set number of top or bottom items are displayed with the conditional formatting.
462
+ # reserved2 (6 bits): MUST be zero and MUST be ignored.
463
+ iParam: i_param # iParam (2 bytes): An unsigned integer that specifies how many values are displayed with the conditional formatting. If fPercent is set to 1 then this field represents a percent and MUST be less than or equal to 100, otherwise this field is a number of cells and MUST be less than or equal to 1000.
464
+ }
465
+ end
466
+
467
+ # 2.5.32 CFGradient
468
+ # The CFGradient structure specifies the parameters of a conditional formatting rule that uses color scale formatting.
469
+ # @param io [StringIO]
470
+ # @return [Hash]
471
+ def cfgradient(io)
472
+ _, _, c_interp_curve, c_gradient_curve, attrs = io.read(6).unpack('vC4')
473
+ # unused (2 bytes): Undefined and MUST be ignored.
474
+ # reserved1 (1 byte): MUST be zero and MUST be ignored.
475
+ attrs = Unxls::BitOps.new(attrs)
476
+
477
+ result = {
478
+ _type: :CFGradient,
479
+ cInterpCurve: c_interp_curve, # cInterpCurve (1 byte): An unsigned integer that specifies the number of control points in the interpolation curve. It MUST be 0x2 or 0x3.
480
+ cGradientCurve: c_gradient_curve, # cGradientCurve (1 byte): An unsigned integer that specifies the number of control points in the gradient curve. It MUST be equal to cInterpCurve.
481
+ fClamp: attrs.set_at?(0), # A - fClamp (1 bit): A bit that specifies that the cell values are not used when they are out of the range of the interpolation curve. The minimum or the maximum of the interpolation curve is used instead of the cell value.
482
+ fBackground: attrs.set_at?(1), # B - fBackground (1 bit): A bit that specifies that the color scale formatting applies to the background of the cells. It MUST be 1.
483
+ # reserved2 (6 bits): MUST be zero and MUST be ignored.
484
+ }
485
+
486
+ result[:rgInterp] = c_interp_curve.times.map { cfgradientinterpitem(io) } # rgInterp (variable): An array of CFGradientInterpItem. Each element is a control point of the interpolation curve. Its element count MUST be cInterpCurve.
487
+ result[:rgCurve] = c_gradient_curve.times.map { cfgradientitem(io) } # rgCurve (variable): An array of CFGradientItem. Each element is a control point of the gradient curve. Its element count MUST be cGradientCurve.
488
+
489
+ result
490
+ end
491
+
492
+ # 2.5.33 CFGradientInterpItem
493
+ # The CFGradientInterpItem structure specifies one control point in the interpolation curve.
494
+ # @param io [StringIO]
495
+ # @return [Hash]
496
+ def cfgradientinterpitem(io)
497
+ {
498
+ cfvoInterp: cfvo(io), # cfvoInterp (variable): A CFVO structure that specifies the cell value associated with the numerical value specified in numDomain.
499
+ numDomain: xnum(io.read(8)) # numDomain (8 bytes): An Xnum (section 2.5.342) structure that specifies the numerical value of this control point.
500
+ }
501
+ end
502
+
503
+ # 2.5.34 CFGradientItem
504
+ # The CFGradientItem structure specifies one control point in the gradient curve. The gradient curve specifies a color scale used in conditional formatting and maps numerical values to colors.
505
+ # @param io [StringIO]
506
+ # @return [Hash]
507
+ def cfgradientitem(io)
508
+ {
509
+ numGrange: xnum(io.read(8)), # numGrange (8 bytes): An Xnum (section 2.5.342) that specifies the numerical value of the control point.
510
+ color: cfcolor(io) # A CFColor that specifies the color associated with the numerical value specified in numGrange.
511
+ }
512
+ end
513
+
514
+ # 2.5.35 CFMStateItem
515
+ # The CFMStateItem structure specifies the threshold value associated with an icon for a CFMultistate conditional formatting rule.
516
+ # @param io [StringIO]
517
+ # @return [Hash]
518
+ def cfmstateitem(io)
519
+ result = {}
520
+
521
+ result[:cfvo] = cfvo(io) # cfvo (variable): A CFVO that specifies the threshold value.
522
+ result[:fEqual] = io.read(1).unpack('C').first == 0x01 # fEqual (1 byte): Cell values that are equal to the threshold value pass the threshold.
523
+
524
+ io.read(4) # unused (4 bytes): Undefined and MUST be ignored.
525
+
526
+ result
527
+ end
528
+
529
+ # 2.5.36 CFMultistate
530
+ # The CFMultistate structure specifies the parameters for a conditional formatting rule that represents cell values with icons from an icon set.
531
+ # @param io [StringIO]
532
+ # @return [Hash]
533
+ def cfmultistate(io)
534
+ _, _, c_states, i_icon_set, a_r3_raw = io.read(6).unpack('vCCCC')
535
+ a_r3 = Unxls::BitOps.new(a_r3_raw)
536
+
537
+ result = {
538
+ _type: :CFMultistate,
539
+ # unused (2 bytes): Undefined and MUST be ignored.
540
+ # reserved1 (1 byte): MUST be zero and MUST be ignored.
541
+ cStates: c_states, # cStates (1 byte): An unsigned integer that specifies the number of items in the icon set.
542
+ iIconSet: i_icon_set, # iIconSet (1 byte): An unsigned integer that specifies the icon set that represents the cell values.
543
+ fIconOnly: a_r3.set_at?(0), # A - fIconOnly (1 bit): A bit that specifies whether only the icon will be displayed in the sheet and that the cell value will be hidden.
544
+ # B - reserved2 (1 bit): MUST be zero and MUST be ignored.
545
+ fReverse: a_r3.set_at?(2), # C - fReverse (1 bit): A bit that specifies whether the order of the icons in the set is reversed.
546
+ # reserved3 (5 bits): MUST be zero and MUST be ignored.
547
+ }
548
+
549
+ result[:rgStates] = c_states.times.map { cfmstateitem(io) } # rgStates (variable): An array of CFMStateItem. Each element specifies a threshold for the respective icon in the set, below which cell values are represented by the next icon in the set. The element count MUST be equal to cStates.
550
+
551
+ result
552
+ end
553
+
554
+ # 2.5.198.5 CFParsedFormula
555
+ # The CFParsedFormula structure specifies a formula (section 2.2.2) used in a conditional formatting rule.
556
+ # @param io [StringIO]
557
+ # @return [Hash]
558
+ def cfparsedformula(io)
559
+ cce = io.read(2).unpack('v').first
560
+ io.read(cce)
561
+
562
+ {
563
+ cce: cce, # cce (2 bytes): An unsigned integer that specifies the length of rgce in bytes.
564
+ rgce: :not_implemented # rgce (variable): An Rgce that specifies the sequence of Ptg structures for the formula.
565
+ }
566
+ end
567
+
568
+ # 2.5.198.6 CFParsedFormulaNoCCE
569
+ # The CFParsedFormulaNoCCE structure specifies a formula (section 2.2.2) used in a conditional formatting rule, in a CF or CF12 record in which the size of the formula in bytes is specified.
570
+ # @param _ [String]
571
+ # @return [Hash]
572
+ def cfparsedformulanocce(_)
573
+ {
574
+ rgce: :not_implemented
575
+ }
576
+ end
577
+
578
+ # 2.5.39 CFVO
579
+ # The CFVO structure specifies a Conditional Formatting Value Object (CFVO) that specifies how to calculate a value from the range of cells that a conditional formatting rule applies to.
580
+ # @param io [StringIO]
581
+ # @return [Hash]
582
+ def cfvo(io)
583
+ cfvo_type = io.read(1).unpack('C').first
584
+ cfvo_type_d = {
585
+ 0x01 => :value, # X
586
+ 0x02 => :range_min, # The minimum value from the range of cells that the conditional formatting rule applies to.
587
+ 0x03 => :range_max, # The maximum value from the range of cells that the conditional formatting rule applies to.
588
+ 0x04 => :range_min_x_percentile, # The minimum value in the range of cells that the conditional formatting rule applies to plus X percent of the difference between the maximum and minimum values in the range of cells that the conditional formatting rule applies to. For example, if the min and max values in the range are 1 and 10 respectively, and X is 10, then the CFVO value is 1.9.
589
+ 0x05 => :range_max_x_percentile, # The minimum value of the cell that is in X percentile of the range of cells that the conditional formatting rule applies to.
590
+ 0x07 => :formula_result # The result of evaluating fmla.
591
+ }[cfvo_type]
592
+
593
+ result = {
594
+ cfvoType: cfvo_type, # cfvoType (1 byte): An unsigned integer that specifies how the CFVO value is determined.
595
+ cfvoType_d: cfvo_type_d,
596
+ fmla: cfvoparsedformula(io), # fmla (variable): A CFVOParsedFormula that specifies the formula used to calculate the CFVO value.
597
+ }
598
+
599
+ unless %i(range_min range_max).include?(cfvo_type_d) && result[:fmla][:cce].zero?
600
+ result[:numValue] = xnum(io.read(8)) # numValue (8 bytes): An Xnum (section 2.5.342) that specifies a static value used to calculate the CFVO value.
601
+ end
602
+
603
+ result
604
+ end
605
+
606
+ # 2.5.198.7 CFVOParsedFormula
607
+ # The CFVOParsedFormula structure specifies a formula (section 2.2.2) without relative references that is used in a conditional formatting rule.
608
+ # @param io [StringIO]
609
+ # @return [Hash]
610
+ alias :cfvoparsedformula :cfparsedformula
611
+
612
+ # See 2.4.122 Font, bCharSet
613
+ # @param id [Integer]
614
+ # @return [Symbol]
615
+ def _character_set(id)
616
+ Unxls::Biff8::Constants::CHARSETS[id]
617
+ end
618
+
619
+ # 2.5.51 ColRelU
620
+ # @param value [Integer]
621
+ # @return [Hash]
622
+ def colrelu(value)
623
+ attrs = Unxls::BitOps.new(value)
624
+ {
625
+ col: attrs.value_at(0..13), # zero-based index of a column in the sheet
626
+ colRelative: attrs.set_at?(14), # specifies whether col is a relative reference
627
+ rowRelative: attrs.set_at?(15) # specifies whether a row index corresponding to col in the structure containing this structure is a relative reference
628
+ }
629
+ end
630
+
631
+ # 2.5.56 CondFmtStructure, page 621
632
+ # @param io [StringIO]
633
+ # @return [Hash]
634
+ def condfmtstructure(io)
635
+ ccf, a_nid_raw = io.read(4).unpack('vv')
636
+ a_nid = Unxls::BitOps.new(a_nid_raw)
637
+ result = {
638
+ ccf: ccf, # ccf (2 bytes): An unsigned integer that specifies the count of CF12 records that follow the containing record.
639
+ fToughRecalc: a_nid.set_at?(0), # A - fToughRecalc (1 bit): A bit that specifies that the appearance of the cell requires significant processing.
640
+ nID: a_nid.value_at(1..15) # nID (15 bits): An unsigned integer that identifies this record.
641
+ }
642
+
643
+ result[:refBound] = ref8u(io.read(8)) # refBound (8 bytes): A Ref8U structure that specifies bounds of the set of cells to which the rules are applied.
644
+ result[:sqref] = sqrefu(io) # sqref (variable): A SqRefU structure that specifies the cells to which the conditional formatting rules apply.
645
+
646
+ result
647
+ end
648
+
649
+ # 2.5.91 DXFALC, page 638
650
+ # @param io [StringIO]
651
+ # @return [Hash]
652
+ def dxfalc(io)
653
+ result = _xfalc(io) # bytes 1-3
654
+ %i(fAtrNum, fAtrFnt, fAtrAlc, fAtrBdr, fAtrPat, fAtrProt).each do |k|
655
+ result.delete(k) # 24…31 unused (8 bits): Undefined and MUST be ignored.
656
+ end
657
+ result[:iIndent] = io.read(4).unpack('l<').first # A signed integer that specifies the relative level of indentation. The relative level of indentation will be added to any previous indentation.
658
+ result
659
+ end
660
+
661
+ # 2.5.92 DXFBdr, page 639
662
+ # @param io [StringIO]
663
+ # @return [Hash]
664
+ def dxfbdr(io)
665
+ result = _xfbdr(io) # 8 bytes
666
+ %i(fHasXFExt fls fls_d).each do |k|
667
+ result.delete(k) # 25…32 unused (7 bits): Undefined and MUST be ignored.
668
+ end
669
+ result
670
+ end
671
+
672
+ # 2.5.93 DXFFntD, page 640
673
+ # Specifies a font and its format attributes.
674
+ # @param io [StringIO]
675
+ # @return [Integer]
676
+ def dxffntd(io)
677
+ result = {
678
+ cchFont: (cch_font = io.read(1).unpack('C').first) # cchFont (1 byte): An unsigned integer that specifies the number of characters of the font name string.
679
+ }
680
+
681
+ st_font_name_raw = io.read(63)
682
+ if cch_font > 0
683
+ st_font_name_io = StringIO.new(st_font_name_raw)
684
+ result[:stFontName] = xlunicodestringnocch(st_font_name_io, cch_font) # stFontName (variable): An XLUnicodeStringNoCch that specifies the font name. MUST exist if and only if cchFont is greater than zero. The number of characters in the string is specified in cchFont.
685
+ end
686
+
687
+ result[:stxp] = stxp(io.read(16)) # stxp (16 bytes): A Stxp that specifies the font attributes.
688
+
689
+ result[:icvFore] = io.read(4).unpack('l<').first # icvFore (4 bytes): An integer that specifies the color of the font. The value MUST be -1, 32767 or any of the valid values of the IcvFont structure. A value of -1 specifies that this value is ignored. A value of 32767 specifies that the color of the font is the default foreground text color. Any other value specifies the color of the font as specified in the IcvFont structure.
690
+ io.read(4) # reserved (4 bytes): MUST be zero, and MUST be ignored.
691
+ result[:tsNinch] = ts(io.read(4).unpack('V').first) # tsNinch (4 bytes): A Ts structure that specifies how the value of stxp.ts is to be interpreted. If tsNinch.ftsItalic is set to 1 then the value of stxp.ts.ftsItalic MUST be ignored. If tsNinch.ftsStrikeout is set to 1 then the value of the stxp.ts.ftsStrikeout MUST be ignored.
692
+
693
+ f_sss_n_inch, f_uls_n_inch, f_bls_n_inch, _, ich, cch, i_fnt = io.read(26).unpack('VVVVl<l<v')
694
+
695
+ result[:fSssNinch] = f_sss_n_inch == 1 # fSssNinch (4 bytes): A Boolean (section 2.5.14) that specifies whether the value of stxp.sss MUST be ignored.
696
+ result[:fUlsNinch] = f_uls_n_inch == 1 # fUlsNinch (4 bytes): A Boolean that specifies whether the value of stxp.uls MUST be ignored.
697
+ result[:fBlsNinch] = f_bls_n_inch == 1 # fBlsNinch (4 bytes): A Boolean that specifies whether the value of stxp.bls MUST be ignored.
698
+ # unused2 (4 bytes): Undefined and MUST be ignored.
699
+ result[:ich] = ich # ich (4 bytes): A signed integer that specifies the zero based index of the first character to which this font applies. MUST be greater than or equal to 0xFFFFFFFF. MUST be set to 0xFFFFFFFF when the font is to be updated.
700
+ result[:cch] = cch # cch (4 bytes): A signed integer that specifies the number of characters to which this font applies. MUST be greater than or equal to ich field. MUST be set to 0xFFFFFFFF if the ich field is set to 0xFFFFFFFF.
701
+ result[:iFnt] = i_fnt # iFnt (2 bytes): An unsigned integer that specifies the font. If the value is 0 then the default font is used. If the value is greater than 0 then the font to be applied is determined by the font name specified in stFontName.
702
+
703
+ result
704
+ end
705
+
706
+ # 2.5.95 DXFN, page 641
707
+ # The DXFN structure specifies differential formatting.
708
+ # @param io [StringIO]
709
+ # @return [Hash]
710
+ def dxfn(io)
711
+ a_p_raw, q_d_raw, e_h_raw = io.read(6).unpack('v3')
712
+
713
+ a_p = Unxls::BitOps.new(a_p_raw)
714
+ result = {
715
+ alchNinch: a_p.set_at?(0), # A - alchNinch (1 bit): A bit that specifies whether the value of dxfalc.alc MUST be ignored.
716
+ alcvNinch: a_p.set_at?(1), # B - alcvNinch (1 bit): A bit that specifies whether the value of dxfalc.alcv MUST be ignored.
717
+ wrapNinch: a_p.set_at?(2), # C - wrapNinch (1 bit): A bit that specifies whether the value of dxfalc.fWrap MUST be ignored.
718
+ trotNinch: a_p.set_at?(3), # D - trotNinch (1 bit): A bit that specifies whether the value of dxfalc.trot MUST be ignored.
719
+ kintoNinch: a_p.set_at?(4), # E - kintoNinch (1 bit): A bit that specifies whether the value of dxfalc.fJustLast MUST be ignored.
720
+ cIndentNinch: a_p.set_at?(5), # F - cIndentNinch (1 bit): A bit that specifies whether the values of dxfalc.cIndent and dxfalc.iIndent MUST be ignored.
721
+ fShrinkNinch: a_p.set_at?(6), # G - fShrinkNinch (1 bit): A bit that specifies whether the value of dxfalc.fShrinkToFit MUST be ignored.
722
+ fMergeCellNinch: a_p.set_at?(7), # H - fMergeCellNinch (1 bit): A bit that specifies whether the value of dxfalc.fMergeCell MUST be ignored.
723
+ lockedNinch: a_p.set_at?(8), # I - lockedNinch (1 bit): A bit that specifies whether the value of dxfprot.fLocked MUST be ignored.
724
+ hiddenNinch: a_p.set_at?(9), # J - hiddenNinch (1 bit): A bit that specifies whether the value of dxfprot.fHidden MUST be ignored.
725
+ glLeftNinch: a_p.set_at?(10), # K - glLeftNinch (1 bit): A bit that specifies whether the values of dxfbdr.dgLeft and dxfbdr.icvLeft MUST be ignored.
726
+ glRightNinch: a_p.set_at?(11), # L - glRightNinch (1 bit): A bit that specifies whether the values of dxfbdr.dgRight and dxfbdr.icvRight MUST be ignored.
727
+ glTopNinch: a_p.set_at?(12), # M - glTopNinch (1 bit): A bit that specifies whether the values of dxfbdr.dgTop and dxfbdr.icvTop MUST be ignored.
728
+ glBottomNinch: a_p.set_at?(13), # N - glBottomNinch (1 bit): A bit that specifies whether the values of dxfbdr.dgBottom and dxfbdr.icvBottom MUST be ignored.
729
+ glDiagDownNinch: a_p.set_at?(14), # O - glDiagDownNinch (1 bit): A bit that specifies whether the value of dxfbdr.bitDiagDown MUST be ignored. When both glDiagDownNinch and glDiagUpNinch are set to 1, the values of dxfbdr.dgDiag and dxfbdr.icvDiag MUST be ignored.
730
+ glDiagUpNinch: a_p.set_at?(15), # P - glDiagUpNinch (1 bit): A bit that specifies whether the value of dxfbdr.bitDiagUp MUST be ignored. When both glDiagDownNinch and glDiagUpNinch are set to 1, the values of dxfbdr.dgDiag and dxfbdr.icvDiag MUST be ignored.
731
+ }
732
+
733
+ q_d = Unxls::BitOps.new(q_d_raw)
734
+ result.merge!({
735
+ flsNinch: q_d.set_at?(0), # Q - flsNinch (1 bit): A bit that specifies whether the value of dxfpat.fls MUST be ignored.
736
+ icvFNinch: q_d.set_at?(1), # R - icvFNinch (1 bit): A bit that specifies whether the value of dxfpat.icvForeground MUST be ignored.
737
+ icvBNinch: q_d.set_at?(2), # S - icvBNinch (1 bit): A bit that specifies whether the value of dxfpat.icvBackground MUST be ignored.
738
+ ifmtNinch: q_d.set_at?(3), # T - ifmtNinch (1 bit): A bit that specifies whether the value of dxfnum.ifmt MUST be ignored.
739
+ fIfntNinch: q_d.set_at?(4), # U - fIfntNinch (1 bit): A bit that specifies whether the value of dxffntd.ifnt MUST be ignored.
740
+ # (5) V - unused1 (1 bit): Undefined and MUST be ignored.
741
+ # (6…8) W - reserved1 (3 bits): MUST be zero and MUST be ignored.
742
+ ibitAtrNum: q_d.set_at?(9), # X - ibitAtrNum (1 bit): A bit that specifies whether number formatting information is part of this structure.
743
+ ibitAtrFnt: q_d.set_at?(10), # Y - ibitAtrFnt (1 bit): A bit that specifies whether font information is part of this structure.
744
+ ibitAtrAlc: q_d.set_at?(11), # Z - ibitAtrAlc (1 bit): A bit that specifies whether alignment information is part of this structure.
745
+ ibitAtrBdr: q_d.set_at?(12), # a - ibitAtrBdr (1 bit): A bit that specifies whether border formatting information is part of this structure.
746
+ ibitAtrPat: q_d.set_at?(13), # b - ibitAtrPat (1 bit): A bit that specifies whether pattern information is part of this structure.
747
+ ibitAtrProt: q_d.set_at?(14), # c - ibitAtrProt (1 bit): A bit that specifies whether rotation information is part of this structure.
748
+ iReadingOrderNinch: q_d.set_at?(15), # d - iReadingOrderNinch (1 bit): A bit that specifies whether the value of dxfalc.iReadingOrder MUST be ignored.
749
+ })
750
+
751
+ e_h = Unxls::BitOps.new(e_h_raw)
752
+ result.merge!({
753
+ fIfmtUser: e_h.set_at?(0), # e - fIfmtUser (1 bit): A bit that specifies that the number format used is a user-defined format string. When set to 1, dxfnum contains a format string.
754
+ # (1) f - unused2 (1 bit): Undefined and MUST be ignored.
755
+ fNewBorder: (f_new_border = e_h.value_at(2)), # g - fNewBorder (1 bit): A bit that specifies how the border formats apply to a range of cells.
756
+ fNewBorder_d: {
757
+ 0 => :all_cells, # Border formats apply to all cells in the range.
758
+ 1 => :outline # Border formats only apply to the outline of the range.
759
+ }[f_new_border],
760
+ # (3…14) reserved2 (12 bits): MUST be zero and MUST be ignored.
761
+ fZeroInited: e_h.set_at?(15) # h - fZeroInited (1 bit): A bit that specifies whether the value of dxfalc.iReadingOrder MUST be taken into account.
762
+ })
763
+
764
+ if result[:ibitAtrNum]
765
+ # 2.5.99 DXFNum
766
+ result[:dxfnum] = result[:fIfmtUser] ? dxfnumusr(io) : dxfnumifmt(io) # dxfnum (variable): A DXFNum that specifies the number formatting. MUST exist if and only if ibitAtrNum is nonzero.
767
+ end
768
+
769
+ if result[:ibitAtrFnt]
770
+ result[:dxffntd] = dxffntd(io) # dxffntd (variable): A DXFFntD that specifies the font. MUST exist if and only if ibitAtrFnt is nonzero.
771
+ end
772
+
773
+ if result[:ibitAtrAlc]
774
+ result[:dxfalc] = dxfalc(io) # (8 bytes): A DXFALC that specifies the text alignment properties.
775
+ end
776
+
777
+ if result[:ibitAtrBdr]
778
+ result[:dxfbdr] = dxfbdr(io) # (8 bytes): A DXFBdr that specifies the border properties.
779
+ end
780
+
781
+ if result[:ibitAtrPat]
782
+ result[:dxfpat] = dxfpat(io) # (4 bytes): A DXFPat that specifies the pattern and colors.
783
+ end
784
+
785
+ if result[:ibitAtrProt]
786
+ result[:dxfprot] = dxfprot(io) # (2 bytes): A DXFProt that specifies the protection attributes.
787
+ end
788
+
789
+ result
790
+ end
791
+
792
+ # 2.5.96 DXFN12
793
+ # The DXFN12 structure specifies differential formatting and is an extension to DXFN.
794
+ # @param io [StringIO]
795
+ # @return [Integer]
796
+ def dxfn12(io)
797
+ cb_dxf = io.read(4).unpack('V').first
798
+ result = {
799
+ cbDxf: cb_dxf # cbDxf (4 bytes): An unsigned integer that specifies the size of the structure in bytes. If greater than zero, it MUST be the total byte count of dfxn and xfext. Otherwise it MUST be zero.
800
+ }
801
+
802
+ if cb_dxf.zero?
803
+ io.read(2) # reserved (2 bytes): MUST be zero and MUST be ignored. MUST be omitted when cbDxf is greater than zero.
804
+ else
805
+ pos = io.pos
806
+ result[:dxfn] = dxfn(io) # dxfn (variable): A DXFN that specifies part of the differential formatting. MUST be omitted if cbDxf is 0x00000000.
807
+ dxfn_size = io.pos - pos
808
+ result[:xfext] = xfextnofrt(io) if cb_dxf > dxfn_size # xfext (variable): An XFExtNoFRT that specifies extensions for the differential formatting. MUST be omitted if cbDxf is equal to the byte count of dxfn.
809
+ end
810
+
811
+ result
812
+ end
813
+
814
+ # 2.5.97 DXFN12List
815
+ # The DXFN12List structure specifies differential formatting used by table block-level formatting. This structure also specifies extensions to the DXFN formatting properties.
816
+ # @param data [String, StringIO]
817
+ # @return [Integer]
818
+ def dxfn12list(data)
819
+ io = data.to_sio
820
+
821
+ result = {
822
+ dxfn: dxfn(io) # dxfn (variable): A DXFN structure that specifies differential formatting used by table block-level formatting.
823
+ }
824
+
825
+ unless io.eof?
826
+ result[:xfext] = xfextnofrt(io) # xfext (variable): An XFExtNoFRT structure that specifies the set of extensions to the differential formatting properties specified in dxfn. MUST exist if and only if the size of this structure is greater than the size of the dxfn field.
827
+ end
828
+
829
+ result
830
+ end
831
+
832
+ # 2.5.98 DXFN12NoCB
833
+ # @param io [StringIO]
834
+ # @return [Integer]
835
+ alias :dxfn12nocb :dxfn12list
836
+
837
+ # 2.5.100 DXFNumIFmt
838
+ # @param io [StringIO]
839
+ # @return [Integer]
840
+ def dxfnumifmt(io)
841
+ # (0…7) unused (8 bits): Undefined and MUST be ignored.
842
+ # (8…16) Specifies the identifier of a number format.
843
+ io.read(2).unpack('CC').last # See 2.5.165 IFmt
844
+ end
845
+
846
+ # 2.5.101 DXFNumUsr
847
+ # @param io [StringIO]
848
+ # @return [String]
849
+ def dxfnumusr(io)
850
+ cb = io.read(2).unpack('v').first
851
+
852
+ {
853
+ cb: cb, # Specifies the size of this structure, in bytes.
854
+ fmt: xlunicodestring(io) # Specifies the number format to use as specified in the stFormat field of Format.
855
+ }
856
+ end
857
+
858
+ # 2.5.102 DXFPat, page 646
859
+ # @param io [StringIO]
860
+ # @return [Hash]
861
+ def dxfpat(io)
862
+ u_a_raw = io.read(4).unpack('V').first
863
+ u_a = Unxls::BitOps.new(u_a_raw)
864
+
865
+ {
866
+ # 0…9 unused1 (10 bits): Undefined and MUST be ignored.
867
+ fls: (fls = u_a.value_at(10..15)), # fls (6 bits): A FillPattern that specifies the fill pattern.
868
+ fls_d: fillpattern(fls),
869
+ icvFore: u_a.value_at(16..22), # @todo _icv_d icvForeground (7 bits): An unsigned integer that specifies the color of the foreground of the cell. The value MUST be an IcvXF value. This value is unused and MUST be ignored if the icvFNinched field in the containing DXFN structure is 1.
870
+ icvBack: u_a.value_at(23..29), # icvBackground (7 bits): An unsigned integer that specifies the color of the background of the cell. The value MUST be an IcvXF value. This value is unused and MUST be ignored if the icvBNinched field in the containing DXFN structure is 1.
871
+ # 30…31 A - unused2 (2 bits): Undefined and MUST be ignored.
872
+ }
873
+ end
874
+
875
+ # 2.5.103 DXFProt, page 647
876
+ # @param io [StringIO]
877
+ # @return [Hash]
878
+ def dxfprot(io)
879
+ a_b_raw = io.read(2).unpack('v').first
880
+ a_b = Unxls::BitOps.new(a_b_raw)
881
+
882
+ {
883
+ fLocked: a_b.set_at?(0), # A - fLocked (1 bit): A bit that specifies if the cell content is locked when the workbook is protected.
884
+ fHidden: a_b.set_at?(1), # B - fHidden (1 bit): A bit that specifies if the cell content is hidden when the workbook is protected.
885
+ # reserved (14 bits): MUST be zero and MUST be ignored.
886
+ }
887
+ end
888
+
889
+ # @param string [String]
890
+ # @param encoding [String]
891
+ # @return [String]
892
+ def _encode_string(string, encoding = Encoding::UTF_16LE)
893
+ string.force_encoding(encoding).encode(Encoding::UTF_8)
894
+ end
895
+
896
+ # @param high_byte [TrueClass, FalseClass]
897
+ # @return [Array]
898
+ def _encoding_params(high_byte)
899
+ high_byte ? [Encoding::UTF_16LE, 2] : [Encoding::UTF_8, 1]
900
+ end
901
+
902
+ # 2.5.108 ExtProp
903
+ # The ExtProp structure specifies an extension to a formatting property.
904
+ # @param io [StringIO]
905
+ # @return [Hash]
906
+ def extprop(io)
907
+ ext_type, cb = io.read(4).unpack('vv')
908
+ prop_data = _ext_prop_d(ext_type)
909
+
910
+ result = {
911
+ extType: ext_type, # extType (2 bytes): An unsigned integer that specifies the type of the extension.
912
+ extType_d: prop_data[:structure],
913
+ _property: prop_data[:property],
914
+ cb: cb, # cb (2 bytes): An unsigned integer that specifies the size of this ExtProp structure.
915
+ }
916
+
917
+ ext_prop_data = io.read(cb - 4) # structure size minus ext_type and cb
918
+ parser_method = prop_data[:structure].downcase
919
+ result[:extPropData] = self.send(parser_method, ext_prop_data) # extPropData (variable): This field specifies the extension data.
920
+
921
+ result
922
+ end
923
+
924
+ # 2.5.109 ExtRst
925
+ # The ExtRst structure specifies phonetic string data.
926
+ # @param data [String]
927
+ # @return [Hash]
928
+ def extrst(data)
929
+ io = StringIO.new(data)
930
+
931
+ _, cb = io.read(4).unpack('vv')
932
+ # reserved (2 bytes): MUST be 1, and MUST be ignored.
933
+ result = { cb: cb } # cb (2 bytes): An unsigned integer that specifies the size, in bytes, of the phonetic string data.
934
+
935
+ result[:phs] = phs(io.read(4)) # phs (4 bytes): A Phs that specifies the formatting information for the phonetic string.
936
+ result[:rphssub] = rphssub(io) # rphssub (variable): An RPHSSub that specifies the phonetic string.
937
+
938
+ result[:rphssub][:crun].times do # See 2.5.219 RPHSSub
939
+ result[:rgphruns] ||= []
940
+ result[:rgphruns] << rgphruns(io)
941
+ end
942
+
943
+ result
944
+ end
945
+
946
+ # See 2.5.108 ExtProp, p. 649
947
+ # @param id [Integer]
948
+ # @return [Hash]
949
+ def _ext_prop_d(id)
950
+ {
951
+ 0x0004 => { structure: :FullColorExt, property: :'cell interior foreground color' },
952
+ 0x0005 => { structure: :FullColorExt, property: :'cell interior background color' },
953
+ 0x0006 => { structure: :XFExtGradient, property: :'cell interior gradient fill' },
954
+ 0x0007 => { structure: :FullColorExt, property: :'top cell border color' },
955
+ 0x0008 => { structure: :FullColorExt, property: :'bottom cell border color' },
956
+ 0x0009 => { structure: :FullColorExt, property: :'left cell border color' },
957
+ 0x000A => { structure: :FullColorExt, property: :'right cell border color' },
958
+ 0x000B => { structure: :FullColorExt, property: :'diagonal cell border color' },
959
+ 0x000D => { structure: :FullColorExt, property: :'cell text color' },
960
+ 0x000E => { structure: :FontScheme, property: :'font scheme' },
961
+ 0x000F => { structure: :_int1b, property: :'text indentation level' }
962
+ }[id]
963
+ end
964
+
965
+ # @todo 2.5.112 Feat11FdaAutoFilter
966
+ # The Feat11FdaAutoFilter structure specifies the definition of an automatically generated filter, or AutoFilter.
967
+ # @param io [StringIO]
968
+ # @return [Symbol]
969
+ def feat11fdaautofilter(io)
970
+ cb_auto_filter, _ = io.read(6).unpack('Vv')
971
+ io.pos += cb_auto_filter
972
+
973
+ :not_implemented
974
+ end
975
+
976
+ # @todo 2.5.113 Feat11FieldDataItem
977
+ # The Feat11FieldDataItem structure specifies a column of a table.
978
+ # @param io [StringIO]
979
+ # @param tft [Hash] Parent TableFeatureType structure
980
+ # @return [Hash]
981
+ def feat11fielddataitem(io, tft)
982
+ id_field, lfdt, lfxidt, ilta, cb_fmt_agg, istn_agg = io.read(24).unpack('V6')
983
+
984
+ result = {
985
+ idField: id_field, # idField (4 bytes): An unsigned integer that specifies the identifier of the column. MUST be nonzero and MUST be unique within the FieldData array in the containing TableFeatureType structure.
986
+ lfdt: lfdt, # lfdt (4 bytes): An unsigned integer that specifies the column’s Web based data provider data type. If the lt field of the containing TableFeatureType structure is not set to 0x00000001, this field MUST be 0x00000000; otherwise it MUST be a value from the following table. For more information about the data types, see [MS-WSSTS] section 2.3.
987
+ lfxidt: lfxidt, # lfxidt (4 bytes): An unsigned integer that specifies the column’s XML data type. If the lt field of the containing TableFeatureType structure is not set to 0x00000002, this field MUST be 0x00000000; otherwise it MUST be a value from the following table. For more information about the data types, see [MSDN-SOM].
988
+ ilta: ilta, # ilta (4 bytes): An unsigned integer that specifies the aggregation function to use for the total row of the column.
989
+ cbFmtAgg: cb_fmt_agg, # cbFmtAgg (4 bytes): An unsigned integer that specifies the size, in bytes, of the dxfFmtAgg field.
990
+ istnAgg: istn_agg, # istnAgg (4 bytes): An unsigned integer that specifies the zero-based index of the Style record in the Globals Substream ABNF that is used for the total row of the column. If this value equals 0xFFFFFFFF, the total row of the column uses built-in table styles.
991
+ }
992
+
993
+ result[:lfdt_d] = {
994
+ 0x01 => :'Text',
995
+ 0x02 => :'Number',
996
+ 0x03 => :'Boolean',
997
+ 0x04 => :'Date Time',
998
+ 0x05 => :'Note',
999
+ 0x06 => :'Currency',
1000
+ 0x07 => :'Lookup',
1001
+ 0x08 => :'Choice',
1002
+ 0x09 => :'URL',
1003
+ 0x0A => :'Counter',
1004
+ 0x0B => :'Multiple Choices',
1005
+ }[lfdt]
1006
+
1007
+ result[:lfxidt_d] = _lfxidt_d(lfxidt)
1008
+
1009
+ result[:ilta_d] = {
1010
+ 0x00 => :'No formula',
1011
+ 0x01 => :Average,
1012
+ 0x02 => :Count,
1013
+ 0x03 => :'Count numbers',
1014
+ 0x04 => :Max,
1015
+ 0x05 => :Min,
1016
+ 0x06 => :Sum,
1017
+ 0x07 => :'Standard deviation',
1018
+ 0x08 => :Variance,
1019
+ 0x09 => :'Custom formula',
1020
+ }[ilta]
1021
+
1022
+ attrs = Unxls::BitOps.new(io.read(4).unpack('V').first)
1023
+ result.merge!({
1024
+ fAutoFilter: attrs.set_at?(0), # A - fAutoFilter (1 bit): A bit that specifies whether the column has an AutoFilter.
1025
+ fAutoFilterHidden: attrs.set_at?(1), # B - fAutoFilterHidden (1 bit): A bit that specifies whether the column has an AutoFilters that is not displayed. When this field is set to 1, fAutoFilter MUST be set to 1.
1026
+ fLoadXmapi: attrs.set_at?(2), # C - fLoadXmapi (1 bit): A bit that specifies whether the rgXmap field is present. MUST be 0 if the lt field of the containing TableFeatureType structure is not equal to 0x00000002.
1027
+ fLoadFmla: attrs.set_at?(3), # D - fLoadFmla (1 bit): A bit that specifies whether the fmla field is present for a table whose data source is a Web based data provider list. MUST be 0 if the lt field of the containing TableFeatureType structure is not equal to 0x00000001.
1028
+ # 4…5 E - unused1 (2 bits): Undefined, and MUST be ignored.
1029
+ # 6 F - reserved2 (1 bit): MUST be zero, and MUST be ignored.
1030
+ fLoadTotalFmla: attrs.set_at?(7), # G - fLoadTotalFmla (1 bit): A bit that specifies whether the totalFmla field is present. SHOULD<165> be 1 if ilta is 0x00000009, MUST be 0 otherwise.
1031
+ fLoadTotalArray: attrs.set_at?(8), # H - fLoadTotalArray (1 bit): A bit that specifies whether the formula specified by totalFmla is an array formula. MUST be 0 when fLoadTotalFmla is 0.
1032
+ fSaveStyleName: attrs.set_at?(9), # I - fSaveStyleName (1 bit): A bit that specifies whether the dskHdrCache.strStyleName field is present.
1033
+ fLoadTotalStr: attrs.set_at?(10), # J - fLoadTotalStr (1 bit): A bit that specifies whether the strTotal field is present. MUST be 0 when ilta is not 0x00000000.
1034
+ fAutoCreateCalcCol: attrs.set_at?(11), # K - fAutoCreateCalcCol (1 bit): A bit that specifies whether the column has a calculated column formula. MUST be 0 if the lt field of the containing TableFeatureType structure is set to 0x00000001.
1035
+ # unused2 (20 bits): Undefined, and MUST be ignored.
1036
+ })
1037
+
1038
+ cb_fmt_insert_row, istn_insert_row = io.read(8).unpack('VV')
1039
+ result.merge!({
1040
+ cbFmtInsertRow: cb_fmt_insert_row, # cbFmtInsertRow (4 bytes): An unsigned integer that specifies the size, in bytes, of the dxfFmtInsertRow field.
1041
+ istnInsertRow: istn_insert_row, # istnInsertRow (4 bytes): An unsigned integer that specifies the zero-based index of the Style record in the Globals Substream ABNF that is used for the insert row of the column. If this value equals 0xFFFFFFFF, the insert row of the column uses built-in table styles.
1042
+ strFieldName: xlunicodestring(io), # strFieldName (variable): An XLUnicodeString that specifies the name of the column, as provided by the data source.
1043
+ strCaption: xlunicodestring(io), # strCaption (variable): An XLUnicodeString that specifies the caption of the column.
1044
+ })
1045
+
1046
+ if result[:cbFmtAgg] > 0
1047
+ result[:dxfFmtAgg] = dxfn12list(io.read(result[:cbFmtAgg])) # dxfFmtAgg (variable): A DXFN12List that specifies the formatting of the total row of the column, if different from the style specified by istnAgg or built-in table styles. This field is present if and only if the cbFmtAgg field is greater than 0x00000000.
1048
+ end
1049
+
1050
+ if result[:cbFmtInsertRow] > 0
1051
+ result[:dxfFmtInsertRow] = dxfn12list(io.read(result[:cbFmtInsertRow])) # dxfFmtInsertRow (variable): A DXFN12List that specifies the formatting of the insert row of the column, if different from the style specified by istnInsertRow or built-in table styles. This field is present if and only if the cbFmtInsertRow field is more than 0x00000000.
1052
+ end
1053
+
1054
+ if result[:fAutoFilter]
1055
+ result[:AutoFilter] = feat11fdaautofilter(io) # AutoFilter (variable): A Feat11FdaAutoFilter that specifies the characteristics of the AutoFilter for the column. This field is present if and only if the fAutoFilter field of the containing TableFeatureType structure is set to 1.
1056
+ end
1057
+
1058
+ if result[:fLoadXmapi]
1059
+ result[:rgXmap] = feat11xmap(io) # rgXmap (variable): A Feat11XMap structure that specifies the mapping to the column data within an XML data source. This field is present if and only if the fLoadXmapi bit is set to 1.
1060
+ end
1061
+
1062
+ if result[:fLoadFmla]
1063
+ result[:fmla] = feat11fmla(io) # # fmla (variable): A Feat11Fmla structure that specifies the column formula whose data source is a Web based data provider list. The specified formula applies to every row of the column, except the total row and the header row. This field is present if and only if the fLoadFmla bit is set to 1.
1064
+ end
1065
+
1066
+ if result[:fLoadTotalFmla]
1067
+ result[:totalFmla] = feat11totalfmla(io, result[:fLoadTotalArray]) # totalFmla (variable): A Feat11TotalFmla structure that specifies the formula to use for the total row of the column. This field is present if and only if the fLoadTotalFmla bit is set to 1.
1068
+ end
1069
+
1070
+ if result[:fLoadTotalStr]
1071
+ result[:strTotal] = xlunicodestring(io) # strTotal (variable): An XLUnicodeString structure that specifies the text to use for the total row of the column. MUST contain less than or equal to 32767 characters. This field is present if and only if the fLoadTotalStr bit is set to 1.
1072
+ end
1073
+
1074
+ if tft[:lt] == 0x01
1075
+ result[:wssInfo] = feat11wsslistinfo(io, lfdt) # wssInfo (variable): A Feat11WSSListInfo that specifies the relationship between the column and a Web based data provider list. This field is present if and only if the lt field of the containing TableFeatureType structure is set to 0x00000001.
1076
+ end
1077
+
1078
+ if tft[:lt] == 0x03
1079
+ result[:qsif] = io.read(4).unpack('V').first # qsif (4 bytes): An unsigned integer that specifies the relationship between the column and its Microsoft Query data source. MUST be equal to the idField field of a Qsif record within the Worksheet Substream. This field is present if and only if the lt field of the containing TableFeatureType structure is set to 0x00000003 (External data source). MUST be greater than zero and MUST be unique within the FieldData array in the containing TableFeatureType structure.
1080
+ end
1081
+
1082
+ unless tft[:crwHeader] || tft[:fSingleCell]
1083
+ result[:dskHdrCache] = cacheddiskheader(io, result[:fSaveStyleName]) # dskHdrCache (variable): A CachedDiskHeader that specifies the column header formatting information. This field is present if and only if the crwHeader field of the containing TableFeatureType structure is set to 0x0000 and the fSingleCell field of the containing TableFeatureType structure is set to 0.
1084
+ end
1085
+
1086
+ result
1087
+
1088
+ rescue StandardError => e # Unfinished listparsedarrayformula in feat11totalfmla might raise in some cases
1089
+ { _error: e }
1090
+ end
1091
+
1092
+ # @todo 2.5.114 Feat11Fmla
1093
+ # The Feat11Fmla structure specifies a formula (section 2.2.2) that is used as a column formula.
1094
+ # @param io [StringIO]
1095
+ # @return [Symbol]
1096
+ def feat11fmla(io)
1097
+ cb_fmla = io.read(2).unpack('v').first # cbFmla (2 bytes): An unsigned integer that specifies the size, in bytes, of the rgbFmla field.
1098
+ io.pos += cb_fmla # rgbFmla (variable): A ListParsedFormula that specifies the parsed expression of the column formula.
1099
+
1100
+ :not_implemented
1101
+ end
1102
+
1103
+ # @todo 2.5.118 Feat11TotalFmla
1104
+ # The Feat11TotalFmla structure specifies a formula (section 2.2.2) that can be used as a total row formula.
1105
+ # @param io [StringIO]
1106
+ # @param f_load_total_array [true, false]
1107
+ # @return [Symbol]
1108
+ def feat11totalfmla(io, f_load_total_array)
1109
+ f_load_total_array ? listparsedarrayformula(io) : listparsedformula(io) # rgbFmlaTotal (variable): A ListParsedFormula or ListParsedArrayFormula that specifies the parsed expression of the total row formula. When the fLoadTotalArray field of the containing Feat11FieldDataItem structure is set to 1, this field is a ListParsedArrayFormula; otherwise, it is a ListParsedFormula.
1110
+
1111
+ :not_implemented
1112
+ end
1113
+
1114
+ # @todo 2.5.120 Feat11XMap
1115
+ # The Feat11XMap structure specifies the mapping between a table column’s data and an XML data source.
1116
+ # @param io [StringIO]
1117
+ # @return [Symbol]
1118
+ def feat11xmap(io)
1119
+ i_xmap_mac = io.read(2).unpack('v').first
1120
+ i_xmap_mac.times { feat11xmapentry(io) }
1121
+
1122
+ :not_implemented
1123
+ end
1124
+
1125
+ # @todo 2.5.121 Feat11XMapEntry
1126
+ # The Feat11XMapEntry structure specifies a mapping to an XML data source.
1127
+ # @param io [StringIO]
1128
+ # @return [Symbol]
1129
+ def feat11xmapentry(io)
1130
+ # A - reserved1 (1 bit): MUST be zero, and MUST be ignored.
1131
+ # B - fLoadXMap (1 bit): MUST be 1, and MUST be ignored.
1132
+ # C - fCanBeSingle (1 bit): A bit that specifies whether details.rgbXPath resolves to a single XML node or a collection of XML nodes.
1133
+ # D - reserved2 (1 bit): MUST be zero, and MUST be ignored.
1134
+ # reserved3 (28 bits): MUST be zero, and MUST be ignored.
1135
+ io.pos += 4
1136
+
1137
+ feat11xmapentry2(io) # details (variable): A Feat11XMapEntry2 that specifies the mapping between the data and the XML data source.
1138
+
1139
+ :not_implemented
1140
+ end
1141
+
1142
+ # @todo 2.5.122 Feat11XMapEntry2
1143
+ # The Feat11XMapEntry2 structure specifies the mapping to an XML data source.
1144
+ # @param io [StringIO]
1145
+ # @return [Symbol]
1146
+ def feat11xmapentry2(io)
1147
+ io.pos += 4 # dwMapId (4 bytes): An unsigned integer that specifies the XML schema associated with this table column. The value MUST equal the value of the ID attribute of a Map element contained within the XML stream (section 2.1.7.22).
1148
+ xlunicodestring(io) # rgbXPath (variable): An XLUnicodeString that contains the XPath expression that specifies the mapped element in the XML schema specified by dwMapId. The length of this string MUST be less than 32000.
1149
+
1150
+ :not_implemented
1151
+ end
1152
+
1153
+ # @todo 2.5.119 Feat11WSSListInfo
1154
+ # The Feat11WSSListInfo structure specifies the relationship between a table column and a Web-based data provider list.
1155
+ # @param io [StringIO]
1156
+ # @param lfdt [Integer]
1157
+ # @return [Symbol]
1158
+ def feat11wsslistinfo(io, lfdt)
1159
+ # LCID (4 bytes): An unsigned integer that specifies the language code identifier (LCID) of the source data.
1160
+ # cDec (4 bytes): An unsigned integer that specifies the number of decimal places for a numeric column.
1161
+ # A - fPercent (1 bit): A bit that specifies whether the numeric values in the column are displayed as percentages.
1162
+ # B - fDecSet (1 bit): A bit that specifies whether the numeric values in the column are displayed with a fixed decimal point. The position of the decimal point is specified by the cDec field.
1163
+ # C - fDateOnly (1 bit): A bit that specifies whether only the date part of date/time values is displayed.
1164
+ # D - fReadingOrder (2 bits): An unsigned integer that specifies the reading order. MUST be a value from the following table:
1165
+ # 0x0 Reading order is determined by the application based on the reading order of the cells surrounding the table.
1166
+ # 0x1 Reading order is left-to-right.
1167
+ # 0x2 Reading order is right-to-left.
1168
+ # E - fRichText (1 bit): A bit that specifies whether the column contains rich text.
1169
+ # F - fUnkRTFormatting (1 bit): A bit that specifies whether the column contains unrecognized rich text formatting.
1170
+ # G - fAlertUnkRTFormatting (1 bit): A bit that specifies whether the column contains unrecognized rich text formatting that requires notifying the user.
1171
+ # unused1 (24 bits): Undefined and MUST be ignored.
1172
+ io.pos += 12
1173
+
1174
+ # H - fReadOnly (1 bit): A bit that specifies whether the column is read only.
1175
+ # I - fRequired (1 bit): A bit that specifies whether every item in this column has to contain data.
1176
+ # J - fMinSet (1 bit): A bit that specifies whether a minimum numeric value for the column exists. The minimum value is stored in the List Data stream within the LISTSCHEMA element, under the Field node's Min attribute.
1177
+ # K - fMaxSet (1 bit): A bit that specifies whether a maximum numeric value for the column exists. The maximum value is stored in the List Data stream within the LISTSCHEMA element, under the Field node's Max attribute.
1178
+ # L - fDefaultSet (1 bit): A bit that specifies whether there is a default value for the column.
1179
+ # M - fDefaultDateToday (1 bit): A bit that specifies whether the default value for the column is the current date.
1180
+ # N - fLoadFormula (1 bit): A bit that specifies whether a validation formula exists for this column. The formula is specified by the strFormula field.
1181
+ # O - fAllowFillIn (1 bit): A bit that specifies whether a choice field allows custom user entries.
1182
+ # bDefaultType (8 bits): An unsigned integer that specifies the type of the rgbDV default value. This field MUST be ignored if fDefaultSet is not 0x1; otherwise, it MUST be a value from the following table:
1183
+ # 0x00 There is no default value specified.
1184
+ # 0x01 rgbDV is a string.
1185
+ # 0x02 rgbDV is a Boolean.
1186
+ # 0x03 rgbDV is a number.
1187
+ # unused2 (16 bits): Undefined, MUST be ignored.
1188
+ attrs = Unxls::BitOps.new(io.read(4).unpack('V').first)
1189
+ f_default_set = attrs.set_at?(4)
1190
+ f_load_formula = attrs.set_at?(6)
1191
+ b_default_type = attrs.value_at(8..15)
1192
+
1193
+ # rgbDV (variable): A field of variable data type that specifies the default value for the column. The data type is specified in the lfdt field of the containing Feat11FieldDataItem structure. MUST be one of the data types specified in the following table:
1194
+ # 0x01 Short Text # An XLUnicodeString with a maximum length of 255 Unicode characters.
1195
+ # 0x08 Choice # An XLUnicodeString with a maximum length of 255 Unicode characters.
1196
+ # 0x0B Multi-choice # An XLUnicodeString with a maximum length of 255 Unicode characters.
1197
+ # 0x02 Number # An Xnum (section 2.5.342).
1198
+ # 0x04 Date time # A DateAsNum. (Xnum)
1199
+ # 0x06 Currency # An Xnum.
1200
+ # 0x03 Yes/No # A 32-bit Boolean (section 2.5.14).
1201
+ # 0x05 Invalid # rgbDV does not exist.
1202
+ # 0x07 Invalid # rgbDV does not exist.
1203
+ # 0x09 Invalid # rgbDV does not exist.
1204
+ # 0x0A Invalid # rgbDV does not exist.
1205
+ if f_default_set && !b_default_type.zero?
1206
+ case lfdt
1207
+ when 0x01, 0x08, 0x0B then xlunicodestring(io)
1208
+ when 0x02, 0x04, 0x06 then io.pos += 8
1209
+ when 0x03 then io.pos += 4
1210
+ else nil
1211
+ end
1212
+ end
1213
+
1214
+ if f_load_formula
1215
+ xlunicodestring(io) # strFormula (variable): An XLUnicodeString that specifies the validation formula as defined by the Web based data provider. This field exists if and only if fLoadFormula is set to 0x1.
1216
+ end
1217
+
1218
+ io.pos += 4 # reserved (4 bytes): MUST be 0x00000000, and MUST be ignored.
1219
+
1220
+ :not_implemented
1221
+ end
1222
+
1223
+ # 2.5.127 FillPattern
1224
+ # @param id [Integer]
1225
+ # @return [Symbol]
1226
+ def fillpattern(id)
1227
+ {
1228
+ 0x00 => :FLSNULL, # No fill pattern
1229
+ 0x01 => :FLSSOLID, # Solid
1230
+ 0x02 => :FLSMEDGRAY, # 50% gray
1231
+ 0x03 => :FLSDKGRAY, # 75% gray
1232
+ 0x04 => :FLSLTGRAY, # 25% gray
1233
+ 0x05 => :FLSDKHOR, # Horizontal stripe
1234
+ 0x06 => :FLSDKVER, # Vertical stripe
1235
+ 0x07 => :FLSDKDOWN, # Reverse diagonal stripe
1236
+ 0x08 => :FLSDKUP, # Diagonal stripe
1237
+ 0x09 => :FLSDKGRID, # Diagonal crosshatch
1238
+ 0x0A => :FLSDKTRELLIS, # Thick diagonal crosshatch
1239
+ 0x0B => :FLSLTHOR, # Thin horizontal stripe
1240
+ 0x0C => :FLSLTVER, # Thin vertical stripe
1241
+ 0x0D => :FLSLTDOWN, # Thin reverse diagonal stripe
1242
+ 0x0E => :FLSLTUP, # Thin diagonal stripe
1243
+ 0x0F => :FLSLTGRID, # Thin horizontal crosshatch
1244
+ 0x10 => :FLSLTTRELLIS, # Thin diagonal crosshatch
1245
+ 0x11 => :FLSGRAY125, # 12.5% gray
1246
+ 0x12 => :FLSGRAY0625, # 6.25% gray
1247
+ }[id]
1248
+ end
1249
+
1250
+ # 2.5.133 FormulaValue
1251
+ # The FormulaValue structure specifies the current value of a formula. It can be a numeric value, a Boolean value, an error value, a string value, or a blank string value.
1252
+ # @param data [String]
1253
+ # @return [Hash]
1254
+ def formulavalue(data)
1255
+ f_expr_o = data[6..7].unpack('v').first
1256
+
1257
+ return { _value: xnum(data), _type: :float } if f_expr_o != 0xFFFF # value is Xnum if last 2 bytes are FFFF
1258
+
1259
+ byte1_val = data[0].unpack('C').first
1260
+ value_type = {
1261
+ 0 => :string,
1262
+ 1 => :boolean,
1263
+ 2 => :error,
1264
+ 3 => :blank_string
1265
+ }[byte1_val]
1266
+
1267
+ value = case value_type
1268
+ when :string then nil # value is stored in a String record that immediately follows Formula record
1269
+ when :boolean then data[2] != "\0" # byte3 specifies Boolean value
1270
+ when :error then berr(data[2].unpack('C').first) # byte 3 specifies an error
1271
+ when :blank_string then ''
1272
+ else raise "Unknown value type #{byte1_val} in FormulaValue structure"
1273
+ end
1274
+
1275
+ { _value: value, _type: value_type }
1276
+ end
1277
+
1278
+ # See 2.4.122 Font, bFamily
1279
+ # @param id [Integer]
1280
+ # @return [Symbol]
1281
+ def _font_family(id)
1282
+ {
1283
+ 0x00 => :'Not applicable',
1284
+ 0x01 => :Roman,
1285
+ 0x02 => :Swiss,
1286
+ 0x03 => :Modern,
1287
+ 0x04 => :Script,
1288
+ 0x05 => :Decorative
1289
+ }[id]
1290
+ end
1291
+
1292
+ # 2.5.131 FontScheme
1293
+ # The FontScheme enumeration specifies the font scheme to which this font belongs.
1294
+ # @param data [String]
1295
+ # @return [Symbol]
1296
+ def fontscheme(data)
1297
+ id = data.unpack('C').first
1298
+
1299
+ name = {
1300
+ 0x00 => :XFSNONE, # No font scheme
1301
+ 0x01 => :XFSMAJOR, # Major scheme
1302
+ 0x02 => :XFSMINOR, # Minor scheme
1303
+ 0xff => :XFSNIL # Ninched state
1304
+ }[id]
1305
+
1306
+ {
1307
+ FontScheme: id,
1308
+ FontScheme_d: name,
1309
+ }
1310
+ end
1311
+
1312
+ # 2.5.132 FormatRun
1313
+ # The FormatRun structure specifies formatting information for a text run.
1314
+ # @param io [StringIO]
1315
+ # @return [Hash]
1316
+ def formatrun(io)
1317
+ ich, ifnt = io.read(4).unpack('vv')
1318
+
1319
+ {
1320
+ ich: ich, # ich (2 bytes): An unsigned integer that specifies the zero-based index of the first character of the text that contains the text run.
1321
+ ifnt: ifnt # ifnt (2 bytes): A FontIndex structure that specifies the font. If ich is equal to the length of the text, this record is undefined and MUST be ignored.
1322
+ }
1323
+ end
1324
+
1325
+ # 2.5.134 FrtFlags
1326
+ # The FrtFlags structure specifies flags used in future record headers.
1327
+ # @param data [String]
1328
+ # @return [Hash]
1329
+ def frtflags(data)
1330
+ attrs = Unxls::BitOps.new(data)
1331
+
1332
+ {
1333
+ fFrtRef: attrs.set_at?(0), # A - fFrtRef (1 bit): A bit that specifies whether the containing record specifies a range of cells.
1334
+ fFrtAlert: attrs.set_at?(1) # B - fFrtAlert (1 bit): A bit that specifies whether to alert the user of possible problems when saving the file without having recognized this record.
1335
+ # reserved (14 bits): MUST be zero, and MUST be ignored.
1336
+ }
1337
+ end
1338
+
1339
+ # 2.5.135 FrtHeader
1340
+ # The FrtHeader structure specifies a future record type header.
1341
+ # @param data [String]
1342
+ # @return [Hash]
1343
+ def frtheader(data)
1344
+ rt, grbit_frt = data.unpack('vvC*')
1345
+
1346
+ {
1347
+ rt: rt, # rt (2 bytes): An unsigned integer that specifies the record type identifier. MUST be identical to the record type identifier of the containing record.
1348
+ grBitFrt: frtflags(grbit_frt) # grbitFrt (2 bytes): An FrtFlags that specifies attributes for this record. The value of grbitFrt.fFrtRef MUST be zero. The value of grbitFrt.fFrtAlert MUST be zero.
1349
+ # reserved (8 bytes): MUST be zero, and MUST be ignored.
1350
+ }
1351
+ end
1352
+
1353
+ # 2.5.137 FrtRefHeader
1354
+ # The FrtRefHeader structure specifies a future record type header.
1355
+ # @param io [StringIO]
1356
+ # @return [Hash]
1357
+ def frtrefheader(io)
1358
+ result = frtheader(io.read(4)) # rt, grbitFrt
1359
+ result[:ref8] = ref8u(io.read(8)) # ref8 (8 bytes): A Ref8U that references the range of cells associated with the containing record.
1360
+ result
1361
+ end
1362
+
1363
+ # 2.5.139 FrtRefHeaderU, page 681
1364
+ # The FrtRefHeaderU structure specifies a future record type header.
1365
+ # @param io [StringIO]
1366
+ # @return [Hash]
1367
+ alias :frtrefheaderu :frtrefheader
1368
+
1369
+ # 2.5.138 FrtRefHeaderNoGrbit
1370
+ # @param io [StringIO]
1371
+ # @return [Hash]
1372
+ def frtrefheadernogrbit(io)
1373
+ {
1374
+ dt: io.read(2).unpack('v').first,
1375
+ ref8: ref8u(io.read(8))
1376
+ }
1377
+ end
1378
+
1379
+ # 2.5.143 FtCmo
1380
+ # The FtCmo structure specifies the common properties of the Obj record that contains this FtCmo.
1381
+ # @param data [String]
1382
+ def ftcmo(data)
1383
+ ft, cb, ot, id, attrs, _ = data.unpack('v5V3')
1384
+
1385
+ ot_d = {
1386
+ 0x00 => :Group,
1387
+ 0x01 => :Line,
1388
+ 0x02 => :Rectangle,
1389
+ 0x03 => :Oval,
1390
+ 0x04 => :Arc,
1391
+ 0x05 => :Chart,
1392
+ 0x06 => :Text,
1393
+ 0x07 => :Button,
1394
+ 0x08 => :Picture,
1395
+ 0x09 => :Polygon,
1396
+ 0x0B => :Checkbox,
1397
+ 0x0C => :'Radio button',
1398
+ 0x0D => :'Edit box',
1399
+ 0x0E => :Label,
1400
+ 0x0F => :'Dialog box',
1401
+ 0x10 => :'Spin control',
1402
+ 0x11 => :Scrollbar,
1403
+ 0x12 => :List,
1404
+ 0x13 => :'Group box',
1405
+ 0x14 => :'Dropdown list',
1406
+ 0x19 => :Note,
1407
+ 0x1E => :'OfficeArt object',
1408
+ }[ot]
1409
+
1410
+ attrs = Unxls::BitOps.new(attrs)
1411
+
1412
+ {
1413
+ ft: ft, # ft (2 bytes): Reserved. MUST be 0x15.
1414
+ cb: cb, # cb (2 bytes): Reserved. MUST be 0x12.
1415
+ ot: ot, # ot (2 bytes): An unsigned integer that specifies the type of object represented by the Obj record that contains this FtCmo.
1416
+ ot_d: ot_d,
1417
+ id: id, # id (2 bytes): An unsigned integer that specifies the identifier of this object. This object identifier is used by other types to refer to this object. The value of id MUST be unique among all Obj records within the Chart Sheet Substream ABNF, Macro Sheet Substream ABNF and Worksheet Substream ABNF.
1418
+ fLocked: attrs.set_at?(0), # A - fLocked (1 bit): A bit that specifies whether this object is locked.
1419
+ # B - reserved (1 bit): Reserved. MUST be 0.
1420
+ fDefaultSize: attrs.set_at?(2), # C - fDefaultSize (1 bit): A bit that specifies whether the application is expected to choose the object’s size.
1421
+ fPublished: attrs.set_at?(3), # D - fPublished (1 bit): A bit that specifies whether this is a chart object that is expected to be published the next time the sheet containing it is published<172>. This bit is ignored if the fPublishedBookItems field of the BookExt_Conditional12 structure is zero.
1422
+ fPrint: attrs.set_at?(4), # E - fPrint (1 bit): A bit that specifies whether the image of this object is intended to be included when printed.
1423
+ # F, G - unused1, 2 (1 bit): Undefined and MUST be ignored.
1424
+ fDisabled: attrs.set_at?(7), # H - fDisabled (1 bit): A bit that specifies whether this object has been disabled.
1425
+ fUIObj: attrs.set_at?(8), # I - fUIObj (1 bit): A bit that specifies whether this is an auxiliary object that can only be automatically inserted by the application (as opposed to an object that can be inserted by a user).
1426
+ fRecalcObj: attrs.set_at?(9), # J - fRecalcObj (1 bit): A bit that specifies whether this object is expected to be updated on load to reflect the values in the range associated with the object.
1427
+ # K, L - unused3, 4 (1 bit): Undefined and MUST be ignored.
1428
+ fRecalcObjAlways: attrs.set_at?(12), # M - fRecalcObjAlways (1 bit): A bit that specifies whether this object is expected to be updated whenever the value of a cell in the range associated with the object changes.
1429
+ # N, O, P - unused5, 6, 7 (1 bit): Undefined and MUST be ignored.
1430
+ # unused8, 9, 10 (4 bytes): Undefined and MUST be ignored.
1431
+ }
1432
+ end
1433
+
1434
+ # 2.5.155 FullColorExt
1435
+ # The FullColorExt structure specifies a color.
1436
+ # @param data [String]
1437
+ # @return [Hash]
1438
+ def fullcolorext(data)
1439
+ io = StringIO.new(data)
1440
+ xclr_type, n_tint_shade = io.read(4).unpack('vs<')
1441
+
1442
+ type = xcolortype(xclr_type)
1443
+
1444
+ {
1445
+ xclrType: xclr_type, # xclrType (2 bytes): An XColorType that specifies how the color information is stored.
1446
+ xclrType_d: type,
1447
+ nTintShade: n_tint_shade, # nTintShade (2 bytes): A signed integer that specifies the tint of the color. Positive values lighten the color, and negative values darken the color.
1448
+ nTintShade_d: _map_tint(n_tint_shade),
1449
+ xclrValue: _interpret_xclr(type, io.read(4)), # xclrValue (4 bytes): An unsigned integer that specifies the color data.
1450
+ # unused (8 bytes): Undefined and MUST be ignored.
1451
+ }
1452
+ end
1453
+
1454
+ # 2.5.156 GradStop
1455
+ # The GradStop structure specifies a gradient stop for a gradient fill.
1456
+ # @param io [StringIO]
1457
+ # @return [Hash]
1458
+ def gradstop(io)
1459
+ xclr_type = io.read(2).unpack('v').first
1460
+ type = xcolortype(xclr_type)
1461
+
1462
+ result = {
1463
+ xclrType: xclr_type, # xclrType (2 bytes): An XColorType that specifies how the color information is stored.
1464
+ xclrType_d: type,
1465
+ xclrValue: _interpret_xclr(type, io.read(4)), # xclrValue (4 bytes): An unsigned integer that specifies the color data.
1466
+ numPosition: xnum(io.read(8)).round(2), # numPosition (8 bytes): An Xnum (section 2.5.342) that specifies the gradient stop position as the percentage of the gradient range. The gradient stop position is the position within the gradient range where this gradient stop’s color begins.
1467
+ }
1468
+
1469
+ num_tint = xnum(io.read(8)).round(2)
1470
+ result[:numTint] = num_tint # numTint (8 bytes): An Xnum that specifies the tint of the color.
1471
+ result[:nTintShade_d] = num_tint # for compability with FullColorExt
1472
+
1473
+ result
1474
+ end
1475
+
1476
+ # 2.5.159 HorizAlign
1477
+ # @param id [Integer]
1478
+ # @return [Symbol]
1479
+ def horizalign(id)
1480
+ {
1481
+ 0xFF => :ALCNIL, # Alignment not specified
1482
+ 0x00 => :ALCGEN, # General alignment
1483
+ 0x01 => :ALCLEFT, # Left alignment
1484
+ 0x02 => :ALCCTR, # Centered alignment
1485
+ 0x03 => :ALCRIGHT, # Right alignment
1486
+ 0x04 => :ALCFILL, # Fill alignment
1487
+ 0x05 => :ALCJUST, # Justify alignment
1488
+ 0x06 => :ALCCONTCTR, # Center-across-selection alignment
1489
+ 0x07 => :ALCDIST, # Distributed alignment
1490
+ }[id]
1491
+ end
1492
+
1493
+ def _icf_template_d(value)
1494
+ {
1495
+ 0x0000 => :'Cell value',
1496
+ 0x0001 => :'Formula',
1497
+ 0x0002 => :'Color scale formatting',
1498
+ 0x0003 => :'Data bar formatting',
1499
+ 0x0004 => :'Icon set formatting',
1500
+ 0x0005 => :'Filter',
1501
+ 0x0007 => :'Unique values',
1502
+ 0x0008 => :'Contains text',
1503
+ 0x0009 => :'Contains blanks',
1504
+ 0x000A => :'Contains no blanks',
1505
+ 0x000B => :'Contains errors',
1506
+ 0x000C => :'Contains no errors',
1507
+ 0x000F => :'Today',
1508
+ 0x0010 => :'Tomorrow',
1509
+ 0x0011 => :'Yesterday',
1510
+ 0x0012 => :'Last 7 days',
1511
+ 0x0013 => :'Last month',
1512
+ 0x0014 => :'Next month',
1513
+ 0x0015 => :'This week',
1514
+ 0x0016 => :'Next week',
1515
+ 0x0017 => :'Last week',
1516
+ 0x0018 => :'This month',
1517
+ 0x0019 => :'Above average',
1518
+ 0x001A => :'Below average',
1519
+ 0x001B => :'Duplicate values',
1520
+ 0x001D => :'Above or equal to average',
1521
+ 0x001E => :'Below or equal to average',
1522
+ }[value]
1523
+ end
1524
+
1525
+ # 2.5.161 Icv
1526
+ # The Icv structure specifies a color in the color table.
1527
+ # @param id [Integer]
1528
+ # @return [Hash]
1529
+ def _icv_d(id)
1530
+ result = Unxls::Biff8::Constants::ICV_COLOR_TABLE[id]
1531
+
1532
+ result[:type] = case id
1533
+ when 0x0000..0x0007 then :builtin # The values that are greater than or equal to 0x0000 and less than or equal to 0x0007 specify built-in color constants. See table at p. 699
1534
+ when 0x0008..0x003F then :palette # The next 56 values in the table, the icv values greater than or equal to 0x0008 and less than or equal to 0x003F, specify the palette colors in the table. If a Palette record exists in this file, these icv values specify colors from the rgColor array in the Palette record. If no Palette record exists, these values specify colors in the default palette. See table at p. 699
1535
+ when 0x0040, 0x0041, 0x004D..0x004F, 0x0051, 0x7FFF then :display_settings # The remaining values in the color table specify colors associated with application display settings, see p. 701
1536
+ else raise "Unexpected icv value #{id}"
1537
+ end
1538
+
1539
+ result
1540
+ end
1541
+
1542
+ # 2.5.164 IcvXF
1543
+ # @param value [Integer]
1544
+ # @return [Integer]
1545
+ def icvxf(value)
1546
+ Unxls::BitOps.new(value).value_at(0..6)
1547
+ end
1548
+
1549
+ # Unsigned 8 bit integer, little endian
1550
+ # @param data [String]
1551
+ # @return [Integer]
1552
+ def _int1b(data)
1553
+ data.unpack('C').first
1554
+ end
1555
+
1556
+ # Unsigned 16 bit integer, little endian
1557
+ # @param data [String]
1558
+ # @return [Integer]
1559
+ def _int2b(data)
1560
+ data.unpack('v').first
1561
+ end
1562
+
1563
+ # Unsigned 32 bit integer, little endian
1564
+ # @param data [String]
1565
+ # @return [Integer]
1566
+ def _int4b(data)
1567
+ data.unpack('V').first
1568
+ end
1569
+
1570
+ # @param type [Symbol]
1571
+ # @param data [String]
1572
+ # @return [Integer, Symbol, String]
1573
+ def _interpret_xclr(type, data)
1574
+ case type
1575
+ when :XCLRINDEXED then icvxf(data.unpack('V').first) # See 2.5.164 IcvXF
1576
+ when :XCLRRGB then longrgba(data) # See 2.5.178 LongRGBA
1577
+ when :XCLRTHEMED then data.unpack('V').first # See 2.5.49 ColorTheme
1578
+ else data
1579
+ end
1580
+ end
1581
+
1582
+ # 2.5.172 LEMMode
1583
+ # The LEMMode enumeration specifies the different edit modes for a table.
1584
+ # @param value [Integer]
1585
+ # @return [Symbol]
1586
+ def lemmode(value)
1587
+ {
1588
+ 0x00 => :LEMNORMAL, # The table can be directly edited inline.
1589
+ 0x01 => :LEMREFRESHCOPY, # The table is refreshed before editing is allowed because is it a copy of a table whose source is a Web based data provider list.
1590
+ 0x02 => :LEMREFRESHCACHE, # The table is refreshed before editing is allowed because caching a user change failed.
1591
+ 0x03 => :LEMREFRESHCACHEUNDO, # The table is refreshed before editing is allowed because undoing a cached user change failed.
1592
+ 0x04 => :LEMREFRESHLOADED, # The table is refreshed before editing is allowed because on load the table source could not be re-connected.
1593
+ 0x05 => :LEMREFRESHTEMPLATE, # The table is refreshed before editing is allowed because it was saved without having its data cached.
1594
+ 0x06 => :LEMREFRESHREFRESH, # The table is refreshed before editing is allowed because a previous refresh failed.
1595
+ 0x07 => :LEMNOINSROWSSPREQUIRED, # Rows cannot be inserted into this web based data provider list because there are hidden required columns.
1596
+ 0x08 => :LEMNOINSROWSSPDOCLIB, # Rows cannot be inserted into this Web based data provider list because it is a document library.
1597
+ 0x09 => :LEMREFRESHLOADDISCARDED, # The table is refreshed before editing is allowed because the user selected to discard cached changes upon loading.
1598
+ 0x0A => :LEMREFRESHLOADHASHVALIDATION, # The table is refreshed before editing is allowed because the validation of the table's data area failed upon loading.
1599
+ 0x0B => :LEMNOEDITSPMODVIEW, # Cannot allow the user to edit this table because of the type of moderated Web based data provider list it is.
1600
+ }[value]
1601
+ end
1602
+
1603
+ # 2.5.174 List12BlockLevel
1604
+ # The List12BlockLevel structure specifies default block-level formatting information for a table, to be applied when the table expands. Style gets applied before DXFN12List for each table region.
1605
+ # @param io [StringIO]
1606
+ # @return [String]
1607
+ def list12blocklevel(io)
1608
+ vars = io.read(36).unpack('l<*')
1609
+
1610
+ result = {
1611
+ cbdxfHeader: vars[0], # cbdxfHeader (4 bytes): A signed integer that specifies the byte count for dxfHeader field. MUST be greater than or equal to zero.
1612
+ istnHeader: vars[1], # istnHeader (4 bytes): A signed integer that specifies a zero-based index to a Style record in the collection of Style records in the Globals Substream. The referenced Style specifies the cell style XF used for the table’s header row cells. If the value is -1, no style is specified for the table’s header row cells.
1613
+ cbdxfData: vars[2], # cbdxfData (4 bytes): A signed integer that specifies the byte count for dxfData field. MUST be greater than or equal to zero.
1614
+ istnData: vars[3], # istnData (4 bytes): A signed integer that specifies a zero-based index to a Style record in the collection of Style records in the Globals Substream. The referenced Style specifies the cell style used for the table’s data cells. If the value is -1, no style is specified for the table’s data cells.
1615
+ cbdxfAgg: vars[4], # cbdxfAgg (4 bytes): A signed integer that specifies the byte count for dxfAgg field. MUST be greater than or equal to zero.
1616
+ istnAgg: vars[5], # istnAgg (4 bytes): A signed integer that specifies a zero-based index to a Style record in the collection of Style records in the Globals Substream. The referenced Style specifies the cell style used for the table’s total row. If the value is -1, no style is specified for the table’s total row.
1617
+ cbdxfBorder: vars[6], # cbdxfBorder (4 bytes): A signed integer that specifies the byte count for dxfBorder field. MUST be greater than or equal to zero.
1618
+ cbdxfHeaderBorder: vars[7], # cbdxfHeaderBorder (4 bytes): A signed integer that specifies the byte count for dxfHeaderBorder field. MUST be greater than or equal to zero.
1619
+ cbdxfAggBorder: vars[8], # cbdxfAggBorder (4 bytes): A signed integer that specifies the byte count for dxfAggBorder field. MUST be greater than or equal to zero.
1620
+ }
1621
+
1622
+ result[:dxfHeader] = dxfn12list(io.read(result[:cbdxfHeader])) unless result[:cbdxfHeader].zero? # dxfHeader (variable): An optional DXFN12List that specifies the formatting for the table’s header row cells. MUST exist if and only if cbdxfHeader is nonzero.
1623
+ result[:dxfData] = dxfn12list(io.read(result[:cbdxfData])) unless result[:cbdxfData].zero? # dxfData (variable): An optional DXFN12List that specifies the formatting for the table’s data cells. MUST exist if and only if cbdxfData is nonzero.
1624
+ result[:dxfAgg] = dxfn12list(io.read(result[:cbdxfAgg])) unless result[:cbdxfAgg].zero? # dxfAgg (variable): An optional DXFN12List that specifies the formatting for the table’s total row. MUST exist if and only if cbdxfAgg is nonzero.
1625
+ result[:dxfBorder] = dxfn12list(io.read(result[:cbdxfBorder])) unless result[:cbdxfBorder].zero? # dxfBorder (variable): An optional DXFN12 that specifies the formatting for the border of the table’s data cells. MUST exist if and only if cbdxfBorder is nonzero.
1626
+ result[:dxfHeaderBorder] = dxfn12list(io.read(result[:cbdxfHeaderBorder])) unless result[:cbdxfHeaderBorder].zero? # dxfHeaderBorder (variable): An optional DXFN12List that specifies the formatting for the border of the table’s header row cells. MUST exist if and only if cbdxfHeaderBorder is nonzero.
1627
+ result[:dxfAggBorder] = dxfn12list(io.read(result[:cbdxfAggBorder])) unless result[:cbdxfAggBorder].zero? # dxfAggBorder (variable): An optional DXFN12List that specifies the formatting for the border of the table’s total row. MUST exist if and only if cbdxfAggBorder is nonzero.
1628
+
1629
+ result[:stHeader] = xlunicodestring(io) unless result[:istnHeader] == -1 # stHeader (variable): An optional XLUnicodeString that specifies the name of the style for the table’s header row cells. MUST exist if and only if istnHeader is not equal to -1. MUST be equal to the name of the Style record specified by istnHeader. If the style is a user-defined style, stHeader MUST be equal to the user field of the Style record.
1630
+ result[:stData] = xlunicodestring(io) unless result[:istnData] == -1 # stData (variable): An optional XLUnicodeString that specifies the name of the style for the table’s data cells. MUST exist if and only if istnData is not equal to -1. MUST be equal to the name of the Style record specified by istnData. If the style is a user-defined style, stData MUST be equal to the user field of the Style record.
1631
+ result[:stAgg] = xlunicodestring(io) unless result[:istnAgg] == -1 # stAgg (variable): An optional XLUnicodeString that specifies the name of the style for the table’s total row. MUST exist if and only if istnAgg is not equal to -1. MUST be equal to the name of the Style record specified by istnAgg. If the style is a user-defined style, stAgg MUST be equal to the user field of the Style record.
1632
+
1633
+ result
1634
+ end
1635
+
1636
+ # 2.5.175 List12DisplayName
1637
+ # The List12DisplayName structure specifies the name and comment strings for the table.
1638
+ # @param io [StringIO]
1639
+ # @return [String]
1640
+ def list12displayname(io)
1641
+ {
1642
+ stListName: xlunicodestring(io), # stListName (variable): An XLNameUnicodeString that specifies the table name. MUST be an empty string if the rgbName field of the TableFeatureType structure embedded in the Feature11 or Feature12 record that specifies the table is not empty. If the table name is not the same as the rgbName field of the TableFeatureType structure for this table, the table name is specified in stListName which is a case-insensitive unique name among all table names and defined names in the workbook.
1643
+ stListComment: xlunicodestring(io) # stListComment (variable): An XLUnicodeString that specifies a comment about the table.
1644
+ }
1645
+ end
1646
+
1647
+ # 2.5.176 List12TableStyleClientInfo
1648
+ # The List12TableStyleClientInfo record specifies information about the style applied to a table.
1649
+ # @param io [StringIO]
1650
+ # @return [String]
1651
+ def list12tablestyleclientinfo(io)
1652
+ attrs = io.read(2).unpack('v').first
1653
+ attrs = Unxls::BitOps.new(attrs)
1654
+
1655
+ {
1656
+ fFirstColumn: attrs.set_at?(0), # A - fFirstColumn (1 bit): A bit that specifies whether any table style elements (as specified by TableStyleElement) with a tseType field equal to 0x00000003 will be applied.
1657
+ fLastColumn: attrs.set_at?(1), # B - fLastColumn (1 bit): A bit that specifies whether any table style elements (as specified by TableStyleElement) with a tseType field equal to 0x00000004 will be applied.
1658
+ fRowStripes: attrs.set_at?(2), # C - fRowStripes (1 bit): A bit that specifies whether any table style elements (as specified by TableStyleElement) with a tseType field equal to 0x00000005 or 0x00000006 will be applied.
1659
+ fColumnStripes: attrs.set_at?(3), # D - fColumnStripes (1 bit): A bit that specifies whether any table style elements (as specified by TableStyleElement) with a tseType field equal to 0x00000007 or 0x00000008 will be applied.
1660
+ # E - unused1 (2 bits): Undefined and MUST be ignored.
1661
+ fDefaultStyle: attrs.set_at?(5), # F - fDefaultStyle (1 bit): A bit that specifies whether the style whose name is specified by stListStyleName is the default table style.
1662
+ # unused2 (9 bits): Undefined and MUST be ignored.
1663
+ stListStyleName: xlunicodestring(io) # stListStyleName (variable): An XLUnicodeString that specifies the name of the table style for the table. Length MUST be greater than zero and less than or equal to 255 characters. If the table style is a custom style, it is defined in a TableStyle record that has rgchName equal to this value.
1664
+ }
1665
+ end
1666
+
1667
+ # @todo 2.5.198.19 ListParsedArrayFormula
1668
+ # The ListParsedArrayFormula structure specifies a formula (section 2.2.2) used in a table.
1669
+ # @param io [StringIO]
1670
+ # @raises [RuntimeError] .rgbextra
1671
+ def listparsedarrayformula(io)
1672
+ cce = io.read(2).unpack('v').first # cce (2 bytes): An unsigned integer that specifies the length of rgce in bytes. MUST be greater than 0.
1673
+ io.pos += cce # rgce (variable): An Rgce that specifies the sequence of Ptgs for the formula. MUST NOT contain PtgExp, PtgTbl, PtgElfLel, PtgElfRw, PtgElfCol, PtgElfRwV, PtgElfColV, PtgElfRadical, PtgElfRadicalS, PtgElfColS, PtgElfColSV, PtgElfRadicalLel, or PtgSxName.
1674
+ rgbextra(io) # rgcb (variable): An RgbExtra that specifies ancillary data for the formula.
1675
+ end
1676
+
1677
+ # @todo 2.5.198.20 ListParsedFormula
1678
+ # The ListParsedFormula structure specifies a formula (section 2.2.2) used in a table.
1679
+ # @param io [StringIO]
1680
+ # @return [Symbol]
1681
+ def listparsedformula(io)
1682
+ cce = io.read(2).unpack('v').first # cce (2 bytes): An unsigned integer that specifies the length of rgce in bytes. MUST be greater than 0.
1683
+ io.pos += cce # rgce (variable): An Rgce that specifies the sequence of Ptgs for the formula. MUST NOT contain PtgExp, PtgTbl, PtgElfLel, PtgElfRw, PtgElfCol, PtgElfRwV, PtgElfColV, PtgElfRadical, PtgElfRadicalS, PtgElfColS, PtgElfColSV, PtgElfRadicalLel, or PtgSxName.
1684
+
1685
+ :not_implemented
1686
+ end
1687
+
1688
+ # 2.5.178 LongRGBA
1689
+ # The LongRGBA structure specifies a color as a combination of red, green, blue and alpha values.
1690
+ # @param data [String]
1691
+ # @return [Array<Symbol>]
1692
+ def longrgba(data)
1693
+ data.unpack('H*').first.upcase.to_sym
1694
+ end
1695
+
1696
+ # 2.5.179 LPWideString
1697
+ # The LPWideString type specifies a Unicode string which is prefixed by a length.
1698
+ # @param data [String, StringIO]
1699
+ # @return [String]
1700
+ def lpwidestring(data)
1701
+ io = data.to_sio
1702
+ cch = io.read(2).unpack('v').first
1703
+ _read_unicodestring(io, cch, 1) # has double-byte characters
1704
+ end
1705
+
1706
+ # See 2.5.113 Feat11FieldDataItem
1707
+ # @param value [Integer]
1708
+ # @return [Symbol]
1709
+ def _lfxidt_d(value)
1710
+ {
1711
+ 0x1000 => :SOMITEM_SCHEMA,
1712
+ 0x1001 => :SOMITEM_ATTRIBUTE,
1713
+ 0x1002 => :SOMITEM_ATTRIBUTEGROUP,
1714
+ 0x1003 => :SOMITEM_NOTATION,
1715
+ 0x1100 => :SOMITEM_IDENTITYCONSTRAINT,
1716
+ 0x1101 => :SOMITEM_KEY,
1717
+ 0x1102 => :SOMITEM_KEYREF,
1718
+ 0x1103 => :SOMITEM_UNIQUE,
1719
+ 0x2000 => :SOMITEM_ANYTYPE,
1720
+ 0x2100 => :SOMITEM_DATATYPE,
1721
+ 0x2101 => :SOMITEM_DATATYPE_ANYTYPE,
1722
+ 0x2102 => :SOMITEM_DATATYPE_ANYURI,
1723
+ 0x2103 => :SOMITEM_DATATYPE_BASE64BINARY,
1724
+ 0x2104 => :SOMITEM_DATATYPE_BOOLEAN,
1725
+ 0x2105 => :SOMITEM_DATATYPE_BYTE,
1726
+ 0x2106 => :SOMITEM_DATATYPE_DATE,
1727
+ 0x2107 => :SOMITEM_DATATYPE_DATETIME,
1728
+ 0x2108 => :SOMITEM_DATATYPE_DAY,
1729
+ 0x2109 => :SOMITEM_DATATYPE_DECIMAL,
1730
+ 0x210A => :SOMITEM_DATATYPE_DOUBLE,
1731
+ 0x210B => :SOMITEM_DATATYPE_DURATION,
1732
+ 0x210C => :SOMITEM_DATATYPE_ENTITIES,
1733
+ 0x210D => :SOMITEM_DATATYPE_ENTITY,
1734
+ 0x210E => :SOMITEM_DATATYPE_FLOAT,
1735
+ 0x210F => :SOMITEM_DATATYPE_HEXBINARY,
1736
+ 0x2110 => :SOMITEM_DATATYPE_ID,
1737
+ 0x2111 => :SOMITEM_DATATYPE_IDREF,
1738
+ 0x2112 => :SOMITEM_DATATYPE_IDREFS,
1739
+ 0x2113 => :SOMITEM_DATATYPE_INT,
1740
+ 0x2114 => :SOMITEM_DATATYPE_INTEGER,
1741
+ 0x2115 => :SOMITEM_DATATYPE_LANGUAGE,
1742
+ 0x2116 => :SOMITEM_DATATYPE_LONG,
1743
+ 0x2117 => :SOMITEM_DATATYPE_MONTH,
1744
+ 0x2118 => :SOMITEM_DATATYPE_MONTHDAY,
1745
+ 0x2119 => :SOMITEM_DATATYPE_NAME,
1746
+ 0x211A => :SOMITEM_DATATYPE_NCNAME,
1747
+ 0x211B => :SOMITEM_DATATYPE_NEGATIVEINTEGER,
1748
+ 0x211C => :SOMITEM_DATATYPE_NMTOKEN,
1749
+ 0x211D => :SOMITEM_DATATYPE_NMTOKENS,
1750
+ 0x211E => :SOMITEM_DATATYPE_NONNEGATIVEINTEGER,
1751
+ 0x211F => :SOMITEM_DATATYPE_NONPOSITIVEINTEGER,
1752
+ 0x2120 => :SOMITEM_DATATYPE_NORMALIZEDSTRING,
1753
+ 0x2121 => :SOMITEM_DATATYPE_NOTATION,
1754
+ 0x2122 => :SOMITEM_DATATYPE_POSITIVEINTEGER,
1755
+ 0x2123 => :SOMITEM_DATATYPE_QNAME,
1756
+ 0x2124 => :SOMITEM_DATATYPE_SHORT,
1757
+ 0x2125 => :SOMITEM_DATATYPE_STRING,
1758
+ 0x2126 => :SOMITEM_DATATYPE_TIME,
1759
+ 0x2127 => :SOMITEM_DATATYPE_TOKEN,
1760
+ 0x2128 => :SOMITEM_DATATYPE_UNSIGNEDBYTE,
1761
+ 0x2129 => :SOMITEM_DATATYPE_UNSIGNEDINT,
1762
+ 0x212A => :SOMITEM_DATATYPE_UNSIGNEDLONG,
1763
+ 0x212B => :SOMITEM_DATATYPE_UNSIGNEDSHORT,
1764
+ 0x212C => :SOMITEM_DATATYPE_YEAR,
1765
+ 0x212D => :SOMITEM_DATATYPE_YEARMONTH,
1766
+ 0x21FF => :SOMITEM_DATATYPE_ANYSIMPLETYPE,
1767
+ 0x2200 => :SOMITEM_SIMPLETYPE,
1768
+ 0x2400 => :SOMITEM_COMPLEXTYPE,
1769
+ 0x4000 => :SOMITEM_PARTICLE,
1770
+ 0x4001 => :SOMITEM_ANY,
1771
+ 0x4002 => :SOMITEM_ANYATTRIBUTE,
1772
+ 0x4003 => :SOMITEM_ELEMENT,
1773
+ 0x4100 => :SOMITEM_GROUP,
1774
+ 0x4101 => :SOMITEM_ALL,
1775
+ 0x4102 => :SOMITEM_CHOICE,
1776
+ 0x4103 => :SOMITEM_SEQUENCE,
1777
+ 0x4104 => :SOMITEM_EMPTYPARTICLE,
1778
+ 0x0800 => :SOMITEM_NULL,
1779
+ 0x2800 => :SOMITEM_NULL_TYPE,
1780
+ 0x4801 => :SOMITEM_NULL_ANY,
1781
+ 0x4802 => :SOMITEM_NULL_ANYATTRIBUTE,
1782
+ 0x4803 => :SOMITEM_NULL_ELEMENT,
1783
+ }[value]
1784
+ end
1785
+
1786
+ # Map value to range -1.0..1.0
1787
+ # @param value [Integer] 16-bit signed int
1788
+ # @return [Float]
1789
+ def _map_tint(value)
1790
+ return 0.0 if value.zero? # save a few cycles
1791
+ result = (value < 0) ? (value / 32768.0) : (value / 32767.0)
1792
+ result.round(2)
1793
+ end
1794
+
1795
+ # 2.5.186 NoteSh
1796
+ # The NoteSh structure specifies a comment associated with a cell.
1797
+ # @param io [StringIO]
1798
+ # @return [Hash]
1799
+ def notesh(io)
1800
+ row, col, attrs, id_obj = io.read(8).unpack('v4')
1801
+ attrs = Unxls::BitOps.new(attrs)
1802
+ {
1803
+ # changed to 'rw' for compability with cell fields
1804
+ rw: row, # row (2 bytes): A RW that specifies the row of the cell to which this comment is associated.
1805
+ col: col, # col (2 bytes): A Col that specifies the column of the cell to which this comment is associated.
1806
+ # A - reserved1 (1 bit): MUST be zero and MUST be ignored.
1807
+ fShow: attrs.set_at?(1), # B - fShow (1 bit): A bit that specifies whether the comment is shown at all times.
1808
+ # (C) reserved2, (D) unused1, (E) reserved3
1809
+ fRwHidden: attrs.set_at?(7), # F - fRwHidden (1 bit): A bit that specifies whether the row specified by row is hidden.
1810
+ fColHidden: attrs.set_at?(8), # G - fColHidden (1 bit): A bit that specifies whether the column specified by col is hidden.
1811
+ # reserved4 (7 bits): MUST be zero and MUST be ignored.
1812
+ idObj: id_obj, # idObj (2 bytes): An ObjId (2.5.188) that specifies the Obj record that specifies the comment text.
1813
+ stAuthor: xlunicodestring(io), # stAuthor (variable): An XLUnicodeString that specifies the name of the comment author. String length MUST be greater than or equal to 1 and less than or equal to 54.
1814
+ # unused2 (1 byte): Undefined and MUST be ignored.
1815
+ }
1816
+ end
1817
+
1818
+ # 2.5.201 Phs
1819
+ # The Phs structure specifies the formatting information for a phonetic string.
1820
+ # @param data [String]
1821
+ # @return [Hash]
1822
+ def phs(data)
1823
+ ifnt, attrs = data.unpack('vv')
1824
+ attrs = Unxls::BitOps.new(attrs)
1825
+
1826
+ ph_type = attrs.value_at(0..1)
1827
+ ph_type_d = {
1828
+ 0x0 => :narrow_katakana,
1829
+ 0x1 => :wide_katakana,
1830
+ 0x2 => :hiragana,
1831
+ 0x3 => :any # Use any type of characters as phonetic string
1832
+ }[ph_type]
1833
+
1834
+ alc_h = attrs.value_at(2..3)
1835
+ alc_h_d = {
1836
+ 0x0 => :general, # General alignment
1837
+ 0x1 => :left, # Left aligned
1838
+ 0x2 => :center, # Center aligned
1839
+ 0x3 => :distributed # Distributed alignment
1840
+ }[alc_h]
1841
+
1842
+ {
1843
+ ifnt: ifnt, # ifnt (2 bytes): A FontIndex structure that specifies the font.
1844
+ phType: ph_type, # A - phType (2 bits): An unsigned integer that specifies the type of the phonetic information.
1845
+ phType_d: ph_type_d,
1846
+ alcH: alc_h, # B - alcH (2 bits): An unsigned integer that specifies the alignment of the phonetic string.
1847
+ alcH_d: alc_h_d
1848
+ # unused (12 bits): Undefined and MUST be ignored.
1849
+ }
1850
+ end
1851
+
1852
+ # 2.5.206 ReadingOrder
1853
+ # @param id [Integer]
1854
+ # @return [Symbol]
1855
+ def readingorder(id)
1856
+ {
1857
+ 0 => :READING_ORDER_CONTEXT, # Context reading order
1858
+ 1 => :READING_ORDER_LTR, # Left-to-right reading order
1859
+ 2 => :READING_ORDER_RTL, # Right-to-left reading order
1860
+ }[id]
1861
+ end
1862
+
1863
+ # @param record [Unxls::Biff8::Record]
1864
+ # @param cch [Integer]
1865
+ # @param high_byte [true, false]
1866
+ # @return [String]
1867
+ def _read_continued_string(record, cch, high_byte)
1868
+ cch_read = 0
1869
+ result = String.new
1870
+
1871
+ # What Excel writes to cch is not really a number of characters in the string,
1872
+ # but rather a string bytesize / 2 if fHighByte is true, or just string bytesize if not,
1873
+ # so careful byte counting required when reading rgb field in case there are characters
1874
+ # encoded with more than 2 bytes (e.g. surrogate pairs):
1875
+
1876
+ while cch > cch_read
1877
+ if record.bytes.eof?
1878
+ record.open_next_record_block
1879
+ attrs = record.bytes.read(1).unpack('C').first # fHighByte may be different in every Continue block
1880
+ high_byte = Unxls::BitOps.new(attrs).set_at?(0)
1881
+ end
1882
+ encoding, char_size = _encoding_params(high_byte)
1883
+ bytes_to_read = (cch - cch_read) * char_size
1884
+ string_bytes = record.bytes.read(bytes_to_read)
1885
+ cch_read += string_bytes.size / char_size
1886
+ result << string_bytes.force_encoding(encoding).encode(Encoding::UTF_8)
1887
+ break if record.end_of_data?
1888
+ end
1889
+
1890
+ result
1891
+ end
1892
+
1893
+ # @param io [StringIO]
1894
+ # @param cch [Integer]
1895
+ # @param flags [Integer]
1896
+ # @return [String]
1897
+ def _read_unicodestring(io, cch, flags)
1898
+ high_byte = Unxls::BitOps.new(flags).set_at?(0)
1899
+ encoding, char_size = _encoding_params(high_byte)
1900
+ _encode_string(io.read(cch * char_size), encoding)
1901
+ end
1902
+
1903
+ # 2.5.208 Ref8
1904
+ # The Ref8 structure specifies a range of cells on the sheet.
1905
+ # @param data [String]
1906
+ # @return [Hash]
1907
+ def ref8(data)
1908
+ rw_first, rw_last, col_first, col_last = data.unpack('v4')
1909
+
1910
+ # If rwFirst is 0 and rwLast is 0xFFFF, the specified range includes all the rows in the sheet
1911
+ # Same holds for columns
1912
+ {
1913
+ rwFirst: rw_first, # 0-based index of first row in the range
1914
+ rwLast: rw_last, # … last row in the range
1915
+ colFirst: col_first, # 0-based index of first column in the range
1916
+ colLast: col_last # … last column in the range
1917
+ }
1918
+ end
1919
+
1920
+ # 2.5.209 Ref8U
1921
+ # The Ref8U structure specifies a range of cells on the sheet.
1922
+ # @param data [String]
1923
+ # @return [Hash]
1924
+ alias :ref8u :ref8 # structurally the same
1925
+
1926
+ # @todo 2.5.198.103 RgbExtra
1927
+ # The RgbExtra structure specifies a set of structures, laid out sequentially in the file, that correspond to and MUST exist for certain Ptgs in the Rgce. The order of the structures MUST be the same as the order of the Ptgs in the Rgce that they correspond to.
1928
+ # @param io [StringIO]
1929
+ # @raises [RuntimeError]
1930
+ def rgbextra(io)
1931
+ # To find out the size of RgbExtra extra one must read all the ptgs, which requires
1932
+ # implementing formula parsing, which in turn is a fairly complex task (maybe in future).
1933
+ # But for now just
1934
+ raise 'Cannot parse RgbExtra structure yet, sorry.'
1935
+ end
1936
+
1937
+ # 2.5.200 PhRuns
1938
+ # The PhRuns structure specifies a phonetic text run that is displayed above a text run.
1939
+ # @param io [StringIO]
1940
+ # @return [Hash]
1941
+ def rgphruns(io)
1942
+ ich_first, ich_mom, cch_mom = io.read(6).unpack('s<s<s<')
1943
+
1944
+ {
1945
+ ichFirst: ich_first, # ichFirst (2 bytes): A signed integer that specifies the zero-based index of the first character of the phonetic text run in the rphssub.st field of the ExtRst structure that contains this PhRuns structure.
1946
+ ichMom: ich_mom, # ichMom (2 bytes): A signed integer that specifies the zero-based index of the first character of the text run
1947
+ cchMom: cch_mom # cchMom (2 bytes): A signed integer that specifies the count of characters in the text run specified in ichMom.
1948
+ }
1949
+ end
1950
+
1951
+ # 2.5.217 RkNumber
1952
+ # @param value [Integer]
1953
+ # @return [Hash]
1954
+ def rknumber(value)
1955
+ data = Unxls::BitOps.new(value)
1956
+
1957
+ f_x100 = data.set_at?(0) # num divided by 100?
1958
+ f_int = data.set_at?(1) # 0 - num is 64-bit floating point number, 1 - signed integer
1959
+ num_raw = ((value >> 2) << 2) # drop bits 0 and 1
1960
+
1961
+ num = if f_int
1962
+ [num_raw].pack('V').unpack('l<').first # 32 bit signed little endian
1963
+ else
1964
+ num_to_64 = [num_raw].pack('xxxxV')
1965
+ num_to_64.unpack('E').first
1966
+ end
1967
+
1968
+ f_x100 ? (num / 100.0) : num
1969
+ end
1970
+
1971
+ # 2.5.218 RkRec
1972
+ # @param data [String]
1973
+ # @return [Hash]
1974
+ def rkrec(data)
1975
+ ixfe, rk_raw = data.unpack('vV')
1976
+ {
1977
+ ixfe: ixfe, # cell XF index
1978
+ RK: rknumber(rk_raw) # Numeric value
1979
+ }
1980
+ end
1981
+
1982
+ # 2.5.219 RPHSSub
1983
+ # The RPHSSub structure specifies a phonetic string.
1984
+ # @param io [StringIO]
1985
+ # @return [Hash]
1986
+ def rphssub(io)
1987
+ crun, cch = io.read(4).unpack('vv')
1988
+
1989
+ {
1990
+ crun: crun, # crun (2 bytes): An unsigned integer that specifies the number of phonetic text runs. MUST be less than or equal to 32767. If crun is zero, there is one phonetic text run.
1991
+ cch: cch, # cch (2 bytes): An unsigned integer that specifies the number of characters in the phonetic string.
1992
+ st: lpwidestring(io) # st (variable): An LPWideString that specifies the phonetic string. The character count in the string MUST be cch.
1993
+ }
1994
+ end
1995
+
1996
+ # 2.5.226 Run
1997
+ # @param io [StringIO]
1998
+ # @return [Hash]
1999
+ def run(io)
2000
+ result = formatrun(io) # read 4 bytes
2001
+ io.read(4) # skip unused1, unused2
2002
+ result
2003
+ end
2004
+
2005
+ # 2.5.232 Script
2006
+ # @param id [Integer]
2007
+ # @return [Symbol]
2008
+ def script(id)
2009
+ {
2010
+ 0x00 => :SSSNONE, # Normal script
2011
+ 0x01 => :SSSSUPER, # Superscript
2012
+ 0x02 => :SSSSUB, # Subscript
2013
+ 0xFF => :ignored, # Indicates that this specification is to be ignored
2014
+ }[id]
2015
+ end
2016
+
2017
+ # 2.5.237 SharedFeatureType
2018
+ # The SharedFeatureType enumeration specifies the different types of Shared Features.
2019
+ # @param value [Integer]
2020
+ # @return [String]
2021
+ def sharedfeaturetype(value)
2022
+ {
2023
+ 0x02 => :ISFPROTECTION, # Specifies the enhanced protection type. A Shared Feature of this type is used to protect a shared workbook by restricting access to the areas of the workbook and to the available functionality.
2024
+ 0x03 => :ISFFEC2, # Specifies the ignored formula errors type. A Shared Feature of this type is used to specify the formula errors to be ignored.
2025
+ 0x04 => :ISFFACTOID, # Specifies the smart tag type. A Shared Feature of this type is used to recognize certain types of entries (for example, proper names, dates/times, financial symbols) and flag them for action.
2026
+ 0x05 => :ISFLIST, # Specifies the list type. A Shared Feature of this type is used to describe a table within a sheet.
2027
+ }[value]
2028
+ end
2029
+
2030
+ # 2.5.240 ShortXLUnicodeString
2031
+ # The ShortXLUnicodeString structure specifies a Unicode string.
2032
+ # @param io [StringIO]
2033
+ # @return [String]
2034
+ def shortxlunicodestring(io)
2035
+ cch, flags = io.read(2).unpack('CC')
2036
+ _read_unicodestring(io, cch, flags)
2037
+ end
2038
+
2039
+ # Signed 16 bit integer, native endian
2040
+ # @param data [String]
2041
+ # @return [Integer]
2042
+ def _sint2b(data)
2043
+ data.unpack('s').first
2044
+ end
2045
+
2046
+ # 2.5.244 SourceType
2047
+ # The SourceType enumeration specifies the source type for a table.
2048
+ # @param value [Integer]
2049
+ # @return [Symbol]
2050
+ def sourcetype(value)
2051
+ {
2052
+ 0x00 => :LTRANGE, # Range
2053
+ 0x01 => :LTSHAREPOINT, # Read/write Web-based data provider list
2054
+ 0x02 => :LTXML, # XML Mapper data
2055
+ 0x03 => :LTEXTERNALDATA, # External data source (query table)<180>
2056
+ }[value]
2057
+ end
2058
+
2059
+ # 2.5.246 SqRef
2060
+ # The SqRef structure specifies a sequence of Ref8 structures on the sheet.
2061
+ # @param io [StringIO]
2062
+ # @return [Hash]
2063
+ def sqref(io)
2064
+ cref = io.read(2).unpack('v').first
2065
+
2066
+ {
2067
+ cref: cref, # cref (2 bytes): An unsigned integer that specifies the number of elements in rgrefs. MUST be less than or equal to 0x2000.
2068
+ rgrefs: cref.times.map { ref8u(io.read(8)) } # rgrefs (variable): An array of Ref8 structures. The number of elements in the array MUST be equal to cref.
2069
+ }
2070
+ end
2071
+
2072
+ # 2.5.247 SqRefU
2073
+ alias :sqrefu :sqref
2074
+
2075
+ # 2.5.248 Stxp, page 858
2076
+ # Specifies various formatting attributes of a font.
2077
+ # @param data [String]
2078
+ # @return [Hash]
2079
+ def stxp(data)
2080
+ twp_height, ts_raw, bls, sss, uls, b_family, b_char_set, _ = data.unpack('l<Vs<s<CCCC')
2081
+
2082
+ {
2083
+ twpHeight: twp_height, # twpHeight (4 bytes): A signed integer that specifies the height of the font in twips. This value MUST be -1, 0, or between 20 and 8191. This value SHOULD NOT<181> be 0. A value of -1 specifies that this field is to be ignored.
2084
+ ts: ts(ts_raw), # ts (4 bytes): A Ts that specifies additional formatting attributes.
2085
+ bls: bls, # bls (2 bytes): A signed integer that specifies the font weight.
2086
+ bls_d: Unxls::Biff8::Structure.bold(bls),
2087
+ sss: sss, # sss (2 bytes): A signed integer that specifies whether the superscript or subscript or normal style of the font is used.
2088
+ sss_d: Unxls::Biff8::Structure.script(sss),
2089
+ uls: uls, # uls (1 byte): An unsigned integer that specifies the underline style.
2090
+ uls_d: Unxls::Biff8::Structure.underline(uls),
2091
+ bFamily: b_family, # bFamily (1 byte): An unsigned integer that specifies the font family, as defined by Windows API LOGFONT structure in [MSDN-FONTS].
2092
+ bFamily_d: Unxls::Biff8::Structure._font_family(b_family),
2093
+ bCharSet: b_char_set, # bCharSet (1 byte): An unsigned integer that specifies the character set, as defined by Windows API LOGFONT structure in [MSDN-FONTS].
2094
+ bCharSet_d: Unxls::Biff8::Structure._character_set(b_char_set),
2095
+ # unused (1 byte): Undefined and MUST be ignored.
2096
+ }
2097
+ end
2098
+
2099
+ # 2.5.249 StyleXF
2100
+ # @param data [String]
2101
+ # @return [Hash]
2102
+ def stylexf(data)
2103
+ result = cellxf(data)
2104
+ # CellXF and StyleXF are identical except for the following fields which are not used in StyleXFs:
2105
+ %i(fAtrNum fAtrFnt fAtrAlc fAtrBdr fAtrPat fAtrProt fsxButton).each { |k| result.delete(k) }
2106
+ result
2107
+ end
2108
+
2109
+ # 2.5.254 SXAxis
2110
+ # The SXAxis structure specifies the PivotTable axis referred to by the containing record.
2111
+ # @param value [Integer]
2112
+ # @return [Hash]
2113
+ def sxaxis4data(value)
2114
+ attrs = Unxls::BitOps.new(value)
2115
+
2116
+ {
2117
+ sxaxisRw: attrs.set_at?(0), # A - sxaxisRw (1 bit): A bit that specifies whether this structure refers to the row axis.
2118
+ sxaxisCol: attrs.set_at?(1), # B - sxaxisCol (1 bit): A bit that specifies whether this structure refers to the column axis.
2119
+ sxaxisPage: attrs.set_at?(2), # C - sxaxisPage (1 bit): A bit that specifies whether this structure refers to the page axis.
2120
+ sxaxisData: attrs.set_at?(3), # D - sxaxisData (1 bit): A bit that specifies whether this structure refers to the value axis.
2121
+ # reserved (12 bits): MUST be zero, and MUST be ignored.
2122
+ }
2123
+ end
2124
+
2125
+ # 2.5.266 TableFeatureType
2126
+ # The TableFeatureType structure specifies the definition of a table within a sheet.
2127
+ # @param io [StringIO]
2128
+ # @return [Hash]
2129
+ def tablefeaturetype(io)
2130
+ lt, id_list, crw_header, crw_totals, id_field_next, cb_fs_data, rup_build, _ = io.read(28).unpack('VVVVVVvv')
2131
+
2132
+ result = {
2133
+ lt: lt, # lt (4 bytes): A SourceType that specifies the type of data source for the table.
2134
+ lt_d: sourcetype(lt),
2135
+ idList: id_list, # idList (4 bytes): An unsigned integer that specifies an identifier for the table. MUST be unique within the sheet. SHOULD<183> be unique within the workbook.
2136
+ crwHeader: crw_header == 1, # crwHeader (4 bytes): A Boolean (section 2.5.14) that specifies whether the table has a header row.
2137
+ crwTotals: crw_totals == 1, # crwTotals (4 bytes): A Boolean that specifies whether there is a total row.
2138
+ idFieldNext: id_field_next, # idFieldNext (4 bytes): An unsigned integer that specifies the next unique identifier to use when assigning unique identifiers to the fieldData.idField field of the table.
2139
+ cbFSData: cb_fs_data, # cbFSData (4 bytes): An unsigned integer that specifies the size, in bytes, of the fixed portion of this structure. The fixed portion starts at the lt field and ends at the rgbHashParam field. MUST be equal to 64.
2140
+ rupBuild: rup_build, # rupBuild (2 bytes): An unsigned integer that specifies the build number of the application that wrote the structure.
2141
+ # unused1 (2 bytes): Undefined, and MUST be ignored.
2142
+ }
2143
+
2144
+ attrs = Unxls::BitOps.new(io.read(4).unpack('V').first)
2145
+ result.merge!({
2146
+ # A - unused2 (1 bit): Undefined, and MUST be ignored.
2147
+ fAutoFilter: attrs.set_at?(1), # B - fAutoFilter (1 bit): A bit that specifies whether the table has an AutoFilter. MUST be 1 when fPersistAutoFilter is 1.
2148
+ fPersistAutoFilter: attrs.set_at?(2), # C - fPersistAutoFilter (1 bit): A bit that specifies whether the AutoFilter is preserved for this table after data refresh operations.<184>
2149
+ fShowInsertRow: attrs.set_at?(3), # D - fShowInsertRow (1 bit): A bit that specifies whether the insert row is visible. MUST be 1 if fInsertRowInsCells is 1.
2150
+ fInsertRowInsCells: attrs.set_at?(4), # E - fInsertRowInsCells (1 bit): A bit that specifies whether rows below the table are shifted down because of the insert row being visible.
2151
+ fLoadPldwIdDeleted: attrs.set_at?(5), # F - fLoadPldwIdDeleted (1 bit): A bit that specifies whether the idDeleted field is present. MUST be zero if the lt field is not set to 0x00000001.
2152
+ fShownTotalRow: attrs.set_at?(6), # G - fShownTotalRow (1 bit): A bit that specifies whether the total row was ever visible.
2153
+ # H - reserved1 (1 bit): MUST be zero and MUST be ignored.
2154
+ fNeedsCommit: attrs.set_at?(8), # I - fNeedsCommit (1 bit): A bit that specifies whether table modifications were not synchronized with the data source. MUST be zero if the lt field is not set to 0x00000001.
2155
+ fSingleCell: attrs.set_at?(9), # J - fSingleCell (1 bit): A bit that specifies whether the table is limited to a single cell. The table cannot have header rows, total rows, or multiple columns. If fSingleCell equals 1, the lt field MUST be set to 0x00000002.
2156
+ # K - reserved2 (1 bit): MUST be zero and MUST be ignored.
2157
+ fApplyAutoFilter: attrs.set_at?(11), # L - fApplyAutoFilter (1 bit): A bit that specifies whether the AutoFilter is currently applied. MUST be 1 if the AutoFilter is currently applied<185>.
2158
+ fForceInsertToBeVis: attrs.set_at?(12), # M - fForceInsertToBeVis (1 bit): A bit that specifies whether the insert row is forced to be visible because the table has no data.
2159
+ fCompressedXml: attrs.set_at?(13), # N - fCompressedXml (1 bit): A bit that specifies whether the cached data for this table in the List Data stream is compressed. MUST be zero if the lt field is not set to 0x00000001.
2160
+ fLoadCSPName: attrs.set_at?(14), # O - fLoadCSPName (1 bit): A bit that specifies whether the cSPName field is present. MUST be zero if the lt field is not set to 0x00000001.
2161
+ fLoadPldwIdChanged: attrs.set_at?(15), # P - fLoadPldwIdChanged (1 bit): A bit that specifies whether idChanged field is present. MUST be zero if the lt field is not set to 0x00000001.
2162
+ verXL: attrs.value_at(16..19), # verXL (4 bits): An unsigned integer that specifies the application version under which the table was created. MUST be either 0xB or 0xC<186>.
2163
+ fLoadEntryId: attrs.set_at?(20), # Q - fLoadEntryId (1 bit): A bit that specifies whether the entryId field is present.
2164
+ fLoadPllstclInvalid: attrs.set_at?(21), # R - fLoadPllstclInvalid (1 bit): A bit that specifies whether the cellInvalid field is present. MUST be zero if the lt field is not set to 0x00000001.
2165
+ fGoodRupBld: attrs.set_at?(22), # S - fGoodRupBld (1 bit): A bit that specifies whether the rupBuild field is valid.
2166
+ # T - unused3 (1 bit): Undefined, and MUST be ignored.
2167
+ fPublished: attrs.set_at?(24), # U - fPublished (1 bit): A bit that specifies whether the table is published. This bit is ignored if the fPublishedBookItems field of the BookExt_Conditional12 structure is zero.
2168
+ # reserved3 (7 bits): Undefined, and MUST be ignored.
2169
+ })
2170
+
2171
+ _, _, _, lem, _ = io.read(32).unpack('V3VC*')
2172
+ result.merge!({
2173
+ lPosStmCache: :not_implemented, # lPosStmCache (4 bytes): An unsigned integer that specifies the position of the cached data within the List Data stream. Undefined and MUST be ignored if the lt field is not set to 0x00000001.
2174
+ cbStmCache: :not_implemented, # cbStmCache (4 bytes): An unsigned integer that specifies the size, in bytes, of the cached data within the List Data stream. Undefined and MUST be ignored if the lt field is not set to 0x00000001.
2175
+ cchStmCache: :not_implemented, # cchStmCache (4 bytes): An unsigned integer that specifies the count of characters of the cached data within the List Data stream when the cached data is uncompressed. Undefined and MUST be ignored if the lt field is not set to 0x00000001.
2176
+ LEMMode: lemmode(lem), # lem (4 bytes): A LEMMode enumeration that specifies the table edit mode. If lt is set to 0x00000000, 0x00000002 or 0x00000003, this field MUST be set to 0x00000000.
2177
+ rgbHashParam: :not_implemented, # rgbHashParam (16 bytes): An array of bytes that specifies round-trip information. SHOULD<187> be ignored and MUST be preserved if the lt field is set to 0x00000001. Undefined and MUST be ignored if the lt field is not set to 0x00000001.
2178
+ })
2179
+
2180
+ result[:rgbName] = xlunicodestring(io) # rgbName (variable): An XLUnicodeString that specifies the name of the table. MUST be unique per workbook, and case-sensitive in all locales.
2181
+ result[:cFieldData] = io.read(2).unpack('v').first # cFieldData (2 bytes): An unsigned integer that specifies the number of columns in the table. MUST be greater than or equal to 0x0001 and less than or equal to 0x0100.
2182
+
2183
+ if result[:fLoadCSPName]
2184
+ result[:cSPName] = xlunicodestring(io) # cSPName (variable): An XLUnicodeString that specifies the name of the cryptographic service provider used to specify rgbHashParam. This field is present only if fLoadCSPName is set to 1.
2185
+ end
2186
+
2187
+ if result[:fLoadEntryId]
2188
+ result[:entryId] = xlunicodestring(io) # entryId (variable): An XLUnicodeString that specifies a unique identifier for the table. The string equals the value of the idList field, represented in decimal format, without any leading zeros. It is used when lt equals 0x00000002 and ignored otherwise. This field is present only if fLoadEntryId is set to 1.
2189
+ end
2190
+
2191
+ result[:fieldData] = result[:cFieldData].times.map { feat11fielddataitem(io, result) } # fieldData (variable): An array of Feat11FieldDataItem that contains the specification of the columns of the table. The number of items in this array is specified by the cFieldData field.
2192
+
2193
+ if result[:fLoadPldwIdDeleted]
2194
+ result[:idDeleted] = :not_implemented # idDeleted (variable): A Feat11RgSharepointIdDel structure that specifies the identifiers of deleted rows. This information is used when synchronizing with the Web based data provider’s data source. This field is only present if the fLoadPldwIdDeleted field is set to 1.
2195
+ end
2196
+
2197
+ if result[:fLoadPldwIdChanged]
2198
+ result[:idChanged] = :not_implemented # idChanged (variable): A Feat11RgSharepointIdChange structure that specifies the identifiers of the edited rows. This information is used when synchronizing with the Web based data provider’s data source. This field is only present if the fLoadPldwIdChanged field is set to 1.
2199
+ end
2200
+
2201
+ if result[:fLoadPllstclInvalid]
2202
+ result[:cellInvalid] = :not_implemented # cellInvalid (variable): A Feat11RgInvalidCells structure that specifies the location of cells within the table that contain values that are invalid based on validation rules on the Web based data provider. This field is only present if the fLoadPllstclInvalid field is set to 1.
2203
+ end
2204
+
2205
+ result
2206
+ end
2207
+
2208
+ # 2.5.270 Ts, page 879
2209
+ # The Ts structure specifies the italic and strikethrough formatting of a font.
2210
+ # @param value [Integer]
2211
+ # @return [Hash]
2212
+ def ts(value)
2213
+ a_unused3 = Unxls::BitOps.new(value)
2214
+
2215
+ {
2216
+ # 0 (A) unused1 Undefined and MUST be ignored.
2217
+ ftsItalic: a_unused3.set_at?(1), # 1 (B) Specifies whether the text style is italic.
2218
+ # 2…6 unused2 (5 bits): Undefined and MUST be ignored.
2219
+ ftsStrikeout: a_unused3.set_at?(7) # 7 (C) Specifies whether the font has strikethrough formatting applied.
2220
+ # 8…31 unused3 (24 bits): Undefined and MUST be ignored.
2221
+ }
2222
+ end
2223
+
2224
+ # See 2.4.321 TableStyleElement, p. 541
2225
+ # @param value [Integer]
2226
+ # @return [Hash]
2227
+ def _tse_type(value)
2228
+ {
2229
+ 0x00 => :whole_table, # Whole table. If this table style is applied to a PivotTable view, this formatting type also applies to page field captions and page item captions.
2230
+ 0x01 => :header_row, # Header row. If this table style is applied to a PivotTable view, this formatting type applies to the collection of rows above the data region. See S in the PivotTable Style Diagram.
2231
+ 0x02 => :total_row, # Total row. If this table style is applied to a PivotTable view, this formatting type applies to the grand total row. See N in the PivotTable Style Diagram.
2232
+ 0x03 => :first_column, # First column. If this table style is applied to a PivotTable view, this formatting type applies to the row label area, which can span multiple columns. See R in the PivotTable Style Diagram.
2233
+ 0x04 => :last_column, # Last column. If this table style is applied to a PivotTable view, this formatting type applies to the grand total column. See A in the PivotTable Style Diagram.
2234
+ 0x05 => :row_stripe_1, # Row stripe band 1
2235
+ 0x06 => :row_stripe_2, # Row stripe band 2
2236
+ 0x07 => :column_stripe_1, # Column stripe band 1
2237
+ 0x08 => :column_stripe_2, # Column stripe band 2
2238
+ 0x09 => :first_cell_header, # First cell of Header row. If this table style is applied to a PivotTable view, this formatting type applies to cells contained in area intersected by the header row and first column.
2239
+ 0x0A => :last_cell_header, # Last cell of Header row. MUST be ignored if this table style is applied to a PivotTable view.
2240
+ 0x0B => :first_cell_total, # First cell of Total row. MUST be ignored if this table style is applied to a PivotTable view.
2241
+ 0x0C => :last_cell_total, # Last cell of Total row. MUST be ignored if this table style is applied to a PivotTable view.
2242
+ 0x0D => :pt_outermost_subtotal_columns, # Outermost subtotal columns in a PivotTable view, specified by the columns displaying subtotals for the first Sxvd record in the PIVOTVD collection where the sxaxis field of the Sxvd record specifies the column axis. See B in the PivotTable Style Diagram. Used only for PivotTables.
2243
+ 0x0E => :pt_alternating_even_subtotal_columns, # Alternating even subtotal columns in a PivotTable view, specified by the columns displaying subtotals for Sxvd records for which the zero-based index in the PIVOTVD collection is an odd number, omitting Sxvd records where the sxaxis field of the Sxvd record does not specify the column axis. See C in the PivotTable Style Diagram. Used only for PivotTables.
2244
+ 0x0F => :pt_alternating_odd_subtotal_columns, # Alternating odd subtotal columns in a PivotTable view, specified by the columns displaying subtotals for Sxvd records for which the zero-based index in the PIVOTVD collection is an even number greater than zero, omitting Sxvd records where the sxaxis field of the Sxvd record does not specify the column axis. See D in the PivotTable Style Diagram. Used only for PivotTables.
2245
+ 0x10 => :pt_outermost_subtotal_rows, # Outermost subtotal rows in a PivotTable view, specified by the rows displaying subtotals for the first Sxvd record in the PIVOTVD collection where the sxaxis field of the Sxvd record specifies the row axis. See M in the PivotTable Style Diagram. Used only for PivotTables.
2246
+ 0x11 => :pt_alternating_even_subtotal_rows, # Alternating even subtotal rows in a PivotTable view, specified by the rows displaying subtotals for Sxvd records for which the zero-based index in the PIVOTVD collection is an odd number, omitting Sxvd records where the sxaxis field of the Sxvd record does not specify the row axis. See K in the PivotTable Style Diagram. Used only for PivotTables.
2247
+ 0x12 => :pt_alternating_odd_subtotal_rows, # Alternating odd subtotal rows in a PivotTable view, specified by the rows displaying subtotals for Sxvd records for which the zero-based index in the PIVOTVD collection is an even number greater than zero, omitting Sxvd records where the sxaxis field of the Sxvd record does not specify the row axis. See J in the PivotTable Style Diagram. Used only for PivotTables.
2248
+ 0x13 => :pt_empty_rows_after_each_subtotal_row, # Empty rows after each subtotal row. See L in the PivotTable Style Diagram. Used only for PivotTables.
2249
+ 0x14 => :pt_outermost_column_subheadings, # Outermost column subheadings in a PivotTable view, specified by the columns displaying pivot field captions for the first Sxvd record in the PIVOTVD collection where the sxaxis field of the Sxvd record specifies the column axis. See O in the PivotTable Style Diagram. Used only for PivotTables.
2250
+ 0x15 => :pt_alternating_even_column_subheadings, # Alternating even column subheadings in a PivotTable view, specified by the column columns displaying pivot field captions for Sxvd records for which the zero-based index in the PIVOTVD collection is an odd number, omitting Sxvd records where the sxaxis field of the Sxvd record does not specify the column axis. See P in the PivotTable Style Diagram. Used only for PivotTables.
2251
+ 0x16 => :pt_alternating_odd_column_subheadings, # Alternating odd column subheadings in a PivotTable view, specified by the columns displaying pivot field captions for Sxvd records for which the zero-based index in the PIVOTVD collection is an even number greater than zero, omitting Sxvd records where the sxaxis field of the Sxvd record does not specify the column axis. See Q in the PivotTable Style Diagram. Used only for PivotTables.
2252
+ 0x17 => :pt_outermost_row_subheadings, # Outermost row subheadings in a PivotTable view, specified by the rows displaying pivot field captions for the first Sxvd record in the PIVOTVD collection where the sxaxis field of the Sxvd record specifies the row axis. See G in the PivotTable Style Diagram. Used only for PivotTables.
2253
+ 0x18 => :pt_alternating_even_row_subheadings, # Alternating even row subheadings in a PivotTable view, specified by the rows displaying pivot field captions for Sxvd records for which the zero-based index in the PIVOTVD collection is an odd number, omitting Sxvd records where the sxaxis field of the Sxvd record does not specify the row axis. See H in the PivotTable Style Diagram. Used only for PivotTables.
2254
+ 0x19 => :pt_alternating_odd_row_subheadings, # Alternating odd row subheadings in a PivotTable view, specified by the rows displaying pivot field captions for Sxvd records for which the zero-based index in the PIVOTVD collection is an even number greater than zero, omitting Sxvd records where the sxaxis field of the Sxvd record does not specify the row axis. See I in the PivotTable Style Diagram. Used only for PivotTables.
2255
+ 0x1A => :pt_page_field_captions, # Page field captions in a PivotTable view, specified by the cells displaying pivot field captions for the Sxvd records in the PIVOTVD collection where the sxaxis field of the Sxvd record specifies the page axis. See F in the PivotTable Style Diagram. Used only for PivotTables.
2256
+ 0x1B => :pt_page_item_captions, # Page item captions in a PivotTable view, specified by the cells displaying pivot item captions for the Sxvd records in the PIVOTVD collection where the sxaxis field of the Sxvd record specifies the page axis. See E in the PivotTable Style Diagram. Used only for PivotTables.
2257
+ }[value]
2258
+ end
2259
+
2260
+ # 2.5.274 Underline
2261
+ # @param id [Integer]
2262
+ # @return [Symbol]
2263
+ def underline(id)
2264
+ {
2265
+ 0x00 => :ULSNONE, # No underline
2266
+ 0x01 => :ULSSINGLE, # Single
2267
+ 0x02 => :ULSDOUBLE, # Double
2268
+ 0x21 => :ULSSINGLEACCOUNTANT, # Single accounting
2269
+ 0x22 => :ULSDOUBLEACCOUNTANT, # Double accounting
2270
+ 0xFF => :ignored # Indicates that this specification is to be ignored
2271
+ }[id]
2272
+ end
2273
+
2274
+ # 2.5.275 VertAlign
2275
+ # @param id [Integer]
2276
+ # @return [Symbol]
2277
+ def vertalign(id)
2278
+ {
2279
+ 0 => :ALCVTOP, # Top alignment
2280
+ 1 => :ALCVCTR, # Center alignment
2281
+ 2 => :ALCVBOT, # Bottom alignment
2282
+ 3 => :ALCVJUST, # Justify alignment
2283
+ 4 => :ALCVDIST, # Distributed alignment
2284
+ }[id]
2285
+ end
2286
+
2287
+ # See p. 1087, notes 30-36
2288
+ # @param id [Integer]
2289
+ # @return [Symbol]
2290
+ def _verxlhigh(id)
2291
+ {
2292
+ 0x0 => :'Excel 97',
2293
+ 0x1 => :'Excel 2000',
2294
+ 0x2 => :'Excel 2002',
2295
+ 0x3 => :'Excel 2003',
2296
+ 0x4 => :'Excel 2007',
2297
+ 0x6 => :'Excel 2010',
2298
+ 0x7 => :'Excel 2013'
2299
+ }[id]
2300
+ end
2301
+
2302
+ alias :_verlastxlsaved :_verxlhigh
2303
+
2304
+ # 2.5.279 XColorType
2305
+ # The XColorType enumeration specifies the color reference types.
2306
+ # @param id [Integer]
2307
+ # @return [Symbol]
2308
+ def xcolortype(id)
2309
+ {
2310
+ 0 => :XCLRAUTO, # Automatic foreground/background colors (not specified)
2311
+ 1 => :XCLRINDEXED, # xclrValue = index to palette color (IcvFX)
2312
+ 2 => :XCLRRGB, # xclrValue = RGB color (LongRGBA)
2313
+ 3 => :XCLRTHEMED, # xclrValue = index to Theme color (ColorTheme)
2314
+ 4 => :XCLRNINCHED # Color not set
2315
+ }[id]
2316
+ end
2317
+
2318
+ # Specifies the text alignment properties
2319
+ # 4-byte XF attribute structure common for:
2320
+ # 2.5.20 CellXF, page 597
2321
+ # 2.5.91 DXFALC, page 638
2322
+ # @param io [StringIO]
2323
+ # @return [Hash]
2324
+ def _xfalc(io)
2325
+ result = {}
2326
+
2327
+ attrs = Unxls::BitOps.new(io.read(4).unpack('V').first)
2328
+
2329
+ result[:alc] = (alc = attrs.value_at(0..2)) # alc (3 bits): A HorizAlign that specifies the horizontal alignment.
2330
+ result[:alc_d] = horizalign(alc)
2331
+
2332
+ result[:fWrap] = attrs.set_at?(3) # A - fWrap (1 bit): A bit that specifies the text display when the text is wider than the cell.
2333
+
2334
+ result[:alcV] = (alcv = attrs.value_at(4..6)) # alcV (3 bits): A VertAlign that specifies the vertical alignment.
2335
+ result[:alcV_d] = vertalign(alcv)
2336
+
2337
+ result[:fJustLast] = attrs.set_at?(7) # B - fJustLast (1 bit): A bit that specifies whether the justified or distributed alignment of the cell is used on the last line of text (setting this to 1 is typical for East Asian text but not typical in other contexts).
2338
+
2339
+ result[:trot] = (trot = attrs.value_at(8..15)) # trot (1 byte): An XFPropTextRotation that specifies the text rotation.
2340
+ result[:trot_d] = xfproptextrotation(trot)
2341
+
2342
+ result[:cIndent] = attrs.value_at(16..19) # cIndent (4 bits): An unsigned integer that specifies the text indentation level.
2343
+
2344
+ result[:fShrinkToFit] = attrs.set_at?(20) # C - fShrinkToFit (1 bit): A bit that specifies whether the cell is shrink to fit.
2345
+
2346
+ # CellXF: D - reserved1 (1 bit): MUST be 0, and MUST be ignored.
2347
+ # DXFALC: D - fMergeCell (1 bit): A bit that specifies that the cell MUST be merged.
2348
+ result[:fMergeCell] = attrs.set_at?(21)
2349
+
2350
+ result[:iReadingOrder] = (r_order = attrs.value_at(22..23)) # E - iReadingOrder (2 bits): A ReadingOrder that specifies the reading order.
2351
+ result[:iReadingOrder_d] = readingorder(r_order)
2352
+
2353
+ # 24…25 reserved2 2 bits (F)
2354
+
2355
+ # Do *not* update fields from parent XF?
2356
+ # ifmt
2357
+ result[:fAtrNum] = attrs.set_at?(26) # G - fAtrNum (1 bit): A bit that specifies that if the ifmt field of the XF record specified by the ixfParent field of the containing XF record is updated, the corresponding field of the containing XF record will *not* be set to the same value.
2358
+ # ifnt
2359
+ result[:fAtrFnt] = attrs.set_at?(27) # H - fAtrFnt (1 bit): A bit that specifies that if the ifnt field of the XF record specified by the ixfParent field of the containing XF record is updated, the corresponding field of the containing XF record will *not* be set to the same value.
2360
+ # alc, fWrap, alcV, fJustLast, trot, cIndent, fShrinkToFit, iReadingOrder
2361
+ result[:fAtrAlc] = attrs.set_at?(28) # I - fAtrAlc (1 bit): A bit that specifies that if the alc field, or the fWrap field, or the alcV field, or the fJustLast field, or the trot field, or the cIndent field, or the fShrinkToFit field or the iReadOrder field of the XF record specified by the ixfParent field of the containing XF record is updated, the corresponding fields of this structure will *not* be set to the same values.
2362
+ # dgLeft, dgRight, dgTop, dgBottom, dgDiag, icvLeft, icvRight, grbitDiag, icvTop, icvBottom, icvDiag
2363
+ result[:fAtrBdr] = attrs.set_at?(29) # J - fAtrBdr (1 bit): A bit that specifies that if the dgLeft field, or the dgRight field, or the dgTop field, or the dgBottom field, or the dgDiag field, or the icvLeft field, or the icvRight field, or the grbitDiag field, or the icvTop field, or the icvBottom field, or the icvDiag field of the XF record specified by the ixfParent field of the containing XF record is updated, the corresponding fields of this structure will *not* be set to the same values.
2364
+ # fls, icvFore, icvBack
2365
+ result[:fAtrPat] = attrs.set_at?(30) # K - fAtrPat (1 bit): A bit that specifies that if the fls field, the icvFore field, or the icvBack field of the XF record specified by the ixfParent field of the containing XF record is updated, the corresponding fields of this structure will *not* be set to the same values.
2366
+ # fLocked, fHidden
2367
+ result[:fAtrProt] = attrs.set_at?(31) # L - fAtrProt (1 bit): A bit that specifies that if the fLocked field or the fHidden field of the XF record specified by the ixfParent field of the containing XF record is updated, the corresponding fields of the containing XF record will *not* be set to the same values.
2368
+
2369
+ result
2370
+ end
2371
+
2372
+ # 8-byte XF attribute structure common for:
2373
+ # 2.5.20 CellXF, page 597
2374
+ # 2.5.92 DXFBdr, page 639
2375
+ # @param io [StringIO]
2376
+ # @return [Hash]
2377
+ def _xfbdr(io)
2378
+ result = {}
2379
+
2380
+ attrs = io.read(4).unpack('V').first
2381
+ attrs = Unxls::BitOps.new(attrs)
2382
+ dg_left = attrs.value_at(0..3) # dgLeft (4 bits): A BorderStyle that specifies the logical left border formatting.
2383
+ result[:dgLeft] = dg_left
2384
+ result[:dgLeft_d] = borderstyle(dg_left)
2385
+ dg_right = attrs.value_at(4..7) # dgRight (4 bits): A BorderStyle that specifies the logical right border formatting.
2386
+ result[:dgRight] = dg_right
2387
+ result[:dgRight_d] = borderstyle(dg_right)
2388
+ dg_top = attrs.value_at(8..11) # dgTop (4 bits): A BorderStyle that specifies the top border formatting.
2389
+ result[:dgTop] = dg_top
2390
+ result[:dgTop_d] = borderstyle(dg_top)
2391
+ dg_bottom = attrs.value_at(12..15) # dgBottom (4 bits): A BorderStyle that specifies the bottom border formatting.
2392
+ result[:dgBottom] = dg_bottom
2393
+ result[:dgBottom_d] = borderstyle(dg_bottom)
2394
+ result[:icvLeft] = attrs.value_at(16..22) # icvLeft (7 bits): An unsigned integer that specifies the color of the logical left border. The value MUST be one of the values specified in IcvXF or 0. A value of 0 means the logical left border color has not been specified.
2395
+ result[:icvRight] = attrs.value_at(23..29) # icvRight (7 bits): An unsigned integer that specifies the color of the logical right border. The value MUST be one of the values specified in IcvXF or 0. A value of 0 means the logical right border color has not been specified.
2396
+ grbit_diag = attrs.value_at(30..31) # grbitDiag (2 bits): An unsigned integer that specifies which diagonal borders are present (if any).
2397
+ result[:grbitDiag] = grbit_diag
2398
+ result[:grbitDiag_d] = {
2399
+ 0 => :'No diagonal border',
2400
+ 1 => :'Diagonal-down border',
2401
+ 2 => :'Diagonal-up border',
2402
+ 3 => :'Both diagonal-down and diagonal-up',
2403
+ }[grbit_diag]
2404
+
2405
+ attrs = io.read(4).unpack('V').first
2406
+ attrs = Unxls::BitOps.new(attrs)
2407
+ result[:icvTop] = attrs.value_at(0..6) # icvTop (7 bits): An unsigned integer that specifies the color of the top border. The value MUST be one of the values specified in IcvXF or 0. A value of 0 means the top border color has not been specified.
2408
+ result[:icvBottom] = attrs.value_at(7..13) # icvBottom (7 bits): An unsigned integer that specifies the color of the bottom border. The value MUST be one of the values specified in IcvXF or 0. A value of 0 means the bottom border color has not been specified.
2409
+ result[:icvDiag] = attrs.value_at(14..20) # icvDiag (7 bits): An unsigned integer that specifies the color of the diagonal border. The value MUST be one of the values specified in IcvXF or 0. A value of 0 means the diagonal border color has not been specified.
2410
+ dg_diag = attrs.value_at(21..24) # dgDiag (4 bits): A BorderStyle that specifies the diagonal border formatting.
2411
+ result[:dgDiag] = dg_diag
2412
+ result[:dgDiag_d] = borderstyle(dg_diag)
2413
+ result[:fHasXFExt] = attrs.set_at?(25) # fHasXFExt (1 bit): A bit that specifies whether an XFExt will extend the information in this XF.
2414
+ fls = attrs.value_at(26..31) # fls (6 bits): A FillPattern that specifies the fill pattern. If this value is 1, which specifies a solid fill pattern, then only icvFore is rendered.
2415
+ result[:fls] = fls
2416
+ result[:fls_d] = fillpattern(fls)
2417
+
2418
+ result
2419
+ end
2420
+
2421
+ # 2.5.280 XFExtGradient
2422
+ # The XFExtGradient structure specifies a gradient fill for a cell interior.
2423
+ # @param data [String]
2424
+ # @return [Hash]
2425
+ def xfextgradient(data)
2426
+ io = StringIO.new(data)
2427
+
2428
+ result = xfpropgradient(io.read(44)) # gradient (44 bytes): An XFPropGradient that specifies the gradient fill.
2429
+
2430
+ result[:cGradStops] = io.read(4).unpack('V').first # cGradStops (4 bytes): An unsigned integer that specifies the number of items in rgGradStops.
2431
+
2432
+ result[:rgGradStops] = [] # rgGradStops (variable): An array of GradStop. Each array element specifies a gradient stop for this gradient fill.
2433
+ result[:cGradStops].times { result[:rgGradStops] << gradstop(io) }
2434
+
2435
+ result
2436
+ end
2437
+
2438
+ # See:
2439
+ # 2.4.355 XFExt, page 585
2440
+ # 2.5.281 XFExtNoFRT, page 885
2441
+ # @param io [StringIO]
2442
+ # @return [Hash]
2443
+ def _xfext(io)
2444
+ _, ixfe, _, cexts = io.read(8).unpack('v4')
2445
+
2446
+ result = {
2447
+ # reserved1 (2 bytes): MUST be zero and MUST be ignored.
2448
+ ixfe: ixfe, # ixfe (2 bytes): An XFIndex structure that specifies the XF record in the file that this record extends.
2449
+ # reserved2 (2 bytes): MUST be zero and MUST be ignored.
2450
+ cexts: cexts, # cexts (2 bytes): An unsigned integer that specifies the number of elements in rgExt.
2451
+ rgExt: [] # rgExt (variable): An array of ExtProp. Each array element specifies a formatting property extension.
2452
+ }
2453
+
2454
+ cexts.times { result[:rgExt] << extprop(io) }
2455
+
2456
+ result
2457
+ end
2458
+
2459
+ # 2.5.281 XFExtNoFRT
2460
+ # The XFExtNoFRT structure specifies a set of extensions to formatting properties.
2461
+ # @param io [StringIO]
2462
+ # @return [Hash]
2463
+ def xfextnofrt(io)
2464
+ result = _xfext(io)
2465
+ result.delete(:ixfe)
2466
+ result
2467
+ end
2468
+
2469
+ # 2.5.283 XFProp
2470
+ # The XFProp structure specifies a formatting property.
2471
+ # @param io [StringIO]
2472
+ # @return [Hash]
2473
+ def xfprop(io)
2474
+ xf_prop_type, cb = io.read(4).unpack('vv')
2475
+ xf_prop_data_blob = io.read(cb - 4) # minus header length
2476
+
2477
+ attrs = _xfproptype(xf_prop_type)
2478
+ method = attrs[:structure].downcase
2479
+ unpack_method = attrs[:unpack]
2480
+ data = unpack_method ? self.send(unpack_method, xf_prop_data_blob) : xf_prop_data_blob
2481
+
2482
+ result = {
2483
+ xfPropType: xf_prop_type, # xfPropType (2 bytes): An unsigned integer that specifies the type of the formatting property.
2484
+ cb: cb, # cb (2 bytes): An unsigned integer that specifies the size of this XFProp structure.
2485
+ _description: attrs[:description],
2486
+ _structure: attrs[:structure],
2487
+ xfPropDataBlob_d: self.send(method, data) # xfPropDataBlob (variable): A field that specifies the formatting property data.
2488
+ }
2489
+
2490
+ result[:xfPropDataBlob] = xf_prop_data_blob if unpack_method
2491
+
2492
+ result
2493
+ end
2494
+
2495
+ # 2.5.284 XFPropBorder
2496
+ # @param data [String]
2497
+ # @return [Hash]
2498
+ def xfpropborder(data)
2499
+ io = StringIO.new(data)
2500
+ color_data = io.read(8)
2501
+ dg_border = io.read(2).unpack('v').first
2502
+
2503
+ {
2504
+ color: xfpropcolor(color_data),
2505
+ dgBorder: dg_border,
2506
+ dgBorder_d: borderstyle(dg_border)
2507
+ }
2508
+ end
2509
+
2510
+ # 2.5.285 XFPropColor
2511
+ # The XFPropColor structure specifies a color.
2512
+ # @param data [String]
2513
+ # @return [Hash]
2514
+ def xfpropcolor(data)
2515
+ io = StringIO.new(data)
2516
+ attrs, icv, n_tint_shade = io.read(4).unpack('CCs<')
2517
+ attrs = Unxls::BitOps.new(attrs)
2518
+ xclr_type = attrs.value_at(1..7)
2519
+
2520
+ {
2521
+ fValidRGBA: attrs.set_at?(0), # A - fValidRGBA (1 bit): A bit that specifies whether the xclrType, icv and nTintShade fields were used to set the dwRgba field.
2522
+ xclrType: xclr_type, # xclrType (7 bits): An XColorType that specifies how the color information is stored.
2523
+ xclrType_d: xcolortype(xclr_type),
2524
+ icv: icv, # icv (1 byte): An unsigned integer that specifies color information. If xclrType equals 0x01, this field MUST be one of the values specified in IcvXF, or equal 0. If xclrType equals 0x03, this field MUST be one of the values specified in ColorTheme.
2525
+ nTintShade: n_tint_shade, # nTintShade (2 bytes): A signed integer that specifies the tint of the color. This value is mapped to the range -1.0 to 1.0. Positive values lighten the color, and negative values darken the color.
2526
+ nTintShade_d: _map_tint(n_tint_shade),
2527
+ dwRgba: longrgba(io.read(4)) # dwRgba (4 bytes): A LongRGBA that specifies the color.
2528
+ }
2529
+ end
2530
+
2531
+ # 2.5.286 XFPropGradient
2532
+ # The XFPropGradient structure specifies a gradient fill.
2533
+ # @param data [String]
2534
+ # @return [Hash]
2535
+ def xfpropgradient(data)
2536
+ io = StringIO.new(data)
2537
+
2538
+ type = io.read(4).unpack('V').first rescue binding.pry
2539
+ type_d = {
2540
+ 0 => :linear,
2541
+ 1 => :rectangular
2542
+ }[type]
2543
+
2544
+ {
2545
+ type: type, # type (4 bytes): A Boolean (section 2.5.14) that specifies the gradient type.
2546
+ type_d: type_d,
2547
+ numDegree: xnum(io.read(8)).round(2), # numDegree (8 bytes): An Xnum (section 2.5.342) that specifies the gradient angle in degrees for a linear gradient. The gradient angle specifies the angle at which gradient strokes are drawn.
2548
+ numFillToLeft: xnum(io.read(8)).round(2), # numFillToLeft (8 bytes): An Xnum that specifies the left coordinate of the inner rectangle for a rectangular gradient, where (0.0,0.0) is the upper-left hand corner of the inner rectangle.
2549
+ numFillToRight: xnum(io.read(8)).round(2), # numFillToRight (8 bytes): An Xnum that specifies the right coordinate of the inner rectangle for a rectangular gradient, where (0.0,0.0) is the upper-left hand corner of the inner rectangle.
2550
+ numFillToTop: xnum(io.read(8)).round(2), # numFillToTop (8 bytes): An Xnum that specifies the top coordinate of the inner rectangle for a rectangular gradient, where (0.0,0.0) is the upper-left hand corner of the inner rectangle.
2551
+ numFillToBottom: xnum(io.read(8)).round(2), # numFillToBottom (8 bytes): An Xnum that specifies the bottom coordinate of the inner rectangle for a rectangular gradient, where (0.0,0.0) is the upper-left hand corner of the inner rectangle.
2552
+ }
2553
+ end
2554
+
2555
+ # 2.5.287 XFPropGradientStop
2556
+ # @param data [String]
2557
+ # @return [Hash]
2558
+ def xfpropgradientstop(data)
2559
+ io = StringIO.new(data)
2560
+ io.read(2) # unused
2561
+
2562
+ {
2563
+ numPosition: xnum(io.read(8)),
2564
+ color: xfpropcolor(io.read(8))
2565
+ }
2566
+ end
2567
+
2568
+ # 2.5.288 XFProps
2569
+ # This structure specifies an array of formatting properties.
2570
+ # @param io [StringIO]
2571
+ # @return [Hash]
2572
+ def xfprops(io)
2573
+ _, cprops = io.read(4).unpack('vv')
2574
+
2575
+ result = {
2576
+ # reserved (2 bytes): MUST be zero and MUST be ignored.
2577
+ cprops: cprops, # cprops (2 bytes): An unsigned integer that specifies the number of XFProp structures in xfPropArray.
2578
+ xfPropArray: [] # xfPropArray (variable): An array of XFProp. Each array element specifies a formatting property. The array of properties specifies the full set of formatting properties.
2579
+ }
2580
+
2581
+ cprops.times { result[:xfPropArray] << xfprop(io) }
2582
+
2583
+ result
2584
+ end
2585
+
2586
+ # 2.5.289 XFPropTextRotation
2587
+ # @param value [Integer]
2588
+ # @return [Hash]
2589
+ def xfproptextrotation(value)
2590
+ case value
2591
+ when 0..90 then :counterclockwise
2592
+ when 91..180 then :clockwise
2593
+ when 255 then :vertical
2594
+ else nil
2595
+ end
2596
+ end
2597
+
2598
+ # See 2.5.283 XFProp page 887
2599
+ # @param id [Integer]
2600
+ # @return [Hash]
2601
+ def _xfproptype(id)
2602
+ {
2603
+ 0x0000 => { description: :fill_pattern, structure: :FillPattern, unpack: :_int1b },
2604
+ 0x0001 => { description: :foreground_color, structure: :XFPropColor },
2605
+ 0x0002 => { description: :background_color, structure: :XFPropColor },
2606
+ 0x0003 => { description: :gradient_fill, structure: :XFPropGradient },
2607
+ 0x0004 => { description: :gradient_stop, structure: :XFPropGradientStop },
2608
+ 0x0005 => { description: :text_color, structure: :XFPropColor },
2609
+ 0x0006 => { description: :top_border, structure: :XFPropBorder },
2610
+ 0x0007 => { description: :bottom_border, structure: :XFPropBorder },
2611
+ 0x0008 => { description: :left_border, structure: :XFPropBorder },
2612
+ 0x0009 => { description: :right_border, structure: :XFPropBorder },
2613
+ 0x000A => { description: :diagonal_border, structure: :XFPropBorder },
2614
+ 0x000B => { description: :vertical_border, structure: :XFPropBorder },
2615
+ 0x000C => { description: :horizontal_border, structure: :XFPropBorder },
2616
+ 0x000D => { description: :diagonal_up, structure: :_bool1b }, # 1 == used, 0 == not used
2617
+ 0x000E => { description: :diagonal_down, structure: :_bool1b },
2618
+ 0x000F => { description: :horizontal_alignment, structure: :HorizAlign, unpack: :_int1b },
2619
+ 0x0010 => { description: :vertical_alignment, structure: :VertAlign, unpack: :_int1b },
2620
+ 0x0011 => { description: :text_rotation, structure: :XFPropTextRotation, unpack: :_int1b },
2621
+ 0x0012 => { description: :indentation_level, structure: :_int2b }, # 2-byte integer
2622
+ 0x0013 => { description: :reading_order, structure: :ReadingOrder, unpack: :_int1b },
2623
+ 0x0014 => { description: :text_wrap, structure: :_bool1b },
2624
+ 0x0015 => { description: :justify, structure: :_bool1b },
2625
+ 0x0016 => { description: :shrink_to_fit, structure: :_bool1b },
2626
+ 0x0017 => { description: :cell_merged, structure: :_bool1b },
2627
+ 0x0018 => { description: :font_name, structure: :LPWideString },
2628
+ 0x0019 => { description: :font_weight, structure: :Bold, unpack: :_int2b },
2629
+ 0x001A => { description: :underline_style, structure: :Underline, unpack: :_int2b },
2630
+ 0x001B => { description: :script_style, structure: :Script, unpack: :_int2b },
2631
+ 0x001C => { description: :italic, structure: :_bool1b },
2632
+ 0x001D => { description: :strikethrough, structure: :_bool1b },
2633
+ 0x001E => { description: :outline, structure: :_bool1b },
2634
+ 0x001F => { description: :shadow, structure: :_bool1b },
2635
+ 0x0020 => { description: :condensed, structure: :_bool1b },
2636
+ 0x0021 => { description: :extended, structure: :_bool1b },
2637
+ 0x0022 => { description: :character_set, structure: :_character_set, unpack: :_int1b },
2638
+ 0x0023 => { description: :font_family, structure: :_font_family, unpack: :_int1b },
2639
+ 0x0024 => { description: :text_size, structure: :_int4b },
2640
+ 0x0025 => { description: :font_scheme, structure: :FontScheme },
2641
+ 0x0026 => { description: :number_format, structure: :XLUnicodeString },
2642
+ 0x0029 => { description: :ifmt, structure: :_int2b },
2643
+ 0x002A => { description: :relative_indentation, structure: :_sint2b },
2644
+ 0x002B => { description: :locked, structure: :_bool1b },
2645
+ 0x002C => { description: :hidden, structure: :_bool1b },
2646
+ }[id]
2647
+ end
2648
+
2649
+ # 2.5.294 XLUnicodeString
2650
+ # The XLUnicodeString structure specifies a Unicode string.
2651
+ # @param data [String, StringIO]
2652
+ # @return [String]
2653
+ def xlunicodestring(data)
2654
+ io = data.to_sio
2655
+ cch, flags = io.read(3).unpack('vC')
2656
+ _read_unicodestring(io, cch, flags)
2657
+ end
2658
+
2659
+ # 2.5.296 XLUnicodeStringNoCch
2660
+ # The XLUnicodeStringNoCch structure specifies a Unicode string.
2661
+ # @param io [StringIO]
2662
+ # @param cch [Integer]
2663
+ # @return [String]
2664
+ def xlunicodestringnocch(io, cch)
2665
+ flags = io.read(1).unpack('C').first
2666
+ _read_unicodestring(io, cch, flags)
2667
+ end
2668
+
2669
+ # 2.5.293 XLUnicodeRichExtendedString
2670
+ # The XLUnicodeRichExtendedString structure specifies a Unicode string, which can contain formatting information and phonetic string data.
2671
+ # @param record [Unxls::Biff8::Record] Has SST and its Continue records
2672
+ # @return [Hash]
2673
+ def xlunicoderichextendedstring(record)
2674
+ record.open_next_record_block if record.bytes.eof?
2675
+
2676
+ cch, attrs = record.bytes.read(3).unpack('vC')
2677
+ attrs = Unxls::BitOps.new(attrs)
2678
+ result = {
2679
+ cch: cch, # cch (2 bytes): An unsigned integer that specifies the count of characters in the string.
2680
+ fHighByte: attrs.set_at?(0), # A - fHighByte (1 bit): A bit that specifies whether the characters in rgb are double-byte characters.
2681
+ # B - reserved1 (1 bit): MUST be zero, and MUST be ignored.
2682
+ fExtSt: attrs.set_at?(2), # C - fExtSt (1 bit): A bit that specifies whether the string contains phonetic string data.
2683
+ fRichSt: attrs.set_at?(3), # D - fRichSt (1 bit): A bit that specifies whether the string is a rich string and the string has at least two character formats applied.
2684
+ # reserved2 (4 bits): MUST be zero, and MUST be ignored.
2685
+ }
2686
+
2687
+ if result[:fRichSt]
2688
+ result[:cRun] = record.bytes.read(2).unpack('v').first # cRun (2 bytes): An optional unsigned integer that specifies the number of elements in rgRun.
2689
+ end
2690
+
2691
+ if result[:fExtSt]
2692
+ result[:cbExtRst] = record.bytes.read(4).unpack('l<').first # cbExtRst (4 bytes): An optional signed integer that specifies the byte count of ExtRst.
2693
+ end
2694
+
2695
+ result[:rgb] = _read_continued_string(record, cch, result[:fHighByte]) # rgb (variable): An array of bytes that specifies the characters in the string.
2696
+
2697
+ if result[:fRichSt]
2698
+ result[:rgRun] = []
2699
+ result[:cRun].times do
2700
+ record.open_next_record_block if record.bytes.eof?
2701
+ result[:rgRun] << formatrun(record.bytes) # rgRun (variable): An optional array of FormatRun structures that specifies the formatting for each text run.
2702
+ break if record.end_of_data?
2703
+ end
2704
+ end
2705
+
2706
+ if result[:fExtSt]
2707
+ result[:ExtRst] = String.new
2708
+ while (unread = result[:cbExtRst] - result[:ExtRst].size) > 0
2709
+ record.open_next_record_block if record.bytes.eof?
2710
+ result[:ExtRst] << record.bytes.read(unread)
2711
+ break if record.end_of_data?
2712
+ end
2713
+ result[:ExtRst] = extrst(result[:ExtRst]) # ExtRst (variable): An optional ExtRst that specifies the phonetic string data. The size of this field is cbExtRst.
2714
+ end
2715
+
2716
+ result
2717
+ end
2718
+
2719
+ # 2.5.342 Xnum
2720
+ # Xnum is a 64-bit binary floating-point number as specified in [IEEE754]. This value MUST NOT be infinity, denormalized, not-a-number (NaN), nor negative zero.
2721
+ # @param data [String]
2722
+ # @return [Float]
2723
+ def xnum(data)
2724
+ data.unpack('E').first
2725
+ end
2726
+
2727
+ # 2.5.343 XORObfuscation
2728
+ # The XORObfuscation structure specifies the XOR obfuscation.
2729
+ # @param io [StringIO]
2730
+ # @return [Hash]
2731
+ def xorobfuscation(io)
2732
+ key, verification_bytes = io.read(4).unpack('vv')
2733
+
2734
+ {
2735
+ key: key, # key (2 bytes): An unsigned integer that specifies the obfuscation key. See [MS-OFFCRYPTO], 2.3.6.2 section, the first step of initializing XOR array where it describes the generation of 16-bit XorKey value.
2736
+ verificationBytes: verification_bytes # verificationBytes (2 bytes): An unsigned integer that specifies the password verification identifier. See Password Verifier Algorithm.
2737
+ }
2738
+ end
2739
+
2740
+ end