source: pmb4.1/trunk/fuentes/pmb/classes/writeexcel/class.writeexcel_formula.inc.php @ 478

Last change on this file since 478 was 478, checked in by mabarracus, 4 years ago

copy trusty code 4.1

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