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