writeexcel 0.1.0 → 0.3.0

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