ILIAS  Release_4_0_x_branch Revision 61816
 All Data Structures Namespaces Files Functions Variables Groups Pages
FormulaParser.php
Go to the documentation of this file.
1 <?php
29 /*
30 PARTLY BASED ON:
31  Copyright (c) 2007 E. W. Bachtal, Inc.
32 
33  Permission is hereby granted, free of charge, to any person obtaining a copy of this software
34  and associated documentation files (the "Software"), to deal in the Software without restriction,
35  including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
36  and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
37  subject to the following conditions:
38 
39  The above copyright notice and this permission notice shall be included in all copies or substantial
40  portions of the Software.
41 
42  The software is provided "as is", without warranty of any kind, express or implied, including but not
43  limited to the warranties of merchantability, fitness for a particular purpose and noninfringement. In
44  no event shall the authors or copyright holders be liable for any claim, damages or other liability,
45  whether in an action of contract, tort or otherwise, arising from, out of or in connection with the
46  software or the use or other dealings in the software.
47 
48  http://ewbi.blogs.com/develops/2007/03/excel_formula_p.html
49  http://ewbi.blogs.com/develops/2004/12/excel_formula_p.html
50 */
51 
53 if (!defined('PHPEXCEL_ROOT')) {
57  define('PHPEXCEL_ROOT', dirname(__FILE__) . '/../../');
58 }
59 
61 require_once PHPEXCEL_ROOT . 'PHPExcel/Calculation/FormulaToken.php';
62 
71  /* Character constants */
72  const QUOTE_DOUBLE = '"';
73  const QUOTE_SINGLE = '\'';
74  const BRACKET_CLOSE = ']';
75  const BRACKET_OPEN = '[';
76  const BRACE_OPEN = '{';
77  const BRACE_CLOSE = '}';
78  const PAREN_OPEN = '(';
79  const PAREN_CLOSE = ')';
80  const SEMICOLON = ';';
81  const WHITESPACE = ' ';
82  const COMMA = ',';
83  const ERROR_START = '#';
84 
85  const OPERATORS_SN = "+-";
86  const OPERATORS_INFIX = "+-*/^&=><";
87  const OPERATORS_POSTFIX = "%";
88 
94  private $_formula;
95 
101  private $_tokens = array();
102 
109  public function __construct($pFormula = '')
110  {
111  // Check parameters
112  if (is_null($pFormula)) {
113  throw new Exception("Invalid parameter passed: formula");
114  }
115 
116  // Initialise values
117  $this->_formula = trim($pFormula);
118  // Parse!
119  $this->_parseToTokens();
120  }
121 
127  public function getFormula() {
128  return $this->_formula;
129  }
130 
138  public function getToken($pId = 0) {
139  if (isset($this->_tokens[$pId])) {
140  return $this->_tokens[$pId];
141  } else {
142  throw new Exception("Token with id $pId does not exist.");
143  }
144  }
145 
151  public function getTokenCount() {
152  return count($this->_tokens);
153  }
154 
160  public function getTokens() {
161  return $this->_tokens;
162  }
163 
167  private function _parseToTokens() {
168  // No attempt is made to verify formulas; assumes formulas are derived from Excel, where
169  // they can only exist if valid; stack overflows/underflows sunk as nulls without exceptions.
170 
171  // Check if the formula has a valid starting =
172  $formulaLength = strlen($this->_formula);
173  if ($formulaLength < 2 || $this->_formula{0} != '=') return;
174 
175  // Helper variables
176  $tokens1 = $tokens2 = $stack = array();
177  $inString = $inPath = $inRange = $inError = false;
178  $token = $previousToken = $nextToken = null;
179 
180  $index = 1;
181  $value = '';
182 
183  $ERRORS = array("#NULL!", "#DIV/0!", "#VALUE!", "#REF!", "#NAME?", "#NUM!", "#N/A");
184  $COMPARATORS_MULTI = array(">=", "<=", "<>");
185 
186  while ($index < $formulaLength) {
187  // state-dependent character evaluation (order is important)
188 
189  // double-quoted strings
190  // embeds are doubled
191  // end marks token
192  if ($inString) {
193  if ($this->_formula{$index} == PHPExcel_Calculation_FormulaParser::QUOTE_DOUBLE) {
194  if ((($index + 2) <= $formulaLength) && ($this->_formula{$index + 1} == PHPExcel_Calculation_FormulaParser::QUOTE_DOUBLE)) {
196  ++$index;
197  } else {
198  $inString = false;
200  $value = "";
201  }
202  } else {
203  $value .= $this->_formula{$index};
204  }
205  ++$index;
206  continue;
207  }
208 
209  // single-quoted strings (links)
210  // embeds are double
211  // end does not mark a token
212  if ($inPath) {
213  if ($this->_formula{$index} == PHPExcel_Calculation_FormulaParser::QUOTE_SINGLE) {
214  if ((($index + 2) <= $formulaLength) && ($this->_formula{$index + 1} == PHPExcel_Calculation_FormulaParser::QUOTE_SINGLE)) {
216  ++$index;
217  } else {
218  $inPath = false;
219  }
220  } else {
221  $value .= $this->_formula{$index};
222  }
223  ++$index;
224  continue;
225  }
226 
227  // bracked strings (R1C1 range index or linked workbook name)
228  // no embeds (changed to "()" by Excel)
229  // end does not mark a token
230  if ($inRange) {
231  if ($this->_formula{$index} == PHPExcel_Calculation_FormulaParser::BRACKET_CLOSE) {
232  $inRange = false;
233  }
234  $value .= $this->_formula{$index};
235  ++$index;
236  continue;
237  }
238 
239  // error values
240  // end marks a token, determined from absolute list of values
241  if ($inError) {
242  $value .= $this->_formula{$index};
243  ++$index;
244  if (in_array($value, $ERRORS)) {
245  $inError = false;
247  $value = "";
248  }
249  continue;
250  }
251 
252  // scientific notation check
253  if (strpos(PHPExcel_Calculation_FormulaParser::OPERATORS_SN, $this->_formula{$index}) !== false) {
254  if (strlen($value) > 1) {
255  if (preg_match("/^[1-9]{1}(\.[0-9]+)?E{1}$/", $this->_formula{$index}) != 0) {
256  $value .= $this->_formula{$index};
257  ++$index;
258  continue;
259  }
260  }
261  }
262 
263  // independent character evaluation (order not important)
264 
265  // establish state-dependent character evaluations
266  if ($this->_formula{$index} == PHPExcel_Calculation_FormulaParser::QUOTE_DOUBLE) {
267  if (strlen($value > 0)) { // unexpected
269  $value = "";
270  }
271  $inString = true;
272  ++$index;
273  continue;
274  }
275 
276  if ($this->_formula{$index} == PHPExcel_Calculation_FormulaParser::QUOTE_SINGLE) {
277  if (strlen($value) > 0) { // unexpected
279  $value = "";
280  }
281  $inPath = true;
282  ++$index;
283  continue;
284  }
285 
286  if ($this->_formula{$index} == PHPExcel_Calculation_FormulaParser::BRACKET_OPEN) {
287  $inRange = true;
289  ++$index;
290  continue;
291  }
292 
293  if ($this->_formula{$index} == PHPExcel_Calculation_FormulaParser::ERROR_START) {
294  if (strlen($value) > 0) { // unexpected
296  $value = "";
297  }
298  $inError = true;
300  ++$index;
301  continue;
302  }
303 
304  // mark start and end of arrays and array rows
305  if ($this->_formula{$index} == PHPExcel_Calculation_FormulaParser::BRACE_OPEN) {
306  if (strlen($value) > 0) { // unexpected
308  $value = "";
309  }
310 
312  $tokens1[] = $tmp;
313  $stack[] = clone $tmp;
314 
316  $tokens1[] = $tmp;
317  $stack[] = clone $tmp;
318 
319  ++$index;
320  continue;
321  }
322 
323  if ($this->_formula{$index} == PHPExcel_Calculation_FormulaParser::SEMICOLON) {
324  if (strlen($value) > 0) {
326  $value = "";
327  }
328 
329  $tmp = array_pop($stack);
330  $tmp->setValue("");
332  $tokens1[] = $tmp;
333 
335  $tokens1[] = $tmp;
336 
338  $tokens1[] = $tmp;
339  $stack[] = clone $tmp;
340 
341  ++$index;
342  continue;
343  }
344 
345  if ($this->_formula{$index} == PHPExcel_Calculation_FormulaParser::BRACE_CLOSE) {
346  if (strlen($value) > 0) {
348  $value = "";
349  }
350 
351  $tmp = array_pop($stack);
352  $tmp->setValue("");
354  $tokens1[] = $tmp;
355 
356  $tmp = array_pop($stack);
357  $tmp->setValue("");
359  $tokens1[] = $tmp;
360 
361  ++$index;
362  continue;
363  }
364 
365  // trim white-space
366  if ($this->_formula{$index} == PHPExcel_Calculation_FormulaParser::WHITESPACE) {
367  if (strlen($value) > 0) {
369  $value = "";
370  }
372  ++$index;
373  while (($this->_formula{$index} == PHPExcel_Calculation_FormulaParser::WHITESPACE) && ($index < $formulaLength)) {
374  ++$index;
375  }
376  continue;
377  }
378 
379  // multi-character comparators
380  if (($index + 2) <= $formulaLength) {
381  if (in_array(substr($this->_formula, $index, 2), $COMPARATORS_MULTI)) {
382  if (strlen($value) > 0) {
384  $value = "";
385  }
387  $index += 2;
388  continue;
389  }
390  }
391 
392  // standard infix operators
393  if (strpos(PHPExcel_Calculation_FormulaParser::OPERATORS_INFIX, $this->_formula{$index}) !== false) {
394  if (strlen($value) > 0) {
396  $value = "";
397  }
399  ++$index;
400  continue;
401  }
402 
403  // standard postfix operators (only one)
404  if (strpos(PHPExcel_Calculation_FormulaParser::OPERATORS_POSTFIX, $this->_formula{$index}) !== false) {
405  if (strlen($value) > 0) {
407  $value = "";
408  }
410  ++$index;
411  continue;
412  }
413 
414  // start subexpression or function
415  if ($this->_formula{$index} == PHPExcel_Calculation_FormulaParser::PAREN_OPEN) {
416  if (strlen($value) > 0) {
418  $tokens1[] = $tmp;
419  $stack[] = clone $tmp;
420  $value = "";
421  } else {
423  $tokens1[] = $tmp;
424  $stack[] = clone $tmp;
425  }
426  ++$index;
427  continue;
428  }
429 
430  // function, subexpression, or array parameters, or operand unions
431  if ($this->_formula{$index} == PHPExcel_Calculation_FormulaParser::COMMA) {
432  if (strlen($value) > 0) {
434  $value = "";
435  }
436 
437  $tmp = array_pop($stack);
438  $tmp->setValue("");
440  $stack[] = $tmp;
441 
442  if ($tmp->getTokenType() == PHPExcel_Calculation_FormulaToken::TOKEN_TYPE_FUNCTION) {
444  } else {
446  }
447  ++$index;
448  continue;
449  }
450 
451  // stop subexpression
452  if ($this->_formula{$index} == PHPExcel_Calculation_FormulaParser::PAREN_CLOSE) {
453  if (strlen($value) > 0) {
455  $value = "";
456  }
457 
458  $tmp = array_pop($stack);
459  $tmp->setValue("");
461  $tokens1[] = $tmp;
462 
463  ++$index;
464  continue;
465  }
466 
467  // token accumulation
468  $value .= $this->_formula{$index};
469  ++$index;
470  }
471 
472  // dump remaining accumulation
473  if (strlen($value) > 0) {
475  }
476 
477  // move tokenList to new set, excluding unnecessary white-space tokens and converting necessary ones to intersections
478  $tokenCount = count($tokens1);
479  for ($i = 0; $i < $tokenCount; ++$i) {
480  $token = $tokens1[$i];
481  if (isset($tokens1[$i - 1])) {
482  $previousToken = $tokens1[$i - 1];
483  } else {
484  $previousToken = null;
485  }
486  if (isset($tokens1[$i + 1])) {
487  $nextToken = $tokens1[$i + 1];
488  } else {
489  $nextToken = null;
490  }
491 
492  if (is_null($token)) {
493  continue;
494  }
495 
496  if ($token->getTokenType() != PHPExcel_Calculation_FormulaToken::TOKEN_TYPE_WHITESPACE) {
497  $tokens2[] = $token;
498  continue;
499  }
500 
501  if (is_null($previousToken)) {
502  continue;
503  }
504 
505  if (! (
506  (($previousToken->getTokenType() == PHPExcel_Calculation_FormulaToken::TOKEN_TYPE_FUNCTION) && ($previousToken->getTokenSubType() == PHPExcel_Calculation_FormulaToken::TOKEN_SUBTYPE_STOP)) ||
507  (($previousToken->getTokenType() == PHPExcel_Calculation_FormulaToken::TOKEN_TYPE_SUBEXPRESSION) && ($previousToken->getTokenSubType() == PHPExcel_Calculation_FormulaToken::TOKEN_SUBTYPE_STOP)) ||
508  ($previousToken->getTokenType() == PHPExcel_Calculation_FormulaToken::TOKEN_TYPE_OPERAND)
509  ) ) {
510  continue;
511  }
512 
513  if (is_null($nextToken)) {
514  continue;
515  }
516 
517  if (! (
518  (($nextToken->getTokenType() == PHPExcel_Calculation_FormulaToken::TOKEN_TYPE_FUNCTION) && ($nextToken->getTokenSubType() == PHPExcel_Calculation_FormulaToken::TOKEN_SUBTYPE_START)) ||
519  (($nextToken->getTokenType() == PHPExcel_Calculation_FormulaToken::TOKEN_TYPE_SUBEXPRESSION) && ($nextToken->getTokenSubType() == PHPExcel_Calculation_FormulaToken::TOKEN_SUBTYPE_START)) ||
520  ($nextToken->getTokenType() == PHPExcel_Calculation_FormulaToken::TOKEN_TYPE_OPERAND)
521  ) ) {
522  continue;
523  }
524 
526  }
527 
528  // move tokens to final list, switching infix "-" operators to prefix when appropriate, switching infix "+" operators
529  // to noop when appropriate, identifying operand and infix-operator subtypes, and pulling "@" from function names
530  $this->_tokens = array();
531 
532  $tokenCount = count($tokens2);
533  for ($i = 0; $i < $tokenCount; ++$i) {
534  $token = $tokens2[$i];
535  if (isset($tokens2[$i - 1])) {
536  $previousToken = $tokens2[$i - 1];
537  } else {
538  $previousToken = null;
539  }
540  if (isset($tokens2[$i + 1])) {
541  $nextToken = $tokens2[$i + 1];
542  } else {
543  $nextToken = null;
544  }
545 
546  if (is_null($token)) {
547  continue;
548  }
549 
550  if ($token->getTokenType() == PHPExcel_Calculation_FormulaToken::TOKEN_TYPE_OPERATORINFIX && $token->getValue() == "-") {
551  if ($i == 0) {
553  } else if (
554  (($previousToken->getTokenType() == PHPExcel_Calculation_FormulaToken::TOKEN_TYPE_FUNCTION) && ($previousToken->getTokenSubType() == PHPExcel_Calculation_FormulaToken::TOKEN_SUBTYPE_STOP)) ||
555  (($previousToken->getTokenType() == PHPExcel_Calculation_FormulaToken::TOKEN_TYPE_SUBEXPRESSION) && ($previousToken->getTokenSubType() == PHPExcel_Calculation_FormulaToken::TOKEN_SUBTYPE_STOP)) ||
556  ($previousToken->getTokenType() == PHPExcel_Calculation_FormulaToken::TOKEN_TYPE_OPERATORPOSTFIX) ||
557  ($previousToken->getTokenType() == PHPExcel_Calculation_FormulaToken::TOKEN_TYPE_OPERAND)
558  ) {
560  } else {
562  }
563 
564  $this->_tokens[] = $token;
565  continue;
566  }
567 
568  if ($token->getTokenType() == PHPExcel_Calculation_FormulaToken::TOKEN_TYPE_OPERATORINFIX && $token->getValue() == "+") {
569  if ($i == 0) {
570  continue;
571  } else if (
572  (($previousToken->getTokenType() == PHPExcel_Calculation_FormulaToken::TOKEN_TYPE_FUNCTION) && ($previousToken->getTokenSubType() == PHPExcel_Calculation_FormulaToken::TOKEN_SUBTYPE_STOP)) ||
573  (($previousToken->getTokenType() == PHPExcel_Calculation_FormulaToken::TOKEN_TYPE_SUBEXPRESSION) && ($previousToken->getTokenSubType() == PHPExcel_Calculation_FormulaToken::TOKEN_SUBTYPE_STOP)) ||
574  ($previousToken->getTokenType() == PHPExcel_Calculation_FormulaToken::TOKEN_TYPE_OPERATORPOSTFIX) ||
575  ($previousToken->getTokenType() == PHPExcel_Calculation_FormulaToken::TOKEN_TYPE_OPERAND)
576  ) {
578  } else {
579  continue;
580  }
581 
582  $this->_tokens[] = $token;
583  continue;
584  }
585 
587  if (strpos("<>=", substr($token->getValue(), 0, 1)) !== false) {
589  } else if ($token->getValue() == "&") {
591  } else {
593  }
594 
595  $this->_tokens[] = $token;
596  continue;
597  }
598 
599  if ($token->getTokenType() == PHPExcel_Calculation_FormulaToken::TOKEN_TYPE_OPERAND && $token->getTokenSubType() == PHPExcel_Calculation_FormulaToken::TOKEN_SUBTYPE_NOTHING) {
600  if (!is_numeric($token->getValue())) {
601  if (strtoupper($token->getValue()) == "TRUE" || strtoupper($token->getValue() == "FALSE")) {
603  } else {
605  }
606  } else {
608  }
609 
610  $this->_tokens[] = $token;
611  continue;
612  }
613 
614  if ($token->getTokenType() == PHPExcel_Calculation_FormulaToken::TOKEN_TYPE_FUNCTION) {
615  if (strlen($token->getValue() > 0)) {
616  if (substr($token->getValue(), 0, 1) == "@") {
617  $token->setValue(substr($token->getValue(), 1));
618  }
619  }
620  }
621 
622  $this->_tokens[] = $token;
623  }
624  }
625 }