PluginProbe
Visualizer – Tables & Charts Manager with Built-in AI Generator / 3.0.3
Visualizer – Tables & Charts Manager with Built-in AI Generator v3.0.3
4.0.8 4.0.7 4.0.6 4.0.5 4.0.4 4.0.3 3.0.5 3.0.6 3.0.7 3.0.8 3.0.9 3.1.0 3.1.1 3.1.2 3.1.3 3.10.0 3.10.1 3.10.10 3.10.11 3.10.12 3.10.13 3.10.14 3.10.15 3.10.2 3.10.3 All 149 releases
visualizer / vendor / phpoffice / phpexcel / Classes / PHPExcel / Writer / Excel5 / Parser.php

Parser.php in Visualizer – Tables & Charts Manager with Built-in AI Generator 3.0.3, at vendor/phpoffice/phpexcel/Classes/PHPExcel/Writer/Excel5/Parser.php

1,584 lines 55.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * PHPExcel
4 *
5 * Copyright (c) 2006 - 2014 PHPExcel
6 *
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2.1 of the License, or (at your option) any later version.
11 *
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this library; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
20 *
21 * @category PHPExcel
22 * @package PHPExcel_Writer_Excel5
23 * @copyright Copyright (c) 2006 - 2014 PHPExcel (http://www.codeplex.com/PHPExcel)
24 * @license http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt LGPL
25 * @version ##VERSION##, ##DATE##
26 */
27
28 // Original file header of PEAR::Spreadsheet_Excel_Writer_Parser (used as the base for this class):
29 // -----------------------------------------------------------------------------------------
30 // * Class for parsing Excel formulas
31 // *
32 // * License Information:
33 // *
34 // * Spreadsheet_Excel_Writer: A library for generating Excel Spreadsheets
35 // * Copyright (c) 2002-2003 Xavier Noguer xnoguer@rezebra.com
36 // *
37 // * This library is free software; you can redistribute it and/or
38 // * modify it under the terms of the GNU Lesser General Public
39 // * License as published by the Free Software Foundation; either
40 // * version 2.1 of the License, or (at your option) any later version.
41 // *
42 // * This library is distributed in the hope that it will be useful,
43 // * but WITHOUT ANY WARRANTY; without even the implied warranty of
44 // * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
45 // * Lesser General Public License for more details.
46 // *
47 // * You should have received a copy of the GNU Lesser General Public
48 // * License along with this library; if not, write to the Free Software
49 // * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
50 // */
51
52
53 /**
54 * PHPExcel_Writer_Excel5_Parser
55 *
56 * @category PHPExcel
57 * @package PHPExcel_Writer_Excel5
58 * @copyright Copyright (c) 2006 - 2014 PHPExcel (http://www.codeplex.com/PHPExcel)
59 */
60 class PHPExcel_Writer_Excel5_Parser
61 {
62 /** Constants */
63 // Sheet title in unquoted form
64 // Invalid sheet title characters cannot occur in the sheet title:
65 // *:/\?[]
66 // Moreover, there are valid sheet title characters that cannot occur in unquoted form (there may be more?)
67 // +-% '^&<>=,;#()"{}
68 const REGEX_SHEET_TITLE_UNQUOTED = '[^\*\:\/\\\\\?\[\]\+\-\% \\\'\^\&\<\>\=\,\;\#\(\)\"\{\}]+';
69
70 // Sheet title in quoted form (without surrounding quotes)
71 // Invalid sheet title characters cannot occur in the sheet title:
72 // *:/\?[] (usual invalid sheet title characters)
73 // Single quote is represented as a pair ''
74 const REGEX_SHEET_TITLE_QUOTED = '(([^\*\:\/\\\\\?\[\]\\\'])+|(\\\'\\\')+)+';
75
76 /**
77 * The index of the character we are currently looking at
78 * @var integer
79 */
80 public $_current_char;
81
82 /**
83 * The token we are working on.
84 * @var string
85 */
86 public $_current_token;
87
88 /**
89 * The formula to parse
90 * @var string
91 */
92 public $_formula;
93
94 /**
95 * The character ahead of the current char
96 * @var string
97 */
98 public $_lookahead;
99
100 /**
101 * The parse tree to be generated
102 * @var string
103 */
104 public $_parse_tree;
105
106 /**
107 * Array of external sheets
108 * @var array
109 */
110 public $_ext_sheets;
111
112 /**
113 * Array of sheet references in the form of REF structures
114 * @var array
115 */
116 public $_references;
117
118 /**
119 * The class constructor
120 *
121 */
122 public function __construct()
123 {
124 $this->_current_char = 0;
125 $this->_current_token = ''; // The token we are working on.
126 $this->_formula = ''; // The formula to parse.
127 $this->_lookahead = ''; // The character ahead of the current char.
128 $this->_parse_tree = ''; // The parse tree to be generated.
129 $this->_initializeHashes(); // Initialize the hashes: ptg's and function's ptg's
130 $this->_ext_sheets = array();
131 $this->_references = array();
132 }
133
134 /**
135 * Initialize the ptg and function hashes.
136 *
137 * @access private
138 */
139 function _initializeHashes()
140 {
141 // The Excel ptg indices
142 $this->ptg = array(
143 'ptgExp' => 0x01,
144 'ptgTbl' => 0x02,
145 'ptgAdd' => 0x03,
146 'ptgSub' => 0x04,
147 'ptgMul' => 0x05,
148 'ptgDiv' => 0x06,
149 'ptgPower' => 0x07,
150 'ptgConcat' => 0x08,
151 'ptgLT' => 0x09,
152 'ptgLE' => 0x0A,
153 'ptgEQ' => 0x0B,
154 'ptgGE' => 0x0C,
155 'ptgGT' => 0x0D,
156 'ptgNE' => 0x0E,
157 'ptgIsect' => 0x0F,
158 'ptgUnion' => 0x10,
159 'ptgRange' => 0x11,
160 'ptgUplus' => 0x12,
161 'ptgUminus' => 0x13,
162 'ptgPercent' => 0x14,
163 'ptgParen' => 0x15,
164 'ptgMissArg' => 0x16,
165 'ptgStr' => 0x17,
166 'ptgAttr' => 0x19,
167 'ptgSheet' => 0x1A,
168 'ptgEndSheet' => 0x1B,
169 'ptgErr' => 0x1C,
170 'ptgBool' => 0x1D,
171 'ptgInt' => 0x1E,
172 'ptgNum' => 0x1F,
173 'ptgArray' => 0x20,
174 'ptgFunc' => 0x21,
175 'ptgFuncVar' => 0x22,
176 'ptgName' => 0x23,
177 'ptgRef' => 0x24,
178 'ptgArea' => 0x25,
179 'ptgMemArea' => 0x26,
180 'ptgMemErr' => 0x27,
181 'ptgMemNoMem' => 0x28,
182 'ptgMemFunc' => 0x29,
183 'ptgRefErr' => 0x2A,
184 'ptgAreaErr' => 0x2B,
185 'ptgRefN' => 0x2C,
186 'ptgAreaN' => 0x2D,
187 'ptgMemAreaN' => 0x2E,
188 'ptgMemNoMemN' => 0x2F,
189 'ptgNameX' => 0x39,
190 'ptgRef3d' => 0x3A,
191 'ptgArea3d' => 0x3B,
192 'ptgRefErr3d' => 0x3C,
193 'ptgAreaErr3d' => 0x3D,
194 'ptgArrayV' => 0x40,
195 'ptgFuncV' => 0x41,
196 'ptgFuncVarV' => 0x42,
197 'ptgNameV' => 0x43,
198 'ptgRefV' => 0x44,
199 'ptgAreaV' => 0x45,
200 'ptgMemAreaV' => 0x46,
201 'ptgMemErrV' => 0x47,
202 'ptgMemNoMemV' => 0x48,
203 'ptgMemFuncV' => 0x49,
204 'ptgRefErrV' => 0x4A,
205 'ptgAreaErrV' => 0x4B,
206 'ptgRefNV' => 0x4C,
207 'ptgAreaNV' => 0x4D,
208 'ptgMemAreaNV' => 0x4E,
209 'ptgMemNoMemN' => 0x4F,
210 'ptgFuncCEV' => 0x58,
211 'ptgNameXV' => 0x59,
212 'ptgRef3dV' => 0x5A,
213 'ptgArea3dV' => 0x5B,
214 'ptgRefErr3dV' => 0x5C,
215 'ptgAreaErr3d' => 0x5D,
216 'ptgArrayA' => 0x60,
217 'ptgFuncA' => 0x61,
218 'ptgFuncVarA' => 0x62,
219 'ptgNameA' => 0x63,
220 'ptgRefA' => 0x64,
221 'ptgAreaA' => 0x65,
222 'ptgMemAreaA' => 0x66,
223 'ptgMemErrA' => 0x67,
224 'ptgMemNoMemA' => 0x68,
225 'ptgMemFuncA' => 0x69,
226 'ptgRefErrA' => 0x6A,
227 'ptgAreaErrA' => 0x6B,
228 'ptgRefNA' => 0x6C,
229 'ptgAreaNA' => 0x6D,
230 'ptgMemAreaNA' => 0x6E,
231 'ptgMemNoMemN' => 0x6F,
232 'ptgFuncCEA' => 0x78,
233 'ptgNameXA' => 0x79,
234 'ptgRef3dA' => 0x7A,
235 'ptgArea3dA' => 0x7B,
236 'ptgRefErr3dA' => 0x7C,
237 'ptgAreaErr3d' => 0x7D
238 );
239
240 // Thanks to Michael Meeks and Gnumeric for the initial arg values.
241 //
242 // The following hash was generated by "function_locale.pl" in the distro.
243 // Refer to function_locale.pl for non-English function names.
244 //
245 // The array elements are as follow:
246 // ptg: The Excel function ptg code.
247 // args: The number of arguments that the function takes:
248 // >=0 is a fixed number of arguments.
249 // -1 is a variable number of arguments.
250 // class: The reference, value or array class of the function args.
251 // vol: The function is volatile.
252 //
253 $this->_functions = array(
254 // function ptg args class vol
255 'COUNT' => array( 0, -1, 0, 0 ),
256 'IF' => array( 1, -1, 1, 0 ),
257 'ISNA' => array( 2, 1, 1, 0 ),
258 'ISERROR' => array( 3, 1, 1, 0 ),
259 'SUM' => array( 4, -1, 0, 0 ),
260 'AVERAGE' => array( 5, -1, 0, 0 ),
261 'MIN' => array( 6, -1, 0, 0 ),
262 'MAX' => array( 7, -1, 0, 0 ),
263 'ROW' => array( 8, -1, 0, 0 ),
264 'COLUMN' => array( 9, -1, 0, 0 ),
265 'NA' => array( 10, 0, 0, 0 ),
266 'NPV' => array( 11, -1, 1, 0 ),
267 'STDEV' => array( 12, -1, 0, 0 ),
268 'DOLLAR' => array( 13, -1, 1, 0 ),
269 'FIXED' => array( 14, -1, 1, 0 ),
270 'SIN' => array( 15, 1, 1, 0 ),
271 'COS' => array( 16, 1, 1, 0 ),
272 'TAN' => array( 17, 1, 1, 0 ),
273 'ATAN' => array( 18, 1, 1, 0 ),
274 'PI' => array( 19, 0, 1, 0 ),
275 'SQRT' => array( 20, 1, 1, 0 ),
276 'EXP' => array( 21, 1, 1, 0 ),
277 'LN' => array( 22, 1, 1, 0 ),
278 'LOG10' => array( 23, 1, 1, 0 ),
279 'ABS' => array( 24, 1, 1, 0 ),
280 'INT' => array( 25, 1, 1, 0 ),
281 'SIGN' => array( 26, 1, 1, 0 ),
282 'ROUND' => array( 27, 2, 1, 0 ),
283 'LOOKUP' => array( 28, -1, 0, 0 ),
284 'INDEX' => array( 29, -1, 0, 1 ),
285 'REPT' => array( 30, 2, 1, 0 ),
286 'MID' => array( 31, 3, 1, 0 ),
287 'LEN' => array( 32, 1, 1, 0 ),
288 'VALUE' => array( 33, 1, 1, 0 ),
289 'TRUE' => array( 34, 0, 1, 0 ),
290 'FALSE' => array( 35, 0, 1, 0 ),
291 'AND' => array( 36, -1, 0, 0 ),
292 'OR' => array( 37, -1, 0, 0 ),
293 'NOT' => array( 38, 1, 1, 0 ),
294 'MOD' => array( 39, 2, 1, 0 ),
295 'DCOUNT' => array( 40, 3, 0, 0 ),
296 'DSUM' => array( 41, 3, 0, 0 ),
297 'DAVERAGE' => array( 42, 3, 0, 0 ),
298 'DMIN' => array( 43, 3, 0, 0 ),
299 'DMAX' => array( 44, 3, 0, 0 ),
300 'DSTDEV' => array( 45, 3, 0, 0 ),
301 'VAR' => array( 46, -1, 0, 0 ),
302 'DVAR' => array( 47, 3, 0, 0 ),
303 'TEXT' => array( 48, 2, 1, 0 ),
304 'LINEST' => array( 49, -1, 0, 0 ),
305 'TREND' => array( 50, -1, 0, 0 ),
306 'LOGEST' => array( 51, -1, 0, 0 ),
307 'GROWTH' => array( 52, -1, 0, 0 ),
308 'PV' => array( 56, -1, 1, 0 ),
309 'FV' => array( 57, -1, 1, 0 ),
310 'NPER' => array( 58, -1, 1, 0 ),
311 'PMT' => array( 59, -1, 1, 0 ),
312 'RATE' => array( 60, -1, 1, 0 ),
313 'MIRR' => array( 61, 3, 0, 0 ),
314 'IRR' => array( 62, -1, 0, 0 ),
315 'RAND' => array( 63, 0, 1, 1 ),
316 'MATCH' => array( 64, -1, 0, 0 ),
317 'DATE' => array( 65, 3, 1, 0 ),
318 'TIME' => array( 66, 3, 1, 0 ),
319 'DAY' => array( 67, 1, 1, 0 ),
320 'MONTH' => array( 68, 1, 1, 0 ),
321 'YEAR' => array( 69, 1, 1, 0 ),
322 'WEEKDAY' => array( 70, -1, 1, 0 ),
323 'HOUR' => array( 71, 1, 1, 0 ),
324 'MINUTE' => array( 72, 1, 1, 0 ),
325 'SECOND' => array( 73, 1, 1, 0 ),
326 'NOW' => array( 74, 0, 1, 1 ),
327 'AREAS' => array( 75, 1, 0, 1 ),
328 'ROWS' => array( 76, 1, 0, 1 ),
329 'COLUMNS' => array( 77, 1, 0, 1 ),
330 'OFFSET' => array( 78, -1, 0, 1 ),
331 'SEARCH' => array( 82, -1, 1, 0 ),
332 'TRANSPOSE' => array( 83, 1, 1, 0 ),
333 'TYPE' => array( 86, 1, 1, 0 ),
334 'ATAN2' => array( 97, 2, 1, 0 ),
335 'ASIN' => array( 98, 1, 1, 0 ),
336 'ACOS' => array( 99, 1, 1, 0 ),
337 'CHOOSE' => array( 100, -1, 1, 0 ),
338 'HLOOKUP' => array( 101, -1, 0, 0 ),
339 'VLOOKUP' => array( 102, -1, 0, 0 ),
340 'ISREF' => array( 105, 1, 0, 0 ),
341 'LOG' => array( 109, -1, 1, 0 ),
342 'CHAR' => array( 111, 1, 1, 0 ),
343 'LOWER' => array( 112, 1, 1, 0 ),
344 'UPPER' => array( 113, 1, 1, 0 ),
345 'PROPER' => array( 114, 1, 1, 0 ),
346 'LEFT' => array( 115, -1, 1, 0 ),
347 'RIGHT' => array( 116, -1, 1, 0 ),
348 'EXACT' => array( 117, 2, 1, 0 ),
349 'TRIM' => array( 118, 1, 1, 0 ),
350 'REPLACE' => array( 119, 4, 1, 0 ),
351 'SUBSTITUTE' => array( 120, -1, 1, 0 ),
352 'CODE' => array( 121, 1, 1, 0 ),
353 'FIND' => array( 124, -1, 1, 0 ),
354 'CELL' => array( 125, -1, 0, 1 ),
355 'ISERR' => array( 126, 1, 1, 0 ),
356 'ISTEXT' => array( 127, 1, 1, 0 ),
357 'ISNUMBER' => array( 128, 1, 1, 0 ),
358 'ISBLANK' => array( 129, 1, 1, 0 ),
359 'T' => array( 130, 1, 0, 0 ),
360 'N' => array( 131, 1, 0, 0 ),
361 'DATEVALUE' => array( 140, 1, 1, 0 ),
362 'TIMEVALUE' => array( 141, 1, 1, 0 ),
363 'SLN' => array( 142, 3, 1, 0 ),
364 'SYD' => array( 143, 4, 1, 0 ),
365 'DDB' => array( 144, -1, 1, 0 ),
366 'INDIRECT' => array( 148, -1, 1, 1 ),
367 'CALL' => array( 150, -1, 1, 0 ),
368 'CLEAN' => array( 162, 1, 1, 0 ),
369 'MDETERM' => array( 163, 1, 2, 0 ),
370 'MINVERSE' => array( 164, 1, 2, 0 ),
371 'MMULT' => array( 165, 2, 2, 0 ),
372 'IPMT' => array( 167, -1, 1, 0 ),
373 'PPMT' => array( 168, -1, 1, 0 ),
374 'COUNTA' => array( 169, -1, 0, 0 ),
375 'PRODUCT' => array( 183, -1, 0, 0 ),
376 'FACT' => array( 184, 1, 1, 0 ),
377 'DPRODUCT' => array( 189, 3, 0, 0 ),
378 'ISNONTEXT' => array( 190, 1, 1, 0 ),
379 'STDEVP' => array( 193, -1, 0, 0 ),
380 'VARP' => array( 194, -1, 0, 0 ),
381 'DSTDEVP' => array( 195, 3, 0, 0 ),
382 'DVARP' => array( 196, 3, 0, 0 ),
383 'TRUNC' => array( 197, -1, 1, 0 ),
384 'ISLOGICAL' => array( 198, 1, 1, 0 ),
385 'DCOUNTA' => array( 199, 3, 0, 0 ),
386 'USDOLLAR' => array( 204, -1, 1, 0 ),
387 'FINDB' => array( 205, -1, 1, 0 ),
388 'SEARCHB' => array( 206, -1, 1, 0 ),
389 'REPLACEB' => array( 207, 4, 1, 0 ),
390 'LEFTB' => array( 208, -1, 1, 0 ),
391 'RIGHTB' => array( 209, -1, 1, 0 ),
392 'MIDB' => array( 210, 3, 1, 0 ),
393 'LENB' => array( 211, 1, 1, 0 ),
394 'ROUNDUP' => array( 212, 2, 1, 0 ),
395 'ROUNDDOWN' => array( 213, 2, 1, 0 ),
396 'ASC' => array( 214, 1, 1, 0 ),
397 'DBCS' => array( 215, 1, 1, 0 ),
398 'RANK' => array( 216, -1, 0, 0 ),
399 'ADDRESS' => array( 219, -1, 1, 0 ),
400 'DAYS360' => array( 220, -1, 1, 0 ),
401 'TODAY' => array( 221, 0, 1, 1 ),
402 'VDB' => array( 222, -1, 1, 0 ),
403 'MEDIAN' => array( 227, -1, 0, 0 ),
404 'SUMPRODUCT' => array( 228, -1, 2, 0 ),
405 'SINH' => array( 229, 1, 1, 0 ),
406 'COSH' => array( 230, 1, 1, 0 ),
407 'TANH' => array( 231, 1, 1, 0 ),
408 'ASINH' => array( 232, 1, 1, 0 ),
409 'ACOSH' => array( 233, 1, 1, 0 ),
410 'ATANH' => array( 234, 1, 1, 0 ),
411 'DGET' => array( 235, 3, 0, 0 ),
412 'INFO' => array( 244, 1, 1, 1 ),
413 'DB' => array( 247, -1, 1, 0 ),
414 'FREQUENCY' => array( 252, 2, 0, 0 ),
415 'ERROR.TYPE' => array( 261, 1, 1, 0 ),
416 'REGISTER.ID' => array( 267, -1, 1, 0 ),
417 'AVEDEV' => array( 269, -1, 0, 0 ),
418 'BETADIST' => array( 270, -1, 1, 0 ),
419 'GAMMALN' => array( 271, 1, 1, 0 ),
420 'BETAINV' => array( 272, -1, 1, 0 ),
421 'BINOMDIST' => array( 273, 4, 1, 0 ),
422 'CHIDIST' => array( 274, 2, 1, 0 ),
423 'CHIINV' => array( 275, 2, 1, 0 ),
424 'COMBIN' => array( 276, 2, 1, 0 ),
425 'CONFIDENCE' => array( 277, 3, 1, 0 ),
426 'CRITBINOM' => array( 278, 3, 1, 0 ),
427 'EVEN' => array( 279, 1, 1, 0 ),
428 'EXPONDIST' => array( 280, 3, 1, 0 ),
429 'FDIST' => array( 281, 3, 1, 0 ),
430 'FINV' => array( 282, 3, 1, 0 ),
431 'FISHER' => array( 283, 1, 1, 0 ),
432 'FISHERINV' => array( 284, 1, 1, 0 ),
433 'FLOOR' => array( 285, 2, 1, 0 ),
434 'GAMMADIST' => array( 286, 4, 1, 0 ),
435 'GAMMAINV' => array( 287, 3, 1, 0 ),
436 'CEILING' => array( 288, 2, 1, 0 ),
437 'HYPGEOMDIST' => array( 289, 4, 1, 0 ),
438 'LOGNORMDIST' => array( 290, 3, 1, 0 ),
439 'LOGINV' => array( 291, 3, 1, 0 ),
440 'NEGBINOMDIST' => array( 292, 3, 1, 0 ),
441 'NORMDIST' => array( 293, 4, 1, 0 ),
442 'NORMSDIST' => array( 294, 1, 1, 0 ),
443 'NORMINV' => array( 295, 3, 1, 0 ),
444 'NORMSINV' => array( 296, 1, 1, 0 ),
445 'STANDARDIZE' => array( 297, 3, 1, 0 ),
446 'ODD' => array( 298, 1, 1, 0 ),
447 'PERMUT' => array( 299, 2, 1, 0 ),
448 'POISSON' => array( 300, 3, 1, 0 ),
449 'TDIST' => array( 301, 3, 1, 0 ),
450 'WEIBULL' => array( 302, 4, 1, 0 ),
451 'SUMXMY2' => array( 303, 2, 2, 0 ),
452 'SUMX2MY2' => array( 304, 2, 2, 0 ),
453 'SUMX2PY2' => array( 305, 2, 2, 0 ),
454 'CHITEST' => array( 306, 2, 2, 0 ),
455 'CORREL' => array( 307, 2, 2, 0 ),
456 'COVAR' => array( 308, 2, 2, 0 ),
457 'FORECAST' => array( 309, 3, 2, 0 ),
458 'FTEST' => array( 310, 2, 2, 0 ),
459 'INTERCEPT' => array( 311, 2, 2, 0 ),
460 'PEARSON' => array( 312, 2, 2, 0 ),
461 'RSQ' => array( 313, 2, 2, 0 ),
462 'STEYX' => array( 314, 2, 2, 0 ),
463 'SLOPE' => array( 315, 2, 2, 0 ),
464 'TTEST' => array( 316, 4, 2, 0 ),
465 'PROB' => array( 317, -1, 2, 0 ),
466 'DEVSQ' => array( 318, -1, 0, 0 ),
467 'GEOMEAN' => array( 319, -1, 0, 0 ),
468 'HARMEAN' => array( 320, -1, 0, 0 ),
469 'SUMSQ' => array( 321, -1, 0, 0 ),
470 'KURT' => array( 322, -1, 0, 0 ),
471 'SKEW' => array( 323, -1, 0, 0 ),
472 'ZTEST' => array( 324, -1, 0, 0 ),
473 'LARGE' => array( 325, 2, 0, 0 ),
474 'SMALL' => array( 326, 2, 0, 0 ),
475 'QUARTILE' => array( 327, 2, 0, 0 ),
476 'PERCENTILE' => array( 328, 2, 0, 0 ),
477 'PERCENTRANK' => array( 329, -1, 0, 0 ),
478 'MODE' => array( 330, -1, 2, 0 ),
479 'TRIMMEAN' => array( 331, 2, 0, 0 ),
480 'TINV' => array( 332, 2, 1, 0 ),
481 'CONCATENATE' => array( 336, -1, 1, 0 ),
482 'POWER' => array( 337, 2, 1, 0 ),
483 'RADIANS' => array( 342, 1, 1, 0 ),
484 'DEGREES' => array( 343, 1, 1, 0 ),
485 'SUBTOTAL' => array( 344, -1, 0, 0 ),
486 'SUMIF' => array( 345, -1, 0, 0 ),
487 'COUNTIF' => array( 346, 2, 0, 0 ),
488 'COUNTBLANK' => array( 347, 1, 0, 0 ),
489 'ISPMT' => array( 350, 4, 1, 0 ),
490 'DATEDIF' => array( 351, 3, 1, 0 ),
491 'DATESTRING' => array( 352, 1, 1, 0 ),
492 'NUMBERSTRING' => array( 353, 2, 1, 0 ),
493 'ROMAN' => array( 354, -1, 1, 0 ),
494 'GETPIVOTDATA' => array( 358, -1, 0, 0 ),
495 'HYPERLINK' => array( 359, -1, 1, 0 ),
496 'PHONETIC' => array( 360, 1, 0, 0 ),
497 'AVERAGEA' => array( 361, -1, 0, 0 ),
498 'MAXA' => array( 362, -1, 0, 0 ),
499 'MINA' => array( 363, -1, 0, 0 ),
500 'STDEVPA' => array( 364, -1, 0, 0 ),
501 'VARPA' => array( 365, -1, 0, 0 ),
502 'STDEVA' => array( 366, -1, 0, 0 ),
503 'VARA' => array( 367, -1, 0, 0 ),
504 'BAHTTEXT' => array( 368, 1, 0, 0 ),
505 );
506 }
507
508 /**
509 * Convert a token to the proper ptg value.
510 *
511 * @access private
512 * @param mixed $token The token to convert.
513 * @return mixed the converted token on success
514 */
515 function _convert($token)
516 {
517 if (preg_match("/\"([^\"]|\"\"){0,255}\"/", $token)) {
518 return $this->_convertString($token);
519
520 } elseif (is_numeric($token)) {
521 return $this->_convertNumber($token);
522
523 // match references like A1 or $A$1
524 } elseif (preg_match('/^\$?([A-Ia-i]?[A-Za-z])\$?(\d+)$/',$token)) {
525 return $this->_convertRef2d($token);
526
527 // match external references like Sheet1!A1 or Sheet1:Sheet2!A1 or Sheet1!$A$1 or Sheet1:Sheet2!$A$1
528 } elseif (preg_match("/^" . self::REGEX_SHEET_TITLE_UNQUOTED . "(\:" . self::REGEX_SHEET_TITLE_UNQUOTED . ")?\!\\$?[A-Ia-i]?[A-Za-z]\\$?(\d+)$/u",$token)) {
529 return $this->_convertRef3d($token);
530
531 // match external references like 'Sheet1'!A1 or 'Sheet1:Sheet2'!A1 or 'Sheet1'!$A$1 or 'Sheet1:Sheet2'!$A$1
532 } elseif (preg_match("/^'" . self::REGEX_SHEET_TITLE_QUOTED . "(\:" . self::REGEX_SHEET_TITLE_QUOTED . ")?'\!\\$?[A-Ia-i]?[A-Za-z]\\$?(\d+)$/u",$token)) {
533 return $this->_convertRef3d($token);
534
535 // match ranges like A1:B2 or $A$1:$B$2
536 } elseif (preg_match('/^(\$)?[A-Ia-i]?[A-Za-z](\$)?(\d+)\:(\$)?[A-Ia-i]?[A-Za-z](\$)?(\d+)$/', $token)) {
537 return $this->_convertRange2d($token);
538
539 // match external ranges like Sheet1!A1:B2 or Sheet1:Sheet2!A1:B2 or Sheet1!$A$1:$B$2 or Sheet1:Sheet2!$A$1:$B$2
540 } elseif (preg_match("/^" . self::REGEX_SHEET_TITLE_UNQUOTED . "(\:" . self::REGEX_SHEET_TITLE_UNQUOTED . ")?\!\\$?([A-Ia-i]?[A-Za-z])?\\$?(\d+)\:\\$?([A-Ia-i]?[A-Za-z])?\\$?(\d+)$/u",$token)) {
541 return $this->_convertRange3d($token);
542
543 // match external ranges like 'Sheet1'!A1:B2 or 'Sheet1:Sheet2'!A1:B2 or 'Sheet1'!$A$1:$B$2 or 'Sheet1:Sheet2'!$A$1:$B$2
544 } elseif (preg_match("/^'" . self::REGEX_SHEET_TITLE_QUOTED . "(\:" . self::REGEX_SHEET_TITLE_QUOTED . ")?'\!\\$?([A-Ia-i]?[A-Za-z])?\\$?(\d+)\:\\$?([A-Ia-i]?[A-Za-z])?\\$?(\d+)$/u",$token)) {
545 return $this->_convertRange3d($token);
546
547 // operators (including parentheses)
548 } elseif (isset($this->ptg[$token])) {
549 return pack("C", $this->ptg[$token]);
550
551 // match error codes
552 } elseif (preg_match("/^#[A-Z0\/]{3,5}[!?]{1}$/", $token) or $token == '#N/A') {
553 return $this->_convertError($token);
554
555 // commented so argument number can be processed correctly. See toReversePolish().
556 /*elseif (preg_match("/[A-Z0-9\xc0-\xdc\.]+/",$token))
557 {
558 return($this->_convertFunction($token,$this->_func_args));
559 }*/
560
561 // if it's an argument, ignore the token (the argument remains)
562 } elseif ($token == 'arg') {
563 return '';
564 }
565
566 // TODO: use real error codes
567 throw new PHPExcel_Writer_Exception("Unknown token $token");
568 }
569
570 /**
571 * Convert a number token to ptgInt or ptgNum
572 *
573 * @access private
574 * @param mixed $num an integer or double for conversion to its ptg value
575 */
576 function _convertNumber($num)
577 {
578 // Integer in the range 0..2**16-1
579 if ((preg_match("/^\d+$/", $num)) and ($num <= 65535)) {
580 return pack("Cv", $this->ptg['ptgInt'], $num);
581 } else { // A float
582 if (PHPExcel_Writer_Excel5_BIFFwriter::getByteOrder()) { // if it's Big Endian
583 $num = strrev($num);
584 }
585 return pack("Cd", $this->ptg['ptgNum'], $num);
586 }
587 }
588
589 /**
590 * Convert a string token to ptgStr
591 *
592 * @access private
593 * @param string $string A string for conversion to its ptg value.
594 * @return mixed the converted token on success
595 */
596 function _convertString($string)
597 {
598 // chop away beggining and ending quotes
599 $string = substr($string, 1, strlen($string) - 2);
600 if (strlen($string) > 255) {
601 throw new PHPExcel_Writer_Exception("String is too long");
602 }
603
604 return pack('C', $this->ptg['ptgStr']) . PHPExcel_Shared_String::UTF8toBIFF8UnicodeShort($string);
605 }
606
607 /**
608 * Convert a function to a ptgFunc or ptgFuncVarV depending on the number of
609 * args that it takes.
610 *
611 * @access private
612 * @param string $token The name of the function for convertion to ptg value.
613 * @param integer $num_args The number of arguments the function receives.
614 * @return string The packed ptg for the function
615 */
616 function _convertFunction($token, $num_args)
617 {
618 $args = $this->_functions[$token][1];
619 // $volatile = $this->_functions[$token][3];
620
621 // Fixed number of args eg. TIME($i,$j,$k).
622 if ($args >= 0) {
623 return pack("Cv", $this->ptg['ptgFuncV'], $this->_functions[$token][0]);
624 }
625 // Variable number of args eg. SUM($i,$j,$k, ..).
626 if ($args == -1) {
627 return pack("CCv", $this->ptg['ptgFuncVarV'], $num_args, $this->_functions[$token][0]);
628 }
629 }
630
631 /**
632 * Convert an Excel range such as A1:D4 to a ptgRefV.
633 *
634 * @access private
635 * @param string $range An Excel range in the A1:A2
636 * @param int $class
637 */
638 function _convertRange2d($range, $class=0)
639 {
640
641 // TODO: possible class value 0,1,2 check Formula.pm
642 // Split the range into 2 cell refs
643 if (preg_match('/^(\$)?([A-Ia-i]?[A-Za-z])(\$)?(\d+)\:(\$)?([A-Ia-i]?[A-Za-z])(\$)?(\d+)$/', $range)) {
644 list($cell1, $cell2) = explode(':', $range);
645 } else {
646 // TODO: use real error codes
647 throw new PHPExcel_Writer_Exception("Unknown range separator");
648 }
649
650 // Convert the cell references
651 list($row1, $col1) = $this->_cellToPackedRowcol($cell1);
652 list($row2, $col2) = $this->_cellToPackedRowcol($cell2);
653
654 // The ptg value depends on the class of the ptg.
655 if ($class == 0) {
656 $ptgArea = pack("C", $this->ptg['ptgArea']);
657 } elseif ($class == 1) {
658 $ptgArea = pack("C", $this->ptg['ptgAreaV']);
659 } elseif ($class == 2) {
660 $ptgArea = pack("C", $this->ptg['ptgAreaA']);
661 } else {
662 // TODO: use real error codes
663 throw new PHPExcel_Writer_Exception("Unknown class $class");
664 }
665 return $ptgArea . $row1 . $row2 . $col1. $col2;
666 }
667
668 /**
669 * Convert an Excel 3d range such as "Sheet1!A1:D4" or "Sheet1:Sheet2!A1:D4" to
670 * a ptgArea3d.
671 *
672 * @access private
673 * @param string $token An Excel range in the Sheet1!A1:A2 format.
674 * @return mixed The packed ptgArea3d token on success.
675 */
676 function _convertRange3d($token)
677 {
678 // $class = 0; // formulas like Sheet1!$A$1:$A$2 in list type data validation need this class (0x3B)
679
680 // Split the ref at the ! symbol
681 list($ext_ref, $range) = explode('!', $token);
682
683 // Convert the external reference part (different for BIFF8)
684 $ext_ref = $this->_getRefIndex($ext_ref);
685
686 // Split the range into 2 cell refs
687 list($cell1, $cell2) = explode(':', $range);
688
689 // Convert the cell references
690 if (preg_match("/^(\\$)?[A-Ia-i]?[A-Za-z](\\$)?(\d+)$/", $cell1)) {
691 list($row1, $col1) = $this->_cellToPackedRowcol($cell1);
692 list($row2, $col2) = $this->_cellToPackedRowcol($cell2);
693 } else { // It's a rows range (like 26:27)
694 list($row1, $col1, $row2, $col2) = $this->_rangeToPackedRange($cell1.':'.$cell2);
695 }
696
697 // The ptg value depends on the class of the ptg.
698 // if ($class == 0) {
699 $ptgArea = pack("C", $this->ptg['ptgArea3d']);
700 // } elseif ($class == 1) {
701 // $ptgArea = pack("C", $this->ptg['ptgArea3dV']);
702 // } elseif ($class == 2) {
703 // $ptgArea = pack("C", $this->ptg['ptgArea3dA']);
704 // } else {
705 // throw new PHPExcel_Writer_Exception("Unknown class $class");
706 // }
707
708 return $ptgArea . $ext_ref . $row1 . $row2 . $col1. $col2;
709 }
710
711 /**
712 * Convert an Excel reference such as A1, $B2, C$3 or $D$4 to a ptgRefV.
713 *
714 * @access private
715 * @param string $cell An Excel cell reference
716 * @return string The cell in packed() format with the corresponding ptg
717 */
718 function _convertRef2d($cell)
719 {
720 // $class = 2; // as far as I know, this is magick.
721
722 // Convert the cell reference
723 $cell_array = $this->_cellToPackedRowcol($cell);
724 list($row, $col) = $cell_array;
725
726 // The ptg value depends on the class of the ptg.
727 // if ($class == 0) {
728 // $ptgRef = pack("C", $this->ptg['ptgRef']);
729 // } elseif ($class == 1) {
730 // $ptgRef = pack("C", $this->ptg['ptgRefV']);
731 // } elseif ($class == 2) {
732 $ptgRef = pack("C", $this->ptg['ptgRefA']);
733 // } else {
734 // // TODO: use real error codes
735 // throw new PHPExcel_Writer_Exception("Unknown class $class");
736 // }
737 return $ptgRef.$row.$col;
738 }
739
740 /**
741 * Convert an Excel 3d reference such as "Sheet1!A1" or "Sheet1:Sheet2!A1" to a
742 * ptgRef3d.
743 *
744 * @access private
745 * @param string $cell An Excel cell reference
746 * @return mixed The packed ptgRef3d token on success.
747 */
748 function _convertRef3d($cell)
749 {
750 // $class = 2; // as far as I know, this is magick.
751
752 // Split the ref at the ! symbol
753 list($ext_ref, $cell) = explode('!', $cell);
754
755 // Convert the external reference part (different for BIFF8)
756 $ext_ref = $this->_getRefIndex($ext_ref);
757
758 // Convert the cell reference part
759 list($row, $col) = $this->_cellToPackedRowcol($cell);
760
761 // The ptg value depends on the class of the ptg.
762 // if ($class == 0) {
763 // $ptgRef = pack("C", $this->ptg['ptgRef3d']);
764 // } elseif ($class == 1) {
765 // $ptgRef = pack("C", $this->ptg['ptgRef3dV']);
766 // } elseif ($class == 2) {
767 $ptgRef = pack("C", $this->ptg['ptgRef3dA']);
768 // } else {
769 // throw new PHPExcel_Writer_Exception("Unknown class $class");
770 // }
771
772 return $ptgRef . $ext_ref. $row . $col;
773 }
774
775 /**
776 * Convert an error code to a ptgErr
777 *
778 * @access private
779 * @param string $errorCode The error code for conversion to its ptg value
780 * @return string The error code ptgErr
781 */
782 function _convertError($errorCode)
783 {
784 switch ($errorCode) {
785 case '#NULL!': return pack("C", 0x00);
786 case '#DIV/0!': return pack("C", 0x07);
787 case '#VALUE!': return pack("C", 0x0F);
788 case '#REF!': return pack("C", 0x17);
789 case '#NAME?': return pack("C", 0x1D);
790 case '#NUM!': return pack("C", 0x24);
791 case '#N/A': return pack("C", 0x2A);
792 }
793 return pack("C", 0xFF);
794 }
795
796 /**
797 * Convert the sheet name part of an external reference, for example "Sheet1" or
798 * "Sheet1:Sheet2", to a packed structure.
799 *
800 * @access private
801 * @param string $ext_ref The name of the external reference
802 * @return string The reference index in packed() format
803 */
804 function _packExtRef($ext_ref)
805 {
806 $ext_ref = preg_replace("/^'/", '', $ext_ref); // Remove leading ' if any.
807 $ext_ref = preg_replace("/'$/", '', $ext_ref); // Remove trailing ' if any.
808
809 // Check if there is a sheet range eg., Sheet1:Sheet2.
810 if (preg_match("/:/", $ext_ref)) {
811 list($sheet_name1, $sheet_name2) = explode(':', $ext_ref);
812
813 $sheet1 = $this->_getSheetIndex($sheet_name1);
814 if ($sheet1 == -1) {
815 throw new PHPExcel_Writer_Exception("Unknown sheet name $sheet_name1 in formula");
816 }
817 $sheet2 = $this->_getSheetIndex($sheet_name2);
818 if ($sheet2 == -1) {
819 throw new PHPExcel_Writer_Exception("Unknown sheet name $sheet_name2 in formula");
820 }
821
822 // Reverse max and min sheet numbers if necessary
823 if ($sheet1 > $sheet2) {
824 list($sheet1, $sheet2) = array($sheet2, $sheet1);
825 }
826 } else { // Single sheet name only.
827 $sheet1 = $this->_getSheetIndex($ext_ref);
828 if ($sheet1 == -1) {
829 throw new PHPExcel_Writer_Exception("Unknown sheet name $ext_ref in formula");
830 }
831 $sheet2 = $sheet1;
832 }
833
834 // References are stored relative to 0xFFFF.
835 $offset = -1 - $sheet1;
836
837 return pack('vdvv', $offset, 0x00, $sheet1, $sheet2);
838 }
839
840 /**
841 * Look up the REF index that corresponds to an external sheet name
842 * (or range). If it doesn't exist yet add it to the workbook's references
843 * array. It assumes all sheet names given must exist.
844 *
845 * @access private
846 * @param string $ext_ref The name of the external reference
847 * @return mixed The reference index in packed() format on success
848 */
849 function _getRefIndex($ext_ref)
850 {
851 $ext_ref = preg_replace("/^'/", '', $ext_ref); // Remove leading ' if any.
852 $ext_ref = preg_replace("/'$/", '', $ext_ref); // Remove trailing ' if any.
853 $ext_ref = str_replace('\'\'', '\'', $ext_ref); // Replace escaped '' with '
854
855 // Check if there is a sheet range eg., Sheet1:Sheet2.
856 if (preg_match("/:/", $ext_ref)) {
857 list($sheet_name1, $sheet_name2) = explode(':', $ext_ref);
858
859 $sheet1 = $this->_getSheetIndex($sheet_name1);
860 if ($sheet1 == -1) {
861 throw new PHPExcel_Writer_Exception("Unknown sheet name $sheet_name1 in formula");
862 }
863 $sheet2 = $this->_getSheetIndex($sheet_name2);
864 if ($sheet2 == -1) {
865 throw new PHPExcel_Writer_Exception("Unknown sheet name $sheet_name2 in formula");
866 }
867
868 // Reverse max and min sheet numbers if necessary
869 if ($sheet1 > $sheet2) {
870 list($sheet1, $sheet2) = array($sheet2, $sheet1);
871 }
872 } else { // Single sheet name only.
873 $sheet1 = $this->_getSheetIndex($ext_ref);
874 if ($sheet1 == -1) {
875 throw new PHPExcel_Writer_Exception("Unknown sheet name $ext_ref in formula");
876 }
877 $sheet2 = $sheet1;
878 }
879
880 // assume all references belong to this document
881 $supbook_index = 0x00;
882 $ref = pack('vvv', $supbook_index, $sheet1, $sheet2);
883 $total_references = count($this->_references);
884 $index = -1;
885 for ($i = 0; $i < $total_references; ++$i) {
886 if ($ref == $this->_references[$i]) {
887 $index = $i;
888 break;
889 }
890 }
891 // if REF was not found add it to references array
892 if ($index == -1) {
893 $this->_references[$total_references] = $ref;
894 $index = $total_references;
895 }
896
897 return pack('v', $index);
898 }
899
900 /**
901 * Look up the index that corresponds to an external sheet name. The hash of
902 * sheet names is updated by the addworksheet() method of the
903 * PHPExcel_Writer_Excel5_Workbook class.
904 *
905 * @access private
906 * @param string $sheet_name Sheet name
907 * @return integer The sheet index, -1 if the sheet was not found
908 */
909 function _getSheetIndex($sheet_name)
910 {
911 if (!isset($this->_ext_sheets[$sheet_name])) {
912 return -1;
913 } else {
914 return $this->_ext_sheets[$sheet_name];
915 }
916 }
917
918 /**
919 * This method is used to update the array of sheet names. It is
920 * called by the addWorksheet() method of the
921 * PHPExcel_Writer_Excel5_Workbook class.
922 *
923 * @access public
924 * @see PHPExcel_Writer_Excel5_Workbook::addWorksheet()
925 * @param string $name The name of the worksheet being added
926 * @param integer $index The index of the worksheet being added
927 */
928 function setExtSheet($name, $index)
929 {
930 $this->_ext_sheets[$name] = $index;
931 }
932
933 /**
934 * pack() row and column into the required 3 or 4 byte format.
935 *
936 * @access private
937 * @param string $cell The Excel cell reference to be packed
938 * @return array Array containing the row and column in packed() format
939 */
940 function _cellToPackedRowcol($cell)
941 {
942 $cell = strtoupper($cell);
943 list($row, $col, $row_rel, $col_rel) = $this->_cellToRowcol($cell);
944 if ($col >= 256) {
945 throw new PHPExcel_Writer_Exception("Column in: $cell greater than 255");
946 }
947 if ($row >= 65536) {
948 throw new PHPExcel_Writer_Exception("Row in: $cell greater than 65536 ");
949 }
950
951 // Set the high bits to indicate if row or col are relative.
952 $col |= $col_rel << 14;
953 $col |= $row_rel << 15;
954 $col = pack('v', $col);
955
956 $row = pack('v', $row);
957
958 return array($row, $col);
959 }
960
961 /**
962 * pack() row range into the required 3 or 4 byte format.
963 * Just using maximum col/rows, which is probably not the correct solution
964 *
965 * @access private
966 * @param string $range The Excel range to be packed
967 * @return array Array containing (row1,col1,row2,col2) in packed() format
968 */
969 function _rangeToPackedRange($range)
970 {
971 preg_match('/(\$)?(\d+)\:(\$)?(\d+)/', $range, $match);
972 // return absolute rows if there is a $ in the ref
973 $row1_rel = empty($match[1]) ? 1 : 0;
974 $row1 = $match[2];
975 $row2_rel = empty($match[3]) ? 1 : 0;
976 $row2 = $match[4];
977 // Convert 1-index to zero-index
978 --$row1;
979 --$row2;
980 // Trick poor inocent Excel
981 $col1 = 0;
982 $col2 = 65535; // FIXME: maximum possible value for Excel 5 (change this!!!)
983
984 // FIXME: this changes for BIFF8
985 if (($row1 >= 65536) or ($row2 >= 65536)) {
986 throw new PHPExcel_Writer_Exception("Row in: $range greater than 65536 ");
987 }
988
989 // Set the high bits to indicate if rows are relative.
990 $col1 |= $row1_rel << 15;
991 $col2 |= $row2_rel << 15;
992 $col1 = pack('v', $col1);
993 $col2 = pack('v', $col2);
994
995 $row1 = pack('v', $row1);
996 $row2 = pack('v', $row2);
997
998 return array($row1, $col1, $row2, $col2);
999 }
1000
1001 /**
1002 * Convert an Excel cell reference such as A1 or $B2 or C$3 or $D$4 to a zero
1003 * indexed row and column number. Also returns two (0,1) values to indicate
1004 * whether the row or column are relative references.
1005 *
1006 * @access private
1007 * @param string $cell The Excel cell reference in A1 format.
1008 * @return array
1009 */
1010 function _cellToRowcol($cell)
1011 {
1012 preg_match('/(\$)?([A-I]?[A-Z])(\$)?(\d+)/',$cell,$match);
1013 // return absolute column if there is a $ in the ref
1014 $col_rel = empty($match[1]) ? 1 : 0;
1015 $col_ref = $match[2];
1016 $row_rel = empty($match[3]) ? 1 : 0;
1017 $row = $match[4];
1018
1019 // Convert base26 column string to a number.
1020 $expn = strlen($col_ref) - 1;
1021 $col = 0;
1022 $col_ref_length = strlen($col_ref);
1023 for ($i = 0; $i < $col_ref_length; ++$i) {
1024 $col += (ord($col_ref{$i}) - 64) * pow(26, $expn);
1025 --$expn;
1026 }
1027
1028 // Convert 1-index to zero-index
1029 --$row;
1030 --$col;
1031
1032 return array($row, $col, $row_rel, $col_rel);
1033 }
1034
1035 /**
1036 * Advance to the next valid token.
1037 *
1038 * @access private
1039 */
1040 function _advance()
1041 {
1042 $i = $this->_current_char;
1043 $formula_length = strlen($this->_formula);
1044 // eat up white spaces
1045 if ($i < $formula_length) {
1046 while ($this->_formula{$i} == " ") {
1047 ++$i;
1048 }
1049
1050 if ($i < ($formula_length - 1)) {
1051 $this->_lookahead = $this->_formula{$i+1};
1052 }
1053 $token = '';
1054 }
1055
1056 while ($i < $formula_length) {
1057 $token .= $this->_formula{$i};
1058
1059 if ($i < ($formula_length - 1)) {
1060 $this->_lookahead = $this->_formula{$i+1};
1061 } else {
1062 $this->_lookahead = '';
1063 }
1064
1065 if ($this->_match($token) != '') {
1066 //if ($i < strlen($this->_formula) - 1) {
1067 // $this->_lookahead = $this->_formula{$i+1};
1068 //}
1069 $this->_current_char = $i + 1;
1070 $this->_current_token = $token;
1071 return 1;
1072 }
1073
1074 if ($i < ($formula_length - 2)) {
1075 $this->_lookahead = $this->_formula{$i+2};
1076 } else { // if we run out of characters _lookahead becomes empty
1077 $this->_lookahead = '';
1078 }
1079 ++$i;
1080 }
1081 //die("Lexical error ".$this->_current_char);
1082 }
1083
1084 /**
1085 * Checks if it's a valid token.
1086 *
1087 * @access private
1088 * @param mixed $token The token to check.
1089 * @return mixed The checked token or false on failure
1090 */
1091 function _match($token)
1092 {
1093 switch($token) {
1094 case "+":
1095 case "-":
1096 case "*":
1097 case "/":
1098 case "(":
1099 case ")":
1100 case ",":
1101 case ";":
1102 case ">=":
1103 case "<=":
1104 case "=":
1105 case "<>":
1106 case "^":
1107 case "&":
1108 case "%":
1109 return $token;
1110 break;
1111 case ">":
1112 if ($this->_lookahead == '=') { // it's a GE token
1113 break;
1114 }
1115 return $token;
1116 break;
1117 case "<":
1118 // it's a LE or a NE token
1119 if (($this->_lookahead == '=') or ($this->_lookahead == '>')) {
1120 break;
1121 }
1122 return $token;
1123 break;
1124 default:
1125 // if it's a reference A1 or $A$1 or $A1 or A$1
1126 if (preg_match('/^\$?[A-Ia-i]?[A-Za-z]\$?[0-9]+$/',$token) and
1127 !preg_match("/[0-9]/",$this->_lookahead) and
1128 ($this->_lookahead != ':') and ($this->_lookahead != '.') and
1129 ($this->_lookahead != '!'))
1130 {
1131 return $token;
1132 }
1133 // If it's an external reference (Sheet1!A1 or Sheet1:Sheet2!A1 or Sheet1!$A$1 or Sheet1:Sheet2!$A$1)
1134 elseif (preg_match("/^" . self::REGEX_SHEET_TITLE_UNQUOTED . "(\:" . self::REGEX_SHEET_TITLE_UNQUOTED . ")?\!\\$?[A-Ia-i]?[A-Za-z]\\$?[0-9]+$/u",$token) and
1135 !preg_match("/[0-9]/",$this->_lookahead) and
1136 ($this->_lookahead != ':') and ($this->_lookahead != '.'))
1137 {
1138 return $token;
1139 }
1140 // If it's an external reference ('Sheet1'!A1 or 'Sheet1:Sheet2'!A1 or 'Sheet1'!$A$1 or 'Sheet1:Sheet2'!$A$1)
1141 elseif (preg_match("/^'" . self::REGEX_SHEET_TITLE_QUOTED . "(\:" . self::REGEX_SHEET_TITLE_QUOTED . ")?'\!\\$?[A-Ia-i]?[A-Za-z]\\$?[0-9]+$/u",$token) and
1142 !preg_match("/[0-9]/",$this->_lookahead) and
1143 ($this->_lookahead != ':') and ($this->_lookahead != '.'))
1144 {
1145 return $token;
1146 }
1147 // if it's a range A1:A2 or $A$1:$A$2
1148 elseif (preg_match('/^(\$)?[A-Ia-i]?[A-Za-z](\$)?[0-9]+:(\$)?[A-Ia-i]?[A-Za-z](\$)?[0-9]+$/', $token) and
1149 !preg_match("/[0-9]/",$this->_lookahead))
1150 {
1151 return $token;
1152 }
1153 // If it's an external range like Sheet1!A1:B2 or Sheet1:Sheet2!A1:B2 or Sheet1!$A$1:$B$2 or Sheet1:Sheet2!$A$1:$B$2
1154 elseif (preg_match("/^" . self::REGEX_SHEET_TITLE_UNQUOTED . "(\:" . self::REGEX_SHEET_TITLE_UNQUOTED . ")?\!\\$?([A-Ia-i]?[A-Za-z])?\\$?[0-9]+:\\$?([A-Ia-i]?[A-Za-z])?\\$?[0-9]+$/u",$token) and
1155 !preg_match("/[0-9]/",$this->_lookahead))
1156 {
1157 return $token;
1158 }
1159 // If it's an external range like 'Sheet1'!A1:B2 or 'Sheet1:Sheet2'!A1:B2 or 'Sheet1'!$A$1:$B$2 or 'Sheet1:Sheet2'!$A$1:$B$2
1160 elseif (preg_match("/^'" . self::REGEX_SHEET_TITLE_QUOTED . "(\:" . self::REGEX_SHEET_TITLE_QUOTED . ")?'\!\\$?([A-Ia-i]?[A-Za-z])?\\$?[0-9]+:\\$?([A-Ia-i]?[A-Za-z])?\\$?[0-9]+$/u",$token) and
1161 !preg_match("/[0-9]/",$this->_lookahead))
1162 {
1163 return $token;
1164 }
1165 // If it's a number (check that it's not a sheet name or range)
1166 elseif (is_numeric($token) and
1167 (!is_numeric($token.$this->_lookahead) or ($this->_lookahead == '')) and
1168 ($this->_lookahead != '!') and ($this->_lookahead != ':'))
1169 {
1170 return $token;
1171 }
1172 // If it's a string (of maximum 255 characters)
1173 elseif (preg_match("/\"([^\"]|\"\"){0,255}\"/",$token) and $this->_lookahead != '"' and (substr_count($token, '"')%2 == 0))
1174 {
1175 return $token;
1176 }
1177 // If it's an error code
1178 elseif (preg_match("/^#[A-Z0\/]{3,5}[!?]{1}$/", $token) or $token == '#N/A')
1179 {
1180 return $token;
1181 }
1182 // if it's a function call
1183 elseif (preg_match("/^[A-Z0-9\xc0-\xdc\.]+$/i",$token) and ($this->_lookahead == "("))
1184 {
1185 return $token;
1186 }
1187 // It's an argument of some description (e.g. a named range),
1188 // precise nature yet to be determined
1189 elseif(substr($token,-1) == ')') {
1190 return $token;
1191 }
1192 return '';
1193 }
1194 }
1195
1196 /**
1197 * The parsing method. It parses a formula.
1198 *
1199 * @access public
1200 * @param string $formula The formula to parse, without the initial equal
1201 * sign (=).
1202 * @return mixed true on success
1203 */
1204 function parse($formula)
1205 {
1206 $this->_current_char = 0;
1207 $this->_formula = $formula;
1208 $this->_lookahead = isset($formula{1}) ? $formula{1} : '';
1209 $this->_advance();
1210 $this->_parse_tree = $this->_condition();
1211 return true;
1212 }
1213
1214 /**
1215 * It parses a condition. It assumes the following rule:
1216 * Cond -> Expr [(">" | "<") Expr]
1217 *
1218 * @access private
1219 * @return mixed The parsed ptg'd tree on success
1220 */
1221 function _condition()
1222 {
1223 $result = $this->_expression();
1224 if ($this->_current_token == "<") {
1225 $this->_advance();
1226 $result2 = $this->_expression();
1227 $result = $this->_createTree('ptgLT', $result, $result2);
1228 } elseif ($this->_current_token == ">") {
1229 $this->_advance();
1230 $result2 = $this->_expression();
1231 $result = $this->_createTree('ptgGT', $result, $result2);
1232 } elseif ($this->_current_token == "<=") {
1233 $this->_advance();
1234 $result2 = $this->_expression();
1235 $result = $this->_createTree('ptgLE', $result, $result2);
1236 } elseif ($this->_current_token == ">=") {
1237 $this->_advance();
1238 $result2 = $this->_expression();
1239 $result = $this->_createTree('ptgGE', $result, $result2);
1240 } elseif ($this->_current_token == "=") {
1241 $this->_advance();
1242 $result2 = $this->_expression();
1243 $result = $this->_createTree('ptgEQ', $result, $result2);
1244 } elseif ($this->_current_token == "<>") {
1245 $this->_advance();
1246 $result2 = $this->_expression();
1247 $result = $this->_createTree('ptgNE', $result, $result2);
1248 } elseif ($this->_current_token == "&") {
1249 $this->_advance();
1250 $result2 = $this->_expression();
1251 $result = $this->_createTree('ptgConcat', $result, $result2);
1252 }
1253 return $result;
1254 }
1255
1256 /**
1257 * It parses a expression. It assumes the following rule:
1258 * Expr -> Term [("+" | "-") Term]
1259 * -> "string"
1260 * -> "-" Term : Negative value
1261 * -> "+" Term : Positive value
1262 * -> Error code
1263 *
1264 * @access private
1265 * @return mixed The parsed ptg'd tree on success
1266 */
1267 function _expression()
1268 {
1269 // If it's a string return a string node
1270 if (preg_match("/\"([^\"]|\"\"){0,255}\"/", $this->_current_token)) {
1271 $tmp = str_replace('""', '"', $this->_current_token);
1272 if (($tmp == '"') || ($tmp == '')) $tmp = '""'; // Trap for "" that has been used for an empty string
1273 $result = $this->_createTree($tmp, '', '');
1274 $this->_advance();
1275 return $result;
1276 // If it's an error code
1277 } elseif (preg_match("/^#[A-Z0\/]{3,5}[!?]{1}$/", $this->_current_token) or $this->_current_token == '#N/A'){
1278 $result = $this->_createTree($this->_current_token, 'ptgErr', '');
1279 $this->_advance();
1280 return $result;
1281 // If it's a negative value
1282 } elseif ($this->_current_token == "-") {
1283 // catch "-" Term
1284 $this->_advance();
1285 $result2 = $this->_expression();
1286 $result = $this->_createTree('ptgUminus', $result2, '');
1287 return $result;
1288 // If it's a positive value
1289 } elseif ($this->_current_token == "+") {
1290 // catch "+" Term
1291 $this->_advance();
1292 $result2 = $this->_expression();
1293 $result = $this->_createTree('ptgUplus', $result2, '');
1294 return $result;
1295 }
1296 $result = $this->_term();
1297 while (($this->_current_token == "+") or
1298 ($this->_current_token == "-") or
1299 ($this->_current_token == "^")) {
1300 /**/
1301 if ($this->_current_token == "+") {
1302 $this->_advance();
1303 $result2 = $this->_term();
1304 $result = $this->_createTree('ptgAdd', $result, $result2);
1305 } elseif ($this->_current_token == "-") {
1306 $this->_advance();
1307 $result2 = $this->_term();
1308 $result = $this->_createTree('ptgSub', $result, $result2);
1309 } else {
1310 $this->_advance();
1311 $result2 = $this->_term();
1312 $result = $this->_createTree('ptgPower', $result, $result2);
1313 }
1314 }
1315 return $result;
1316 }
1317
1318 /**
1319 * This function just introduces a ptgParen element in the tree, so that Excel
1320 * doesn't get confused when working with a parenthesized formula afterwards.
1321 *
1322 * @access private
1323 * @see _fact()
1324 * @return array The parsed ptg'd tree
1325 */
1326 function _parenthesizedExpression()
1327 {
1328 $result = $this->_createTree('ptgParen', $this->_expression(), '');
1329 return $result;
1330 }
1331
1332 /**
1333 * It parses a term. It assumes the following rule:
1334 * Term -> Fact [("*" | "/") Fact]
1335 *
1336 * @access private
1337 * @return mixed The parsed ptg'd tree on success
1338 */
1339 function _term()
1340 {
1341 $result = $this->_fact();
1342 while (($this->_current_token == "*") or
1343 ($this->_current_token == "/")) {
1344 /**/
1345 if ($this->_current_token == "*") {
1346 $this->_advance();
1347 $result2 = $this->_fact();
1348 $result = $this->_createTree('ptgMul', $result, $result2);
1349 } else {
1350 $this->_advance();
1351 $result2 = $this->_fact();
1352 $result = $this->_createTree('ptgDiv', $result, $result2);
1353 }
1354 }
1355 return $result;
1356 }
1357
1358 /**
1359 * It parses a factor. It assumes the following rule:
1360 * Fact -> ( Expr )
1361 * | CellRef
1362 * | CellRange
1363 * | Number
1364 * | Function
1365 *
1366 * @access private
1367 * @return mixed The parsed ptg'd tree on success
1368 */
1369 function _fact()
1370 {
1371 if ($this->_current_token == "(") {
1372 $this->_advance(); // eat the "("
1373 $result = $this->_parenthesizedExpression();
1374 if ($this->_current_token != ")") {
1375 throw new PHPExcel_Writer_Exception("')' token expected.");
1376 }
1377 $this->_advance(); // eat the ")"
1378 return $result;
1379 }
1380 // if it's a reference
1381 if (preg_match('/^\$?[A-Ia-i]?[A-Za-z]\$?[0-9]+$/',$this->_current_token))
1382 {
1383 $result = $this->_createTree($this->_current_token, '', '');
1384 $this->_advance();
1385 return $result;
1386 }
1387 // If it's an external reference (Sheet1!A1 or Sheet1:Sheet2!A1 or Sheet1!$A$1 or Sheet1:Sheet2!$A$1)
1388 elseif (preg_match("/^" . self::REGEX_SHEET_TITLE_UNQUOTED . "(\:" . self::REGEX_SHEET_TITLE_UNQUOTED . ")?\!\\$?[A-Ia-i]?[A-Za-z]\\$?[0-9]+$/u",$this->_current_token))
1389 {
1390 $result = $this->_createTree($this->_current_token, '', '');
1391 $this->_advance();
1392 return $result;
1393 }
1394 // If it's an external reference ('Sheet1'!A1 or 'Sheet1:Sheet2'!A1 or 'Sheet1'!$A$1 or 'Sheet1:Sheet2'!$A$1)
1395 elseif (preg_match("/^'" . self::REGEX_SHEET_TITLE_QUOTED . "(\:" . self::REGEX_SHEET_TITLE_QUOTED . ")?'\!\\$?[A-Ia-i]?[A-Za-z]\\$?[0-9]+$/u",$this->_current_token))
1396 {
1397 $result = $this->_createTree($this->_current_token, '', '');
1398 $this->_advance();
1399 return $result;
1400 }
1401 // if it's a range A1:B2 or $A$1:$B$2
1402 elseif (preg_match('/^(\$)?[A-Ia-i]?[A-Za-z](\$)?[0-9]+:(\$)?[A-Ia-i]?[A-Za-z](\$)?[0-9]+$/',$this->_current_token) or
1403 preg_match('/^(\$)?[A-Ia-i]?[A-Za-z](\$)?[0-9]+\.\.(\$)?[A-Ia-i]?[A-Za-z](\$)?[0-9]+$/',$this->_current_token))
1404 {
1405 // must be an error?
1406 $result = $this->_createTree($this->_current_token, '', '');
1407 $this->_advance();
1408 return $result;
1409 }
1410 // If it's an external range (Sheet1!A1:B2 or Sheet1:Sheet2!A1:B2 or Sheet1!$A$1:$B$2 or Sheet1:Sheet2!$A$1:$B$2)
1411 elseif (preg_match("/^" . self::REGEX_SHEET_TITLE_UNQUOTED . "(\:" . self::REGEX_SHEET_TITLE_UNQUOTED . ")?\!\\$?([A-Ia-i]?[A-Za-z])?\\$?[0-9]+:\\$?([A-Ia-i]?[A-Za-z])?\\$?[0-9]+$/u",$this->_current_token))
1412 {
1413 // must be an error?
1414 //$result = $this->_current_token;
1415 $result = $this->_createTree($this->_current_token, '', '');
1416 $this->_advance();
1417 return $result;
1418 }
1419 // If it's an external range ('Sheet1'!A1:B2 or 'Sheet1'!A1:B2 or 'Sheet1'!$A$1:$B$2 or 'Sheet1'!$A$1:$B$2)
1420 elseif (preg_match("/^'" . self::REGEX_SHEET_TITLE_QUOTED . "(\:" . self::REGEX_SHEET_TITLE_QUOTED . ")?'\!\\$?([A-Ia-i]?[A-Za-z])?\\$?[0-9]+:\\$?([A-Ia-i]?[A-Za-z])?\\$?[0-9]+$/u",$this->_current_token))
1421 {
1422 // must be an error?
1423 //$result = $this->_current_token;
1424 $result = $this->_createTree($this->_current_token, '', '');
1425 $this->_advance();
1426 return $result;
1427 }
1428 // If it's a number or a percent
1429 elseif (is_numeric($this->_current_token))
1430 {
1431 if($this->_lookahead == '%'){
1432 $result = $this->_createTree('ptgPercent', $this->_current_token, '');
1433 $this->_advance(); // Skip the percentage operator once we've pre-built that tree
1434 } else {
1435 $result = $this->_createTree($this->_current_token, '', '');
1436 }
1437 $this->_advance();
1438 return $result;
1439 }
1440 // if it's a function call
1441 elseif (preg_match("/^[A-Z0-9\xc0-\xdc\.]+$/i",$this->_current_token))
1442 {
1443 $result = $this->_func();
1444 return $result;
1445 }
1446 throw new PHPExcel_Writer_Exception("Syntax error: ".$this->_current_token.
1447 ", lookahead: ".$this->_lookahead.
1448 ", current char: ".$this->_current_char);
1449 }
1450
1451 /**
1452 * It parses a function call. It assumes the following rule:
1453 * Func -> ( Expr [,Expr]* )
1454 *
1455 * @access private
1456 * @return mixed The parsed ptg'd tree on success
1457 */
1458 function _func()
1459 {
1460 $num_args = 0; // number of arguments received
1461 $function = strtoupper($this->_current_token);
1462 $result = ''; // initialize result
1463 $this->_advance();
1464 $this->_advance(); // eat the "("
1465 while ($this->_current_token != ')') {
1466 /**/
1467 if ($num_args > 0) {
1468 if ($this->_current_token == "," or
1469 $this->_current_token == ";")
1470 {
1471 $this->_advance(); // eat the "," or ";"
1472 } else {
1473 throw new PHPExcel_Writer_Exception("Syntax error: comma expected in ".
1474 "function $function, arg #{$num_args}");
1475 }
1476 $result2 = $this->_condition();
1477 $result = $this->_createTree('arg', $result, $result2);
1478 } else { // first argument
1479 $result2 = $this->_condition();
1480 $result = $this->_createTree('arg', '', $result2);
1481 }
1482 ++$num_args;
1483 }
1484 if (!isset($this->_functions[$function])) {
1485 throw new PHPExcel_Writer_Exception("Function $function() doesn't exist");
1486 }
1487 $args = $this->_functions[$function][1];
1488 // If fixed number of args eg. TIME($i,$j,$k). Check that the number of args is valid.
1489 if (($args >= 0) and ($args != $num_args)) {
1490 throw new PHPExcel_Writer_Exception("Incorrect number of arguments in function $function() ");
1491 }
1492
1493 $result = $this->_createTree($function, $result, $num_args);
1494 $this->_advance(); // eat the ")"
1495 return $result;
1496 }
1497
1498 /**
1499 * Creates a tree. In fact an array which may have one or two arrays (sub-trees)
1500 * as elements.
1501 *
1502 * @access private
1503 * @param mixed $value The value of this node.
1504 * @param mixed $left The left array (sub-tree) or a final node.
1505 * @param mixed $right The right array (sub-tree) or a final node.
1506 * @return array A tree
1507 */
1508 function _createTree($value, $left, $right)
1509 {
1510 return array('value' => $value, 'left' => $left, 'right' => $right);
1511 }
1512
1513 /**
1514 * Builds a string containing the tree in reverse polish notation (What you
1515 * would use in a HP calculator stack).
1516 * The following tree:
1517 *
1518 * +
1519 * / \
1520 * 2 3
1521 *
1522 * produces: "23+"
1523 *
1524 * The following tree:
1525 *
1526 * +
1527 * / \
1528 * 3 *
1529 * / \
1530 * 6 A1
1531 *
1532 * produces: "36A1*+"
1533 *
1534 * In fact all operands, functions, references, etc... are written as ptg's
1535 *
1536 * @access public
1537 * @param array $tree The optional tree to convert.
1538 * @return string The tree in reverse polish notation
1539 */
1540 function toReversePolish($tree = array())
1541 {
1542 $polish = ""; // the string we are going to return
1543 if (empty($tree)) { // If it's the first call use _parse_tree
1544 $tree = $this->_parse_tree;
1545 }
1546
1547 if (is_array($tree['left'])) {
1548 $converted_tree = $this->toReversePolish($tree['left']);
1549 $polish .= $converted_tree;
1550 } elseif ($tree['left'] != '') { // It's a final node
1551 $converted_tree = $this->_convert($tree['left']);
1552 $polish .= $converted_tree;
1553 }
1554 if (is_array($tree['right'])) {
1555 $converted_tree = $this->toReversePolish($tree['right']);
1556 $polish .= $converted_tree;
1557 } elseif ($tree['right'] != '') { // It's a final node
1558 $converted_tree = $this->_convert($tree['right']);
1559 $polish .= $converted_tree;
1560 }
1561 // if it's a function convert it here (so we can set it's arguments)
1562 if (preg_match("/^[A-Z0-9\xc0-\xdc\.]+$/",$tree['value']) and
1563 !preg_match('/^([A-Ia-i]?[A-Za-z])(\d+)$/',$tree['value']) and
1564 !preg_match("/^[A-Ia-i]?[A-Za-z](\d+)\.\.[A-Ia-i]?[A-Za-z](\d+)$/",$tree['value']) and
1565 !is_numeric($tree['value']) and
1566 !isset($this->ptg[$tree['value']]))
1567 {
1568 // left subtree for a function is always an array.
1569 if ($tree['left'] != '') {
1570 $left_tree = $this->toReversePolish($tree['left']);
1571 } else {
1572 $left_tree = '';
1573 }
1574 // add it's left subtree and return.
1575 return $left_tree.$this->_convertFunction($tree['value'], $tree['right']);
1576 } else {
1577 $converted_tree = $this->_convert($tree['value']);
1578 }
1579 $polish .= $converted_tree;
1580 return $polish;
1581 }
1582
1583 }
1584