コード例 #1
0
 private function _processTokenStack($tokens, $cellID = null, PHPExcel_Cell $pCell = null)
 {
     if ($tokens == false) {
         return false;
     }
     //	If we're using cell caching, then $pCell may well be flushed back to the cache (which detaches the parent worksheet),
     //		so we store the parent worksheet so that we can re-attach it when necessary
     $pCellParent = !is_null($pCell) ? $pCell->getParent() : null;
     $stack = new PHPExcel_Token_Stack();
     //	Loop through each token in turn
     foreach ($tokens as $tokenData) {
         //			print_r($tokenData);
         //			echo '<br />';
         $token = $tokenData['value'];
         //			echo '<b>Token is '.$token.'</b><br />';
         // if the token is a binary operator, pop the top two values off the stack, do the operation, and push the result back on the stack
         if (in_array($token, self::$_binaryOperators, true)) {
             //				echo 'Token is a binary operator<br />';
             //	We must have two operands, error if we don't
             if (is_null($operand2Data = $stack->pop())) {
                 return $this->_raiseFormulaError('Internal error - Operand value missing from stack');
             }
             if (is_null($operand1Data = $stack->pop())) {
                 return $this->_raiseFormulaError('Internal error - Operand value missing from stack');
             }
             //	Log what we're doing
             $operand1 = $operand1Data['value'];
             $operand2 = $operand2Data['value'];
             if ($token == ':') {
                 $this->_writeDebug('Evaluating Range ' . self::_showValue($operand1Data['reference']) . $token . self::_showValue($operand2Data['reference']));
             } else {
                 $this->_writeDebug('Evaluating ' . self::_showValue($operand1) . ' ' . $token . ' ' . self::_showValue($operand2));
             }
             //	Process the operation in the appropriate manner
             switch ($token) {
                 //	Comparison (Boolean) Operators
                 case '>':
                     //	Greater than
                 //	Greater than
                 case '<':
                     //	Less than
                 //	Less than
                 case '>=':
                     //	Greater than or Equal to
                 //	Greater than or Equal to
                 case '<=':
                     //	Less than or Equal to
                 //	Less than or Equal to
                 case '=':
                     //	Equality
                 //	Equality
                 case '<>':
                     //	Inequality
                     $this->_executeBinaryComparisonOperation($cellID, $operand1, $operand2, $token, $stack);
                     break;
                     //	Binary Operators
                 //	Binary Operators
                 case ':':
                     //	Range
                     $sheet1 = $sheet2 = '';
                     if (strpos($operand1Data['reference'], '!') !== false) {
                         list($sheet1, $operand1Data['reference']) = explode('!', $operand1Data['reference']);
                     } else {
                         $sheet1 = !is_null($pCellParent) ? $pCellParent->getTitle() : '';
                     }
                     if (strpos($operand2Data['reference'], '!') !== false) {
                         list($sheet2, $operand2Data['reference']) = explode('!', $operand2Data['reference']);
                     } else {
                         $sheet2 = $sheet1;
                     }
                     if ($sheet1 == $sheet2) {
                         if (is_null($operand1Data['reference'])) {
                             if (trim($operand1Data['value']) != '' && is_numeric($operand1Data['value'])) {
                                 $operand1Data['reference'] = $pCell->getColumn() . $operand1Data['value'];
                             } elseif (trim($operand1Data['reference']) == '') {
                                 $operand1Data['reference'] = $pCell->getColumn() . $pCell->getRow();
                             } else {
                                 $operand1Data['reference'] = $operand1Data['value'] . $pCell->getRow();
                             }
                         }
                         if (is_null($operand2Data['reference'])) {
                             if (trim($operand2Data['value']) != '' && is_numeric($operand2Data['value'])) {
                                 $operand2Data['reference'] = $pCell->getColumn() . $operand2Data['value'];
                             } elseif (trim($operand2Data['reference']) == '') {
                                 $operand2Data['reference'] = $pCell->getColumn() . $pCell->getRow();
                             } else {
                                 $operand2Data['reference'] = $operand2Data['value'] . $pCell->getRow();
                             }
                         }
                         $oData = array_merge(explode(':', $operand1Data['reference']), explode(':', $operand2Data['reference']));
                         $oCol = $oRow = array();
                         foreach ($oData as $oDatum) {
                             $oCR = PHPExcel_Cell::coordinateFromString($oDatum);
                             $oCol[] = PHPExcel_Cell::columnIndexFromString($oCR[0]) - 1;
                             $oRow[] = $oCR[1];
                         }
                         $cellRef = PHPExcel_Cell::stringFromColumnIndex(min($oCol)) . min($oRow) . ':' . PHPExcel_Cell::stringFromColumnIndex(max($oCol)) . max($oRow);
                         if (!is_null($pCellParent)) {
                             $cellValue = $this->extractCellRange($cellRef, $pCellParent->getParent()->getSheetByName($sheet1), false);
                         } else {
                             return $this->_raiseFormulaError('Unable to access Cell Reference');
                         }
                         $stack->push('Cell Reference', $cellValue, $cellRef);
                     } else {
                         $stack->push('Error', PHPExcel_Calculation_Functions::REF(), NULL);
                     }
                     break;
                 case '+':
                     //	Addition
                     $this->_executeNumericBinaryOperation($cellID, $operand1, $operand2, $token, 'plusEquals', $stack);
                     break;
                 case '-':
                     //	Subtraction
                     $this->_executeNumericBinaryOperation($cellID, $operand1, $operand2, $token, 'minusEquals', $stack);
                     break;
                 case '*':
                     //	Multiplication
                     $this->_executeNumericBinaryOperation($cellID, $operand1, $operand2, $token, 'arrayTimesEquals', $stack);
                     break;
                 case '/':
                     //	Division
                     $this->_executeNumericBinaryOperation($cellID, $operand1, $operand2, $token, 'arrayRightDivide', $stack);
                     break;
                 case '^':
                     //	Exponential
                     $this->_executeNumericBinaryOperation($cellID, $operand1, $operand2, $token, 'power', $stack);
                     break;
                 case '&':
                     //	Concatenation
                     //	If either of the operands is a matrix, we need to treat them both as matrices
                     //		(converting the other operand to a matrix if need be); then perform the required
                     //		matrix operation
                     if (is_bool($operand1)) {
                         $operand1 = $operand1 ? self::$_localeBoolean['TRUE'] : self::$_localeBoolean['FALSE'];
                     }
                     if (is_bool($operand2)) {
                         $operand2 = $operand2 ? self::$_localeBoolean['TRUE'] : self::$_localeBoolean['FALSE'];
                     }
                     if (is_array($operand1) || is_array($operand2)) {
                         //	Ensure that both operands are arrays/matrices
                         self::_checkMatrixOperands($operand1, $operand2, 2);
                         try {
                             //	Convert operand 1 from a PHP array to a matrix
                             $matrix = new Matrix($operand1);
                             //	Perform the required operation against the operand 1 matrix, passing in operand 2
                             $matrixResult = $matrix->concat($operand2);
                             $result = $matrixResult->getArray();
                         } catch (Exception $ex) {
                             $this->_writeDebug('JAMA Matrix Exception: ' . $ex->getMessage());
                             $result = '#VALUE!';
                         }
                     } else {
                         $result = '"' . str_replace('""', '"', self::_unwrapResult($operand1, '"') . self::_unwrapResult($operand2, '"')) . '"';
                     }
                     $this->_writeDebug('Evaluation Result is ' . self::_showTypeDetails($result));
                     $stack->push('Value', $result);
                     break;
                 case '|':
                     //	Intersect
                     $rowIntersect = array_intersect_key($operand1, $operand2);
                     $cellIntersect = $oCol = $oRow = array();
                     foreach (array_keys($rowIntersect) as $col) {
                         $oCol[] = PHPExcel_Cell::columnIndexFromString($col) - 1;
                         $cellIntersect[$col] = array_intersect_key($operand1[$col], $operand2[$col]);
                         foreach ($cellIntersect[$col] as $row => $data) {
                             $oRow[] = $row;
                         }
                     }
                     $cellRef = PHPExcel_Cell::stringFromColumnIndex(min($oCol)) . min($oRow) . ':' . PHPExcel_Cell::stringFromColumnIndex(max($oCol)) . max($oRow);
                     $this->_writeDebug('Evaluation Result is ' . self::_showTypeDetails($cellIntersect));
                     $stack->push('Value', $cellIntersect, $cellRef);
                     break;
             }
             // if the token is a unary operator, pop one value off the stack, do the operation, and push it back on
         } elseif ($token === '~' || $token === '%') {
             //				echo 'Token is a unary operator<br />';
             if (is_null($arg = $stack->pop())) {
                 return $this->_raiseFormulaError('Internal error - Operand value missing from stack');
             }
             $arg = $arg['value'];
             if ($token === '~') {
                 //					echo 'Token is a negation operator<br />';
                 $this->_writeDebug('Evaluating Negation of ' . self::_showValue($arg));
                 $multiplier = -1;
             } else {
                 //					echo 'Token is a percentile operator<br />';
                 $this->_writeDebug('Evaluating Percentile of ' . self::_showValue($arg));
                 $multiplier = 0.01;
             }
             if (is_array($arg)) {
                 self::_checkMatrixOperands($arg, $multiplier, 2);
                 try {
                     $matrix1 = new Matrix($arg);
                     $matrixResult = $matrix1->arrayTimesEquals($multiplier);
                     $result = $matrixResult->getArray();
                 } catch (Exception $ex) {
                     $this->_writeDebug('JAMA Matrix Exception: ' . $ex->getMessage());
                     $result = '#VALUE!';
                 }
                 $this->_writeDebug('Evaluation Result is ' . self::_showTypeDetails($result));
                 $stack->push('Value', $result);
             } else {
                 $this->_executeNumericBinaryOperation($cellID, $multiplier, $arg, '*', 'arrayTimesEquals', $stack);
             }
         } elseif (preg_match('/^' . self::CALCULATION_REGEXP_CELLREF . '$/i', $token, $matches)) {
             $cellRef = null;
             //				echo 'Element '.$token.' is a Cell reference<br />';
             if (isset($matches[8])) {
                 //					echo 'Reference is a Range of cells<br />';
                 if (is_null($pCell)) {
                     //						We can't access the range, so return a REF error
                     $cellValue = PHPExcel_Calculation_Functions::REF();
                 } else {
                     $cellRef = $matches[6] . $matches[7] . ':' . $matches[9] . $matches[10];
                     if ($matches[2] > '') {
                         $matches[2] = trim($matches[2], "\"'");
                         //							echo '$cellRef='.$cellRef.' in worksheet '.$matches[2].'<br />';
                         $this->_writeDebug('Evaluating Cell Range ' . $cellRef . ' in worksheet ' . $matches[2]);
                         if (!is_null($pCellParent)) {
                             $cellValue = $this->extractCellRange($cellRef, $pCellParent->getParent()->getSheetByName($matches[2]), false);
                         } else {
                             return $this->_raiseFormulaError('Unable to access Cell Reference');
                         }
                         $this->_writeDebug('Evaluation Result for cells ' . $cellRef . ' in worksheet ' . $matches[2] . ' is ' . self::_showTypeDetails($cellValue));
                         //							$cellRef = $matches[2].'!'.$cellRef;
                     } else {
                         //							echo '$cellRef='.$cellRef.' in current worksheet<br />';
                         $this->_writeDebug('Evaluating Cell Range ' . $cellRef . ' in current worksheet');
                         if (!is_null($pCellParent)) {
                             $cellValue = $this->extractCellRange($cellRef, $pCellParent, false);
                         } else {
                             return $this->_raiseFormulaError('Unable to access Cell Reference');
                         }
                         $this->_writeDebug('Evaluation Result for cells ' . $cellRef . ' is ' . self::_showTypeDetails($cellValue));
                     }
                 }
             } else {
                 //					echo 'Reference is a single Cell<br />';
                 if (is_null($pCell)) {
                     //						We can't access the cell, so return a REF error
                     $cellValue = PHPExcel_Calculation_Functions::REF();
                 } else {
                     $cellRef = $matches[6] . $matches[7];
                     if ($matches[2] > '') {
                         $matches[2] = trim($matches[2], "\"'");
                         //							echo '$cellRef='.$cellRef.' in worksheet '.$matches[2].'<br />';
                         $this->_writeDebug('Evaluating Cell ' . $cellRef . ' in worksheet ' . $matches[2]);
                         if (!is_null($pCellParent)) {
                             if ($pCellParent->getParent()->getSheetByName($matches[2])->cellExists($cellRef)) {
                                 $cellValue = $this->extractCellRange($cellRef, $pCellParent->getParent()->getSheetByName($matches[2]), false);
                                 $pCell->attach($pCellParent);
                             } else {
                                 $cellValue = PHPExcel_Calculation_Functions::REF();
                             }
                         } else {
                             return $this->_raiseFormulaError('Unable to access Cell Reference');
                         }
                         $this->_writeDebug('Evaluation Result for cell ' . $cellRef . ' in worksheet ' . $matches[2] . ' is ' . self::_showTypeDetails($cellValue));
                         //							$cellRef = $matches[2].'!'.$cellRef;
                     } else {
                         //							echo '$cellRef='.$cellRef.' in current worksheet<br />';
                         $this->_writeDebug('Evaluating Cell ' . $cellRef . ' in current worksheet');
                         if ($pCellParent->cellExists($cellRef)) {
                             $cellValue = $this->extractCellRange($cellRef, $pCellParent, false);
                             $pCell->attach($pCellParent);
                         } else {
                             $cellValue = NULL;
                         }
                         $this->_writeDebug('Evaluation Result for cell ' . $cellRef . ' is ' . self::_showTypeDetails($cellValue));
                     }
                 }
             }
             $stack->push('Value', $cellValue, $cellRef);
             // if the token is a function, pop arguments off the stack, hand them to the function, and push the result back on
         } elseif (preg_match('/^' . self::CALCULATION_REGEXP_FUNCTION . '$/i', $token, $matches)) {
             //				echo 'Token is a function<br />';
             $functionName = $matches[1];
             $argCount = $stack->pop();
             $argCount = $argCount['value'];
             if ($functionName != 'MKMATRIX') {
                 $this->_writeDebug('Evaluating Function ' . self::_localeFunc($functionName) . '() with ' . ($argCount == 0 ? 'no' : $argCount) . ' argument' . ($argCount == 1 ? '' : 's'));
             }
             if (array_key_exists($functionName, self::$_PHPExcelFunctions) || array_key_exists($functionName, self::$_controlFunctions)) {
                 // function
                 if (array_key_exists($functionName, self::$_PHPExcelFunctions)) {
                     $functionCall = self::$_PHPExcelFunctions[$functionName]['functionCall'];
                     $passByReference = isset(self::$_PHPExcelFunctions[$functionName]['passByReference']);
                     $passCellReference = isset(self::$_PHPExcelFunctions[$functionName]['passCellReference']);
                 } elseif (array_key_exists($functionName, self::$_controlFunctions)) {
                     $functionCall = self::$_controlFunctions[$functionName]['functionCall'];
                     $passByReference = isset(self::$_controlFunctions[$functionName]['passByReference']);
                     $passCellReference = isset(self::$_controlFunctions[$functionName]['passCellReference']);
                 }
                 // get the arguments for this function
                 //					echo 'Function '.$functionName.' expects '.$argCount.' arguments<br />';
                 $args = $argArrayVals = array();
                 for ($i = 0; $i < $argCount; ++$i) {
                     $arg = $stack->pop();
                     $a = $argCount - $i - 1;
                     if ($passByReference && isset(self::$_PHPExcelFunctions[$functionName]['passByReference'][$a]) && self::$_PHPExcelFunctions[$functionName]['passByReference'][$a]) {
                         if (is_null($arg['reference'])) {
                             $args[] = $cellID;
                             if ($functionName != 'MKMATRIX') {
                                 $argArrayVals[] = self::_showValue($cellID);
                             }
                         } else {
                             $args[] = $arg['reference'];
                             if ($functionName != 'MKMATRIX') {
                                 $argArrayVals[] = self::_showValue($arg['reference']);
                             }
                         }
                     } else {
                         $args[] = self::_unwrapResult($arg['value']);
                         if ($functionName != 'MKMATRIX') {
                             $argArrayVals[] = self::_showValue($arg['value']);
                         }
                     }
                 }
                 //	Reverse the order of the arguments
                 krsort($args);
                 if ($passByReference && $argCount == 0) {
                     $args[] = $cellID;
                     $argArrayVals[] = self::_showValue($cellID);
                 }
                 //					echo 'Arguments are: ';
                 //					print_r($args);
                 //					echo '<br />';
                 if ($functionName != 'MKMATRIX') {
                     krsort($argArrayVals);
                     $this->_writeDebug('Evaluating ' . self::_localeFunc($functionName) . '( ' . implode(self::$_localeArgumentSeparator . ' ', $argArrayVals) . ' )');
                 }
                 //	Process each argument in turn, building the return value as an array
                 //					if (($argCount == 1) && (is_array($args[1])) && ($functionName != 'MKMATRIX')) {
                 //						$operand1 = $args[1];
                 //						$this->_writeDebug('Argument is a matrix: '.self::_showValue($operand1));
                 //						$result = array();
                 //						$row = 0;
                 //						foreach($operand1 as $args) {
                 //							if (is_array($args)) {
                 //								foreach($args as $arg) {
                 //									$this->_writeDebug('Evaluating '.self::_localeFunc($functionName).'( '.self::_showValue($arg).' )');
                 //									$r = call_user_func_array($functionCall,$arg);
                 //									$this->_writeDebug('Evaluation Result for '.self::_localeFunc($functionName).'() function call is '.self::_showTypeDetails($r));
                 //									$result[$row][] = $r;
                 //								}
                 //								++$row;
                 //							} else {
                 //								$this->_writeDebug('Evaluating '.self::_localeFunc($functionName).'( '.self::_showValue($args).' )');
                 //								$r = call_user_func_array($functionCall,$args);
                 //								$this->_writeDebug('Evaluation Result for '.self::_localeFunc($functionName).'() function call is '.self::_showTypeDetails($r));
                 //								$result[] = $r;
                 //							}
                 //						}
                 //					} else {
                 //	Process the argument with the appropriate function call
                 if ($passCellReference) {
                     $args[] = $pCell;
                 }
                 if (strpos($functionCall, '::') !== false) {
                     $result = call_user_func_array(explode('::', $functionCall), $args);
                 } else {
                     foreach ($args as &$arg) {
                         $arg = PHPExcel_Calculation_Functions::flattenSingleValue($arg);
                     }
                     unset($arg);
                     $result = call_user_func_array($functionCall, $args);
                 }
                 //					}
                 if ($functionName != 'MKMATRIX') {
                     $this->_writeDebug('Evaluation Result for ' . self::_localeFunc($functionName) . '() function call is ' . self::_showTypeDetails($result));
                 }
                 $stack->push('Value', self::_wrapResult($result));
             }
         } else {
             // if the token is a number, boolean, string or an Excel error, push it onto the stack
             if (array_key_exists(strtoupper($token), self::$_ExcelConstants)) {
                 $excelConstant = strtoupper($token);
                 //					echo 'Token is a PHPExcel constant: '.$excelConstant.'<br />';
                 $stack->push('Constant Value', self::$_ExcelConstants[$excelConstant]);
                 $this->_writeDebug('Evaluating Constant ' . $excelConstant . ' as ' . self::_showTypeDetails(self::$_ExcelConstants[$excelConstant]));
             } elseif (is_numeric($token) || is_bool($token) || is_null($token) || $token == '' || $token[0] == '"' || $token[0] == '#') {
                 //					echo 'Token is a number, boolean, string, null or an Excel error<br />';
                 $stack->push('Value', $token);
                 // if the token is a named range, push the named range name onto the stack
             } elseif (preg_match('/^' . self::CALCULATION_REGEXP_NAMEDRANGE . '$/i', $token, $matches)) {
                 //					echo 'Token is a named range<br />';
                 $namedRange = $matches[6];
                 //					echo 'Named Range is '.$namedRange.'<br />';
                 $this->_writeDebug('Evaluating Named Range ' . $namedRange);
                 $cellValue = $this->extractNamedRange($namedRange, null !== $pCell ? $pCellParent : null, false);
                 $pCell->attach($pCellParent);
                 $this->_writeDebug('Evaluation Result for named range ' . $namedRange . ' is ' . self::_showTypeDetails($cellValue));
                 $stack->push('Named Range', $cellValue, $namedRange);
             } else {
                 return $this->_raiseFormulaError("undefined variable '{$token}'");
             }
         }
     }
     // when we're out of tokens, the stack should have a single element, the final result
     if ($stack->count() != 1) {
         return $this->_raiseFormulaError("internal error");
     }
     $output = $stack->pop();
     $output = $output['value'];
     //		if ((is_array($output)) && (self::$returnArrayAsType != self::RETURN_ARRAY_AS_ARRAY)) {
     //			return array_shift(PHPExcel_Calculation_Functions::flattenArray($output));
     //		}
     return $output;
 }
