Code

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