Subversion-Projekte lars-tiefland.php_share

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

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