parseexcel 0.5.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.
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+ # ParseExcel -- Spreadsheet -- 03.06.2003 -- hwyss@ywesee.com
3
+
4
+ require 'parseexcel/olestorage'
5
+ require 'parseexcel/parser'
6
+
7
+ module Spreadsheet
8
+ module ParseExcel
9
+ def parse(source, params={})
10
+ Parser.new(params).parse(source)
11
+ end
12
+ module_function :parse
13
+ end
14
+ end
@@ -0,0 +1,1032 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # Spreadsheet::ParseExcel -- Extract Data from an Excel File
4
+ # Copyright (C) 2003 ywesee -- intellectual capital connected
5
+ #
6
+ # This library is free software; you can redistribute it and/or
7
+ # modify it under the terms of the GNU Lesser General Public
8
+ # License as published by the Free Software Foundation; either
9
+ # version 2.1 of the License, or (at your option) any later version.
10
+ #
11
+ # This library is distributed in the hope that it will be useful,
12
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14
+ # Lesser General Public License for more details.
15
+ #
16
+ # You should have received a copy of the GNU Lesser General Public
17
+ # License along with this library; if not, write to the Free Software
18
+ # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19
+ #
20
+ # ywesee - intellectual capital connected, Winterthurerstrasse 52, CH-8006 Z�rich, Switzerland
21
+ # hwyss@ywesee.com
22
+ #
23
+ # Parser -- Spreadsheet::ParseExcel -- 10.06.2003 -- hwyss@ywesee.com
24
+
25
+ require 'parseexcel/olestorage'
26
+ require 'parseexcel/workbook'
27
+ require 'parseexcel/worksheet'
28
+ require 'parseexcel/format'
29
+
30
+ module Spreadsheet
31
+ module ParseExcel
32
+ class Parser
33
+ attr_reader :bigendian
34
+ CONTINUE = 0x03C
35
+ NOTE = 0x1B6
36
+ SST = 0x0FC
37
+ EVENT_TABLE = {
38
+ #Develpers' Kit P291
39
+ 0x14 => :header, # Header
40
+ 0x15 => :footer, # Footer
41
+ =begin
42
+ 0x18 => :name, # NAME(?)
43
+ 0x1A => :v_page_break, # Veritical Page Break
44
+ 0x1B => :h_page_break, # Horizontal Page Break
45
+ =end
46
+ 0x1C => :note, # Note
47
+ 0x22 => :flg_1904, # 1904 Flag
48
+ =begin
49
+ 0x26 => :margin, # Left Mergin
50
+ 0x27 => :margin, # Right Mergin
51
+ 0x28 => :margin, # Top Mergin
52
+ 0x29 => :margin, # Bottom Mergin
53
+ 0x2A => :print_headers, # Print Headers
54
+ 0x2B => :print_gridlines, # Print Gridlines
55
+ =end
56
+ CONTINUE=> :continue, # Continue
57
+ 0x42 => :codepage, # BIFF_CODEPAGE
58
+ 0x43 => :xf, # ExTended Format(?)
59
+ =begin
60
+ #Develpers' Kit P292
61
+ 0x55 => :def_col_width, # Consider
62
+ 0x5C => :write_access, # WRITEACCESS
63
+ 0x7D => :col_info, # Colinfo
64
+ =end
65
+ 0x7E => :rk, # RK
66
+ 0x81 => :ws_bool, # WSBOOL
67
+ =begin
68
+ 0x83 => :h_center, # HCENTER
69
+ 0x84 => :v_center, # VCENTER
70
+ 0x85 => :bound_sheet, # BoundSheet
71
+
72
+ 0x92 => :palette, # Palette, fgp
73
+
74
+ 0x99 => :standard_width, # Standard Col
75
+ #Develpers' Kit P293
76
+ =end
77
+ 0xA1 => :setup, # SETUP
78
+ 0xBD => :mul_rk, # MULRK
79
+ 0xBE => :mul_blank, # MULBLANK
80
+ 0xD6 => :rstring, # RString
81
+ #Develpers' Kit P294
82
+ 0xe0 => :xf, # ExTended Format
83
+ =begin
84
+ 0xE5 => :merge_area, # MergeArea (Not Documented)
85
+ =end
86
+ SST => :sst, # Shared String Table
87
+ 0xFD => :label_sst, # Label SST
88
+ NOTE => :biff8_annotation,
89
+ #Develpers' Kit P295
90
+ 0x201 => :blank, # Blank
91
+
92
+ 0x202 => :integer, # Integer(Not Documented)
93
+ 0x203 => :number, # Number
94
+ 0x204 => :label , # Label
95
+ 0x205 => :bool_err, # BoolErr
96
+ 0x207 => :string, # STRING
97
+ 0x208 => :row, # RowData
98
+ 0x221 => :array, # Array (Consider)
99
+ 0x225 => :default_row_height,# Consider
100
+ =begin
101
+ 0x31 => :font, # Font
102
+ 0x231 => :font, # Font
103
+
104
+ =end
105
+ 0x27E => :rk, # RK
106
+ 0x41E => :format, # Format
107
+ 0x06 => :formula, # Formula
108
+ 0x406 => :formula, # Formula
109
+ 0x09 => :bof, # BOF(BIFF2)
110
+ 0x209 => :bof, # BOF(BIFF3)
111
+ 0x409 => :bof, # BOF(BIFF4)
112
+ 0x809 => :bof, # BOF(BIFF5-8)
113
+ }
114
+ UNIMPLEMENTED = {
115
+ #Develpers' Kit P291
116
+ 0x18 => :name, # NAME(?)
117
+ 0x1A => :v_page_break, # Veritical Page Break
118
+ 0x1B => :h_page_break, # Horizontal Page Break
119
+ 0x26 => :margin, # Left Mergin
120
+ 0x27 => :margin, # Right Mergin
121
+ 0x28 => :margin, # Top Mergin
122
+ 0x29 => :margin, # Bottom Mergin
123
+ 0x2A => :print_headers, # Print Headers
124
+ 0x2B => :print_gridlines, # Print Gridlines
125
+ #Develpers' Kit P292
126
+ 0x55 => :def_col_width, # Consider
127
+ 0x5C => :write_access, # WRITEACCESS
128
+ 0x7D => :col_info, # Colinfo
129
+ 0x81 => :ws_bool, # WSBOOL
130
+ 0x83 => :h_center, # HCENTER
131
+ 0x84 => :v_center, # VCENTER
132
+ 0x85 => :bound_sheet, # BoundSheet
133
+
134
+ 0x92 => :palette, # Palette, fgp
135
+
136
+ 0x99 => :standard_width, # Standard Col
137
+ #Develpers' Kit P293
138
+ 0xc1 => :add_menu, # BIFF_ADDMENU
139
+ 0xe1 => :interface_hdr, # BIFF_INTERFACEHDR
140
+ 0xe2 => :interface_end, # BIFF_INTERFACEEND
141
+ 0xE5 => :merge_area, # MergeArea (Not Documented)
142
+ 0x31 => :font, # Font
143
+ 0x161 => :dsf, # BIFF_DSF
144
+ 0x231 => :font, # Font
145
+ 0x293 => :style, # BIFF_STYLE
146
+ }
147
+ def initialize(params={})
148
+ #0. Check ENDIAN(Little: Intel etc. BIG: Sparc etc)
149
+ @bigendian = params.fetch(:bigendian) {
150
+ [2].pack('L').unpack('H8').first != '02000000'
151
+ }
152
+ @buff = ''
153
+ #1.2 Set Event Handler
154
+ set_event_handlers(params[:event_handlers] || EVENT_TABLE)
155
+ if(params[:add_handlers].is_a? Hash)
156
+ params[:add_handlers].each { |key, value|
157
+ set_event_handler(key, value)
158
+ }
159
+ end
160
+ =begin
161
+ #Experimental
162
+ $_CellHandler = $hParam{CellHandler} if($hParam{CellHandler});
163
+ $_NotSetCell = $hParam{NotSetCell};
164
+ $_Object = $hParam{Object};
165
+ =end
166
+ end
167
+ def parse(source, format=nil)
168
+ begin
169
+ #0. New $oBook
170
+ @workbook = Workbook.new
171
+ #1.Get content
172
+ @ole = OLE::Storage.new(source)
173
+ biff = @ole.search_pps(
174
+ [
175
+ OLE.asc2ucs('Book'),
176
+ OLE.asc2ucs('Workbook'),
177
+ ], true).first.data
178
+
179
+ #2. Ready for format
180
+ @workbook.format = (format || Format.new)
181
+
182
+ #3. Parse content
183
+ pos = 0
184
+ work = biff[pos, 4]
185
+ pos += 4
186
+ ef_flag = false
187
+ blen = biff.length
188
+ while(pos <= blen)
189
+ op, len = work.unpack('v2')
190
+ #puts "*"*33
191
+ #puts sprintf("0x%03x %i ->%s<-", op, len, work.inspect[0,200])
192
+ #p "#{biff.length} / #{pos}"
193
+ #p work, op, len
194
+ if(len)
195
+ work = biff[pos, len]
196
+ pos += len
197
+ end
198
+ #Check EF, EOF
199
+ if(op == 0xEF) #EF
200
+ ef_flag = op
201
+ elsif(op == 0x0A) #EOF
202
+ ef_flag = nil
203
+ end
204
+ #puts "ef_flag: =>#{ef_flag}<="
205
+ unless(ef_flag)
206
+ #1. Formula String, but not string (0x207)
207
+ if(!@prev_pos.nil? && @proc_table.include?(op) && op != 0x207)
208
+ row, col, fmt = @prev_pos
209
+ @prev_pos = nil
210
+ params = {
211
+ :kind => :formula_string,
212
+ :value => '',
213
+ :format_no => fmt,
214
+ :numeric => false,
215
+ }
216
+ cell_factory(row, col, params)
217
+ end
218
+ if(prc = @proc_table[op])
219
+ #puts sprintf("%s 0x%03x %i ->%s<-", prc, op, len, work.inspect[0,30])
220
+ prc.call(op,len,work)
221
+ elsif(prc = UNIMPLEMENTED[op])
222
+ #puts sprintf("opcode not implemented: 0x%03x/%s", op.to_i, prc.to_s)
223
+ else
224
+ #puts sprintf("unknown opcode: 0x%03x (%s)", op.to_i, work.inspect[0,30])
225
+ end
226
+ (@prev_prc = op) unless(op == CONTINUE)
227
+ end
228
+ work = biff[pos, 4] if((pos+4) <= blen)
229
+ pos += 4
230
+ if(@parse_abort)
231
+ return @workbook
232
+ end
233
+ end
234
+ @workbook
235
+ ensure
236
+ @ole.close if @ole
237
+ end
238
+ end
239
+ def set_event_handler(key, handler)
240
+ if(handler.is_a? Symbol)
241
+ handler = self.method(handler)
242
+ end
243
+ @proc_table.store(key, handler)
244
+ end
245
+ def set_event_handlers(hash)
246
+ @proc_table = {}
247
+ hash.each { |key, value|
248
+ set_event_handler(key, value)
249
+ }
250
+ end
251
+ private
252
+ VERSION_EXCEL95 = 0x500;
253
+ VERSION_EXCEL97 = 0x600;
254
+ VERSION_BIFF2 = 0x00;
255
+ VERSION_BIFF3 = 0x02;
256
+ VERSION_BIFF4 = 0x04;
257
+ VERSION_BIFF5 = 0x08;
258
+ VERSION_BIFF8 = 0x18; #Added (Not in BOOK)
259
+ CODEPAGES = {
260
+ 367 => "ASCII",
261
+ 437 => "IBM437", #(US)
262
+ 720 => "IBM720", #(OEM Arabic)
263
+ 737 => "IBM737", #(Greek)
264
+ 775 => "IBM775", #(Baltic)
265
+ 850 => "IBM850", #(Latin I)
266
+ 852 => "IBM852", #(Latin II (Central European))
267
+ 855 => "IBM855", #(Cyrillic)
268
+ 857 => "IBM857", #(Turkish)
269
+ 858 => "IBM858", #(Multilingual Latin I with Euro)
270
+ 860 => "IBM860", #(Portuguese)
271
+ 861 => "IBM861", #(Icelandic)
272
+ 862 => "IBM862", #(Hebrew)
273
+ 863 => "IBM863", #(Canadian (French))
274
+ 864 => "IBM864", #(Arabic)
275
+ 865 => "IBM865", #(Nordic)
276
+ 866 => "IBM866", #(Cyrillic (Russian))
277
+ 869 => "IBM869", #(Greek (Modern))
278
+ 874 => "WINDOWS-874", #(Thai)
279
+ 932 => "WINDOWS-932", #(Japanese Shift-JIS)
280
+ 936 => "WINDOWS-936", #(Chinese Simplified GBK)
281
+ 949 => "WINDOWS-949", #(Korean (Wansung))
282
+ 950 => "WINDOWS-950", #(Chinese Traditional BIG5)
283
+ 1200 => "UTF-16LE", #(BIFF8)
284
+ 1250 => "WINDOWS-1250", #(Latin II) (Central European)
285
+ 1251 => "WINDOWS-1251", #(Cyrillic)
286
+ 1252 => "WINDOWS-1252", #(Latin I) (BIFF4-BIFF7)
287
+ 1253 => "WINDOWS-1253", #(Greek)
288
+ 1254 => "WINDOWS-1254", #(Turkish)
289
+ 1255 => "WINDOWS-1255", #(Hebrew)
290
+ 1256 => "WINDOWS-1256", #(Arabic)
291
+ 1257 => "WINDOWS-1257", #(Baltic)
292
+ 1258 => "WINDOWS-1258", #(Vietnamese)
293
+ 1361 => "WINDOWS-1361", #(Korean (Johab))
294
+ 10000 => "MACINTOSH",
295
+ 32768 => "MACINTOSH",
296
+ 32769 => "WINDOWS-1252", #(Latin I) (BIFF2-BIFF3)
297
+ }
298
+ def ann_wk(work)
299
+ if(@annotation.length < @annotation_length)
300
+ @annotation << work[1,@annotation_length]
301
+ else
302
+ pkg_str = Worksheet::PkgString.new(@annotation, false, false, false)
303
+ @workbook.add_annotation(pkg_str)
304
+ end
305
+ end
306
+ def array(op, len, work) # DK:P297
307
+ warn "array is not implemented"
308
+ end
309
+ def biff8_annotation(op, len, work)
310
+ @annotation = ''
311
+ @annotation_length = work[10,2].unpack('v').first
312
+ end
313
+ def blank(op, len, work) # DK:P303
314
+ row, col, fmt = work.unpack('v3')
315
+ params = {
316
+ :kind => :blank,
317
+ :value => '',
318
+ :format_no => fmt,
319
+ :numeric => false,
320
+ }
321
+ cell_factory(row, col, params)
322
+ end
323
+ def bof(op, len, work) # Developers' Kit : P303
324
+ version, dtype = work.unpack('v2')
325
+
326
+ #Workbook Global
327
+ if(dtype == 0x5)
328
+ #puts "dtype: #{dtype}(0x5)"
329
+ @workbook.version = version
330
+ @workbook.biffversion = if(version == VERSION_EXCEL95)
331
+ VERSION_BIFF5
332
+ else
333
+ VERSION_BIFF8
334
+ end
335
+ @current_sheet = nil
336
+ @curr_sheet_idx = nil
337
+ @prev_sheet_idx = -1
338
+
339
+ #Worksheet or Dialogsheet
340
+ elsif(dtype != 0x20)
341
+ #puts "dtype: #{dtype}(!0x20)"
342
+ unless(@prev_sheet_idx.nil?)
343
+ #puts "we have a prev_sheet_index - make a new sheet"
344
+ @curr_sheet_idx = @prev_sheet_idx += 1
345
+ @current_sheet = @workbook.worksheet(@curr_sheet_idx)
346
+ if(work.length > 4)
347
+ @current_sheet.sheet_version,
348
+ @current_sheet.sheet_type, = work.unpack('v2')
349
+ end
350
+ else
351
+ #puts "no current sheet_index so far..."
352
+ @workbook.biffversion = (op/0x100).to_i
353
+ if([VERSION_BIFF2,
354
+ VERSION_BIFF3,
355
+ VERSION_BIFF4,
356
+ ].include?(@workbook.biffversion))
357
+ #puts "found biffversion #{sprintf('%04x', @workbook.biffversion)}"
358
+ @workbook.version = @workbook.biffversion
359
+ @workbook.worksheet(@workbook.sheet_count)
360
+ @curr_sheet_idx = 0
361
+ @current_sheet = @workbook.worksheet(@curr_sheet_idx)
362
+ end
363
+ end
364
+ else
365
+ @prev_sheet_idx = @curr_sheet_idx || -1
366
+ @curr_sheet_idx = nil
367
+ @current_sheet = nil
368
+ end
369
+ end
370
+ def bool_err(op, len, work) # DK:P306
371
+ row, col, fmt = work.unpack('v3')
372
+ val, flg = work[6,2].unpack('cc')
373
+ txt = decode_bool_err(val, flg.nonzero?)
374
+ param = {
375
+ :kind => :bool_error,
376
+ :value => txt,
377
+ :format_no => fmt,
378
+ :numeric => false,
379
+ }
380
+ cell_factory(row, col, param)
381
+ end
382
+ def cell_factory(row, col, params)
383
+ return if @current_sheet.nil?
384
+ fmt = params[:format_no]
385
+ format = params[:format] = @workbook.format(fmt)
386
+ params[:book] = @workbook
387
+ if(@encoding)
388
+ params[:encoding] = @encoding
389
+ end
390
+ cell = Worksheet::Cell.new(params)
391
+ #p format
392
+ #cell.type = @workbook.format.cell_type(cell) unless format.nil?
393
+ @current_sheet.add_cell(row, col, cell)
394
+ end
395
+ def codepage(op, len, work)
396
+ codepage, = work.unpack('v')
397
+ @encoding = CODEPAGES[codepage]
398
+ end
399
+ def continue(op, len, work) #DK:P311
400
+ case @prev_prc
401
+ when SST # previous prc was Shared String Table (:sst)
402
+ str_wk(work, true)
403
+ when NOTE
404
+ ann_wk(work)
405
+ end
406
+ end
407
+ def conv_biff8(work, conv_flag=false)
408
+ chars, flg = work.unpack('vc')
409
+ ## in the usual (compressed) case, length of the string = number of chars
410
+ len = chars
411
+ uncompressed = ibool(flg & 0x01)
412
+ ext = ibool(flg & 0x04)
413
+ rich = ibool(flg & 0x08)
414
+ ecnt, rcnt, pos, str = 0, 0
415
+ #2. Rich and Ext
416
+ if(rich && ext)
417
+ pos = 9
418
+ rcnt, ecnt = work[3,6].unpack('vV')
419
+ elsif(rich) #Only Rich
420
+ pos = 5
421
+ rcnt, = work[3,2].unpack('v')
422
+ elsif(ext) #Only Ext
423
+ pos = 7
424
+ ecnt, = work[3,4].unpack('V')
425
+ else #Nothing Special
426
+ pos = 3
427
+ end
428
+ #3.Get String
429
+ if(uncompressed)
430
+ len = chars * 2
431
+ str = work[pos,len]
432
+ else
433
+ str = ''
434
+ work[pos,chars].each_byte { |byte|
435
+ str << byte.chr << "\0"
436
+ }
437
+ end
438
+ #puts [uncompressed, pos, len, rcnt, ecnt].inspect
439
+ [str, uncompressed, pos, len, rcnt, ecnt]
440
+ end
441
+ def conv_biff8_data(work, conv_flag=false)
442
+ str, high, pos, len, rcnt, ecnt = conv_biff8(work, conv_flag)
443
+ #4. return
444
+ spos = pos + len + rcnt*4
445
+ epos = spos + ecnt
446
+ #4.1 Get Rich and Ext
447
+ #puts "work: #{work.length} < epos: #{epos} ?"
448
+ if(work.length < epos)
449
+ [
450
+ [nil, high, nil, nil],
451
+ epos,
452
+ pos,
453
+ len,
454
+ ]
455
+ else
456
+ [
457
+ [str, high, work[spos..-1], work[spos, ecnt]],
458
+ epos,
459
+ pos,
460
+ len,
461
+ ]
462
+ end
463
+ end
464
+ def conv_biff8_string(work, conv_flag=false)
465
+ conv_biff8(work, conv_flag).first
466
+ end
467
+ def conv_dval(val)
468
+ val = val.unpack('c8').reverse.collect { |bit|
469
+ bit.to_i
470
+ }.pack('c8') if @bigendian
471
+ val.unpack('d').first
472
+ end
473
+ def decode_bool_err(val, flag=false) # DK:P306
474
+ if(flag) # ERROR
475
+ case val
476
+ when 0x00
477
+ '#NULL!'
478
+ when 0x07
479
+ '#DIV/0!'
480
+ when 0x0F
481
+ '#VALUE!'
482
+ when 0x17
483
+ '#REF!'
484
+ when 0x1D
485
+ '#NAME?'
486
+ when 0x24
487
+ '#NUM!'
488
+ when 0x2A
489
+ '#N/A!'
490
+ else
491
+ '#ERR'
492
+ end
493
+ else
494
+ (val.nonzero?) ? 'TRUE' : 'FALSE'
495
+ end
496
+ end
497
+ def default_row_height(op, len, work) # DK: P318
498
+ return if(@current_sheet.nil?)
499
+
500
+ #1. RowHeight
501
+ dum, hght = work.unpack('v2')
502
+ @current_sheet.default_row_height = hght/20.0
503
+ end
504
+ def flg_1904(op, len, work) # DK:P296
505
+ @workbook.flg_1904 = work.unpack('v').first.nonzero?
506
+ end
507
+ def footer(op, len, work) #DK:P335
508
+ return unless(@current_sheet)
509
+ @current_sheet.footer = simple_string(work)
510
+ end
511
+ def format(op, len, work) # DK:P336
512
+ fmt = if([
513
+ VERSION_BIFF2,
514
+ VERSION_BIFF3,
515
+ VERSION_BIFF4,
516
+ VERSION_BIFF5,
517
+ ].include?(@workbook.biffversion))
518
+ work[3, work[2,1].unpack('c').first]
519
+ else
520
+ conv_biff8_string(work[2..-1])
521
+ end
522
+ idx = work[0,2].unpack('v').first
523
+ @workbook.add_text_format(idx, fmt)
524
+ end
525
+ def formula(op, len, work) # DK:P336
526
+ row, col, fmt = work.unpack('v3')
527
+ flag = work[12,2].unpack('v')
528
+ if(flag == 0xffff)
529
+ kind = work[6,1].unpack('c')
530
+ val = work[8,1].unpack('c')
531
+ if(1..2.include?(kind))
532
+ txt = decode_bool_err(val, kind == 2)
533
+ params = {
534
+ :kind => :formula_bool,
535
+ :value => txt,
536
+ :format_no => fmt,
537
+ :numeric => false,
538
+ :code => nil,
539
+ }
540
+ cell_factory(row, col, params)
541
+ else
542
+ @prev_pos = [row, col, fmt]
543
+ end
544
+ else
545
+ dval = conv_dval(work[6,8])
546
+ params = {
547
+ :kind => :formula_number,
548
+ :value => dval,
549
+ :format_no => fmt,
550
+ :numeric => true,
551
+ :code => nil,
552
+ }
553
+ end
554
+ end
555
+ def header(op, len, work) # DK:P340
556
+ return unless(@current_sheet)
557
+ @current_sheet.header = simple_string(work)
558
+ end
559
+ def ibool(num)
560
+ (num != 0)
561
+ end
562
+ def integer(op, len, work) # Not in DK
563
+ row, col, fmt, dum, txt = work.unpack('v3cv')
564
+ params = {
565
+ :kind => :integer,
566
+ :value => txt,
567
+ :format_no => fmt,
568
+ :numeric => false,
569
+ }
570
+ cell_factory(row, col, params)
571
+ end
572
+ def label(op, len, work) # DK:P344
573
+ row, col, fmt = work.unpack('v3')
574
+ #BIFF8
575
+ label, code = nil
576
+ if(@workbook.biffversion && @workbook.biffversion >= VERSION_BIFF8)
577
+ buff, tln, pos, lens = conv_biff8_data(work[6..-1], true)
578
+ label = buff.at(0)
579
+ code = buff.at(1) ? :ucs2 : nil
580
+ #Before BIFF8
581
+ else
582
+ label = work[8..-1]
583
+ code = :_native_
584
+ end
585
+ params = {
586
+ :kind => :label,
587
+ :value => label,
588
+ :format_no => fmt,
589
+ :numeric => false,
590
+ :code => code,
591
+ }
592
+ cell_factory(row, col, params)
593
+ end
594
+ def label_sst(op, len, work) # DK: P345
595
+ row, col, fmt, idx = work.unpack('v3V')
596
+ reference = @workbook.pkg_str(idx) or return
597
+ code = (reference.unicode) ? :ucs2 : nil
598
+ params = {
599
+ :kind => :packed_idx,
600
+ :value => reference.text,
601
+ :format_no => fmt,
602
+ :numeric => false,
603
+ :code => code,
604
+ :rich => reference.rich
605
+ }
606
+ cell_factory(row, col, params)
607
+ end
608
+ def mul_rk(op, len, work) # DK:P349
609
+ return nil unless (@workbook.sheet_count > 0);
610
+
611
+ row, scol = work.unpack('v2')
612
+ ecol, = work[-2,2].unpack('v')
613
+ pos = 4
614
+
615
+ scol.upto(ecol) { |col|
616
+ #puts "unpacking: #{work[pos,6].inspect}"
617
+ fmt, val = unpack_rk_rec(work[pos,6])
618
+ params = {
619
+ :kind => :mul_rk,
620
+ :value => val,
621
+ :format_no => fmt,
622
+ :numeric => true,
623
+ }
624
+ cell_factory(row, col, params)
625
+ pos += 6
626
+ }
627
+ end
628
+ def mul_blank(op, len, work) # DK:P349
629
+ row, scol = work.unpack('v2')
630
+ ecol, = work[-2,2].unpack('v')
631
+ pos = 4
632
+
633
+ scol.upto(ecol) { |col|
634
+ fmt, = work[pos,2].unpack('v')
635
+ params = {
636
+ :kind => :mul_blank,
637
+ :value => '',
638
+ :format_no => fmt,
639
+ :numeric => false,
640
+ }
641
+ cell_factory(row, col, params)
642
+ pos += 2
643
+ }
644
+ end
645
+ =begin
646
+ def name(op, len, work) # DK: P350
647
+ gr_bit, key, ch, cce, xals, tab, cust, dsc, hep, status = work.unpack('vc2v3c4')
648
+ #Builtin Name + Length == 1
649
+ if((gr_bit & 0x20).nonzero? && (ch == 1))
650
+ #BIFF8
651
+ if(@workbook.biffversion >= VERSION_BIFF8)
652
+ name = work[14,2].unpack('n').first
653
+ sheet = work[8,2].unpack('v').first - 1
654
+ sheet_w, area = parse_name_area(work[16..-1])
655
+ if(name == 6) #PrintArea
656
+ @workbook.print_area[sheet] = area
657
+ elsif(name == 7) #Title
658
+ title_r = []
659
+ title_c = []
660
+ area.each { |array|
661
+ if(array.at(3) == 0xFF) #Row Title
662
+ title_r.push([array.at(0), array.at(2)])
663
+ else #Col Title
664
+ title_c.push([array.at(1), array.at(3)])
665
+ end
666
+ }
667
+ @workbook.print_title[sheet] = {:row=>title_r, :column=>title_c}
668
+ end
669
+ else
670
+ name = work[14,1].unpack('c')
671
+ sheet, area = parse_name_area95(work[15..-1])
672
+ if(name == 6) #PrintArea
673
+ @workbook.print_area[sheet] = area
674
+ elsif(name == 7) #Title
675
+ title_r = []
676
+ title_c = []
677
+ area.each { |array|
678
+ if(array.at(3) == 0xFF) #Row Title
679
+ title_r.push([array.at(0), array.at(2)])
680
+ else #Col Title
681
+ title_c.push([array.at(1), array.at(3)])
682
+ end
683
+ }
684
+ @workbook.print_title[sheet] = {:row=>title_r, :column=>title_c}
685
+ end
686
+ end
687
+ end
688
+ end
689
+ =end
690
+ def note(op, len, work)
691
+ return if(@current_sheet.nil?)
692
+ ann = Annotation.new
693
+ row, col, clen = work.unpack('v3')
694
+ if(clen.nonzero?)
695
+ ann << work[6,clen]
696
+ else
697
+ idx, authlen = work[6,4].unpack('v2')
698
+ if(authlen > 0)
699
+ ann.author = work[11, authlen]
700
+ end
701
+ reference = @workbook.annotation(idx - 1) or return
702
+ ann << reference.text
703
+ end
704
+ @current_sheet.cell(row, col).annotation = ann
705
+ end
706
+ def number(op, len, work) # DK: P354
707
+ row, col, fmt = work.unpack('v3')
708
+ dval = conv_dval(work[6,8])
709
+ params = {
710
+ :kind => :number,
711
+ :value => dval,
712
+ :format_no => fmt,
713
+ :numeric => true,
714
+ }
715
+ cell_factory(row, col, params)
716
+ end
717
+ def rk(op, len, work) # DK:P401
718
+ row, col = work.unpack('v2')
719
+
720
+ fmt, txt = unpack_rk_rec(work[4,6])
721
+ params = {
722
+ :kind => :rk,
723
+ :value => txt,
724
+ :format_no => fmt,
725
+ :numeric => true,
726
+ :code => nil,
727
+ }
728
+ cell_factory(row, col, params)
729
+ end
730
+ def row(op, len, work) # DK:P403
731
+ return if(@current_sheet.nil?)
732
+
733
+ #0. Get Worksheet info (MaxRow, MaxCol, MinRow, MinCol)
734
+ row, scol, ecol, hght, nil1, nil2, gr, xf = work.unpack('v8')
735
+ ecol -= 1
736
+
737
+ #1. RowHeight
738
+ if(ibool(gr & 0x20)) # Height == 0
739
+ @current_sheet.set_row_height(row, 0)
740
+ else
741
+ @current_sheet.set_row_height(row, hght/20.0)
742
+ end
743
+ @current_sheet.set_dimensions(row, scol, ecol)
744
+ end
745
+ def rstring(op, len, work) # DK:P405
746
+ row, col, fmt, tln = work.unpack('v4')
747
+ params = {
748
+ :kind => :rstring,
749
+ :value => work[8,tln],
750
+ :format_no => fmt,
751
+ :numeric => false,
752
+ :code => :_native_,
753
+ }
754
+ #Has STRN
755
+ if(work.length > (8+tln))
756
+ params.store(:rich, work[8+tln..-1])
757
+ end
758
+ cell_factory(row, col, params)
759
+ end
760
+ def setup(op, len, work) # DK: P409
761
+ return unless(ws = @current_sheet)
762
+ ws.paper, ws.scale, ws.page_start, ws.fit_width, ws.fit_height, \
763
+ gr_bit, ws.resolution, ws.v_resolution = work.unpack('v8')
764
+
765
+ ws.header_margin = conv_dval(work[16,8]) * 127 / 50
766
+ ws.footer_margin = conv_dval(work[24,8]) * 127 / 50
767
+ ws.copies, = work[32,2].unpack('v') # $oWkS->{Copis}
768
+ ws.left_to_right = ibool(gr_bit & 0x01)
769
+ ws.landscape = ibool(gr_bit & 0x02)
770
+ ws.no_pls = ibool(gr_bit & 0x04)
771
+ ws.no_color = ibool(gr_bit & 0x08)
772
+ ws.draft = ibool(gr_bit & 0x10)
773
+ ws.notes = ibool(gr_bit & 0x20)
774
+ ws.no_orient = ibool(gr_bit & 0x40)
775
+ ws.use_page = ibool(gr_bit & 0x80)
776
+ end
777
+ def simple_string(work)
778
+ return "" if(work.empty?)
779
+ #BIFF8
780
+ if(@workbook.biffversion >= VERSION_BIFF8)
781
+ str = conv_biff8_string(work)
782
+ (str == "\x00\x00") ? nil : str
783
+ #Before BIFF8
784
+ else
785
+ len, = work.unpack('c')
786
+ str = work[1,len]
787
+ (str == "\x00\x00\x00") ? nil : str
788
+ end
789
+ end
790
+ def sst(op, len, work) # DK:P413
791
+ str_wk(work[8..-1])
792
+ end
793
+ def string(op, len, work) # DK:P414
794
+ #Position (not enough for ARRAY)
795
+ return if @prev_pos.nil?
796
+ row, col, fmt = @prev_pos
797
+ @prev_pos = nil
798
+
799
+ txt, code = nil
800
+ if(@workbook.biffversion == VERSION_BIFF8)
801
+ buff, = conv_biff8_data(work, true)
802
+ txt = buff.at(0)
803
+ code = (buff.at(1)) ? :ucs2 : nil
804
+ elsif(@workbook.biffversion == VERSION_BIFF5)
805
+ code = :_native_
806
+ tln, = work.unpack('v')
807
+ txt = work[2,tln]
808
+ else
809
+ code = :_native_
810
+ tln, = work.unpack('c')
811
+ txt = work[1,tln]
812
+ end
813
+ params = {
814
+ :kind => :string,
815
+ :value => txt,
816
+ :format_no => fmt,
817
+ :numeric => false,
818
+ :code => code,
819
+ }
820
+ cell_factory(row, col, params)
821
+ end
822
+ def str_wk(work, cnt=nil) # DK:P280
823
+ #1. Continue
824
+ #1.1 Before No Data No
825
+ if(cnt.nil? || @buff == '')
826
+ #puts "cnt was nil or buff was empty"
827
+ @buff << work
828
+ #1.1 No PrevCond
829
+ elsif(@prev_cond.nil?)
830
+ #puts "no prev_cond, adding work to buffer"
831
+ @buff << work[1..-1]
832
+ else
833
+ #puts "else..."
834
+ cnt1st = work[0] # 1st byte of Continue may be a GR byte
835
+ stp, lens = @prev_info
836
+ lenb = @buff.length
837
+
838
+ #puts "cnt1st, @prev_cond"
839
+ #p cnt1st, @prev_cond
840
+
841
+ #1.1 Not in String
842
+ if(lenb >= (stp + lens))
843
+ #puts "lenb (#{lenb}) >= stp + lens (#{stp+lens})"
844
+ @buff << work
845
+ #1.2 Same code (Unicode or ASCII)
846
+ elsif(((@prev_cond ? 1 : 0) & 0x01) == (cnt1st & 0x01))
847
+ #puts "same code"
848
+ @buff << work[1..-1]
849
+ #1.3 Diff code (Unicode or ASCII)
850
+ else
851
+ #puts "codes differ"
852
+ diff = stp + lens - lenb
853
+ if(ibool(cnt1st & 0x01))
854
+ #puts "new code is unicode"
855
+ dum, gr = @buff.unpack('vc')
856
+ @buff[2,1] = [gr | 0x01].pack('c')
857
+ (lenb-stp).downto(1) { |idx|
858
+ @buff[stp+idx,0] = "\x00"
859
+ }
860
+ else
861
+ #puts "old code is unicode"
862
+ (diff/2).downto(1) { |idx|
863
+ work[idx+1,0] = "\x00"
864
+ }
865
+ end
866
+ @buff << work[1..-1]
867
+ end
868
+ end
869
+ @prev_cond = nil
870
+ @prev_info = nil
871
+
872
+ while(@buff.length >= 4)
873
+ buff, len, stpos, lens = conv_biff8_data(@buff, true)
874
+ #puts buff.inspect
875
+ unless(buff[0].nil?)
876
+ pkg_str = Worksheet::PkgString.new(*buff)
877
+ @workbook.add_pkg_str(pkg_str)
878
+ #puts pkg_str
879
+ @buff = @buff[len..-1]
880
+ else
881
+ #puts "code convert, breaking with @prev_cond: #{buff[1]} and @prev_info: [#{stpos}, #{lens}]"
882
+ @prev_cond = buff[1]
883
+ @prev_info = [stpos, lens]
884
+ break
885
+ end
886
+ end
887
+ end
888
+ def unpack_rk_rec(arg) # DK:P401
889
+ ef, = arg[0,2].unpack('v')
890
+ lwk, = arg[2,4]
891
+ swk = lwk.unpack('c4').reverse.pack('c4')
892
+ ptn = (swk[3,1].unpack('c').first & 0x03)
893
+ null = "\0\0\0\0"
894
+ res = nil
895
+ if(ptn == 0)
896
+ #puts "ptn==0"
897
+ res, = ((@bigendian) ? swk + null : null + lwk).unpack('d')
898
+ elsif(ptn == 1)
899
+ #puts "ptn==1"
900
+ swk[3] &= [(swk[3,1].unpack('c').first & 0xFC)].pack('c')[0]
901
+ lwk[0] &= [(lwk[0,1].unpack('c').first & 0xFC)].pack('c')[0]
902
+ res = ((@bigendian) ? swk + null : null + lwk).unpack('d').first.to_f / 100.0
903
+ elsif(ptn == 2)
904
+ #puts "ptn==2"
905
+ bin, = swk.unpack('B32')
906
+ wklb = [((bin[0,1]*2) + bin[0,30])].pack('B32')
907
+ wkl = (@bigendian) ? wklb : wklb.unpack('c4').reverse.pack('c4')
908
+ res, = wkl.unpack('i')
909
+ else
910
+ #puts "ptn==#{ptn}"
911
+ ub, = swk.unpack('B32')
912
+ wklb = [((ub[0,1]*2) + ub[0,30])].pack('B32')
913
+ wkl = (@bigendian) ? wklb : wklb.unpack('c4').reverse.pack('c4')
914
+ res = wkl.unpack('i').first / 100.00
915
+ end
916
+ #p lwk, swk, swk[3,1], res if([5,12].include? res)
917
+ [ef, res]
918
+ end
919
+ def ws_bool(op, len, work) # DK: P452
920
+ return if(@current_sheet.nil?)
921
+ fit = ibool(work.unpack('v').first & 0x100)
922
+ @current_sheet.page_fit = fit
923
+ end
924
+ def xf(op, len, work) # DK:P453
925
+ fnt, idx, lock, hidden, style, i123, alh, wrap, alv, justl = nil
926
+ rotate, ind, shrink, merge, readdir, bdr_d, bdr_sl, bdr_sr = nil
927
+ bdr_st, bdr_sb, bdr_sd, bdr_cl, bdr_cr, bdr_ct, bdr_cb = nil
928
+ bdr_cd, fill_p, fill_cf, fill_cb = nil
929
+
930
+ if(@workbook.biffversion == VERSION_BIFF8)
931
+ fnt, idx, gen1, align, gen2,
932
+ bdr1, bdr2, bdr3, ptn = work.unpack('v7Vv')
933
+ lock = ibool(gen1 & 0x01)
934
+ hidden = ibool(gen1 & 0x02)
935
+ style = ibool(gen1 & 0x04)
936
+ i123 = ibool(gen1 & 0x08)
937
+
938
+ alh = (align & 0x07)
939
+ wrap = ibool(align & 0x08)
940
+ alv = (align & 0x70) / 0x10
941
+ justl = ibool(align & 0x80)
942
+
943
+ rotate = ((align & 0xFF00) / 0x100) & 0x00FF
944
+ rotate = 90 if(rotate == 255)
945
+ rotate = (90 - rotate) if(rotate > 90)
946
+
947
+ ind = (gen2 & 0x0F)
948
+ shrink = ibool(gen2 & 0x10)
949
+ merge = ibool(gen2 & 0x20)
950
+ readdir = ((gen2 & 0xC0) / 0x40) & 0x03
951
+
952
+ bdr_sl = bdr1 & 0x0F
953
+ bdr_sr = ((bdr1 & 0xF0) / 0x10) & 0x0F
954
+ bdr_st = ((bdr1 & 0xF00) / 0x100) & 0x0F
955
+ bdr_sb = ((bdr1 & 0xF000) / 0x1000) & 0x0F
956
+
957
+ bdr_cl = ((bdr2 & 0x7F)) & 0x7F
958
+ bdr_cr = ((bdr2 & 0x3F80) / 0x80) & 0x7F
959
+ bdr_d = ((bdr2 & 0xC000) / 0x4000) & 0x7F
960
+
961
+ bdr_ct = ((bdr3 & 0x7F)) & 0x7F
962
+ bdr_cb = ((bdr3 & 0x3F80) / 0x80) & 0x7F
963
+ bdr_cd = ((bdr2 & 0x1FC000) / 0x4000) & 0x7F
964
+ bdr_sd = ((bdr2 & 0x1E00000) / 0x200000) & 0x7F
965
+ fill_p = ((bdr2 & 0xFC000000) / 0x4000000) & 0x3F
966
+
967
+ fill_cf = ptn & 0x7F
968
+ fill_cb = ((ptn & 0x3F80) / 0x80) & 0x7F
969
+ else
970
+ fnt, idx, gen1, align, ptn1, ptn2, bdr1, bdr2 = work.unpack('v8')
971
+
972
+ lock = ibool(gen1 & 0x01)
973
+ hidden = ibool(gen1 & 0x02)
974
+ style = ibool(gen1 & 0x04)
975
+ i123 = ibool(gen1 & 0x08)
976
+
977
+ alh = (align & 0x07)
978
+ wrap = ibool(align & 0x08)
979
+ alv = (align & 0x70) / 0x10
980
+ justl = ibool(align & 0x80)
981
+
982
+ rotate = ((align & 0x300) / 0x100) & 0x03
983
+
984
+ fill_cf = ptn1 & 0x7F
985
+ fill_cb = ((ptn1 & 0x1F80) / 0x80) & 0x7F
986
+
987
+ fill_p = ptn2 & 0x3F
988
+ bdr_sb = ((ptn2 & 0x1C0) / 0x40) & 0x07
989
+ bdr_cb = ((ptn2 & 0xFE00) / 0x200) & 0x7F
990
+
991
+ bdr_st = bdr1 & 0x07
992
+ bdr_sl = ((bdr1 & 0x38) / 0x8) & 0x07
993
+ bdr_sr = ((bdr1 & 0x1C0) / 0x40) & 0x07
994
+ bdr_ct = ((bdr1 & 0xFE00) / 0x200) & 0x7F
995
+
996
+ bdr_cl = (bdr2 & 0x7F) & 0x7F
997
+ bdr_cr = ((bdr2 & 0x3F80) / 0x80) & 0x7F
998
+ end
999
+
1000
+ params = {
1001
+ :font_no => fnt,
1002
+ #:font => workbook.fonts[fnt],
1003
+ :fmt_idx => idx,
1004
+ :lock => lock,
1005
+ :hidden => hidden,
1006
+ :style => style,
1007
+ :key_123 => i123,
1008
+ :align_h => alh,
1009
+ :wrap => wrap,
1010
+ :align_v => alv,
1011
+ :just_last => justl,
1012
+ :rotate => rotate,
1013
+ :indent => ind,
1014
+ :shrink => shrink,
1015
+ :merge => merge,
1016
+ :read_dir => readdir,
1017
+ :border_style => [bdr_sl, bdr_sr, bdr_st, bdr_sb],
1018
+ :border_color => [bdr_cl, bdr_cr, bdr_ct, bdr_cb],
1019
+ :border_diag => [bdr_d, bdr_sd, bdr_cd],
1020
+ :fill => [fill_p, fill_cf, fill_cb],
1021
+ }
1022
+ if(@encoding)
1023
+ params.store(:encoding, @encoding)
1024
+ end
1025
+ #p "**"*33
1026
+ #p work
1027
+ #p idx
1028
+ @workbook.add_cell_format(Format.new(params))
1029
+ end
1030
+ end
1031
+ end
1032
+ end