コード例 #2
0
 private function _processTokenStack($tokens, $cellID = null, PHPExcel_Cell $pCell = null)
 {
     if ($tokens == false) {
         return false;
     }
     $stack = new PHPExcel_Token_Stack();
     //	Loop through each token in turn
     foreach ($tokens as $token) {
         //			echo '<b>Token is '.$token.'</b><br />';
         // if the token is a binary operator, pop the top two values off the stack, do the operation, and push the result back on the stack
         if (in_array($token, $this->_binaryOperators, true)) {
             //				echo 'Token is a binary operator<br />';
             //	We must have two operands, error if we don't
             if (is_null($operand2 = $stack->pop())) {
                 return $this->_raiseFormulaError('Internal error - Operand value missing from stack');
             }
             if (is_null($operand1 = $stack->pop())) {
                 return $this->_raiseFormulaError('Internal error - Operand value missing from stack');
             }
             //	Log what we're doing
             $this->_writeDebug($cellID, 'Evaluating ' . self::_showValue($operand1) . ' ' . $token . ' ' . self::_showValue($operand2));
             //	Process the operation in the appropriate manner
             switch ($token) {
                 //	Comparison (Boolean) Operators
                 case '>':
                     //	Greater than
                 //	Greater than
                 case '<':
                     //	Less than
                 //	Less than
                 case '>=':
                     //	Greater than or Equal to
                 //	Greater than or Equal to
                 case '<=':
                     //	Less than or Equal to
                 //	Less than or Equal to
                 case '=':
                     //	Equality
                 //	Equality
                 case '<>':
                     //	Inequality
                     $this->_executeBinaryComparisonOperation($cellID, $operand1, $operand2, $token, $stack);
                     break;
                     //	Binary Operators
                 //	Binary Operators
                 case '+':
                     //	Addition
                     $this->_executeNumericBinaryOperation($cellID, $operand1, $operand2, $token, 'plusEquals', $stack);
                     break;
                 case '-':
                     //	Subtraction
                     $this->_executeNumericBinaryOperation($cellID, $operand1, $operand2, $token, 'minusEquals', $stack);
                     break;
                 case '*':
                     //	Multiplication
                     $this->_executeNumericBinaryOperation($cellID, $operand1, $operand2, $token, 'arrayTimesEquals', $stack);
                     break;
                 case '/':
                     //	Division
                     $this->_executeNumericBinaryOperation($cellID, $operand1, $operand2, $token, 'arrayRightDivide', $stack);
                     break;
                 case '^':
                     //	Exponential
                     $this->_executeNumericBinaryOperation($cellID, $operand1, $operand2, $token, 'power', $stack);
                     break;
                 case '&':
                     //	Concatenation
                     //	If either of the operands is a matrix, we need to treat them both as matrices
                     //		(converting the other operand to a matrix if need be); then perform the required
                     //		matrix operation
                     if (is_array($operand1) || is_array($operand2)) {
                         //	Ensure that both operands are arrays/matrices
                         self::_checkMatrixOperands($operand1, $operand2);
                         try {
                             //	Convert operand 1 from a PHP array to a matrix
                             $matrix = new Matrix($operand1);
                             //	Perform the required operation against the operand 1 matrix, passing in operand 2
                             $matrixResult = $matrix->concat($operand2);
                             $result = $matrixResult->getArray();
                         } catch (Exception $ex) {
                             $this->_writeDebug($cellID, 'JAMA Matrix Exception: ' . $ex->getMessage());
                             $result = '#VALUE!';
                         }
                     } else {
                         $result = '"' . str_replace('""', '"', self::_unwrapResult($operand1, '"') . self::_unwrapResult($operand2, '"')) . '"';
                     }
                     $this->_writeDebug($cellID, 'Evaluation Result is ' . self::_showTypeDetails($result));
                     $stack->push($result);
                     break;
             }
             // if the token is a unary operator, pop one value off the stack, do the operation, and push it back on
         } elseif ($token === "_" || $token === "%") {
             //				echo 'Token is a unary operator<br />';
             if (is_null($arg = $stack->pop())) {
                 return $this->_raiseFormulaError('Internal error - Operand value missing from stack');
             }
             if ($token === "_") {
                 //					echo 'Token is a negation operator<br />';
                 $this->_writeDebug($cellID, 'Evaluating Negation of ' . self::_showValue($arg));
                 $multiplier = -1;
             } else {
                 //					echo 'Token is a percentile operator<br />';
                 $this->_writeDebug($cellID, 'Evaluating Percentile of ' . self::_showValue($arg));
                 $multiplier = 0.01;
             }
             if (is_array($arg)) {
                 self::_checkMatrixOperands($arg, $multiplier);
                 try {
                     $matrix1 = new Matrix($arg);
                     $matrixResult = $matrix1->arrayTimesEquals($multiplier);
                     $result = $matrixResult->getArray();
                 } catch (Exception $ex) {
                     $this->_writeDebug($cellID, 'JAMA Matrix Exception: ' . $ex->getMessage());
                     $result = '#VALUE!';
                 }
             } else {
                 $result = $multiplier * $arg;
             }
             $this->_writeDebug($cellID, 'Evaluation Result is ' . self::_showTypeDetails($result));
             $stack->push($result);
         } elseif (preg_match('/^' . self::CALCULATION_REGEXP_CELLREF . '$/i', $token, $matches)) {
             //				echo 'Element '.$token.' is a Cell reference<br />';
             if (isset($matches[8])) {
                 //					echo 'Reference is a Range of cells<br />';
                 if (is_null($pCell)) {
                     //						We can't access the range, so return a REF error
                     $cellValue = PHPExcel_Calculation_Functions::REF();
                 } else {
                     $cellRef = $matches[6] . $matches[7] . ':' . $matches[9] . $matches[10];
                     if ($matches[2] > '') {
                         //							echo '$cellRef='.$cellRef.' in worksheet '.$matches[2].'<br />';
                         $this->_writeDebug($cellID, 'Evaluating Cell Range ' . $cellRef . ' in worksheet ' . $matches[2]);
                         $cellValue = $this->extractCellRange($cellRef, $pCell->getParent()->getParent()->getSheetByName($matches[2]), false);
                         $this->_writeDebug($cellID, 'Evaluation Result for cells ' . $cellRef . ' in worksheet ' . $matches[2] . ' is ' . self::_showTypeDetails($cellValue));
                     } else {
                         //							echo '$cellRef='.$cellRef.' in current worksheet<br />';
                         $this->_writeDebug($cellID, 'Evaluating Cell Range ' . $cellRef . ' in current worksheet');
                         $cellValue = $this->extractCellRange($cellRef, $pCell->getParent(), false);
                         $this->_writeDebug($cellID, 'Evaluation Result for cells ' . $cellRef . ' is ' . self::_showTypeDetails($cellValue));
                     }
                 }
             } else {
                 //					echo 'Reference is a single Cell<br />';
                 if (is_null($pCell)) {
                     //						We can't access the cell, so return a REF error
                     $cellValue = PHPExcel_Calculation_Functions::REF();
                 } else {
                     $cellRef = $matches[6] . $matches[7];
                     if ($matches[2] > '') {
                         //							echo '$cellRef='.$cellRef.' in worksheet '.$matches[2].'<br />';
                         $this->_writeDebug($cellID, 'Evaluating Cell ' . $cellRef . ' in worksheet ' . $matches[2]);
                         if ($pCell->getParent()->cellExists($cellRef)) {
                             $cellValue = $this->extractCellRange($cellRef, $pCell->getParent()->getParent()->getSheetByName($matches[2]), false);
                         } else {
                             $cellValue = null;
                         }
                         $this->_writeDebug($cellID, 'Evaluation Result for cell ' . $cellRef . ' in worksheet ' . $matches[2] . ' is ' . self::_showTypeDetails($cellValue));
                     } else {
                         //							echo '$cellRef='.$cellRef.' in current worksheet<br />';
                         $this->_writeDebug($cellID, 'Evaluating Cell ' . $cellRef . ' in current worksheet');
                         if ($pCell->getParent()->cellExists($cellRef)) {
                             $cellValue = $pCell->getParent()->getCell($cellRef)->getCalculatedValue(false);
                         } else {
                             $cellValue = '';
                         }
                         $this->_writeDebug($cellID, 'Evaluation Result for cell ' . $cellRef . ' is ' . self::_showTypeDetails($cellValue));
                     }
                 }
             }
             $stack->push($cellValue);
             // if the token is a function, pop arguments off the stack, hand them to the function, and push the result back on
         } elseif (preg_match('/^' . self::CALCULATION_REGEXP_FUNCTION . '$/i', $token, $matches)) {
             //				echo 'Token is a function<br />';
             $functionName = $matches[1];
             $argCount = $stack->pop();
             if ($functionName != 'MKMATRIX') {
                 $this->_writeDebug($cellID, 'Evaluating Function ' . $functionName . '() with ' . ($argCount == 0 ? 'no' : $argCount) . ' argument' . ($argCount == 1 ? '' : 's'));
             }
             if (array_key_exists($functionName, $this->_controlFunctions) || array_key_exists($functionName, $this->_PHPExcelFunctions)) {
                 // function
                 if (array_key_exists($functionName, $this->_controlFunctions)) {
                     $functionCall = $this->_controlFunctions[$functionName]['functionCall'];
                 } elseif (array_key_exists($functionName, $this->_PHPExcelFunctions)) {
                     $functionCall = $this->_PHPExcelFunctions[$functionName]['functionCall'];
                 }
                 // get the arguments for this function
                 //					echo 'Function '.$functionName.' expects '.$argCount.' arguments<br />';
                 $args = array();
                 for ($i = $argCount; $i > 0; --$i) {
                     $arg = $stack->pop();
                     //						if (is_null($arg)) return $this->_raiseFormulaError("internal error");
                     $args[$i] = $arg;
                 }
                 //	Reverse the order of the arguments
                 ksort($args);
                 //					echo 'Arguments are: ';
                 //					print_r($args);
                 //					echo '<br />';
                 if ($functionName != 'MKMATRIX') {
                     $argArrayVals = array();
                     foreach ($args as &$arg) {
                         $argArrayVals[] = self::_showValue($arg);
                         $arg = self::_unwrapResult($arg);
                     }
                     unset($arg);
                     $this->_writeDebug($cellID, 'Evaluating ' . $functionName . '( ' . implode(', ', $argArrayVals) . ' )');
                 }
                 //	Process each argument in turn, building the return value as an array
                 //					if (($argCount == 1) && (is_array($args[1])) && ($functionName != 'MKMATRIX')) {
                 //						$operand1 = $args[1];
                 //						$this->_writeDebug($cellID,'Argument is a matrix: '.self::_showValue($operand1));
                 //						$result = array();
                 //						$row = 0;
                 //						foreach($operand1 as $args) {
                 //							if (is_array($args)) {
                 //								foreach($args as $arg) {
                 //									$this->_writeDebug($cellID,'Evaluating '. $functionName.'( '.self::_showValue($arg).' )');
                 //									$r = call_user_func_array($functionCall,$arg);
                 //									$this->_writeDebug($cellID,'Evaluation Result is '.self::_showTypeDetails($r));
                 //									$result[$row][] = $r;
                 //								}
                 //								++$row;
                 //							} else {
                 //								$this->_writeDebug($cellID,'Evaluating '. $functionName.'( '.self::_showValue($args).' )');
                 //								$r = call_user_func_array($functionCall,$args);
                 //								$this->_writeDebug($cellID,'Evaluation Result is '.self::_showTypeDetails($r));
                 //								$result[] = $r;
                 //							}
                 //						}
                 //					} else {
                 //	Process the argument with the appropriate function call
                 $result = call_user_func_array($functionCall, $args);
                 //					}
                 if ($functionName != 'MKMATRIX') {
                     $this->_writeDebug($cellID, 'Evaluation Result is ' . self::_showTypeDetails($result));
                 }
                 $stack->push(self::_wrapResult($result));
             }
         } else {
             // if the token is a number, boolean, string or an Excel error, push it onto the stack
             if (array_key_exists(strtoupper($token), $this->_ExcelConstants)) {
                 $excelConstant = strtoupper($token);
                 //					echo 'Token is a PHPExcel constant: '.$excelConstant.'<br />';
                 $stack->push($this->_ExcelConstants[$excelConstant]);
                 $this->_writeDebug($cellID, 'Evaluating Constant ' . $excelConstant . ' as ' . self::_showTypeDetails($this->_ExcelConstants[$excelConstant]));
             } elseif (is_null($token) || $token == '' || is_bool($token) || is_numeric($token) || $token[0] == '"' || $token[0] == '#') {
                 //					echo 'Token is a number, boolean, string or an Excel error<br />';
                 $stack->push($token);
                 // if the token is a constant, push the constant value on the stack
             } elseif (preg_match('/^' . self::CALCULATION_REGEXP_NAMEDRANGE . '$/i', $token, $matches)) {
                 //					echo 'Token is a named range<br />';
                 $namedRange = $matches[6];
                 //					echo 'Named Range is '.$namedRange.'<br />';
                 $this->_writeDebug($cellID, 'Evaluating Named Range ' . $namedRange);
                 $cellValue = $this->extractNamedRange($namedRange, $pCell->getParent(), false);
                 $this->_writeDebug($cellID, 'Evaluation Result for named range ' . $namedRange . ' is ' . self::_showTypeDetails($cellValue));
                 $stack->push($cellValue);
             } else {
                 return $this->_raiseFormulaError("undefined variable '{$token}'");
             }
         }
     }
     // when we're out of tokens, the stack should have a single element, the final result
     if ($stack->count() != 1) {
         return $this->_raiseFormulaError("internal error");
     }
     $output = $stack->pop();
     if (is_array($output) && self::$returnArrayAsType != self::RETURN_ARRAY_AS_ARRAY) {
         return array_unshift(array_unshift($output));
     }
     return $output;
 }