1 <?php
3 /*
4 * Copyleft 2002 Johann Hanne
5 *
6 * This is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
10 *
11 * This software is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this software; if not, write to the
18 * Free Software Foundation, Inc., 59 Temple Place,
19 * Suite 330, Boston, MA 02111-1307 USA
20 */
22 /* This file contains source from the PEAR::Spreadsheet class Parser.php file version 0.4 .
23 The raiseError was replaced by triggerError function.
24 The PEAR::isError was imported to keep compatibility to PEAR::Spreadsheet class
26 Imported and adapted by Andreas Brodowski 2003 (andreas.brodowski@oscar-gmbh.com).
28 There should be no license rights in question because the Parser.php from PEAR class is
29 published under GNU License the same way like this class.
31 Changes: 03/08/27 Added SPREADSHEET_EXCEL_WRITER_SCOLON for arg seperation in excel functions
32 */
34 /*
35 * This is the Spreadsheet::WriteExcel Perl package ported to PHP
36 * Spreadsheet::WriteExcel was written by John McNamara, jmcnamara@cpan.org
37 */
39 define('SPREADSHEET_EXCEL_WRITER_ADD',"+");\r // @const SPREADSHEET_EXCEL_WRITER_ADD token identifier for character "+"\r
40 define('SPREADSHEET_EXCEL_WRITER_SUB',"-");\r // @const SPREADSHEET_EXCEL_WRITER_SUB token identifier for character "-"\r
41 define('SPREADSHEET_EXCEL_WRITER_MUL',"*");\r // @const SPREADSHEET_EXCEL_WRITER_MUL token identifier for character "*"\r
42 define('SPREADSHEET_EXCEL_WRITER_DIV',"/");\r // @const SPREADSHEET_EXCEL_WRITER_DIV token identifier for character "/"\r
43 define('SPREADSHEET_EXCEL_WRITER_OPEN',"(");\r // @const SPREADSHEET_EXCEL_WRITER_OPEN token identifier for character "("\r
44 define('SPREADSHEET_EXCEL_WRITER_CLOSE',")"); \r // @const SPREADSHEET_EXCEL_WRITER_CLOSE token identifier for character ")"\r
45 define('SPREADSHEET_EXCEL_WRITER_COMA',",");\r // @const SPREADSHEET_EXCEL_WRITER_COMA token identifier for character ","\r
46 define('SPREADSHEET_EXCEL_WRITER_SCOLON',";"); \r// @const SPREADSHEET_EXCEL_WRITER_SCOLON token identifier for character ";"\r
47 define('SPREADSHEET_EXCEL_WRITER_GT',">");\r // @const SPREADSHEET_EXCEL_WRITER_GT token identifier for character ">"\r
48 define('SPREADSHEET_EXCEL_WRITER_LT',"<");\r // @const SPREADSHEET_EXCEL_WRITER_LT token identifier for character "<"\r
49 define('SPREADSHEET_EXCEL_WRITER_LE',"<=");\r // @const SPREADSHEET_EXCEL_WRITER_LE token identifier for character "<="\r
50 define('SPREADSHEET_EXCEL_WRITER_GE',">=");\r // @const SPREADSHEET_EXCEL_WRITER_GE token identifier for character ">="\r
51 define('SPREADSHEET_EXCEL_WRITER_EQ',"=");\r // @const SPREADSHEET_EXCEL_WRITER_EQ token identifier for character "="\r
52 define('SPREADSHEET_EXCEL_WRITER_NE',"<>");\r // @const SPREADSHEET_EXCEL_WRITER_NE token identifier for character "<>"\r
53 \r
55 class writeexcel_formula {
57 ###############################################################################
58 #
59 # Class data.
60 #
61 var $parser;
62 var $ptg;
63 var $_functions;
64 var $_current_char;
65 var $_current_token;
66 var $_lookahead;
67 var $_debug;
68 var $_byte_order;
69 var $_volatile;
70 var $_workbook;
71 var $_ext_sheets;
72 var $_formula;
74 ###############################################################################
75 #
76 # new()
77 #
78 # Constructor
79 #
80 function writeexcel_formula($byte_order) {
82 $this->parser = false;
83 $this->ptg = array();
84 $this->_functions = array();
85 $this->_debug = 0;
86 $this->_byte_order = $byte_order;
87 $this->_volatile = 0;
88 $this->_workbook = "";
89 $this->_ext_sheets = array();
90 $this->_current_token = '';
91 $this->_lookahead = '';
92 $this->_current_char = 0;
93 $this->_formula = '';
94 }
96 ###############################################################################
97 #
98 # _init_parser()
99 #
100 # There is a small overhead involved in generating the parser. Therefore, the
101 # initialisation is delayed until a formula is required. TODO: use a pre-
102 # compiled header.
103 #
104 function _init_parser() {
106 $this->_initializeHashes();
109 if ($this->_debug) {
110 print "Init_parser.\n\n";
111 }
112 }
114 ###############################################################################
115 #
116 # parse_formula()
117 #
118 # This is the only public method. It takes a textual description of a formula
119 # and returns a RPN encoded byte string.
120 #
121 function parse_formula() {
123 $_=func_get_args();
125 # Initialise the parser if this is the first call
126 if ($this->parser===false) {
127 $this->_init_parser();
128 }
130 $formula = array_shift($_);
131 //$str;
132 //$tokens;
134 if ($this->_debug) {
135 print "$formula\n";
136 }
138 # Build the parse tree for the formula
140 $this->_formula = $formula;
141 $this->_current_char = 0;
142 $this->_lookahead = $this->_formula{1};
143 $this->_advance($formula);
144 $parsetree = $this->_condition();
146 $str = $this->toReversePolish($parsetree);
148 return $str;
149 }
151 function set_ext_sheet($key, $value) {
153 $this->_ext_sheets->$key = $value;
154 }
156 function isError($data) {
157 return (bool)(is_object($data) &&
158 (get_class($data) == 'pear_error' ||
159 is_subclass_of($data, 'pear_error')));
160 }
161 \r
162 /**\r
163 * Class for parsing Excel formulas\r
164 *\r
165 * @author Xavier Noguer <xnoguer@rezebra.com>\r
166 * @category FileFormats\r
167 * @package Spreadsheet_Excel_Writer\r
168 */\r
169 \r
170 \r
171 /**\r
172 * Initialize the ptg and function hashes. \r
173 *\r
174 * @access private\r
175 */\r
177 // The Excel ptg indices\r
178 $this->ptg = array(\r
191 \r 'ptgArea3d' => 0x3B,\r 'ptgRefErr3d' => 0x3C,\r 'ptgAreaErr3d' => 0x3D,\r 'ptgArrayV' => 0x40,\r
193 'ptgAreaV' => 0x45,\r 'ptgMemAreaV' => 0x46,\r 'ptgMemErrV' => 0x47,\r 'ptgMemNoMemV' => 0x48,\r
195 'ptgAreaNV' => 0x4D,\r 'ptgMemAreaNV' => 0x4E,\r 'ptgMemNoMemN' => 0x4F,\r 'ptgFuncCEV' => 0x58,\r
199 'ptgMemErrA' => 0x67,\r 'ptgMemNoMemA' => 0x68,\r 'ptgMemFuncA' => 0x69,\r 'ptgRefErrA' => 0x6A,\r
203 );\r
204 \r
205 // Thanks to Michael Meeks and Gnumeric for the initial arg values.\r
206 //\r
207 // The following hash was generated by "function_locale.pl" in the distro.\r
208 // Refer to function_locale.pl for non-English function names.\r
209 //\r
210 // The array elements are as follow:\r
211 // ptg: The Excel function ptg code.\r
212 // args: The number of arguments that the function takes:\r
213 // >=0 is a fixed number of arguments.\r
214 // -1 is a variable number of arguments.\r
215 // class: The reference, value or array class of the function args.\r
216 // vol: The function is volatile.\r
217 //\r
218 $this->_functions = array(\r
219 // function ptg args class vol\r
220 'COUNT' => array( 0, -1, 0, 0 ),\r
221 'IF' => array( 1, -1, 1, 0 ),\r
222 'ISNA' => array( 2, 1, 1, 0 ),\r
223 'ISERROR' => array( 3, 1, 1, 0 ),\r
224 'SUM' => array( 4, -1, 0, 0 ),\r
225 'AVERAGE' => array( 5, -1, 0, 0 ),\r
226 'MIN' => array( 6, -1, 0, 0 ),\r
227 'MAX' => array( 7, -1, 0, 0 ),\r
228 'ROW' => array( 8, -1, 0, 0 ),\r
229 'COLUMN' => array( 9, -1, 0, 0 ),\r
230 'NA' => array( 10, 0, 0, 0 ),\r
231 'NPV' => array( 11, -1, 1, 0 ),\r
232 'STDEV' => array( 12, -1, 0, 0 ),\r
233 'DOLLAR' => array( 13, -1, 1, 0 ),\r
234 'FIXED' => array( 14, -1, 1, 0 ),\r
235 'SIN' => array( 15, 1, 1, 0 ),\r
236 'COS' => array( 16, 1, 1, 0 ),\r
237 'TAN' => array( 17, 1, 1, 0 ),\r
238 'ATAN' => array( 18, 1, 1, 0 ),\r
239 'PI' => array( 19, 0, 1, 0 ),\r
240 'SQRT' => array( 20, 1, 1, 0 ),\r
241 'EXP' => array( 21, 1, 1, 0 ),\r
242 'LN' => array( 22, 1, 1, 0 ),\r
243 'LOG10' => array( 23, 1, 1, 0 ),\r
244 'ABS' => array( 24, 1, 1, 0 ),\r
245 'INT' => array( 25, 1, 1, 0 ),\r
246 'SIGN' => array( 26, 1, 1, 0 ),\r
247 'ROUND' => array( 27, 2, 1, 0 ),\r
248 'LOOKUP' => array( 28, -1, 0, 0 ),\r
249 'INDEX' => array( 29, -1, 0, 1 ),\r
250 'REPT' => array( 30, 2, 1, 0 ),\r
251 'MID' => array( 31, 3, 1, 0 ),\r
252 'LEN' => array( 32, 1, 1, 0 ),\r
253 'VALUE' => array( 33, 1, 1, 0 ),\r
254 'TRUE' => array( 34, 0, 1, 0 ),\r
255 'FALSE' => array( 35, 0, 1, 0 ),\r
256 'AND' => array( 36, -1, 0, 0 ),\r
257 'OR' => array( 37, -1, 0, 0 ),\r
258 'NOT' => array( 38, 1, 1, 0 ),\r
259 'MOD' => array( 39, 2, 1, 0 ),\r
260 'DCOUNT' => array( 40, 3, 0, 0 ),\r
261 'DSUM' => array( 41, 3, 0, 0 ),\r
262 'DAVERAGE' => array( 42, 3, 0, 0 ),\r
263 'DMIN' => array( 43, 3, 0, 0 ),\r
264 'DMAX' => array( 44, 3, 0, 0 ),\r
265 'DSTDEV' => array( 45, 3, 0, 0 ),\r
266 'VAR' => array( 46, -1, 0, 0 ),\r
267 'DVAR' => array( 47, 3, 0, 0 ),\r
268 'TEXT' => array( 48, 2, 1, 0 ),\r
269 'LINEST' => array( 49, -1, 0, 0 ),\r
270 'TREND' => array( 50, -1, 0, 0 ),\r
271 'LOGEST' => array( 51, -1, 0, 0 ),\r
272 'GROWTH' => array( 52, -1, 0, 0 ),\r
273 'PV' => array( 56, -1, 1, 0 ),\r
274 'FV' => array( 57, -1, 1, 0 ),\r
275 'NPER' => array( 58, -1, 1, 0 ),\r
276 'PMT' => array( 59, -1, 1, 0 ),\r
277 'RATE' => array( 60, -1, 1, 0 ),\r
278 'MIRR' => array( 61, 3, 0, 0 ),\r
279 'IRR' => array( 62, -1, 0, 0 ),\r
280 'RAND' => array( 63, 0, 1, 1 ),\r
281 'MATCH' => array( 64, -1, 0, 0 ),\r
282 'DATE' => array( 65, 3, 1, 0 ),\r
283 'TIME' => array( 66, 3, 1, 0 ),\r
284 'DAY' => array( 67, 1, 1, 0 ),\r
285 'MONTH' => array( 68, 1, 1, 0 ),\r
286 'YEAR' => array( 69, 1, 1, 0 ),\r
287 'WEEKDAY' => array( 70, -1, 1, 0 ),\r
288 'HOUR' => array( 71, 1, 1, 0 ),\r
289 'MINUTE' => array( 72, 1, 1, 0 ),\r
290 'SECOND' => array( 73, 1, 1, 0 ),\r
291 'NOW' => array( 74, 0, 1, 1 ),\r
292 'AREAS' => array( 75, 1, 0, 1 ),\r
293 'ROWS' => array( 76, 1, 0, 1 ),\r
294 'COLUMNS' => array( 77, 1, 0, 1 ),\r
295 'OFFSET' => array( 78, -1, 0, 1 ),\r
296 'SEARCH' => array( 82, -1, 1, 0 ),\r
297 'TRANSPOSE' => array( 83, 1, 1, 0 ),\r
298 'TYPE' => array( 86, 1, 1, 0 ),\r
299 'ATAN2' => array( 97, 2, 1, 0 ),\r
300 'ASIN' => array( 98, 1, 1, 0 ),\r
301 'ACOS' => array( 99, 1, 1, 0 ),\r
302 'CHOOSE' => array( 100, -1, 1, 0 ),\r
303 'HLOOKUP' => array( 101, -1, 0, 0 ),\r
304 'VLOOKUP' => array( 102, -1, 0, 0 ),\r
305 'ISREF' => array( 105, 1, 0, 0 ),\r
306 'LOG' => array( 109, -1, 1, 0 ),\r
307 'CHAR' => array( 111, 1, 1, 0 ),\r
308 'LOWER' => array( 112, 1, 1, 0 ),\r
309 'UPPER' => array( 113, 1, 1, 0 ),\r
310 'PROPER' => array( 114, 1, 1, 0 ),\r
311 'LEFT' => array( 115, -1, 1, 0 ),\r
312 'RIGHT' => array( 116, -1, 1, 0 ),\r
313 'EXACT' => array( 117, 2, 1, 0 ),\r
314 'TRIM' => array( 118, 1, 1, 0 ),\r
315 'REPLACE' => array( 119, 4, 1, 0 ),\r
316 'SUBSTITUTE' => array( 120, -1, 1, 0 ),\r
317 'CODE' => array( 121, 1, 1, 0 ),\r
318 'FIND' => array( 124, -1, 1, 0 ),\r
319 'CELL' => array( 125, -1, 0, 1 ),\r
320 'ISERR' => array( 126, 1, 1, 0 ),\r
321 'ISTEXT' => array( 127, 1, 1, 0 ),\r
322 'ISNUMBER' => array( 128, 1, 1, 0 ),\r
323 'ISBLANK' => array( 129, 1, 1, 0 ),\r
324 'T' => array( 130, 1, 0, 0 ),\r
325 'N' => array( 131, 1, 0, 0 ),\r
326 'DATEVALUE' => array( 140, 1, 1, 0 ),\r
327 'TIMEVALUE' => array( 141, 1, 1, 0 ),\r
328 'SLN' => array( 142, 3, 1, 0 ),\r
329 'SYD' => array( 143, 4, 1, 0 ),\r
330 'DDB' => array( 144, -1, 1, 0 ),\r
331 'INDIRECT' => array( 148, -1, 1, 1 ),\r
332 'CALL' => array( 150, -1, 1, 0 ),\r
333 'CLEAN' => array( 162, 1, 1, 0 ),\r
334 'MDETERM' => array( 163, 1, 2, 0 ),\r
335 'MINVERSE' => array( 164, 1, 2, 0 ),\r
336 'MMULT' => array( 165, 2, 2, 0 ),\r
337 'IPMT' => array( 167, -1, 1, 0 ),\r
338 'PPMT' => array( 168, -1, 1, 0 ),\r
339 'COUNTA' => array( 169, -1, 0, 0 ),\r
340 'PRODUCT' => array( 183, -1, 0, 0 ),\r
341 'FACT' => array( 184, 1, 1, 0 ),\r
342 'DPRODUCT' => array( 189, 3, 0, 0 ),\r
343 'ISNONTEXT' => array( 190, 1, 1, 0 ),\r
344 'STDEVP' => array( 193, -1, 0, 0 ),\r
345 'VARP' => array( 194, -1, 0, 0 ),\r
346 'DSTDEVP' => array( 195, 3, 0, 0 ),\r
347 'DVARP' => array( 196, 3, 0, 0 ),\r
348 'TRUNC' => array( 197, -1, 1, 0 ),\r
349 'ISLOGICAL' => array( 198, 1, 1, 0 ),\r
350 'DCOUNTA' => array( 199, 3, 0, 0 ),\r
351 'ROUNDUP' => array( 212, 2, 1, 0 ),\r
352 'ROUNDDOWN' => array( 213, 2, 1, 0 ),\r
353 'RANK' => array( 216, -1, 0, 0 ),\r
354 'ADDRESS' => array( 219, -1, 1, 0 ),\r
355 'DAYS360' => array( 220, -1, 1, 0 ),\r
356 'TODAY' => array( 221, 0, 1, 1 ),\r
357 'VDB' => array( 222, -1, 1, 0 ),\r
358 'MEDIAN' => array( 227, -1, 0, 0 ),\r
359 'SUMPRODUCT' => array( 228, -1, 2, 0 ),\r
360 'SINH' => array( 229, 1, 1, 0 ),\r
361 'COSH' => array( 230, 1, 1, 0 ),\r
362 'TANH' => array( 231, 1, 1, 0 ),\r
363 'ASINH' => array( 232, 1, 1, 0 ),\r
364 'ACOSH' => array( 233, 1, 1, 0 ),\r
365 'ATANH' => array( 234, 1, 1, 0 ),\r
366 'DGET' => array( 235, 3, 0, 0 ),\r
367 'INFO' => array( 244, 1, 1, 1 ),\r
368 'DB' => array( 247, -1, 1, 0 ),\r
369 'FREQUENCY' => array( 252, 2, 0, 0 ),\r
370 'ERROR.TYPE' => array( 261, 1, 1, 0 ),\r
371 'REGISTER.ID' => array( 267, -1, 1, 0 ),\r
372 'AVEDEV' => array( 269, -1, 0, 0 ),\r
373 'BETADIST' => array( 270, -1, 1, 0 ),\r
374 'GAMMALN' => array( 271, 1, 1, 0 ),\r
375 'BETAINV' => array( 272, -1, 1, 0 ),\r
376 'BINOMDIST' => array( 273, 4, 1, 0 ),\r
377 'CHIDIST' => array( 274, 2, 1, 0 ),\r
378 'CHIINV' => array( 275, 2, 1, 0 ),\r
379 'COMBIN' => array( 276, 2, 1, 0 ),\r
380 'CONFIDENCE' => array( 277, 3, 1, 0 ),\r
381 'CRITBINOM' => array( 278, 3, 1, 0 ),\r
382 'EVEN' => array( 279, 1, 1, 0 ),\r
383 'EXPONDIST' => array( 280, 3, 1, 0 ),\r
384 'FDIST' => array( 281, 3, 1, 0 ),\r
385 'FINV' => array( 282, 3, 1, 0 ),\r
386 'FISHER' => array( 283, 1, 1, 0 ),\r
387 'FISHERINV' => array( 284, 1, 1, 0 ),\r
388 'FLOOR' => array( 285, 2, 1, 0 ),\r
389 'GAMMADIST' => array( 286, 4, 1, 0 ),\r
390 'GAMMAINV' => array( 287, 3, 1, 0 ),\r
391 'CEILING' => array( 288, 2, 1, 0 ),\r
392 'HYPGEOMDIST' => array( 289, 4, 1, 0 ),\r
393 'LOGNORMDIST' => array( 290, 3, 1, 0 ),\r
394 'LOGINV' => array( 291, 3, 1, 0 ),\r
395 'NEGBINOMDIST' => array( 292, 3, 1, 0 ),\r
396 'NORMDIST' => array( 293, 4, 1, 0 ),\r
397 'NORMSDIST' => array( 294, 1, 1, 0 ),\r
398 'NORMINV' => array( 295, 3, 1, 0 ),\r
399 'NORMSINV' => array( 296, 1, 1, 0 ),\r
400 'STANDARDIZE' => array( 297, 3, 1, 0 ),\r
401 'ODD' => array( 298, 1, 1, 0 ),\r
402 'PERMUT' => array( 299, 2, 1, 0 ),\r
403 'POISSON' => array( 300, 3, 1, 0 ),\r
404 'TDIST' => array( 301, 3, 1, 0 ),\r
405 'WEIBULL' => array( 302, 4, 1, 0 ),\r
406 'SUMXMY2' => array( 303, 2, 2, 0 ),\r
407 'SUMX2MY2' => array( 304, 2, 2, 0 ),\r
408 'SUMX2PY2' => array( 305, 2, 2, 0 ),\r
409 'CHITEST' => array( 306, 2, 2, 0 ),\r
410 'CORREL' => array( 307, 2, 2, 0 ),\r
411 'COVAR' => array( 308, 2, 2, 0 ),\r
412 'FORECAST' => array( 309, 3, 2, 0 ),\r
413 'FTEST' => array( 310, 2, 2, 0 ),\r
414 'INTERCEPT' => array( 311, 2, 2, 0 ),\r
415 'PEARSON' => array( 312, 2, 2, 0 ),\r
416 'RSQ' => array( 313, 2, 2, 0 ),\r
417 'STEYX' => array( 314, 2, 2, 0 ),\r
418 'SLOPE' => array( 315, 2, 2, 0 ),\r
419 'TTEST' => array( 316, 4, 2, 0 ),\r
420 'PROB' => array( 317, -1, 2, 0 ),\r
421 'DEVSQ' => array( 318, -1, 0, 0 ),\r
422 'GEOMEAN' => array( 319, -1, 0, 0 ),\r
423 'HARMEAN' => array( 320, -1, 0, 0 ),\r
424 'SUMSQ' => array( 321, -1, 0, 0 ),\r
425 'KURT' => array( 322, -1, 0, 0 ),\r
426 'SKEW' => array( 323, -1, 0, 0 ),\r
427 'ZTEST' => array( 324, -1, 0, 0 ),\r
428 'LARGE' => array( 325, 2, 0, 0 ),\r
429 'SMALL' => array( 326, 2, 0, 0 ),\r
430 'QUARTILE' => array( 327, 2, 0, 0 ),\r
431 'PERCENTILE' => array( 328, 2, 0, 0 ),\r
432 'PERCENTRANK' => array( 329, -1, 0, 0 ),\r
433 'MODE' => array( 330, -1, 2, 0 ),\r
434 'TRIMMEAN' => array( 331, 2, 0, 0 ),\r
435 'TINV' => array( 332, 2, 1, 0 ),\r
436 'CONCATENATE' => array( 336, -1, 1, 0 ),\r
437 'POWER' => array( 337, 2, 1, 0 ),\r
438 'RADIANS' => array( 342, 1, 1, 0 ),\r
439 'DEGREES' => array( 343, 1, 1, 0 ),\r
440 'SUBTOTAL' => array( 344, -1, 0, 0 ),\r
441 'SUMIF' => array( 345, -1, 0, 0 ),\r
442 'COUNTIF' => array( 346, 2, 0, 0 ),\r
443 'COUNTBLANK' => array( 347, 1, 0, 0 ),\r
444 'ROMAN' => array( 354, -1, 1, 0 )\r
445 );\r
446 }\r
447 \r
448 /**\r
449 * Convert a token to the proper ptg value.\r
450 *\r
451 * @access private\r
452 * @param mixed $token The token to convert.\r
453 * @return mixed the converted token on success. PEAR_Error if the token\r
454 * is not recognized\r
455 */\r
458 return $this->_convertString($token);\r
460 return $this->_convertNumber($token);\r
461 }\r
462 // match references like A1 or $A$1
464 return $this->_convertRef2d($token);\r
465 }\r
466 // match external references like Sheet1:Sheet2!A1\r
468 return $this->_convertRef3d($token);\r
469 }\r
470 // match ranges like A1:B2\r
471 elseif (preg_match("/^(\$)?[A-Ia-i]?[A-Za-z](\$)?(\d+)\:(\$)?[A-Ia-i]?[A-Za-z](\$)?(\d+)$/",$token))\r {\r
472 return $this->_convertRange2d($token);\r
473 }\r
474 // match ranges like A1..B2\r
475 elseif (preg_match("/^(\$)?[A-Ia-i]?[A-Za-z](\$)?(\d+)\.\.(\$)?[A-Ia-i]?[A-Za-z](\$)?(\d+)$/",$token))\r {\r
476 return $this->_convertRange2d($token);\r
477 }\r
478 // match external ranges like Sheet1:Sheet2!A1:B2\r
479 elseif (preg_match("/^[A-Za-z0-9_]+(\:[A-Za-z0-9_]+)?\!([A-Ia-i]?[A-Za-z])?(\d+)\:([A-Ia-i]?[A-Za-z])?(\d+)$/",$token))\r {\r
480 return $this->_convertRange3d($token);\r
481 }\r
482 // match external ranges like 'Sheet1:Sheet2'!A1:B2\r
483 elseif (preg_match("/^'[A-Za-z0-9_ ]+(\:[A-Za-z0-9_ ]+)?'\!([A-Ia-i]?[A-Za-z])?(\d+)\:([A-Ia-i]?[A-Za-z])?(\d+)$/",$token))\r {\r
484 return $this->_convertRange3d($token);\r
485 }\r
487 return pack("C", $this->ptg[$token]);\r
488 }\r
489 // commented so argument number can be processed correctly. See toReversePolish().\r
490 /*elseif (preg_match("/[A-Z0-9\xc0-\xdc\.]+/",$token))\r
491 {\r
492 return($this->_convertFunction($token,$this->_func_args));\r
493 }*/\r
494 // if it's an argument, ignore the token (the argument remains)\r
496 return '';\r
497 }\r
498 // TODO: use real error codes\r
499 trigger_error("Unknown token $token", E_USER_ERROR);\r
500 }\r
501 \r
502 /**\r
503 * Convert a number token to ptgInt or ptgNum\r
504 *\r
505 * @access private\r
506 * @param mixed $num an integer or double for conversion to its ptg value\r
507 */\r
510 // Integer in the range 0..2**16-1\r
512 if ((preg_match("/^\d+$/",$num)) and ($num <= 65535)) {\r
513 return(pack("Cv", $this->ptg['ptgInt'], $num));\r
515 if ($this->_byte_order) { // if it's Big Endian\r
516 $num = strrev($num);\r
517 }\r
518 return pack("Cd", $this->ptg['ptgNum'], $num);\r
519 }\r
520 }\r
521 \r
522 /**\r
523 * Convert a string token to ptgStr\r
524 *\r
525 * @access private\r
526 * @param string $string A string for conversion to its ptg value\r
527 */\r
529 // chop away beggining and ending quotes\r
530 $string = substr($string, 1, strlen($string) - 2);\r
531 return pack("CC", $this->ptg['ptgStr'], strlen($string)).$string;\r
532 }\r
533 \r
534 /**\r
535 * Convert a function to a ptgFunc or ptgFuncVarV depending on the number of\r
536 * args that it takes.\r
537 *\r
538 * @access private\r
539 * @param string $token The name of the function for convertion to ptg value.\r
540 * @param integer $num_args The number of arguments the function receives.\r
541 * @return string The packed ptg for the function\r
542 */\r
544 $args = $this->_functions[$token][1];\r
545 $volatile = $this->_functions[$token][3];\r
546 \r
547 // Fixed number of args eg. TIME($i,$j,$k).\r
548 if ($args >= 0) {\r
549 return pack("Cv", $this->ptg['ptgFuncV'], $this->_functions[$token][0]);\r
550 }\r
551 // Variable number of args eg. SUM($i,$j,$k, ..).\r
552 if ($args == -1) {\r
553 return pack("CCv", $this->ptg['ptgFuncVarV'], $num_args, $this->_functions[$token][0]);\r
554 }\r
555 }\r
556 \r
557 /**\r
558 * Convert an Excel range such as A1:D4 to a ptgRefV.\r
559 *\r
560 * @access private\r
561 * @param string $range An Excel range in the A1:A2 or A1..A2 format.\r
562 */\r
564 $class = 2; // as far as I know, this is magick.\r
565 \r
566 // Split the range into 2 cell refs\r
567 if (preg_match("/^([A-Ia-i]?[A-Za-z])(\d+)\:([A-Ia-i]?[A-Za-z])(\d+)$/",$range)) {\r
568 list($cell1, $cell2) = split(':', $range);\r
570 list($cell1, $cell2) = split('\.\.', $range);\r
572 // TODO: use real error codes\r
573 trigger_error("Unknown range separator", E_USER_ERROR);\r
574 }\r
575 \r
576 // Convert the cell references\r
577 $cell_array1 = $this->_cellToPackedRowcol($cell1);\r
578 if ($this->isError($cell_array1)) {\r
579 return $cell_array1;\r
580 }\r
581 list($row1, $col1) = $cell_array1;\r
582 $cell_array2 = $this->_cellToPackedRowcol($cell2);\r
583 if ($this->isError($cell_array2)) {\r
584 return $cell_array2;\r
585 }\r
586 list($row2, $col2) = $cell_array2;\r
587 \r
588 // The ptg value depends on the class of the ptg.\r
589 if ($class == 0) {\r
590 $ptgArea = pack("C", $this->ptg['ptgArea']);\r
592 $ptgArea = pack("C", $this->ptg['ptgAreaV']);\r
594 $ptgArea = pack("C", $this->ptg['ptgAreaA']);\r
596 // TODO: use real error codes\r
597 trigger_error("Unknown class $class", E_USER_ERROR);\r
598 }\r
599 return $ptgArea . $row1 . $row2 . $col1. $col2;\r
600 }\r
601 \r
602 /**\r
603 * Convert an Excel 3d range such as "Sheet1!A1:D4" or "Sheet1:Sheet2!A1:D4" to\r
604 * a ptgArea3dV.\r
605 *\r
606 * @access private\r
607 * @param string $token An Excel range in the Sheet1!A1:A2 format.\r
608 */\r
610 $class = 2; // as far as I know, this is magick.\r
611 \r
612 // Split the ref at the ! symbol\r
613 list($ext_ref, $range) = split('!', $token);\r
614 \r
615 // Convert the external reference part\r
616 $ext_ref = $this->_packExtRef($ext_ref);\r
617 if ($this->isError($ext_ref)) {\r
618 return $ext_ref;\r
619 }\r
620 \r
621 // Split the range into 2 cell refs\r
622 list($cell1, $cell2) = split(':', $range);\r
623 \r
624 // Convert the cell references\r
626 $cell_array1 = $this->_cellToPackedRowcol($cell1);\r
627 if ($this->isError($cell_array1)) {\r
628 return $cell_array1;\r
629 }\r
630 list($row1, $col1) = $cell_array1;\r
631 $cell_array2 = $this->_cellToPackedRowcol($cell2);\r
632 if ($this->isError($cell_array2)) {\r
633 return $cell_array2;\r
634 }\r
635 list($row2, $col2) = $cell_array2;\r
637 $cells_array = $this->_rangeToPackedRange($cell1.':'.$cell2);\r
638 if ($this->isError($cells_array)) {\r
639 return $cells_array;\r
640 }\r
641 list($row1, $col1, $row2, $col2) = $cells_array;\r
642 }\r
643 \r
644 // The ptg value depends on the class of the ptg.\r
645 if ($class == 0) {\r
646 $ptgArea = pack("C", $this->ptg['ptgArea3d']);\r
648 $ptgArea = pack("C", $this->ptg['ptgArea3dV']);\r
650 $ptgArea = pack("C", $this->ptg['ptgArea3dA']);\r
652 trigger_error("Unknown class $class", E_USER_ERROR);\r
653 }\r
654 \r
655 return $ptgArea . $ext_ref . $row1 . $row2 . $col1. $col2;\r
656 }\r
657 \r
658 /**\r
659 * Convert an Excel reference such as A1, $B2, C$3 or $D$4 to a ptgRefV.\r
660 *\r
661 * @access private\r
662 * @param string $cell An Excel cell reference\r
663 * @return string The cell in packed() format with the corresponding ptg\r
664 */\r
666 $class = 2; // as far as I know, this is magick.\r
667 \r
668 // Convert the cell reference\r
669 $cell_array = $this->_cellToPackedRowcol($cell);\r
670 if ($this->isError($cell_array)) {\r
671 return $cell_array;\r
672 }\r
673 list($row, $col) = $cell_array;\r
674 \r
675 // The ptg value depends on the class of the ptg.\r
676 if ($class == 0) {\r
677 $ptgRef = pack("C", $this->ptg['ptgRef']);\r
679 $ptgRef = pack("C", $this->ptg['ptgRefV']);\r
681 $ptgRef = pack("C", $this->ptg['ptgRefA']);\r
683 // TODO: use real error codes\r
684 trigger_error("Unknown class $class",E_USER_ERROR);\r
685 }\r
686 return $ptgRef.$row.$col;\r
687 }\r
688 \r
689 /**\r
690 * Convert an Excel 3d reference such as "Sheet1!A1" or "Sheet1:Sheet2!A1" to a\r
691 * ptgRef3dV.\r
692 *\r
693 * @access private\r
694 * @param string $cell An Excel cell reference\r
695 * @return string The cell in packed() format with the corresponding ptg\r
696 */\r
698 $class = 2; // as far as I know, this is magick.\r
699 \r
700 // Split the ref at the ! symbol\r
701 list($ext_ref, $cell) = split('!', $cell);\r
702 \r
703 // Convert the external reference part\r
704 $ext_ref = $this->_packExtRef($ext_ref);\r
705 if ($this->isError($ext_ref)) {\r
706 return $ext_ref;\r
707 }\r
708 \r
709 // Convert the cell reference part\r
710 list($row, $col) = $this->_cellToPackedRowcol($cell);\r
711 \r
712 // The ptg value depends on the class of the ptg.\r
713 if ($class == 0) {\r
714 $ptgRef = pack("C", $this->ptg['ptgRef3d']);\r
715 } elseif ($class == 1) {\r
716 $ptgRef = pack("C", $this->ptg['ptgRef3dV']);\r
717 } elseif ($class == 2) {\r
718 $ptgRef = pack("C", $this->ptg['ptgRef3dA']);\r
720 trigger_error("Unknown class $class", E_USER_ERROR);\r
721 }\r
722 \r
723 return $ptgRef . $ext_ref. $row . $col;\r
724 }\r
725 \r
726 /**\r
727 * Convert the sheet name part of an external reference, for example "Sheet1" or\r
728 * "Sheet1:Sheet2", to a packed structure.\r
729 *\r
730 * @access private\r
731 * @param string $ext_ref The name of the external reference\r
732 * @return string The reference index in packed() format\r
733 */\r
734 function _packExtRef($ext_ref) {\r
735 $ext_ref = preg_replace("/^'/", '', $ext_ref); // Remove leading ' if any.\r
736 $ext_ref = preg_replace("/'$/", '', $ext_ref); // Remove trailing ' if any.\r
737 \r
738 // Check if there is a sheet range eg., Sheet1:Sheet2.\r
740 list($sheet_name1, $sheet_name2) = split(':', $ext_ref);\r
741 \r
742 $sheet1 = $this->_getSheetIndex($sheet_name1);\r
743 if ($sheet1 == -1) {\r
744 trigger_error("Unknown sheet name $sheet_name1 in formula",E_USER_ERROR);\r
745 }\r
746 $sheet2 = $this->_getSheetIndex($sheet_name2);\r
747 if ($sheet2 == -1) {\r
748 trigger_error("Unknown sheet name $sheet_name2 in formula",E_USER_ERROR);\r
749 }\r
750 \r
751 // Reverse max and min sheet numbers if necessary\r
752 if ($sheet1 > $sheet2) {\r
753 list($sheet1, $sheet2) = array($sheet2, $sheet1);\r
754 }\r
756 $sheet1 = $this->_getSheetIndex($ext_ref);\r
757 if ($sheet1 == -1) {\r
758 trigger_error("Unknown sheet name $ext_ref in formula",E_USER_ERROR);\r
759 }\r
760 $sheet2 = $sheet1;\r
761 }\r
762 \r
763 // References are stored relative to 0xFFFF.\r
764 $offset = -1 - $sheet1;\r
766 return pack('vdvv', $offset, 0x00, $sheet1, $sheet2);\r
767 }\r
768 \r
769 /**\r
770 * Look up the index that corresponds to an external sheet name. The hash of\r
771 * sheet names is updated by the addworksheet() method of the \r
772 * Spreadsheet_Excel_Writer_Workbook class.\r
773 *\r
774 * @access private\r
775 * @return integer\r
776 */\r
778 if (!isset($this->_ext_sheets[$sheet_name])) {\r
779 return -1;\r
781 return $this->_ext_sheets[$sheet_name];\r
782 }\r
783 }\r
784 \r
785 /**\r
786 * This method is used to update the array of sheet names. It is\r
787 * called by the addWorksheet() method of the Spreadsheet_Excel_Writer_Workbook class.\r
788 *\r
789 * @access private\r
790 * @param string $name The name of the worksheet being added\r
791 * @param integer $index The index of the worksheet being added\r
792 */\r
794 $this->_ext_sheets[$name] = $index;\r
795 }\r
796 \r
797 /**\r
798 * pack() row and column into the required 3 byte format.\r
799 *\r
800 * @access private\r
801 * @param string $cell The Excel cell reference to be packed\r
802 * @return array Array containing the row and column in packed() format\r
803 */\r
805 $cell = strtoupper($cell);\r
806 list($row, $col, $row_rel, $col_rel) = $this->_cellToRowcol($cell);\r
807 if ($col >= 256) {\r
808 trigger_error("Column in: $cell greater than 255", E_USER_ERROR);\r
809 }\r
810 if ($row >= 16384) {\r
811 trigger_error("Row in: $cell greater than 16384 ", E_USER_ERROR);\r
812 }\r
813 \r
814 // Set the high bits to indicate if row or col are relative.\r
815 $row |= $col_rel << 14;\r
816 $row |= $row_rel << 15;\r
817 \r
818 $row = pack('v', $row);\r
819 $col = pack('C', $col);\r
820 \r
821 return array($row, $col);\r
822 }\r
823 \r
824 /**\r
825 * pack() row range into the required 3 byte format.\r
826 * Just using maximun col/rows, which is probably not the correct solution\r
827 *\r
828 * @access private\r
829 * @param string $range The Excel range to be packed\r
830 * @return array Array containing (row1,col1,row2,col2) in packed() format\r
831 */\r
833 preg_match('/(\$)?(\d+)\:(\$)?(\d+)/', $range, $match);\r
834 // return absolute rows if there is a $ in the ref\r
835 $row1_rel = empty($match[1]) ? 1 : 0;\r
836 $row1 = $match[2];\r
837 $row2_rel = empty($match[3]) ? 1 : 0;\r
838 $row2 = $match[4];\r
839 // Convert 1-index to zero-index\r
840 $row1--;\r
841 $row2--;\r
842 // Trick poor inocent Excel\r
843 $col1 = 0;\r
844 $col2 = 16383; // maximum possible value for Excel 5 (change this!!!)\r
846 //list($row, $col, $row_rel, $col_rel) = $this->_cellToRowcol($cell);\r
847 if (($row1 >= 16384) or ($row2 >= 16384)) {\r
848 trigger_error("Row in: $range greater than 16384 ",E_USER_ERROR);\r
849 }\r
850 \r
851 // Set the high bits to indicate if rows are relative.\r
852 $row1 |= $row1_rel << 14;\r
853 $row2 |= $row2_rel << 15;\r
854 \r
855 $row1 = pack('v', $row1);\r
856 $row2 = pack('v', $row2);\r
857 $col1 = pack('C', $col1);\r
858 $col2 = pack('C', $col2);\r
859 \r
860 return array($row1, $col1, $row2, $col2);\r
861 }\r
862 \r
863 /**\r
864 * Convert an Excel cell reference such as A1 or $B2 or C$3 or $D$4 to a zero\r
865 * indexed row and column number. Also returns two (0,1) values to indicate\r
866 * whether the row or column are relative references.\r
867 *\r
868 * @access private\r
869 * @param string $cell The Excel cell reference in A1 format.\r
870 * @return array\r
871 */\r
873 preg_match('/(\$)?([A-I]?[A-Z])(\$)?(\d+)/',$cell,$match);\r
874 // return absolute column if there is a $ in the ref\r
875 $col_rel = empty($match[1]) ? 1 : 0;\r
876 $col_ref = $match[2];\r
877 $row_rel = empty($match[3]) ? 1 : 0;\r
878 $row = $match[4];\r
879 \r
880 // Convert base26 column string to a number.\r
881 $expn = strlen($col_ref) - 1;\r
882 $col = 0;\r
884 $col += (ord($col_ref{$i}) - ord('A') + 1) * pow(26, $expn);\r
885 $expn--;\r
886 }\r
887 \r
888 // Convert 1-index to zero-index\r
889 $row--;\r
890 $col--;\r
891 \r
892 return array($row, $col, $row_rel, $col_rel);\r
893 }\r
894 \r
895 /**\r
896 * Advance to the next valid token.\r
897 *\r
898 * @access private\r
899 */\r
901 $i = $this->_current_char;\r
902 // eat up white spaces\r
904 while ($this->_formula{$i} == " ") {\r
905 $i++;\r
906 }\r
907 if ($i < strlen($this->_formula) - 1) {\r
908 $this->_lookahead = $this->_formula{$i+1};\r
909 }\r
910 $token = "";\r
911 }\r
913 $token .= $this->_formula{$i};\r
914 if ($i < strlen($this->_formula) - 1) {\r
915 $this->_lookahead = $this->_formula{$i+1};\r
917 $this->_lookahead = '';\r
918 }\r
920 //if ($i < strlen($this->_formula) - 1) {\r
921 // $this->_lookahead = $this->_formula{$i+1};\r
922 //}\r
923 $this->_current_char = $i + 1;\r
924 $this->_current_token = $token;\r
925 return 1;\r
926 }\r
927 if ($i < strlen($this->_formula) - 2) {\r
928 $this->_lookahead = $this->_formula{$i+2};\r
930 // if we run out of characters _lookahead becomes empty\r
931 $this->_lookahead = '';\r
932 }\r
933 $i++;\r
934 }\r
935 //die("Lexical error ".$this->_current_char);\r
936 }\r
937 \r
938 /**\r
939 * Checks if it's a valid token.\r
940 *\r
941 * @access private\r
942 * @param mixed $token The token to check.\r
943 * @return mixed The checked token or false on failure\r
944 */\r
947 case SPREADSHEET_EXCEL_WRITER_ADD:\r
948 return($token);\r
949 break;\r
950 case SPREADSHEET_EXCEL_WRITER_SUB:\r
951 return($token);\r
952 break;\r
953 case SPREADSHEET_EXCEL_WRITER_MUL:\r
954 return($token);\r
955 break;\r
956 case SPREADSHEET_EXCEL_WRITER_DIV:\r
957 return($token);\r
958 break;\r
959 case SPREADSHEET_EXCEL_WRITER_OPEN:\r
960 return($token);\r
961 break;\r
962 case SPREADSHEET_EXCEL_WRITER_CLOSE:\r
963 return($token);\r
964 break;\r
965 case SPREADSHEET_EXCEL_WRITER_SCOLON:\r
966 return($token);\r
967 break;\r
968 case SPREADSHEET_EXCEL_WRITER_COMA:\r
969 return($token);\r
970 break;\r
971 case SPREADSHEET_EXCEL_WRITER_GT:\r
972 if ($this->_lookahead == '=') { // it's a GE token\r
973 break;\r
974 }\r
975 return($token);\r
976 break;\r
977 case SPREADSHEET_EXCEL_WRITER_LT:\r
978 // it's a LE or a NE token\r
979 if (($this->_lookahead == '=') or ($this->_lookahead == '>')) {\r
980 break;\r
981 }\r
982 return($token);\r
983 break;\r
984 case SPREADSHEET_EXCEL_WRITER_GE:\r
985 return($token);\r
986 break;\r
987 case SPREADSHEET_EXCEL_WRITER_LE:\r
988 return($token);\r
989 break;\r
990 case SPREADSHEET_EXCEL_WRITER_EQ:\r
991 return($token);\r
992 break;\r
993 case SPREADSHEET_EXCEL_WRITER_NE:\r
994 return($token);\r
995 break;\r
996 default:\r
997 // if it's a reference\r
998 if (preg_match('/^\$?[A-Ia-i]?[A-Za-z]\$?[0-9]+$/',$token) and\r
999 !ereg("[0-9]",$this->_lookahead) and \r
1000 ($this->_lookahead != ':') and ($this->_lookahead != '.') and\r
1002 return $token;\r
1003 }\r
1004 // If it's an external reference (Sheet1!A1 or Sheet1:Sheet2!A1)\r
1005 elseif (preg_match("/^[A-Za-z0-9_]+(\:[A-Za-z0-9_]+)?\![A-Ia-i]?[A-Za-z][0-9]+$/",$token) and\r
1006 !ereg("[0-9]",$this->_lookahead) and\r
1008 return $token;\r
1009 }\r
1010 // if it's a range (A1:A2)\r
1011 elseif (preg_match("/^(\$)?[A-Ia-i]?[A-Za-z](\$)?[0-9]+:(\$)?[A-Ia-i]?[A-Za-z](\$)?[0-9]+$/",$token) and \r
1013 return $token;\r
1014 }\r
1015 // if it's a range (A1..A2)\r
1016 elseif (preg_match("/^(\$)?[A-Ia-i]?[A-Za-z](\$)?[0-9]+\.\.(\$)?[A-Ia-i]?[A-Za-z](\$)?[0-9]+$/",$token) and \r
1018 return $token;\r
1019 }\r
1020 // If it's an external range like Sheet1:Sheet2!A1:B2\r
1021 elseif (preg_match("/^[A-Za-z0-9_]+(\:[A-Za-z0-9_]+)?\!([A-Ia-i]?[A-Za-z])?[0-9]+:([A-Ia-i]?[A-Za-z])?[0-9]+$/",$token) and\r
1023 return $token;\r
1024 }\r
1025 // If it's an external range like 'Sheet1:Sheet2'!A1:B2\r
1026 elseif (preg_match("/^'[A-Za-z0-9_ ]+(\:[A-Za-z0-9_ ]+)?'\!([A-Ia-i]?[A-Za-z])?[0-9]+:([A-Ia-i]?[A-Za-z])?[0-9]+$/",$token) and\r
1028 return $token;\r
1029 }\r
1030 // If it's a number (check that it's not a sheet name or range)\r
1031 elseif (is_numeric($token) and \r
1032 (!is_numeric($token.$this->_lookahead) or ($this->_lookahead == '')) and\r
1034 return $token;\r
1035 }\r
1036 // If it's a string (of maximum 255 characters)\r
1038 return $token;\r
1039 }\r
1040 // if it's a function call\r
1041 elseif (eregi("^[A-Z0-9\xc0-\xdc\.]+$",$token) and ($this->_lookahead == "(")) {\r
1042 return $token;\r
1043 }\r
1044 return '';\r
1045 }\r
1046 }\r
1047 \r
1048 /**\r
1049 * The parsing method. It parses a formula.\r
1050 *\r
1051 * @access public\r
1052 * @param string $formula The formula to parse, without the initial equal sign (=).\r
1053 */\r
1055 $this->_current_char = 0;\r
1056 $this->_formula = $formula;\r
1057 $this->_lookahead = $formula{1};\r
1058 $this->_advance();\r
1059 $this->_parse_tree = $this->_condition();\r
1060 if ($this->isError($this->_parse_tree)) {\r
1061 return $this->_parse_tree;\r
1062 }\r
1063 }\r
1064 \r
1065 /**\r
1066 * It parses a condition. It assumes the following rule:\r
1067 * Cond -> Expr [(">" | "<") Expr]\r
1068 *\r
1069 * @access private\r
1070 * @return mixed The parsed ptg'd tree\r
1071 */\r
1073 $result = $this->_expression();\r
1074 if ($this->isError($result)) {\r
1075 return $result;\r
1076 }\r
1078 $this->_advance();\r
1079 $result2 = $this->_expression();\r
1080 if ($this->isError($result2)) {\r
1081 return $result2;\r
1082 }\r
1083 $result = $this->_createTree('ptgLT', $result, $result2);\r
1085 $this->_advance();\r
1086 $result2 = $this->_expression();\r
1087 if ($this->isError($result2)) {\r
1088 return $result2;\r
1089 }\r
1090 $result = $this->_createTree('ptgGT', $result, $result2);\r
1092 $this->_advance();\r
1093 $result2 = $this->_expression();\r
1094 if ($this->isError($result2)) {\r
1095 return $result2;\r
1096 }\r
1097 $result = $this->_createTree('ptgLE', $result, $result2);\r
1099 $this->_advance();\r
1100 $result2 = $this->_expression();\r
1101 if ($this->isError($result2)) {\r
1102 return $result2;\r
1103 }\r
1104 $result = $this->_createTree('ptgGE', $result, $result2);\r
1106 $this->_advance();\r
1107 $result2 = $this->_expression();\r
1108 if ($this->isError($result2)) {\r
1109 return $result2;\r
1110 }\r
1111 $result = $this->_createTree('ptgEQ', $result, $result2);\r
1113 $this->_advance();\r
1114 $result2 = $this->_expression();\r
1115 if ($this->isError($result2)) {\r
1116 return $result2;\r
1117 }\r
1118 $result = $this->_createTree('ptgNE', $result, $result2);\r
1119 }\r
1120 return $result;\r
1121 }\r
1123 /**\r
1124 * It parses a expression. It assumes the following rule:\r
1125 * Expr -> Term [("+" | "-") Term]\r
1126 *\r
1127 * @access private\r
1128 * @return mixed The parsed ptg'd tree\r
1129 */\r
1131 // If it's a string return a string node\r
1133 $result = $this->_createTree($this->_current_token, '', '');\r
1134 $this->_advance();\r
1135 return $result;\r
1136 }\r
1137 $result = $this->_term();\r
1138 if ($this->isError($result)) {\r
1139 return $result;\r
1140 }\r
1141 while (($this->_current_token == SPREADSHEET_EXCEL_WRITER_ADD) or \r
1144 $this->_advance();\r
1145 $result2 = $this->_term();\r
1146 if ($this->isError($result2)) {\r
1147 return $result2;\r
1148 }\r
1149 $result = $this->_createTree('ptgAdd', $result, $result2);\r
1151 $this->_advance();\r
1152 $result2 = $this->_term();\r
1153 if ($this->isError($result2)) {\r
1154 return $result2;\r
1155 }\r
1156 $result = $this->_createTree('ptgSub', $result, $result2);\r
1157 }\r
1158 }\r
1159 return $result;\r
1160 }\r
1161 \r
1162 /**\r
1163 * This function just introduces a ptgParen element in the tree, so that Excel\r
1164 * doesn't get confused when working with a parenthesized formula afterwards.\r
1165 *\r
1166 * @access private\r
1167 * @see _fact()\r
1168 * @return mixed The parsed ptg'd tree\r
1169 */\r
1171 $result = $this->_createTree('ptgParen', $this->_expression(), '');\r
1172 return $result;\r
1173 }\r
1174 \r
1175 /**\r
1176 * It parses a term. It assumes the following rule:\r
1177 * Term -> Fact [("*" | "/") Fact]\r
1178 *\r
1179 * @access private\r
1180 * @return mixed The parsed ptg'd tree\r
1181 */\r
1183 $result = $this->_fact();\r
1184 if ($this->isError($result)) {\r
1185 return $result;\r
1186 }\r
1187 while (($this->_current_token == SPREADSHEET_EXCEL_WRITER_MUL) or \r
1188 ($this->_current_token == SPREADSHEET_EXCEL_WRITER_DIV)) {
1190 $this->_advance();\r
1191 $result2 = $this->_fact();\r
1192 if ($this->isError($result2)) {\r
1193 return $result2;\r
1194 }\r
1195 $result = $this->_createTree('ptgMul', $result, $result2);\r
1197 $this->_advance();\r
1198 $result2 = $this->_fact();\r
1199 if ($this->isError($result2)) {\r
1200 return $result2;\r
1201 }\r
1202 $result = $this->_createTree('ptgDiv', $result, $result2);\r
1203 }\r
1204 }\r
1205 return $result;\r
1206 }\r
1207 \r
1208 /**\r
1209 * It parses a factor. It assumes the following rule:\r
1210 * Fact -> ( Expr )\r
1211 * | CellRef\r
1212 * | CellRange\r
1213 * | Number\r
1214 * | Function\r
1215 *\r
1216 * @access private\r
1217 * @return mixed The parsed ptg'd tree\r
1218 */\r
1221 $this->_advance(); // eat the "("\r
1222 $result = $this->_parenthesizedExpression();\r
1223 if ($this->_current_token != SPREADSHEET_EXCEL_WRITER_CLOSE) {\r
1224 trigger_error("')' token expected.",E_USER_ERROR);\r
1225 }\r
1226 $this->_advance(); // eat the ")"\r
1227 return $result;\r
1229 // if it's a reference\r
1230 $result = $this->_createTree($this->_current_token, '', '');\r
1231 $this->_advance();\r
1232 return $result;\r
1233 }\r elseif (preg_match("/^[A-Za-z0-9_]+(\:[A-Za-z0-9_]+)?\![A-Ia-i]?[A-Za-z][0-9]+$/",$this->_current_token))\r {\r
1234 // If it's an external reference (Sheet1!A1 or Sheet1:Sheet2!A1)\r
1235 $result = $this->_createTree($this->_current_token, '', '');\r
1236 $this->_advance();\r
1237 return $result;\r
1238 }\r elseif (preg_match("/^(\$)?[A-Ia-i]?[A-Za-z](\$)?[0-9]+:(\$)?[A-Ia-i]?[A-Za-z](\$)?[0-9]+$/",$this->_current_token) or \r
1239 preg_match("/^(\$)?[A-Ia-i]?[A-Za-z](\$)?[0-9]+\.\.(\$)?[A-Ia-i]?[A-Za-z](\$)?[0-9]+$/",$this->_current_token))\r {\r
1240 // if it's a range\r
1241 $result = $this->_current_token;\r
1242 $this->_advance();\r
1243 return $result;\r
1244 }\r elseif (preg_match("/^[A-Za-z0-9_]+(\:[A-Za-z0-9_]+)?\!([A-Ia-i]?[A-Za-z])?[0-9]+:([A-Ia-i]?[A-Za-z])?[0-9]+$/",$this->_current_token))\r {\r
1245 // If it's an external range (Sheet1!A1:B2)\r
1246 $result = $this->_current_token;\r
1247 $this->_advance();\r
1248 return $result;\r
1249 }\r elseif (preg_match("/^'[A-Za-z0-9_ ]+(\:[A-Za-z0-9_ ]+)?'\!([A-Ia-i]?[A-Za-z])?[0-9]+:([A-Ia-i]?[A-Za-z])?[0-9]+$/",$this->_current_token))\r {\r
1250 // If it's an external range ('Sheet1'!A1:B2)\r
1251 $result = $this->_current_token;\r
1252 $this->_advance();\r
1253 return $result;\r
1255 $result = $this->_createTree($this->_current_token, '', '');\r
1256 $this->_advance();\r
1257 return $result;\r
1259 // if it's a function call\r
1260 $result = $this->_func();\r
1261 return $result;\r
1262 }\r
1263 trigger_error("Sintactic error: ".$this->_current_token.", lookahead: ".\r
1264 $this->_lookahead.", current char: ".$this->_current_char, E_USER_ERROR);\r
1265 }\r
1266 \r
1267 /**\r
1268 * It parses a function call. It assumes the following rule:\r
1269 * Func -> ( Expr [,Expr]* )\r
1270 *\r
1271 * @access private\r
1272 */\r
1274 $num_args = 0; // number of arguments received\r
1275 $function = $this->_current_token;\r
1276 $this->_advance();\r
1277 $this->_advance(); // eat the "("\r
1280 if ($this->_current_token == SPREADSHEET_EXCEL_WRITER_COMA ||
1281 $this->_current_token == SPREADSHEET_EXCEL_WRITER_SCOLON) {\r
1282 $this->_advance(); // eat the ","\r
1284 trigger_error("Sintactic error: coma expected in ".\r
1285 "function $function, {$num_args}ยบ arg", E_USER_ERROR);\r
1286 }\r
1287 $result2 = $this->_condition();\r
1288 if ($this->isError($result2)) {\r
1289 return $result2;\r
1290 }\r
1291 $result = $this->_createTree('arg', $result, $result2);\r
1293 $result2 = $this->_condition();\r
1294 if ($this->isError($result2)) {\r
1295 return $result2;\r
1296 }\r
1297 $result = $this->_createTree('arg', '', $result2);\r
1298 }\r
1299 $num_args++;\r
1300 }\r
1301 $args = $this->_functions[$function][1];\r
1302 // If fixed number of args eg. TIME($i,$j,$k). Check that the number of args is valid.\r
1303 if (($args >= 0) and ($args != $num_args)) {\r
1304 trigger_error("Incorrect number of arguments in function $function() ",E_USER_ERROR);\r
1305 }\r
1306 \r
1307 $result = $this->_createTree($function, $result, $num_args);\r
1308 $this->_advance(); // eat the ")"\r
1309 return $result;\r
1310 }\r
1311 \r
1312 /**\r
1313 * Creates a tree. In fact an array which may have one or two arrays (sub-trees)\r
1314 * as elements.\r
1315 *\r
1316 * @access private\r
1317 * @param mixed $value The value of this node.\r
1318 * @param mixed $left The left array (sub-tree) or a final node.\r
1319 * @param mixed $right The right array (sub-tree) or a final node.\r
1320 */\r
1322 return(array('value' => $value, 'left' => $left, 'right' => $right));\r
1323 }\r
1324 \r
1325 /**\r
1326 * Builds a string containing the tree in reverse polish notation (What you \r
1327 * would use in a HP calculator stack).\r
1328 * The following tree:\r
1329 * \r
1330 * +\r
1331 * / \\r
1332 * 2 3\r
1333 *\r
1334 * produces: "23+"\r
1335 *\r
1336 * The following tree:\r
1337 *\r
1338 * +\r
1339 * / \\r
1340 * 3 *\r
1341 * / \\r
1342 * 6 A1\r
1343 *\r
1344 * produces: "36A1*+"\r
1345 *\r
1346 * In fact all operands, functions, references, etc... are written as ptg's\r
1347 *\r
1348 * @access public\r
1349 * @param array $tree The optional tree to convert.\r
1350 * @return string The tree in reverse polish notation\r
1351 */\r
1353 $polish = ""; // the string we are going to return\r
1354 if (empty($tree)) { // If it's the first call use _parse_tree\r
1355 $tree = $this->_parse_tree;\r
1356 }\r
1358 $converted_tree = $this->toReversePolish($tree['left']);\r
1359 if ($this->isError($converted_tree)) {\r
1360 return $converted_tree;\r
1361 }\r
1362 $polish .= $converted_tree;\r
1364 $converted_tree = $this->_convert($tree['left']);\r
1365 if ($this->isError($converted_tree)) {\r
1366 return $converted_tree;\r
1367 }\r
1368 $polish .= $converted_tree;\r
1369 }\r
1371 $converted_tree = $this->toReversePolish($tree['right']);\r
1372 if ($this->isError($converted_tree)) {\r
1373 return $converted_tree;\r
1374 }\r
1375 $polish .= $converted_tree;\r
1377 $converted_tree = $this->_convert($tree['right']);\r
1378 if ($this->isError($converted_tree)) {\r
1379 return $converted_tree;\r
1380 }\r
1381 $polish .= $converted_tree;\r
1382 }\r
1383 // if it's a function convert it here (so we can set it's arguments)\r
1384 if (preg_match("/^[A-Z0-9\xc0-\xdc\.]+$/",$tree['value']) and\r
1385 !preg_match('/^([A-Ia-i]?[A-Za-z])(\d+)$/',$tree['value']) and\r
1386 !preg_match("/^[A-Ia-i]?[A-Za-z](\d+)\.\.[A-Ia-i]?[A-Za-z](\d+)$/",$tree['value']) and\r
1387 !is_numeric($tree['value']) and\r
1389 // left subtree for a function is always an array.\r
1390 if ($tree['left'] != '') {\r
1391 $left_tree = $this->toReversePolish($tree['left']);\r
1393 $left_tree = '';\r
1394 }\r
1395 if ($this->isError($left_tree)) {\r
1396 return $left_tree;\r
1397 }\r
1398 // add it's left subtree and return.\r
1399 return $left_tree.$this->_convertFunction($tree['value'], $tree['right']);\r
1401 $converted_tree = $this->_convert($tree['value']);\r
1402 if ($this->isError($converted_tree)) {\r
1403 return $converted_tree;\r
1404 }\r
1405 }\r
1406 $polish .= $converted_tree;\r
1407 return $polish;\r
1408 }\r
1410 }\r
1413 ?>