ricardoo27-writeexcel 0.6.12.1

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