writeexcel 0.4.2 → 0.4.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -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