unxls 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/ext/extconf.rb +4 -0
- data/lib/formulas/constants.rb +886 -0
- data/lib/formulas/map.rb +5 -0
- data/lib/formulas/parsed_expressions.rb +229 -0
- data/lib/formulas/record.rb +130 -0
- data/lib/formulas/structure.rb +30 -0
- data/lib/unxls.rb +16 -0
- data/lib/unxls/biff8/browser.rb +347 -0
- data/lib/unxls/biff8/constants.rb +624 -0
- data/lib/unxls/biff8/record.rb +1267 -0
- data/lib/unxls/biff8/structure.rb +2740 -0
- data/lib/unxls/biff8/workbook_stream.rb +322 -0
- data/lib/unxls/bit_ops.rb +92 -0
- data/lib/unxls/dtyp.rb +33 -0
- data/lib/unxls/log.rb +59 -0
- data/lib/unxls/map.rb +59 -0
- data/lib/unxls/offcrypto.rb +423 -0
- data/lib/unxls/oshared.rb +210 -0
- data/lib/unxls/parser.rb +100 -0
- data/lib/unxls/version.rb +5 -0
- data/spec/bitops_spec.rb +95 -0
- data/spec/spec_helper.rb +117 -0
- data/spec/unxls_spec.rb +2651 -0
- metadata +167 -0
data/spec/unxls_spec.rb
ADDED
@@ -0,0 +1,2651 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
RSpec.describe Unxls do
|
4
|
+
it 'Has a version number' do
|
5
|
+
expect(Unxls::VERSION).to_not eq nil
|
6
|
+
end
|
7
|
+
|
8
|
+
it 'Only opens BIFF8 files for now' do
|
9
|
+
expect {
|
10
|
+
Unxls.parse(testfile('other', 'biff2-empty.xls'))
|
11
|
+
}.to raise_error 'Sorry, BIFF2 is not supported yet'
|
12
|
+
|
13
|
+
expect {
|
14
|
+
Unxls.parse(testfile('other', 'biff3-empty.xls'))
|
15
|
+
}.to raise_error 'Sorry, BIFF3 is not supported yet'
|
16
|
+
|
17
|
+
expect {
|
18
|
+
Unxls.parse(testfile('other', 'biff4-empty.xls'))
|
19
|
+
}.to raise_error 'Sorry, BIFF4 is not supported yet'
|
20
|
+
|
21
|
+
expect {
|
22
|
+
Unxls.parse(testfile('other', 'biff5-empty.xls'))
|
23
|
+
}.to raise_error 'Sorry, BIFF5 is not supported yet'
|
24
|
+
end
|
25
|
+
|
26
|
+
context 'Workbook stream, Globals substream' do
|
27
|
+
context 'Single records' do
|
28
|
+
before(:context) do
|
29
|
+
@browser1 = Unxls::Biff8::Browser.new(testfile('biff8', 'empty.xls'))
|
30
|
+
@browser2 = Unxls::Biff8::Browser.new(testfile('biff8', 'preferences.xls'))
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'Decodes BOF record' do
|
34
|
+
expect(@browser1.globals[:BOF][:dt_d]).to eq :globals
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'Decodes CalcPrecision record' do
|
38
|
+
expect(@browser1.globals[:CalcPrecision][:fFullPrec]).to eq false
|
39
|
+
expect(@browser2.globals[:CalcPrecision][:fFullPrec]).to eq true
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'Decodes CodePage record' do
|
43
|
+
expect(@browser1.globals[:CodePage][:cv_d]).to eq :'UTF-16LE'
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'Decodes Country record' do
|
47
|
+
c1 = @browser1.globals[:Country]
|
48
|
+
expect(c1[:iCountryDef]).to eq 1
|
49
|
+
expect(c1[:iCountryDef_d]).to eq :"United States"
|
50
|
+
expect(c1[:iCountryWinIni]).to eq 1
|
51
|
+
expect(c1[:iCountryWinIni_d]).to eq :"United States"
|
52
|
+
|
53
|
+
c2 = @browser2.globals[:Country]
|
54
|
+
expect(c2[:iCountryDef]).to eq 81
|
55
|
+
expect(c2[:iCountryDef_d]).to eq :Japan
|
56
|
+
expect(c2[:iCountryWinIni]).to eq 7
|
57
|
+
expect(c2[:iCountryWinIni_d]).to eq :Russia
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'Decodes Date1904 record' do
|
61
|
+
expect(@browser1.globals[:Date1904][:f1904DateSystem]).to eq false
|
62
|
+
expect(@browser2.globals[:Date1904][:f1904DateSystem]).to eq true
|
63
|
+
end
|
64
|
+
|
65
|
+
it 'Decodes Theme record' do
|
66
|
+
expect(@browser1.globals[:Theme]).to eq nil
|
67
|
+
|
68
|
+
browser = Unxls::Biff8::Browser.new(testfile('biff8', 'font.xls'))
|
69
|
+
theme = browser.globals[:Theme]
|
70
|
+
|
71
|
+
expect(theme[:rgb_d].size).to eq 5
|
72
|
+
filename = 'theme/theme/theme1.xml'
|
73
|
+
expect(theme[:rgb_d].keys).to include filename
|
74
|
+
|
75
|
+
require 'rexml/document'
|
76
|
+
parsed_theme = REXML::Document.new(theme[:rgb_d][filename])
|
77
|
+
expect(parsed_theme.root.elements['a:themeElements/a:clrScheme[@name="Office"]'].size).to eq 12
|
78
|
+
expect(parsed_theme.root.elements['a:themeElements/a:clrScheme/a:dk1/a:sysClr/@lastClr'].value).to eq '000000'
|
79
|
+
expect(parsed_theme.root.elements['a:themeElements/a:fontScheme[@name="Office"]'].size).to eq 2
|
80
|
+
expect(parsed_theme.root.elements['a:themeElements/a:fmtScheme[@name="Office"]'].size).to eq 4
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
context 'BoundSheet8 record' do
|
85
|
+
before(:context) do
|
86
|
+
@browser1 = Unxls::Biff8::Browser.new(testfile('biff8', 'empty.xls'))
|
87
|
+
@browser2 = Unxls::Biff8::Browser.new(testfile('biff8', 'preferences.xls'))
|
88
|
+
end
|
89
|
+
|
90
|
+
specify 'For worksheet "Worksheet"' do
|
91
|
+
bs = @browser2.globals[:BoundSheet8][0]
|
92
|
+
expect(bs[:lbPlyPos]).to eq(@browser2.wbs[1][:BOF][:_record][:pos])
|
93
|
+
expect(bs[:hsState]).to eq 0
|
94
|
+
expect(bs[:hsState_d]).to eq :visible
|
95
|
+
expect(bs[:dt]).to eq 0
|
96
|
+
expect(bs[:dt_d]).to eq :dialog_or_work_sheet
|
97
|
+
expect(bs[:dt_d]).to eq(@browser2.wbs[1][:BOF][:dt_d])
|
98
|
+
expect(bs[:stName]).to eq 'Worksheet'
|
99
|
+
end
|
100
|
+
|
101
|
+
specify 'For dialog sheet "Dialog"' do
|
102
|
+
bs = @browser2.globals[:BoundSheet8][1]
|
103
|
+
expect(bs[:lbPlyPos]).to eq @browser2.wbs[2][:BOF][:_record][:pos]
|
104
|
+
expect(bs[:hsState]).to eq 0
|
105
|
+
expect(bs[:hsState_d]).to eq :visible
|
106
|
+
expect(bs[:dt]).to eq 0
|
107
|
+
expect(bs[:dt_d]).to eq :dialog_or_work_sheet
|
108
|
+
expect(bs[:dt_d]).to eq @browser2.wbs[2][:BOF][:dt_d]
|
109
|
+
expect(@browser2.wbs[2][:WsBool][:fDialog]).to eq true
|
110
|
+
expect(bs[:stName]).to eq 'Dialog'
|
111
|
+
end
|
112
|
+
|
113
|
+
# https://superuser.com/questions/1253212/what-is-macro-worksheet-in-excel
|
114
|
+
specify 'For macro sheet "Excel 4 macro"' do
|
115
|
+
bs = @browser2.globals[:BoundSheet8][2]
|
116
|
+
expect(bs[:lbPlyPos]).to eq @browser2.wbs[3][:BOF][:_record][:pos]
|
117
|
+
expect(bs[:hsState]).to eq 0
|
118
|
+
expect(bs[:hsState_d]).to eq :visible
|
119
|
+
expect(bs[:dt]).to eq 1
|
120
|
+
expect(bs[:dt_d]).to eq :macro
|
121
|
+
expect(bs[:dt_d]).to eq @browser2.wbs[3][:BOF][:dt_d]
|
122
|
+
expect(bs[:stName]).to eq 'Excel 4 macro'
|
123
|
+
end
|
124
|
+
|
125
|
+
specify 'For macro sheet "International macro"' do
|
126
|
+
bs = @browser2.globals[:BoundSheet8][3]
|
127
|
+
expect(bs[:lbPlyPos]).to eq @browser2.wbs[4][:BOF][:_record][:pos]
|
128
|
+
expect(bs[:hsState]).to eq 0
|
129
|
+
expect(bs[:hsState_d]).to eq :visible
|
130
|
+
expect(bs[:dt]).to eq 1
|
131
|
+
expect(bs[:dt_d]).to eq :macro
|
132
|
+
expect(bs[:dt_d]).to eq @browser2.wbs[4][:BOF][:dt_d]
|
133
|
+
expect(bs[:stName]).to eq 'International macro'
|
134
|
+
end
|
135
|
+
|
136
|
+
specify 'For chart sheet "Chart"' do
|
137
|
+
bs = @browser2.globals[:BoundSheet8][4]
|
138
|
+
expect(bs[:lbPlyPos]).to eq @browser2.wbs[5][:BOF][:_record][:pos]
|
139
|
+
expect(bs[:hsState]).to eq 0
|
140
|
+
expect(bs[:hsState_d]).to eq :visible
|
141
|
+
expect(bs[:dt]).to eq 2
|
142
|
+
expect(bs[:dt_d]).to eq :chart
|
143
|
+
expect(bs[:dt_d]).to eq @browser2.wbs[5][:BOF][:dt_d]
|
144
|
+
expect(bs[:stName]).to eq 'Chart'
|
145
|
+
end
|
146
|
+
|
147
|
+
specify 'For a hidden worksheet' do
|
148
|
+
bs = @browser2.globals[:BoundSheet8][5]
|
149
|
+
expect(bs[:hsState_d]).to eq :hidden
|
150
|
+
expect(bs[:stName]).to eq 'Hidden'
|
151
|
+
end
|
152
|
+
|
153
|
+
specify 'For a very hidden worksheet' do
|
154
|
+
bs = @browser2.globals[:BoundSheet8][6]
|
155
|
+
expect(bs[:hsState_d]).to eq :very_hidden
|
156
|
+
expect(bs[:stName]).to eq 'Very hidden'
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
context 'DXF record' do
|
161
|
+
before(:context) do
|
162
|
+
@browser = Unxls::Biff8::Browser.new(testfile('biff8', 'tablestyle.xls'))
|
163
|
+
end
|
164
|
+
|
165
|
+
let(:dxf) { @browser.get_tse_dxf('Custom Table Style', :whole_table) }
|
166
|
+
|
167
|
+
it 'Decodes basic fields' do
|
168
|
+
expect(dxf[:frtHeader]).to be
|
169
|
+
expect(dxf[:fNewBorder]).to eq true
|
170
|
+
expect(dxf[:cprops]).to eq 14
|
171
|
+
end
|
172
|
+
|
173
|
+
it 'Decodes structure of xfPropType 0x00 (FillPattern)' do
|
174
|
+
prop = dxf[:xfPropArray].find { |e| e[:xfPropType] == 0 }
|
175
|
+
expect(prop[:cb]).to eq 5
|
176
|
+
expect(prop[:_description]).to eq :fill_pattern
|
177
|
+
expect(prop[:_structure]).to eq :FillPattern
|
178
|
+
expect(prop[:xfPropDataBlob_d]).to eq :FLSGRAY0625
|
179
|
+
end
|
180
|
+
|
181
|
+
# Fully tested in StyleExt
|
182
|
+
it 'Decodes structures already tested in StyleExt' do
|
183
|
+
prop = dxf[:xfPropArray].find { |e| e[:xfPropType] == 1 } # foreground_color
|
184
|
+
expect(prop[:xfPropDataBlob_d][:dwRgba]).to eq :'2A8EF2FF'
|
185
|
+
|
186
|
+
prop = dxf[:xfPropArray].find { |e| e[:xfPropType] == 2 } # background_color
|
187
|
+
expect(prop[:xfPropDataBlob_d][:dwRgba]).to eq :'7030A0FF'
|
188
|
+
|
189
|
+
prop = dxf[:xfPropArray].find { |e| e[:xfPropType] == 5 } # text_color
|
190
|
+
expect(prop[:xfPropDataBlob_d][:dwRgba]).to eq :'2A8EF2FF'
|
191
|
+
|
192
|
+
prop = dxf[:xfPropArray].find { |e| e[:xfPropType] == 6 } # top_border
|
193
|
+
expect(prop[:xfPropDataBlob_d][:dgBorder_d]).to eq :DOTTED
|
194
|
+
|
195
|
+
prop = dxf[:xfPropArray].find { |e| e[:xfPropType] == 7 } # bottom_border
|
196
|
+
expect(prop[:xfPropDataBlob_d][:dgBorder_d]).to eq :DASHDOT
|
197
|
+
|
198
|
+
prop = dxf[:xfPropArray].find { |e| e[:xfPropType] == 8 } # left_border
|
199
|
+
expect(prop[:xfPropDataBlob_d][:dgBorder_d]).to eq :HAIR
|
200
|
+
|
201
|
+
prop = dxf[:xfPropArray].find { |e| e[:xfPropType] == 9 } # right_border
|
202
|
+
expect(prop[:xfPropDataBlob_d][:dgBorder_d]).to eq :DASHDOTDOT
|
203
|
+
end
|
204
|
+
|
205
|
+
it 'Decodes structure of xfPropType 0x19 (Bold, font weight)' do
|
206
|
+
prop = dxf[:xfPropArray].find { |e| e[:xfPropType] == 0x19 }
|
207
|
+
expect(prop[:cb]).to eq 6
|
208
|
+
expect(prop[:_description]).to eq :font_weight
|
209
|
+
expect(prop[:_structure]).to eq :Bold
|
210
|
+
expect(prop[:xfPropDataBlob_d]).to eq :BLSBOLD
|
211
|
+
end
|
212
|
+
|
213
|
+
it 'Decodes structure of xfPropType 0x1A (Underline, underline style)' do
|
214
|
+
prop = dxf[:xfPropArray].find { |e| e[:xfPropType] == 0x1A }
|
215
|
+
expect(prop[:cb]).to eq 6
|
216
|
+
expect(prop[:_description]).to eq :underline_style
|
217
|
+
expect(prop[:_structure]).to eq :Underline
|
218
|
+
expect(prop[:xfPropDataBlob_d]).to eq :ULSSINGLE
|
219
|
+
end
|
220
|
+
|
221
|
+
it 'Decodes structure of xfPropType 0x1C (_bool1b, text is italicized)' do
|
222
|
+
prop = dxf[:xfPropArray].find { |e| e[:xfPropType] == 0x1C }
|
223
|
+
expect(prop[:cb]).to eq 5
|
224
|
+
expect(prop[:_description]).to eq :italic
|
225
|
+
expect(prop[:_structure]).to eq :_bool1b
|
226
|
+
expect(prop[:xfPropDataBlob_d]).to eq true
|
227
|
+
end
|
228
|
+
|
229
|
+
it 'Decodes structure of xfPropType 0x1D (_bool1b, text has strikethrough formatting)' do
|
230
|
+
prop = dxf[:xfPropArray].find { |e| e[:xfPropType] == 0x1D }
|
231
|
+
expect(prop[:cb]).to eq 5
|
232
|
+
expect(prop[:_description]).to eq :strikethrough
|
233
|
+
expect(prop[:_structure]).to eq :_bool1b
|
234
|
+
expect(prop[:xfPropDataBlob_d]).to eq true
|
235
|
+
end
|
236
|
+
|
237
|
+
it 'Decodes structure of xfPropType 0x0B (XFPropBorder, vertical border formatting)' do
|
238
|
+
prop = dxf[:xfPropArray].find { |e| e[:xfPropType] == 0x0B }
|
239
|
+
expect(prop[:cb]).to eq 14
|
240
|
+
expect(prop[:_description]).to eq :vertical_border
|
241
|
+
expect(prop[:_structure]).to eq :XFPropBorder
|
242
|
+
|
243
|
+
prop_data = prop[:xfPropDataBlob_d]
|
244
|
+
expect(prop_data[:dgBorder]).to eq 1
|
245
|
+
expect(prop_data[:dgBorder_d]).to eq :THIN
|
246
|
+
|
247
|
+
color = prop_data[:color]
|
248
|
+
expect(color[:fValidRGBA]).to eq true
|
249
|
+
expect(color[:xclrType]).to eq 2
|
250
|
+
expect(color[:xclrType_d]).to eq :XCLRRGB
|
251
|
+
expect(color[:icv]).to eq 0xFF
|
252
|
+
expect(color[:nTintShade]).to eq 0
|
253
|
+
expect(color[:nTintShade_d]).to eq 0.0
|
254
|
+
expect(color[:dwRgba]).to eq :'2A8EF2FF'
|
255
|
+
end
|
256
|
+
|
257
|
+
it 'Decodes structure of xfPropType 0x0C (XFPropBorder, horizontal border formatting)' do
|
258
|
+
prop = dxf[:xfPropArray].find { |e| e[:xfPropType] == 0x0C }
|
259
|
+
expect(prop[:cb]).to eq 14
|
260
|
+
expect(prop[:_description]).to eq :horizontal_border
|
261
|
+
expect(prop[:_structure]).to eq :XFPropBorder
|
262
|
+
|
263
|
+
prop_data = prop[:xfPropDataBlob_d]
|
264
|
+
expect(prop_data[:dgBorder]).to eq 3
|
265
|
+
expect(prop_data[:dgBorder_d]).to eq :DASHED
|
266
|
+
|
267
|
+
color = prop_data[:color]
|
268
|
+
expect(color[:fValidRGBA]).to eq true
|
269
|
+
expect(color[:xclrType]).to eq 2
|
270
|
+
expect(color[:xclrType_d]).to eq :XCLRRGB
|
271
|
+
expect(color[:icv]).to eq 0xFF
|
272
|
+
expect(color[:nTintShade]).to eq 0
|
273
|
+
expect(color[:nTintShade_d]).to eq 0.0
|
274
|
+
expect(color[:dwRgba]).to eq :'92D050FF'
|
275
|
+
end
|
276
|
+
end
|
277
|
+
|
278
|
+
context 'FilePass record' do
|
279
|
+
it 'Decodes from files encrypted using XOR obfuscation' do
|
280
|
+
file = testfile('biff8', 'filepass', 'filepass_xor-password.xls')
|
281
|
+
browser = Unxls::Biff8::Browser.new(file, password: 'password')
|
282
|
+
|
283
|
+
fp = browser.globals[:FilePass]
|
284
|
+
expect(fp[:wEncryptionType]).to eq 0
|
285
|
+
expect(fp[:_type]).to eq :XOR
|
286
|
+
expect(fp[:key]).to eq 5242
|
287
|
+
expect(fp[:verificationBytes]).to eq 33711
|
288
|
+
|
289
|
+
expect(browser.globals[:Font][0][:fontName]).to eq 'Arial'
|
290
|
+
end
|
291
|
+
|
292
|
+
it 'Decodes from files encrypted using RC4 encryption, RC4 header structure' do
|
293
|
+
file = testfile('biff8', 'filepass', 'filepass_rc4_97_2000-password.xls')
|
294
|
+
browser = Unxls::Biff8::Browser.new(file, password: 'password')
|
295
|
+
|
296
|
+
fp = browser.globals[:FilePass]
|
297
|
+
expect(fp[:wEncryptionType]).to eq 1
|
298
|
+
expect(fp[:_type]).to eq :RC4
|
299
|
+
expect(fp[:EncryptionVersionInfo]).to eq({ vMajor: 1, vMinor: 1 })
|
300
|
+
%i(Salt EncryptedVerifier EncryptedVerifierHash).each do |attr|
|
301
|
+
expect(fp[attr]).to be_an_instance_of String
|
302
|
+
expect(fp[attr].size).to eq 16
|
303
|
+
expect(fp[attr].encoding.name).to eq 'ASCII-8BIT'
|
304
|
+
end
|
305
|
+
|
306
|
+
expect(browser.globals[:Font][0][:fontName]).to eq 'Arial'
|
307
|
+
end
|
308
|
+
|
309
|
+
[
|
310
|
+
{ file: 'filepass_cryptoapi_40_basecrypto-password.xls', CSPName: 'Microsoft Base Cryptographic Provider v1.0', KeySize: 40 },
|
311
|
+
{ file: 'filepass_cryptoapi_40_dhsc-password.xls', CSPName: 'Microsoft DH SChannel Cryptographic Provider', KeySize: 40 },
|
312
|
+
{ file: 'filepass_cryptoapi_40_dss-password.xls', CSPName: 'Microsoft Base DSS and Diffie-Hellman Cryptographic Provider', KeySize: 40 },
|
313
|
+
{ file: 'filepass_cryptoapi_128_enhanced-password.xls', CSPName: 'Microsoft Enhanced Cryptographic Provider v1.0', KeySize: 128 },
|
314
|
+
{ file: 'filepass_cryptoapi_128_enhdss-password.xls', CSPName: 'Microsoft Enhanced DSS and Diffie-Hellman Cryptographic Provider', KeySize: 128 },
|
315
|
+
{ file: 'filepass_cryptoapi_128_enhrsa-password.xls', CSPName: 'Microsoft Enhanced RSA and AES Cryptographic Provider (Prototype)', KeySize: 128 },
|
316
|
+
{ file: 'filepass_cryptoapi_128_enhrsasc-password.xls', CSPName: 'Microsoft RSA SChannel Cryptographic Provider', KeySize: 128 },
|
317
|
+
{ file: 'filepass_cryptoapi_128_strong-password.xls', CSPName: 'Microsoft Strong Cryptographic Provider', KeySize: 128 },
|
318
|
+
].each do |params|
|
319
|
+
it "Decodes from #{params[:file]} encrypted using RC4 encryption, RC4 CryptoAPI header structure" do
|
320
|
+
file = testfile('biff8', 'filepass', params[:file])
|
321
|
+
browser = Unxls::Biff8::Browser.new(file, password: 'password')
|
322
|
+
fp = browser.globals[:FilePass]
|
323
|
+
expect(fp[:wEncryptionType]).to eq 1
|
324
|
+
expect(fp[:_type]).to eq :CryptoAPI
|
325
|
+
expect(fp[:EncryptionVersionInfo]).to eq({ vMajor: 2, vMinor: 2 })
|
326
|
+
flags = { fCryptoAPI: true, fDocProps: false, fExternal: false, fAES: false }
|
327
|
+
expect(fp[:EncryptionHeaderFlags]).to eq flags
|
328
|
+
expect(fp[:EncryptionHeaderSize]).to be > 0
|
329
|
+
|
330
|
+
eh = fp[:EncryptionHeader]
|
331
|
+
expect(eh[:Flags]).to eq flags
|
332
|
+
expect(eh[:SizeExtra]).to eq 0
|
333
|
+
expect(eh[:AlgID]).to eq 0x6801
|
334
|
+
expect(eh[:AlgID_d]).to eq :RC4
|
335
|
+
expect(eh[:KeySize]).to eq params[:KeySize]
|
336
|
+
expect(eh[:ProviderType]).to be_an_kind_of Integer
|
337
|
+
expect(eh[:CSPName]).to eq params[:CSPName]
|
338
|
+
|
339
|
+
ev = fp[:EncryptionVerifier]
|
340
|
+
%i(Salt EncryptedVerifier).each do |attr|
|
341
|
+
expect(ev[attr]).to be_an_instance_of String
|
342
|
+
expect(ev[attr].size).to eq ev[:SaltSize]
|
343
|
+
expect(ev[attr].encoding.name).to eq 'ASCII-8BIT'
|
344
|
+
end
|
345
|
+
expect(ev[:VerifierHashSize]).to eq 20
|
346
|
+
expect(ev[:EncryptedVerifierHash].size).to eq ev[:VerifierHashSize]
|
347
|
+
expect(ev[:EncryptedVerifierHash]).to be_an_instance_of String
|
348
|
+
expect(ev[:EncryptedVerifierHash].encoding.name).to eq 'ASCII-8BIT'
|
349
|
+
|
350
|
+
expect(fp[:_encryption_algorithm]).to eq :RC4
|
351
|
+
expect(fp[:_hashing_algorithm]).to eq :SHA1
|
352
|
+
|
353
|
+
expect(browser.globals[:Font][0][:fontName]).to eq 'Arial'
|
354
|
+
end
|
355
|
+
end
|
356
|
+
end
|
357
|
+
|
358
|
+
context 'Font record' do
|
359
|
+
before(:context) do
|
360
|
+
@browser = Unxls::Biff8::Browser.new(testfile('biff8', 'font.xls'))
|
361
|
+
end
|
362
|
+
|
363
|
+
def get_font(stream, row, col)
|
364
|
+
font_index = @browser.get_xf(stream, row, col)[:ifnt]
|
365
|
+
@browser.get_font(font_index)
|
366
|
+
end
|
367
|
+
|
368
|
+
# Size
|
369
|
+
it 'Decodes dyHeight field' do
|
370
|
+
expect(get_font(1, 2, 0)[:dyHeight]).to eq 200
|
371
|
+
expect(get_font(1, 3, 0)[:dyHeight]).to eq 240
|
372
|
+
expect(get_font(1, 4, 0)[:dyHeight]).to eq 300
|
373
|
+
end
|
374
|
+
|
375
|
+
it 'Decodes fItalic field' do
|
376
|
+
expect(get_font(1, 2, 1)[:fItalic]).to eq true
|
377
|
+
end
|
378
|
+
|
379
|
+
it 'Decodes fStrikeOut field' do
|
380
|
+
expect(get_font(1, 2, 2)[:fStrikeOut]).to eq true
|
381
|
+
end
|
382
|
+
|
383
|
+
# Seems like fOutline, fShadow are only used in old (~2) versions of Excel for Mac
|
384
|
+
# Seems like fCondense, fExtend came from Word but can't be found in the interface of Excel
|
385
|
+
|
386
|
+
# Color
|
387
|
+
it 'Decodes icv field' do
|
388
|
+
expect(get_font(1, 2, 3)[:icv]).to eq 0x7FFF # Font automatic color
|
389
|
+
expect(get_font(1, 3, 3)[:icv]).to eq 0x000A # rgColor[2] of Palette, FF0000
|
390
|
+
end
|
391
|
+
|
392
|
+
# Bold
|
393
|
+
it 'Decodes bls field' do
|
394
|
+
f = get_font(1, 2, 4)
|
395
|
+
expect(f[:bls]).to eq 400
|
396
|
+
expect(f[:bls_d]).to eq :BLSNORMAL
|
397
|
+
|
398
|
+
f = get_font(1, 3, 4)
|
399
|
+
expect(f[:bls]).to eq 700
|
400
|
+
expect(f[:bls_d]).to eq :BLSBOLD
|
401
|
+
end
|
402
|
+
|
403
|
+
# Superscript, subscript
|
404
|
+
it 'Decodes sss field' do
|
405
|
+
f = get_font(1, 2, 5)
|
406
|
+
expect(f[:sss]).to eq 0
|
407
|
+
expect(f[:sss_d]).to eq :SSSNONE
|
408
|
+
|
409
|
+
f = get_font(1, 3, 5)
|
410
|
+
expect(f[:sss]).to eq 1
|
411
|
+
expect(f[:sss_d]).to eq :SSSSUPER
|
412
|
+
|
413
|
+
f = get_font(1, 4, 5)
|
414
|
+
expect(f[:sss]).to eq 2
|
415
|
+
expect(f[:sss_d]).to eq :SSSSUB
|
416
|
+
end
|
417
|
+
|
418
|
+
# Underline
|
419
|
+
it 'Decodes uls field' do
|
420
|
+
f = get_font(1, 2, 6)
|
421
|
+
expect(f[:uls]).to eq 0
|
422
|
+
expect(f[:uls_d]).to eq :ULSNONE
|
423
|
+
|
424
|
+
f = get_font(1, 3, 6)
|
425
|
+
expect(f[:uls]).to eq 1
|
426
|
+
expect(f[:uls_d]).to eq :ULSSINGLE
|
427
|
+
|
428
|
+
f = get_font(1, 4, 6)
|
429
|
+
expect(f[:uls]).to eq 2
|
430
|
+
expect(f[:uls_d]).to eq :ULSDOUBLE
|
431
|
+
|
432
|
+
f = get_font(1, 5, 6)
|
433
|
+
expect(f[:uls]).to eq 33
|
434
|
+
expect(f[:uls_d]).to eq :ULSSINGLEACCOUNTANT
|
435
|
+
|
436
|
+
f = get_font(1, 6, 6)
|
437
|
+
expect(f[:uls]).to eq 34
|
438
|
+
expect(f[:uls_d]).to eq :ULSDOUBLEACCOUNTANT
|
439
|
+
end
|
440
|
+
|
441
|
+
# Font family (Mac versions seem to not to write this field)
|
442
|
+
it 'Decodes bFamily field' do
|
443
|
+
f = get_font(1, 2, 7)
|
444
|
+
expect(f[:bFamily]).to eq 0
|
445
|
+
expect(f[:bFamily_d]).to eq :'Not applicable'
|
446
|
+
|
447
|
+
f = get_font(1, 3, 7)
|
448
|
+
expect(f[:bFamily]).to eq 1
|
449
|
+
expect(f[:bFamily_d]).to eq :Roman
|
450
|
+
|
451
|
+
f = get_font(1, 4, 7)
|
452
|
+
expect(f[:bFamily]).to eq 2
|
453
|
+
expect(f[:bFamily_d]).to eq :Swiss
|
454
|
+
|
455
|
+
f = get_font(1, 5, 7)
|
456
|
+
expect(f[:bFamily]).to eq 3
|
457
|
+
expect(f[:bFamily_d]).to eq :Modern
|
458
|
+
|
459
|
+
f = get_font(1, 6, 7)
|
460
|
+
expect(f[:bFamily]).to eq 4
|
461
|
+
expect(f[:bFamily_d]).to eq :Script
|
462
|
+
end
|
463
|
+
|
464
|
+
# Font charset (Mac versions seem to not to write this field)
|
465
|
+
it 'Decodes bCharSet field' do
|
466
|
+
f = get_font(1, 2, 8)
|
467
|
+
expect(f[:bCharSet]).to eq 128
|
468
|
+
expect(f[:bCharSet_d]).to eq :ShiftJIS
|
469
|
+
|
470
|
+
f = get_font(1, 3, 8)
|
471
|
+
expect(f[:bCharSet]).to eq 129
|
472
|
+
expect(f[:bCharSet_d]).to eq :Jangul
|
473
|
+
|
474
|
+
f = get_font(1, 4, 8)
|
475
|
+
expect(f[:bCharSet]).to eq 136
|
476
|
+
expect(f[:bCharSet_d]).to eq :ChineseBIG5
|
477
|
+
|
478
|
+
f = get_font(1, 5, 8)
|
479
|
+
expect(f[:bCharSet]).to eq 134
|
480
|
+
expect(f[:bCharSet_d]).to eq :GB2312
|
481
|
+
|
482
|
+
f = get_font(1, 6, 8)
|
483
|
+
expect(f[:bCharSet]).to eq 0
|
484
|
+
expect(f[:bCharSet_d]).to eq :ANSI
|
485
|
+
end
|
486
|
+
|
487
|
+
it 'Decodes fontName field' do
|
488
|
+
@browser = Unxls::Biff8::Browser.new(testfile('biff8', 'font-fontname.xls'))
|
489
|
+
|
490
|
+
expect(get_font(1, 2, 0)[:fontName]).to eq 'HGP創英角ゴシックUB'
|
491
|
+
expect(get_font(1, 3, 0)[:fontName]).to eq 'メイリオ'
|
492
|
+
expect(get_font(1, 4, 0)[:fontName]).to eq '나눔고딕 ExtraBold'
|
493
|
+
expect(get_font(1, 5, 0)[:fontName]).to eq 'مِصحفي'
|
494
|
+
expect(get_font(1, 6, 0)[:fontName]).to eq 'Цветные эмодзи Apple'
|
495
|
+
expect(get_font(1, 7, 0)[:fontName]).to eq 'Arial'
|
496
|
+
end
|
497
|
+
end
|
498
|
+
|
499
|
+
context 'Format record' do
|
500
|
+
before(:context) do
|
501
|
+
@browser = Unxls::Biff8::Browser.new(testfile('biff8', 'format.xls'))
|
502
|
+
end
|
503
|
+
|
504
|
+
# Record id
|
505
|
+
it 'Decodes ifmt field' do
|
506
|
+
1.upto(5).each do |row|
|
507
|
+
expect(@browser.get_format(1, row, 0)[:ifmt]).to be_an_kind_of Integer
|
508
|
+
end
|
509
|
+
end
|
510
|
+
|
511
|
+
# Format string
|
512
|
+
it 'Decodes stFormat field' do
|
513
|
+
expect(@browser.get_format(1, 1, 0)[:stFormat]).to eq '"Format フォーマット"'
|
514
|
+
expect(@browser.get_format(1, 2, 0)[:stFormat]).to eq '[$-409]d\-mmm\-yy;@'
|
515
|
+
expect(@browser.get_format(1, 3, 0)[:stFormat]).to eq 'm"月"d"日";@'
|
516
|
+
expect(@browser.get_format(1, 4, 0)[:stFormat]).to eq 'dd/mm/yy;@'
|
517
|
+
expect(@browser.get_format(1, 5, 0)[:stFormat]).to eq '_-[$₩-412]* #,##0.00_-;\-[$₩-412]* #,##0.00_-;_-[$₩-412]* "-"??_-;_-@_-'
|
518
|
+
end
|
519
|
+
end
|
520
|
+
|
521
|
+
context 'Palette record' do
|
522
|
+
before(:context) do
|
523
|
+
@browser1 = Unxls::Biff8::Browser.new(testfile('biff8', 'empty.xls'))
|
524
|
+
@browser2 = Unxls::Biff8::Browser.new(testfile('biff8', 'palette.xls'))
|
525
|
+
end
|
526
|
+
|
527
|
+
it 'Decodes' do
|
528
|
+
expect(@browser1.globals[:Palette]).to eq nil # Uses standard palette
|
529
|
+
|
530
|
+
p = @browser2.globals[:Palette]
|
531
|
+
expect(p[:ccv]).to eq 56
|
532
|
+
expect(p[:rgColor].size).to eq p[:ccv]
|
533
|
+
expect(p[:rgColor][0]).to eq :"2A8EF200" # Black changed to RGB 42, 142, 242
|
534
|
+
expect(p[:rgColor][1]).to eq :"F28E2A00" # White changed to RGB 242, 142, 142
|
535
|
+
expect(p[:rgColor][2]).to eq :"8E2AF200" # Red changed to RGB 142, 42, 242
|
536
|
+
expect(p[:rgColor][3]).to eq :"00FF0000" # Green unchanged
|
537
|
+
end
|
538
|
+
end
|
539
|
+
|
540
|
+
context 'SST record' do
|
541
|
+
before(:context) do
|
542
|
+
@browser = Unxls::Biff8::Browser.new(testfile('biff8', 'sst.xls'))
|
543
|
+
end
|
544
|
+
|
545
|
+
it 'Decodes cstTotal field' do
|
546
|
+
expect(@browser.globals[:SST][:cstTotal]).to eq 24
|
547
|
+
end
|
548
|
+
|
549
|
+
it 'Decodes cstUnique field' do
|
550
|
+
expect(@browser.globals[:SST][:cstUnique]).to eq 20
|
551
|
+
end
|
552
|
+
|
553
|
+
describe 'String-related fields' do
|
554
|
+
specify 'For short ASCII string' do
|
555
|
+
sst = @browser.get_sst(1, 3, 0)
|
556
|
+
expect(sst[:fHighByte]).to eq false
|
557
|
+
expect(sst[:fRichSt]).to eq false
|
558
|
+
expect(sst[:cch]).to eq 35
|
559
|
+
expect(sst[:rgb]).to eq 'String with single-byte characters.'
|
560
|
+
expect(sst[:rgb].size).to eq sst[:cch]
|
561
|
+
end
|
562
|
+
|
563
|
+
specify 'For short string with single and double-byte characters' do
|
564
|
+
sst = @browser.get_sst(1, 4, 0)
|
565
|
+
expect(sst[:fHighByte]).to eq true
|
566
|
+
expect(sst[:fRichSt]).to eq false
|
567
|
+
expect(sst[:cch]).to eq 49
|
568
|
+
expect(sst[:rgb]).to eq 'String with double-byte characters: АБВГД, アイウエオ。'
|
569
|
+
expect(sst[:rgb].size).to eq sst[:cch]
|
570
|
+
end
|
571
|
+
|
572
|
+
specify 'For very long string with surrogate pair characters' do
|
573
|
+
sst = @browser.get_sst(1, 7, 0)
|
574
|
+
expect(sst[:fHighByte]).to eq true
|
575
|
+
expect(sst[:fRichSt]).to eq false
|
576
|
+
expect(sst[:rgb].size).to eq 17000
|
577
|
+
expect(sst[:cch] - sst[:rgb].size).to eq 5 # 1 surrogate pair character (emojis, etc.) is counted as 2
|
578
|
+
expect(sst[:rgb][0..9]).to eq 'Very long '
|
579
|
+
expect(sst[:rgb][-9..-1]).to eq ' W W END.'
|
580
|
+
end
|
581
|
+
end
|
582
|
+
|
583
|
+
describe 'Formatting runs-related fields' do
|
584
|
+
let(:sst) do
|
585
|
+
@browser.get_sst(1, 3, 1)
|
586
|
+
end
|
587
|
+
|
588
|
+
it 'Decodes basic fields' do
|
589
|
+
expect(sst[:fRichSt]).to eq true
|
590
|
+
expect(sst[:cRun]).to eq 10
|
591
|
+
expect(sst[:rgb][0..10]).to eq 'Lorem ipsum'
|
592
|
+
end
|
593
|
+
|
594
|
+
it 'Decodes first run ("ipsum")' do
|
595
|
+
expect(sst[:rgRun][0][:ich]).to eq 6 # Run's start character position
|
596
|
+
expect(sst[:rgRun][1][:ich]).to eq 11 # Run's end character position + 1
|
597
|
+
frun_ifnt = sst[:rgRun][0][:ifnt] - 1 # See 2.5.129 FontIndex
|
598
|
+
font = @browser.globals[:Font][frun_ifnt]
|
599
|
+
expect(font[:fontName]).to eq 'Meiryo'
|
600
|
+
expect(sst[:rgRun][1][:ifnt]).to eq 0
|
601
|
+
end
|
602
|
+
|
603
|
+
it 'Decodes second run ("amet")' do
|
604
|
+
expect(sst[:rgRun][2][:ich]).to eq 22
|
605
|
+
expect(sst[:rgRun][3][:ich]).to eq 26
|
606
|
+
frun_ifnt = sst[:rgRun][2][:ifnt] - 1
|
607
|
+
font = @browser.globals[:Font][frun_ifnt]
|
608
|
+
expect(font[:fontName]).to eq 'Osaka'
|
609
|
+
expect(sst[:rgRun][3][:ifnt]).to eq 0
|
610
|
+
end
|
611
|
+
|
612
|
+
it 'Decodes third run ("adipiscing")' do
|
613
|
+
expect(sst[:rgRun][4][:ich]).to eq 40
|
614
|
+
expect(sst[:rgRun][5][:ich]).to eq 50
|
615
|
+
frun_ifnt = sst[:rgRun][4][:ifnt] - 1
|
616
|
+
font = @browser.globals[:Font][frun_ifnt]
|
617
|
+
expect(font[:fontName]).to eq 'FangSong'
|
618
|
+
expect(sst[:rgRun][5][:ifnt]).to eq 0
|
619
|
+
end
|
620
|
+
|
621
|
+
# etc
|
622
|
+
end
|
623
|
+
|
624
|
+
context 'Phonetic text runs-related fields' do
|
625
|
+
it 'Decodes basic fields' do
|
626
|
+
sst = @browser.get_sst(1, 3, 2)
|
627
|
+
expect(sst[:fExtSt]).to eq true
|
628
|
+
expect(sst[:cbExtRst]).to eq 92
|
629
|
+
expect(sst[:rgb]).to eq '世界選手権でメダルを取った選手が池江選手にメッセージ'
|
630
|
+
expect(sst[:ExtRst][:cb]).to eq 88
|
631
|
+
end
|
632
|
+
|
633
|
+
it 'Decodes rphssub field' do
|
634
|
+
rphssub = @browser.get_sst(1, 3, 2)[:ExtRst][:rphssub]
|
635
|
+
expect(rphssub[:crun]).to eq 6
|
636
|
+
expect(rphssub[:cch]).to eq 21
|
637
|
+
expect(rphssub[:cch]).to eq rphssub[:st].size
|
638
|
+
expect(rphssub[:st]).to eq 'セカイセンシュケントセンシュイケエセンシュ'
|
639
|
+
end
|
640
|
+
|
641
|
+
it 'Decodes rgphruns field' do
|
642
|
+
rgphruns = @browser.get_sst(1, 3, 2)[:ExtRst][:rgphruns]
|
643
|
+
|
644
|
+
# First run: "セカイ" (0-) over "世界" (0-1)
|
645
|
+
expect(rgphruns[0][:ichFirst]).to eq 0 # furigana (rphssub.st) start char
|
646
|
+
expect(rgphruns[0][:ichMom]).to eq 0 # text (SST.rgb[].rgb) start char
|
647
|
+
expect(rgphruns[0][:cchMom]).to eq 2 # text char count
|
648
|
+
|
649
|
+
# Second run: "センシュケン" (3-) over "選手権" (2-4)
|
650
|
+
expect(rgphruns[1][:ichFirst]).to eq 3
|
651
|
+
expect(rgphruns[1][:ichMom]).to eq 2
|
652
|
+
expect(rgphruns[1][:cchMom]).to eq 3
|
653
|
+
|
654
|
+
# Third run: "ト" (9-) over "取" (10)
|
655
|
+
expect(rgphruns[2][:ichFirst]).to eq 9
|
656
|
+
expect(rgphruns[2][:ichMom]).to eq 10
|
657
|
+
expect(rgphruns[2][:cchMom]).to eq 1
|
658
|
+
|
659
|
+
# 4th run: "センシュ" (10-) over "選手" (14-15)
|
660
|
+
expect(rgphruns[3][:ichFirst]).to eq 10
|
661
|
+
expect(rgphruns[3][:ichMom]).to eq 13
|
662
|
+
expect(rgphruns[3][:cchMom]).to eq 2
|
663
|
+
|
664
|
+
# etc
|
665
|
+
end
|
666
|
+
|
667
|
+
it 'Decodes phs field' do
|
668
|
+
phs = @browser.get_sst(1, 3, 2)[:ExtRst][:phs]
|
669
|
+
expect(phs[:phType]).to eq 0
|
670
|
+
expect(phs[:phType_d]).to eq :narrow_katakana
|
671
|
+
|
672
|
+
phs = @browser.get_sst(1, 4, 2)[:ExtRst][:phs]
|
673
|
+
expect(phs[:phType]).to eq 1
|
674
|
+
expect(phs[:phType_d]).to eq :wide_katakana
|
675
|
+
|
676
|
+
phs = @browser.get_sst(1, 5, 2)[:ExtRst][:phs]
|
677
|
+
expect(phs[:phType]).to eq 2
|
678
|
+
expect(phs[:phType_d]).to eq :hiragana
|
679
|
+
|
680
|
+
phs = @browser.get_sst(1, 3, 3)[:ExtRst][:phs]
|
681
|
+
expect(phs[:alcH]).to eq 0
|
682
|
+
expect(phs[:alcH_d]).to eq :general
|
683
|
+
|
684
|
+
phs = @browser.get_sst(1, 4, 3)[:ExtRst][:phs]
|
685
|
+
expect(phs[:alcH]).to eq 1
|
686
|
+
expect(phs[:alcH_d]).to eq :left
|
687
|
+
|
688
|
+
phs = @browser.get_sst(1, 5, 3)[:ExtRst][:phs]
|
689
|
+
expect(phs[:alcH]).to eq 2
|
690
|
+
expect(phs[:alcH_d]).to eq :center
|
691
|
+
|
692
|
+
phs = @browser.get_sst(1, 6, 3)[:ExtRst][:phs]
|
693
|
+
expect(phs[:alcH]).to eq 3
|
694
|
+
expect(phs[:alcH_d]).to eq :distributed
|
695
|
+
|
696
|
+
phs = @browser.get_sst(1, 3, 4)[:ExtRst][:phs]
|
697
|
+
phs_ifnt = phs[:ifnt] - 1
|
698
|
+
font = @browser.globals[:Font][phs_ifnt]
|
699
|
+
expect(font[:fontName]).to eq 'HGPMinchoE'
|
700
|
+
|
701
|
+
phs = @browser.get_sst(1, 4, 4)[:ExtRst][:phs]
|
702
|
+
phs_ifnt = phs[:ifnt] - 1
|
703
|
+
font = @browser.globals[:Font][phs_ifnt]
|
704
|
+
expect(font[:fontName]).to eq 'Hiragino Kaku Gothic StdN W8'
|
705
|
+
end
|
706
|
+
end
|
707
|
+
end
|
708
|
+
|
709
|
+
context 'Style record' do
|
710
|
+
before(:context) do
|
711
|
+
@browser = Unxls::Biff8::Browser.new(testfile('biff8', 'style-styleext.xls'))
|
712
|
+
end
|
713
|
+
|
714
|
+
it 'Decodes fields for built-in styles' do
|
715
|
+
s = @browser.get_style(1, 2, 0)
|
716
|
+
expect(s[:ixfe]).to eq 0
|
717
|
+
expect(s[:fBuiltIn]).to eq true
|
718
|
+
expect(s[:builtInData][:istyBuiltIn]).to eq 0
|
719
|
+
expect(s[:builtInData][:iLevel]).to eq 0xFF
|
720
|
+
expect(s[:builtInData][:istyBuiltIn_d]).to eq :Normal
|
721
|
+
|
722
|
+
s = @browser.get_style(1, 3, 0)
|
723
|
+
expect(s[:ixfe]).to eq 43
|
724
|
+
expect(s[:fBuiltIn]).to eq true
|
725
|
+
expect(s[:builtInData][:istyBuiltIn]).to eq 3
|
726
|
+
expect(s[:builtInData][:iLevel]).to eq 0xFF
|
727
|
+
expect(s[:builtInData][:istyBuiltIn_d]).to eq :Comma
|
728
|
+
end
|
729
|
+
|
730
|
+
it 'Decodes fields for basic styles written by Excel' do
|
731
|
+
s = @browser.get_style(1, 2, 1)
|
732
|
+
expect(s[:ixfe]).to eq 40
|
733
|
+
expect(s[:fBuiltIn]).to eq false
|
734
|
+
expect(s[:user]).to eq :Bad
|
735
|
+
|
736
|
+
s = @browser.get_style(1, 3, 1)
|
737
|
+
expect(s[:ixfe]).to eq 41
|
738
|
+
expect(s[:fBuiltIn]).to eq false
|
739
|
+
expect(s[:user]).to eq :Calculation
|
740
|
+
end
|
741
|
+
|
742
|
+
it 'Decodes custom styles' do
|
743
|
+
s = @browser.get_style(1, 4, 2)
|
744
|
+
expect(s[:ixfe]).to eq 47
|
745
|
+
expect(s[:fBuiltIn]).to eq false
|
746
|
+
expect(s[:user]).to eq :'Custom 1'
|
747
|
+
end
|
748
|
+
end
|
749
|
+
|
750
|
+
context 'StyleExt record' do
|
751
|
+
before(:context) do
|
752
|
+
@browser = Unxls::Biff8::Browser.new(testfile('biff8', 'style-styleext.xls'))
|
753
|
+
end
|
754
|
+
|
755
|
+
it 'Decodes basic fields' do
|
756
|
+
s = @browser.get_styleext(1, 2, 2) # Modified built-in style
|
757
|
+
expect(s[:fBuiltIn]).to eq true
|
758
|
+
expect(s[:fHidden]).to eq false
|
759
|
+
expect(s[:fCustom]).to eq true
|
760
|
+
expect(s[:iCategory]).to eq 5
|
761
|
+
expect(s[:iCategory_d]).to eq :'Number format style'
|
762
|
+
expect(s[:builtInData]).to eq @browser.get_style(1, 2, 2)[:builtInData]
|
763
|
+
expect(s[:stName]).to eq :Currency
|
764
|
+
expect(s[:cprops]).to eq 3
|
765
|
+
|
766
|
+
s = @browser.get_styleext(1, 3, 2) # Modified basic style
|
767
|
+
expect(s[:fBuiltIn]).to eq true # fBuiltIn in corresponding Style is false
|
768
|
+
expect(s[:fHidden]).to eq false
|
769
|
+
expect(s[:fCustom]).to eq true
|
770
|
+
expect(s[:iCategory]).to eq 3
|
771
|
+
expect(s[:iCategory_d]).to eq :'Title and heading style'
|
772
|
+
expect(s[:stName]).to eq :Total
|
773
|
+
expect(s[:cprops]).to eq 3
|
774
|
+
|
775
|
+
s = @browser.get_styleext(1, 4, 2) # Custom style
|
776
|
+
expect(s[:fBuiltIn]).to eq false
|
777
|
+
expect(s[:fHidden]).to eq false
|
778
|
+
expect(s[:fCustom]).to eq false
|
779
|
+
expect(s[:iCategory]).to eq 0
|
780
|
+
expect(s[:iCategory_d]).to eq :'Custom style'
|
781
|
+
expect(s[:stName]).to eq :'Custom 1'
|
782
|
+
expect(s[:cprops]).to eq 10
|
783
|
+
end
|
784
|
+
|
785
|
+
context 'Decodes xfPropArray field' do
|
786
|
+
let(:custom1) { @browser.get_styleext(1, 4, 2)[:xfPropArray] }
|
787
|
+
let(:custom2) { @browser.get_styleext(1, 5, 2)[:xfPropArray] }
|
788
|
+
|
789
|
+
it 'Decodes structure of xfPropType 0x01 (XFPropColor, foreground color)' do
|
790
|
+
prop = custom1.find { |e| e[:xfPropType] == 1 }
|
791
|
+
expect(prop[:cb]).to eq 12
|
792
|
+
expect(prop[:_description]).to eq :foreground_color
|
793
|
+
expect(prop[:_structure]).to eq :XFPropColor
|
794
|
+
|
795
|
+
prop_data = prop[:xfPropDataBlob_d]
|
796
|
+
expect(prop_data[:fValidRGBA]).to eq true
|
797
|
+
expect(prop_data[:xclrType]).to eq 3
|
798
|
+
expect(prop_data[:xclrType_d]).to eq :XCLRTHEMED
|
799
|
+
expect(prop_data[:icv]).to eq 5
|
800
|
+
expect(prop_data[:nTintShade]).to eq 0
|
801
|
+
expect(prop_data[:nTintShade_d]).to eq 0.0
|
802
|
+
expect(prop_data[:dwRgba]).to eq :ED7D31FF
|
803
|
+
end
|
804
|
+
|
805
|
+
it 'Decodes structure of xfPropType 0x02 (XFPropColor, background color)' do
|
806
|
+
prop = custom1.find { |e| e[:xfPropType] == 2 }
|
807
|
+
expect(prop[:cb]).to eq 12
|
808
|
+
expect(prop[:_description]).to eq :background_color
|
809
|
+
expect(prop[:_structure]).to eq :XFPropColor
|
810
|
+
|
811
|
+
prop_data = prop[:xfPropDataBlob_d]
|
812
|
+
expect(prop_data[:fValidRGBA]).to eq true
|
813
|
+
expect(prop_data[:xclrType]).to eq 2
|
814
|
+
expect(prop_data[:xclrType_d]).to eq :XCLRRGB
|
815
|
+
expect(prop_data[:icv]).to eq 0xFF
|
816
|
+
expect(prop_data[:nTintShade]).to eq 0
|
817
|
+
expect(prop_data[:nTintShade_d]).to eq 0.0
|
818
|
+
expect(prop_data[:dwRgba]).to eq :'2A8EF2FF' # rgb(42, 142, 242)
|
819
|
+
end
|
820
|
+
|
821
|
+
it 'Decodes structure of xfPropType 0x03 (XFPropGradient, gradient fill)' do
|
822
|
+
prop = custom2.find { |e| e[:xfPropType] == 3 }
|
823
|
+
expect(prop[:cb]).to eq 48
|
824
|
+
expect(prop[:_description]).to eq :gradient_fill
|
825
|
+
expect(prop[:_structure]).to eq :XFPropGradient
|
826
|
+
|
827
|
+
prop_data = prop[:xfPropDataBlob_d]
|
828
|
+
expect(prop_data[:type]).to eq 0
|
829
|
+
expect(prop_data[:type_d]).to eq :linear
|
830
|
+
expect(prop_data[:numDegree]).to eq 45.0
|
831
|
+
expect(prop_data[:numFillToLeft]).to eq 0.0
|
832
|
+
expect(prop_data[:numFillToRight]).to eq 0.0
|
833
|
+
expect(prop_data[:numFillToTop]).to eq 0.0
|
834
|
+
expect(prop_data[:numFillToBottom]).to eq 0.0
|
835
|
+
end
|
836
|
+
|
837
|
+
it 'Decodes structure of xfPropType 0x04 (XFPropGradientStop, gradient stop)' do
|
838
|
+
props = custom2.select { |e| e[:xfPropType] == 4 }
|
839
|
+
|
840
|
+
{
|
841
|
+
0 => 0.0,
|
842
|
+
2 => 1.0
|
843
|
+
}.each do |prop_index, num_position|
|
844
|
+
prop = props[prop_index]
|
845
|
+
expect(prop[:cb]).to eq 22
|
846
|
+
expect(prop[:_description]).to eq :gradient_stop
|
847
|
+
expect(prop[:_structure]).to eq :XFPropGradientStop
|
848
|
+
|
849
|
+
prop_data = prop[:xfPropDataBlob_d]
|
850
|
+
expect(prop_data[:numPosition]).to eq num_position
|
851
|
+
|
852
|
+
color = prop_data[:color]
|
853
|
+
expect(color[:fValidRGBA]).to eq true
|
854
|
+
expect(color[:xclrType]).to eq 3
|
855
|
+
expect(color[:xclrType_d]).to eq :XCLRTHEMED
|
856
|
+
expect(color[:icv]).to eq 7
|
857
|
+
expect(color[:nTintShade]).to eq -8224
|
858
|
+
expect(color[:nTintShade_d]).to eq -0.25
|
859
|
+
expect(color[:dwRgba]).to eq :BD8E00FF
|
860
|
+
end
|
861
|
+
|
862
|
+
prop = props[1]
|
863
|
+
expect(prop[:cb]).to eq 22
|
864
|
+
expect(prop[:_description]).to eq :gradient_stop
|
865
|
+
expect(prop[:_structure]).to eq :XFPropGradientStop
|
866
|
+
|
867
|
+
prop_data = prop[:xfPropDataBlob_d]
|
868
|
+
expect(prop_data[:numPosition]).to eq 0.5
|
869
|
+
|
870
|
+
color = prop_data[:color]
|
871
|
+
expect(color[:fValidRGBA]).to eq true
|
872
|
+
expect(color[:xclrType]).to eq 3
|
873
|
+
expect(color[:xclrType_d]).to eq :XCLRTHEMED
|
874
|
+
expect(color[:icv]).to eq 9
|
875
|
+
expect(color[:nTintShade]).to eq 13107
|
876
|
+
expect(color[:nTintShade_d]).to eq 0.4
|
877
|
+
expect(color[:dwRgba]).to eq :A9D08EFF
|
878
|
+
end
|
879
|
+
|
880
|
+
it 'Decodes structure of xfPropType 0x05 (XFPropColor, text color)' do
|
881
|
+
prop = custom1.find { |e| e[:xfPropType] == 5 }
|
882
|
+
expect(prop[:cb]).to eq 12
|
883
|
+
expect(prop[:_description]).to eq :text_color
|
884
|
+
expect(prop[:_structure]).to eq :XFPropColor
|
885
|
+
|
886
|
+
prop_data = prop[:xfPropDataBlob_d]
|
887
|
+
expect(prop_data[:fValidRGBA]).to eq true
|
888
|
+
expect(prop_data[:xclrType]).to eq 3
|
889
|
+
expect(prop_data[:xclrType_d]).to eq :XCLRTHEMED
|
890
|
+
expect(prop_data[:icv]).to eq 5
|
891
|
+
expect(prop_data[:nTintShade]).to eq -8190
|
892
|
+
expect(prop_data[:nTintShade_d]).to eq -0.25
|
893
|
+
expect(prop_data[:dwRgba]).to eq :C65911FF
|
894
|
+
end
|
895
|
+
|
896
|
+
it 'Decodes structure of xfPropType 0x06 (XFPropBorder, top border formatting)' do
|
897
|
+
prop = custom1.find { |e| e[:xfPropType] == 6 }
|
898
|
+
expect(prop[:cb]).to eq 14
|
899
|
+
expect(prop[:_description]).to eq :top_border
|
900
|
+
expect(prop[:_structure]).to eq :XFPropBorder
|
901
|
+
|
902
|
+
prop_data = prop[:xfPropDataBlob_d]
|
903
|
+
expect(prop_data[:dgBorder]).to eq 8
|
904
|
+
expect(prop_data[:dgBorder_d]).to eq :MEDIUMDASHED
|
905
|
+
|
906
|
+
color = prop_data[:color]
|
907
|
+
expect(color[:fValidRGBA]).to eq true
|
908
|
+
expect(color[:xclrType]).to eq 3
|
909
|
+
expect(color[:xclrType_d]).to eq :XCLRTHEMED
|
910
|
+
expect(color[:icv]).to eq 9
|
911
|
+
expect(color[:nTintShade]).to eq 0
|
912
|
+
expect(color[:nTintShade_d]).to eq 0.0
|
913
|
+
expect(color[:dwRgba]).to eq :'70AD47FF'
|
914
|
+
end
|
915
|
+
|
916
|
+
it 'Decodes structure of xfPropType 0x07 (XFPropBorder, bottom border formatting)' do
|
917
|
+
prop = custom1.find { |e| e[:xfPropType] == 7 }
|
918
|
+
expect(prop[:cb]).to eq 14
|
919
|
+
expect(prop[:_description]).to eq :bottom_border
|
920
|
+
expect(prop[:_structure]).to eq :XFPropBorder
|
921
|
+
|
922
|
+
prop_data = prop[:xfPropDataBlob_d]
|
923
|
+
expect(prop_data[:dgBorder]).to eq 1
|
924
|
+
expect(prop_data[:dgBorder_d]).to eq :THIN
|
925
|
+
|
926
|
+
color = prop_data[:color]
|
927
|
+
expect(color[:fValidRGBA]).to eq true
|
928
|
+
expect(color[:xclrType]).to eq 2
|
929
|
+
expect(color[:xclrType_d]).to eq :XCLRRGB
|
930
|
+
expect(color[:icv]).to eq 0xFF
|
931
|
+
expect(color[:nTintShade]).to eq 0
|
932
|
+
expect(color[:nTintShade_d]).to eq 0.0
|
933
|
+
expect(color[:dwRgba]).to eq :'7030A0FF'
|
934
|
+
end
|
935
|
+
|
936
|
+
it 'Decodes structure of xfPropType 0x08 (XFPropBorder, left border formatting)' do
|
937
|
+
prop = custom1.find { |e| e[:xfPropType] == 8 }
|
938
|
+
expect(prop[:cb]).to eq 14
|
939
|
+
expect(prop[:_description]).to eq :left_border
|
940
|
+
expect(prop[:_structure]).to eq :XFPropBorder
|
941
|
+
|
942
|
+
prop_data = prop[:xfPropDataBlob_d]
|
943
|
+
expect(prop_data[:dgBorder]).to eq 5
|
944
|
+
expect(prop_data[:dgBorder_d]).to eq :THICK
|
945
|
+
|
946
|
+
color = prop_data[:color]
|
947
|
+
expect(color[:fValidRGBA]).to eq true
|
948
|
+
expect(color[:xclrType]).to eq 2
|
949
|
+
expect(color[:xclrType_d]).to eq :XCLRRGB
|
950
|
+
expect(color[:icv]).to eq 0xFF
|
951
|
+
expect(color[:nTintShade]).to eq 0
|
952
|
+
expect(color[:nTintShade_d]).to eq 0.0
|
953
|
+
expect(color[:dwRgba]).to eq :'2A8EF2FF'
|
954
|
+
end
|
955
|
+
|
956
|
+
it 'Decodes structure of xfPropType 0x09 (XFPropBorder, right border formatting)' do
|
957
|
+
prop = custom1.find { |e| e[:xfPropType] == 9 }
|
958
|
+
expect(prop[:cb]).to eq 14
|
959
|
+
expect(prop[:_description]).to eq :right_border
|
960
|
+
expect(prop[:_structure]).to eq :XFPropBorder
|
961
|
+
|
962
|
+
prop_data = prop[:xfPropDataBlob_d]
|
963
|
+
expect(prop_data[:dgBorder]).to eq 3
|
964
|
+
expect(prop_data[:dgBorder_d]).to eq :DASHED
|
965
|
+
|
966
|
+
color = prop_data[:color]
|
967
|
+
expect(color[:fValidRGBA]).to eq true
|
968
|
+
expect(color[:xclrType]).to eq 2
|
969
|
+
expect(color[:xclrType_d]).to eq :XCLRRGB
|
970
|
+
expect(color[:icv]).to eq 0xFF
|
971
|
+
expect(color[:nTintShade]).to eq 0
|
972
|
+
expect(color[:nTintShade_d]).to eq 0.0
|
973
|
+
expect(color[:dwRgba]).to eq :FF0000FF
|
974
|
+
end
|
975
|
+
|
976
|
+
it 'Decodes structure of xfPropType 0x0A (XFPropBorder, diagonal border formatting)' do
|
977
|
+
prop = custom1.find { |e| e[:xfPropType] == 10 }
|
978
|
+
expect(prop[:cb]).to eq 14
|
979
|
+
expect(prop[:_description]).to eq :diagonal_border
|
980
|
+
expect(prop[:_structure]).to eq :XFPropBorder
|
981
|
+
|
982
|
+
prop_data = prop[:xfPropDataBlob_d]
|
983
|
+
expect(prop_data[:dgBorder]).to eq 9
|
984
|
+
expect(prop_data[:dgBorder_d]).to eq :DASHDOT
|
985
|
+
|
986
|
+
color = prop_data[:color]
|
987
|
+
expect(color[:fValidRGBA]).to eq true
|
988
|
+
expect(color[:xclrType]).to eq 3
|
989
|
+
expect(color[:xclrType_d]).to eq :XCLRTHEMED
|
990
|
+
expect(color[:icv]).to eq 7
|
991
|
+
expect(color[:nTintShade]).to eq 0
|
992
|
+
expect(color[:nTintShade_d]).to eq 0.0
|
993
|
+
expect(color[:dwRgba]).to eq :FFC000FF
|
994
|
+
end
|
995
|
+
|
996
|
+
it 'Decodes structure of 0x0D (diagonal up border)' do
|
997
|
+
prop = custom1.find { |e| e[:xfPropType] == 13 }
|
998
|
+
expect(prop[:cb]).to eq 5
|
999
|
+
expect(prop[:_description]).to eq :diagonal_up
|
1000
|
+
expect(prop[:_structure]).to eq :_bool1b
|
1001
|
+
expect(prop[:xfPropDataBlob_d]).to eq true
|
1002
|
+
end
|
1003
|
+
|
1004
|
+
it 'Decodes structure of 0x0E (diagonal down)' do
|
1005
|
+
prop = custom1.find { |e| e[:xfPropType] == 14 }
|
1006
|
+
expect(prop[:cb]).to eq 5
|
1007
|
+
expect(prop[:_description]).to eq :diagonal_down
|
1008
|
+
expect(prop[:_structure]).to eq :_bool1b
|
1009
|
+
expect(prop[:xfPropDataBlob_d]).to eq true
|
1010
|
+
end
|
1011
|
+
end
|
1012
|
+
|
1013
|
+
# It seems that in StyleExt record Excel uses only the xfPropTypes that specify
|
1014
|
+
# color, to render the resuling RGB values (e.g. themed color with applied tint,
|
1015
|
+
# or RGB color) to the dwRgba field. Other properties, even though they are
|
1016
|
+
# specified for the style, are written to style XFs. At any rate, there seems
|
1017
|
+
# no way to make Excel to write out these properties to a StyleExt record using
|
1018
|
+
# the UI.
|
1019
|
+
context 'Seemingly unused xfPropTypes' do
|
1020
|
+
# it 'Decodes structure of xfPropType 0x00 (FillPattern)' # used by TableStyleElement/DXF
|
1021
|
+
# it 'Decodes structure of xfPropType 0x0B (XFPropBorder, vertical border formatting)' # used by TableStyleElement/DXF
|
1022
|
+
# it 'Decodes structure of xfPropType 0x0C (XFPropBorder, horizontal border formatting)' # used by TableStyleElement/DXF
|
1023
|
+
it 'Decodes structure of 0x0F (HorizAlign, horizontal alignment)'
|
1024
|
+
it 'Decodes structure of 0x10 (VertAlign, vertical alignment)'
|
1025
|
+
it 'Decodes structure of 0x11 (XFPropTextRotation, text rotation)'
|
1026
|
+
it 'Decodes structure of 0x12 (indentation level)'
|
1027
|
+
it 'Decodes structure of 0x13 (ReadingOrder, reading order)'
|
1028
|
+
it 'Decodes structure of 0x14 (text wrap)'
|
1029
|
+
it 'Decodes structure of 0x15 (justify distributed)'
|
1030
|
+
it 'Decodes structure of 0x16 (shrink to fit)'
|
1031
|
+
it 'Decodes structure of 0x17 (cell is merged)'
|
1032
|
+
it 'Decodes structure of 0x18 (LPWideString, font name)'
|
1033
|
+
# it 'Decodes structure of 0x19 (Bold, font_weight)' # used by TableStyleElement/DXF
|
1034
|
+
# it 'Decodes structure of 0x1A (Underline, underline style)' # used by TableStyleElement/DXF
|
1035
|
+
it 'Decodes structure of 0x1B (Script, script style)'
|
1036
|
+
# it 'Decodes structure of 0x1C (text is italicized)' # used by TableStyleElement/DXF
|
1037
|
+
# it 'Decodes structure of 0x1D (text has strikethrough formatting)' # used by TableStyleElement/DXF
|
1038
|
+
it 'Decodes structure of 0x1E (text has an outline style)'
|
1039
|
+
it 'Decodes structure of 0x1F (text has a shadow style)'
|
1040
|
+
it 'Decodes structure of 0x20 (text is condensed)'
|
1041
|
+
it 'Decodes structure of 0x21 (text is extended)'
|
1042
|
+
it 'Decodes structure of 0x22 (font character set)'
|
1043
|
+
it 'Decodes structure of 0x23 (font family)'
|
1044
|
+
it 'Decodes structure of 0x24 (text size)'
|
1045
|
+
it 'Decodes structure of 0x25 (FontScheme, font scheme)'
|
1046
|
+
it 'Decodes structure of 0x26 (XLUnicodeString, number format string)'
|
1047
|
+
it 'Decodes structure of 0x29 (IFmt, number format identifier)'
|
1048
|
+
it 'Decodes structure of 0x2A (text relative indentation)'
|
1049
|
+
it 'Decodes structure of 0x2B (locked)'
|
1050
|
+
it 'Decodes structure of 0x2C (hidden)'
|
1051
|
+
end
|
1052
|
+
end
|
1053
|
+
|
1054
|
+
context 'TableStyles, TableStyle, TableStyleElement records' do
|
1055
|
+
before(:context) do
|
1056
|
+
@browser = Unxls::Biff8::Browser.new(testfile('biff8', 'tablestyle.xls'))
|
1057
|
+
end
|
1058
|
+
|
1059
|
+
let(:ts_name) { 'Custom Table Style' }
|
1060
|
+
let(:pts_name) { 'Custom PivotTable Style' }
|
1061
|
+
|
1062
|
+
it 'Decodes TableStyles record' do
|
1063
|
+
record = @browser.globals[:TableStyles]
|
1064
|
+
expect(record[:frtHeader]).to be
|
1065
|
+
expect(record[:cts]).to eq 146
|
1066
|
+
expect(record[:cchDefTableStyle]).to eq 18
|
1067
|
+
expect(record[:cchDefPivotStyle]).to eq 23
|
1068
|
+
expect(record[:rgchDefTableStyle]).to eq ts_name
|
1069
|
+
expect(record[:rgchDefPivotStyle]).to eq pts_name
|
1070
|
+
end
|
1071
|
+
|
1072
|
+
it 'Decodes TableStyle record' do
|
1073
|
+
record = @browser.get_tablestyle(ts_name)
|
1074
|
+
expect(record[:rgchName]).to eq ts_name
|
1075
|
+
expect(record[:fIsPivot]).to eq false
|
1076
|
+
expect(record[:fIsTable]).to eq true
|
1077
|
+
expect(record[:ctse]).to be_between(1, 28)
|
1078
|
+
expect(record[:cchName]).to eq 18
|
1079
|
+
|
1080
|
+
record = @browser.get_tablestyle(pts_name)
|
1081
|
+
expect(record[:rgchName]).to eq pts_name
|
1082
|
+
expect(record[:fIsPivot]).to eq true
|
1083
|
+
expect(record[:fIsTable]).to eq false
|
1084
|
+
expect(record[:ctse]).to be_between(1, 28)
|
1085
|
+
expect(record[:cchName]).to eq 23
|
1086
|
+
end
|
1087
|
+
|
1088
|
+
context 'TableStyleElement record' do
|
1089
|
+
[
|
1090
|
+
{ tseType: 0x00, tseType_d: :whole_table }, # Whole Table
|
1091
|
+
{ tseType: 0x01, tseType_d: :header_row }, # Header Row
|
1092
|
+
{ tseType: 0x02, tseType_d: :total_row }, # Grand Total Row
|
1093
|
+
{ tseType: 0x03, tseType_d: :first_column }, # First Column
|
1094
|
+
{ tseType: 0x04, tseType_d: :last_column }, # Grand Total Column
|
1095
|
+
{ tseType: 0x05, tseType_d: :row_stripe_1, size: 4 }, # First Row Stripe
|
1096
|
+
{ tseType: 0x06, tseType_d: :row_stripe_2, size: 5 }, # Second Row Stripe
|
1097
|
+
{ tseType: 0x07, tseType_d: :column_stripe_1, size: 2 }, # First Column Stripe
|
1098
|
+
{ tseType: 0x08, tseType_d: :column_stripe_2, size: 3 }, # Second Column Stripe
|
1099
|
+
{ tseType: 0x09, tseType_d: :first_cell_header }, # First Header Cell
|
1100
|
+
{ tseType: 0x0A, tseType_d: :last_cell_header }, # Last Header Cell
|
1101
|
+
{ tseType: 0x0B, tseType_d: :first_cell_total }, # First Total Cell
|
1102
|
+
{ tseType: 0x0C, tseType_d: :last_cell_total }, # Last Total Cell
|
1103
|
+
{ tseType: 0x0D, tseType_d: :pt_outermost_subtotal_columns }, # Subtotal Column 1
|
1104
|
+
{ tseType: 0x0E, tseType_d: :pt_alternating_even_subtotal_columns }, # Subtotal Column 2
|
1105
|
+
{ tseType: 0x0F, tseType_d: :pt_alternating_odd_subtotal_columns }, # Subtotal Column 3
|
1106
|
+
{ tseType: 0x10, tseType_d: :pt_outermost_subtotal_rows }, # Subtotal Row 1
|
1107
|
+
{ tseType: 0x11, tseType_d: :pt_alternating_even_subtotal_rows }, # Subtotal Row 2
|
1108
|
+
{ tseType: 0x12, tseType_d: :pt_alternating_odd_subtotal_rows }, # Subtotal Row 3
|
1109
|
+
{ tseType: 0x13, tseType_d: :pt_empty_rows_after_each_subtotal_row }, # Blank Row
|
1110
|
+
{ tseType: 0x14, tseType_d: :pt_outermost_column_subheadings }, # Column Subheading 1
|
1111
|
+
{ tseType: 0x15, tseType_d: :pt_alternating_even_column_subheadings }, # Column Subheading 2
|
1112
|
+
{ tseType: 0x16, tseType_d: :pt_alternating_odd_column_subheadings }, # Column Subheading 3
|
1113
|
+
{ tseType: 0x17, tseType_d: :pt_outermost_row_subheadings }, # Row Subheading 1
|
1114
|
+
{ tseType: 0x18, tseType_d: :pt_alternating_even_row_subheadings }, # Row Subheading 2
|
1115
|
+
{ tseType: 0x19, tseType_d: :pt_alternating_odd_row_subheadings }, # Row Subheading 3
|
1116
|
+
{ tseType: 0x1A, tseType_d: :pt_page_field_captions }, # Report Filter Labels
|
1117
|
+
{ tseType: 0x1B, tseType_d: :pt_page_item_captions }, # Report Filter Values
|
1118
|
+
].each do |params|
|
1119
|
+
it "Decodes records of type #{params[:tseType_d]}" do
|
1120
|
+
style_name = params[:tseType_d].to_s.start_with?('pt_') ? pts_name : ts_name
|
1121
|
+
element = @browser.get_tablestyleelement(style_name, params[:tseType_d])
|
1122
|
+
expect(element[:tseType]).to eq params[:tseType]
|
1123
|
+
expect(element[:tseType_d]).to eq params[:tseType_d]
|
1124
|
+
if params[:size]
|
1125
|
+
expect(element[:size]).to eq params[:size]
|
1126
|
+
else
|
1127
|
+
expect(element[:size]).to be_between(1, 9)
|
1128
|
+
end
|
1129
|
+
expect(element[:index]).to be_an_kind_of Integer
|
1130
|
+
end
|
1131
|
+
end
|
1132
|
+
|
1133
|
+
it 'Adds custom :_tsi field to reference parent TableStyle record' do
|
1134
|
+
styles = @browser.globals[:TableStyle]
|
1135
|
+
styles.each do |style|
|
1136
|
+
index = style[:_record][:index]
|
1137
|
+
number_of_elements = @browser.globals[:TableStyleElement].select { |r| r[:_tsi] == index }.size
|
1138
|
+
expect(style[:ctse]).to eq number_of_elements
|
1139
|
+
end
|
1140
|
+
end
|
1141
|
+
end
|
1142
|
+
end
|
1143
|
+
|
1144
|
+
context 'XF record' do
|
1145
|
+
before(:context) do
|
1146
|
+
@browser = Unxls::Biff8::Browser.new(testfile('biff8', 'xf.xls'))
|
1147
|
+
end
|
1148
|
+
|
1149
|
+
# Relation to Font record
|
1150
|
+
it 'Decodes ifnt field' do
|
1151
|
+
ifnt = @browser.get_xf(1, 2, 0)[:ifnt]
|
1152
|
+
ifnt -= 1 if ifnt >= 4 # See p. 677
|
1153
|
+
expect(@browser.globals[:Font][ifnt][:fontName]).to eq 'Mongolian Baiti'
|
1154
|
+
end
|
1155
|
+
|
1156
|
+
# Relation to Format record
|
1157
|
+
it 'Decodes ifmt field' do
|
1158
|
+
expect(@browser.get_xf(1, 2, 1)[:ifmt]).to eq 0
|
1159
|
+
|
1160
|
+
ifmt = @browser.get_xf(1, 3, 1)[:ifmt]
|
1161
|
+
format = @browser.globals[:Format].find { |r| r[:ifmt] == ifmt }
|
1162
|
+
expect(format[:stFormat]).to eq '"Format フォーマット"'
|
1163
|
+
end
|
1164
|
+
|
1165
|
+
# Format - Cells - Protection - Locked
|
1166
|
+
it 'Decodes fLocked field' do
|
1167
|
+
expect(@browser.get_xf(1, 2, 2)[:fLocked]).to eq true
|
1168
|
+
expect(@browser.get_xf(1, 3, 2)[:fLocked]).to eq false
|
1169
|
+
end
|
1170
|
+
|
1171
|
+
# Format - Cells - Protection - Hidden
|
1172
|
+
it 'Decodes fHidden field' do
|
1173
|
+
expect(@browser.get_xf(1, 2, 3)[:fHidden]).to eq true
|
1174
|
+
expect(@browser.get_xf(1, 3, 3)[:fHidden]).to eq false
|
1175
|
+
end
|
1176
|
+
|
1177
|
+
# Lotus 1-2-3 prefixes for text alignment. Enable (Win-only): Tools - Options - Transition - Transition navigation keys
|
1178
|
+
# Mac version seems to keep these fields between saves
|
1179
|
+
it 'Decodes f123Prefix field' do
|
1180
|
+
expect(@browser.get_xf(1, 2, 4)[:f123Prefix]).to eq false # none
|
1181
|
+
expect(@browser.get_xf(1, 3, 4)[:f123Prefix]).to eq true # ' single quote 0x27 (left-aligned)
|
1182
|
+
expect(@browser.get_xf(1, 4, 4)[:f123Prefix]).to eq true # " double quote 0x22 (right-aligned)
|
1183
|
+
expect(@browser.get_xf(1, 5, 4)[:f123Prefix]).to eq true # ^ caret 0x5E (centered)
|
1184
|
+
expect(@browser.get_xf(1, 6, 4)[:f123Prefix]).to eq true # \ backslash 0x5C (justified)
|
1185
|
+
end
|
1186
|
+
|
1187
|
+
# Specifies whether XF is a style
|
1188
|
+
it 'Decodes fStyle field' do
|
1189
|
+
14.times do |i|
|
1190
|
+
expect(@browser.globals[:XF][i][:fStyle]).to eq true # Built-in style XFs
|
1191
|
+
end
|
1192
|
+
|
1193
|
+
expect(@browser.globals[:XF][15][:fStyle]).to eq false # Default cell format XF
|
1194
|
+
|
1195
|
+
user_style1_xf_index = @browser.get_xf(1, 5, 5)[:ixfParent] # "User style 1" XF
|
1196
|
+
expect(@browser.globals[:XF][user_style1_xf_index][:fStyle]).to eq true
|
1197
|
+
end
|
1198
|
+
|
1199
|
+
# Relation of cell XF to its style XF
|
1200
|
+
it 'Decodes ixfParent field' do
|
1201
|
+
expect(@browser.globals[:XF][0][:ixfParent]).to eq 0xFFF # Builtin Normal style XF
|
1202
|
+
expect(@browser.globals[:XF][15][:ixfParent]).to eq 0 # Default cell format XF
|
1203
|
+
expect(@browser.get_xf(1, 2, 5)[:ixfParent]).to eq 0 # Cell set to Normal style
|
1204
|
+
expect(@browser.get_xf(1, 3, 5)[:ixfParent]).to eq 16 # Set to 20% - Accent1 style
|
1205
|
+
expect(@browser.get_xf(1, 4, 5)[:ixfParent]).to eq 40 # Set to Bad style
|
1206
|
+
end
|
1207
|
+
|
1208
|
+
# Names of built-in style XFs
|
1209
|
+
it 'Adds _description field for 16 default XFs' do
|
1210
|
+
expect(@browser.globals[:XF][0][:_description]).to eq :'Normal style'
|
1211
|
+
expect(@browser.globals[:XF][1][:_description]).to eq :'Row outline level 1'
|
1212
|
+
expect(@browser.globals[:XF][8][:_description]).to eq :'Column outline level 1'
|
1213
|
+
expect(@browser.globals[:XF][15][:_description]).to eq :'Default cell format'
|
1214
|
+
expect(@browser.globals[:XF][16][:_description]).to eq nil
|
1215
|
+
end
|
1216
|
+
|
1217
|
+
# Decoded fStyle field
|
1218
|
+
it 'Adds _type field' do
|
1219
|
+
expect(@browser.globals[:XF][0][:_type]).to eq :stylexf
|
1220
|
+
expect(@browser.globals[:XF][15][:_type]).to eq :cellxf
|
1221
|
+
expect(@browser.get_xf(1, 5, 5)[:_type]).to eq :cellxf
|
1222
|
+
end
|
1223
|
+
|
1224
|
+
# Text alignment
|
1225
|
+
it 'Decodes alc field and adds alc_d field' do
|
1226
|
+
# Can't find usage of alc == 0xFF (:ALCNIL, Alignment not specified) in cell XFs and style XFs
|
1227
|
+
|
1228
|
+
f = @browser.get_xf(1, 2, 6)
|
1229
|
+
expect(f[:alc]).to eq 0
|
1230
|
+
expect(f[:alc_d]).to eq :ALCGEN
|
1231
|
+
|
1232
|
+
f = @browser.get_xf(1, 3, 6)
|
1233
|
+
expect(f[:alc]).to eq 1
|
1234
|
+
expect(f[:alc_d]).to eq :ALCLEFT
|
1235
|
+
|
1236
|
+
f = @browser.get_xf(1, 4, 6)
|
1237
|
+
expect(f[:alc]).to eq 2
|
1238
|
+
expect(f[:alc_d]).to eq :ALCCTR
|
1239
|
+
|
1240
|
+
f = @browser.get_xf(1, 5, 6)
|
1241
|
+
expect(f[:alc]).to eq 3
|
1242
|
+
expect(f[:alc_d]).to eq :ALCRIGHT
|
1243
|
+
|
1244
|
+
f = @browser.get_xf(1, 6, 6)
|
1245
|
+
expect(f[:alc]).to eq 4
|
1246
|
+
expect(f[:alc_d]).to eq :ALCFILL
|
1247
|
+
|
1248
|
+
f = @browser.get_xf(1, 7, 6)
|
1249
|
+
expect(f[:alc]).to eq 5
|
1250
|
+
expect(f[:alc_d]).to eq :ALCJUST
|
1251
|
+
|
1252
|
+
f = @browser.get_xf(1, 8, 6)
|
1253
|
+
expect(f[:alc]).to eq 6
|
1254
|
+
expect(f[:alc_d]).to eq :ALCCONTCTR
|
1255
|
+
|
1256
|
+
f = @browser.get_xf(1, 9, 6)
|
1257
|
+
expect(f[:alc]).to eq 7
|
1258
|
+
expect(f[:alc_d]).to eq :ALCDIST
|
1259
|
+
end
|
1260
|
+
|
1261
|
+
# Alignment - Text control - Wrap text
|
1262
|
+
it 'Decodes fWrap field' do
|
1263
|
+
expect(@browser.get_xf(1, 2, 7)[:fWrap]).to eq true
|
1264
|
+
expect(@browser.get_xf(1, 3, 7)[:fWrap]).to eq false
|
1265
|
+
end
|
1266
|
+
|
1267
|
+
# Vertical alignment
|
1268
|
+
it 'Decodes alcV field and adds alcV_d field' do
|
1269
|
+
f = @browser.get_xf(1, 2, 8)
|
1270
|
+
expect(f[:alcV]).to eq 0
|
1271
|
+
expect(f[:alcV_d]).to eq :ALCVTOP
|
1272
|
+
|
1273
|
+
f = @browser.get_xf(1, 3, 8)
|
1274
|
+
expect(f[:alcV]).to eq 1
|
1275
|
+
expect(f[:alcV_d]).to eq :ALCVCTR
|
1276
|
+
|
1277
|
+
f = @browser.get_xf(1, 4, 8)
|
1278
|
+
expect(f[:alcV]).to eq 2
|
1279
|
+
expect(f[:alcV_d]).to eq :ALCVBOT
|
1280
|
+
|
1281
|
+
f = @browser.get_xf(1, 5, 8)
|
1282
|
+
expect(f[:alcV]).to eq 3
|
1283
|
+
expect(f[:alcV_d]).to eq :ALCVJUST
|
1284
|
+
|
1285
|
+
f = @browser.get_xf(1, 6, 8)
|
1286
|
+
expect(f[:alcV]).to eq 4
|
1287
|
+
expect(f[:alcV_d]).to eq :ALCVDIST
|
1288
|
+
end
|
1289
|
+
|
1290
|
+
# Alignment - Text alignment – Justify distributed
|
1291
|
+
it 'Decodes fJustLast field' do
|
1292
|
+
expect(@browser.get_xf(1, 2, 9)[:fJustLast]).to eq true # 'Justify distributed' on
|
1293
|
+
expect(@browser.get_xf(1, 3, 9)[:fJustLast]).to eq false
|
1294
|
+
end
|
1295
|
+
|
1296
|
+
# Alignment – Orientation
|
1297
|
+
it 'Decodes trot field and adds trot_d field' do
|
1298
|
+
f = @browser.get_xf(1, 2, 10) # normal
|
1299
|
+
expect(f[:trot]).to eq 0
|
1300
|
+
expect(f[:trot_d]).to eq :counterclockwise
|
1301
|
+
|
1302
|
+
f = @browser.get_xf(1, 3, 10) # -45
|
1303
|
+
expect(f[:trot]).to eq 45
|
1304
|
+
expect(f[:trot_d]).to eq :counterclockwise
|
1305
|
+
|
1306
|
+
f = @browser.get_xf(1, 4, 10) # +45
|
1307
|
+
expect(f[:trot]).to eq 135
|
1308
|
+
expect(f[:trot_d]).to eq :clockwise
|
1309
|
+
|
1310
|
+
f = @browser.get_xf(1, 5, 10) # vertical
|
1311
|
+
expect(f[:trot]).to eq 255
|
1312
|
+
expect(f[:trot_d]).to eq :vertical
|
1313
|
+
end
|
1314
|
+
|
1315
|
+
# Alignment - Text alignment – Indent
|
1316
|
+
it 'Decodes cIndent field' do
|
1317
|
+
expect(@browser.get_xf(1, 2, 11)[:cIndent]).to eq 0
|
1318
|
+
expect(@browser.get_xf(1, 3, 11)[:cIndent]).to eq 5
|
1319
|
+
expect(@browser.get_xf(1, 4, 11)[:cIndent]).to eq 15
|
1320
|
+
end
|
1321
|
+
|
1322
|
+
# Alignment - Text control - Shrink to fit
|
1323
|
+
it 'Decodes fShrinkToFit field' do
|
1324
|
+
expect(@browser.get_xf(1, 2, 12)[:fShrinkToFit]).to eq true
|
1325
|
+
expect(@browser.get_xf(1, 3, 13)[:fShrinkToFit]).to eq false
|
1326
|
+
end
|
1327
|
+
|
1328
|
+
# Alignment - Right-to-left - Text direction
|
1329
|
+
it 'Decodes iReadingOrder field and adds iReadingOrder_d field' do
|
1330
|
+
f = @browser.get_xf(1, 2, 13)
|
1331
|
+
expect(f[:iReadingOrder]).to eq 0
|
1332
|
+
expect(f[:iReadingOrder_d]).to eq :READING_ORDER_CONTEXT
|
1333
|
+
|
1334
|
+
f = @browser.get_xf(1, 3, 13)
|
1335
|
+
expect(f[:iReadingOrder]).to eq 1
|
1336
|
+
expect(f[:iReadingOrder_d]).to eq :READING_ORDER_LTR
|
1337
|
+
|
1338
|
+
f = @browser.get_xf(1, 4, 13)
|
1339
|
+
expect(f[:iReadingOrder]).to eq 2
|
1340
|
+
expect(f[:iReadingOrder_d]).to eq :READING_ORDER_RTL
|
1341
|
+
end
|
1342
|
+
|
1343
|
+
# Flags specifying whether changes in parent XFs are not reflected in this XF
|
1344
|
+
# Hoping that there are records in the test file with this bit set to true:
|
1345
|
+
%i(fAtrNum fAtrFnt fAtrAlc fAtrBdr fAtrPat fAtrProt).each do |attr|
|
1346
|
+
it "Decodes #{attr} field" do
|
1347
|
+
[true, false].each do |val|
|
1348
|
+
xf = @browser.mapif(:XF, first: true) { |r, _, _, ssi| r if ssi == 0 && r[attr] == val }
|
1349
|
+
expect(xf[attr]).to eq val
|
1350
|
+
end
|
1351
|
+
end
|
1352
|
+
end
|
1353
|
+
|
1354
|
+
let(:border_styles) do
|
1355
|
+
[
|
1356
|
+
{ row: 2, num_val: 0x00, decoded_val: :NONE },
|
1357
|
+
{ row: 3, num_val: 0x01, decoded_val: :THIN },
|
1358
|
+
{ row: 4, num_val: 0x02, decoded_val: :MEDIUM },
|
1359
|
+
{ row: 5, num_val: 0x03, decoded_val: :DASHED },
|
1360
|
+
{ row: 6, num_val: 0x04, decoded_val: :DOTTED },
|
1361
|
+
{ row: 7, num_val: 0x05, decoded_val: :THICK },
|
1362
|
+
{ row: 8, num_val: 0x06, decoded_val: :DOUBLE },
|
1363
|
+
{ row: 9, num_val: 0x07, decoded_val: :HAIR },
|
1364
|
+
{ row: 10, num_val: 0x08, decoded_val: :MEDIUMDASHED },
|
1365
|
+
{ row: 11, num_val: 0x09, decoded_val: :DASHDOT },
|
1366
|
+
{ row: 12, num_val: 0x0A, decoded_val: :MEDIUMDASHDOT },
|
1367
|
+
{ row: 13, num_val: 0x0B, decoded_val: :DASHDOTDOT },
|
1368
|
+
{ row: 14, num_val: 0x0C, decoded_val: :MEDIUMDASHDOTDOT },
|
1369
|
+
{ row: 15, num_val: 0x0D, decoded_val: :SLANTEDDASHDOTDOT },
|
1370
|
+
]
|
1371
|
+
end
|
1372
|
+
|
1373
|
+
[
|
1374
|
+
{ col: 14, num_prop: :dgLeft, decoded_prop: :dgLeft_d },
|
1375
|
+
{ col: 15, num_prop: :dgRight, decoded_prop: :dgRight_d },
|
1376
|
+
{ col: 17, num_prop: :dgTop, decoded_prop: :dgTop_d },
|
1377
|
+
{ col: 18, num_prop: :dgBottom, decoded_prop: :dgBottom_d },
|
1378
|
+
{ col: 26, num_prop: :dgDiag, decoded_prop: :dgDiag_d },
|
1379
|
+
].each do |attrs|
|
1380
|
+
it "Decodes #{attrs[:num_prop]} and adds #{attrs[:decoded_prop]} field" do
|
1381
|
+
border_styles.each do |bs|
|
1382
|
+
f = @browser.get_xf(1, bs[:row], attrs[:col])
|
1383
|
+
expect(f[attrs[:decoded_prop]]).to eq bs[:decoded_val]
|
1384
|
+
expect(f[attrs[:num_prop]]).to eq bs[:num_val]
|
1385
|
+
end
|
1386
|
+
end
|
1387
|
+
end
|
1388
|
+
|
1389
|
+
# Border colors (see 2.5.161 Icv)
|
1390
|
+
let(:border_icvs) do
|
1391
|
+
{
|
1392
|
+
2 => 0x40, # default text color
|
1393
|
+
3 => 0x23, # cyan
|
1394
|
+
4 => 0x22, # yellow
|
1395
|
+
}
|
1396
|
+
end
|
1397
|
+
|
1398
|
+
{
|
1399
|
+
icvLeft: 20,
|
1400
|
+
icvRight: 21,
|
1401
|
+
icvTop: 23,
|
1402
|
+
icvBottom: 24,
|
1403
|
+
icvDiag: 25,
|
1404
|
+
}.each do |field, col|
|
1405
|
+
it "Decodes #{field} field" do
|
1406
|
+
border_icvs.each do |row, icv|
|
1407
|
+
expect(@browser.get_xf(1, row, col)[field]).to eq icv
|
1408
|
+
end
|
1409
|
+
end
|
1410
|
+
end
|
1411
|
+
|
1412
|
+
let(:diagonals) do
|
1413
|
+
[
|
1414
|
+
{ row: 2, grbitDiag: 0, grbitDiag_d: :'No diagonal border' },
|
1415
|
+
{ row: 3, grbitDiag: 1, grbitDiag_d: :'Diagonal-down border' },
|
1416
|
+
{ row: 4, grbitDiag: 2, grbitDiag_d: :'Diagonal-up border' },
|
1417
|
+
{ row: 5, grbitDiag: 3, grbitDiag_d: :'Both diagonal-down and diagonal-up' },
|
1418
|
+
]
|
1419
|
+
end
|
1420
|
+
|
1421
|
+
# Diagonal lines (borders)
|
1422
|
+
it 'Decodes grbitDiag and adds grbitDiag_d field' do
|
1423
|
+
diagonals.each do |attrs|
|
1424
|
+
f = @browser.get_xf(1, attrs[:row], 22)
|
1425
|
+
expect(f[:grbitDiag]).to eq(attrs[:grbitDiag])
|
1426
|
+
expect(f[:grbitDiag_d]).to eq(attrs[:grbitDiag_d])
|
1427
|
+
end
|
1428
|
+
end
|
1429
|
+
|
1430
|
+
it 'Decodes fHasXFExt field' do
|
1431
|
+
expect(@browser.get_xf(1, 2, 27)[:fHasXFExt]).to eq true # Uses advanced styling (RGB font color) specified in XFExt
|
1432
|
+
expect(@browser.get_xf(1, 3, 27)[:fHasXFExt]).to eq false
|
1433
|
+
end
|
1434
|
+
|
1435
|
+
let(:fill_patterns) do
|
1436
|
+
[
|
1437
|
+
{ row: 2, fls: 0x00, fls_d: :FLSNULL }, # No fill pattern
|
1438
|
+
{ row: 3, fls: 0x01, fls_d: :FLSSOLID }, # Solid
|
1439
|
+
{ row: 4, fls: 0x02, fls_d: :FLSMEDGRAY }, # 50% gray
|
1440
|
+
{ row: 5, fls: 0x03, fls_d: :FLSDKGRAY }, # 75% gray
|
1441
|
+
{ row: 6, fls: 0x04, fls_d: :FLSLTGRAY }, # 25% gray
|
1442
|
+
{ row: 7, fls: 0x05, fls_d: :FLSDKHOR }, # Horizontal stripe
|
1443
|
+
{ row: 8, fls: 0x06, fls_d: :FLSDKVER }, # Vertical stripe
|
1444
|
+
{ row: 9, fls: 0x07, fls_d: :FLSDKDOWN }, # Reverse diagonal stripe
|
1445
|
+
{ row: 10, fls: 0x08, fls_d: :FLSDKUP }, # Diagonal stripe
|
1446
|
+
{ row: 11, fls: 0x09, fls_d: :FLSDKGRID }, # Diagonal crosshatch
|
1447
|
+
{ row: 12, fls: 0x0A, fls_d: :FLSDKTRELLIS }, # Thick diagonal crosshatch
|
1448
|
+
{ row: 13, fls: 0x0B, fls_d: :FLSLTHOR }, # Thin horizontal stripe
|
1449
|
+
{ row: 14, fls: 0x0C, fls_d: :FLSLTVER }, # Thin vertical stripe
|
1450
|
+
{ row: 15, fls: 0x0D, fls_d: :FLSLTDOWN }, # Thin reverse diagonal stripe
|
1451
|
+
{ row: 16, fls: 0x0E, fls_d: :FLSLTUP }, # Thin diagonal stripe
|
1452
|
+
{ row: 17, fls: 0x0F, fls_d: :FLSLTGRID }, # Thin horizontal crosshatch
|
1453
|
+
{ row: 18, fls: 0x10, fls_d: :FLSLTTRELLIS }, # Thin diagonal crosshatch
|
1454
|
+
{ row: 19, fls: 0x11, fls_d: :FLSGRAY125 }, # 12.5% gray
|
1455
|
+
{ row: 20, fls: 0x12, fls_d: :FLSGRAY0625 }, # 6.25% gray
|
1456
|
+
]
|
1457
|
+
end
|
1458
|
+
|
1459
|
+
# Cell pattern type
|
1460
|
+
it 'Decodes fls and adds fls_d field' do
|
1461
|
+
fill_patterns.each do |attrs|
|
1462
|
+
f = @browser.get_xf(1, attrs[:row], 28)
|
1463
|
+
expect(f[:fls]).to eq attrs[:fls]
|
1464
|
+
expect(f[:fls_d]).to eq attrs[:fls_d]
|
1465
|
+
end
|
1466
|
+
end
|
1467
|
+
|
1468
|
+
# Cell pattern color (see 2.5.161 Icv)
|
1469
|
+
it 'Decodes icvFore field' do
|
1470
|
+
expect(@browser.get_xf(1, 2, 29)[:icvFore]).to eq 0x40 # default foreground color
|
1471
|
+
expect(@browser.get_xf(1, 3, 29)[:icvFore]).to eq 0x23 # cyan
|
1472
|
+
end
|
1473
|
+
|
1474
|
+
# Cell background color (see 2.5.161 Icv)
|
1475
|
+
it 'Decodes icvBack field' do
|
1476
|
+
expect(@browser.get_xf(1, 2, 30)[:icvBack]).to eq 0x41 # default background color
|
1477
|
+
expect(@browser.get_xf(1, 3, 30)[:icvBack]).to eq 0x23 # cyan
|
1478
|
+
end
|
1479
|
+
|
1480
|
+
# Cell is a PivotTable button
|
1481
|
+
it 'Decodes fsxButton field' do
|
1482
|
+
expect(@browser.get_xf(1, 2, 31)[:fsxButton]).to eq true
|
1483
|
+
expect(@browser.get_xf(1, 3, 31)[:fsxButton]).to eq true
|
1484
|
+
expect(@browser.get_xf(1, 4, 31)[:fsxButton]).to eq false
|
1485
|
+
end
|
1486
|
+
end
|
1487
|
+
|
1488
|
+
context 'XFExt record' do
|
1489
|
+
before(:context) do
|
1490
|
+
@browser = Unxls::Biff8::Browser.new(testfile('biff8', 'xfext.xls'))
|
1491
|
+
end
|
1492
|
+
|
1493
|
+
it 'Decodes frtHeader field' do
|
1494
|
+
f = @browser.get_xfext(1, 3, 0)
|
1495
|
+
expect(f[:frtHeader][:rt]).to eq f[:_record][:id]
|
1496
|
+
expect(f[:frtHeader][:grBitFrt]).to eq({ fFrtRef: false, fFrtAlert: false })
|
1497
|
+
end
|
1498
|
+
|
1499
|
+
it 'Decodes ixfe field' do
|
1500
|
+
expect(@browser.get_xfext(1, 3, 0)[:ixfe]).to be_an_kind_of Integer
|
1501
|
+
expect(@browser.get_xf(1, 3, 0)[:fHasXFExt]).to eq true
|
1502
|
+
end
|
1503
|
+
|
1504
|
+
it 'Decodes cexts field' do
|
1505
|
+
expect(@browser.get_xfext(1, 3, 1)[:cexts]).to eq 2
|
1506
|
+
end
|
1507
|
+
|
1508
|
+
context 'Decodes rgExt field' do
|
1509
|
+
context 'ExtProp type 0x04 (FullColorExt) for foreground (pattern) color' do
|
1510
|
+
it 'With xclrType 0 (XCLRAUTO)' # cannot find an example
|
1511
|
+
|
1512
|
+
it 'With xclrType 1 (XCLRINDEXED)' # cannot find an example
|
1513
|
+
|
1514
|
+
it 'With xclrType 2 (XCLRRGB)' do
|
1515
|
+
extprop = @browser.get_xfext(1, 3, 0)[:rgExt].find { |h| h[:_property] == :'cell interior foreground color' }
|
1516
|
+
|
1517
|
+
expect(extprop[:extType]).to eq 4
|
1518
|
+
expect(extprop[:extType_d]).to eq :FullColorExt
|
1519
|
+
expect(extprop[:cb]).to eq 20
|
1520
|
+
|
1521
|
+
propdata = extprop[:extPropData]
|
1522
|
+
expect(propdata[:xclrType]).to eq 2
|
1523
|
+
expect(propdata[:xclrType_d]).to eq :XCLRRGB
|
1524
|
+
expect(propdata[:nTintShade]).to eq 0
|
1525
|
+
expect(propdata[:nTintShade_d]).to eq 0.0
|
1526
|
+
expect(propdata[:xclrValue]).to eq :'2A8EF2FF' # rgb(42, 142, 242)
|
1527
|
+
end
|
1528
|
+
|
1529
|
+
it 'With xclrType 3 (XCLRTHEMED)' do
|
1530
|
+
extprop = @browser.get_xfext(1, 4, 0)[:rgExt].find { |h| h[:_property] == :'cell interior foreground color' }
|
1531
|
+
|
1532
|
+
expect(extprop[:extType]).to eq 4
|
1533
|
+
expect(extprop[:extType_d]).to eq :FullColorExt
|
1534
|
+
expect(extprop[:cb]).to eq 20
|
1535
|
+
|
1536
|
+
propdata = extprop[:extPropData]
|
1537
|
+
expect(propdata[:xclrType]).to eq 3
|
1538
|
+
expect(propdata[:xclrType_d]).to eq :XCLRTHEMED # Blue, Accent 5, lighter 40%
|
1539
|
+
expect(propdata[:nTintShade]).to eq 13105
|
1540
|
+
expect(propdata[:nTintShade_d]).to eq 0.4
|
1541
|
+
expect(propdata[:xclrValue]).to eq 8
|
1542
|
+
end
|
1543
|
+
|
1544
|
+
it 'With xclrType 4 (XCLRNINCHED)' # cannot find an example
|
1545
|
+
end
|
1546
|
+
|
1547
|
+
context 'ExtProp type 0x05 (FullColorExt) for background color' do
|
1548
|
+
it 'With xclrType 2 (XCLRRGB)' do
|
1549
|
+
extprop = @browser.get_xfext(1, 3, 1)[:rgExt].find { |h| h[:_property] == :'cell interior background color' }
|
1550
|
+
propdata = extprop[:extPropData]
|
1551
|
+
expect(propdata[:xclrType_d]).to eq :XCLRRGB
|
1552
|
+
expect(propdata[:xclrValue]).to eq :'2A8EF2FF'
|
1553
|
+
end
|
1554
|
+
|
1555
|
+
it 'With xclrType 3 (XCLRTHEMED)' do
|
1556
|
+
extprop = @browser.get_xfext(1, 4, 1)[:rgExt].find { |h| h[:_property] == :'cell interior background color' }
|
1557
|
+
propdata = extprop[:extPropData]
|
1558
|
+
expect(propdata[:xclrType_d]).to eq :XCLRTHEMED
|
1559
|
+
expect(propdata[:nTintShade_d]).to eq 0.4
|
1560
|
+
expect(propdata[:xclrValue]).to eq 8
|
1561
|
+
end
|
1562
|
+
end
|
1563
|
+
|
1564
|
+
context 'ExtProp type 0x06 (XFExtGradient) for cell interior gradient' do
|
1565
|
+
def get_gradient_rgext(substream_index, row, column)
|
1566
|
+
@browser.get_xfext(substream_index, row, column)[:rgExt].find do |h|
|
1567
|
+
h[:_property] == :'cell interior gradient fill'
|
1568
|
+
end
|
1569
|
+
end
|
1570
|
+
|
1571
|
+
def check_type(extprop)
|
1572
|
+
expect(extprop[:extType]).to eq 6
|
1573
|
+
expect(extprop[:extType_d]).to eq :XFExtGradient
|
1574
|
+
end
|
1575
|
+
|
1576
|
+
def check_linear_grad_props(propdata)
|
1577
|
+
expect(propdata[:type]).to eq 0
|
1578
|
+
expect(propdata[:type_d]).to eq :linear
|
1579
|
+
%i(numFillToLeft numFillToRight numFillToTop numFillToBottom).each do |prop|
|
1580
|
+
expect(propdata[prop]).to eql 0.0
|
1581
|
+
end
|
1582
|
+
end
|
1583
|
+
|
1584
|
+
def check_rectangular_grad_props(propdata)
|
1585
|
+
expect(propdata[:type]).to eq 1
|
1586
|
+
expect(propdata[:type_d]).to eq :rectangular
|
1587
|
+
expect(propdata[:numDegree]).to eql 0.0
|
1588
|
+
end
|
1589
|
+
|
1590
|
+
def check_gradstops_2_themed(gradstops)
|
1591
|
+
(0..1).each do |i|
|
1592
|
+
expect(gradstops[i][:xclrType]).to eq 3
|
1593
|
+
expect(gradstops[i][:xclrType_d]).to eq :XCLRTHEMED
|
1594
|
+
expect(gradstops[i][:numTint]).to eql 0.0
|
1595
|
+
end
|
1596
|
+
|
1597
|
+
expect(gradstops[0][:xclrValue]).to eq 0
|
1598
|
+
expect(gradstops[0][:numPosition]).to eql 0.0
|
1599
|
+
|
1600
|
+
expect(gradstops[1][:xclrValue]).to eq 4
|
1601
|
+
expect(gradstops[1][:numPosition]).to eql 1.0
|
1602
|
+
end
|
1603
|
+
|
1604
|
+
def check_gradstops_3_themed(gradstops)
|
1605
|
+
(0..2).each do |i|
|
1606
|
+
expect(gradstops[i][:xclrType]).to eq 3
|
1607
|
+
expect(gradstops[i][:xclrType_d]).to eq :XCLRTHEMED
|
1608
|
+
expect(gradstops[i][:numTint]).to eql 0.0
|
1609
|
+
end
|
1610
|
+
|
1611
|
+
expect(gradstops[0][:xclrValue]).to eq 0
|
1612
|
+
expect(gradstops[0][:numPosition]).to eql 0.0
|
1613
|
+
|
1614
|
+
expect(gradstops[1][:xclrValue]).to eq 4
|
1615
|
+
expect(gradstops[1][:numPosition]).to eql 0.5
|
1616
|
+
|
1617
|
+
expect(gradstops[2][:xclrValue]).to eq 0
|
1618
|
+
expect(gradstops[2][:numPosition]).to eql 1.0
|
1619
|
+
end
|
1620
|
+
|
1621
|
+
it 'With XFExtGradient type 0 (linear), 0 deg, 2 stops, themed color' do
|
1622
|
+
extprop = get_gradient_rgext(1, 3, 2)
|
1623
|
+
|
1624
|
+
check_type(extprop)
|
1625
|
+
expect(extprop[:cb]).to eq 96
|
1626
|
+
|
1627
|
+
propdata = extprop[:extPropData]
|
1628
|
+
check_linear_grad_props(propdata)
|
1629
|
+
expect(propdata[:numDegree]).to eql 0.0
|
1630
|
+
expect(propdata[:cGradStops]).to eq 2
|
1631
|
+
|
1632
|
+
check_gradstops_2_themed(propdata[:rgGradStops])
|
1633
|
+
end
|
1634
|
+
|
1635
|
+
it 'With XFExtGradient type 0 (linear), 45 deg, 2 stops, themed color' do
|
1636
|
+
extprop = get_gradient_rgext(1, 4, 2)
|
1637
|
+
|
1638
|
+
check_type(extprop)
|
1639
|
+
expect(extprop[:cb]).to eq 96
|
1640
|
+
|
1641
|
+
propdata = extprop[:extPropData]
|
1642
|
+
check_linear_grad_props(propdata)
|
1643
|
+
expect(propdata[:numDegree]).to eql 45.0
|
1644
|
+
expect(propdata[:cGradStops]).to eq 2
|
1645
|
+
|
1646
|
+
check_gradstops_2_themed(propdata[:rgGradStops])
|
1647
|
+
end
|
1648
|
+
|
1649
|
+
it 'With XFExtGradient type 0 (linear), 90 deg, 3 stops, themed color' do
|
1650
|
+
extprop = get_gradient_rgext(1, 5, 2)
|
1651
|
+
|
1652
|
+
check_type(extprop)
|
1653
|
+
expect(extprop[:cb]).to eq 118
|
1654
|
+
|
1655
|
+
propdata = extprop[:extPropData]
|
1656
|
+
check_linear_grad_props(propdata)
|
1657
|
+
expect(propdata[:numDegree]).to eql 90.0
|
1658
|
+
expect(propdata[:cGradStops]).to eq 3
|
1659
|
+
|
1660
|
+
check_gradstops_3_themed(propdata[:rgGradStops])
|
1661
|
+
end
|
1662
|
+
|
1663
|
+
it 'With XFExtGradient type 1 (rectangular), left-top aligned, 2 stops, themed color' do
|
1664
|
+
extprop = get_gradient_rgext(1, 6, 2)
|
1665
|
+
|
1666
|
+
check_type(extprop)
|
1667
|
+
expect(extprop[:cb]).to eq 96
|
1668
|
+
|
1669
|
+
propdata = extprop[:extPropData]
|
1670
|
+
check_rectangular_grad_props(propdata)
|
1671
|
+
%i(numFillToLeft numFillToRight numFillToTop numFillToBottom).each do |prop|
|
1672
|
+
expect(propdata[prop]).to eql 0.0
|
1673
|
+
end
|
1674
|
+
expect(propdata[:cGradStops]).to eq 2
|
1675
|
+
|
1676
|
+
check_gradstops_2_themed(propdata[:rgGradStops])
|
1677
|
+
end
|
1678
|
+
|
1679
|
+
it 'With XFExtGradient type 1 (rectangular), right-top aligned, 2 stops, themed color' do
|
1680
|
+
extprop = get_gradient_rgext(1, 7, 2)
|
1681
|
+
|
1682
|
+
check_type(extprop)
|
1683
|
+
expect(extprop[:cb]).to eq 96
|
1684
|
+
|
1685
|
+
propdata = extprop[:extPropData]
|
1686
|
+
check_rectangular_grad_props(propdata)
|
1687
|
+
%i(numFillToLeft numFillToRight).each { |prop| expect(propdata[prop]).to eql 1.0 }
|
1688
|
+
%i(numFillToTop numFillToBottom).each { |prop| expect(propdata[prop]).to eql 0.0 }
|
1689
|
+
expect(propdata[:cGradStops]).to eq 2
|
1690
|
+
|
1691
|
+
check_gradstops_2_themed(propdata[:rgGradStops])
|
1692
|
+
end
|
1693
|
+
|
1694
|
+
it 'With XFExtGradient type 1 (rectangular), right-bottom aligned, 2 stops, themed color' do
|
1695
|
+
extprop = get_gradient_rgext(1, 8, 2)
|
1696
|
+
|
1697
|
+
check_type(extprop)
|
1698
|
+
expect(extprop[:cb]).to eq 96
|
1699
|
+
|
1700
|
+
propdata = extprop[:extPropData]
|
1701
|
+
check_rectangular_grad_props(propdata)
|
1702
|
+
%i(numFillToLeft numFillToRight numFillToTop numFillToBottom).each do |prop|
|
1703
|
+
expect(propdata[prop]).to eql 1.0
|
1704
|
+
end
|
1705
|
+
expect(propdata[:cGradStops]).to eq 2
|
1706
|
+
|
1707
|
+
check_gradstops_2_themed(propdata[:rgGradStops])
|
1708
|
+
end
|
1709
|
+
|
1710
|
+
it 'With XFExtGradient type 1 (rectangular), centered, 2 stops, themed color' do
|
1711
|
+
extprop = get_gradient_rgext(1, 9, 2)
|
1712
|
+
|
1713
|
+
check_type(extprop)
|
1714
|
+
expect(extprop[:cb]).to eq 96
|
1715
|
+
|
1716
|
+
propdata = extprop[:extPropData]
|
1717
|
+
check_rectangular_grad_props(propdata)
|
1718
|
+
%i(numFillToLeft numFillToRight numFillToTop numFillToBottom).each do |prop|
|
1719
|
+
expect(propdata[prop]).to eql 0.5
|
1720
|
+
end
|
1721
|
+
expect(propdata[:cGradStops]).to eq 2
|
1722
|
+
|
1723
|
+
check_gradstops_2_themed(propdata[:rgGradStops])
|
1724
|
+
end
|
1725
|
+
|
1726
|
+
it 'With XFExtGradient type 0 (linear), 0 deg, 2 stops, custom colors' do
|
1727
|
+
extprop = get_gradient_rgext(1, 10, 2)
|
1728
|
+
|
1729
|
+
check_type(extprop)
|
1730
|
+
expect(extprop[:cb]).to eq 96
|
1731
|
+
|
1732
|
+
propdata = extprop[:extPropData]
|
1733
|
+
check_linear_grad_props(propdata)
|
1734
|
+
expect(propdata[:numDegree]).to eql 0.0
|
1735
|
+
expect(propdata[:cGradStops]).to eq 2
|
1736
|
+
|
1737
|
+
gradstops = propdata[:rgGradStops]
|
1738
|
+
expect(gradstops[0][:xclrType]).to eq 3
|
1739
|
+
expect(gradstops[0][:xclrType_d]).to eq :XCLRTHEMED
|
1740
|
+
expect(gradstops[0][:xclrValue]).to eq 8
|
1741
|
+
expect(gradstops[0][:numPosition]).to eql 0.0
|
1742
|
+
expect(gradstops[0][:numTint]).to eql 0.4
|
1743
|
+
|
1744
|
+
expect(gradstops[1][:xclrType]).to eq 2
|
1745
|
+
expect(gradstops[1][:xclrType_d]).to eq :XCLRRGB
|
1746
|
+
expect(gradstops[1][:xclrValue]).to eq :'2A8EF2FF'
|
1747
|
+
expect(gradstops[1][:numPosition]).to eql 1.0
|
1748
|
+
expect(gradstops[1][:numTint]).to eql 0.0
|
1749
|
+
end
|
1750
|
+
end
|
1751
|
+
|
1752
|
+
let(:border_colors) do
|
1753
|
+
[
|
1754
|
+
{ row: 3, propdata: { xclrType: 2, xclrType_d: :XCLRRGB, nTintShade_d: 0.0, xclrValue: :'2A8EF2FF' } },
|
1755
|
+
{ row: 4, propdata: { xclrType: 3, xclrType_d: :XCLRTHEMED, nTintShade_d: 0.4, xclrValue: 8 } },
|
1756
|
+
]
|
1757
|
+
end
|
1758
|
+
|
1759
|
+
[
|
1760
|
+
{ column: 3, type: 0x07, property: :'top cell border color' },
|
1761
|
+
{ column: 4, type: 0x08, property: :'bottom cell border color' },
|
1762
|
+
{ column: 5, type: 0x09, property: :'left cell border color' },
|
1763
|
+
{ column: 6, type: 0x0A, property: :'right cell border color' },
|
1764
|
+
{ column: 7, type: 0x0B, property: :'diagonal cell border color' },
|
1765
|
+
].each do |attrs|
|
1766
|
+
it "ExtProp type #{attrs[:type]} (FullColorExt) for #{attrs[:property]}" do
|
1767
|
+
border_colors.each do |bc|
|
1768
|
+
extprop = @browser.get_xfext(1, bc[:row], attrs[:column])[:rgExt].find do |h|
|
1769
|
+
h[:_property] == attrs[:property]
|
1770
|
+
end
|
1771
|
+
|
1772
|
+
expect(extprop[:extType]).to eq(attrs[:type])
|
1773
|
+
expect(extprop[:extType_d]).to eq(:FullColorExt)
|
1774
|
+
|
1775
|
+
# nTintShade written by Excel seems to differ a bit for exactly same colors
|
1776
|
+
propdata = extprop[:extPropData]
|
1777
|
+
case extprop[:extPropData][:xclrType_d]
|
1778
|
+
when :XCLRRGB then expect(propdata[:nTintShade]).to eq 0
|
1779
|
+
else expect(propdata[:nTintShade]).to be_within(2).of(13103) # :XCLRTHEMED
|
1780
|
+
end
|
1781
|
+
propdata.delete(:nTintShade)
|
1782
|
+
|
1783
|
+
expect(propdata).to eq(bc[:propdata])
|
1784
|
+
end
|
1785
|
+
end
|
1786
|
+
end
|
1787
|
+
|
1788
|
+
context 'ExtProp type 0x0D (FullColorExt) for cell text color' do
|
1789
|
+
it 'With xclrType 2 (XCLRRGB)' do
|
1790
|
+
extprop = @browser.get_xfext(1, 3, 8)[:rgExt].find { |h| h[:_property] == :'cell text color' }
|
1791
|
+
propdata = extprop[:extPropData]
|
1792
|
+
expect(propdata[:xclrType_d]).to eq :XCLRRGB
|
1793
|
+
expect(propdata[:xclrValue]).to eq :'2A8EF2FF'
|
1794
|
+
end
|
1795
|
+
|
1796
|
+
it 'With xclrType 3 (XCLRTHEMED)' do
|
1797
|
+
extprop = @browser.get_xfext(1, 4, 8)[:rgExt].find { |h| h[:_property] == :'cell text color' }
|
1798
|
+
propdata = extprop[:extPropData]
|
1799
|
+
expect(propdata[:xclrType_d]).to eq :XCLRTHEMED
|
1800
|
+
expect(propdata[:nTintShade_d]).to eq 0.4
|
1801
|
+
expect(propdata[:xclrValue]).to eq 8
|
1802
|
+
end
|
1803
|
+
end
|
1804
|
+
|
1805
|
+
it 'Type 0x0E (FontScheme) for font scheme' do
|
1806
|
+
xfext = @browser.globals[:XFExt][0]
|
1807
|
+
extprop = xfext[:rgExt].find { |h| h[:_property] == :'font scheme' }
|
1808
|
+
expect(extprop[:extType]).to eq 14
|
1809
|
+
expect(extprop[:extType_d]).to eq :FontScheme
|
1810
|
+
expect(extprop[:cb]).to eq 5
|
1811
|
+
expect(extprop[:extPropData][:FontScheme]).to eq 2
|
1812
|
+
expect(extprop[:extPropData][:FontScheme_d]).to eq :XFSMINOR
|
1813
|
+
end
|
1814
|
+
|
1815
|
+
it 'Type 0x0F (_int1b) for text indentation level' do
|
1816
|
+
extprop = @browser.get_xfext(1, 3, 10)[:rgExt].find { |h| h[:_property] == :'text indentation level' }
|
1817
|
+
expect(extprop[:extType]).to eq 15
|
1818
|
+
expect(extprop[:extType_d]).to eq :_int1b
|
1819
|
+
expect(extprop[:cb]).to eq 6
|
1820
|
+
expect(extprop[:extPropData]).to eq 16
|
1821
|
+
end
|
1822
|
+
end
|
1823
|
+
end
|
1824
|
+
end
|
1825
|
+
|
1826
|
+
context 'Workbook stream, Worksheet substream' do
|
1827
|
+
context 'Various single records' do
|
1828
|
+
before(:context) do
|
1829
|
+
@browser1 = Unxls::Biff8::Browser.new(testfile('biff8', 'empty.xls'))
|
1830
|
+
@browser2 = Unxls::Biff8::Browser.new(testfile('biff8', 'preferences.xls'))
|
1831
|
+
end
|
1832
|
+
|
1833
|
+
it 'Decodes WsBool record' do
|
1834
|
+
wsb1 = @browser1.wbs[1][:WsBool] # empty.xls / Sheet1
|
1835
|
+
expect(wsb1[:fShowAutoBreaks]).to eq true # Defaults
|
1836
|
+
expect(wsb1[:fDialog]).to eq false
|
1837
|
+
expect(wsb1[:fApplyStyles]).to eq false
|
1838
|
+
expect(wsb1[:fRowSumsBelow]).to eq true
|
1839
|
+
expect(wsb1[:fColSumsRight]).to eq true
|
1840
|
+
expect(wsb1[:fFitToPage]).to eq false
|
1841
|
+
expect(wsb1[:fSyncHoriz]).to eq false
|
1842
|
+
expect(wsb1[:fSyncVert]).to eq false
|
1843
|
+
expect(wsb1[:fAltExprEval]).to eq false
|
1844
|
+
expect(wsb1[:fFormulaEntry]).to eq false
|
1845
|
+
|
1846
|
+
wsb2 = @browser2.wbs[1][:WsBool] # preferences.xls / Worksheet
|
1847
|
+
expect(wsb2[:fShowAutoBreaks]).to eq false # Options - View - Window Options - Page breaks
|
1848
|
+
expect(wsb2[:fDialog]).to eq false # Sheet is a Dialog sheet
|
1849
|
+
expect(wsb2[:fApplyStyles]).to eq true # Data - Group and outline - Settings - Automatic styles
|
1850
|
+
expect(wsb2[:fRowSumsBelow]).to eq false # Data - Group and outline - Settings - Summary rows below detail
|
1851
|
+
expect(wsb2[:fColSumsRight]).to eq false # Data - Group and outline - Settings - Summary columns to right of detail
|
1852
|
+
expect(wsb2[:fFitToPage]).to eq true # Options - International - Printing - Allow A4/Letter paper resizing
|
1853
|
+
expect(wsb2[:fSyncHoriz]).to eq true # To set the attributes add and run this subroutine for the 'Worksheet' sheet:
|
1854
|
+
expect(wsb2[:fSyncVert]).to eq true # Sub Main(); Windows.Arrange xlArrangeStyleVertical, True, True, True; End Sub;
|
1855
|
+
expect(wsb2[:fAltExprEval]).to eq true # Options - Transition - Sheet options - Transition formula evaluation
|
1856
|
+
expect(wsb2[:fFormulaEntry]).to eq true # Options - Transition - Sheet options - Transition formula entry
|
1857
|
+
|
1858
|
+
wsb3 = @browser2.wbs[2][:WsBool] # preferences.xls / Dialog
|
1859
|
+
expect(wsb3[:fDialog]).to eq true
|
1860
|
+
end
|
1861
|
+
|
1862
|
+
context 'PhoneticInfo' do
|
1863
|
+
let(:record) { @browser2.wbs[1][:PhoneticInfo] }
|
1864
|
+
|
1865
|
+
it 'Decodes phs field' do
|
1866
|
+
phs = record[:phs]
|
1867
|
+
font = @browser2.get_font(phs[:ifnt])
|
1868
|
+
expect(font[:fontName]).to eq 'Hiragino Kaku Gothic Pro W3'
|
1869
|
+
expect(phs[:phType]).to eq 0
|
1870
|
+
expect(phs[:phType_d]).to eq :narrow_katakana
|
1871
|
+
expect(phs[:alcH]).to eq 1
|
1872
|
+
expect(phs[:alcH_d]).to eq :left
|
1873
|
+
end
|
1874
|
+
|
1875
|
+
it 'Decodes sqref field' do
|
1876
|
+
sqref = record[:sqref]
|
1877
|
+
expect(sqref[:cref]).to eq(2)
|
1878
|
+
|
1879
|
+
rgrefs = sqref[:rgrefs]
|
1880
|
+
expect(rgrefs[0][:rwFirst]).to eq 2
|
1881
|
+
expect(rgrefs[0][:rwLast]).to eq 3
|
1882
|
+
expect(rgrefs[0][:colFirst]).to eq 1
|
1883
|
+
expect(rgrefs[0][:colLast]).to eq 1
|
1884
|
+
expect(rgrefs[1][:rwFirst]).to eq 5
|
1885
|
+
expect(rgrefs[1][:rwLast]).to eq 5
|
1886
|
+
expect(rgrefs[1][:colFirst]).to eq 1
|
1887
|
+
expect(rgrefs[1][:colLast]).to eq 1
|
1888
|
+
end
|
1889
|
+
|
1890
|
+
end
|
1891
|
+
end
|
1892
|
+
|
1893
|
+
context 'CELLTABLE-related records' do
|
1894
|
+
before(:context) do
|
1895
|
+
@browser = Unxls::Biff8::Browser.new(testfile('biff8', 'cells.xls'))
|
1896
|
+
end
|
1897
|
+
|
1898
|
+
it 'Decodes Blank record' do
|
1899
|
+
record = @browser.get_cell(1, 4, 0)
|
1900
|
+
expect(record[:rw]).to eq 4
|
1901
|
+
expect(record[:col]).to eq 0
|
1902
|
+
expect(record[:ixfe]).to be_an_kind_of Integer
|
1903
|
+
expect(@browser.globals[:XF][record[:ixfe]][:fls_d]).to eq :FLSGRAY125
|
1904
|
+
end
|
1905
|
+
|
1906
|
+
context 'BoolErr record' do
|
1907
|
+
it 'Decodes basic fields' do
|
1908
|
+
record = @browser.get_cell(1, 25, 0)
|
1909
|
+
|
1910
|
+
expect(record[:rw]).to eq 25
|
1911
|
+
expect(record[:col]).to eq 0
|
1912
|
+
expect(record[:ixfe]).to be_an_kind_of Integer
|
1913
|
+
end
|
1914
|
+
|
1915
|
+
[
|
1916
|
+
{ fError: 0, row: 25, col: 0, bBoolErr: 0x01, bBoolErr_d: :True },
|
1917
|
+
{ fError: 0, row: 25, col: 1, bBoolErr: 0x00, bBoolErr_d: :False },
|
1918
|
+
{ fError: 1, row: 29, col: 0, bBoolErr: 0x00, bBoolErr_d: :'#NULL!' },
|
1919
|
+
{ fError: 1, row: 29, col: 1, bBoolErr: 0x07, bBoolErr_d: :'#DIV/0!' },
|
1920
|
+
{ fError: 1, row: 29, col: 2, bBoolErr: 0x0F, bBoolErr_d: :'#VALUE!' },
|
1921
|
+
{ fError: 1, row: 29, col: 3, bBoolErr: 0x17, bBoolErr_d: :'#REF!' },
|
1922
|
+
{ fError: 1, row: 29, col: 4, bBoolErr: 0x1D, bBoolErr_d: :'#NAME?' },
|
1923
|
+
{ fError: 1, row: 29, col: 5, bBoolErr: 0x24, bBoolErr_d: :'#NUM!' },
|
1924
|
+
{ fError: 1, row: 29, col: 6, bBoolErr: 0x2A, bBoolErr_d: :'#N/A' },
|
1925
|
+
# { fError: 1, row: 29, col: 7, bBoolErr: 0x2B, bBoolErr_d: :'#GETTING_DATA' }, # not a permanent error
|
1926
|
+
].each do |params|
|
1927
|
+
it "Decodes BoolErr containing #{params[:bBoolErr_d]}" do
|
1928
|
+
record = @browser.get_cell(1, params[:row], params[:col])
|
1929
|
+
|
1930
|
+
expect(record[:bes][:bBoolErr]).to eq params[:bBoolErr]
|
1931
|
+
expect(record[:bes][:bBoolErr_d]).to eq params[:bBoolErr_d]
|
1932
|
+
expect(record[:bes][:fError]).to eq params[:fError]
|
1933
|
+
end
|
1934
|
+
end
|
1935
|
+
end
|
1936
|
+
|
1937
|
+
context 'Formula record' do
|
1938
|
+
it 'Decodes basic fields' do
|
1939
|
+
record = @browser.get_cell(1, 33, 0)
|
1940
|
+
|
1941
|
+
expect(record[:rw]).to eq 33
|
1942
|
+
expect(record[:col]).to eq 0
|
1943
|
+
expect(record[:ixfe]).to be_an_kind_of Integer
|
1944
|
+
expect(record[:fAlwaysCalc]).to eq false
|
1945
|
+
expect(record[:fFill]).to eq false
|
1946
|
+
expect(record[:fShrFmla]).to eq false
|
1947
|
+
expect(record[:fClearErrors]).to eq false
|
1948
|
+
expect(record[:chn]).to eq :not_implemented
|
1949
|
+
expect(record[:formula]).to eq :not_implemented
|
1950
|
+
end
|
1951
|
+
|
1952
|
+
it 'Decodes Formula that returns a string' do
|
1953
|
+
record = @browser.get_cell(1, 33, 0)
|
1954
|
+
expect(record[:_value]).to eq nil
|
1955
|
+
expect(record[:_type]).to eq :string
|
1956
|
+
|
1957
|
+
value = @browser.get_cell(1, 33, 0, record_type: :String) # See 2.5.133 FormulaValue
|
1958
|
+
expect(value[:string]).to eq 'Text'
|
1959
|
+
end
|
1960
|
+
|
1961
|
+
it 'Decodes Formula that returns a float' do
|
1962
|
+
record = @browser.get_cell(1, 33, 1)
|
1963
|
+
expect(record[:_value]).to eq 2.0
|
1964
|
+
expect(record[:_type]).to eq :float
|
1965
|
+
end
|
1966
|
+
|
1967
|
+
it 'Decodes Formula that returns a boolean' do
|
1968
|
+
record = @browser.get_cell(1, 33, 2)
|
1969
|
+
expect(record[:_value]).to eq true
|
1970
|
+
expect(record[:_type]).to eq :boolean
|
1971
|
+
end
|
1972
|
+
|
1973
|
+
it 'Decodes Formula that returns an error' do
|
1974
|
+
record = @browser.get_cell(1, 33, 3)
|
1975
|
+
expect(record[:_value]).to eq :'N/A'
|
1976
|
+
expect(record[:_type]).to eq :error
|
1977
|
+
end
|
1978
|
+
|
1979
|
+
it 'Decodes Formula that returns an empty string' do
|
1980
|
+
record = @browser.get_cell(1, 33, 4)
|
1981
|
+
expect(record[:_value]).to eq ''
|
1982
|
+
expect(record[:_type]).to eq :blank_string
|
1983
|
+
end
|
1984
|
+
end
|
1985
|
+
|
1986
|
+
it 'Decodes LabelSst record' do
|
1987
|
+
[
|
1988
|
+
'Shared',
|
1989
|
+
'Text',
|
1990
|
+
' Text ',
|
1991
|
+
"Line 1\nLine 2",
|
1992
|
+
" Line 1 \n Line 2 "
|
1993
|
+
].each_with_index do |text, column|
|
1994
|
+
record = @browser.get_cell(1, 21, column)
|
1995
|
+
expect(record[:rw]).to eq 21
|
1996
|
+
expect(record[:col]).to eq column
|
1997
|
+
expect(record[:ixfe]).to be_an_kind_of Integer
|
1998
|
+
|
1999
|
+
expect(record[:isst]).to be_an_kind_of Integer
|
2000
|
+
sst = @browser.globals[:SST][:rgb][record[:isst]]
|
2001
|
+
expect(sst[:rgb]).to eq text
|
2002
|
+
end
|
2003
|
+
end
|
2004
|
+
|
2005
|
+
it 'Decodes MulBlank record' do
|
2006
|
+
record = @browser.get_cell(1, 0, 1)
|
2007
|
+
|
2008
|
+
expect(record[:rw]).to eq 0
|
2009
|
+
expect(record[:colFirst]).to eq 1
|
2010
|
+
expect(record[:colLast]).to eq 7
|
2011
|
+
expect(record[:rgixfe]).to be_an_instance_of Array
|
2012
|
+
expect(record[:rgixfe].size).to eq 7
|
2013
|
+
expect(record[:rgixfe].first).to eq 68
|
2014
|
+
end
|
2015
|
+
|
2016
|
+
it 'Decodes MulRk record' do
|
2017
|
+
record = @browser.get_cell(1, 16, 1)
|
2018
|
+
|
2019
|
+
expect(record[:rw]).to eq 16
|
2020
|
+
expect(record[:colFirst]).to eq 1
|
2021
|
+
expect(record[:colLast]).to eq 5
|
2022
|
+
expect(record[:rgrkrec]).to be_an_instance_of Array
|
2023
|
+
expect(record[:rgrkrec].size).to eq 5
|
2024
|
+
|
2025
|
+
[1.0, -1.0, 0.01, -0.01, 0.0,].each_with_index do |rk, index|
|
2026
|
+
rkrec = record[:rgrkrec][index]
|
2027
|
+
expect(rkrec[:ixfe]).to eq 15
|
2028
|
+
expect(rkrec[:RK]).to eq rk
|
2029
|
+
end
|
2030
|
+
end
|
2031
|
+
|
2032
|
+
it 'Decodes Number record' do
|
2033
|
+
[
|
2034
|
+
{ col: 1, num: 1.2345 },
|
2035
|
+
{ col: 2, num: -1.2345 },
|
2036
|
+
].each do |params|
|
2037
|
+
record = @browser.get_cell(1, 7, params[:col])
|
2038
|
+
|
2039
|
+
expect(record[:rw]).to eq 7
|
2040
|
+
expect(record[:col]).to eq params[:col]
|
2041
|
+
expect(record[:ixfe]).to eq 15
|
2042
|
+
expect(record[:num]).to eq params[:num]
|
2043
|
+
end
|
2044
|
+
end
|
2045
|
+
|
2046
|
+
it 'Decodes RK record' do
|
2047
|
+
[
|
2048
|
+
{ col: 1, RK: 1.0 },
|
2049
|
+
{ col: 3, RK: -1.0 },
|
2050
|
+
{ col: 5, RK: 0.01 },
|
2051
|
+
{ col: 7, RK: -0.01 },
|
2052
|
+
].each do |params|
|
2053
|
+
record = @browser.get_cell(1, 10, params[:col])
|
2054
|
+
|
2055
|
+
expect(record[:rw]).to eq 10
|
2056
|
+
expect(record[:col]).to eq params[:col]
|
2057
|
+
expect(record[:ixfe]).to eq 15
|
2058
|
+
expect(record[:RK]).to eq params[:RK]
|
2059
|
+
end
|
2060
|
+
end
|
2061
|
+
end
|
2062
|
+
|
2063
|
+
context 'Row, ColInfo, Dimensions, MergeCells records' do
|
2064
|
+
before(:context) do
|
2065
|
+
@browser = Unxls::Biff8::Browser.new(testfile('biff8', 'row-colinfo-dimensions-mergecells.xls'))
|
2066
|
+
end
|
2067
|
+
|
2068
|
+
context 'ColInfo record' do
|
2069
|
+
let(:default_width) { 2742 } # 10 points
|
2070
|
+
let(:default_ixfe) { 15 }
|
2071
|
+
let(:sheet_index) { 2 }
|
2072
|
+
|
2073
|
+
it 'Decodes for column with custom width' do
|
2074
|
+
colinfo = @browser.get_colinfo(sheet_index, 1)
|
2075
|
+
|
2076
|
+
expect(colinfo[:colFirst]).to eq 1
|
2077
|
+
expect(colinfo[:colLast]).to eq 1
|
2078
|
+
expect(colinfo[:coldx]).to eq 2486 # custom width (9 points)
|
2079
|
+
expect(colinfo[:ixfe]).to eq default_ixfe
|
2080
|
+
expect(colinfo[:fHidden]).to eq false
|
2081
|
+
expect(colinfo[:fUserSet]).to eq true
|
2082
|
+
expect(colinfo[:fBestFit]).to eq false
|
2083
|
+
expect(colinfo[:fPhonetic]).to eq false
|
2084
|
+
expect(colinfo[:iOutLevel]).to eq 0
|
2085
|
+
expect(colinfo[:fCollapsed]).to eq false
|
2086
|
+
end
|
2087
|
+
|
2088
|
+
it 'Decodes for column with custom styling' do
|
2089
|
+
colinfo = @browser.get_colinfo(sheet_index, 2)
|
2090
|
+
expect(colinfo[:colFirst]).to eq 2
|
2091
|
+
expect(colinfo[:colLast]).to eq 2
|
2092
|
+
expect(colinfo[:coldx]).to eq default_width
|
2093
|
+
|
2094
|
+
expect(colinfo[:ixfe]).to be_an_kind_of Integer # custom XF
|
2095
|
+
xfext = @browser.globals[:XFExt].find { |r| r[:ixfe] == colinfo[:ixfe] }
|
2096
|
+
extprop = xfext[:rgExt].find { |x| x[:_property] == :'cell interior foreground color' }
|
2097
|
+
expect(extprop[:extPropData][:xclrValue]).to eq :'2A8EF2FF'
|
2098
|
+
|
2099
|
+
expect(colinfo[:fHidden]).to eq false
|
2100
|
+
expect(colinfo[:fUserSet]).to eq false
|
2101
|
+
expect(colinfo[:fBestFit]).to eq false
|
2102
|
+
expect(colinfo[:fPhonetic]).to eq false
|
2103
|
+
expect(colinfo[:iOutLevel]).to eq 0
|
2104
|
+
expect(colinfo[:fCollapsed]).to eq false
|
2105
|
+
end
|
2106
|
+
|
2107
|
+
it 'Decodes for hidden column' do
|
2108
|
+
colinfo = @browser.get_colinfo(sheet_index, 3)
|
2109
|
+
expect(colinfo[:colFirst]).to eq 3
|
2110
|
+
expect(colinfo[:colLast]).to eq 3
|
2111
|
+
expect(colinfo[:coldx]).to eq 0
|
2112
|
+
expect(colinfo[:ixfe]).to eq default_ixfe
|
2113
|
+
expect(colinfo[:fHidden]).to eq true
|
2114
|
+
expect(colinfo[:fUserSet]).to eq true
|
2115
|
+
expect(colinfo[:fBestFit]).to eq false
|
2116
|
+
expect(colinfo[:fPhonetic]).to eq false
|
2117
|
+
expect(colinfo[:iOutLevel]).to eq 0
|
2118
|
+
expect(colinfo[:fCollapsed]).to eq false
|
2119
|
+
end
|
2120
|
+
|
2121
|
+
it 'Decodes for column with AutoFit Selection' do
|
2122
|
+
colinfo = @browser.get_colinfo(sheet_index, 4)
|
2123
|
+
expect(colinfo[:colFirst]).to eq 4
|
2124
|
+
expect(colinfo[:colLast]).to eq 4
|
2125
|
+
expect(colinfo[:coldx]).to eq 3254
|
2126
|
+
expect(colinfo[:ixfe]).to eq default_ixfe
|
2127
|
+
expect(colinfo[:fHidden]).to eq false
|
2128
|
+
expect(colinfo[:fUserSet]).to eq true
|
2129
|
+
expect(colinfo[:fBestFit]).to eq true
|
2130
|
+
expect(colinfo[:fPhonetic]).to eq false
|
2131
|
+
expect(colinfo[:iOutLevel]).to eq 0
|
2132
|
+
expect(colinfo[:fCollapsed]).to eq false
|
2133
|
+
end
|
2134
|
+
|
2135
|
+
it 'Decodes for column with phonetic fields displayed' do
|
2136
|
+
colinfo = @browser.get_colinfo(sheet_index, 5)
|
2137
|
+
expect(colinfo[:colFirst]).to eq 5
|
2138
|
+
expect(colinfo[:colLast]).to eq 5
|
2139
|
+
expect(colinfo[:coldx]).to eq default_width
|
2140
|
+
expect(colinfo[:ixfe]).to eq default_ixfe
|
2141
|
+
expect(colinfo[:fHidden]).to eq false
|
2142
|
+
expect(colinfo[:fUserSet]).to eq false
|
2143
|
+
expect(colinfo[:fBestFit]).to eq false
|
2144
|
+
expect(colinfo[:fPhonetic]).to eq true
|
2145
|
+
expect(colinfo[:iOutLevel]).to eq 0
|
2146
|
+
expect(colinfo[:fCollapsed]).to eq false
|
2147
|
+
end
|
2148
|
+
|
2149
|
+
# Columns G and H are actually collapsed, but the flag fCollapsed is not set,
|
2150
|
+
# whereas flags fHidden and fUserSet *are* set.
|
2151
|
+
# Seems like flag fCollapsed is only set to the columns
|
2152
|
+
# that follow a set of collapsed columns.
|
2153
|
+
it 'Decodes for collapsed columns' do
|
2154
|
+
# Column G, inner outline, collapsed
|
2155
|
+
colinfo = @browser.get_colinfo(sheet_index, 6)
|
2156
|
+
expect(colinfo[:colFirst]).to eq 6
|
2157
|
+
expect(colinfo[:colLast]).to eq 6
|
2158
|
+
expect(colinfo[:coldx]).to eq default_width
|
2159
|
+
expect(colinfo[:ixfe]).to eq default_ixfe
|
2160
|
+
expect(colinfo[:fHidden]).to eq true
|
2161
|
+
expect(colinfo[:fUserSet]).to eq true
|
2162
|
+
expect(colinfo[:fBestFit]).to eq false
|
2163
|
+
expect(colinfo[:fPhonetic]).to eq false
|
2164
|
+
expect(colinfo[:iOutLevel]).to eq 2
|
2165
|
+
expect(colinfo[:fCollapsed]).to eq false
|
2166
|
+
|
2167
|
+
# Column G, outer outline, collapsed
|
2168
|
+
colinfo = @browser.get_colinfo(sheet_index, 7)
|
2169
|
+
expect(colinfo[:colFirst]).to eq 7
|
2170
|
+
expect(colinfo[:colLast]).to eq 7
|
2171
|
+
expect(colinfo[:coldx]).to eq default_width
|
2172
|
+
expect(colinfo[:ixfe]).to eq default_ixfe
|
2173
|
+
expect(colinfo[:fHidden]).to eq true
|
2174
|
+
expect(colinfo[:fUserSet]).to eq true
|
2175
|
+
expect(colinfo[:fBestFit]).to eq false
|
2176
|
+
expect(colinfo[:fPhonetic]).to eq false
|
2177
|
+
expect(colinfo[:iOutLevel]).to eq 1
|
2178
|
+
expect(colinfo[:fCollapsed]).to eq true
|
2179
|
+
|
2180
|
+
# Column I with default settings, directly follows collapsed column G.
|
2181
|
+
colinfo = @browser.get_colinfo(sheet_index, 8)
|
2182
|
+
expect(colinfo[:colFirst]).to eq 8
|
2183
|
+
expect(colinfo[:colLast]).to eq 8
|
2184
|
+
expect(colinfo[:coldx]).to eq default_width
|
2185
|
+
expect(colinfo[:ixfe]).to eq default_ixfe
|
2186
|
+
expect(colinfo[:fHidden]).to eq false
|
2187
|
+
expect(colinfo[:fUserSet]).to eq false
|
2188
|
+
expect(colinfo[:fBestFit]).to eq false
|
2189
|
+
expect(colinfo[:fPhonetic]).to eq false
|
2190
|
+
expect(colinfo[:iOutLevel]).to eq 0
|
2191
|
+
expect(colinfo[:fCollapsed]).to eq true
|
2192
|
+
end
|
2193
|
+
end
|
2194
|
+
|
2195
|
+
context 'Row record' do
|
2196
|
+
let(:sheet_index) { 1 }
|
2197
|
+
|
2198
|
+
specify 'Row record not written if row has no content' do
|
2199
|
+
expect(@browser.get_row(sheet_index, 2)).to eq nil
|
2200
|
+
end
|
2201
|
+
|
2202
|
+
# colMic and colMac are the same for row groups of 16 records, e.g. rows 0-15, then 16-31 etc.
|
2203
|
+
# So if row 0 has cells in cols 1 and 3, and row 15 has cell in cols 5 and 10, both Row records
|
2204
|
+
# will have colMic = 1 and colMac = 10, but rows 16…31 will have their own "bounding box".
|
2205
|
+
it 'Decodes colMic and colMac fields' do
|
2206
|
+
(3..5).each do |row_index|
|
2207
|
+
row = @browser.get_row(sheet_index, row_index)
|
2208
|
+
expect(row[:colMic]).to eq 0
|
2209
|
+
expect(row[:colMac]).to eq 6
|
2210
|
+
end
|
2211
|
+
end
|
2212
|
+
|
2213
|
+
it 'Decodes miyRw and fUnsynced fields' do
|
2214
|
+
row = @browser.get_row(sheet_index, 7)
|
2215
|
+
expect(row[:miyRw]).to eq 400
|
2216
|
+
expect(row[:fUnsynced]).to eq false
|
2217
|
+
|
2218
|
+
row = @browser.get_row(sheet_index, 8)
|
2219
|
+
expect(row[:miyRw]).to eq 600
|
2220
|
+
expect(row[:fUnsynced]).to eq true
|
2221
|
+
end
|
2222
|
+
|
2223
|
+
it 'Decodes iOutLevel, fCollapsed and fDyZero fields' do
|
2224
|
+
[
|
2225
|
+
{ row_index: 10, iOutLevel: 4, fCollapsed: false, fDyZero: true },
|
2226
|
+
{ row_index: 11, iOutLevel: 3, fCollapsed: true, fDyZero: true },
|
2227
|
+
{ row_index: 12, iOutLevel: 2, fCollapsed: false, fDyZero: true },
|
2228
|
+
{ row_index: 13, iOutLevel: 1, fCollapsed: true, fDyZero: false },
|
2229
|
+
].each do |params|
|
2230
|
+
row = @browser.get_row(sheet_index, params[:row_index])
|
2231
|
+
expect(row[:iOutLevel]).to eq params[:iOutLevel]
|
2232
|
+
expect(row[:fCollapsed]).to eq params[:fCollapsed]
|
2233
|
+
expect(row[:fDyZero]).to eq params[:fDyZero]
|
2234
|
+
end
|
2235
|
+
|
2236
|
+
expect(@browser.get_row(sheet_index, 15)[:fDyZero]).to eq true
|
2237
|
+
end
|
2238
|
+
|
2239
|
+
it 'Decodes fGhostDirty and ixfe fields' do
|
2240
|
+
row = @browser.get_row(sheet_index, 17)
|
2241
|
+
expect(row[:fGhostDirty]).to eq true
|
2242
|
+
expect(row[:ixfe]).to be_an_kind_of Integer
|
2243
|
+
|
2244
|
+
row_xfext = @browser.mapif(:XFExt, first: true) { |r| r if r[:ixfe] == row[:ixfe] }
|
2245
|
+
color_ext = row_xfext[:rgExt].find { |p| p[:_property] == :'cell interior foreground color' }
|
2246
|
+
expect(color_ext[:extPropData][:xclrValue]).to eq :'187CE0FF'
|
2247
|
+
end
|
2248
|
+
|
2249
|
+
it 'Decodes fExAsc field' do
|
2250
|
+
{
|
2251
|
+
18 => false,
|
2252
|
+
19 => false,
|
2253
|
+
20 => true,
|
2254
|
+
21 => false,
|
2255
|
+
22 => true,
|
2256
|
+
23 => false,
|
2257
|
+
24 => true,
|
2258
|
+
25 => false,
|
2259
|
+
26 => true,
|
2260
|
+
}.each do |row_index, f_ex_asc|
|
2261
|
+
expect(@browser.get_row(sheet_index, row_index)[:fExAsc]).to eq f_ex_asc
|
2262
|
+
end
|
2263
|
+
end
|
2264
|
+
|
2265
|
+
it 'Decodes fExDes' do
|
2266
|
+
{
|
2267
|
+
27 => false,
|
2268
|
+
28 => true,
|
2269
|
+
29 => true,
|
2270
|
+
30 => false,
|
2271
|
+
31 => true,
|
2272
|
+
32 => true,
|
2273
|
+
33 => false,
|
2274
|
+
34 => true,
|
2275
|
+
35 => true,
|
2276
|
+
36 => false,
|
2277
|
+
37 => true,
|
2278
|
+
38 => true,
|
2279
|
+
39 => false,
|
2280
|
+
40 => true,
|
2281
|
+
41 => true,
|
2282
|
+
42 => false,
|
2283
|
+
43 => true,
|
2284
|
+
44 => true,
|
2285
|
+
45 => false,
|
2286
|
+
46 => true,
|
2287
|
+
47 => true,
|
2288
|
+
48 => false,
|
2289
|
+
}.each do |row_index, f_ex_des|
|
2290
|
+
expect(@browser.get_row(sheet_index, row_index)[:fExDes]).to eq f_ex_des
|
2291
|
+
end
|
2292
|
+
end
|
2293
|
+
|
2294
|
+
it 'Decodes fPhonetic field' do
|
2295
|
+
expect(@browser.get_row(sheet_index, 50)[:fPhonetic]).to eq false
|
2296
|
+
expect(@browser.get_row(sheet_index, 51)[:fPhonetic]).to eq true
|
2297
|
+
end
|
2298
|
+
end
|
2299
|
+
|
2300
|
+
it 'Decodes Dimensions record' do
|
2301
|
+
record = @browser.wbs[3][:Dimensions]
|
2302
|
+
expect(record[:rwMic]).to eq 1
|
2303
|
+
expect(record[:rwMac]).to eq 8
|
2304
|
+
expect(record[:colMic]).to eq 1
|
2305
|
+
expect(record[:colMac]).to eq 6
|
2306
|
+
end
|
2307
|
+
|
2308
|
+
it 'Decodes MergeCells record' do
|
2309
|
+
record = @browser.wbs[4][:MergeCells].first
|
2310
|
+
expect(record[:cmcs]).to eq 3
|
2311
|
+
expect(record[:rgref][0]).to eq({ rwFirst: 1, rwLast: 4, colFirst: 1, colLast: 1 })
|
2312
|
+
expect(record[:rgref][1]).to eq({ rwFirst: 1, rwLast: 1, colFirst: 3, colLast: 6 })
|
2313
|
+
expect(record[:rgref][2]).to eq({ rwFirst: 3, rwLast: 6, colFirst: 3, colLast: 6 })
|
2314
|
+
end
|
2315
|
+
end
|
2316
|
+
|
2317
|
+
context 'Note, Obj, TxO records' do
|
2318
|
+
before(:context) do
|
2319
|
+
@browser = Unxls::Biff8::Browser.new(testfile('biff8', 'note-obj-txo.xls'))
|
2320
|
+
end
|
2321
|
+
|
2322
|
+
context 'Note record' do
|
2323
|
+
specify 'For cell with normal comment' do
|
2324
|
+
record = @browser.get_note(1, 1, 0)
|
2325
|
+
expect(record[:rw]).to eq 1
|
2326
|
+
expect(record[:col]).to eq 0
|
2327
|
+
expect(record[:fShow]).to eq false
|
2328
|
+
expect(record[:fRwHidden]).to eq false
|
2329
|
+
expect(record[:fColHidden]).to eq false
|
2330
|
+
expect(record[:idObj]).to be_an_kind_of Integer
|
2331
|
+
expect(record[:stAuthor]).to eq 'Microsoft Office User'
|
2332
|
+
end
|
2333
|
+
|
2334
|
+
specify 'For cell with comment always shown' do
|
2335
|
+
record = @browser.get_note(1, 2, 0)
|
2336
|
+
expect(record[:rw]).to eq 2
|
2337
|
+
expect(record[:col]).to eq 0
|
2338
|
+
expect(record[:fShow]).to eq true
|
2339
|
+
expect(record[:fRwHidden]).to eq false
|
2340
|
+
expect(record[:fColHidden]).to eq false
|
2341
|
+
expect(record[:idObj]).to be_an_kind_of Integer
|
2342
|
+
expect(record[:stAuthor]).to eq 'Microsoft Office User'
|
2343
|
+
end
|
2344
|
+
|
2345
|
+
specify 'For cell in hidden row' do
|
2346
|
+
record = @browser.get_note(1, 3, 0)
|
2347
|
+
expect(record[:rw]).to eq 3
|
2348
|
+
expect(record[:col]).to eq 0
|
2349
|
+
expect(record[:fShow]).to eq false
|
2350
|
+
expect(record[:fRwHidden]).to eq true
|
2351
|
+
expect(record[:fColHidden]).to eq false
|
2352
|
+
expect(record[:idObj]).to be_an_kind_of Integer
|
2353
|
+
expect(record[:stAuthor]).to eq 'Microsoft Office User'
|
2354
|
+
end
|
2355
|
+
|
2356
|
+
specify 'For cell in hidden column' do
|
2357
|
+
record = @browser.get_note(1, 1, 1)
|
2358
|
+
expect(record[:rw]).to eq 1
|
2359
|
+
expect(record[:col]).to eq 1
|
2360
|
+
expect(record[:fShow]).to eq false
|
2361
|
+
expect(record[:fRwHidden]).to eq false
|
2362
|
+
expect(record[:fColHidden]).to eq true
|
2363
|
+
expect(record[:idObj]).to be_an_kind_of Integer
|
2364
|
+
expect(record[:stAuthor]).to eq 'Microsoft Office User'
|
2365
|
+
end
|
2366
|
+
end
|
2367
|
+
|
2368
|
+
context 'Obj record' do
|
2369
|
+
[
|
2370
|
+
[1, 0],
|
2371
|
+
[2, 0],
|
2372
|
+
[3, 0],
|
2373
|
+
[1, 1],
|
2374
|
+
].each do |rc|
|
2375
|
+
it 'Decodes cmo field' do
|
2376
|
+
row, column = rc
|
2377
|
+
note_obj_id = @browser.get_note(1, row, column)[:idObj]
|
2378
|
+
record = @browser.get_obj(1, note_obj_id)
|
2379
|
+
|
2380
|
+
cmo = record[:cmo]
|
2381
|
+
expect(cmo[:ft]).to eq 0x15
|
2382
|
+
expect(cmo[:cb]).to eq 0x12
|
2383
|
+
expect(cmo[:ot]).to eq 0x19
|
2384
|
+
expect(cmo[:ot_d]).to eq :Note
|
2385
|
+
expect(cmo[:fLocked]).to eq true
|
2386
|
+
expect(cmo[:fPrint]).to eq true
|
2387
|
+
%i(fDefaultSize fPublished fDisabled fUIObj fRecalcObj fRecalcObjAlways).each do |field_name|
|
2388
|
+
expect(cmo[field_name]).to eq false
|
2389
|
+
end
|
2390
|
+
|
2391
|
+
expect(record[:_other_fields]).to eq :not_implemented
|
2392
|
+
end
|
2393
|
+
end
|
2394
|
+
end
|
2395
|
+
|
2396
|
+
context 'TxO record' do
|
2397
|
+
def get_note_txo(stream, row, col)
|
2398
|
+
note_obj_id = @browser.get_note(stream, row, col)[:idObj]
|
2399
|
+
note_obj_index = @browser.get_obj(stream, note_obj_id)[:_record][:index]
|
2400
|
+
@browser.wbs[stream][:TxO][note_obj_index]
|
2401
|
+
end
|
2402
|
+
|
2403
|
+
specify 'For note with default formatting' do
|
2404
|
+
record = get_note_txo(2, 1, 0)
|
2405
|
+
expect(record[:hAlignment]).to eq 1
|
2406
|
+
expect(record[:hAlignment_d]).to eq :left
|
2407
|
+
expect(record[:vAlignment]).to eq 1
|
2408
|
+
expect(record[:vAlignment_d]).to eq :top
|
2409
|
+
expect(record[:fLockText]).to eq true
|
2410
|
+
expect(record[:fJustLast]).to eq false
|
2411
|
+
expect(record[:fSecretEdit]).to eq false
|
2412
|
+
expect(record[:rot]).to eq 0
|
2413
|
+
expect(record[:rot_d]).to eq :none
|
2414
|
+
expect(record[:ifntEmpty]).to eq 0
|
2415
|
+
expect(record[:fmla]).to eq :not_implemented
|
2416
|
+
expect(record[:text_string]).to eq 'default style, halign left, valign top, rot none'
|
2417
|
+
expect(record[:text_string].size).to eq record[:cchText]
|
2418
|
+
expect(record[:formatting_runs].size).to eq 1
|
2419
|
+
expect(record[:formatting_runs].first[:ich]).to be_an_kind_of Integer
|
2420
|
+
expect(record[:formatting_runs].first[:ifnt]).to be_an_kind_of Integer
|
2421
|
+
end
|
2422
|
+
|
2423
|
+
specify 'For note with center-middle aligned, unlocked text' do
|
2424
|
+
record = get_note_txo(2, 3, 0)
|
2425
|
+
expect(record[:hAlignment]).to eq 2
|
2426
|
+
expect(record[:hAlignment_d]).to eq :centered
|
2427
|
+
expect(record[:vAlignment]).to eq 2
|
2428
|
+
expect(record[:vAlignment_d]).to eq :middle
|
2429
|
+
expect(record[:fLockText]).to eq false
|
2430
|
+
expect(record[:text_string]).to eq 'halign center, valign middle, lock text false'
|
2431
|
+
end
|
2432
|
+
|
2433
|
+
specify 'For note with right-bottom aligned text' do
|
2434
|
+
record = get_note_txo(2, 5, 0)
|
2435
|
+
expect(record[:hAlignment]).to eq 3
|
2436
|
+
expect(record[:hAlignment_d]).to eq :right
|
2437
|
+
expect(record[:vAlignment]).to eq 3
|
2438
|
+
expect(record[:vAlignment_d]).to eq :bottom
|
2439
|
+
expect(record[:text_string]).to eq 'halign right, valign bottom'
|
2440
|
+
end
|
2441
|
+
|
2442
|
+
specify 'For note with horizontally and vertically justified text' do
|
2443
|
+
record = get_note_txo(2, 7, 0)
|
2444
|
+
expect(record[:hAlignment]).to eq 4
|
2445
|
+
expect(record[:hAlignment_d]).to eq :justify
|
2446
|
+
expect(record[:vAlignment]).to eq 4
|
2447
|
+
expect(record[:vAlignment_d]).to eq :justify
|
2448
|
+
expect(record[:text_string]).to eq 'halign justify, valign justify'
|
2449
|
+
end
|
2450
|
+
|
2451
|
+
specify 'For note with horizontally and vertically distributed text' do
|
2452
|
+
record = get_note_txo(2, 9, 0)
|
2453
|
+
expect(record[:hAlignment]).to eq 7
|
2454
|
+
expect(record[:hAlignment_d]).to eq :justify_distributed
|
2455
|
+
expect(record[:vAlignment]).to eq 7
|
2456
|
+
expect(record[:vAlignment_d]).to eq :justify_distributed
|
2457
|
+
expect(record[:text_string]).to eq 'halign distributed, valign distributed'
|
2458
|
+
end
|
2459
|
+
|
2460
|
+
specify 'For note with stacked orientation' do
|
2461
|
+
record = get_note_txo(2, 1, 5)
|
2462
|
+
expect(record[:rot]).to eq 1
|
2463
|
+
expect(record[:rot_d]).to eq :stacked
|
2464
|
+
expect(record[:text_string]).to eq 'rot stacked'
|
2465
|
+
end
|
2466
|
+
|
2467
|
+
specify 'For note with text rotated 90 ccw' do
|
2468
|
+
record = get_note_txo(2, 3, 5)
|
2469
|
+
expect(record[:rot]).to eq 2
|
2470
|
+
expect(record[:rot_d]).to eq :ccw_90
|
2471
|
+
expect(record[:text_string]).to eq 'rot 90 ccw'
|
2472
|
+
end
|
2473
|
+
|
2474
|
+
specify 'For note with text rotated 90 cw' do
|
2475
|
+
record = get_note_txo(2, 5, 5)
|
2476
|
+
expect(record[:rot]).to eq 3
|
2477
|
+
expect(record[:rot_d]).to eq :cw_90
|
2478
|
+
expect(record[:text_string]).to eq 'rot 90 cw'
|
2479
|
+
end
|
2480
|
+
|
2481
|
+
specify 'For note with no text' do
|
2482
|
+
record = get_note_txo(2, 7, 5)
|
2483
|
+
expect(@browser.get_font(record[:ifntEmpty])[:fontName]).to eq 'Tahoma'
|
2484
|
+
end
|
2485
|
+
|
2486
|
+
specify 'For note with text split across several Continue records' do
|
2487
|
+
record = get_note_txo(2, 11, 0)
|
2488
|
+
expect(record[:text_string].size).to eq 16440 # characters
|
2489
|
+
expect(record[:cchText]).to eq 16445 # bytes. 5 extra bytes for 5 emojis
|
2490
|
+
expect(record[:text_string][4105..4107]).to eq 'Ж>W'
|
2491
|
+
expect(record[:text_string][12327..12329]).to eq 'W>Ж'
|
2492
|
+
expect(record[:text_string][-5..-1]).to eq 'Ж END'
|
2493
|
+
end
|
2494
|
+
|
2495
|
+
specify 'For note with text with formatting runs' do
|
2496
|
+
record = get_note_txo(2, 13, 0)
|
2497
|
+
expect(record[:formatting_runs].size).to eq 5
|
2498
|
+
|
2499
|
+
run = record[:formatting_runs][0]
|
2500
|
+
expect(run[:ich]).to eq 0
|
2501
|
+
expect(@browser.get_font(run[:ifnt])[:fontName]).to eq 'Candara'
|
2502
|
+
|
2503
|
+
run = record[:formatting_runs][1]
|
2504
|
+
expect(run[:ich]).to eq 23
|
2505
|
+
expect(@browser.get_font(run[:ifnt])[:fontName]).to eq 'Baskerville Old Face'
|
2506
|
+
|
2507
|
+
run = record[:formatting_runs][2]
|
2508
|
+
expect(run[:ich]).to eq 33
|
2509
|
+
expect(@browser.get_font(run[:ifnt])[:fontName]).to eq 'Candara'
|
2510
|
+
|
2511
|
+
run = record[:formatting_runs][3]
|
2512
|
+
expect(run[:ich]).to eq 34
|
2513
|
+
expect(@browser.get_font(run[:ifnt])[:fontName]).to eq 'Corbel'
|
2514
|
+
|
2515
|
+
run = record[:formatting_runs][4]
|
2516
|
+
expect(run[:ich]).to eq 38
|
2517
|
+
expect(@browser.get_font(run[:ifnt])[:fontName]).to eq 'Candara'
|
2518
|
+
end
|
2519
|
+
end
|
2520
|
+
end
|
2521
|
+
|
2522
|
+
context 'HLink, HLinkTooltip records' do
|
2523
|
+
before(:context) do
|
2524
|
+
@browser = Unxls::Biff8::Browser.new(testfile('biff8', 'hyperlinks.xls'))
|
2525
|
+
end
|
2526
|
+
|
2527
|
+
context 'HLink record' do
|
2528
|
+
it 'Decodes basic fields' do
|
2529
|
+
record = @browser.get_hlink(1, 16, 1)
|
2530
|
+
|
2531
|
+
expect(record[:rwFirst]).to eq 16
|
2532
|
+
expect(record[:rwLast]).to eq 16
|
2533
|
+
expect(record[:colFirst]).to eq 1
|
2534
|
+
expect(record[:colLast]).to eq 1
|
2535
|
+
expect(record[:hlinkClsid]).to eq :'79eac9d0-baf9-11ce-8c82-00aa004ba90b'
|
2536
|
+
end
|
2537
|
+
|
2538
|
+
it 'Decodes hyperlink field for an internal link' do
|
2539
|
+
field = @browser.get_hlink(1, 16, 1)[:hyperlink]
|
2540
|
+
|
2541
|
+
expect(field[:streamVersion]).to eq 2
|
2542
|
+
|
2543
|
+
expect(field[:hlstmfHasMoniker]).to eq false
|
2544
|
+
expect(field[:hlstmfIsAbsolute]).to eq false
|
2545
|
+
expect(field[:hlstmfSiteGaveDisplayName]).to eq true
|
2546
|
+
expect(field[:hlstmfHasLocationStr]).to eq true
|
2547
|
+
expect(field[:hlstmfHasDisplayName]).to eq true
|
2548
|
+
expect(field[:hlstmfHasGUID]).to eq false
|
2549
|
+
expect(field[:hlstmfHasCreationTime]).to eq false
|
2550
|
+
expect(field[:hlstmfHasFrameName]).to eq false
|
2551
|
+
expect(field[:hlstmfMonikerSavedAsStr]).to eq false
|
2552
|
+
expect(field[:hlstmfAbsFromGetdataRel]).to eq false
|
2553
|
+
|
2554
|
+
expect(field[:displayName]).to eq "#Sheet2!localname"
|
2555
|
+
expect(field[:location]).to eq "Sheet2!localname"
|
2556
|
+
end
|
2557
|
+
|
2558
|
+
it 'Decodes hyperlink field for local file link' do
|
2559
|
+
field = @browser.get_hlink(1, 27, 1)[:hyperlink]
|
2560
|
+
|
2561
|
+
expect(field[:hlstmfHasMoniker]).to eq true
|
2562
|
+
expect(field[:hlstmfIsAbsolute]).to eq false
|
2563
|
+
expect(field[:hlstmfSiteGaveDisplayName]).to eq true
|
2564
|
+
expect(field[:hlstmfHasLocationStr]).to eq true
|
2565
|
+
expect(field[:hlstmfHasDisplayName]).to eq true
|
2566
|
+
expect(field[:hlstmfHasGUID]).to eq false
|
2567
|
+
expect(field[:hlstmfHasCreationTime]).to eq false
|
2568
|
+
expect(field[:hlstmfHasFrameName]).to eq false
|
2569
|
+
expect(field[:hlstmfMonikerSavedAsStr]).to eq false
|
2570
|
+
expect(field[:hlstmfAbsFromGetdataRel]).to eq false
|
2571
|
+
expect(field[:displayName]).to eq 'long_unicode_filename_€.xls#A1'
|
2572
|
+
expect(field[:location]).to eq 'A1'
|
2573
|
+
|
2574
|
+
ole_moniker = field[:oleMoniker]
|
2575
|
+
expect(ole_moniker[:monikerClsid]).to eq :'00000303-0000-0000-c000-000000000046'
|
2576
|
+
expect(ole_moniker[:monikerClsid_d]).to eq :FileMoniker
|
2577
|
+
|
2578
|
+
moniker_data = ole_moniker[:data]
|
2579
|
+
expect(moniker_data[:cAnti]).to eq 0
|
2580
|
+
expect(moniker_data[:ansiLength]).to eq 28
|
2581
|
+
expect(moniker_data[:ansiPath]).to eq 'long_unicode_filename_?.xls'
|
2582
|
+
expect(moniker_data[:versionNumber]).to eq 57005
|
2583
|
+
expect(moniker_data[:cbUnicodePathSize]).to eq 60
|
2584
|
+
expect(moniker_data[:cbUnicodePathBytes]).to eq 54
|
2585
|
+
expect(moniker_data[:unicodePath]).to eq 'long_unicode_filename_€.xls'
|
2586
|
+
end
|
2587
|
+
|
2588
|
+
it 'Decodes hyperlink field for a UNC path link' do
|
2589
|
+
field = @browser.get_hlink(1, 37, 1)[:hyperlink]
|
2590
|
+
|
2591
|
+
expect(field[:hlstmfHasMoniker]).to eq true
|
2592
|
+
expect(field[:hlstmfIsAbsolute]).to eq true
|
2593
|
+
expect(field[:hlstmfSiteGaveDisplayName]).to eq true
|
2594
|
+
expect(field[:hlstmfHasLocationStr]).to eq false
|
2595
|
+
expect(field[:hlstmfHasDisplayName]).to eq true
|
2596
|
+
expect(field[:hlstmfHasGUID]).to eq false
|
2597
|
+
expect(field[:hlstmfHasCreationTime]).to eq false
|
2598
|
+
expect(field[:hlstmfHasFrameName]).to eq false
|
2599
|
+
expect(field[:hlstmfMonikerSavedAsStr]).to eq true
|
2600
|
+
expect(field[:hlstmfAbsFromGetdataRel]).to eq false
|
2601
|
+
expect(field[:displayName]).to eq "\\\\127.0.0.1\\PATH\\EXTP8_4.XLS"
|
2602
|
+
expect(field[:moniker]).to eq "\\\\127.0.0.1\\PATH\\EXTP8_4.XLS"
|
2603
|
+
end
|
2604
|
+
|
2605
|
+
it 'Decodes hyperlink field for a HTTP link'
|
2606
|
+
it 'Decodes hyperlink field for a mail link'
|
2607
|
+
end
|
2608
|
+
|
2609
|
+
context 'HLinkTooltip record'
|
2610
|
+
end
|
2611
|
+
end
|
2612
|
+
|
2613
|
+
context 'Optimizations' do
|
2614
|
+
before(:context) do
|
2615
|
+
@browser = Unxls::Biff8::Browser.new(testfile('biff8', 'row-colinfo-dimensions-mergecells.xls'))
|
2616
|
+
end
|
2617
|
+
|
2618
|
+
it 'Adds cell value dimensions' do
|
2619
|
+
expect(@browser.cell_index[:dimensions][3]).to eq({ rmin: 1, rmax: 5, cmin: 1, cmax: 4 })
|
2620
|
+
end
|
2621
|
+
|
2622
|
+
it 'Adds cell index for quick lookup' do
|
2623
|
+
expect(@browser.cell_index[:'3_5_4']).to eq :Formula_0
|
2624
|
+
end
|
2625
|
+
|
2626
|
+
context 'Note, HLink, HLinkTooltip' do
|
2627
|
+
before(:context) do
|
2628
|
+
@browser = Unxls::Biff8::Browser.new(testfile('biff8', 'note-obj-txo.xls'))
|
2629
|
+
end
|
2630
|
+
|
2631
|
+
it 'Adds Note index for quick lookup' do
|
2632
|
+
expect(@browser.cell_index[:notes][:'1_1_0']).to eq 0
|
2633
|
+
end
|
2634
|
+
end
|
2635
|
+
|
2636
|
+
context 'Note, HLink, HLinkTooltip' do
|
2637
|
+
before(:context) do
|
2638
|
+
@browser = Unxls::Biff8::Browser.new(testfile('biff8', 'hyperlinks.xls'))
|
2639
|
+
end
|
2640
|
+
|
2641
|
+
it 'Adds HLink index for quick lookup' do
|
2642
|
+
expect(@browser.cell_index[:hlinks][:'1_6_1']).to eq 0
|
2643
|
+
end
|
2644
|
+
|
2645
|
+
it 'Adds HLinkTooltip index for quick lookup' do
|
2646
|
+
expect(@browser.cell_index[:hlinktooltips][:'1_57_1']).to eq 0
|
2647
|
+
end
|
2648
|
+
end
|
2649
|
+
end
|
2650
|
+
|
2651
|
+
end
|