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