ILIAS  eassessment Revision 61809
 All Data Structures Namespaces Files Functions Variables Groups Pages
Parser.php
Go to the documentation of this file.
1 <?php
28 define('SPREADSHEET_EXCEL_WRITER_ADD',"+");
29 
33 define('SPREADSHEET_EXCEL_WRITER_SUB',"-");
34 
38 define('SPREADSHEET_EXCEL_WRITER_MUL',"*");
39 
43 define('SPREADSHEET_EXCEL_WRITER_DIV',"/");
44 
48 define('SPREADSHEET_EXCEL_WRITER_OPEN',"(");
49 
53 define('SPREADSHEET_EXCEL_WRITER_CLOSE',")");
54 
58 define('SPREADSHEET_EXCEL_WRITER_COMA',",");
59 
63 define('SPREADSHEET_EXCEL_WRITER_SEMICOLON',";");
64 
68 define('SPREADSHEET_EXCEL_WRITER_GT',">");
69 
73 define('SPREADSHEET_EXCEL_WRITER_LT',"<");
74 
78 define('SPREADSHEET_EXCEL_WRITER_LE',"<=");
79 
83 define('SPREADSHEET_EXCEL_WRITER_GE',">=");
84 
88 define('SPREADSHEET_EXCEL_WRITER_EQ',"=");
89 
93 define('SPREADSHEET_EXCEL_WRITER_NE',"<>");
94 
95 
96 require_once('PEAR.php');
97 
107 {
113 
119 
125 
131 
137 
143 
149 
155 
161 
168  function Spreadsheet_Excel_Writer_Parser($byte_order, $biff_version)
169  {
170  $this->_current_char = 0;
171  $this->_BIFF_version = $biff_version;
172  $this->_current_token = ''; // The token we are working on.
173  $this->_formula = ""; // The formula to parse.
174  $this->_lookahead = ''; // The character ahead of the current char.
175  $this->_parse_tree = ''; // The parse tree to be generated.
176  $this->_initializeHashes(); // Initialize the hashes: ptg's and function's ptg's
177  $this->_byte_order = $byte_order; // Little Endian or Big Endian
178  $this->_ext_sheets = array();
179  $this->_references = array();
180  }
181 
187  function _initializeHashes()
188  {
189  // The Excel ptg indices
190  $this->ptg = array(
191  'ptgExp' => 0x01,
192  'ptgTbl' => 0x02,
193  'ptgAdd' => 0x03,
194  'ptgSub' => 0x04,
195  'ptgMul' => 0x05,
196  'ptgDiv' => 0x06,
197  'ptgPower' => 0x07,
198  'ptgConcat' => 0x08,
199  'ptgLT' => 0x09,
200  'ptgLE' => 0x0A,
201  'ptgEQ' => 0x0B,
202  'ptgGE' => 0x0C,
203  'ptgGT' => 0x0D,
204  'ptgNE' => 0x0E,
205  'ptgIsect' => 0x0F,
206  'ptgUnion' => 0x10,
207  'ptgRange' => 0x11,
208  'ptgUplus' => 0x12,
209  'ptgUminus' => 0x13,
210  'ptgPercent' => 0x14,
211  'ptgParen' => 0x15,
212  'ptgMissArg' => 0x16,
213  'ptgStr' => 0x17,
214  'ptgAttr' => 0x19,
215  'ptgSheet' => 0x1A,
216  'ptgEndSheet' => 0x1B,
217  'ptgErr' => 0x1C,
218  'ptgBool' => 0x1D,
219  'ptgInt' => 0x1E,
220  'ptgNum' => 0x1F,
221  'ptgArray' => 0x20,
222  'ptgFunc' => 0x21,
223  'ptgFuncVar' => 0x22,
224  'ptgName' => 0x23,
225  'ptgRef' => 0x24,
226  'ptgArea' => 0x25,
227  'ptgMemArea' => 0x26,
228  'ptgMemErr' => 0x27,
229  'ptgMemNoMem' => 0x28,
230  'ptgMemFunc' => 0x29,
231  'ptgRefErr' => 0x2A,
232  'ptgAreaErr' => 0x2B,
233  'ptgRefN' => 0x2C,
234  'ptgAreaN' => 0x2D,
235  'ptgMemAreaN' => 0x2E,
236  'ptgMemNoMemN' => 0x2F,
237  'ptgNameX' => 0x39,
238  'ptgRef3d' => 0x3A,
239  'ptgArea3d' => 0x3B,
240  'ptgRefErr3d' => 0x3C,
241  'ptgAreaErr3d' => 0x3D,
242  'ptgArrayV' => 0x40,
243  'ptgFuncV' => 0x41,
244  'ptgFuncVarV' => 0x42,
245  'ptgNameV' => 0x43,
246  'ptgRefV' => 0x44,
247  'ptgAreaV' => 0x45,
248  'ptgMemAreaV' => 0x46,
249  'ptgMemErrV' => 0x47,
250  'ptgMemNoMemV' => 0x48,
251  'ptgMemFuncV' => 0x49,
252  'ptgRefErrV' => 0x4A,
253  'ptgAreaErrV' => 0x4B,
254  'ptgRefNV' => 0x4C,
255  'ptgAreaNV' => 0x4D,
256  'ptgMemAreaNV' => 0x4E,
257  'ptgMemNoMemN' => 0x4F,
258  'ptgFuncCEV' => 0x58,
259  'ptgNameXV' => 0x59,
260  'ptgRef3dV' => 0x5A,
261  'ptgArea3dV' => 0x5B,
262  'ptgRefErr3dV' => 0x5C,
263  'ptgAreaErr3d' => 0x5D,
264  'ptgArrayA' => 0x60,
265  'ptgFuncA' => 0x61,
266  'ptgFuncVarA' => 0x62,
267  'ptgNameA' => 0x63,
268  'ptgRefA' => 0x64,
269  'ptgAreaA' => 0x65,
270  'ptgMemAreaA' => 0x66,
271  'ptgMemErrA' => 0x67,
272  'ptgMemNoMemA' => 0x68,
273  'ptgMemFuncA' => 0x69,
274  'ptgRefErrA' => 0x6A,
275  'ptgAreaErrA' => 0x6B,
276  'ptgRefNA' => 0x6C,
277  'ptgAreaNA' => 0x6D,
278  'ptgMemAreaNA' => 0x6E,
279  'ptgMemNoMemN' => 0x6F,
280  'ptgFuncCEA' => 0x78,
281  'ptgNameXA' => 0x79,
282  'ptgRef3dA' => 0x7A,
283  'ptgArea3dA' => 0x7B,
284  'ptgRefErr3dA' => 0x7C,
285  'ptgAreaErr3d' => 0x7D
286  );
287 
288  // Thanks to Michael Meeks and Gnumeric for the initial arg values.
289  //
290  // The following hash was generated by "function_locale.pl" in the distro.
291  // Refer to function_locale.pl for non-English function names.
292  //
293  // The array elements are as follow:
294  // ptg: The Excel function ptg code.
295  // args: The number of arguments that the function takes:
296  // >=0 is a fixed number of arguments.
297  // -1 is a variable number of arguments.
298  // class: The reference, value or array class of the function args.
299  // vol: The function is volatile.
300  //
301  $this->_functions = array(
302  // function ptg args class vol
303  'COUNT' => array( 0, -1, 0, 0 ),
304  'IF' => array( 1, -1, 1, 0 ),
305  'ISNA' => array( 2, 1, 1, 0 ),
306  'ISERROR' => array( 3, 1, 1, 0 ),
307  'SUM' => array( 4, -1, 0, 0 ),
308  'AVERAGE' => array( 5, -1, 0, 0 ),
309  'MIN' => array( 6, -1, 0, 0 ),
310  'MAX' => array( 7, -1, 0, 0 ),
311  'ROW' => array( 8, -1, 0, 0 ),
312  'COLUMN' => array( 9, -1, 0, 0 ),
313  'NA' => array( 10, 0, 0, 0 ),
314  'NPV' => array( 11, -1, 1, 0 ),
315  'STDEV' => array( 12, -1, 0, 0 ),
316  'DOLLAR' => array( 13, -1, 1, 0 ),
317  'FIXED' => array( 14, -1, 1, 0 ),
318  'SIN' => array( 15, 1, 1, 0 ),
319  'COS' => array( 16, 1, 1, 0 ),
320  'TAN' => array( 17, 1, 1, 0 ),
321  'ATAN' => array( 18, 1, 1, 0 ),
322  'PI' => array( 19, 0, 1, 0 ),
323  'SQRT' => array( 20, 1, 1, 0 ),
324  'EXP' => array( 21, 1, 1, 0 ),
325  'LN' => array( 22, 1, 1, 0 ),
326  'LOG10' => array( 23, 1, 1, 0 ),
327  'ABS' => array( 24, 1, 1, 0 ),
328  'INT' => array( 25, 1, 1, 0 ),
329  'SIGN' => array( 26, 1, 1, 0 ),
330  'ROUND' => array( 27, 2, 1, 0 ),
331  'LOOKUP' => array( 28, -1, 0, 0 ),
332  'INDEX' => array( 29, -1, 0, 1 ),
333  'REPT' => array( 30, 2, 1, 0 ),
334  'MID' => array( 31, 3, 1, 0 ),
335  'LEN' => array( 32, 1, 1, 0 ),
336  'VALUE' => array( 33, 1, 1, 0 ),
337  'TRUE' => array( 34, 0, 1, 0 ),
338  'FALSE' => array( 35, 0, 1, 0 ),
339  'AND' => array( 36, -1, 0, 0 ),
340  'OR' => array( 37, -1, 0, 0 ),
341  'NOT' => array( 38, 1, 1, 0 ),
342  'MOD' => array( 39, 2, 1, 0 ),
343  'DCOUNT' => array( 40, 3, 0, 0 ),
344  'DSUM' => array( 41, 3, 0, 0 ),
345  'DAVERAGE' => array( 42, 3, 0, 0 ),
346  'DMIN' => array( 43, 3, 0, 0 ),
347  'DMAX' => array( 44, 3, 0, 0 ),
348  'DSTDEV' => array( 45, 3, 0, 0 ),
349  'VAR' => array( 46, -1, 0, 0 ),
350  'DVAR' => array( 47, 3, 0, 0 ),
351  'TEXT' => array( 48, 2, 1, 0 ),
352  'LINEST' => array( 49, -1, 0, 0 ),
353  'TREND' => array( 50, -1, 0, 0 ),
354  'LOGEST' => array( 51, -1, 0, 0 ),
355  'GROWTH' => array( 52, -1, 0, 0 ),
356  'PV' => array( 56, -1, 1, 0 ),
357  'FV' => array( 57, -1, 1, 0 ),
358  'NPER' => array( 58, -1, 1, 0 ),
359  'PMT' => array( 59, -1, 1, 0 ),
360  'RATE' => array( 60, -1, 1, 0 ),
361  'MIRR' => array( 61, 3, 0, 0 ),
362  'IRR' => array( 62, -1, 0, 0 ),
363  'RAND' => array( 63, 0, 1, 1 ),
364  'MATCH' => array( 64, -1, 0, 0 ),
365  'DATE' => array( 65, 3, 1, 0 ),
366  'TIME' => array( 66, 3, 1, 0 ),
367  'DAY' => array( 67, 1, 1, 0 ),
368  'MONTH' => array( 68, 1, 1, 0 ),
369  'YEAR' => array( 69, 1, 1, 0 ),
370  'WEEKDAY' => array( 70, -1, 1, 0 ),
371  'HOUR' => array( 71, 1, 1, 0 ),
372  'MINUTE' => array( 72, 1, 1, 0 ),
373  'SECOND' => array( 73, 1, 1, 0 ),
374  'NOW' => array( 74, 0, 1, 1 ),
375  'AREAS' => array( 75, 1, 0, 1 ),
376  'ROWS' => array( 76, 1, 0, 1 ),
377  'COLUMNS' => array( 77, 1, 0, 1 ),
378  'OFFSET' => array( 78, -1, 0, 1 ),
379  'SEARCH' => array( 82, -1, 1, 0 ),
380  'TRANSPOSE' => array( 83, 1, 1, 0 ),
381  'TYPE' => array( 86, 1, 1, 0 ),
382  'ATAN2' => array( 97, 2, 1, 0 ),
383  'ASIN' => array( 98, 1, 1, 0 ),
384  'ACOS' => array( 99, 1, 1, 0 ),
385  'CHOOSE' => array( 100, -1, 1, 0 ),
386  'HLOOKUP' => array( 101, -1, 0, 0 ),
387  'VLOOKUP' => array( 102, -1, 0, 0 ),
388  'ISREF' => array( 105, 1, 0, 0 ),
389  'LOG' => array( 109, -1, 1, 0 ),
390  'CHAR' => array( 111, 1, 1, 0 ),
391  'LOWER' => array( 112, 1, 1, 0 ),
392  'UPPER' => array( 113, 1, 1, 0 ),
393  'PROPER' => array( 114, 1, 1, 0 ),
394  'LEFT' => array( 115, -1, 1, 0 ),
395  'RIGHT' => array( 116, -1, 1, 0 ),
396  'EXACT' => array( 117, 2, 1, 0 ),
397  'TRIM' => array( 118, 1, 1, 0 ),
398  'REPLACE' => array( 119, 4, 1, 0 ),
399  'SUBSTITUTE' => array( 120, -1, 1, 0 ),
400  'CODE' => array( 121, 1, 1, 0 ),
401  'FIND' => array( 124, -1, 1, 0 ),
402  'CELL' => array( 125, -1, 0, 1 ),
403  'ISERR' => array( 126, 1, 1, 0 ),
404  'ISTEXT' => array( 127, 1, 1, 0 ),
405  'ISNUMBER' => array( 128, 1, 1, 0 ),
406  'ISBLANK' => array( 129, 1, 1, 0 ),
407  'T' => array( 130, 1, 0, 0 ),
408  'N' => array( 131, 1, 0, 0 ),
409  'DATEVALUE' => array( 140, 1, 1, 0 ),
410  'TIMEVALUE' => array( 141, 1, 1, 0 ),
411  'SLN' => array( 142, 3, 1, 0 ),
412  'SYD' => array( 143, 4, 1, 0 ),
413  'DDB' => array( 144, -1, 1, 0 ),
414  'INDIRECT' => array( 148, -1, 1, 1 ),
415  'CALL' => array( 150, -1, 1, 0 ),
416  'CLEAN' => array( 162, 1, 1, 0 ),
417  'MDETERM' => array( 163, 1, 2, 0 ),
418  'MINVERSE' => array( 164, 1, 2, 0 ),
419  'MMULT' => array( 165, 2, 2, 0 ),
420  'IPMT' => array( 167, -1, 1, 0 ),
421  'PPMT' => array( 168, -1, 1, 0 ),
422  'COUNTA' => array( 169, -1, 0, 0 ),
423  'PRODUCT' => array( 183, -1, 0, 0 ),
424  'FACT' => array( 184, 1, 1, 0 ),
425  'DPRODUCT' => array( 189, 3, 0, 0 ),
426  'ISNONTEXT' => array( 190, 1, 1, 0 ),
427  'STDEVP' => array( 193, -1, 0, 0 ),
428  'VARP' => array( 194, -1, 0, 0 ),
429  'DSTDEVP' => array( 195, 3, 0, 0 ),
430  'DVARP' => array( 196, 3, 0, 0 ),
431  'TRUNC' => array( 197, -1, 1, 0 ),
432  'ISLOGICAL' => array( 198, 1, 1, 0 ),
433  'DCOUNTA' => array( 199, 3, 0, 0 ),
434  'ROUNDUP' => array( 212, 2, 1, 0 ),
435  'ROUNDDOWN' => array( 213, 2, 1, 0 ),
436  'RANK' => array( 216, -1, 0, 0 ),
437  'ADDRESS' => array( 219, -1, 1, 0 ),
438  'DAYS360' => array( 220, -1, 1, 0 ),
439  'TODAY' => array( 221, 0, 1, 1 ),
440  'VDB' => array( 222, -1, 1, 0 ),
441  'MEDIAN' => array( 227, -1, 0, 0 ),
442  'SUMPRODUCT' => array( 228, -1, 2, 0 ),
443  'SINH' => array( 229, 1, 1, 0 ),
444  'COSH' => array( 230, 1, 1, 0 ),
445  'TANH' => array( 231, 1, 1, 0 ),
446  'ASINH' => array( 232, 1, 1, 0 ),
447  'ACOSH' => array( 233, 1, 1, 0 ),
448  'ATANH' => array( 234, 1, 1, 0 ),
449  'DGET' => array( 235, 3, 0, 0 ),
450  'INFO' => array( 244, 1, 1, 1 ),
451  'DB' => array( 247, -1, 1, 0 ),
452  'FREQUENCY' => array( 252, 2, 0, 0 ),
453  'ERROR.TYPE' => array( 261, 1, 1, 0 ),
454  'REGISTER.ID' => array( 267, -1, 1, 0 ),
455  'AVEDEV' => array( 269, -1, 0, 0 ),
456  'BETADIST' => array( 270, -1, 1, 0 ),
457  'GAMMALN' => array( 271, 1, 1, 0 ),
458  'BETAINV' => array( 272, -1, 1, 0 ),
459  'BINOMDIST' => array( 273, 4, 1, 0 ),
460  'CHIDIST' => array( 274, 2, 1, 0 ),
461  'CHIINV' => array( 275, 2, 1, 0 ),
462  'COMBIN' => array( 276, 2, 1, 0 ),
463  'CONFIDENCE' => array( 277, 3, 1, 0 ),
464  'CRITBINOM' => array( 278, 3, 1, 0 ),
465  'EVEN' => array( 279, 1, 1, 0 ),
466  'EXPONDIST' => array( 280, 3, 1, 0 ),
467  'FDIST' => array( 281, 3, 1, 0 ),
468  'FINV' => array( 282, 3, 1, 0 ),
469  'FISHER' => array( 283, 1, 1, 0 ),
470  'FISHERINV' => array( 284, 1, 1, 0 ),
471  'FLOOR' => array( 285, 2, 1, 0 ),
472  'GAMMADIST' => array( 286, 4, 1, 0 ),
473  'GAMMAINV' => array( 287, 3, 1, 0 ),
474  'CEILING' => array( 288, 2, 1, 0 ),
475  'HYPGEOMDIST' => array( 289, 4, 1, 0 ),
476  'LOGNORMDIST' => array( 290, 3, 1, 0 ),
477  'LOGINV' => array( 291, 3, 1, 0 ),
478  'NEGBINOMDIST' => array( 292, 3, 1, 0 ),
479  'NORMDIST' => array( 293, 4, 1, 0 ),
480  'NORMSDIST' => array( 294, 1, 1, 0 ),
481  'NORMINV' => array( 295, 3, 1, 0 ),
482  'NORMSINV' => array( 296, 1, 1, 0 ),
483  'STANDARDIZE' => array( 297, 3, 1, 0 ),
484  'ODD' => array( 298, 1, 1, 0 ),
485  'PERMUT' => array( 299, 2, 1, 0 ),
486  'POISSON' => array( 300, 3, 1, 0 ),
487  'TDIST' => array( 301, 3, 1, 0 ),
488  'WEIBULL' => array( 302, 4, 1, 0 ),
489  'SUMXMY2' => array( 303, 2, 2, 0 ),
490  'SUMX2MY2' => array( 304, 2, 2, 0 ),
491  'SUMX2PY2' => array( 305, 2, 2, 0 ),
492  'CHITEST' => array( 306, 2, 2, 0 ),
493  'CORREL' => array( 307, 2, 2, 0 ),
494  'COVAR' => array( 308, 2, 2, 0 ),
495  'FORECAST' => array( 309, 3, 2, 0 ),
496  'FTEST' => array( 310, 2, 2, 0 ),
497  'INTERCEPT' => array( 311, 2, 2, 0 ),
498  'PEARSON' => array( 312, 2, 2, 0 ),
499  'RSQ' => array( 313, 2, 2, 0 ),
500  'STEYX' => array( 314, 2, 2, 0 ),
501  'SLOPE' => array( 315, 2, 2, 0 ),
502  'TTEST' => array( 316, 4, 2, 0 ),
503  'PROB' => array( 317, -1, 2, 0 ),
504  'DEVSQ' => array( 318, -1, 0, 0 ),
505  'GEOMEAN' => array( 319, -1, 0, 0 ),
506  'HARMEAN' => array( 320, -1, 0, 0 ),
507  'SUMSQ' => array( 321, -1, 0, 0 ),
508  'KURT' => array( 322, -1, 0, 0 ),
509  'SKEW' => array( 323, -1, 0, 0 ),
510  'ZTEST' => array( 324, -1, 0, 0 ),
511  'LARGE' => array( 325, 2, 0, 0 ),
512  'SMALL' => array( 326, 2, 0, 0 ),
513  'QUARTILE' => array( 327, 2, 0, 0 ),
514  'PERCENTILE' => array( 328, 2, 0, 0 ),
515  'PERCENTRANK' => array( 329, -1, 0, 0 ),
516  'MODE' => array( 330, -1, 2, 0 ),
517  'TRIMMEAN' => array( 331, 2, 0, 0 ),
518  'TINV' => array( 332, 2, 1, 0 ),
519  'CONCATENATE' => array( 336, -1, 1, 0 ),
520  'POWER' => array( 337, 2, 1, 0 ),
521  'RADIANS' => array( 342, 1, 1, 0 ),
522  'DEGREES' => array( 343, 1, 1, 0 ),
523  'SUBTOTAL' => array( 344, -1, 0, 0 ),
524  'SUMIF' => array( 345, -1, 0, 0 ),
525  'COUNTIF' => array( 346, 2, 0, 0 ),
526  'COUNTBLANK' => array( 347, 1, 0, 0 ),
527  'ROMAN' => array( 354, -1, 1, 0 )
528  );
529  }
530 
539  function _convert($token)
540  {
541  if (preg_match("/^\"[^\"]{0,255}\"$/", $token))
542  {
543  return $this->_convertString($token);
544  }
545  elseif (is_numeric($token))
546  {
547  return $this->_convertNumber($token);
548  }
549  // match references like A1 or $A$1
550  elseif (preg_match('/^\$?([A-Ia-i]?[A-Za-z])\$?(\d+)$/',$token))
551  {
552  return $this->_convertRef2d($token);
553  }
554  // match external references like Sheet1!A1 or Sheet1:Sheet2!A1
555  elseif (preg_match("/^\w+(\:\w+)?\![A-Ia-i]?[A-Za-z](\d+)$/",$token))
556  {
557  return $this->_convertRef3d($token);
558  }
559  // match external references like Sheet1!A1 or Sheet1:Sheet2!A1
560  elseif (preg_match("/^'\w+(\:\w+)?'\![A-Ia-i]?[A-Za-z](\d+)$/",$token))
561  {
562  return $this->_convertRef3d($token);
563  }
564  // match ranges like A1:B2
565  elseif (preg_match("/^(\$)?[A-Ia-i]?[A-Za-z](\$)?(\d+)\:(\$)?[A-Ia-i]?[A-Za-z](\$)?(\d+)$/",$token))
566  {
567  return $this->_convertRange2d($token);
568  }
569  // match ranges like A1..B2
570  elseif (preg_match("/^(\$)?[A-Ia-i]?[A-Za-z](\$)?(\d+)\.\.(\$)?[A-Ia-i]?[A-Za-z](\$)?(\d+)$/",$token))
571  {
572  return $this->_convertRange2d($token);
573  }
574  // match external ranges like Sheet1!A1 or Sheet1:Sheet2!A1:B2
575  elseif (preg_match("/^\w+(\:\w+)?\!([A-Ia-i]?[A-Za-z])?(\d+)\:([A-Ia-i]?[A-Za-z])?(\d+)$/",$token))
576  {
577  return $this->_convertRange3d($token);
578  }
579  // match external ranges like 'Sheet1'!A1 or 'Sheet1:Sheet2'!A1:B2
580  elseif (preg_match("/^'\w+(\:\w+)?'\!([A-Ia-i]?[A-Za-z])?(\d+)\:([A-Ia-i]?[A-Za-z])?(\d+)$/",$token))
581  {
582  return $this->_convertRange3d($token);
583  }
584  elseif (isset($this->ptg[$token])) // operators (including parentheses)
585  {
586  return pack("C", $this->ptg[$token]);
587  }
588  // commented so argument number can be processed correctly. See toReversePolish().
589  /*elseif (preg_match("/[A-Z0-9\xc0-\xdc\.]+/",$token))
590  {
591  return($this->_convertFunction($token,$this->_func_args));
592  }*/
593  // if it's an argument, ignore the token (the argument remains)
594  elseif ($token == 'arg')
595  {
596  return '';
597  }
598  // TODO: use real error codes
599  return $this->raiseError("Unknown token $token");
600  }
601 
608  function _convertNumber($num)
609  {
610  // Integer in the range 0..2**16-1
611  if ((preg_match("/^\d+$/",$num)) and ($num <= 65535)) {
612  return pack("Cv", $this->ptg['ptgInt'], $num);
613  }
614  else // A float
615  {
616  if ($this->_byte_order) { // if it's Big Endian
617  $num = strrev($num);
618  }
619  return pack("Cd", $this->ptg['ptgNum'], $num);
620  }
621  }
622 
631  function _convertString($string)
632  {
633  // chop away beggining and ending quotes
634  $string = substr($string, 1, strlen($string) - 2);
635  if (strlen($string) > 255) {
636  return $this->raiseError("String is too long");
637  }
638  if ($this->_BIFF_version == 0x0500) {
639  return pack("CC", $this->ptg['ptgStr'], strlen($string)).$string;
640  }
641  elseif ($this->_BIFF_version == 0x0600) {
642  $encoding = 0; // TODO: Unicode support
643  return pack("CCC", $this->ptg['ptgStr'], strlen($string), $encoding).$string;
644  }
645  }
646 
656  function _convertFunction($token, $num_args)
657  {
658  $args = $this->_functions[$token][1];
659  $volatile = $this->_functions[$token][3];
660 
661  // Fixed number of args eg. TIME($i,$j,$k).
662  if ($args >= 0) {
663  return pack("Cv", $this->ptg['ptgFuncV'], $this->_functions[$token][0]);
664  }
665  // Variable number of args eg. SUM($i,$j,$k, ..).
666  if ($args == -1) {
667  return pack("CCv", $this->ptg['ptgFuncVarV'], $num_args, $this->_functions[$token][0]);
668  }
669  }
670 
677  function _convertRange2d($range)
678  {
679  $class = 2; // as far as I know, this is magick.
680 
681  // Split the range into 2 cell refs
682  if (preg_match("/^([A-Ia-i]?[A-Za-z])(\d+)\:([A-Ia-i]?[A-Za-z])(\d+)$/",$range)) {
683  list($cell1, $cell2) = split(':', $range);
684  }
685  elseif (preg_match("/^([A-Ia-i]?[A-Za-z])(\d+)\.\.([A-Ia-i]?[A-Za-z])(\d+)$/",$range)) {
686  list($cell1, $cell2) = split('\.\.', $range);
687 
688  }
689  else {
690  // TODO: use real error codes
691  return $this->raiseError("Unknown range separator", 0, PEAR_ERROR_DIE);
692  }
693 
694  // Convert the cell references
695  $cell_array1 = $this->_cellToPackedRowcol($cell1);
696  if (PEAR::isError($cell_array1)) {
697  return $cell_array1;
698  }
699  list($row1, $col1) = $cell_array1;
700  $cell_array2 = $this->_cellToPackedRowcol($cell2);
701  if (PEAR::isError($cell_array2)) {
702  return $cell_array2;
703  }
704  list($row2, $col2) = $cell_array2;
705 
706  // The ptg value depends on the class of the ptg.
707  if ($class == 0) {
708  $ptgArea = pack("C", $this->ptg['ptgArea']);
709  }
710  elseif ($class == 1) {
711  $ptgArea = pack("C", $this->ptg['ptgAreaV']);
712  }
713  elseif ($class == 2) {
714  $ptgArea = pack("C", $this->ptg['ptgAreaA']);
715  }
716  else {
717  // TODO: use real error codes
718  return $this->raiseError("Unknown class $class", 0, PEAR_ERROR_DIE);
719  }
720  return $ptgArea . $row1 . $row2 . $col1. $col2;
721  }
722 
731  function _convertRange3d($token)
732  {
733  $class = 2; // as far as I know, this is magick.
734 
735  // Split the ref at the ! symbol
736  list($ext_ref, $range) = split('!', $token);
737 
738  // Convert the external reference part (different for BIFF8)
739  if ($this->_BIFF_version == 0x0500) {
740  $ext_ref = $this->_packExtRef($ext_ref);
741  if (PEAR::isError($ext_ref)) {
742  return $ext_ref;
743  }
744  }
745  elseif ($this->_BIFF_version == 0x0600) {
746  $ext_ref = $this->_getRefIndex($ext_ref);
747  if (PEAR::isError($ext_ref)) {
748  return $ext_ref;
749  }
750  }
751 
752  // Split the range into 2 cell refs
753  list($cell1, $cell2) = split(':', $range);
754 
755  // Convert the cell references
756  if (preg_match("/^(\$)?[A-Ia-i]?[A-Za-z](\$)?(\d+)$/", $cell1))
757  {
758  $cell_array1 = $this->_cellToPackedRowcol($cell1);
759  if (PEAR::isError($cell_array1)) {
760  return $cell_array1;
761  }
762  list($row1, $col1) = $cell_array1;
763  $cell_array2 = $this->_cellToPackedRowcol($cell2);
764  if (PEAR::isError($cell_array2)) {
765  return $cell_array2;
766  }
767  list($row2, $col2) = $cell_array2;
768  }
769  else { // It's a rows range (like 26:27)
770  $cells_array = $this->_rangeToPackedRange($cell1.':'.$cell2);
771  if (PEAR::isError($cells_array)) {
772  return $cells_array;
773  }
774  list($row1, $col1, $row2, $col2) = $cells_array;
775  }
776 
777  // The ptg value depends on the class of the ptg.
778  if ($class == 0) {
779  $ptgArea = pack("C", $this->ptg['ptgArea3d']);
780  }
781  elseif ($class == 1) {
782  $ptgArea = pack("C", $this->ptg['ptgArea3dV']);
783  }
784  elseif ($class == 2) {
785  $ptgArea = pack("C", $this->ptg['ptgArea3dA']);
786  }
787  else {
788  return $this->raiseError("Unknown class $class", 0, PEAR_ERROR_DIE);
789  }
790 
791  return $ptgArea . $ext_ref . $row1 . $row2 . $col1. $col2;
792  }
793 
801  function _convertRef2d($cell)
802  {
803  $class = 2; // as far as I know, this is magick.
804 
805  // Convert the cell reference
806  $cell_array = $this->_cellToPackedRowcol($cell);
807  if (PEAR::isError($cell_array)) {
808  return $cell_array;
809  }
810  list($row, $col) = $cell_array;
811 
812  // The ptg value depends on the class of the ptg.
813  if ($class == 0) {
814  $ptgRef = pack("C", $this->ptg['ptgRef']);
815  }
816  elseif ($class == 1) {
817  $ptgRef = pack("C", $this->ptg['ptgRefV']);
818  }
819  elseif ($class == 2) {
820  $ptgRef = pack("C", $this->ptg['ptgRefA']);
821  }
822  else {
823  // TODO: use real error codes
824  return $this->raiseError("Unknown class $class");
825  }
826  return $ptgRef.$row.$col;
827  }
828 
837  function _convertRef3d($cell)
838  {
839  $class = 2; // as far as I know, this is magick.
840 
841  // Split the ref at the ! symbol
842  list($ext_ref, $cell) = split('!', $cell);
843 
844  // Convert the external reference part (different for BIFF8)
845  if ($this->_BIFF_version == 0x0500) {
846  $ext_ref = $this->_packExtRef($ext_ref);
847  if (PEAR::isError($ext_ref)) {
848  return $ext_ref;
849  }
850  }
851  elseif ($this->_BIFF_version == 0x0600) {
852  $ext_ref = $this->_getRefIndex($ext_ref);
853  if (PEAR::isError($ext_ref)) {
854  return $ext_ref;
855  }
856  }
857 
858  // Convert the cell reference part
859  list($row, $col) = $this->_cellToPackedRowcol($cell);
860 
861  // The ptg value depends on the class of the ptg.
862  if ($class == 0) {
863  $ptgRef = pack("C", $this->ptg['ptgRef3d']);
864  }
865  elseif ($class == 1) {
866  $ptgRef = pack("C", $this->ptg['ptgRef3dV']);
867  }
868  elseif ($class == 2) {
869  $ptgRef = pack("C", $this->ptg['ptgRef3dA']);
870  }
871  else {
872  return $this->raiseError("Unknown class $class", 0, PEAR_ERROR_DIE);
873  }
874 
875  return $ptgRef . $ext_ref. $row . $col;
876  }
877 
886  function _packExtRef($ext_ref)
887  {
888  $ext_ref = preg_replace("/^'/", '', $ext_ref); // Remove leading ' if any.
889  $ext_ref = preg_replace("/'$/", '', $ext_ref); // Remove trailing ' if any.
890 
891  // Check if there is a sheet range eg., Sheet1:Sheet2.
892  if (preg_match("/:/", $ext_ref))
893  {
894  list($sheet_name1, $sheet_name2) = split(':', $ext_ref);
895 
896  $sheet1 = $this->_getSheetIndex($sheet_name1);
897  if ($sheet1 == -1) {
898  return $this->raiseError("Unknown sheet name $sheet_name1 in formula");
899  }
900  $sheet2 = $this->_getSheetIndex($sheet_name2);
901  if ($sheet2 == -1) {
902  return $this->raiseError("Unknown sheet name $sheet_name2 in formula");
903  }
904 
905  // Reverse max and min sheet numbers if necessary
906  if ($sheet1 > $sheet2) {
907  list($sheet1, $sheet2) = array($sheet2, $sheet1);
908  }
909  }
910  else // Single sheet name only.
911  {
912  $sheet1 = $this->_getSheetIndex($ext_ref);
913  if ($sheet1 == -1) {
914  return $this->raiseError("Unknown sheet name $ext_ref in formula");
915  }
916  $sheet2 = $sheet1;
917  }
918 
919  // References are stored relative to 0xFFFF.
920  $offset = -1 - $sheet1;
921 
922  return pack('vdvv', $offset, 0x00, $sheet1, $sheet2);
923  }
924 
935  function _getRefIndex($ext_ref)
936  {
937  $ext_ref = preg_replace("/^'/", '', $ext_ref); // Remove leading ' if any.
938  $ext_ref = preg_replace("/'$/", '', $ext_ref); // Remove trailing ' if any.
939 
940  // Check if there is a sheet range eg., Sheet1:Sheet2.
941  if (preg_match("/:/", $ext_ref))
942  {
943  list($sheet_name1, $sheet_name2) = split(':', $ext_ref);
944 
945  $sheet1 = $this->_getSheetIndex($sheet_name1);
946  if ($sheet1 == -1) {
947  return $this->raiseError("Unknown sheet name $sheet_name1 in formula");
948  }
949  $sheet2 = $this->_getSheetIndex($sheet_name2);
950  if ($sheet2 == -1) {
951  return $this->raiseError("Unknown sheet name $sheet_name2 in formula");
952  }
953 
954  // Reverse max and min sheet numbers if necessary
955  if ($sheet1 > $sheet2) {
956  list($sheet1, $sheet2) = array($sheet2, $sheet1);
957  }
958  }
959  else // Single sheet name only.
960  {
961  $sheet1 = $this->_getSheetIndex($ext_ref);
962  if ($sheet1 == -1) {
963  return $this->raiseError("Unknown sheet name $ext_ref in formula");
964  }
965  $sheet2 = $sheet1;
966  }
967 
968  // assume all references belong to this document
969  $supbook_index = 0x00;
970  $ref = pack('vvv', $supbook_index, $sheet1, $sheet2);
971  $total_references = count($this->_references);
972  $index = -1;
973  for ($i = 0; $i < $total_references; $i++)
974  {
975  if ($ref == $this->_references[$i]) {
976  $index = $i;
977  break;
978  }
979  }
980  // if REF was not found add it to references array
981  if ($index == -1)
982  {
983  $this->_references[$total_references] = $ref;
984  $index = $total_references;
985  }
986 
987  return pack('v', $index);
988  }
989 
998  function _getSheetIndex($sheet_name)
999  {
1000  if (!isset($this->_ext_sheets[$sheet_name])) {
1001  return -1;
1002  }
1003  else {
1004  return $this->_ext_sheets[$sheet_name];
1005  }
1006  }
1007 
1018  function setExtSheet($name, $index)
1019  {
1020  $this->_ext_sheets[$name] = $index;
1021  }
1022 
1030  function _cellToPackedRowcol($cell)
1031  {
1032  $cell = strtoupper($cell);
1033  list($row, $col, $row_rel, $col_rel) = $this->_cellToRowcol($cell);
1034  if ($col >= 256) {
1035  return $this->raiseError("Column in: $cell greater than 255");
1036  }
1037  // FIXME: change for BIFF8
1038  if ($row >= 16384) {
1039  return $this->raiseError("Row in: $cell greater than 16384 ");
1040  }
1041 
1042  // Set the high bits to indicate if row or col are relative.
1043  if ($this->_BIFF_version == 0x0500) {
1044  $row |= $col_rel << 14;
1045  $row |= $row_rel << 15;
1046  $col = pack('C', $col);
1047  }
1048  elseif ($this->_BIFF_version == 0x0600) {
1049  $col |= $col_rel << 14;
1050  $col |= $row_rel << 15;
1051  $col = pack('v', $col);
1052  }
1053  $row = pack('v', $row);
1054 
1055  return array($row, $col);
1056  }
1057 
1066  function _rangeToPackedRange($range)
1067  {
1068  preg_match('/(\$)?(\d+)\:(\$)?(\d+)/', $range, $match);
1069  // return absolute rows if there is a $ in the ref
1070  $row1_rel = empty($match[1]) ? 1 : 0;
1071  $row1 = $match[2];
1072  $row2_rel = empty($match[3]) ? 1 : 0;
1073  $row2 = $match[4];
1074  // Convert 1-index to zero-index
1075  $row1--;
1076  $row2--;
1077  // Trick poor inocent Excel
1078  $col1 = 0;
1079  $col2 = 16383; // FIXME: maximum possible value for Excel 5 (change this!!!)
1080 
1081  // FIXME: this changes for BIFF8
1082  if (($row1 >= 16384) or ($row2 >= 16384)) {
1083  return $this->raiseError("Row in: $range greater than 16384 ");
1084  }
1085 
1086  // Set the high bits to indicate if rows are relative.
1087  if ($this->_BIFF_version == 0x0500) {
1088  $row1 |= $row1_rel << 14; // FIXME: probably a bug
1089  $row2 |= $row2_rel << 15;
1090  $col1 = pack('C', $col1);
1091  $col2 = pack('C', $col2);
1092  }
1093  elseif ($this->_BIFF_version == 0x0600) {
1094  $col1 |= $row1_rel << 15;
1095  $col2 |= $row2_rel << 15;
1096  $col1 = pack('v', $col1);
1097  $col2 = pack('v', $col2);
1098  }
1099  $row1 = pack('v', $row1);
1100  $row2 = pack('v', $row2);
1101 
1102  return array($row1, $col1, $row2, $col2);
1103  }
1104 
1114  function _cellToRowcol($cell)
1115  {
1116  preg_match('/(\$)?([A-I]?[A-Z])(\$)?(\d+)/',$cell,$match);
1117  // return absolute column if there is a $ in the ref
1118  $col_rel = empty($match[1]) ? 1 : 0;
1119  $col_ref = $match[2];
1120  $row_rel = empty($match[3]) ? 1 : 0;
1121  $row = $match[4];
1122 
1123  // Convert base26 column string to a number.
1124  $expn = strlen($col_ref) - 1;
1125  $col = 0;
1126  for ($i=0; $i < strlen($col_ref); $i++)
1127  {
1128  $col += (ord($col_ref{$i}) - ord('A') + 1) * pow(26, $expn);
1129  $expn--;
1130  }
1131 
1132  // Convert 1-index to zero-index
1133  $row--;
1134  $col--;
1135 
1136  return array($row, $col, $row_rel, $col_rel);
1137  }
1138 
1144  function _advance()
1145  {
1146  $i = $this->_current_char;
1147  // eat up white spaces
1148  if ($i < strlen($this->_formula))
1149  {
1150  while ($this->_formula{$i} == " ") {
1151  $i++;
1152  }
1153  if ($i < strlen($this->_formula) - 1) {
1154  $this->_lookahead = $this->_formula{$i+1};
1155  }
1156  $token = "";
1157  }
1158  while ($i < strlen($this->_formula))
1159  {
1160  $token .= $this->_formula{$i};
1161  if ($i < strlen($this->_formula) - 1) {
1162  $this->_lookahead = $this->_formula{$i+1};
1163  }
1164  else {
1165  $this->_lookahead = '';
1166  }
1167  if ($this->_match($token) != '')
1168  {
1169  //if ($i < strlen($this->_formula) - 1) {
1170  // $this->_lookahead = $this->_formula{$i+1};
1171  //}
1172  $this->_current_char = $i + 1;
1173  $this->_current_token = $token;
1174  return 1;
1175  }
1176  if ($i < strlen($this->_formula) - 2) {
1177  $this->_lookahead = $this->_formula{$i+2};
1178  }
1179  // if we run out of characters _lookahead becomes empty
1180  else {
1181  $this->_lookahead = '';
1182  }
1183  $i++;
1184  }
1185  //die("Lexical error ".$this->_current_char);
1186  }
1187 
1195  function _match($token)
1196  {
1197  switch($token)
1198  {
1200  return $token;
1201  break;
1203  return $token;
1204  break;
1206  return $token;
1207  break;
1209  return $token;
1210  break;
1212  return $token;
1213  break;
1215  return $token;
1216  break;
1218  return $token;
1219  break;
1221  return $token;
1222  break;
1224  if ($this->_lookahead == '=') { // it's a GE token
1225  break;
1226  }
1227  return $token;
1228  break;
1230  // it's a LE or a NE token
1231  if (($this->_lookahead == '=') or ($this->_lookahead == '>')) {
1232  break;
1233  }
1234  return $token;
1235  break;
1237  return $token;
1238  break;
1240  return $token;
1241  break;
1243  return $token;
1244  break;
1246  return $token;
1247  break;
1248  default:
1249  // if it's a reference
1250  if (preg_match('/^\$?[A-Ia-i]?[A-Za-z]\$?[0-9]+$/',$token) and
1251  !ereg("[0-9]",$this->_lookahead) and
1252  ($this->_lookahead != ':') and ($this->_lookahead != '.') and
1253  ($this->_lookahead != '!'))
1254  {
1255  return $token;
1256  }
1257  // If it's an external reference (Sheet1!A1 or Sheet1:Sheet2!A1)
1258  elseif (preg_match("/^\w+(\:\w+)?\![A-Ia-i]?[A-Za-z][0-9]+$/",$token) and
1259  !ereg("[0-9]",$this->_lookahead) and
1260  ($this->_lookahead != ':') and ($this->_lookahead != '.'))
1261  {
1262  return $token;
1263  }
1264  // If it's an external reference (Sheet1!A1 or Sheet1:Sheet2!A1)
1265  elseif (preg_match("/^'\w+(\:\w+)?'\![A-Ia-i]?[A-Za-z][0-9]+$/",$token) and
1266  !ereg("[0-9]",$this->_lookahead) and
1267  ($this->_lookahead != ':') and ($this->_lookahead != '.'))
1268  {
1269  return $token;
1270  }
1271  // if it's a range (A1:A2)
1272  elseif (preg_match("/^(\$)?[A-Ia-i]?[A-Za-z](\$)?[0-9]+:(\$)?[A-Ia-i]?[A-Za-z](\$)?[0-9]+$/",$token) and
1273  !ereg("[0-9]",$this->_lookahead))
1274  {
1275  return $token;
1276  }
1277  // if it's a range (A1..A2)
1278  elseif (preg_match("/^(\$)?[A-Ia-i]?[A-Za-z](\$)?[0-9]+\.\.(\$)?[A-Ia-i]?[A-Za-z](\$)?[0-9]+$/",$token) and
1279  !ereg("[0-9]",$this->_lookahead))
1280  {
1281  return $token;
1282  }
1283  // If it's an external range like Sheet1!A1 or Sheet1:Sheet2!A1:B2
1284  elseif (preg_match("/^\w+(\:\w+)?\!([A-Ia-i]?[A-Za-z])?[0-9]+:([A-Ia-i]?[A-Za-z])?[0-9]+$/",$token) and
1285  !ereg("[0-9]",$this->_lookahead))
1286  {
1287  return $token;
1288  }
1289  // If it's an external range like 'Sheet1'!A1 or 'Sheet1:Sheet2'!A1:B2
1290  elseif (preg_match("/^'\w+(\:\w+)?'\!([A-Ia-i]?[A-Za-z])?[0-9]+:([A-Ia-i]?[A-Za-z])?[0-9]+$/",$token) and
1291  !ereg("[0-9]",$this->_lookahead))
1292  {
1293  return $token;
1294  }
1295  // If it's a number (check that it's not a sheet name or range)
1296  elseif (is_numeric($token) and
1297  (!is_numeric($token.$this->_lookahead) or ($this->_lookahead == '')) and
1298  ($this->_lookahead != '!') and ($this->_lookahead != ':'))
1299  {
1300  return $token;
1301  }
1302  // If it's a string (of maximum 255 characters)
1303  elseif (ereg("^\"[^\"]{0,255}\"$",$token))
1304  {
1305  return $token;
1306  }
1307  // if it's a function call
1308  elseif (eregi("^[A-Z0-9\xc0-\xdc\.]+$",$token) and ($this->_lookahead == "("))
1309  {
1310  return $token;
1311  }
1312  return '';
1313  }
1314  }
1315 
1324  function parse($formula)
1325  {
1326  $this->_current_char = 0;
1327  $this->_formula = $formula;
1328  $this->_lookahead = $formula{1};
1329  $this->_advance();
1330  $this->_parse_tree = $this->_condition();
1331  if (PEAR::isError($this->_parse_tree)) {
1332  return $this->_parse_tree;
1333  }
1334  return true;
1335  }
1336 
1344  function _condition()
1345  {
1346  $result = $this->_expression();
1347  if (PEAR::isError($result)) {
1348  return $result;
1349  }
1350  if ($this->_current_token == SPREADSHEET_EXCEL_WRITER_LT)
1351  {
1352  $this->_advance();
1353  $result2 = $this->_expression();
1354  if (PEAR::isError($result2)) {
1355  return $result2;
1356  }
1357  $result = $this->_createTree('ptgLT', $result, $result2);
1358  }
1359  elseif ($this->_current_token == SPREADSHEET_EXCEL_WRITER_GT)
1360  {
1361  $this->_advance();
1362  $result2 = $this->_expression();
1363  if (PEAR::isError($result2)) {
1364  return $result2;
1365  }
1366  $result = $this->_createTree('ptgGT', $result, $result2);
1367  }
1368  elseif ($this->_current_token == SPREADSHEET_EXCEL_WRITER_LE)
1369  {
1370  $this->_advance();
1371  $result2 = $this->_expression();
1372  if (PEAR::isError($result2)) {
1373  return $result2;
1374  }
1375  $result = $this->_createTree('ptgLE', $result, $result2);
1376  }
1377  elseif ($this->_current_token == SPREADSHEET_EXCEL_WRITER_GE)
1378  {
1379  $this->_advance();
1380  $result2 = $this->_expression();
1381  if (PEAR::isError($result2)) {
1382  return $result2;
1383  }
1384  $result = $this->_createTree('ptgGE', $result, $result2);
1385  }
1386  elseif ($this->_current_token == SPREADSHEET_EXCEL_WRITER_EQ)
1387  {
1388  $this->_advance();
1389  $result2 = $this->_expression();
1390  if (PEAR::isError($result2)) {
1391  return $result2;
1392  }
1393  $result = $this->_createTree('ptgEQ', $result, $result2);
1394  }
1395  elseif ($this->_current_token == SPREADSHEET_EXCEL_WRITER_NE)
1396  {
1397  $this->_advance();
1398  $result2 = $this->_expression();
1399  if (PEAR::isError($result2)) {
1400  return $result2;
1401  }
1402  $result = $this->_createTree('ptgNE', $result, $result2);
1403  }
1404  return $result;
1405  }
1406 
1416  function _expression()
1417  {
1418  // If it's a string return a string node
1419  if (ereg("^\"[^\"]{0,255}\"$", $this->_current_token)) {
1420  $result = $this->_createTree($this->_current_token, '', '');
1421  $this->_advance();
1422  return $result;
1423  }
1424  // catch "-" Term
1425  elseif ($this->_current_token == SPREADSHEET_EXCEL_WRITER_SUB) {
1426  $this->_advance();
1427  $result2 = $this->_expression();
1428  $result = $this->_createTree('ptgUminus', $result2, '');
1429  return $result;
1430  }
1431  $result = $this->_term();
1432  if (PEAR::isError($result)) {
1433  return $result;
1434  }
1435  while (($this->_current_token == SPREADSHEET_EXCEL_WRITER_ADD) or
1436  ($this->_current_token == SPREADSHEET_EXCEL_WRITER_SUB))
1437  {
1438  if ($this->_current_token == SPREADSHEET_EXCEL_WRITER_ADD)
1439  {
1440  $this->_advance();
1441  $result2 = $this->_term();
1442  if (PEAR::isError($result2)) {
1443  return $result2;
1444  }
1445  $result = $this->_createTree('ptgAdd', $result, $result2);
1446  }
1447  else
1448  {
1449  $this->_advance();
1450  $result2 = $this->_term();
1451  if (PEAR::isError($result2)) {
1452  return $result2;
1453  }
1454  $result = $this->_createTree('ptgSub', $result, $result2);
1455  }
1456  }
1457  return $result;
1458  }
1459 
1469  {
1470  $result = $this->_createTree('ptgParen', $this->_expression(), '');
1471  return $result;
1472  }
1473 
1481  function _term()
1482  {
1483  $result = $this->_fact();
1484  if (PEAR::isError($result)) {
1485  return $result;
1486  }
1487  while (($this->_current_token == SPREADSHEET_EXCEL_WRITER_MUL) or
1488  ($this->_current_token == SPREADSHEET_EXCEL_WRITER_DIV))
1489  {
1490  if ($this->_current_token == SPREADSHEET_EXCEL_WRITER_MUL)
1491  {
1492  $this->_advance();
1493  $result2 = $this->_fact();
1494  if (PEAR::isError($result2)) {
1495  return $result2;
1496  }
1497  $result = $this->_createTree('ptgMul', $result, $result2);
1498  }
1499  else
1500  {
1501  $this->_advance();
1502  $result2 = $this->_fact();
1503  if (PEAR::isError($result2)) {
1504  return $result2;
1505  }
1506  $result = $this->_createTree('ptgDiv', $result, $result2);
1507  }
1508  }
1509  return $result;
1510  }
1511 
1523  function _fact()
1524  {
1525  if ($this->_current_token == SPREADSHEET_EXCEL_WRITER_OPEN)
1526  {
1527  $this->_advance(); // eat the "("
1528  $result = $this->_parenthesizedExpression();
1529  if ($this->_current_token != SPREADSHEET_EXCEL_WRITER_CLOSE) {
1530  return $this->raiseError("')' token expected.");
1531  }
1532  $this->_advance(); // eat the ")"
1533  return $result;
1534  }
1535  // if it's a reference
1536  if (preg_match('/^\$?[A-Ia-i]?[A-Za-z]\$?[0-9]+$/',$this->_current_token))
1537  {
1538  $result = $this->_createTree($this->_current_token, '', '');
1539  $this->_advance();
1540  return $result;
1541  }
1542  // If it's an external reference (Sheet1!A1 or Sheet1:Sheet2!A1)
1543  elseif (preg_match("/^\w+(\:\w+)?\![A-Ia-i]?[A-Za-z][0-9]+$/",$this->_current_token))
1544  {
1545  $result = $this->_createTree($this->_current_token, '', '');
1546  $this->_advance();
1547  return $result;
1548  }
1549  // If it's an external reference (Sheet1!A1 or Sheet1:Sheet2!A1)
1550  elseif (preg_match("/^'\w+(\:\w+)?'\![A-Ia-i]?[A-Za-z][0-9]+$/",$this->_current_token))
1551  {
1552  $result = $this->_createTree($this->_current_token, '', '');
1553  $this->_advance();
1554  return $result;
1555  }
1556  // if it's a range
1557  elseif (preg_match("/^(\$)?[A-Ia-i]?[A-Za-z](\$)?[0-9]+:(\$)?[A-Ia-i]?[A-Za-z](\$)?[0-9]+$/",$this->_current_token) or
1558  preg_match("/^(\$)?[A-Ia-i]?[A-Za-z](\$)?[0-9]+\.\.(\$)?[A-Ia-i]?[A-Za-z](\$)?[0-9]+$/",$this->_current_token))
1559  {
1560  $result = $this->_current_token;
1561  $this->_advance();
1562  return $result;
1563  }
1564  // If it's an external range (Sheet1!A1 or Sheet1!A1:B2)
1565  elseif (preg_match("/^\w+(\:\w+)?\!([A-Ia-i]?[A-Za-z])?[0-9]+:([A-Ia-i]?[A-Za-z])?[0-9]+$/",$this->_current_token))
1566  {
1567  $result = $this->_current_token;
1568  $this->_advance();
1569  return $result;
1570  }
1571  // If it's an external range ('Sheet1'!A1 or 'Sheet1'!A1:B2)
1572  elseif (preg_match("/^'\w+(\:\w+)?'\!([A-Ia-i]?[A-Za-z])?[0-9]+:([A-Ia-i]?[A-Za-z])?[0-9]+$/",$this->_current_token))
1573  {
1574  $result = $this->_current_token;
1575  $this->_advance();
1576  return $result;
1577  }
1578  elseif (is_numeric($this->_current_token))
1579  {
1580  $result = $this->_createTree($this->_current_token, '', '');
1581  $this->_advance();
1582  return $result;
1583  }
1584  // if it's a function call
1585  elseif (eregi("^[A-Z0-9\xc0-\xdc\.]+$",$this->_current_token))
1586  {
1587  $result = $this->_func();
1588  return $result;
1589  }
1590  return $this->raiseError("Syntax error: ".$this->_current_token.
1591  ", lookahead: ".$this->_lookahead.
1592  ", current char: ".$this->_current_char);
1593  }
1594 
1602  function _func()
1603  {
1604  $num_args = 0; // number of arguments received
1605  $function = $this->_current_token;
1606  $this->_advance();
1607  $this->_advance(); // eat the "("
1608  while ($this->_current_token != ')')
1609  {
1610  if ($num_args > 0)
1611  {
1612  if ($this->_current_token == SPREADSHEET_EXCEL_WRITER_COMA ||
1613  $this->_current_token == SPREADSHEET_EXCEL_WRITER_SEMICOLON)
1614  {
1615  $this->_advance(); // eat the "," or ";"
1616  }
1617  else {
1618  return $this->raiseError("Syntax error: comma expected in ".
1619  "function $function, arg #{$num_args}");
1620  }
1621  $result2 = $this->_condition();
1622  if (PEAR::isError($result2)) {
1623  return $result2;
1624  }
1625  $result = $this->_createTree('arg', $result, $result2);
1626  }
1627  else // first argument
1628  {
1629  $result2 = $this->_condition();
1630  if (PEAR::isError($result2)) {
1631  return $result2;
1632  }
1633  $result = $this->_createTree('arg', '', $result2);
1634  }
1635  $num_args++;
1636  }
1637  $args = $this->_functions[$function][1];
1638  // If fixed number of args eg. TIME($i,$j,$k). Check that the number of args is valid.
1639  if (($args >= 0) and ($args != $num_args)) {
1640  return $this->raiseError("Incorrect number of arguments in function $function() ");
1641  }
1642 
1643  $result = $this->_createTree($function, $result, $num_args);
1644  $this->_advance(); // eat the ")"
1645  return $result;
1646  }
1647 
1658  function _createTree($value, $left, $right)
1659  {
1660  return array('value' => $value, 'left' => $left, 'right' => $right);
1661  }
1662 
1690  function toReversePolish($tree = array())
1691  {
1692  $polish = ""; // the string we are going to return
1693  if (empty($tree)) // If it's the first call use _parse_tree
1694  {
1695  $tree = $this->_parse_tree;
1696  }
1697  if (is_array($tree['left']))
1698  {
1699  $converted_tree = $this->toReversePolish($tree['left']);
1700  if (PEAR::isError($converted_tree)) {
1701  return $converted_tree;
1702  }
1703  $polish .= $converted_tree;
1704  }
1705  elseif ($tree['left'] != '') // It's a final node
1706  {
1707  $converted_tree = $this->_convert($tree['left']);
1708  if (PEAR::isError($converted_tree)) {
1709  return $converted_tree;
1710  }
1711  $polish .= $converted_tree;
1712  }
1713  if (is_array($tree['right']))
1714  {
1715  $converted_tree = $this->toReversePolish($tree['right']);
1716  if (PEAR::isError($converted_tree)) {
1717  return $converted_tree;
1718  }
1719  $polish .= $converted_tree;
1720  }
1721  elseif ($tree['right'] != '') // It's a final node
1722  {
1723  $converted_tree = $this->_convert($tree['right']);
1724  if (PEAR::isError($converted_tree)) {
1725  return $converted_tree;
1726  }
1727  $polish .= $converted_tree;
1728  }
1729  // if it's a function convert it here (so we can set it's arguments)
1730  if (preg_match("/^[A-Z0-9\xc0-\xdc\.]+$/",$tree['value']) and
1731  !preg_match('/^([A-Ia-i]?[A-Za-z])(\d+)$/',$tree['value']) and
1732  !preg_match("/^[A-Ia-i]?[A-Za-z](\d+)\.\.[A-Ia-i]?[A-Za-z](\d+)$/",$tree['value']) and
1733  !is_numeric($tree['value']) and
1734  !isset($this->ptg[$tree['value']]))
1735  {
1736  // left subtree for a function is always an array.
1737  if ($tree['left'] != '') {
1738  $left_tree = $this->toReversePolish($tree['left']);
1739  }
1740  else {
1741  $left_tree = '';
1742  }
1743  if (PEAR::isError($left_tree)) {
1744  return $left_tree;
1745  }
1746  // add it's left subtree and return.
1747  return $left_tree.$this->_convertFunction($tree['value'], $tree['right']);
1748  }
1749  else
1750  {
1751  $converted_tree = $this->_convert($tree['value']);
1752  if (PEAR::isError($converted_tree)) {
1753  return $converted_tree;
1754  }
1755  }
1756  $polish .= $converted_tree;
1757  return $polish;
1758  }
1759 }
1760 ?>