writeexcel 0.4.2 → 0.4.3
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +149 -144
- data/VERSION +1 -1
- data/lib/writeexcel/format.rb +1600 -1600
- data/lib/writeexcel/formula.rb +1051 -1051
- data/lib/writeexcel/worksheet.rb +8801 -8801
- data/test/test_format.rb +1192 -1192
- data/writeexcel.gemspec +2 -2
- data/writeexcel.rdoc +1382 -1382
- metadata +3 -3
data/lib/writeexcel/formula.rb
CHANGED
@@ -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
|