writeexcel 0.4.2 → 0.4.3

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.
@@ -1,1051 +1,1051 @@
1
- # -*- coding: utf-8 -*-
2
- ###############################################################################
3
- #
4
- # Formula - A class for generating Excel formulas.
5
- #
6
- #
7
- # Used in conjunction with WriteExcel
8
- #
9
- # Copyright 2000-2010, John McNamara, jmcnamara@cpan.org
10
- #
11
- # original written in Perl by John McNamara
12
- # converted to Ruby by Hideo Nakamura, cxn03651@msj.biglobe.ne.jp
13
- #
14
- require 'strscan'
15
- require 'writeexcel/excelformulaparser'
16
-
17
- module Writeexcel
18
-
19
- class Formula < ExcelFormulaParser #:nodoc:
20
- require 'writeexcel/helper'
21
- private :convert_to_ascii_if_ascii
22
-
23
- attr_accessor :byte_order, :workbook, :ext_sheets, :ext_refs, :ext_ref_count
24
-
25
- def initialize(byte_order)
26
- @byte_order = byte_order
27
- @workbook = ""
28
- @ext_sheets = {}
29
- @ext_refs = {}
30
- @ext_ref_count = 0
31
- @ext_names = {}
32
- initialize_hashes
33
- end
34
-
35
- ###############################################################################
36
- #
37
- # parse_formula()
38
- #
39
- # Takes a textual description of a formula and returns a RPN encoded byte
40
- # string.
41
- #
42
- def parse_formula(formula, byte_stream = false)
43
- # Build the parse tree for the formula
44
- tokens = reverse(parse(formula))
45
-
46
- # Add a volatile token if the formula contains a volatile function.
47
- # This must be the first token in the list
48
- #
49
- tokens.unshift('_vol') if check_volatile(tokens) != 0
50
-
51
- # The return value depends on which Worksheet.pm method is the caller
52
- unless byte_stream
53
- # Parse formula to see if it throws any errors and then
54
- # return raw tokens to Worksheet::store_formula()
55
- #
56
- parse_tokens(tokens)
57
- tokens
58
- else
59
- # Return byte stream to Worksheet::write_formula()
60
- parse_tokens(tokens)
61
- end
62
- end
63
-
64
- ###############################################################################
65
- #
66
- # parse_tokens()
67
- #
68
- # Convert each token or token pair to its Excel 'ptg' equivalent.
69
- #
70
- def parse_tokens(tokens)
71
- parse_str = ''
72
- last_type = ''
73
- modifier = ''
74
- num_args = 0
75
- _class = 0
76
- _classary = [1]
77
- args = tokens.dup
78
- # A note about the class modifiers used below. In general the class,
79
- # "reference" or "value", of a function is applied to all of its operands.
80
- # However, in certain circumstances the operands can have mixed classes,
81
- # e.g. =VLOOKUP with external references. These will eventually be dealt
82
- # with by the parser. However, as a workaround the class type of a token
83
- # can be changed via the repeat_formula interface. Thus, a _ref2d token can
84
- # be changed by the user to _ref2dA or _ref2dR to change its token class.
85
- #
86
- while (!args.empty?)
87
- token = args.shift
88
-
89
- if (token == '_arg')
90
- num_args = args.shift
91
- elsif (token == '_class')
92
- token = args.shift
93
- _class = @functions[token][2]
94
- # If _class is undef then it means that the function isn't valid.
95
- exit "Unknown function #{token}() in formula\n" if _class.nil?
96
- _classary.push(_class)
97
- elsif (token == '_vol')
98
- parse_str += convert_volatile()
99
- elsif (token == 'ptgBool')
100
- token = args.shift
101
- parse_str += convert_bool(token)
102
- elsif (token == '_num')
103
- token = args.shift
104
- parse_str += convert_number(token)
105
- elsif (token == '_str')
106
- token = args.shift
107
- parse_str += convert_string(token)
108
- elsif (token =~ /^_ref2d/)
109
- modifier = token.sub(/_ref2d/, '')
110
- _class = _classary[-1]
111
- _class = 0 if modifier == 'R'
112
- _class = 1 if modifier == 'V'
113
- token = args.shift
114
- parse_str += convert_ref2d(token, _class)
115
- elsif (token =~ /^_ref3d/)
116
- modifier = token.sub(/_ref3d/,'')
117
- _class = _classary[-1]
118
- _class = 0 if modifier == 'R'
119
- _class = 1 if modifier == 'V'
120
- token = args.shift
121
- parse_str += convert_ref3d(token, _class)
122
- elsif (token =~ /^_range2d/)
123
- modifier = token.sub(/_range2d/,'')
124
- _class = _classary[-1]
125
- _class = 0 if modifier == 'R'
126
- _class = 1 if modifier == 'V'
127
- token = args.shift
128
- parse_str += convert_range2d(token, _class)
129
- elsif (token =~ /^_range3d/)
130
- modifier = token.sub(/_range3d/,'')
131
- _class = _classary[-1]
132
- _class = 0 if modifier == 'R'
133
- _class = 1 if modifier == 'V'
134
- token = args.shift
135
- parse_str += convert_range3d(token, _class)
136
- elsif (token =~ /^_name/)
137
- modifier = token.sub(/_name/, '')
138
- _class = _classary[-1]
139
- _class = 0 if modifier == 'R'
140
- _class = 1 if modifier == 'V'
141
- token = args.shift
142
- parse_str += convert_name(token, _class)
143
- elsif (token == '_func')
144
- token = args.shift
145
- parse_str += convert_function(token, num_args.to_i)
146
- _classary.pop
147
- num_args = 0 # Reset after use
148
- elsif @ptg[token]
149
- parse_str += [@ptg[token]].pack("C")
150
- else
151
- # Unrecognised token
152
- return nil
153
- end
154
- end
155
-
156
- parse_str
157
- end
158
-
159
- def scan(formula)
160
- s = StringScanner.new(formula)
161
- q = []
162
- until s.eos?
163
- # order is important.
164
- if s.scan(/(?=\d|\.\d)\d*(\.\d*)?([Ee]([+-]?\d+))?/)
165
- q.push [:NUMBER, s.matched]
166
- elsif s.scan(/"([^"]|"")*"/)
167
- q.push [:STRING, s.matched]
168
- elsif s.scan(/\$?[A-I]?[A-Z]\$?(\d+)?:\$?[A-I]?[A-Z]\$?(\d+)?/)
169
- q.push [:RANGE2D , s.matched]
170
- elsif s.scan(/[^!(,]+!\$?[A-I]?[A-Z]\$?(\d+)?:\$?[A-I]?[A-Z]\$?(\d+)?/)
171
- q.push [:RANGE3D , s.matched]
172
- elsif s.scan(/'[^']+'!\$?[A-I]?[A-Z]\$?(\d+)?:\$?[A-I]?[A-Z]\$?(\d+)?/)
173
- q.push [:RANGE3D , s.matched]
174
- elsif s.scan(/\$?[A-I]?[A-Z]\$?\d+/)
175
- q.push [:REF2D, s.matched]
176
- elsif s.scan(/[^!(,]+!\$?[A-I]?[A-Z]\$?\d+/)
177
- q.push [:REF3D , s.matched]
178
- elsif s.scan(/'[^']+'!\$?[A-I]?[A-Z]\$?\d+/)
179
- q.push [:REF3D , s.matched]
180
- elsif s.scan(/<=/)
181
- q.push [:LE , s.matched]
182
- elsif s.scan(/>=/)
183
- q.push [:GE , s.matched]
184
- elsif s.scan(/<>/)
185
- q.push [:NE , s.matched]
186
- elsif s.scan(/</)
187
- q.push [:LT , s.matched]
188
- elsif s.scan(/>/)
189
- q.push [:GT , s.matched]
190
- elsif s.scan(/[A-Z0-9_.]+\(/)
191
- s.unscan
192
- s.scan(/[A-Z0-9_.]+/)
193
- q.push [:FUNC, s.matched]
194
- elsif s.scan(/TRUE/)
195
- q.push [:TRUE, s.matched]
196
- elsif s.scan(/FALSE/)
197
- q.push [:FALSE, s.matched]
198
- elsif s.scan(/[A-Za-z_]\w+/)
199
- q.push [:NAME , s.matched]
200
- elsif s.scan(/\s+/)
201
- ;
202
- elsif s.scan(/./)
203
- q.push [s.matched, s.matched]
204
- end
205
- end
206
- q.push [:EOL, nil]
207
- end
208
-
209
- def parse(formula)
210
- @q = scan(formula)
211
- @q.push [false, nil]
212
- @yydebug=true
213
- do_parse
214
- end
215
-
216
- def next_token
217
- @q.shift
218
- end
219
-
220
- def reverse(expression)
221
- expression.flatten
222
- end
223
-
224
- ###############################################################################
225
-
226
- private
227
-
228
- ###############################################################################
229
-
230
-
231
- ###############################################################################
232
- #
233
- # _check_volatile()
234
- #
235
- # Check if the formula contains a volatile function, i.e. a function that must
236
- # be recalculated each time a cell is updated. These formulas require a ptgAttr
237
- # with the volatile flag set as the first token in the parsed expression.
238
- #
239
- # Examples of volatile functions: RAND(), NOW(), TODAY()
240
- #
241
- def check_volatile(tokens)
242
- volatile = 0
243
-
244
- (0..tokens.size - 1).each do |i|
245
- # If the next token is a function check if it is volatile.
246
- if tokens[i] == '_func' and @functions[tokens[i+1]][3] != 0
247
- volatile = 1
248
- break
249
- end
250
- end
251
-
252
- volatile
253
- end
254
-
255
- ###############################################################################
256
- #
257
- # _convert_volatile()
258
- #
259
- # Convert _vol to a ptgAttr tag formatted to indicate that the formula contains
260
- # a volatile function. See _check_volatile()
261
- #
262
- def convert_volatile
263
- # Set bitFattrSemi flag to indicate volatile function, "w" is set to zero.
264
- return [@ptg['ptgAttr'], 0x1, 0x0].pack("CCv")
265
- end
266
-
267
- ###############################################################################
268
- #
269
- # _convert_bool()
270
- #
271
- # Convert a boolean token to ptgBool
272
- #
273
- def convert_bool(bool)
274
- [@ptg['ptgBool'], bool.to_i].pack("CC")
275
- end
276
-
277
-
278
- ###############################################################################
279
- #
280
- # _convert_number()
281
- #
282
- # Convert a number token to ptgInt or ptgNum
283
- #
284
- def convert_number(num)
285
- # Integer in the range 0..2**16-1
286
- if ((num =~ /^\d+$/) && (num.to_i <= 65535))
287
- return [@ptg['ptgInt'], num.to_i].pack("Cv")
288
- else # A float
289
- num = [num].pack("d")
290
- num.reverse! if @byte_order != 0 && @byte_order != ''
291
- return [@ptg['ptgNum']].pack("C") + num
292
- end
293
- end
294
-
295
- ###############################################################################
296
- #
297
- # _convert_string()
298
- #
299
- # Convert a string to a ptg Str.
300
- #
301
- def convert_string(str)
302
- str = convert_to_ascii_if_ascii(str)
303
-
304
- encoding = 0
305
-
306
- str.sub!(/^"/,'') # Remove leading "
307
- str.sub!(/"$/,'') # Remove trailing "
308
- str.gsub!(/""/,'"') # Substitute Excel's escaped double quote "" for "
309
-
310
- # number of characters in str
311
- length = ruby_18 { str.gsub(/[^\WA-Za-z_\d]/, ' ').length } ||
312
- ruby_19 { str.length }
313
-
314
- # Handle utf8 strings
315
- if str.encoding == Encoding::UTF_8
316
- str = utf8_to_16le(str)
317
- str.force_encoding('BINARY')
318
- encoding = 1
319
- end
320
-
321
- exit "String in formula has more than 255 chars\n" if length > 255
322
-
323
- [@ptg['ptgStr'], length, encoding].pack("CCC") + str
324
- end
325
-
326
- ###############################################################################
327
- #
328
- # _convert_ref2d()
329
- #
330
- # Convert an Excel reference such as A1, $B2, C$3 or $D$4 to a ptgRefV.
331
- #
332
- def convert_ref2d(cell, _class)
333
- # Convert the cell reference
334
- row, col = cell_to_packed_rowcol(cell)
335
-
336
- # The ptg value depends on the class of the ptg.
337
- if (_class == 0)
338
- ptgref = [@ptg['ptgRef']].pack("C")
339
- elsif (_class == 1)
340
- ptgref = [@ptg['ptgRefV']].pack("C")
341
- elsif (_class == 2)
342
- ptgref = [@ptg['ptgRefA']].pack("C")
343
- else
344
- exit "Unknown function class in formula\n"
345
- end
346
-
347
- ptgref + row + col
348
- end
349
-
350
- ###############################################################################
351
- #
352
- # _convert_ref3d
353
- #
354
- # Convert an Excel 3d reference such as "Sheet1!A1" or "Sheet1:Sheet2!A1" to a
355
- # ptgRef3dV.
356
- #
357
- def convert_ref3d(token, _class)
358
- # Split the ref at the ! symbol
359
- ext_ref, cell = token.split('!')
360
-
361
- # Convert the external reference part
362
- ext_ref = pack_ext_ref(ext_ref)
363
-
364
- # Convert the cell reference part
365
- row, col = cell_to_packed_rowcol(cell)
366
-
367
- # The ptg value depends on the class of the ptg.
368
- if (_class == 0)
369
- ptgref = [@ptg['ptgRef3d']].pack("C")
370
- elsif (_class == 1)
371
- ptgref = [@ptg['ptgRef3dV']].pack("C")
372
- elsif (_class == 2)
373
- ptgref = [@ptg['ptgRef3dA']].pack("C")
374
- else
375
- exit "Unknown function class in formula\n"
376
- end
377
-
378
- ptgref + ext_ref + row + col
379
- end
380
-
381
- ###############################################################################
382
- #
383
- # _convert_range2d()
384
- #
385
- # Convert an Excel range such as A1:D4 or A:D to a ptgRefV.
386
- #
387
- def convert_range2d(range, _class)
388
- # Split the range into 2 cell refs
389
- cell1, cell2 = range.split(':')
390
-
391
- # A range such as A:D is equivalent to A1:D65536, so add rows as required
392
- cell1 += '1' unless cell1 =~ /\d/
393
- cell2 += '65536' unless cell2 =~ /\d/
394
-
395
- # Convert the cell references
396
- row1, col1 = cell_to_packed_rowcol(cell1)
397
- row2, col2 = cell_to_packed_rowcol(cell2)
398
-
399
- # The ptg value depends on the class of the ptg.
400
- if (_class == 0)
401
- ptgarea = [@ptg['ptgArea']].pack("C")
402
- elsif (_class == 1)
403
- ptgarea = [@ptg['ptgAreaV']].pack("C")
404
- elsif (_class == 2)
405
- ptgarea = [@ptg['ptgAreaA']].pack("C")
406
- else
407
- exit "Unknown function class in formula\n"
408
- end
409
-
410
- ptgarea + row1 + row2 + col1 + col2
411
- end
412
-
413
- ###############################################################################
414
- #
415
- # _convert_range3d
416
- #
417
- # Convert an Excel 3d range such as "Sheet1!A1:D4" or "Sheet1:Sheet2!A1:D4" to
418
- # a ptgArea3dV.
419
- #
420
- def convert_range3d(token, _class)
421
- # Split the ref at the ! symbol
422
- ext_ref, range = token.split('!')
423
-
424
- # Convert the external reference part
425
- ext_ref = pack_ext_ref(ext_ref)
426
-
427
- # Split the range into 2 cell refs
428
- cell1, cell2 = range.split(':')
429
-
430
- # A range such as A:D is equivalent to A1:D65536, so add rows as required
431
- cell1 += '1' unless cell1 =~ /\d/
432
- cell2 += '65536' unless cell2 =~ /\d/
433
-
434
- # Convert the cell references
435
- row1, col1 = cell_to_packed_rowcol(cell1)
436
- row2, col2 = cell_to_packed_rowcol(cell2)
437
-
438
- # The ptg value depends on the class of the ptg.
439
- if (_class == 0)
440
- ptgarea = [@ptg['ptgArea3d']].pack("C")
441
- elsif (_class == 1)
442
- ptgarea = [@ptg['ptgArea3dV']].pack("C")
443
- elsif (_class == 2)
444
- ptgarea = [@ptg['ptgArea3dA']].pack("C")
445
- else
446
- exit "Unknown function class in formula\n"
447
- end
448
-
449
- ptgarea + ext_ref + row1 + row2 + col1+ col2
450
- end
451
-
452
- ###############################################################################
453
- #
454
- # _pack_ext_ref()
455
- #
456
- # Convert the sheet name part of an external reference, for example "Sheet1" or
457
- # "Sheet1:Sheet2", to a packed structure.
458
- #
459
- def pack_ext_ref(ext_ref)
460
- ext_ref.sub!(/^'/,'') # Remove leading ' if any.
461
- ext_ref.sub!(/'$/,'') # Remove trailing ' if any.
462
-
463
- # Check if there is a sheet range eg., Sheet1:Sheet2.
464
- if (ext_ref =~ /:/)
465
- sheet1, sheet2 = ext_ref.split(':')
466
-
467
- sheet1 = get_sheet_index(sheet1)
468
- sheet2 = get_sheet_index(sheet2)
469
-
470
- # Reverse max and min sheet numbers if necessary
471
- if (sheet1 > sheet2)
472
- sheet1, sheet2 = [sheet2, sheet1]
473
- end
474
- else
475
- # Single sheet name only.
476
- sheet1, sheet2 = [ext_ref, ext_ref]
477
-
478
- sheet1 = get_sheet_index(sheet1)
479
- sheet2 = sheet1
480
- end
481
-
482
- key = "#{sheet1}:#{sheet2}"
483
-
484
- if @ext_refs[key].nil?
485
- index = get_ext_ref_count
486
- @ext_refs[key] = index
487
- @ext_ref_count += 1
488
- else
489
- index = @ext_refs[key]
490
- end
491
-
492
- [index].pack("v")
493
- end
494
-
495
- ###############################################################################
496
- #
497
- # _get_sheet_index()
498
- #
499
- # Look up the index that corresponds to an external sheet name. The hash of
500
- # sheet names is updated by the add_worksheet() method of the Workbook class.
501
- #
502
- def get_sheet_index(sheet_name)
503
- sheet_name = convert_to_ascii_if_ascii(sheet_name)
504
-
505
- # Handle utf8 sheetnames
506
- if sheet_name.encoding == Encoding::UTF_8
507
- sheet_name = sheet_name.encode('UTF-16BE')
508
- end
509
-
510
- if @ext_sheets[sheet_name].nil?
511
- raise "Unknown sheet name '#{sheet_name}' in formula\n"
512
- else
513
- return @ext_sheets[sheet_name]
514
- end
515
- end
516
- public :get_sheet_index
517
-
518
- ###############################################################################
519
- #
520
- # set_ext_sheets()
521
- #
522
- # This semi-public method is used to update the hash of sheet names. It is
523
- # updated by the add_worksheet() method of the Workbook class.
524
- #
525
- def set_ext_sheets(worksheet, index)
526
- # The _ext_sheets hash is used to translate between worksheet names
527
- # and their index
528
- @ext_sheets[worksheet] = index
529
- end
530
- public :set_ext_sheets
531
-
532
- ###############################################################################
533
- #
534
- # get_ext_sheets()
535
- #
536
- # This semi-public method is used to get the worksheet references that were
537
- # used in formulas for inclusion in the EXTERNSHEET Workbook record.
538
- #
539
- def get_ext_sheets
540
- @ext_refs
541
- end
542
- public :get_ext_sheets
543
-
544
- ###############################################################################
545
- #
546
- # get_ext_ref_count()
547
- #
548
- # TODO This semi-public method is used to update the hash of sheet names. It is
549
- # updated by the add_worksheet() method of the Workbook class.
550
- #
551
- def get_ext_ref_count
552
- @ext_ref_count
553
- end
554
-
555
- ###############################################################################
556
- #
557
- # _get_name_index()
558
- #
559
- # Look up the index that corresponds to an external defined name. The hash of
560
- # defined names is updated by the define_name() method in the Workbook class.
561
- #
562
- def get_name_index(name)
563
- if @ext_names.has_key?(name)
564
- @ext_names[name]
565
- else
566
- raise "Unknown defined name #{name} in formula\n"
567
- end
568
- end
569
- private :get_name_index
570
-
571
- ###############################################################################
572
- #
573
- # set_ext_name()
574
- #
575
- # This semi-public method is used to update the hash of defined names.
576
- #
577
- def set_ext_name(name, index)
578
- @ext_names[name] = index
579
- end
580
- public :set_ext_name
581
-
582
- ###############################################################################
583
- #
584
- # _convert_function()
585
- #
586
- # Convert a function to a ptgFunc or ptgFuncVarV depending on the number of
587
- # args that it takes.
588
- #
589
- def convert_function(token, num_args)
590
- exit "Unknown function #{token}() in formula\n" if @functions[token][0].nil?
591
-
592
- args = @functions[token][1]
593
-
594
- # Fixed number of args eg. TIME($i,$j,$k).
595
- if (args >= 0)
596
- # Check that the number of args is valid.
597
- if (args != num_args)
598
- raise "Incorrect number of arguments for #{token}() in formula\n";
599
- else
600
- return [@ptg['ptgFuncV'], @functions[token][0]].pack("Cv")
601
- end
602
- end
603
-
604
- # Variable number of args eg. SUM(i,j,k, ..).
605
- if (args == -1)
606
- return [@ptg['ptgFuncVarV'], num_args, @functions[token][0]].pack("CCv")
607
- end
608
- end
609
-
610
- ###############################################################################
611
- #
612
- # _convert_name()
613
- #
614
- # Convert a symbolic name into a name reference.
615
- #
616
- def convert_name(name, _class)
617
- name_index = get_name_index(name)
618
-
619
- # The ptg value depends on the class of the ptg.
620
- if _class == 0
621
- ptgName = @ptg['ptgName']
622
- elsif _class == 1
623
- ptgName = @ptg['ptgNameV']
624
- elsif _class == 2
625
- ptgName = @ptg['ptgNameA']
626
- end
627
-
628
- [ptgName, name_index].pack('CV')
629
- end
630
- private :convert_name
631
-
632
- ###############################################################################
633
- #
634
- # _cell_to_rowcol($cell_ref)
635
- #
636
- # Convert an Excel cell reference such as A1 or $B2 or C$3 or $D$4 to a zero
637
- # indexed row and column number. Also returns two boolean values to indicate
638
- # whether the row or column are relative references.
639
- # TODO use function in Utility.pm
640
- #
641
- def cell_to_rowcol(cell)
642
- cell =~ /(\$?)([A-I]?[A-Z])(\$?)(\d+)/
643
-
644
- col_rel = $1 == "" ? 1 : 0
645
- row_rel = $3 == "" ? 1 : 0
646
- row = $4.to_i
647
-
648
- col = chars_to_col($2.split(//))
649
-
650
- # Convert 1-index to zero-index
651
- row -= 1
652
- col -= 1
653
-
654
- [row, col, row_rel, col_rel]
655
- end
656
-
657
- ###############################################################################
658
- #
659
- # _cell_to_packed_rowcol($row, $col, $row_rel, $col_rel)
660
- #
661
- # pack() row and column into the required 3 byte format.
662
- #
663
- def cell_to_packed_rowcol(cell)
664
- row, col, row_rel, col_rel = cell_to_rowcol(cell)
665
-
666
- exit "Column #{cell} greater than IV in formula\n" if col >= 256
667
- exit "Row #{cell} greater than 65536 in formula\n" if row >= 65536
668
-
669
- # Set the high bits to indicate if row or col are relative.
670
- col |= col_rel << 14
671
- col |= row_rel << 15
672
-
673
- row = [row].pack('v')
674
- col = [col].pack('v')
675
-
676
- [row, col]
677
- end
678
-
679
- ###############################################################################
680
- #
681
- # _initialize_hashes()
682
- #
683
- def initialize_hashes
684
-
685
- # The Excel ptg indices
686
- @ptg = {
687
- 'ptgExp' => 0x01,
688
- 'ptgTbl' => 0x02,
689
- 'ptgAdd' => 0x03,
690
- 'ptgSub' => 0x04,
691
- 'ptgMul' => 0x05,
692
- 'ptgDiv' => 0x06,
693
- 'ptgPower' => 0x07,
694
- 'ptgConcat' => 0x08,
695
- 'ptgLT' => 0x09,
696
- 'ptgLE' => 0x0A,
697
- 'ptgEQ' => 0x0B,
698
- 'ptgGE' => 0x0C,
699
- 'ptgGT' => 0x0D,
700
- 'ptgNE' => 0x0E,
701
- 'ptgIsect' => 0x0F,
702
- 'ptgUnion' => 0x10,
703
- 'ptgRange' => 0x11,
704
- 'ptgUplus' => 0x12,
705
- 'ptgUminus' => 0x13,
706
- 'ptgPercent' => 0x14,
707
- 'ptgParen' => 0x15,
708
- 'ptgMissArg' => 0x16,
709
- 'ptgStr' => 0x17,
710
- 'ptgAttr' => 0x19,
711
- 'ptgSheet' => 0x1A,
712
- 'ptgEndSheet' => 0x1B,
713
- 'ptgErr' => 0x1C,
714
- 'ptgBool' => 0x1D,
715
- 'ptgInt' => 0x1E,
716
- 'ptgNum' => 0x1F,
717
- 'ptgArray' => 0x20,
718
- 'ptgFunc' => 0x21,
719
- 'ptgFuncVar' => 0x22,
720
- 'ptgName' => 0x23,
721
- 'ptgRef' => 0x24,
722
- 'ptgArea' => 0x25,
723
- 'ptgMemArea' => 0x26,
724
- 'ptgMemErr' => 0x27,
725
- 'ptgMemNoMem' => 0x28,
726
- 'ptgMemFunc' => 0x29,
727
- 'ptgRefErr' => 0x2A,
728
- 'ptgAreaErr' => 0x2B,
729
- 'ptgRefN' => 0x2C,
730
- 'ptgAreaN' => 0x2D,
731
- 'ptgMemAreaN' => 0x2E,
732
- 'ptgMemNoMemN' => 0x2F,
733
- 'ptgNameX' => 0x39,
734
- 'ptgRef3d' => 0x3A,
735
- 'ptgArea3d' => 0x3B,
736
- 'ptgRefErr3d' => 0x3C,
737
- 'ptgAreaErr3d' => 0x3D,
738
- 'ptgArrayV' => 0x40,
739
- 'ptgFuncV' => 0x41,
740
- 'ptgFuncVarV' => 0x42,
741
- 'ptgNameV' => 0x43,
742
- 'ptgRefV' => 0x44,
743
- 'ptgAreaV' => 0x45,
744
- 'ptgMemAreaV' => 0x46,
745
- 'ptgMemErrV' => 0x47,
746
- 'ptgMemNoMemV' => 0x48,
747
- 'ptgMemFuncV' => 0x49,
748
- 'ptgRefErrV' => 0x4A,
749
- 'ptgAreaErrV' => 0x4B,
750
- 'ptgRefNV' => 0x4C,
751
- 'ptgAreaNV' => 0x4D,
752
- 'ptgMemAreaNV' => 0x4E,
753
- 'ptgMemNoMemN' => 0x4F,
754
- 'ptgFuncCEV' => 0x58,
755
- 'ptgNameXV' => 0x59,
756
- 'ptgRef3dV' => 0x5A,
757
- 'ptgArea3dV' => 0x5B,
758
- 'ptgRefErr3dV' => 0x5C,
759
- 'ptgAreaErr3d' => 0x5D,
760
- 'ptgArrayA' => 0x60,
761
- 'ptgFuncA' => 0x61,
762
- 'ptgFuncVarA' => 0x62,
763
- 'ptgNameA' => 0x63,
764
- 'ptgRefA' => 0x64,
765
- 'ptgAreaA' => 0x65,
766
- 'ptgMemAreaA' => 0x66,
767
- 'ptgMemErrA' => 0x67,
768
- 'ptgMemNoMemA' => 0x68,
769
- 'ptgMemFuncA' => 0x69,
770
- 'ptgRefErrA' => 0x6A,
771
- 'ptgAreaErrA' => 0x6B,
772
- 'ptgRefNA' => 0x6C,
773
- 'ptgAreaNA' => 0x6D,
774
- 'ptgMemAreaNA' => 0x6E,
775
- 'ptgMemNoMemN' => 0x6F,
776
- 'ptgFuncCEA' => 0x78,
777
- 'ptgNameXA' => 0x79,
778
- 'ptgRef3dA' => 0x7A,
779
- 'ptgArea3dA' => 0x7B,
780
- 'ptgRefErr3dA' => 0x7C,
781
- 'ptgAreaErr3d' => 0x7D
782
- };
783
-
784
- # Thanks to Michael Meeks and Gnumeric for the initial arg values.
785
- #
786
- # The following hash was generated by "function_locale.pl" in the distro.
787
- # Refer to function_locale.pl for non-English function names.
788
- #
789
- # The array elements are as follow:
790
- # ptg: The Excel function ptg code.
791
- # args: The number of arguments that the function takes:
792
- # >=0 is a fixed number of arguments.
793
- # -1 is a variable number of arguments.
794
- # class: The reference, value or array class of the function args.
795
- # vol: The function is volatile.
796
- #
797
- @functions = {
798
- # ptg args class vol
799
- 'COUNT' => [ 0, -1, 0, 0 ],
800
- 'IF' => [ 1, -1, 1, 0 ],
801
- 'ISNA' => [ 2, 1, 1, 0 ],
802
- 'ISERROR' => [ 3, 1, 1, 0 ],
803
- 'SUM' => [ 4, -1, 0, 0 ],
804
- 'AVERAGE' => [ 5, -1, 0, 0 ],
805
- 'MIN' => [ 6, -1, 0, 0 ],
806
- 'MAX' => [ 7, -1, 0, 0 ],
807
- 'ROW' => [ 8, -1, 0, 0 ],
808
- 'COLUMN' => [ 9, -1, 0, 0 ],
809
- 'NA' => [ 10, 0, 0, 0 ],
810
- 'NPV' => [ 11, -1, 1, 0 ],
811
- 'STDEV' => [ 12, -1, 0, 0 ],
812
- 'DOLLAR' => [ 13, -1, 1, 0 ],
813
- 'FIXED' => [ 14, -1, 1, 0 ],
814
- 'SIN' => [ 15, 1, 1, 0 ],
815
- 'COS' => [ 16, 1, 1, 0 ],
816
- 'TAN' => [ 17, 1, 1, 0 ],
817
- 'ATAN' => [ 18, 1, 1, 0 ],
818
- 'PI' => [ 19, 0, 1, 0 ],
819
- 'SQRT' => [ 20, 1, 1, 0 ],
820
- 'EXP' => [ 21, 1, 1, 0 ],
821
- 'LN' => [ 22, 1, 1, 0 ],
822
- 'LOG10' => [ 23, 1, 1, 0 ],
823
- 'ABS' => [ 24, 1, 1, 0 ],
824
- 'INT' => [ 25, 1, 1, 0 ],
825
- 'SIGN' => [ 26, 1, 1, 0 ],
826
- 'ROUND' => [ 27, 2, 1, 0 ],
827
- 'LOOKUP' => [ 28, -1, 0, 0 ],
828
- 'INDEX' => [ 29, -1, 0, 1 ],
829
- 'REPT' => [ 30, 2, 1, 0 ],
830
- 'MID' => [ 31, 3, 1, 0 ],
831
- 'LEN' => [ 32, 1, 1, 0 ],
832
- 'VALUE' => [ 33, 1, 1, 0 ],
833
- 'TRUE' => [ 34, 0, 1, 0 ],
834
- 'FALSE' => [ 35, 0, 1, 0 ],
835
- 'AND' => [ 36, -1, 1, 0 ],
836
- 'OR' => [ 37, -1, 1, 0 ],
837
- 'NOT' => [ 38, 1, 1, 0 ],
838
- 'MOD' => [ 39, 2, 1, 0 ],
839
- 'DCOUNT' => [ 40, 3, 0, 0 ],
840
- 'DSUM' => [ 41, 3, 0, 0 ],
841
- 'DAVERAGE' => [ 42, 3, 0, 0 ],
842
- 'DMIN' => [ 43, 3, 0, 0 ],
843
- 'DMAX' => [ 44, 3, 0, 0 ],
844
- 'DSTDEV' => [ 45, 3, 0, 0 ],
845
- 'VAR' => [ 46, -1, 0, 0 ],
846
- 'DVAR' => [ 47, 3, 0, 0 ],
847
- 'TEXT' => [ 48, 2, 1, 0 ],
848
- 'LINEST' => [ 49, -1, 0, 0 ],
849
- 'TREND' => [ 50, -1, 0, 0 ],
850
- 'LOGEST' => [ 51, -1, 0, 0 ],
851
- 'GROWTH' => [ 52, -1, 0, 0 ],
852
- 'PV' => [ 56, -1, 1, 0 ],
853
- 'FV' => [ 57, -1, 1, 0 ],
854
- 'NPER' => [ 58, -1, 1, 0 ],
855
- 'PMT' => [ 59, -1, 1, 0 ],
856
- 'RATE' => [ 60, -1, 1, 0 ],
857
- 'MIRR' => [ 61, 3, 0, 0 ],
858
- 'IRR' => [ 62, -1, 0, 0 ],
859
- 'RAND' => [ 63, 0, 1, 1 ],
860
- 'MATCH' => [ 64, -1, 0, 0 ],
861
- 'DATE' => [ 65, 3, 1, 0 ],
862
- 'TIME' => [ 66, 3, 1, 0 ],
863
- 'DAY' => [ 67, 1, 1, 0 ],
864
- 'MONTH' => [ 68, 1, 1, 0 ],
865
- 'YEAR' => [ 69, 1, 1, 0 ],
866
- 'WEEKDAY' => [ 70, -1, 1, 0 ],
867
- 'HOUR' => [ 71, 1, 1, 0 ],
868
- 'MINUTE' => [ 72, 1, 1, 0 ],
869
- 'SECOND' => [ 73, 1, 1, 0 ],
870
- 'NOW' => [ 74, 0, 1, 1 ],
871
- 'AREAS' => [ 75, 1, 0, 1 ],
872
- 'ROWS' => [ 76, 1, 0, 1 ],
873
- 'COLUMNS' => [ 77, 1, 0, 1 ],
874
- 'OFFSET' => [ 78, -1, 0, 1 ],
875
- 'SEARCH' => [ 82, -1, 1, 0 ],
876
- 'TRANSPOSE' => [ 83, 1, 1, 0 ],
877
- 'TYPE' => [ 86, 1, 1, 0 ],
878
- 'ATAN2' => [ 97, 2, 1, 0 ],
879
- 'ASIN' => [ 98, 1, 1, 0 ],
880
- 'ACOS' => [ 99, 1, 1, 0 ],
881
- 'CHOOSE' => [ 100, -1, 1, 0 ],
882
- 'HLOOKUP' => [ 101, -1, 0, 0 ],
883
- 'VLOOKUP' => [ 102, -1, 0, 0 ],
884
- 'ISREF' => [ 105, 1, 0, 0 ],
885
- 'LOG' => [ 109, -1, 1, 0 ],
886
- 'CHAR' => [ 111, 1, 1, 0 ],
887
- 'LOWER' => [ 112, 1, 1, 0 ],
888
- 'UPPER' => [ 113, 1, 1, 0 ],
889
- 'PROPER' => [ 114, 1, 1, 0 ],
890
- 'LEFT' => [ 115, -1, 1, 0 ],
891
- 'RIGHT' => [ 116, -1, 1, 0 ],
892
- 'EXACT' => [ 117, 2, 1, 0 ],
893
- 'TRIM' => [ 118, 1, 1, 0 ],
894
- 'REPLACE' => [ 119, 4, 1, 0 ],
895
- 'SUBSTITUTE' => [ 120, -1, 1, 0 ],
896
- 'CODE' => [ 121, 1, 1, 0 ],
897
- 'FIND' => [ 124, -1, 1, 0 ],
898
- 'CELL' => [ 125, -1, 0, 1 ],
899
- 'ISERR' => [ 126, 1, 1, 0 ],
900
- 'ISTEXT' => [ 127, 1, 1, 0 ],
901
- 'ISNUMBER' => [ 128, 1, 1, 0 ],
902
- 'ISBLANK' => [ 129, 1, 1, 0 ],
903
- 'T' => [ 130, 1, 0, 0 ],
904
- 'N' => [ 131, 1, 0, 0 ],
905
- 'DATEVALUE' => [ 140, 1, 1, 0 ],
906
- 'TIMEVALUE' => [ 141, 1, 1, 0 ],
907
- 'SLN' => [ 142, 3, 1, 0 ],
908
- 'SYD' => [ 143, 4, 1, 0 ],
909
- 'DDB' => [ 144, -1, 1, 0 ],
910
- 'INDIRECT' => [ 148, -1, 1, 1 ],
911
- 'CALL' => [ 150, -1, 1, 0 ],
912
- 'CLEAN' => [ 162, 1, 1, 0 ],
913
- 'MDETERM' => [ 163, 1, 2, 0 ],
914
- 'MINVERSE' => [ 164, 1, 2, 0 ],
915
- 'MMULT' => [ 165, 2, 2, 0 ],
916
- 'IPMT' => [ 167, -1, 1, 0 ],
917
- 'PPMT' => [ 168, -1, 1, 0 ],
918
- 'COUNTA' => [ 169, -1, 0, 0 ],
919
- 'PRODUCT' => [ 183, -1, 0, 0 ],
920
- 'FACT' => [ 184, 1, 1, 0 ],
921
- 'DPRODUCT' => [ 189, 3, 0, 0 ],
922
- 'ISNONTEXT' => [ 190, 1, 1, 0 ],
923
- 'STDEVP' => [ 193, -1, 0, 0 ],
924
- 'VARP' => [ 194, -1, 0, 0 ],
925
- 'DSTDEVP' => [ 195, 3, 0, 0 ],
926
- 'DVARP' => [ 196, 3, 0, 0 ],
927
- 'TRUNC' => [ 197, -1, 1, 0 ],
928
- 'ISLOGICAL' => [ 198, 1, 1, 0 ],
929
- 'DCOUNTA' => [ 199, 3, 0, 0 ],
930
- 'ROUNDUP' => [ 212, 2, 1, 0 ],
931
- 'ROUNDDOWN' => [ 213, 2, 1, 0 ],
932
- 'RANK' => [ 216, -1, 0, 0 ],
933
- 'ADDRESS' => [ 219, -1, 1, 0 ],
934
- 'DAYS360' => [ 220, -1, 1, 0 ],
935
- 'TODAY' => [ 221, 0, 1, 1 ],
936
- 'VDB' => [ 222, -1, 1, 0 ],
937
- 'MEDIAN' => [ 227, -1, 0, 0 ],
938
- 'SUMPRODUCT' => [ 228, -1, 2, 0 ],
939
- 'SINH' => [ 229, 1, 1, 0 ],
940
- 'COSH' => [ 230, 1, 1, 0 ],
941
- 'TANH' => [ 231, 1, 1, 0 ],
942
- 'ASINH' => [ 232, 1, 1, 0 ],
943
- 'ACOSH' => [ 233, 1, 1, 0 ],
944
- 'ATANH' => [ 234, 1, 1, 0 ],
945
- 'DGET' => [ 235, 3, 0, 0 ],
946
- 'INFO' => [ 244, 1, 1, 1 ],
947
- 'DB' => [ 247, -1, 1, 0 ],
948
- 'FREQUENCY' => [ 252, 2, 0, 0 ],
949
- 'ERROR.TYPE' => [ 261, 1, 1, 0 ],
950
- 'REGISTER.ID' => [ 267, -1, 1, 0 ],
951
- 'AVEDEV' => [ 269, -1, 0, 0 ],
952
- 'BETADIST' => [ 270, -1, 1, 0 ],
953
- 'GAMMALN' => [ 271, 1, 1, 0 ],
954
- 'BETAINV' => [ 272, -1, 1, 0 ],
955
- 'BINOMDIST' => [ 273, 4, 1, 0 ],
956
- 'CHIDIST' => [ 274, 2, 1, 0 ],
957
- 'CHIINV' => [ 275, 2, 1, 0 ],
958
- 'COMBIN' => [ 276, 2, 1, 0 ],
959
- 'CONFIDENCE' => [ 277, 3, 1, 0 ],
960
- 'CRITBINOM' => [ 278, 3, 1, 0 ],
961
- 'EVEN' => [ 279, 1, 1, 0 ],
962
- 'EXPONDIST' => [ 280, 3, 1, 0 ],
963
- 'FDIST' => [ 281, 3, 1, 0 ],
964
- 'FINV' => [ 282, 3, 1, 0 ],
965
- 'FISHER' => [ 283, 1, 1, 0 ],
966
- 'FISHERINV' => [ 284, 1, 1, 0 ],
967
- 'FLOOR' => [ 285, 2, 1, 0 ],
968
- 'GAMMADIST' => [ 286, 4, 1, 0 ],
969
- 'GAMMAINV' => [ 287, 3, 1, 0 ],
970
- 'CEILING' => [ 288, 2, 1, 0 ],
971
- 'HYPGEOMDIST' => [ 289, 4, 1, 0 ],
972
- 'LOGNORMDIST' => [ 290, 3, 1, 0 ],
973
- 'LOGINV' => [ 291, 3, 1, 0 ],
974
- 'NEGBINOMDIST' => [ 292, 3, 1, 0 ],
975
- 'NORMDIST' => [ 293, 4, 1, 0 ],
976
- 'NORMSDIST' => [ 294, 1, 1, 0 ],
977
- 'NORMINV' => [ 295, 3, 1, 0 ],
978
- 'NORMSINV' => [ 296, 1, 1, 0 ],
979
- 'STANDARDIZE' => [ 297, 3, 1, 0 ],
980
- 'ODD' => [ 298, 1, 1, 0 ],
981
- 'PERMUT' => [ 299, 2, 1, 0 ],
982
- 'POISSON' => [ 300, 3, 1, 0 ],
983
- 'TDIST' => [ 301, 3, 1, 0 ],
984
- 'WEIBULL' => [ 302, 4, 1, 0 ],
985
- 'SUMXMY2' => [ 303, 2, 2, 0 ],
986
- 'SUMX2MY2' => [ 304, 2, 2, 0 ],
987
- 'SUMX2PY2' => [ 305, 2, 2, 0 ],
988
- 'CHITEST' => [ 306, 2, 2, 0 ],
989
- 'CORREL' => [ 307, 2, 2, 0 ],
990
- 'COVAR' => [ 308, 2, 2, 0 ],
991
- 'FORECAST' => [ 309, 3, 2, 0 ],
992
- 'FTEST' => [ 310, 2, 2, 0 ],
993
- 'INTERCEPT' => [ 311, 2, 2, 0 ],
994
- 'PEARSON' => [ 312, 2, 2, 0 ],
995
- 'RSQ' => [ 313, 2, 2, 0 ],
996
- 'STEYX' => [ 314, 2, 2, 0 ],
997
- 'SLOPE' => [ 315, 2, 2, 0 ],
998
- 'TTEST' => [ 316, 4, 2, 0 ],
999
- 'PROB' => [ 317, -1, 2, 0 ],
1000
- 'DEVSQ' => [ 318, -1, 0, 0 ],
1001
- 'GEOMEAN' => [ 319, -1, 0, 0 ],
1002
- 'HARMEAN' => [ 320, -1, 0, 0 ],
1003
- 'SUMSQ' => [ 321, -1, 0, 0 ],
1004
- 'KURT' => [ 322, -1, 0, 0 ],
1005
- 'SKEW' => [ 323, -1, 0, 0 ],
1006
- 'ZTEST' => [ 324, -1, 0, 0 ],
1007
- 'LARGE' => [ 325, 2, 0, 0 ],
1008
- 'SMALL' => [ 326, 2, 0, 0 ],
1009
- 'QUARTILE' => [ 327, 2, 0, 0 ],
1010
- 'PERCENTILE' => [ 328, 2, 0, 0 ],
1011
- 'PERCENTRANK' => [ 329, -1, 0, 0 ],
1012
- 'MODE' => [ 330, -1, 2, 0 ],
1013
- 'TRIMMEAN' => [ 331, 2, 0, 0 ],
1014
- 'TINV' => [ 332, 2, 1, 0 ],
1015
- 'CONCATENATE' => [ 336, -1, 1, 0 ],
1016
- 'POWER' => [ 337, 2, 1, 0 ],
1017
- 'RADIANS' => [ 342, 1, 1, 0 ],
1018
- 'DEGREES' => [ 343, 1, 1, 0 ],
1019
- 'SUBTOTAL' => [ 344, -1, 0, 0 ],
1020
- 'SUMIF' => [ 345, -1, 0, 0 ],
1021
- 'COUNTIF' => [ 346, 2, 0, 0 ],
1022
- 'COUNTBLANK' => [ 347, 1, 0, 0 ],
1023
- 'ROMAN' => [ 354, -1, 1, 0 ]
1024
- }
1025
-
1026
- end
1027
- end
1028
-
1029
- if $0 ==__FILE__
1030
-
1031
-
1032
- parser = Formula.new
1033
- puts
1034
- puts 'type "Q" to quit.'
1035
- puts
1036
- while true
1037
- puts
1038
- print '? '
1039
- str = gets.chop!
1040
- break if /q/i =~ str
1041
- begin
1042
- e = parser.parse(str)
1043
- p parser.reverse(e)
1044
- rescue ParseError
1045
- puts $!
1046
- end
1047
- end
1048
-
1049
- end # class Formula
1050
-
1051
- end # module Writeexcel
1
+ # -*- coding: utf-8 -*-
2
+ ###############################################################################
3
+ #
4
+ # Formula - A class for generating Excel formulas.
5
+ #
6
+ #
7
+ # Used in conjunction with WriteExcel
8
+ #
9
+ # Copyright 2000-2010, John McNamara, jmcnamara@cpan.org
10
+ #
11
+ # original written in Perl by John McNamara
12
+ # converted to Ruby by Hideo Nakamura, cxn03651@msj.biglobe.ne.jp
13
+ #
14
+ require 'strscan'
15
+ require 'writeexcel/excelformulaparser'
16
+
17
+ module Writeexcel
18
+
19
+ class Formula < ExcelFormulaParser #:nodoc:
20
+ require 'writeexcel/helper'
21
+ private :convert_to_ascii_if_ascii
22
+
23
+ attr_accessor :byte_order, :workbook, :ext_sheets, :ext_refs, :ext_ref_count
24
+
25
+ def initialize(byte_order)
26
+ @byte_order = byte_order
27
+ @workbook = ""
28
+ @ext_sheets = {}
29
+ @ext_refs = {}
30
+ @ext_ref_count = 0
31
+ @ext_names = {}
32
+ initialize_hashes
33
+ end
34
+
35
+ ###############################################################################
36
+ #
37
+ # parse_formula()
38
+ #
39
+ # Takes a textual description of a formula and returns a RPN encoded byte
40
+ # string.
41
+ #
42
+ def parse_formula(formula, byte_stream = false)
43
+ # Build the parse tree for the formula
44
+ tokens = reverse(parse(formula))
45
+
46
+ # Add a volatile token if the formula contains a volatile function.
47
+ # This must be the first token in the list
48
+ #
49
+ tokens.unshift('_vol') if check_volatile(tokens) != 0
50
+
51
+ # The return value depends on which Worksheet.pm method is the caller
52
+ unless byte_stream
53
+ # Parse formula to see if it throws any errors and then
54
+ # return raw tokens to Worksheet::store_formula()
55
+ #
56
+ parse_tokens(tokens)
57
+ tokens
58
+ else
59
+ # Return byte stream to Worksheet::write_formula()
60
+ parse_tokens(tokens)
61
+ end
62
+ end
63
+
64
+ ###############################################################################
65
+ #
66
+ # parse_tokens()
67
+ #
68
+ # Convert each token or token pair to its Excel 'ptg' equivalent.
69
+ #
70
+ def parse_tokens(tokens)
71
+ parse_str = ''
72
+ last_type = ''
73
+ modifier = ''
74
+ num_args = 0
75
+ _class = 0
76
+ _classary = [1]
77
+ args = tokens.dup
78
+ # A note about the class modifiers used below. In general the class,
79
+ # "reference" or "value", of a function is applied to all of its operands.
80
+ # However, in certain circumstances the operands can have mixed classes,
81
+ # e.g. =VLOOKUP with external references. These will eventually be dealt
82
+ # with by the parser. However, as a workaround the class type of a token
83
+ # can be changed via the repeat_formula interface. Thus, a _ref2d token can
84
+ # be changed by the user to _ref2dA or _ref2dR to change its token class.
85
+ #
86
+ while (!args.empty?)
87
+ token = args.shift
88
+
89
+ if (token == '_arg')
90
+ num_args = args.shift
91
+ elsif (token == '_class')
92
+ token = args.shift
93
+ _class = @functions[token][2]
94
+ # If _class is undef then it means that the function isn't valid.
95
+ exit "Unknown function #{token}() in formula\n" if _class.nil?
96
+ _classary.push(_class)
97
+ elsif (token == '_vol')
98
+ parse_str += convert_volatile()
99
+ elsif (token == 'ptgBool')
100
+ token = args.shift
101
+ parse_str += convert_bool(token)
102
+ elsif (token == '_num')
103
+ token = args.shift
104
+ parse_str += convert_number(token)
105
+ elsif (token == '_str')
106
+ token = args.shift
107
+ parse_str += convert_string(token)
108
+ elsif (token =~ /^_ref2d/)
109
+ modifier = token.sub(/_ref2d/, '')
110
+ _class = _classary[-1]
111
+ _class = 0 if modifier == 'R'
112
+ _class = 1 if modifier == 'V'
113
+ token = args.shift
114
+ parse_str += convert_ref2d(token, _class)
115
+ elsif (token =~ /^_ref3d/)
116
+ modifier = token.sub(/_ref3d/,'')
117
+ _class = _classary[-1]
118
+ _class = 0 if modifier == 'R'
119
+ _class = 1 if modifier == 'V'
120
+ token = args.shift
121
+ parse_str += convert_ref3d(token, _class)
122
+ elsif (token =~ /^_range2d/)
123
+ modifier = token.sub(/_range2d/,'')
124
+ _class = _classary[-1]
125
+ _class = 0 if modifier == 'R'
126
+ _class = 1 if modifier == 'V'
127
+ token = args.shift
128
+ parse_str += convert_range2d(token, _class)
129
+ elsif (token =~ /^_range3d/)
130
+ modifier = token.sub(/_range3d/,'')
131
+ _class = _classary[-1]
132
+ _class = 0 if modifier == 'R'
133
+ _class = 1 if modifier == 'V'
134
+ token = args.shift
135
+ parse_str += convert_range3d(token, _class)
136
+ elsif (token =~ /^_name/)
137
+ modifier = token.sub(/_name/, '')
138
+ _class = _classary[-1]
139
+ _class = 0 if modifier == 'R'
140
+ _class = 1 if modifier == 'V'
141
+ token = args.shift
142
+ parse_str += convert_name(token, _class)
143
+ elsif (token == '_func')
144
+ token = args.shift
145
+ parse_str += convert_function(token, num_args.to_i)
146
+ _classary.pop
147
+ num_args = 0 # Reset after use
148
+ elsif @ptg[token]
149
+ parse_str += [@ptg[token]].pack("C")
150
+ else
151
+ # Unrecognised token
152
+ return nil
153
+ end
154
+ end
155
+
156
+ parse_str
157
+ end
158
+
159
+ def scan(formula)
160
+ s = StringScanner.new(formula)
161
+ q = []
162
+ until s.eos?
163
+ # order is important.
164
+ if s.scan(/(?=\d|\.\d)\d*(\.\d*)?([Ee]([+-]?\d+))?/)
165
+ q.push [:NUMBER, s.matched]
166
+ elsif s.scan(/"([^"]|"")*"/)
167
+ q.push [:STRING, s.matched]
168
+ elsif s.scan(/\$?[A-I]?[A-Z]\$?(\d+)?:\$?[A-I]?[A-Z]\$?(\d+)?/)
169
+ q.push [:RANGE2D , s.matched]
170
+ elsif s.scan(/[^!(,]+!\$?[A-I]?[A-Z]\$?(\d+)?:\$?[A-I]?[A-Z]\$?(\d+)?/)
171
+ q.push [:RANGE3D , s.matched]
172
+ elsif s.scan(/'[^']+'!\$?[A-I]?[A-Z]\$?(\d+)?:\$?[A-I]?[A-Z]\$?(\d+)?/)
173
+ q.push [:RANGE3D , s.matched]
174
+ elsif s.scan(/\$?[A-I]?[A-Z]\$?\d+/)
175
+ q.push [:REF2D, s.matched]
176
+ elsif s.scan(/[^!(,]+!\$?[A-I]?[A-Z]\$?\d+/)
177
+ q.push [:REF3D , s.matched]
178
+ elsif s.scan(/'[^']+'!\$?[A-I]?[A-Z]\$?\d+/)
179
+ q.push [:REF3D , s.matched]
180
+ elsif s.scan(/<=/)
181
+ q.push [:LE , s.matched]
182
+ elsif s.scan(/>=/)
183
+ q.push [:GE , s.matched]
184
+ elsif s.scan(/<>/)
185
+ q.push [:NE , s.matched]
186
+ elsif s.scan(/</)
187
+ q.push [:LT , s.matched]
188
+ elsif s.scan(/>/)
189
+ q.push [:GT , s.matched]
190
+ elsif s.scan(/[A-Z0-9_.]+\(/)
191
+ s.unscan
192
+ s.scan(/[A-Z0-9_.]+/)
193
+ q.push [:FUNC, s.matched]
194
+ elsif s.scan(/TRUE/)
195
+ q.push [:TRUE, s.matched]
196
+ elsif s.scan(/FALSE/)
197
+ q.push [:FALSE, s.matched]
198
+ elsif s.scan(/[A-Za-z_]\w+/)
199
+ q.push [:NAME , s.matched]
200
+ elsif s.scan(/\s+/)
201
+ ;
202
+ elsif s.scan(/./)
203
+ q.push [s.matched, s.matched]
204
+ end
205
+ end
206
+ q.push [:EOL, nil]
207
+ end
208
+
209
+ def parse(formula)
210
+ @q = scan(formula)
211
+ @q.push [false, nil]
212
+ @yydebug=true
213
+ do_parse
214
+ end
215
+
216
+ def next_token
217
+ @q.shift
218
+ end
219
+
220
+ def reverse(expression)
221
+ expression.flatten
222
+ end
223
+
224
+ ###############################################################################
225
+
226
+ private
227
+
228
+ ###############################################################################
229
+
230
+
231
+ ###############################################################################
232
+ #
233
+ # _check_volatile()
234
+ #
235
+ # Check if the formula contains a volatile function, i.e. a function that must
236
+ # be recalculated each time a cell is updated. These formulas require a ptgAttr
237
+ # with the volatile flag set as the first token in the parsed expression.
238
+ #
239
+ # Examples of volatile functions: RAND(), NOW(), TODAY()
240
+ #
241
+ def check_volatile(tokens)
242
+ volatile = 0
243
+
244
+ (0..tokens.size - 1).each do |i|
245
+ # If the next token is a function check if it is volatile.
246
+ if tokens[i] == '_func' and @functions[tokens[i+1]][3] != 0
247
+ volatile = 1
248
+ break
249
+ end
250
+ end
251
+
252
+ volatile
253
+ end
254
+
255
+ ###############################################################################
256
+ #
257
+ # _convert_volatile()
258
+ #
259
+ # Convert _vol to a ptgAttr tag formatted to indicate that the formula contains
260
+ # a volatile function. See _check_volatile()
261
+ #
262
+ def convert_volatile
263
+ # Set bitFattrSemi flag to indicate volatile function, "w" is set to zero.
264
+ return [@ptg['ptgAttr'], 0x1, 0x0].pack("CCv")
265
+ end
266
+
267
+ ###############################################################################
268
+ #
269
+ # _convert_bool()
270
+ #
271
+ # Convert a boolean token to ptgBool
272
+ #
273
+ def convert_bool(bool)
274
+ [@ptg['ptgBool'], bool.to_i].pack("CC")
275
+ end
276
+
277
+
278
+ ###############################################################################
279
+ #
280
+ # _convert_number()
281
+ #
282
+ # Convert a number token to ptgInt or ptgNum
283
+ #
284
+ def convert_number(num)
285
+ # Integer in the range 0..2**16-1
286
+ if ((num =~ /^\d+$/) && (num.to_i <= 65535))
287
+ return [@ptg['ptgInt'], num.to_i].pack("Cv")
288
+ else # A float
289
+ num = [num.to_f].pack("d")
290
+ num.reverse! if @byte_order != 0 && @byte_order != ''
291
+ return [@ptg['ptgNum']].pack("C") + num
292
+ end
293
+ end
294
+
295
+ ###############################################################################
296
+ #
297
+ # _convert_string()
298
+ #
299
+ # Convert a string to a ptg Str.
300
+ #
301
+ def convert_string(str)
302
+ str = convert_to_ascii_if_ascii(str)
303
+
304
+ encoding = 0
305
+
306
+ str.sub!(/^"/,'') # Remove leading "
307
+ str.sub!(/"$/,'') # Remove trailing "
308
+ str.gsub!(/""/,'"') # Substitute Excel's escaped double quote "" for "
309
+
310
+ # number of characters in str
311
+ length = ruby_18 { str.gsub(/[^\WA-Za-z_\d]/, ' ').length } ||
312
+ ruby_19 { str.length }
313
+
314
+ # Handle utf8 strings
315
+ if str.encoding == Encoding::UTF_8
316
+ str = utf8_to_16le(str)
317
+ str.force_encoding('BINARY')
318
+ encoding = 1
319
+ end
320
+
321
+ exit "String in formula has more than 255 chars\n" if length > 255
322
+
323
+ [@ptg['ptgStr'], length, encoding].pack("CCC") + str
324
+ end
325
+
326
+ ###############################################################################
327
+ #
328
+ # _convert_ref2d()
329
+ #
330
+ # Convert an Excel reference such as A1, $B2, C$3 or $D$4 to a ptgRefV.
331
+ #
332
+ def convert_ref2d(cell, _class)
333
+ # Convert the cell reference
334
+ row, col = cell_to_packed_rowcol(cell)
335
+
336
+ # The ptg value depends on the class of the ptg.
337
+ if (_class == 0)
338
+ ptgref = [@ptg['ptgRef']].pack("C")
339
+ elsif (_class == 1)
340
+ ptgref = [@ptg['ptgRefV']].pack("C")
341
+ elsif (_class == 2)
342
+ ptgref = [@ptg['ptgRefA']].pack("C")
343
+ else
344
+ exit "Unknown function class in formula\n"
345
+ end
346
+
347
+ ptgref + row + col
348
+ end
349
+
350
+ ###############################################################################
351
+ #
352
+ # _convert_ref3d
353
+ #
354
+ # Convert an Excel 3d reference such as "Sheet1!A1" or "Sheet1:Sheet2!A1" to a
355
+ # ptgRef3dV.
356
+ #
357
+ def convert_ref3d(token, _class)
358
+ # Split the ref at the ! symbol
359
+ ext_ref, cell = token.split('!')
360
+
361
+ # Convert the external reference part
362
+ ext_ref = pack_ext_ref(ext_ref)
363
+
364
+ # Convert the cell reference part
365
+ row, col = cell_to_packed_rowcol(cell)
366
+
367
+ # The ptg value depends on the class of the ptg.
368
+ if (_class == 0)
369
+ ptgref = [@ptg['ptgRef3d']].pack("C")
370
+ elsif (_class == 1)
371
+ ptgref = [@ptg['ptgRef3dV']].pack("C")
372
+ elsif (_class == 2)
373
+ ptgref = [@ptg['ptgRef3dA']].pack("C")
374
+ else
375
+ exit "Unknown function class in formula\n"
376
+ end
377
+
378
+ ptgref + ext_ref + row + col
379
+ end
380
+
381
+ ###############################################################################
382
+ #
383
+ # _convert_range2d()
384
+ #
385
+ # Convert an Excel range such as A1:D4 or A:D to a ptgRefV.
386
+ #
387
+ def convert_range2d(range, _class)
388
+ # Split the range into 2 cell refs
389
+ cell1, cell2 = range.split(':')
390
+
391
+ # A range such as A:D is equivalent to A1:D65536, so add rows as required
392
+ cell1 += '1' unless cell1 =~ /\d/
393
+ cell2 += '65536' unless cell2 =~ /\d/
394
+
395
+ # Convert the cell references
396
+ row1, col1 = cell_to_packed_rowcol(cell1)
397
+ row2, col2 = cell_to_packed_rowcol(cell2)
398
+
399
+ # The ptg value depends on the class of the ptg.
400
+ if (_class == 0)
401
+ ptgarea = [@ptg['ptgArea']].pack("C")
402
+ elsif (_class == 1)
403
+ ptgarea = [@ptg['ptgAreaV']].pack("C")
404
+ elsif (_class == 2)
405
+ ptgarea = [@ptg['ptgAreaA']].pack("C")
406
+ else
407
+ exit "Unknown function class in formula\n"
408
+ end
409
+
410
+ ptgarea + row1 + row2 + col1 + col2
411
+ end
412
+
413
+ ###############################################################################
414
+ #
415
+ # _convert_range3d
416
+ #
417
+ # Convert an Excel 3d range such as "Sheet1!A1:D4" or "Sheet1:Sheet2!A1:D4" to
418
+ # a ptgArea3dV.
419
+ #
420
+ def convert_range3d(token, _class)
421
+ # Split the ref at the ! symbol
422
+ ext_ref, range = token.split('!')
423
+
424
+ # Convert the external reference part
425
+ ext_ref = pack_ext_ref(ext_ref)
426
+
427
+ # Split the range into 2 cell refs
428
+ cell1, cell2 = range.split(':')
429
+
430
+ # A range such as A:D is equivalent to A1:D65536, so add rows as required
431
+ cell1 += '1' unless cell1 =~ /\d/
432
+ cell2 += '65536' unless cell2 =~ /\d/
433
+
434
+ # Convert the cell references
435
+ row1, col1 = cell_to_packed_rowcol(cell1)
436
+ row2, col2 = cell_to_packed_rowcol(cell2)
437
+
438
+ # The ptg value depends on the class of the ptg.
439
+ if (_class == 0)
440
+ ptgarea = [@ptg['ptgArea3d']].pack("C")
441
+ elsif (_class == 1)
442
+ ptgarea = [@ptg['ptgArea3dV']].pack("C")
443
+ elsif (_class == 2)
444
+ ptgarea = [@ptg['ptgArea3dA']].pack("C")
445
+ else
446
+ exit "Unknown function class in formula\n"
447
+ end
448
+
449
+ ptgarea + ext_ref + row1 + row2 + col1+ col2
450
+ end
451
+
452
+ ###############################################################################
453
+ #
454
+ # _pack_ext_ref()
455
+ #
456
+ # Convert the sheet name part of an external reference, for example "Sheet1" or
457
+ # "Sheet1:Sheet2", to a packed structure.
458
+ #
459
+ def pack_ext_ref(ext_ref)
460
+ ext_ref.sub!(/^'/,'') # Remove leading ' if any.
461
+ ext_ref.sub!(/'$/,'') # Remove trailing ' if any.
462
+
463
+ # Check if there is a sheet range eg., Sheet1:Sheet2.
464
+ if (ext_ref =~ /:/)
465
+ sheet1, sheet2 = ext_ref.split(':')
466
+
467
+ sheet1 = get_sheet_index(sheet1)
468
+ sheet2 = get_sheet_index(sheet2)
469
+
470
+ # Reverse max and min sheet numbers if necessary
471
+ if (sheet1 > sheet2)
472
+ sheet1, sheet2 = [sheet2, sheet1]
473
+ end
474
+ else
475
+ # Single sheet name only.
476
+ sheet1, sheet2 = [ext_ref, ext_ref]
477
+
478
+ sheet1 = get_sheet_index(sheet1)
479
+ sheet2 = sheet1
480
+ end
481
+
482
+ key = "#{sheet1}:#{sheet2}"
483
+
484
+ if @ext_refs[key].nil?
485
+ index = get_ext_ref_count
486
+ @ext_refs[key] = index
487
+ @ext_ref_count += 1
488
+ else
489
+ index = @ext_refs[key]
490
+ end
491
+
492
+ [index].pack("v")
493
+ end
494
+
495
+ ###############################################################################
496
+ #
497
+ # _get_sheet_index()
498
+ #
499
+ # Look up the index that corresponds to an external sheet name. The hash of
500
+ # sheet names is updated by the add_worksheet() method of the Workbook class.
501
+ #
502
+ def get_sheet_index(sheet_name)
503
+ sheet_name = convert_to_ascii_if_ascii(sheet_name)
504
+
505
+ # Handle utf8 sheetnames
506
+ if sheet_name.encoding == Encoding::UTF_8
507
+ sheet_name = sheet_name.encode('UTF-16BE')
508
+ end
509
+
510
+ if @ext_sheets[sheet_name].nil?
511
+ raise "Unknown sheet name '#{sheet_name}' in formula\n"
512
+ else
513
+ return @ext_sheets[sheet_name]
514
+ end
515
+ end
516
+ public :get_sheet_index
517
+
518
+ ###############################################################################
519
+ #
520
+ # set_ext_sheets()
521
+ #
522
+ # This semi-public method is used to update the hash of sheet names. It is
523
+ # updated by the add_worksheet() method of the Workbook class.
524
+ #
525
+ def set_ext_sheets(worksheet, index)
526
+ # The _ext_sheets hash is used to translate between worksheet names
527
+ # and their index
528
+ @ext_sheets[worksheet] = index
529
+ end
530
+ public :set_ext_sheets
531
+
532
+ ###############################################################################
533
+ #
534
+ # get_ext_sheets()
535
+ #
536
+ # This semi-public method is used to get the worksheet references that were
537
+ # used in formulas for inclusion in the EXTERNSHEET Workbook record.
538
+ #
539
+ def get_ext_sheets
540
+ @ext_refs
541
+ end
542
+ public :get_ext_sheets
543
+
544
+ ###############################################################################
545
+ #
546
+ # get_ext_ref_count()
547
+ #
548
+ # TODO This semi-public method is used to update the hash of sheet names. It is
549
+ # updated by the add_worksheet() method of the Workbook class.
550
+ #
551
+ def get_ext_ref_count
552
+ @ext_ref_count
553
+ end
554
+
555
+ ###############################################################################
556
+ #
557
+ # _get_name_index()
558
+ #
559
+ # Look up the index that corresponds to an external defined name. The hash of
560
+ # defined names is updated by the define_name() method in the Workbook class.
561
+ #
562
+ def get_name_index(name)
563
+ if @ext_names.has_key?(name)
564
+ @ext_names[name]
565
+ else
566
+ raise "Unknown defined name #{name} in formula\n"
567
+ end
568
+ end
569
+ private :get_name_index
570
+
571
+ ###############################################################################
572
+ #
573
+ # set_ext_name()
574
+ #
575
+ # This semi-public method is used to update the hash of defined names.
576
+ #
577
+ def set_ext_name(name, index)
578
+ @ext_names[name] = index
579
+ end
580
+ public :set_ext_name
581
+
582
+ ###############################################################################
583
+ #
584
+ # _convert_function()
585
+ #
586
+ # Convert a function to a ptgFunc or ptgFuncVarV depending on the number of
587
+ # args that it takes.
588
+ #
589
+ def convert_function(token, num_args)
590
+ exit "Unknown function #{token}() in formula\n" if @functions[token][0].nil?
591
+
592
+ args = @functions[token][1]
593
+
594
+ # Fixed number of args eg. TIME($i,$j,$k).
595
+ if (args >= 0)
596
+ # Check that the number of args is valid.
597
+ if (args != num_args)
598
+ raise "Incorrect number of arguments for #{token}() in formula\n";
599
+ else
600
+ return [@ptg['ptgFuncV'], @functions[token][0]].pack("Cv")
601
+ end
602
+ end
603
+
604
+ # Variable number of args eg. SUM(i,j,k, ..).
605
+ if (args == -1)
606
+ return [@ptg['ptgFuncVarV'], num_args, @functions[token][0]].pack("CCv")
607
+ end
608
+ end
609
+
610
+ ###############################################################################
611
+ #
612
+ # _convert_name()
613
+ #
614
+ # Convert a symbolic name into a name reference.
615
+ #
616
+ def convert_name(name, _class)
617
+ name_index = get_name_index(name)
618
+
619
+ # The ptg value depends on the class of the ptg.
620
+ if _class == 0
621
+ ptgName = @ptg['ptgName']
622
+ elsif _class == 1
623
+ ptgName = @ptg['ptgNameV']
624
+ elsif _class == 2
625
+ ptgName = @ptg['ptgNameA']
626
+ end
627
+
628
+ [ptgName, name_index].pack('CV')
629
+ end
630
+ private :convert_name
631
+
632
+ ###############################################################################
633
+ #
634
+ # _cell_to_rowcol($cell_ref)
635
+ #
636
+ # Convert an Excel cell reference such as A1 or $B2 or C$3 or $D$4 to a zero
637
+ # indexed row and column number. Also returns two boolean values to indicate
638
+ # whether the row or column are relative references.
639
+ # TODO use function in Utility.pm
640
+ #
641
+ def cell_to_rowcol(cell)
642
+ cell =~ /(\$?)([A-I]?[A-Z])(\$?)(\d+)/
643
+
644
+ col_rel = $1 == "" ? 1 : 0
645
+ row_rel = $3 == "" ? 1 : 0
646
+ row = $4.to_i
647
+
648
+ col = chars_to_col($2.split(//))
649
+
650
+ # Convert 1-index to zero-index
651
+ row -= 1
652
+ col -= 1
653
+
654
+ [row, col, row_rel, col_rel]
655
+ end
656
+
657
+ ###############################################################################
658
+ #
659
+ # _cell_to_packed_rowcol($row, $col, $row_rel, $col_rel)
660
+ #
661
+ # pack() row and column into the required 3 byte format.
662
+ #
663
+ def cell_to_packed_rowcol(cell)
664
+ row, col, row_rel, col_rel = cell_to_rowcol(cell)
665
+
666
+ exit "Column #{cell} greater than IV in formula\n" if col >= 256
667
+ exit "Row #{cell} greater than 65536 in formula\n" if row >= 65536
668
+
669
+ # Set the high bits to indicate if row or col are relative.
670
+ col |= col_rel << 14
671
+ col |= row_rel << 15
672
+
673
+ row = [row].pack('v')
674
+ col = [col].pack('v')
675
+
676
+ [row, col]
677
+ end
678
+
679
+ ###############################################################################
680
+ #
681
+ # _initialize_hashes()
682
+ #
683
+ def initialize_hashes
684
+
685
+ # The Excel ptg indices
686
+ @ptg = {
687
+ 'ptgExp' => 0x01,
688
+ 'ptgTbl' => 0x02,
689
+ 'ptgAdd' => 0x03,
690
+ 'ptgSub' => 0x04,
691
+ 'ptgMul' => 0x05,
692
+ 'ptgDiv' => 0x06,
693
+ 'ptgPower' => 0x07,
694
+ 'ptgConcat' => 0x08,
695
+ 'ptgLT' => 0x09,
696
+ 'ptgLE' => 0x0A,
697
+ 'ptgEQ' => 0x0B,
698
+ 'ptgGE' => 0x0C,
699
+ 'ptgGT' => 0x0D,
700
+ 'ptgNE' => 0x0E,
701
+ 'ptgIsect' => 0x0F,
702
+ 'ptgUnion' => 0x10,
703
+ 'ptgRange' => 0x11,
704
+ 'ptgUplus' => 0x12,
705
+ 'ptgUminus' => 0x13,
706
+ 'ptgPercent' => 0x14,
707
+ 'ptgParen' => 0x15,
708
+ 'ptgMissArg' => 0x16,
709
+ 'ptgStr' => 0x17,
710
+ 'ptgAttr' => 0x19,
711
+ 'ptgSheet' => 0x1A,
712
+ 'ptgEndSheet' => 0x1B,
713
+ 'ptgErr' => 0x1C,
714
+ 'ptgBool' => 0x1D,
715
+ 'ptgInt' => 0x1E,
716
+ 'ptgNum' => 0x1F,
717
+ 'ptgArray' => 0x20,
718
+ 'ptgFunc' => 0x21,
719
+ 'ptgFuncVar' => 0x22,
720
+ 'ptgName' => 0x23,
721
+ 'ptgRef' => 0x24,
722
+ 'ptgArea' => 0x25,
723
+ 'ptgMemArea' => 0x26,
724
+ 'ptgMemErr' => 0x27,
725
+ 'ptgMemNoMem' => 0x28,
726
+ 'ptgMemFunc' => 0x29,
727
+ 'ptgRefErr' => 0x2A,
728
+ 'ptgAreaErr' => 0x2B,
729
+ 'ptgRefN' => 0x2C,
730
+ 'ptgAreaN' => 0x2D,
731
+ 'ptgMemAreaN' => 0x2E,
732
+ 'ptgMemNoMemN' => 0x2F,
733
+ 'ptgNameX' => 0x39,
734
+ 'ptgRef3d' => 0x3A,
735
+ 'ptgArea3d' => 0x3B,
736
+ 'ptgRefErr3d' => 0x3C,
737
+ 'ptgAreaErr3d' => 0x3D,
738
+ 'ptgArrayV' => 0x40,
739
+ 'ptgFuncV' => 0x41,
740
+ 'ptgFuncVarV' => 0x42,
741
+ 'ptgNameV' => 0x43,
742
+ 'ptgRefV' => 0x44,
743
+ 'ptgAreaV' => 0x45,
744
+ 'ptgMemAreaV' => 0x46,
745
+ 'ptgMemErrV' => 0x47,
746
+ 'ptgMemNoMemV' => 0x48,
747
+ 'ptgMemFuncV' => 0x49,
748
+ 'ptgRefErrV' => 0x4A,
749
+ 'ptgAreaErrV' => 0x4B,
750
+ 'ptgRefNV' => 0x4C,
751
+ 'ptgAreaNV' => 0x4D,
752
+ 'ptgMemAreaNV' => 0x4E,
753
+ 'ptgMemNoMemN' => 0x4F,
754
+ 'ptgFuncCEV' => 0x58,
755
+ 'ptgNameXV' => 0x59,
756
+ 'ptgRef3dV' => 0x5A,
757
+ 'ptgArea3dV' => 0x5B,
758
+ 'ptgRefErr3dV' => 0x5C,
759
+ 'ptgAreaErr3d' => 0x5D,
760
+ 'ptgArrayA' => 0x60,
761
+ 'ptgFuncA' => 0x61,
762
+ 'ptgFuncVarA' => 0x62,
763
+ 'ptgNameA' => 0x63,
764
+ 'ptgRefA' => 0x64,
765
+ 'ptgAreaA' => 0x65,
766
+ 'ptgMemAreaA' => 0x66,
767
+ 'ptgMemErrA' => 0x67,
768
+ 'ptgMemNoMemA' => 0x68,
769
+ 'ptgMemFuncA' => 0x69,
770
+ 'ptgRefErrA' => 0x6A,
771
+ 'ptgAreaErrA' => 0x6B,
772
+ 'ptgRefNA' => 0x6C,
773
+ 'ptgAreaNA' => 0x6D,
774
+ 'ptgMemAreaNA' => 0x6E,
775
+ 'ptgMemNoMemN' => 0x6F,
776
+ 'ptgFuncCEA' => 0x78,
777
+ 'ptgNameXA' => 0x79,
778
+ 'ptgRef3dA' => 0x7A,
779
+ 'ptgArea3dA' => 0x7B,
780
+ 'ptgRefErr3dA' => 0x7C,
781
+ 'ptgAreaErr3d' => 0x7D
782
+ };
783
+
784
+ # Thanks to Michael Meeks and Gnumeric for the initial arg values.
785
+ #
786
+ # The following hash was generated by "function_locale.pl" in the distro.
787
+ # Refer to function_locale.pl for non-English function names.
788
+ #
789
+ # The array elements are as follow:
790
+ # ptg: The Excel function ptg code.
791
+ # args: The number of arguments that the function takes:
792
+ # >=0 is a fixed number of arguments.
793
+ # -1 is a variable number of arguments.
794
+ # class: The reference, value or array class of the function args.
795
+ # vol: The function is volatile.
796
+ #
797
+ @functions = {
798
+ # ptg args class vol
799
+ 'COUNT' => [ 0, -1, 0, 0 ],
800
+ 'IF' => [ 1, -1, 1, 0 ],
801
+ 'ISNA' => [ 2, 1, 1, 0 ],
802
+ 'ISERROR' => [ 3, 1, 1, 0 ],
803
+ 'SUM' => [ 4, -1, 0, 0 ],
804
+ 'AVERAGE' => [ 5, -1, 0, 0 ],
805
+ 'MIN' => [ 6, -1, 0, 0 ],
806
+ 'MAX' => [ 7, -1, 0, 0 ],
807
+ 'ROW' => [ 8, -1, 0, 0 ],
808
+ 'COLUMN' => [ 9, -1, 0, 0 ],
809
+ 'NA' => [ 10, 0, 0, 0 ],
810
+ 'NPV' => [ 11, -1, 1, 0 ],
811
+ 'STDEV' => [ 12, -1, 0, 0 ],
812
+ 'DOLLAR' => [ 13, -1, 1, 0 ],
813
+ 'FIXED' => [ 14, -1, 1, 0 ],
814
+ 'SIN' => [ 15, 1, 1, 0 ],
815
+ 'COS' => [ 16, 1, 1, 0 ],
816
+ 'TAN' => [ 17, 1, 1, 0 ],
817
+ 'ATAN' => [ 18, 1, 1, 0 ],
818
+ 'PI' => [ 19, 0, 1, 0 ],
819
+ 'SQRT' => [ 20, 1, 1, 0 ],
820
+ 'EXP' => [ 21, 1, 1, 0 ],
821
+ 'LN' => [ 22, 1, 1, 0 ],
822
+ 'LOG10' => [ 23, 1, 1, 0 ],
823
+ 'ABS' => [ 24, 1, 1, 0 ],
824
+ 'INT' => [ 25, 1, 1, 0 ],
825
+ 'SIGN' => [ 26, 1, 1, 0 ],
826
+ 'ROUND' => [ 27, 2, 1, 0 ],
827
+ 'LOOKUP' => [ 28, -1, 0, 0 ],
828
+ 'INDEX' => [ 29, -1, 0, 1 ],
829
+ 'REPT' => [ 30, 2, 1, 0 ],
830
+ 'MID' => [ 31, 3, 1, 0 ],
831
+ 'LEN' => [ 32, 1, 1, 0 ],
832
+ 'VALUE' => [ 33, 1, 1, 0 ],
833
+ 'TRUE' => [ 34, 0, 1, 0 ],
834
+ 'FALSE' => [ 35, 0, 1, 0 ],
835
+ 'AND' => [ 36, -1, 1, 0 ],
836
+ 'OR' => [ 37, -1, 1, 0 ],
837
+ 'NOT' => [ 38, 1, 1, 0 ],
838
+ 'MOD' => [ 39, 2, 1, 0 ],
839
+ 'DCOUNT' => [ 40, 3, 0, 0 ],
840
+ 'DSUM' => [ 41, 3, 0, 0 ],
841
+ 'DAVERAGE' => [ 42, 3, 0, 0 ],
842
+ 'DMIN' => [ 43, 3, 0, 0 ],
843
+ 'DMAX' => [ 44, 3, 0, 0 ],
844
+ 'DSTDEV' => [ 45, 3, 0, 0 ],
845
+ 'VAR' => [ 46, -1, 0, 0 ],
846
+ 'DVAR' => [ 47, 3, 0, 0 ],
847
+ 'TEXT' => [ 48, 2, 1, 0 ],
848
+ 'LINEST' => [ 49, -1, 0, 0 ],
849
+ 'TREND' => [ 50, -1, 0, 0 ],
850
+ 'LOGEST' => [ 51, -1, 0, 0 ],
851
+ 'GROWTH' => [ 52, -1, 0, 0 ],
852
+ 'PV' => [ 56, -1, 1, 0 ],
853
+ 'FV' => [ 57, -1, 1, 0 ],
854
+ 'NPER' => [ 58, -1, 1, 0 ],
855
+ 'PMT' => [ 59, -1, 1, 0 ],
856
+ 'RATE' => [ 60, -1, 1, 0 ],
857
+ 'MIRR' => [ 61, 3, 0, 0 ],
858
+ 'IRR' => [ 62, -1, 0, 0 ],
859
+ 'RAND' => [ 63, 0, 1, 1 ],
860
+ 'MATCH' => [ 64, -1, 0, 0 ],
861
+ 'DATE' => [ 65, 3, 1, 0 ],
862
+ 'TIME' => [ 66, 3, 1, 0 ],
863
+ 'DAY' => [ 67, 1, 1, 0 ],
864
+ 'MONTH' => [ 68, 1, 1, 0 ],
865
+ 'YEAR' => [ 69, 1, 1, 0 ],
866
+ 'WEEKDAY' => [ 70, -1, 1, 0 ],
867
+ 'HOUR' => [ 71, 1, 1, 0 ],
868
+ 'MINUTE' => [ 72, 1, 1, 0 ],
869
+ 'SECOND' => [ 73, 1, 1, 0 ],
870
+ 'NOW' => [ 74, 0, 1, 1 ],
871
+ 'AREAS' => [ 75, 1, 0, 1 ],
872
+ 'ROWS' => [ 76, 1, 0, 1 ],
873
+ 'COLUMNS' => [ 77, 1, 0, 1 ],
874
+ 'OFFSET' => [ 78, -1, 0, 1 ],
875
+ 'SEARCH' => [ 82, -1, 1, 0 ],
876
+ 'TRANSPOSE' => [ 83, 1, 1, 0 ],
877
+ 'TYPE' => [ 86, 1, 1, 0 ],
878
+ 'ATAN2' => [ 97, 2, 1, 0 ],
879
+ 'ASIN' => [ 98, 1, 1, 0 ],
880
+ 'ACOS' => [ 99, 1, 1, 0 ],
881
+ 'CHOOSE' => [ 100, -1, 1, 0 ],
882
+ 'HLOOKUP' => [ 101, -1, 0, 0 ],
883
+ 'VLOOKUP' => [ 102, -1, 0, 0 ],
884
+ 'ISREF' => [ 105, 1, 0, 0 ],
885
+ 'LOG' => [ 109, -1, 1, 0 ],
886
+ 'CHAR' => [ 111, 1, 1, 0 ],
887
+ 'LOWER' => [ 112, 1, 1, 0 ],
888
+ 'UPPER' => [ 113, 1, 1, 0 ],
889
+ 'PROPER' => [ 114, 1, 1, 0 ],
890
+ 'LEFT' => [ 115, -1, 1, 0 ],
891
+ 'RIGHT' => [ 116, -1, 1, 0 ],
892
+ 'EXACT' => [ 117, 2, 1, 0 ],
893
+ 'TRIM' => [ 118, 1, 1, 0 ],
894
+ 'REPLACE' => [ 119, 4, 1, 0 ],
895
+ 'SUBSTITUTE' => [ 120, -1, 1, 0 ],
896
+ 'CODE' => [ 121, 1, 1, 0 ],
897
+ 'FIND' => [ 124, -1, 1, 0 ],
898
+ 'CELL' => [ 125, -1, 0, 1 ],
899
+ 'ISERR' => [ 126, 1, 1, 0 ],
900
+ 'ISTEXT' => [ 127, 1, 1, 0 ],
901
+ 'ISNUMBER' => [ 128, 1, 1, 0 ],
902
+ 'ISBLANK' => [ 129, 1, 1, 0 ],
903
+ 'T' => [ 130, 1, 0, 0 ],
904
+ 'N' => [ 131, 1, 0, 0 ],
905
+ 'DATEVALUE' => [ 140, 1, 1, 0 ],
906
+ 'TIMEVALUE' => [ 141, 1, 1, 0 ],
907
+ 'SLN' => [ 142, 3, 1, 0 ],
908
+ 'SYD' => [ 143, 4, 1, 0 ],
909
+ 'DDB' => [ 144, -1, 1, 0 ],
910
+ 'INDIRECT' => [ 148, -1, 1, 1 ],
911
+ 'CALL' => [ 150, -1, 1, 0 ],
912
+ 'CLEAN' => [ 162, 1, 1, 0 ],
913
+ 'MDETERM' => [ 163, 1, 2, 0 ],
914
+ 'MINVERSE' => [ 164, 1, 2, 0 ],
915
+ 'MMULT' => [ 165, 2, 2, 0 ],
916
+ 'IPMT' => [ 167, -1, 1, 0 ],
917
+ 'PPMT' => [ 168, -1, 1, 0 ],
918
+ 'COUNTA' => [ 169, -1, 0, 0 ],
919
+ 'PRODUCT' => [ 183, -1, 0, 0 ],
920
+ 'FACT' => [ 184, 1, 1, 0 ],
921
+ 'DPRODUCT' => [ 189, 3, 0, 0 ],
922
+ 'ISNONTEXT' => [ 190, 1, 1, 0 ],
923
+ 'STDEVP' => [ 193, -1, 0, 0 ],
924
+ 'VARP' => [ 194, -1, 0, 0 ],
925
+ 'DSTDEVP' => [ 195, 3, 0, 0 ],
926
+ 'DVARP' => [ 196, 3, 0, 0 ],
927
+ 'TRUNC' => [ 197, -1, 1, 0 ],
928
+ 'ISLOGICAL' => [ 198, 1, 1, 0 ],
929
+ 'DCOUNTA' => [ 199, 3, 0, 0 ],
930
+ 'ROUNDUP' => [ 212, 2, 1, 0 ],
931
+ 'ROUNDDOWN' => [ 213, 2, 1, 0 ],
932
+ 'RANK' => [ 216, -1, 0, 0 ],
933
+ 'ADDRESS' => [ 219, -1, 1, 0 ],
934
+ 'DAYS360' => [ 220, -1, 1, 0 ],
935
+ 'TODAY' => [ 221, 0, 1, 1 ],
936
+ 'VDB' => [ 222, -1, 1, 0 ],
937
+ 'MEDIAN' => [ 227, -1, 0, 0 ],
938
+ 'SUMPRODUCT' => [ 228, -1, 2, 0 ],
939
+ 'SINH' => [ 229, 1, 1, 0 ],
940
+ 'COSH' => [ 230, 1, 1, 0 ],
941
+ 'TANH' => [ 231, 1, 1, 0 ],
942
+ 'ASINH' => [ 232, 1, 1, 0 ],
943
+ 'ACOSH' => [ 233, 1, 1, 0 ],
944
+ 'ATANH' => [ 234, 1, 1, 0 ],
945
+ 'DGET' => [ 235, 3, 0, 0 ],
946
+ 'INFO' => [ 244, 1, 1, 1 ],
947
+ 'DB' => [ 247, -1, 1, 0 ],
948
+ 'FREQUENCY' => [ 252, 2, 0, 0 ],
949
+ 'ERROR.TYPE' => [ 261, 1, 1, 0 ],
950
+ 'REGISTER.ID' => [ 267, -1, 1, 0 ],
951
+ 'AVEDEV' => [ 269, -1, 0, 0 ],
952
+ 'BETADIST' => [ 270, -1, 1, 0 ],
953
+ 'GAMMALN' => [ 271, 1, 1, 0 ],
954
+ 'BETAINV' => [ 272, -1, 1, 0 ],
955
+ 'BINOMDIST' => [ 273, 4, 1, 0 ],
956
+ 'CHIDIST' => [ 274, 2, 1, 0 ],
957
+ 'CHIINV' => [ 275, 2, 1, 0 ],
958
+ 'COMBIN' => [ 276, 2, 1, 0 ],
959
+ 'CONFIDENCE' => [ 277, 3, 1, 0 ],
960
+ 'CRITBINOM' => [ 278, 3, 1, 0 ],
961
+ 'EVEN' => [ 279, 1, 1, 0 ],
962
+ 'EXPONDIST' => [ 280, 3, 1, 0 ],
963
+ 'FDIST' => [ 281, 3, 1, 0 ],
964
+ 'FINV' => [ 282, 3, 1, 0 ],
965
+ 'FISHER' => [ 283, 1, 1, 0 ],
966
+ 'FISHERINV' => [ 284, 1, 1, 0 ],
967
+ 'FLOOR' => [ 285, 2, 1, 0 ],
968
+ 'GAMMADIST' => [ 286, 4, 1, 0 ],
969
+ 'GAMMAINV' => [ 287, 3, 1, 0 ],
970
+ 'CEILING' => [ 288, 2, 1, 0 ],
971
+ 'HYPGEOMDIST' => [ 289, 4, 1, 0 ],
972
+ 'LOGNORMDIST' => [ 290, 3, 1, 0 ],
973
+ 'LOGINV' => [ 291, 3, 1, 0 ],
974
+ 'NEGBINOMDIST' => [ 292, 3, 1, 0 ],
975
+ 'NORMDIST' => [ 293, 4, 1, 0 ],
976
+ 'NORMSDIST' => [ 294, 1, 1, 0 ],
977
+ 'NORMINV' => [ 295, 3, 1, 0 ],
978
+ 'NORMSINV' => [ 296, 1, 1, 0 ],
979
+ 'STANDARDIZE' => [ 297, 3, 1, 0 ],
980
+ 'ODD' => [ 298, 1, 1, 0 ],
981
+ 'PERMUT' => [ 299, 2, 1, 0 ],
982
+ 'POISSON' => [ 300, 3, 1, 0 ],
983
+ 'TDIST' => [ 301, 3, 1, 0 ],
984
+ 'WEIBULL' => [ 302, 4, 1, 0 ],
985
+ 'SUMXMY2' => [ 303, 2, 2, 0 ],
986
+ 'SUMX2MY2' => [ 304, 2, 2, 0 ],
987
+ 'SUMX2PY2' => [ 305, 2, 2, 0 ],
988
+ 'CHITEST' => [ 306, 2, 2, 0 ],
989
+ 'CORREL' => [ 307, 2, 2, 0 ],
990
+ 'COVAR' => [ 308, 2, 2, 0 ],
991
+ 'FORECAST' => [ 309, 3, 2, 0 ],
992
+ 'FTEST' => [ 310, 2, 2, 0 ],
993
+ 'INTERCEPT' => [ 311, 2, 2, 0 ],
994
+ 'PEARSON' => [ 312, 2, 2, 0 ],
995
+ 'RSQ' => [ 313, 2, 2, 0 ],
996
+ 'STEYX' => [ 314, 2, 2, 0 ],
997
+ 'SLOPE' => [ 315, 2, 2, 0 ],
998
+ 'TTEST' => [ 316, 4, 2, 0 ],
999
+ 'PROB' => [ 317, -1, 2, 0 ],
1000
+ 'DEVSQ' => [ 318, -1, 0, 0 ],
1001
+ 'GEOMEAN' => [ 319, -1, 0, 0 ],
1002
+ 'HARMEAN' => [ 320, -1, 0, 0 ],
1003
+ 'SUMSQ' => [ 321, -1, 0, 0 ],
1004
+ 'KURT' => [ 322, -1, 0, 0 ],
1005
+ 'SKEW' => [ 323, -1, 0, 0 ],
1006
+ 'ZTEST' => [ 324, -1, 0, 0 ],
1007
+ 'LARGE' => [ 325, 2, 0, 0 ],
1008
+ 'SMALL' => [ 326, 2, 0, 0 ],
1009
+ 'QUARTILE' => [ 327, 2, 0, 0 ],
1010
+ 'PERCENTILE' => [ 328, 2, 0, 0 ],
1011
+ 'PERCENTRANK' => [ 329, -1, 0, 0 ],
1012
+ 'MODE' => [ 330, -1, 2, 0 ],
1013
+ 'TRIMMEAN' => [ 331, 2, 0, 0 ],
1014
+ 'TINV' => [ 332, 2, 1, 0 ],
1015
+ 'CONCATENATE' => [ 336, -1, 1, 0 ],
1016
+ 'POWER' => [ 337, 2, 1, 0 ],
1017
+ 'RADIANS' => [ 342, 1, 1, 0 ],
1018
+ 'DEGREES' => [ 343, 1, 1, 0 ],
1019
+ 'SUBTOTAL' => [ 344, -1, 0, 0 ],
1020
+ 'SUMIF' => [ 345, -1, 0, 0 ],
1021
+ 'COUNTIF' => [ 346, 2, 0, 0 ],
1022
+ 'COUNTBLANK' => [ 347, 1, 0, 0 ],
1023
+ 'ROMAN' => [ 354, -1, 1, 0 ]
1024
+ }
1025
+
1026
+ end
1027
+ end
1028
+
1029
+ if $0 ==__FILE__
1030
+
1031
+
1032
+ parser = Formula.new
1033
+ puts
1034
+ puts 'type "Q" to quit.'
1035
+ puts
1036
+ while true
1037
+ puts
1038
+ print '? '
1039
+ str = gets.chop!
1040
+ break if /q/i =~ str
1041
+ begin
1042
+ e = parser.parse(str)
1043
+ p parser.reverse(e)
1044
+ rescue ParseError
1045
+ puts $!
1046
+ end
1047
+ end
1048
+
1049
+ end # class Formula
1050
+
1051
+ end # module Writeexcel