writeexcel 0.1.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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