ILIAS  release_5-4 Revision v5.4.26-12-gabc799a52e6
PhpOffice\PhpSpreadsheet\Calculation\Calculation Class Reference
+ Collaboration diagram for PhpOffice\PhpSpreadsheet\Calculation\Calculation:

Public Member Functions

static static static __construct (?Spreadsheet $spreadsheet=null)
 
 flushInstance ()
 Flush the calculation cache for any existing instance of this class but only if a Calculation instance exists. More...
 
 getDebugLog ()
 Get the Logger for this calculation engine instance. More...
 
 __clone ()
 __clone implementation. More...
 
 getCalculationCacheEnabled ()
 Is calculation caching enabled? More...
 
 setCalculationCacheEnabled ($pValue)
 Enable/disable calculation cache. More...
 
 enableCalculationCache ()
 Enable calculation cache. More...
 
 disableCalculationCache ()
 Disable calculation cache. More...
 
 clearCalculationCache ()
 Clear calculation cache. More...
 
 clearCalculationCacheForWorksheet ($worksheetName)
 Clear calculation cache for a specified worksheet. More...
 
 renameCalculationCacheForWorksheet ($fromWorksheetName, $toWorksheetName)
 Rename calculation cache for a specified worksheet. More...
 
 setBranchPruningEnabled ($enabled)
 Enable/disable calculation cache. More...
 
 enableBranchPruning ()
 
 disableBranchPruning ()
 
 clearBranchStore ()
 
 getLocale ()
 Get the currently defined locale code. More...
 
 setLocale (string $locale)
 Set the locale code. More...
 
 _translateFormulaToLocale ($formula)
 
 _translateFormulaToEnglish ($formula)
 
 calculate (?Cell $pCell=null)
 Calculate cell value (using formula from a cell ID) Retained for backward compatibility. More...
 
 calculateCellValue (?Cell $pCell=null, $resetLog=true)
 Calculate the value of a cell formula. More...
 
 parseFormula ($formula)
 Validate and parse a formula string. More...
 
 calculateFormula ($formula, $cellID=null, ?Cell $pCell=null)
 Calculate the value of a formula. More...
 
 getValueFromCache (string $cellReference, &$cellValue)
 
 saveValueToCache ($cellReference, $cellValue)
 
 _calculateFormulaValue ($formula, $cellID=null, ?Cell $pCell=null)
 Parse a cell formula and calculate its value. More...
 
 extractCellRange (&$pRange='A1', ?Worksheet $pSheet=null, $resetLog=true)
 Extract range values. More...
 
 extractNamedRange (&$pRange='A1', ?Worksheet $pSheet=null, $resetLog=true)
 Extract range values. More...
 
 isImplemented ($pFunction)
 Is a specific function implemented? More...
 
 getFunctions ()
 Get a list of all implemented functions as an array of function objects. More...
 
 getImplementedFunctionNames ()
 Get a list of implemented Excel function names. More...
 

Static Public Member Functions

static getInstance (?Spreadsheet $spreadsheet=null)
 Get an instance of this class. More...
 
static getTRUE ()
 Return the locale-specific translation of TRUE. More...
 
static getFALSE ()
 Return the locale-specific translation of FALSE. More...
 
static setArrayReturnType ($returnType)
 Set the Array Return Type (Array or Value of first element in the array). More...
 
static getArrayReturnType ()
 Return the Array Return Type (Array or Value of first element in the array). More...
 
static translateSeparator ($fromSeparator, $toSeparator, $formula, &$inBraces)
 
static localeFunc ($function)
 
static wrapResult ($value)
 Wrap string values in quotes. More...
 
static unwrapResult ($value)
 Remove quotes used as a wrapper to identify string values. More...
 
static getMatrixDimensions (array &$matrix)
 Read the dimensions of a matrix, and re-index it with straight numeric keys starting from row 0, column 0. More...
 

Data Fields

const CALCULATION_REGEXP_NUMBER = '[-+]?\d*\.?\d+(e[-+]?\d+)?'
 Constants. More...
 
const CALCULATION_REGEXP_STRING = '"(?:[^"]|"")*"'
 
const CALCULATION_REGEXP_OPENBRACE = '\('
 
const CALCULATION_REGEXP_FUNCTION = '@?(?:_xlfn\.)?([\p{L}][\p{L}\p{N}\.]*)[\s]*\('
 
const CALCULATION_REGEXP_CELLREF = '((([^\s,!&%^\/\*\+<>=-]*)|(\'[^\']*\')|(\"[^\"]*\"))!)?\$?\b([a-z]{1,3})\$?(\d{1,7})(?![\w.])'
 
const CALCULATION_REGEXP_CELLREF_RELATIVE = '((([^\s\(,!&%^\/\*\+<>=-]*)|(\'[^\']*\')|(\"[^\"]*\"))!)?(\$?\b[a-z]{1,3})(\$?\d{1,7})(?![\w.])'
 
const CALCULATION_REGEXP_COLUMN_RANGE = '(((([^\s\(,!&%^\/\*\+<>=-]*)|(\'[^\']*\')|(\"[^\"]*\"))!)?(\$?[a-z]{1,3})):(?![.*])'
 
const CALCULATION_REGEXP_ROW_RANGE = '(((([^\s\(,!&%^\/\*\+<>=-]*)|(\'[^\']*\')|(\"[^\"]*\"))!)?(\$?[1-9][0-9]{0,6})):(?![.*])'
 
const CALCULATION_REGEXP_COLUMNRANGE_RELATIVE = '(\$?[a-z]{1,3}):(\$?[a-z]{1,3})'
 
const CALCULATION_REGEXP_ROWRANGE_RELATIVE = '(\$?\d{1,7}):(\$?\d{1,7})'
 
const CALCULATION_REGEXP_DEFINEDNAME = '((([^\s,!&%^\/\*\+<>=-]*)|(\'[^\']*\')|(\"[^\"]*\"))!)?([_\p{L}][_\p{L}\p{N}\.]*)'
 
const CALCULATION_REGEXP_ERROR = '\#[A-Z][A-Z0_\/]*[!\?]?'
 
const RETURN_ARRAY_AS_ERROR = 'error'
 constants More...
 
const RETURN_ARRAY_AS_VALUE = 'value'
 
const RETURN_ARRAY_AS_ARRAY = 'array'
 
const FORMULA_OPEN_FUNCTION_BRACE = '{'
 
const FORMULA_CLOSE_FUNCTION_BRACE = '}'
 
const FORMULA_STRING_QUOTE = '"'
 
 $suppressFormulaErrors = false
 
 $formulaError
 
 $cyclicFormulaCount = 1
 

Static Public Attributes

static $localeBoolean
 

Protected Member Functions

 raiseFormulaError ($errorMessage)
 

Private Member Functions

 getLocaleFile (string $localeDir, string $locale, string $language, string $file)
 
 showValue ($value)
 Format details of an operand for display in the log (based on operand type). More...
 
 showTypeDetails ($value)
 Format type and details of an operand for display in the log (based on operand type). More...
 
 convertMatrixReferences ($formula)
 
static static static internalParseFormula ($formula, ?Cell $pCell=null)
 
 processTokenStack ($tokens, $cellID=null, ?Cell $pCell=null)
 
 validateBinaryOperand (&$operand, &$stack)
 
 executeArrayComparison ($cellID, $operand1, $operand2, $operation, Stack &$stack, bool $recursingArrays)
 
 executeBinaryComparisonOperation ($cellID, $operand1, $operand2, $operation, Stack &$stack, $recursingArrays=false)
 
 strcmpLowercaseFirst ($str1, $str2)
 Compare two strings in the same way as strcmp() except that lowercase come before uppercase letters. More...
 
 strcmpAllowNull ($str1, $str2)
 PHP8.1 deprecates passing null to strcmp. More...
 
 executeNumericBinaryOperation ($operand1, $operand2, $operation, $matrixFunction, &$stack)
 
 addCellReference (array $args, $passCellReference, $functionCall, ?Cell $pCell=null)
 Add cell reference if needed while making sure that it is the last argument. More...
 
 getUnusedBranchStoreKey ()
 
 getTokensAsString ($tokens)
 
 evaluateDefinedName (Cell $pCell, DefinedName $namedRange, Worksheet $pCellWorksheet, Stack $stack)
 

Static Private Member Functions

static loadLocales ()
 
static translateFormula (array $from, array $to, $formula, $fromSeparator, $toSeparator)
 
static checkMatrixOperands (&$operand1, &$operand2, $resize=1)
 Ensure that paired matrix operands are both matrices and of the same size. More...
 
static resizeMatricesShrink (&$matrix1, &$matrix2, $matrix1Rows, $matrix1Columns, $matrix2Rows, $matrix2Columns)
 Ensure that paired matrix operands are both matrices of the same size. More...
 
static resizeMatricesExtend (&$matrix1, &$matrix2, $matrix1Rows, $matrix1Columns, $matrix2Rows, $matrix2Columns)
 Ensure that paired matrix operands are both matrices of the same size. More...
 
static dataTestReference (&$operandData)
 

Private Attributes

 $spreadsheet
 
 $calculationCache = []
 
 $calculationCacheEnabled = true
 
 $branchStoreKeyCounter = 0
 
 $branchPruningEnabled = true
 
 $debugLog
 
 $cyclicReferenceStack
 
 $cellStack = []
 
 $cyclicFormulaCounter = 1
 
 $cyclicFormulaCell = ''
 
 $delta = 0.1e-12
 

Static Private Attributes

static $returnArrayAsType = self::RETURN_ARRAY_AS_VALUE
 
static $instance
 
static $operators
 
static $binaryOperators
 
static $referenceHelper
 
static $localeLanguage = 'en_us'
 
static $validLocaleLanguages
 
static $localeArgumentSeparator = ','
 
static $localeFunctions = []
 
static $excelConstants
 
static static $phpSpreadsheetFunctions
 
static static static $controlFunctions
 
static $functionReplaceFromExcel = null
 
static $functionReplaceToLocale = null
 
static $functionReplaceFromLocale = null
 
static $functionReplaceToExcel = null
 
static $operatorAssociativity
 
static static $comparisonOperators = ['>' => true
 
static static static $operatorPrecedence
 

Detailed Description

Definition at line 17 of file Calculation.php.

Constructor & Destructor Documentation

◆ __construct()

static static static PhpOffice\PhpSpreadsheet\Calculation\Calculation::__construct ( ?Spreadsheet  $spreadsheet = null)

Definition at line 2758 of file Calculation.php.

References PhpOffice\PhpSpreadsheet\Calculation\Calculation\$spreadsheet, and PhpOffice\PhpSpreadsheet\ReferenceHelper\getInstance().

2759  {
2760  $this->delta = 1 * 10 ** (0 - ini_get('precision'));
2761 
2762  $this->spreadsheet = $spreadsheet;
2763  $this->cyclicReferenceStack = new CyclicReferenceStack();
2764  $this->debugLog = new Logger($this->cyclicReferenceStack);
2765  self::$referenceHelper = ReferenceHelper::getInstance();
2766  }
static getInstance()
Get an instance of this class.
+ Here is the call graph for this function:

Member Function Documentation

◆ __clone()

PhpOffice\PhpSpreadsheet\Calculation\Calculation::__clone ( )
final

__clone implementation.

Cloning should not be allowed in a Singleton!

Definition at line 2824 of file Calculation.php.

2825  {
2826  throw new Exception('Cloning the calculation engine is not allowed!');
2827  }

◆ _calculateFormulaValue()

PhpOffice\PhpSpreadsheet\Calculation\Calculation::_calculateFormulaValue (   $formula,
  $cellID = null,
?Cell  $pCell = null 
)

Parse a cell formula and calculate its value.

Parameters
string$formulaThe formula to parse and calculate
string$cellIDThe ID (e.g. A3) of the cell that we are calculating
Cell$pCellCell to calculate
Returns
mixed

Definition at line 3482 of file Calculation.php.

References PhpOffice\PhpSpreadsheet\Calculation\Calculation\$cyclicFormulaCounter, PhpOffice\PhpSpreadsheet\Calculation\Calculation\getValueFromCache(), PhpOffice\PhpSpreadsheet\Calculation\Calculation\internalParseFormula(), PhpOffice\PhpSpreadsheet\Calculation\Calculation\processTokenStack(), PhpOffice\PhpSpreadsheet\Calculation\Calculation\raiseFormulaError(), and PhpOffice\PhpSpreadsheet\Calculation\Calculation\saveValueToCache().

Referenced by PhpOffice\PhpSpreadsheet\Calculation\Calculation\calculateCellValue(), and PhpOffice\PhpSpreadsheet\Calculation\Calculation\calculateFormula().

3483  {
3484  $cellValue = null;
3485 
3486  // Quote-Prefixed cell values cannot be formulae, but are treated as strings
3487  if ($pCell !== null && $pCell->getStyle()->getQuotePrefix() === true) {
3488  return self::wrapResult((string) $formula);
3489  }
3490 
3491  if (preg_match('/^=\s*cmd\s*\|/miu', $formula) !== 0) {
3492  return self::wrapResult($formula);
3493  }
3494 
3495  // Basic validation that this is indeed a formula
3496  // We simply return the cell value if not
3497  $formula = trim($formula);
3498  if ($formula[0] != '=') {
3499  return self::wrapResult($formula);
3500  }
3501  $formula = ltrim(substr($formula, 1));
3502  if (!isset($formula[0])) {
3503  return self::wrapResult($formula);
3504  }
3505 
3506  $pCellParent = ($pCell !== null) ? $pCell->getWorksheet() : null;
3507  $wsTitle = ($pCellParent !== null) ? $pCellParent->getTitle() : "\x00Wrk";
3508  $wsCellReference = $wsTitle . '!' . $cellID;
3509 
3510  if (($cellID !== null) && ($this->getValueFromCache($wsCellReference, $cellValue))) {
3511  return $cellValue;
3512  }
3513  $this->debugLog->writeDebugLog("Evaluating formula for cell {$wsCellReference}");
3514 
3515  if (($wsTitle[0] !== "\x00") && ($this->cyclicReferenceStack->onStack($wsCellReference))) {
3516  if ($this->cyclicFormulaCount <= 0) {
3517  $this->cyclicFormulaCell = '';
3518 
3519  return $this->raiseFormulaError('Cyclic Reference in Formula');
3520  } elseif ($this->cyclicFormulaCell === $wsCellReference) {
3522  if ($this->cyclicFormulaCounter >= $this->cyclicFormulaCount) {
3523  $this->cyclicFormulaCell = '';
3524 
3525  return $cellValue;
3526  }
3527  } elseif ($this->cyclicFormulaCell == '') {
3528  if ($this->cyclicFormulaCounter >= $this->cyclicFormulaCount) {
3529  return $cellValue;
3530  }
3531  $this->cyclicFormulaCell = $wsCellReference;
3532  }
3533  }
3534 
3535  $this->debugLog->writeDebugLog("Formula for cell {$wsCellReference} is {$formula}");
3536  // Parse the formula onto the token stack and calculate the value
3537  $this->cyclicReferenceStack->push($wsCellReference);
3538 
3539  $cellValue = $this->processTokenStack($this->internalParseFormula($formula, $pCell), $cellID, $pCell);
3540  $this->cyclicReferenceStack->pop();
3541 
3542  // Save to calculation cache
3543  if ($cellID !== null) {
3544  $this->saveValueToCache($wsCellReference, $cellValue);
3545  }
3546 
3547  // Return the calculated value
3548  return $cellValue;
3549  }
processTokenStack($tokens, $cellID=null, ?Cell $pCell=null)
static static static internalParseFormula($formula, ?Cell $pCell=null)
getValueFromCache(string $cellReference, &$cellValue)
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ _translateFormulaToEnglish()

PhpOffice\PhpSpreadsheet\Calculation\Calculation::_translateFormulaToEnglish (   $formula)

Definition at line 3195 of file Calculation.php.

3196  {
3197  if (self::$functionReplaceFromLocale === null) {
3198  self::$functionReplaceFromLocale = [];
3199  foreach (self::$localeFunctions as $localeFunctionName) {
3200  self::$functionReplaceFromLocale[] = '/(@?[^\w\.])' . preg_quote($localeFunctionName, '/') . '([\s]*\()/Ui';
3201  }
3202  foreach (self::$localeBoolean as $excelBoolean) {
3203  self::$functionReplaceFromLocale[] = '/(@?[^\w\.])' . preg_quote($excelBoolean, '/') . '([^\w\.])/Ui';
3204  }
3205  }
3206 
3207  if (self::$functionReplaceToExcel === null) {
3208  self::$functionReplaceToExcel = [];
3209  foreach (array_keys(self::$localeFunctions) as $excelFunctionName) {
3210  self::$functionReplaceToExcel[] = '$1' . trim($excelFunctionName) . '$2';
3211  }
3212  foreach (array_keys(self::$localeBoolean) as $excelBoolean) {
3213  self::$functionReplaceToExcel[] = '$1' . trim($excelBoolean) . '$2';
3214  }
3215  }
3216 
3217  return self::translateFormula(self::$functionReplaceFromLocale, self::$functionReplaceToExcel, $formula, self::$localeArgumentSeparator, ',');
3218  }

◆ _translateFormulaToLocale()

PhpOffice\PhpSpreadsheet\Calculation\Calculation::_translateFormulaToLocale (   $formula)

Definition at line 3166 of file Calculation.php.

References PhpOffice\PhpSpreadsheet\Calculation\Calculation\$localeBoolean.

3167  {
3168  if (self::$functionReplaceFromExcel === null) {
3169  self::$functionReplaceFromExcel = [];
3170  foreach (array_keys(self::$localeFunctions) as $excelFunctionName) {
3171  self::$functionReplaceFromExcel[] = '/(@?[^\w\.])' . preg_quote($excelFunctionName, '/') . '([\s]*\()/Ui';
3172  }
3173  foreach (array_keys(self::$localeBoolean) as $excelBoolean) {
3174  self::$functionReplaceFromExcel[] = '/(@?[^\w\.])' . preg_quote($excelBoolean, '/') . '([^\w\.])/Ui';
3175  }
3176  }
3177 
3178  if (self::$functionReplaceToLocale === null) {
3179  self::$functionReplaceToLocale = [];
3180  foreach (self::$localeFunctions as $localeFunctionName) {
3181  self::$functionReplaceToLocale[] = '$1' . trim($localeFunctionName) . '$2';
3182  }
3183  foreach (self::$localeBoolean as $localeBoolean) {
3184  self::$functionReplaceToLocale[] = '$1' . trim($localeBoolean) . '$2';
3185  }
3186  }
3187 
3188  return self::translateFormula(self::$functionReplaceFromExcel, self::$functionReplaceToLocale, $formula, ',', self::$localeArgumentSeparator);
3189  }

◆ addCellReference()

PhpOffice\PhpSpreadsheet\Calculation\Calculation::addCellReference ( array  $args,
  $passCellReference,
  $functionCall,
?Cell  $pCell = null 
)
private

Add cell reference if needed while making sure that it is the last argument.

Parameters
bool$passCellReference
array | string$functionCall
Returns
array

Definition at line 5397 of file Calculation.php.

Referenced by PhpOffice\PhpSpreadsheet\Calculation\Calculation\processTokenStack().

5398  {
5399  if ($passCellReference) {
5400  if (is_array($functionCall)) {
5401  $className = $functionCall[0];
5402  $methodName = $functionCall[1];
5403 
5404  $reflectionMethod = new ReflectionMethod($className, $methodName);
5405  $argumentCount = count($reflectionMethod->getParameters());
5406  while (count($args) < $argumentCount - 1) {
5407  $args[] = null;
5408  }
5409  }
5410 
5411  $args[] = $pCell;
5412  }
5413 
5414  return $args;
5415  }
+ Here is the caller graph for this function:

◆ calculate()

PhpOffice\PhpSpreadsheet\Calculation\Calculation::calculate ( ?Cell  $pCell = null)

Calculate cell value (using formula from a cell ID) Retained for backward compatibility.

Parameters
Cell$pCellCell to calculate
Returns
mixed

Definition at line 3291 of file Calculation.php.

References PhpOffice\PhpSpreadsheet\Calculation\Calculation\calculateCellValue().

3292  {
3293  try {
3294  return $this->calculateCellValue($pCell);
3295  } catch (\Exception $e) {
3296  throw new Exception($e->getMessage());
3297  }
3298  }
calculateCellValue(?Cell $pCell=null, $resetLog=true)
Calculate the value of a cell formula.
+ Here is the call graph for this function:

◆ calculateCellValue()

PhpOffice\PhpSpreadsheet\Calculation\Calculation::calculateCellValue ( ?Cell  $pCell = null,
  $resetLog = true 
)

Calculate the value of a cell formula.

Parameters
Cell$pCellCell to calculate
bool$resetLogFlag indicating whether the debug log should be reset or not
Returns
mixed

Definition at line 3308 of file Calculation.php.

References $c, $r, $result, PhpOffice\PhpSpreadsheet\Calculation\Calculation\$returnArrayAsType, PhpOffice\PhpSpreadsheet\Calculation\Calculation\_calculateFormulaValue(), PhpOffice\PhpSpreadsheet\Calculation\Functions\flattenArray(), PhpOffice\PhpSpreadsheet\Calculation\Functions\NAN(), and PhpOffice\PhpSpreadsheet\Calculation\Functions\VALUE().

Referenced by PhpOffice\PhpSpreadsheet\Calculation\Calculation\calculate().

3309  {
3310  if ($pCell === null) {
3311  return null;
3312  }
3313 
3314  $returnArrayAsType = self::$returnArrayAsType;
3315  if ($resetLog) {
3316  // Initialise the logging settings if requested
3317  $this->formulaError = null;
3318  $this->debugLog->clearLog();
3319  $this->cyclicReferenceStack->clear();
3320  $this->cyclicFormulaCounter = 1;
3321 
3322  self::$returnArrayAsType = self::RETURN_ARRAY_AS_ARRAY;
3323  }
3324 
3325  // Execute the calculation for the cell formula
3326  $this->cellStack[] = [
3327  'sheet' => $pCell->getWorksheet()->getTitle(),
3328  'cell' => $pCell->getCoordinate(),
3329  ];
3330 
3331  try {
3332  $result = self::unwrapResult($this->_calculateFormulaValue($pCell->getValue(), $pCell->getCoordinate(), $pCell));
3333  $cellAddress = array_pop($this->cellStack);
3334  $this->spreadsheet->getSheetByName($cellAddress['sheet'])->getCell($cellAddress['cell']);
3335  } catch (\Exception $e) {
3336  $cellAddress = array_pop($this->cellStack);
3337  $this->spreadsheet->getSheetByName($cellAddress['sheet'])->getCell($cellAddress['cell']);
3338 
3339  throw new Exception($e->getMessage());
3340  }
3341 
3342  if ((is_array($result)) && (self::$returnArrayAsType != self::RETURN_ARRAY_AS_ARRAY)) {
3343  self::$returnArrayAsType = $returnArrayAsType;
3344  $testResult = Functions::flattenArray($result);
3345  if (self::$returnArrayAsType == self::RETURN_ARRAY_AS_ERROR) {
3346  return Functions::VALUE();
3347  }
3348  // If there's only a single cell in the array, then we allow it
3349  if (count($testResult) != 1) {
3350  // If keys are numeric, then it's a matrix result rather than a cell range result, so we permit it
3351  $r = array_keys($result);
3352  $r = array_shift($r);
3353  if (!is_numeric($r)) {
3354  return Functions::VALUE();
3355  }
3356  if (is_array($result[$r])) {
3357  $c = array_keys($result[$r]);
3358  $c = array_shift($c);
3359  if (!is_numeric($c)) {
3360  return Functions::VALUE();
3361  }
3362  }
3363  }
3364  $result = array_shift($testResult);
3365  }
3366  self::$returnArrayAsType = $returnArrayAsType;
3367 
3368  if ($result === null && $pCell->getWorksheet()->getSheetView()->getShowZeros()) {
3369  return 0;
3370  } elseif ((is_float($result)) && ((is_nan($result)) || (is_infinite($result)))) {
3371  return Functions::NAN();
3372  }
3373 
3374  return $result;
3375  }
$result
static flattenArray($array)
Convert a multi-dimensional array to a simple 1-dimensional array.
Definition: Functions.php:583
$r
Definition: example_031.php:79
_calculateFormulaValue($formula, $cellID=null, ?Cell $pCell=null)
Parse a cell formula and calculate its value.
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ calculateFormula()

PhpOffice\PhpSpreadsheet\Calculation\Calculation::calculateFormula (   $formula,
  $cellID = null,
?Cell  $pCell = null 
)

Calculate the value of a formula.

Parameters
string$formulaFormula to parse
string$cellIDAddress of the cell to calculate
Cell$pCellCell to calculate
Returns
mixed

Definition at line 3410 of file Calculation.php.

References $result, PhpOffice\PhpSpreadsheet\Calculation\Calculation\_calculateFormulaValue(), and PhpOffice\PhpSpreadsheet\Calculation\Calculation\getCalculationCacheEnabled().

3411  {
3412  // Initialise the logging settings
3413  $this->formulaError = null;
3414  $this->debugLog->clearLog();
3415  $this->cyclicReferenceStack->clear();
3416 
3417  $resetCache = $this->getCalculationCacheEnabled();
3418  if ($this->spreadsheet !== null && $cellID === null && $pCell === null) {
3419  $cellID = 'A1';
3420  $pCell = $this->spreadsheet->getActiveSheet()->getCell($cellID);
3421  } else {
3422  // Disable calculation cacheing because it only applies to cell calculations, not straight formulae
3423  // But don't actually flush any cache
3424  $this->calculationCacheEnabled = false;
3425  }
3426 
3427  // Execute the calculation
3428  try {
3429  $result = self::unwrapResult($this->_calculateFormulaValue($formula, $cellID, $pCell));
3430  } catch (\Exception $e) {
3431  throw new Exception($e->getMessage());
3432  }
3433 
3434  if ($this->spreadsheet === null) {
3435  // Reset calculation cacheing to its previous state
3436  $this->calculationCacheEnabled = $resetCache;
3437  }
3438 
3439  return $result;
3440  }
$result
getCalculationCacheEnabled()
Is calculation caching enabled?
_calculateFormulaValue($formula, $cellID=null, ?Cell $pCell=null)
Parse a cell formula and calculate its value.
+ Here is the call graph for this function:

◆ checkMatrixOperands()

static PhpOffice\PhpSpreadsheet\Calculation\Calculation::checkMatrixOperands ( $operand1,
$operand2,
  $resize = 1 
)
staticprivate

Ensure that paired matrix operands are both matrices and of the same size.

Parameters
mixed$operand1First matrix operand
mixed$operand2Second matrix operand
int$resizeFlag indicating whether the matrices should be resized to match and (if so), whether the smaller dimension should grow or the larger should shrink. 0 = no resize 1 = shrink to fit 2 = extend to fit
Returns
array

Definition at line 3565 of file Calculation.php.

3566  {
3567  // Examine each of the two operands, and turn them into an array if they aren't one already
3568  // Note that this function should only be called if one or both of the operand is already an array
3569  if (!is_array($operand1)) {
3570  [$matrixRows, $matrixColumns] = self::getMatrixDimensions($operand2);
3571  $operand1 = array_fill(0, $matrixRows, array_fill(0, $matrixColumns, $operand1));
3572  $resize = 0;
3573  } elseif (!is_array($operand2)) {
3574  [$matrixRows, $matrixColumns] = self::getMatrixDimensions($operand1);
3575  $operand2 = array_fill(0, $matrixRows, array_fill(0, $matrixColumns, $operand2));
3576  $resize = 0;
3577  }
3578 
3579  [$matrix1Rows, $matrix1Columns] = self::getMatrixDimensions($operand1);
3580  [$matrix2Rows, $matrix2Columns] = self::getMatrixDimensions($operand2);
3581  if (($matrix1Rows == $matrix2Columns) && ($matrix2Rows == $matrix1Columns)) {
3582  $resize = 1;
3583  }
3584 
3585  if ($resize == 2) {
3586  // Given two matrices of (potentially) unequal size, convert the smaller in each dimension to match the larger
3587  self::resizeMatricesExtend($operand1, $operand2, $matrix1Rows, $matrix1Columns, $matrix2Rows, $matrix2Columns);
3588  } elseif ($resize == 1) {
3589  // Given two matrices of (potentially) unequal size, convert the larger in each dimension to match the smaller
3590  self::resizeMatricesShrink($operand1, $operand2, $matrix1Rows, $matrix1Columns, $matrix2Rows, $matrix2Columns);
3591  }
3592 
3593  return [$matrix1Rows, $matrix1Columns, $matrix2Rows, $matrix2Columns];
3594  }

◆ clearBranchStore()

PhpOffice\PhpSpreadsheet\Calculation\Calculation::clearBranchStore ( )

Definition at line 2972 of file Calculation.php.

Referenced by PhpOffice\PhpSpreadsheet\Calculation\Calculation\flushInstance().

2972  : void
2973  {
2974  $this->branchStoreKeyCounter = 0;
2975  }
+ Here is the caller graph for this function:

◆ clearCalculationCache()

PhpOffice\PhpSpreadsheet\Calculation\Calculation::clearCalculationCache ( )

Clear calculation cache.

Definition at line 2921 of file Calculation.php.

Referenced by PhpOffice\PhpSpreadsheet\Calculation\Calculation\flushInstance(), and PhpOffice\PhpSpreadsheet\Calculation\Calculation\setCalculationCacheEnabled().

2921  : void
2922  {
2923  $this->calculationCache = [];
2924  }
+ Here is the caller graph for this function:

◆ clearCalculationCacheForWorksheet()

PhpOffice\PhpSpreadsheet\Calculation\Calculation::clearCalculationCacheForWorksheet (   $worksheetName)

Clear calculation cache for a specified worksheet.

Parameters
string$worksheetName

Definition at line 2931 of file Calculation.php.

2931  : void
2932  {
2933  if (isset($this->calculationCache[$worksheetName])) {
2934  unset($this->calculationCache[$worksheetName]);
2935  }
2936  }

◆ convertMatrixReferences()

PhpOffice\PhpSpreadsheet\Calculation\Calculation::convertMatrixReferences (   $formula)
private
Parameters
string$formula
Returns
false|string False indicates an error

Definition at line 3794 of file Calculation.php.

References $i, and PhpOffice\PhpSpreadsheet\Calculation\Calculation\raiseFormulaError().

Referenced by PhpOffice\PhpSpreadsheet\Calculation\Calculation\internalParseFormula().

3795  {
3796  static $matrixReplaceFrom = [self::FORMULA_OPEN_FUNCTION_BRACE, ';', self::FORMULA_CLOSE_FUNCTION_BRACE];
3797  static $matrixReplaceTo = ['MKMATRIX(MKMATRIX(', '),MKMATRIX(', '))'];
3798 
3799  // Convert any Excel matrix references to the MKMATRIX() function
3800  if (strpos($formula, self::FORMULA_OPEN_FUNCTION_BRACE) !== false) {
3801  // If there is the possibility of braces within a quoted string, then we don't treat those as matrix indicators
3802  if (strpos($formula, self::FORMULA_STRING_QUOTE) !== false) {
3803  // So instead we skip replacing in any quoted strings by only replacing in every other array element after we've exploded
3804  // the formula
3805  $temp = explode(self::FORMULA_STRING_QUOTE, $formula);
3806  // Open and Closed counts used for trapping mismatched braces in the formula
3807  $openCount = $closeCount = 0;
3808  $i = false;
3809  foreach ($temp as &$value) {
3810  // Only count/replace in alternating array entries
3811  if ($i = !$i) {
3812  $openCount += substr_count($value, self::FORMULA_OPEN_FUNCTION_BRACE);
3813  $closeCount += substr_count($value, self::FORMULA_CLOSE_FUNCTION_BRACE);
3814  $value = str_replace($matrixReplaceFrom, $matrixReplaceTo, $value);
3815  }
3816  }
3817  unset($value);
3818  // Then rebuild the formula string
3819  $formula = implode(self::FORMULA_STRING_QUOTE, $temp);
3820  } else {
3821  // If there's no quoted strings, then we do a simple count/replace
3822  $openCount = substr_count($formula, self::FORMULA_OPEN_FUNCTION_BRACE);
3823  $closeCount = substr_count($formula, self::FORMULA_CLOSE_FUNCTION_BRACE);
3824  $formula = str_replace($matrixReplaceFrom, $matrixReplaceTo, $formula);
3825  }
3826  // Trap for mismatched braces and trigger an appropriate error
3827  if ($openCount < $closeCount) {
3828  if ($openCount > 0) {
3829  return $this->raiseFormulaError("Formula Error: Mismatched matrix braces '}'");
3830  }
3831 
3832  return $this->raiseFormulaError("Formula Error: Unexpected '}' encountered");
3833  } elseif ($openCount > $closeCount) {
3834  if ($closeCount > 0) {
3835  return $this->raiseFormulaError("Formula Error: Mismatched matrix braces '{'");
3836  }
3837 
3838  return $this->raiseFormulaError("Formula Error: Unexpected '{' encountered");
3839  }
3840  }
3841 
3842  return $formula;
3843  }
$i
Definition: disco.tpl.php:19
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ dataTestReference()

static PhpOffice\PhpSpreadsheet\Calculation\Calculation::dataTestReference ( $operandData)
staticprivate

Definition at line 4361 of file Calculation.php.

4362  {
4363  $operand = $operandData['value'];
4364  if (($operandData['reference'] === null) && (is_array($operand))) {
4365  $rKeys = array_keys($operand);
4366  $rowKey = array_shift($rKeys);
4367  $cKeys = array_keys(array_keys($operand[$rowKey]));
4368  $colKey = array_shift($cKeys);
4369  if (ctype_upper($colKey)) {
4370  $operandData['reference'] = $colKey . $rowKey;
4371  }
4372  }
4373 
4374  return $operand;
4375  }

◆ disableBranchPruning()

PhpOffice\PhpSpreadsheet\Calculation\Calculation::disableBranchPruning ( )

Definition at line 2967 of file Calculation.php.

References PhpOffice\PhpSpreadsheet\Calculation\Calculation\setBranchPruningEnabled().

2967  : void
2968  {
2969  $this->setBranchPruningEnabled(false);
2970  }
setBranchPruningEnabled($enabled)
Enable/disable calculation cache.
+ Here is the call graph for this function:

◆ disableCalculationCache()

PhpOffice\PhpSpreadsheet\Calculation\Calculation::disableCalculationCache ( )

Disable calculation cache.

Definition at line 2913 of file Calculation.php.

References PhpOffice\PhpSpreadsheet\Calculation\Calculation\setCalculationCacheEnabled().

2913  : void
2914  {
2915  $this->setCalculationCacheEnabled(false);
2916  }
setCalculationCacheEnabled($pValue)
Enable/disable calculation cache.
+ Here is the call graph for this function:

◆ enableBranchPruning()

PhpOffice\PhpSpreadsheet\Calculation\Calculation::enableBranchPruning ( )

Definition at line 2962 of file Calculation.php.

References PhpOffice\PhpSpreadsheet\Calculation\Calculation\setBranchPruningEnabled().

2962  : void
2963  {
2964  $this->setBranchPruningEnabled(true);
2965  }
setBranchPruningEnabled($enabled)
Enable/disable calculation cache.
+ Here is the call graph for this function:

◆ enableCalculationCache()

PhpOffice\PhpSpreadsheet\Calculation\Calculation::enableCalculationCache ( )

Enable calculation cache.

Definition at line 2905 of file Calculation.php.

References PhpOffice\PhpSpreadsheet\Calculation\Calculation\setCalculationCacheEnabled().

2905  : void
2906  {
2907  $this->setCalculationCacheEnabled(true);
2908  }
setCalculationCacheEnabled($pValue)
Enable/disable calculation cache.
+ Here is the call graph for this function:

◆ evaluateDefinedName()

PhpOffice\PhpSpreadsheet\Calculation\Calculation::evaluateDefinedName ( Cell  $pCell,
DefinedName  $namedRange,
Worksheet  $pCellWorksheet,
Stack  $stack 
)
private
Returns
mixed|string

Definition at line 5442 of file Calculation.php.

References $result, PhpOffice\PhpSpreadsheet\Calculation\Calculation\$spreadsheet, PhpOffice\PhpSpreadsheet\Cell\Coordinate\columnIndexFromString(), PhpOffice\PhpSpreadsheet\Cell\Cell\getColumn(), PhpOffice\PhpSpreadsheet\Cell\Cell\getCoordinate(), PhpOffice\PhpSpreadsheet\Calculation\Calculation\getDebugLog(), PhpOffice\PhpSpreadsheet\DefinedName\getName(), PhpOffice\PhpSpreadsheet\Cell\Cell\getRow(), PhpOffice\PhpSpreadsheet\DefinedName\getScope(), PhpOffice\PhpSpreadsheet\DefinedName\getValue(), PhpOffice\PhpSpreadsheet\DefinedName\getWorksheet(), PhpOffice\PhpSpreadsheet\DefinedName\isFormula(), PhpOffice\PhpSpreadsheet\Calculation\Token\Stack\push(), and PhpOffice\PhpSpreadsheet\Calculation\Functions\REF().

Referenced by PhpOffice\PhpSpreadsheet\Calculation\Calculation\processTokenStack().

5443  {
5444  $definedNameScope = $namedRange->getScope();
5445  if ($definedNameScope !== null && $definedNameScope !== $pCellWorksheet) {
5446  // The defined name isn't in our current scope, so #REF
5447  $result = Functions::REF();
5448  $stack->push('Error', $result, $namedRange->getName());
5449 
5450  return $result;
5451  }
5452 
5453  $definedNameValue = $namedRange->getValue();
5454  $definedNameType = $namedRange->isFormula() ? 'Formula' : 'Range';
5455  $definedNameWorksheet = $namedRange->getWorksheet();
5456 
5457  if ($definedNameValue[0] !== '=') {
5458  $definedNameValue = '=' . $definedNameValue;
5459  }
5460 
5461  $this->debugLog->writeDebugLog("Defined Name is a {$definedNameType} with a value of {$definedNameValue}");
5462 
5463  $recursiveCalculationCell = ($definedNameWorksheet !== null && $definedNameWorksheet !== $pCellWorksheet)
5464  ? $definedNameWorksheet->getCell('A1')
5465  : $pCell;
5466  $recursiveCalculationCellAddress = $recursiveCalculationCell->getCoordinate();
5467 
5468  // Adjust relative references in ranges and formulae so that we execute the calculation for the correct rows and columns
5469  $definedNameValue = self::$referenceHelper->updateFormulaReferencesAnyWorksheet(
5470  $definedNameValue,
5471  Coordinate::columnIndexFromString($pCell->getColumn()) - 1,
5472  $pCell->getRow() - 1
5473  );
5474 
5475  $this->debugLog->writeDebugLog("Value adjusted for relative references is {$definedNameValue}");
5476 
5477  $recursiveCalculator = new self($this->spreadsheet);
5478  $recursiveCalculator->getDebugLog()->setWriteDebugLog($this->getDebugLog()->getWriteDebugLog());
5479  $recursiveCalculator->getDebugLog()->setEchoDebugLog($this->getDebugLog()->getEchoDebugLog());
5480  $result = $recursiveCalculator->_calculateFormulaValue($definedNameValue, $recursiveCalculationCellAddress, $recursiveCalculationCell);
5481 
5482  if ($this->getDebugLog()->getWriteDebugLog()) {
5483  $this->debugLog->mergeDebugLog(array_slice($recursiveCalculator->getDebugLog()->getLog(), 3));
5484  $this->debugLog->writeDebugLog("Evaluation Result for Named {$definedNameType} {$namedRange->getName()} is {$this->showTypeDetails($result)}");
5485  }
5486 
5487  $stack->push('Defined Name', $result, $namedRange->getName());
5488 
5489  return $result;
5490  }
$result
getDebugLog()
Get the Logger for this calculation engine instance.
static columnIndexFromString($pString)
Column index from string.
Definition: Coordinate.php:265
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ executeArrayComparison()

PhpOffice\PhpSpreadsheet\Calculation\Calculation::executeArrayComparison (   $cellID,
  $operand1,
  $operand2,
  $operation,
Stack $stack,
bool  $recursingArrays 
)
private
Parameters
null | string$cellID
mixed$operand1
mixed$operand2
string$operation
Returns
array

Definition at line 4948 of file Calculation.php.

References $r, $result, $x, PhpOffice\PhpSpreadsheet\Calculation\Calculation\executeBinaryComparisonOperation(), PhpOffice\PhpSpreadsheet\Calculation\Token\Stack\pop(), PhpOffice\PhpSpreadsheet\Calculation\Token\Stack\push(), PhpOffice\PhpSpreadsheet\Calculation\Calculation\showTypeDetails(), and PhpOffice\PhpSpreadsheet\Calculation\Calculation\showValue().

Referenced by PhpOffice\PhpSpreadsheet\Calculation\Calculation\executeBinaryComparisonOperation().

4949  {
4950  $result = [];
4951  if (!is_array($operand2)) {
4952  // Operand 1 is an array, Operand 2 is a scalar
4953  foreach ($operand1 as $x => $operandData) {
4954  $this->debugLog->writeDebugLog('Evaluating Comparison ', $this->showValue($operandData), ' ', $operation, ' ', $this->showValue($operand2));
4955  $this->executeBinaryComparisonOperation($cellID, $operandData, $operand2, $operation, $stack);
4956  $r = $stack->pop();
4957  $result[$x] = $r['value'];
4958  }
4959  } elseif (!is_array($operand1)) {
4960  // Operand 1 is a scalar, Operand 2 is an array
4961  foreach ($operand2 as $x => $operandData) {
4962  $this->debugLog->writeDebugLog('Evaluating Comparison ', $this->showValue($operand1), ' ', $operation, ' ', $this->showValue($operandData));
4963  $this->executeBinaryComparisonOperation($cellID, $operand1, $operandData, $operation, $stack);
4964  $r = $stack->pop();
4965  $result[$x] = $r['value'];
4966  }
4967  } else {
4968  // Operand 1 and Operand 2 are both arrays
4969  if (!$recursingArrays) {
4970  self::checkMatrixOperands($operand1, $operand2, 2);
4971  }
4972  foreach ($operand1 as $x => $operandData) {
4973  $this->debugLog->writeDebugLog('Evaluating Comparison ', $this->showValue($operandData), ' ', $operation, ' ', $this->showValue($operand2[$x]));
4974  $this->executeBinaryComparisonOperation($cellID, $operandData, $operand2[$x], $operation, $stack, true);
4975  $r = $stack->pop();
4976  $result[$x] = $r['value'];
4977  }
4978  }
4979  // Log the result details
4980  $this->debugLog->writeDebugLog('Comparison Evaluation Result is ', $this->showTypeDetails($result));
4981  // And push the result onto the stack
4982  $stack->push('Array', $result);
4983 
4984  return $result;
4985  }
$result
showValue($value)
Format details of an operand for display in the log (based on operand type).
executeBinaryComparisonOperation($cellID, $operand1, $operand2, $operation, Stack &$stack, $recursingArrays=false)
$r
Definition: example_031.php:79
showTypeDetails($value)
Format type and details of an operand for display in the log (based on operand type).
$x
Definition: complexTest.php:9
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ executeBinaryComparisonOperation()

PhpOffice\PhpSpreadsheet\Calculation\Calculation::executeBinaryComparisonOperation (   $cellID,
  $operand1,
  $operand2,
  $operation,
Stack $stack,
  $recursingArrays = false 
)
private
Parameters
null | string$cellID
mixed$operand1
mixed$operand2
string$operation
bool$recursingArrays
Returns
mixed

Definition at line 4996 of file Calculation.php.

References PhpOffice\PhpSpreadsheet\Calculation\Calculation\$delta, $result, PhpOffice\PhpSpreadsheet\Calculation\Functions\COMPATIBILITY_OPENOFFICE, PhpOffice\PhpSpreadsheet\Calculation\Calculation\executeArrayComparison(), PhpOffice\PhpSpreadsheet\Calculation\Functions\getCompatibilityMode(), PhpOffice\PhpSpreadsheet\Calculation\Token\Stack\push(), PhpOffice\PhpSpreadsheet\Calculation\Calculation\showTypeDetails(), PhpOffice\PhpSpreadsheet\Calculation\Calculation\strcmpAllowNull(), PhpOffice\PhpSpreadsheet\Calculation\Calculation\strcmpLowercaseFirst(), and PhpOffice\PhpSpreadsheet\Shared\StringHelper\strToUpper().

Referenced by PhpOffice\PhpSpreadsheet\Calculation\Calculation\executeArrayComparison(), and PhpOffice\PhpSpreadsheet\Calculation\Calculation\processTokenStack().

4997  {
4998  // If we're dealing with matrix operations, we want a matrix result
4999  if ((is_array($operand1)) || (is_array($operand2))) {
5000  return $this->executeArrayComparison($cellID, $operand1, $operand2, $operation, $stack, $recursingArrays);
5001  }
5002 
5003  // Simple validate the two operands if they are string values
5004  if (is_string($operand1) && $operand1 > '' && $operand1[0] == self::FORMULA_STRING_QUOTE) {
5005  $operand1 = self::unwrapResult($operand1);
5006  }
5007  if (is_string($operand2) && $operand2 > '' && $operand2[0] == self::FORMULA_STRING_QUOTE) {
5008  $operand2 = self::unwrapResult($operand2);
5009  }
5010 
5011  // Use case insensitive comparaison if not OpenOffice mode
5013  if (is_string($operand1)) {
5014  $operand1 = Shared\StringHelper::strToUpper($operand1);
5015  }
5016  if (is_string($operand2)) {
5017  $operand2 = Shared\StringHelper::strToUpper($operand2);
5018  }
5019  }
5020 
5021  $useLowercaseFirstComparison = is_string($operand1) && is_string($operand2) && Functions::getCompatibilityMode() == Functions::COMPATIBILITY_OPENOFFICE;
5022 
5023  // execute the necessary operation
5024  switch ($operation) {
5025  // Greater than
5026  case '>':
5027  if ($useLowercaseFirstComparison) {
5028  $result = $this->strcmpLowercaseFirst($operand1, $operand2) > 0;
5029  } else {
5030  $result = ($operand1 > $operand2);
5031  }
5032 
5033  break;
5034  // Less than
5035  case '<':
5036  if ($useLowercaseFirstComparison) {
5037  $result = $this->strcmpLowercaseFirst($operand1, $operand2) < 0;
5038  } else {
5039  $result = ($operand1 < $operand2);
5040  }
5041 
5042  break;
5043  // Equality
5044  case '=':
5045  if (is_numeric($operand1) && is_numeric($operand2)) {
5046  $result = (abs($operand1 - $operand2) < $this->delta);
5047  } else {
5048  $result = $this->strcmpAllowNull($operand1, $operand2) == 0;
5049  }
5050 
5051  break;
5052  // Greater than or equal
5053  case '>=':
5054  if (is_numeric($operand1) && is_numeric($operand2)) {
5055  $result = ((abs($operand1 - $operand2) < $this->delta) || ($operand1 > $operand2));
5056  } elseif ($useLowercaseFirstComparison) {
5057  $result = $this->strcmpLowercaseFirst($operand1, $operand2) >= 0;
5058  } else {
5059  $result = $this->strcmpAllowNull($operand1, $operand2) >= 0;
5060  }
5061 
5062  break;
5063  // Less than or equal
5064  case '<=':
5065  if (is_numeric($operand1) && is_numeric($operand2)) {
5066  $result = ((abs($operand1 - $operand2) < $this->delta) || ($operand1 < $operand2));
5067  } elseif ($useLowercaseFirstComparison) {
5068  $result = $this->strcmpLowercaseFirst($operand1, $operand2) <= 0;
5069  } else {
5070  $result = $this->strcmpAllowNull($operand1, $operand2) <= 0;
5071  }
5072 
5073  break;
5074  // Inequality
5075  case '<>':
5076  if (is_numeric($operand1) && is_numeric($operand2)) {
5077  $result = (abs($operand1 - $operand2) > 1E-14);
5078  } else {
5079  $result = $this->strcmpAllowNull($operand1, $operand2) != 0;
5080  }
5081 
5082  break;
5083 
5084  default:
5085  throw new Exception('Unsupported binary comparison operation');
5086  }
5087 
5088  // Log the result details
5089  $this->debugLog->writeDebugLog('Evaluation Result is ', $this->showTypeDetails($result));
5090  // And push the result onto the stack
5091  $stack->push('Value', $result);
5092 
5093  return $result;
5094  }
strcmpAllowNull($str1, $str2)
PHP8.1 deprecates passing null to strcmp.
$result
static strToUpper($pValue)
Convert a UTF-8 encoded string to upper case.
strcmpLowercaseFirst($str1, $str2)
Compare two strings in the same way as strcmp() except that lowercase come before uppercase letters...
executeArrayComparison($cellID, $operand1, $operand2, $operation, Stack &$stack, bool $recursingArrays)
showTypeDetails($value)
Format type and details of an operand for display in the log (based on operand type).
static getCompatibilityMode()
Return the current Compatibility Mode.
Definition: Functions.php:93
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ executeNumericBinaryOperation()

PhpOffice\PhpSpreadsheet\Calculation\Calculation::executeNumericBinaryOperation (   $operand1,
  $operand2,
  $operation,
  $matrixFunction,
$stack 
)
private
Parameters
mixed$operand1
mixed$operand2
mixed$operation
string$matrixFunction
mixed$stack
Returns
bool|mixed

Definition at line 5134 of file Calculation.php.

References $matrix, $result, PhpOffice\PhpSpreadsheet\Calculation\Functions\COMPATIBILITY_OPENOFFICE, PhpOffice\PhpSpreadsheet\Calculation\Functions\getCompatibilityMode(), PhpOffice\PhpSpreadsheet\Calculation\Calculation\showTypeDetails(), PhpOffice\PhpSpreadsheet\Calculation\Calculation\validateBinaryOperand(), and PhpOffice\PhpSpreadsheet\Calculation\Functions\VALUE().

Referenced by PhpOffice\PhpSpreadsheet\Calculation\Calculation\processTokenStack().

5135  {
5136  // Validate the two operands
5137  if (!$this->validateBinaryOperand($operand1, $stack)) {
5138  return false;
5139  }
5140  if (!$this->validateBinaryOperand($operand2, $stack)) {
5141  return false;
5142  }
5143 
5144  // If either of the operands is a matrix, we need to treat them both as matrices
5145  // (converting the other operand to a matrix if need be); then perform the required
5146  // matrix operation
5147  if ((is_array($operand1)) || (is_array($operand2))) {
5148  // Ensure that both operands are arrays/matrices of the same size
5149  self::checkMatrixOperands($operand1, $operand2, 2);
5150 
5151  try {
5152  // Convert operand 1 from a PHP array to a matrix
5153  $matrix = new Shared\JAMA\Matrix($operand1);
5154  // Perform the required operation against the operand 1 matrix, passing in operand 2
5155  $matrixResult = $matrix->$matrixFunction($operand2);
5156  $result = $matrixResult->getArray();
5157  } catch (\Exception $ex) {
5158  $this->debugLog->writeDebugLog('JAMA Matrix Exception: ', $ex->getMessage());
5159  $result = '#VALUE!';
5160  }
5161  } else {
5162  if (
5164  ((is_string($operand1) && !is_numeric($operand1) && strlen($operand1) > 0) ||
5165  (is_string($operand2) && !is_numeric($operand2) && strlen($operand2) > 0))
5166  ) {
5168  } else {
5169  // If we're dealing with non-matrix operations, execute the necessary operation
5170  switch ($operation) {
5171  // Addition
5172  case '+':
5173  $result = $operand1 + $operand2;
5174 
5175  break;
5176  // Subtraction
5177  case '-':
5178  $result = $operand1 - $operand2;
5179 
5180  break;
5181  // Multiplication
5182  case '*':
5183  $result = $operand1 * $operand2;
5184 
5185  break;
5186  // Division
5187  case '/':
5188  if ($operand2 == 0) {
5189  // Trap for Divide by Zero error
5190  $stack->push('Error', '#DIV/0!');
5191  $this->debugLog->writeDebugLog('Evaluation Result is ', $this->showTypeDetails('#DIV/0!'));
5192 
5193  return false;
5194  }
5195  $result = $operand1 / $operand2;
5196 
5197  break;
5198  // Power
5199  case '^':
5200  $result = $operand1 ** $operand2;
5201 
5202  break;
5203 
5204  default:
5205  throw new Exception('Unsupported numeric binary operation');
5206  }
5207  }
5208  }
5209 
5210  // Log the result details
5211  $this->debugLog->writeDebugLog('Evaluation Result is ', $this->showTypeDetails($result));
5212  // And push the result onto the stack
5213  $stack->push('Value', $result);
5214 
5215  return $result;
5216  }
$result
$matrix
Definition: test.php:18
showTypeDetails($value)
Format type and details of an operand for display in the log (based on operand type).
static getCompatibilityMode()
Return the current Compatibility Mode.
Definition: Functions.php:93
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ extractCellRange()

PhpOffice\PhpSpreadsheet\Calculation\Calculation::extractCellRange ( $pRange = 'A1',
?Worksheet  $pSheet = null,
  $resetLog = true 
)

Extract range values.

Parameters
string$pRangeString based range representation
Worksheet$pSheetWorksheet
bool$resetLogFlag indicating whether calculation log should be reset or not
Returns
mixed Array of values in range if range contains more than one element. Otherwise, a single value is returned.

Definition at line 5240 of file Calculation.php.

References PhpOffice\PhpSpreadsheet\Cell\Coordinate\extractAllCellReferencesInRange(), and PhpOffice\PhpSpreadsheet\Worksheet\Worksheet\extractSheetTitle().

Referenced by PhpOffice\PhpSpreadsheet\Calculation\LookupRef\Indirect\extractRequiredCells(), PhpOffice\PhpSpreadsheet\Calculation\LookupRef\Offset\extractRequiredCells(), and PhpOffice\PhpSpreadsheet\Calculation\Calculation\processTokenStack().

5241  {
5242  // Return value
5243  $returnValue = [];
5244 
5245  if ($pSheet !== null) {
5246  $pSheetName = $pSheet->getTitle();
5247 
5248  if (strpos($pRange, '!') !== false) {
5249  [$pSheetName, $pRange] = Worksheet::extractSheetTitle($pRange, true);
5250  $pSheet = $this->spreadsheet->getSheetByName($pSheetName);
5251  }
5252 
5253  // Extract range
5254  $aReferences = Coordinate::extractAllCellReferencesInRange($pRange);
5255  $pRange = $pSheetName . '!' . $pRange;
5256  if (!isset($aReferences[1])) {
5257  $currentCol = '';
5258  $currentRow = 0;
5259  // Single cell in range
5260  sscanf($aReferences[0], '%[A-Z]%d', $currentCol, $currentRow);
5261  if ($pSheet->cellExists($aReferences[0])) {
5262  $returnValue[$currentRow][$currentCol] = $pSheet->getCell($aReferences[0])->getCalculatedValue($resetLog);
5263  } else {
5264  $returnValue[$currentRow][$currentCol] = null;
5265  }
5266  } else {
5267  // Extract cell data for all cells in the range
5268  foreach ($aReferences as $reference) {
5269  $currentCol = '';
5270  $currentRow = 0;
5271  // Extract range
5272  sscanf($reference, '%[A-Z]%d', $currentCol, $currentRow);
5273  if ($pSheet->cellExists($reference)) {
5274  $returnValue[$currentRow][$currentCol] = $pSheet->getCell($reference)->getCalculatedValue($resetLog);
5275  } else {
5276  $returnValue[$currentRow][$currentCol] = null;
5277  }
5278  }
5279  }
5280  }
5281 
5282  return $returnValue;
5283  }
static extractSheetTitle($pRange, $returnRange=false)
Extract worksheet title from range.
Definition: Worksheet.php:2719
static extractAllCellReferencesInRange($cellRange)
Extract all cell references in range, which may be comprised of multiple cell ranges.
Definition: Coordinate.php:338
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ extractNamedRange()

PhpOffice\PhpSpreadsheet\Calculation\Calculation::extractNamedRange ( $pRange = 'A1',
?Worksheet  $pSheet = null,
  $resetLog = true 
)

Extract range values.

Parameters
string$pRangeString based range representation
Worksheet$pSheetWorksheet
bool$resetLogFlag indicating whether calculation log should be reset or not
Returns
mixed Array of values in range if range contains more than one element. Otherwise, a single value is returned.

Definition at line 5294 of file Calculation.php.

References PhpOffice\PhpSpreadsheet\Cell\Coordinate\coordinateFromString(), PhpOffice\PhpSpreadsheet\Cell\Coordinate\extractAllCellReferencesInRange(), PhpOffice\PhpSpreadsheet\Worksheet\Worksheet\extractSheetTitle(), PhpOffice\PhpSpreadsheet\Calculation\Functions\REF(), PhpOffice\PhpSpreadsheet\DefinedName\resolveName(), and PhpOffice\PhpSpreadsheet\Cell\Coordinate\splitRange().

5295  {
5296  // Return value
5297  $returnValue = [];
5298 
5299  if ($pSheet !== null) {
5300  $pSheetName = $pSheet->getTitle();
5301  if (strpos($pRange, '!') !== false) {
5302  [$pSheetName, $pRange] = Worksheet::extractSheetTitle($pRange, true);
5303  $pSheet = $this->spreadsheet->getSheetByName($pSheetName);
5304  }
5305 
5306  // Named range?
5307  $namedRange = DefinedName::resolveName($pRange, $pSheet);
5308  if ($namedRange === null) {
5309  return Functions::REF();
5310  }
5311 
5312  $pSheet = $namedRange->getWorksheet();
5313  $pRange = $namedRange->getValue();
5314  $splitRange = Coordinate::splitRange($pRange);
5315  // Convert row and column references
5316  if (ctype_alpha($splitRange[0][0])) {
5317  $pRange = $splitRange[0][0] . '1:' . $splitRange[0][1] . $namedRange->getWorksheet()->getHighestRow();
5318  } elseif (ctype_digit($splitRange[0][0])) {
5319  $pRange = 'A' . $splitRange[0][0] . ':' . $namedRange->getWorksheet()->getHighestColumn() . $splitRange[0][1];
5320  }
5321 
5322  // Extract range
5323  $aReferences = Coordinate::extractAllCellReferencesInRange($pRange);
5324  if (!isset($aReferences[1])) {
5325  // Single cell (or single column or row) in range
5326  [$currentCol, $currentRow] = Coordinate::coordinateFromString($aReferences[0]);
5327  if ($pSheet->cellExists($aReferences[0])) {
5328  $returnValue[$currentRow][$currentCol] = $pSheet->getCell($aReferences[0])->getCalculatedValue($resetLog);
5329  } else {
5330  $returnValue[$currentRow][$currentCol] = null;
5331  }
5332  } else {
5333  // Extract cell data for all cells in the range
5334  foreach ($aReferences as $reference) {
5335  // Extract range
5336  [$currentCol, $currentRow] = Coordinate::coordinateFromString($reference);
5337  if ($pSheet->cellExists($reference)) {
5338  $returnValue[$currentRow][$currentCol] = $pSheet->getCell($reference)->getCalculatedValue($resetLog);
5339  } else {
5340  $returnValue[$currentRow][$currentCol] = null;
5341  }
5342  }
5343  }
5344  }
5345 
5346  return $returnValue;
5347  }
static splitRange($pRange)
Split range into coordinate strings.
Definition: Coordinate.php:140
static extractSheetTitle($pRange, $returnRange=false)
Extract worksheet title from range.
Definition: Worksheet.php:2719
static resolveName(string $pDefinedName, Worksheet $pSheet, string $sheetName='')
Resolve a named range to a regular cell range or formula.
static coordinateFromString($pCoordinateString)
Coordinate from string.
Definition: Coordinate.php:32
static extractAllCellReferencesInRange($cellRange)
Extract all cell references in range, which may be comprised of multiple cell ranges.
Definition: Coordinate.php:338
+ Here is the call graph for this function:

◆ flushInstance()

PhpOffice\PhpSpreadsheet\Calculation\Calculation::flushInstance ( )

Flush the calculation cache for any existing instance of this class but only if a Calculation instance exists.

Definition at line 2805 of file Calculation.php.

References PhpOffice\PhpSpreadsheet\Calculation\Calculation\clearBranchStore(), and PhpOffice\PhpSpreadsheet\Calculation\Calculation\clearCalculationCache().

2805  : void
2806  {
2807  $this->clearCalculationCache();
2808  $this->clearBranchStore();
2809  }
clearCalculationCache()
Clear calculation cache.
+ Here is the call graph for this function:

◆ getArrayReturnType()

static PhpOffice\PhpSpreadsheet\Calculation\Calculation::getArrayReturnType ( )
static

Return the Array Return Type (Array or Value of first element in the array).

Returns
string $returnType Array return type

Definition at line 2876 of file Calculation.php.

Referenced by PhpOffice\PhpSpreadsheet\Writer\Html\generateHtmlAll(), and PhpOffice\PhpSpreadsheet\Writer\Csv\save().

2877  {
2878  return self::$returnArrayAsType;
2879  }
+ Here is the caller graph for this function:

◆ getCalculationCacheEnabled()

PhpOffice\PhpSpreadsheet\Calculation\Calculation::getCalculationCacheEnabled ( )

Is calculation caching enabled?

Returns
bool

Definition at line 2886 of file Calculation.php.

References PhpOffice\PhpSpreadsheet\Calculation\Calculation\$calculationCacheEnabled.

Referenced by PhpOffice\PhpSpreadsheet\Calculation\Calculation\calculateFormula().

+ Here is the caller graph for this function:

◆ getDebugLog()

PhpOffice\PhpSpreadsheet\Calculation\Calculation::getDebugLog ( )

Get the Logger for this calculation engine instance.

Returns
Logger

Definition at line 2816 of file Calculation.php.

References PhpOffice\PhpSpreadsheet\Calculation\Calculation\$debugLog.

Referenced by PhpOffice\PhpSpreadsheet\Calculation\Calculation\evaluateDefinedName().

+ Here is the caller graph for this function:

◆ getFALSE()

static PhpOffice\PhpSpreadsheet\Calculation\Calculation::getFALSE ( )
static

◆ getFunctions()

PhpOffice\PhpSpreadsheet\Calculation\Calculation::getFunctions ( )

Get a list of all implemented functions as an array of function objects.

Definition at line 5367 of file Calculation.php.

5367  : array
5368  {
5369  return self::$phpSpreadsheetFunctions;
5370  }

◆ getImplementedFunctionNames()

PhpOffice\PhpSpreadsheet\Calculation\Calculation::getImplementedFunctionNames ( )

Get a list of implemented Excel function names.

Returns
array

Definition at line 5377 of file Calculation.php.

References PhpOffice\PhpSpreadsheet\Calculation\Calculation\isImplemented().

5378  {
5379  $returnValue = [];
5380  foreach (self::$phpSpreadsheetFunctions as $functionName => $function) {
5381  if ($this->isImplemented($functionName)) {
5382  $returnValue[] = $functionName;
5383  }
5384  }
5385 
5386  return $returnValue;
5387  }
isImplemented($pFunction)
Is a specific function implemented?
+ Here is the call graph for this function:

◆ getInstance()

static PhpOffice\PhpSpreadsheet\Calculation\Calculation::getInstance ( ?Spreadsheet  $spreadsheet = null)
static

Get an instance of this class.

Parameters
?Spreadsheet$spreadsheet Injected spreadsheet for working with a PhpSpreadsheet Spreadsheet object, or NULL to create a standalone calculation engine

Definition at line 2785 of file Calculation.php.

References PhpOffice\PhpSpreadsheet\Calculation\Calculation\$instance, and PhpOffice\PhpSpreadsheet\Calculation\Calculation\$spreadsheet.

Referenced by PhpOffice\PhpSpreadsheet\Worksheet\Worksheet\__destruct(), PhpOffice\PhpSpreadsheet\Shared\StringHelper\convertToNumberIfFraction(), PhpOffice\PhpSpreadsheet\Calculation\Database\DatabaseAbstract\executeQuery(), PhpOffice\PhpSpreadsheet\Calculation\LookupRef\Indirect\extractRequiredCells(), PhpOffice\PhpSpreadsheet\Calculation\LookupRef\Offset\extractRequiredCells(), PhpOffice\PhpSpreadsheet\Writer\Html\generateHtmlAll(), PhpOffice\PhpSpreadsheet\Cell\Cell\getCalculatedValue(), PhpOffice\PhpSpreadsheet\Cell\DataValidator\isValueInList(), PhpOffice\PhpSpreadsheet\Chart\DataSeriesValues\refresh(), PhpOffice\PhpSpreadsheet\Writer\Csv\save(), PhpOffice\PhpSpreadsheet\Writer\Xls\save(), PhpOffice\PhpSpreadsheet\Writer\Xlsx\save(), PhpOffice\PhpSpreadsheet\Settings\setLocale(), and PhpOffice\PhpSpreadsheet\Worksheet\AutoFilter\showHideRows().

2785  : self
2786  {
2787  if ($spreadsheet !== null) {
2788  $instance = $spreadsheet->getCalculationEngine();
2789  if (isset($instance)) {
2790  return $instance;
2791  }
2792  }
2793 
2794  if (!isset(self::$instance) || (self::$instance === null)) {
2795  self::$instance = new self();
2796  }
2797 
2798  return self::$instance;
2799  }
+ Here is the caller graph for this function:

◆ getLocale()

PhpOffice\PhpSpreadsheet\Calculation\Calculation::getLocale ( )

Get the currently defined locale code.

Returns
string

Definition at line 2982 of file Calculation.php.

2983  {
2984  return self::$localeLanguage;
2985  }

◆ getLocaleFile()

PhpOffice\PhpSpreadsheet\Calculation\Calculation::getLocaleFile ( string  $localeDir,
string  $locale,
string  $language,
string  $file 
)
private

Definition at line 2987 of file Calculation.php.

Referenced by PhpOffice\PhpSpreadsheet\Calculation\Calculation\setLocale().

2987  : string
2988  {
2989  $localeFileName = $localeDir . str_replace('_', DIRECTORY_SEPARATOR, $locale) .
2990  DIRECTORY_SEPARATOR . $file;
2991  if (!file_exists($localeFileName)) {
2992  // If there isn't a locale specific file, look for a language specific file
2993  $localeFileName = $localeDir . $language . DIRECTORY_SEPARATOR . $file;
2994  if (!file_exists($localeFileName)) {
2995  throw new Exception('Locale file not found');
2996  }
2997  }
2998 
2999  return $localeFileName;
3000  }
+ Here is the caller graph for this function:

◆ getMatrixDimensions()

static PhpOffice\PhpSpreadsheet\Calculation\Calculation::getMatrixDimensions ( array &  $matrix)
static

Read the dimensions of a matrix, and re-index it with straight numeric keys starting from row 0, column 0.

Parameters
array$matrixmatrix operand
Returns
int[] An array comprising the number of rows, and number of columns

Definition at line 3603 of file Calculation.php.

Referenced by PhpOffice\PhpSpreadsheet\Calculation\LookupRef\RowColumnInformation\COLUMNS(), and PhpOffice\PhpSpreadsheet\Calculation\LookupRef\RowColumnInformation\ROWS().

3604  {
3605  $matrixRows = count($matrix);
3606  $matrixColumns = 0;
3607  foreach ($matrix as $rowKey => $rowValue) {
3608  if (!is_array($rowValue)) {
3609  $matrix[$rowKey] = [$rowValue];
3610  $matrixColumns = max(1, $matrixColumns);
3611  } else {
3612  $matrix[$rowKey] = array_values($rowValue);
3613  $matrixColumns = max(count($rowValue), $matrixColumns);
3614  }
3615  }
3616  $matrix = array_values($matrix);
3617 
3618  return [$matrixRows, $matrixColumns];
3619  }
$matrix
Definition: test.php:18
+ Here is the caller graph for this function:

◆ getTokensAsString()

PhpOffice\PhpSpreadsheet\Calculation\Calculation::getTokensAsString (   $tokens)
private

Definition at line 5425 of file Calculation.php.

References PHPMailer\PHPMailer\$token.

5426  {
5427  $tokensStr = array_map(function ($token) {
5428  $value = $token['value'] ?? 'no value';
5429  while (is_array($value)) {
5430  $value = array_pop($value);
5431  }
5432 
5433  return $value;
5434  }, $tokens);
5435 
5436  return '[ ' . implode(' | ', $tokensStr) . ' ]';
5437  }

◆ getTRUE()

static PhpOffice\PhpSpreadsheet\Calculation\Calculation::getTRUE ( )
static

◆ getUnusedBranchStoreKey()

PhpOffice\PhpSpreadsheet\Calculation\Calculation::getUnusedBranchStoreKey ( )
private

Definition at line 5417 of file Calculation.php.

References PhpOffice\PhpSpreadsheet\Calculation\Calculation\$branchStoreKeyCounter.

Referenced by PhpOffice\PhpSpreadsheet\Calculation\Calculation\internalParseFormula().

5418  {
5419  $storeKeyValue = 'storeKey-' . $this->branchStoreKeyCounter;
5421 
5422  return $storeKeyValue;
5423  }
+ Here is the caller graph for this function:

◆ getValueFromCache()

PhpOffice\PhpSpreadsheet\Calculation\Calculation::getValueFromCache ( string  $cellReference,
$cellValue 
)
Parameters
mixed$cellValue

Definition at line 3445 of file Calculation.php.

Referenced by PhpOffice\PhpSpreadsheet\Calculation\Calculation\_calculateFormulaValue().

3445  : bool
3446  {
3447  $this->debugLog->writeDebugLog("Testing cache value for cell {$cellReference}");
3448  // Is calculation cacheing enabled?
3449  // If so, is the required value present in calculation cache?
3450  if (($this->calculationCacheEnabled) && (isset($this->calculationCache[$cellReference]))) {
3451  $this->debugLog->writeDebugLog("Retrieving value for cell {$cellReference} from cache");
3452  // Return the cached result
3453 
3454  $cellValue = $this->calculationCache[$cellReference];
3455 
3456  return true;
3457  }
3458 
3459  return false;
3460  }
+ Here is the caller graph for this function:

◆ internalParseFormula()

static static static PhpOffice\PhpSpreadsheet\Calculation\Calculation::internalParseFormula (   $formula,
?Cell  $pCell = null 
)
private
Parameters
string$formula
Returns
array<int, mixed>|false

Definition at line 3883 of file Calculation.php.

References $d, $index, Sabre\VObject\$output, $row, PhpOffice\PhpSpreadsheet\Calculation\Calculation\convertMatrixReferences(), PhpOffice\PhpSpreadsheet\Worksheet\Worksheet\extractSheetTitle(), PhpOffice\PhpSpreadsheet\Calculation\Calculation\getUnusedBranchStoreKey(), and PhpOffice\PhpSpreadsheet\Calculation\Calculation\raiseFormulaError().

Referenced by PhpOffice\PhpSpreadsheet\Calculation\Calculation\_calculateFormulaValue(), and PhpOffice\PhpSpreadsheet\Calculation\Calculation\parseFormula().

3884  {
3885  if (($formula = $this->convertMatrixReferences(trim($formula))) === false) {
3886  return false;
3887  }
3888 
3889  // If we're using cell caching, then $pCell may well be flushed back to the cache (which detaches the parent worksheet),
3890  // so we store the parent worksheet so that we can re-attach it when necessary
3891  $pCellParent = ($pCell !== null) ? $pCell->getWorksheet() : null;
3892 
3893  $regexpMatchString = '/^(' . self::CALCULATION_REGEXP_FUNCTION .
3894  '|' . self::CALCULATION_REGEXP_CELLREF .
3895  '|' . self::CALCULATION_REGEXP_COLUMN_RANGE .
3896  '|' . self::CALCULATION_REGEXP_ROW_RANGE .
3897  '|' . self::CALCULATION_REGEXP_NUMBER .
3898  '|' . self::CALCULATION_REGEXP_STRING .
3899  '|' . self::CALCULATION_REGEXP_OPENBRACE .
3900  '|' . self::CALCULATION_REGEXP_DEFINEDNAME .
3901  '|' . self::CALCULATION_REGEXP_ERROR .
3902  ')/sui';
3903 
3904  // Start with initialisation
3905  $index = 0;
3906  $stack = new Stack();
3907  $output = [];
3908  $expectingOperator = false; // We use this test in syntax-checking the expression to determine when a
3909  // - is a negation or + is a positive operator rather than an operation
3910  $expectingOperand = false; // We use this test in syntax-checking the expression to determine whether an operand
3911  // should be null in a function call
3912 
3913  // IF branch pruning
3914  // currently pending storeKey (last item of the storeKeysStack
3915  $pendingStoreKey = null;
3916  // stores a list of storeKeys (string[])
3917  $pendingStoreKeysStack = [];
3918  $expectingConditionMap = []; // ['storeKey' => true, ...]
3919  $expectingThenMap = []; // ['storeKey' => true, ...]
3920  $expectingElseMap = []; // ['storeKey' => true, ...]
3921  $parenthesisDepthMap = []; // ['storeKey' => 4, ...]
3922 
3923  // The guts of the lexical parser
3924  // Loop through the formula extracting each operator and operand in turn
3925  while (true) {
3926  // Branch pruning: we adapt the output item to the context (it will
3927  // be used to limit its computation)
3928  $currentCondition = null;
3929  $currentOnlyIf = null;
3930  $currentOnlyIfNot = null;
3931  $previousStoreKey = null;
3932  $pendingStoreKey = end($pendingStoreKeysStack);
3933 
3934  if ($this->branchPruningEnabled) {
3935  // this is a condition ?
3936  if (isset($expectingConditionMap[$pendingStoreKey]) && $expectingConditionMap[$pendingStoreKey]) {
3937  $currentCondition = $pendingStoreKey;
3938  $stackDepth = count($pendingStoreKeysStack);
3939  if ($stackDepth > 1) { // nested if
3940  $previousStoreKey = $pendingStoreKeysStack[$stackDepth - 2];
3941  }
3942  }
3943  if (isset($expectingThenMap[$pendingStoreKey]) && $expectingThenMap[$pendingStoreKey]) {
3944  $currentOnlyIf = $pendingStoreKey;
3945  } elseif (isset($previousStoreKey)) {
3946  if (isset($expectingThenMap[$previousStoreKey]) && $expectingThenMap[$previousStoreKey]) {
3947  $currentOnlyIf = $previousStoreKey;
3948  }
3949  }
3950  if (isset($expectingElseMap[$pendingStoreKey]) && $expectingElseMap[$pendingStoreKey]) {
3951  $currentOnlyIfNot = $pendingStoreKey;
3952  } elseif (isset($previousStoreKey)) {
3953  if (isset($expectingElseMap[$previousStoreKey]) && $expectingElseMap[$previousStoreKey]) {
3954  $currentOnlyIfNot = $previousStoreKey;
3955  }
3956  }
3957  }
3958 
3959  $opCharacter = $formula[$index]; // Get the first character of the value at the current index position
3960 
3961  if ((isset(self::$comparisonOperators[$opCharacter])) && (strlen($formula) > $index) && (isset(self::$comparisonOperators[$formula[$index + 1]]))) {
3962  $opCharacter .= $formula[++$index];
3963  }
3964  // Find out if we're currently at the beginning of a number, variable, cell reference, function, parenthesis or operand
3965  $isOperandOrFunction = (bool) preg_match($regexpMatchString, substr($formula, $index), $match);
3966 
3967  if ($opCharacter == '-' && !$expectingOperator) { // Is it a negation instead of a minus?
3968  // Put a negation on the stack
3969  $stack->push('Unary Operator', '~', null, $currentCondition, $currentOnlyIf, $currentOnlyIfNot);
3970  ++$index; // and drop the negation symbol
3971  } elseif ($opCharacter == '%' && $expectingOperator) {
3972  // Put a percentage on the stack
3973  $stack->push('Unary Operator', '%', null, $currentCondition, $currentOnlyIf, $currentOnlyIfNot);
3974  ++$index;
3975  } elseif ($opCharacter == '+' && !$expectingOperator) { // Positive (unary plus rather than binary operator plus) can be discarded?
3976  ++$index; // Drop the redundant plus symbol
3977  } elseif ((($opCharacter == '~') || ($opCharacter == '|')) && (!$isOperandOrFunction)) { // We have to explicitly deny a tilde or pipe, because they are legal
3978  return $this->raiseFormulaError("Formula Error: Illegal character '~'"); // on the stack but not in the input expression
3979  } elseif ((isset(self::$operators[$opCharacter]) || $isOperandOrFunction) && $expectingOperator) { // Are we putting an operator on the stack?
3980  while (
3981  $stack->count() > 0 &&
3982  ($o2 = $stack->last()) &&
3983  isset(self::$operators[$o2['value']]) &&
3984  @(self::$operatorAssociativity[$opCharacter] ? self::$operatorPrecedence[$opCharacter] < self::$operatorPrecedence[$o2['value']] : self::$operatorPrecedence[$opCharacter] <= self::$operatorPrecedence[$o2['value']])
3985  ) {
3986  $output[] = $stack->pop(); // Swap operands and higher precedence operators from the stack to the output
3987  }
3988 
3989  // Finally put our current operator onto the stack
3990  $stack->push('Binary Operator', $opCharacter, null, $currentCondition, $currentOnlyIf, $currentOnlyIfNot);
3991 
3992  ++$index;
3993  $expectingOperator = false;
3994  } elseif ($opCharacter == ')' && $expectingOperator) { // Are we expecting to close a parenthesis?
3995  $expectingOperand = false;
3996  while (($o2 = $stack->pop()) && $o2['value'] != '(') { // Pop off the stack back to the last (
3997  if ($o2 === null) {
3998  return $this->raiseFormulaError('Formula Error: Unexpected closing brace ")"');
3999  }
4000  $output[] = $o2;
4001  }
4002  $d = $stack->last(2);
4003 
4004  // Branch pruning we decrease the depth whether is it a function
4005  // call or a parenthesis
4006  if (!empty($pendingStoreKey)) {
4007  --$parenthesisDepthMap[$pendingStoreKey];
4008  }
4009 
4010  if (is_array($d) && preg_match('/^' . self::CALCULATION_REGEXP_FUNCTION . '$/miu', $d['value'], $matches)) { // Did this parenthesis just close a function?
4011  if (!empty($pendingStoreKey) && $parenthesisDepthMap[$pendingStoreKey] == -1) {
4012  // we are closing an IF(
4013  if ($d['value'] != 'IF(') {
4014  return $this->raiseFormulaError('Parser bug we should be in an "IF("');
4015  }
4016  if ($expectingConditionMap[$pendingStoreKey]) {
4017  return $this->raiseFormulaError('We should not be expecting a condition');
4018  }
4019  $expectingThenMap[$pendingStoreKey] = false;
4020  $expectingElseMap[$pendingStoreKey] = false;
4021  --$parenthesisDepthMap[$pendingStoreKey];
4022  array_pop($pendingStoreKeysStack);
4023  unset($pendingStoreKey);
4024  }
4025 
4026  $functionName = $matches[1]; // Get the function name
4027  $d = $stack->pop();
4028  $argumentCount = $d['value']; // See how many arguments there were (argument count is the next value stored on the stack)
4029  $output[] = $d; // Dump the argument count on the output
4030  $output[] = $stack->pop(); // Pop the function and push onto the output
4031  if (isset(self::$controlFunctions[$functionName])) {
4032  $expectedArgumentCount = self::$controlFunctions[$functionName]['argumentCount'];
4033  $functionCall = self::$controlFunctions[$functionName]['functionCall'];
4034  } elseif (isset(self::$phpSpreadsheetFunctions[$functionName])) {
4035  $expectedArgumentCount = self::$phpSpreadsheetFunctions[$functionName]['argumentCount'];
4036  $functionCall = self::$phpSpreadsheetFunctions[$functionName]['functionCall'];
4037  } else { // did we somehow push a non-function on the stack? this should never happen
4038  return $this->raiseFormulaError('Formula Error: Internal error, non-function on stack');
4039  }
4040  // Check the argument count
4041  $argumentCountError = false;
4042  $expectedArgumentCountString = null;
4043  if (is_numeric($expectedArgumentCount)) {
4044  if ($expectedArgumentCount < 0) {
4045  if ($argumentCount > abs($expectedArgumentCount)) {
4046  $argumentCountError = true;
4047  $expectedArgumentCountString = 'no more than ' . abs($expectedArgumentCount);
4048  }
4049  } else {
4050  if ($argumentCount != $expectedArgumentCount) {
4051  $argumentCountError = true;
4052  $expectedArgumentCountString = $expectedArgumentCount;
4053  }
4054  }
4055  } elseif ($expectedArgumentCount != '*') {
4056  $isOperandOrFunction = preg_match('/(\d*)([-+,])(\d*)/', $expectedArgumentCount, $argMatch);
4057  switch ($argMatch[2]) {
4058  case '+':
4059  if ($argumentCount < $argMatch[1]) {
4060  $argumentCountError = true;
4061  $expectedArgumentCountString = $argMatch[1] . ' or more ';
4062  }
4063 
4064  break;
4065  case '-':
4066  if (($argumentCount < $argMatch[1]) || ($argumentCount > $argMatch[3])) {
4067  $argumentCountError = true;
4068  $expectedArgumentCountString = 'between ' . $argMatch[1] . ' and ' . $argMatch[3];
4069  }
4070 
4071  break;
4072  case ',':
4073  if (($argumentCount != $argMatch[1]) && ($argumentCount != $argMatch[3])) {
4074  $argumentCountError = true;
4075  $expectedArgumentCountString = 'either ' . $argMatch[1] . ' or ' . $argMatch[3];
4076  }
4077 
4078  break;
4079  }
4080  }
4081  if ($argumentCountError) {
4082  return $this->raiseFormulaError("Formula Error: Wrong number of arguments for $functionName() function: $argumentCount given, " . $expectedArgumentCountString . ' expected');
4083  }
4084  }
4085  ++$index;
4086  } elseif ($opCharacter == ',') { // Is this the separator for function arguments?
4087  if (
4088  !empty($pendingStoreKey) &&
4089  $parenthesisDepthMap[$pendingStoreKey] == 0
4090  ) {
4091  // We must go to the IF next argument
4092  if ($expectingConditionMap[$pendingStoreKey]) {
4093  $expectingConditionMap[$pendingStoreKey] = false;
4094  $expectingThenMap[$pendingStoreKey] = true;
4095  } elseif ($expectingThenMap[$pendingStoreKey]) {
4096  $expectingThenMap[$pendingStoreKey] = false;
4097  $expectingElseMap[$pendingStoreKey] = true;
4098  } elseif ($expectingElseMap[$pendingStoreKey]) {
4099  return $this->raiseFormulaError('Reaching fourth argument of an IF');
4100  }
4101  }
4102  while (($o2 = $stack->pop()) && $o2['value'] != '(') { // Pop off the stack back to the last (
4103  if ($o2 === null) {
4104  return $this->raiseFormulaError('Formula Error: Unexpected ,');
4105  }
4106  $output[] = $o2; // pop the argument expression stuff and push onto the output
4107  }
4108  // If we've a comma when we're expecting an operand, then what we actually have is a null operand;
4109  // so push a null onto the stack
4110  if (($expectingOperand) || (!$expectingOperator)) {
4111  $output[] = ['type' => 'NULL Value', 'value' => self::$excelConstants['NULL'], 'reference' => null];
4112  }
4113  // make sure there was a function
4114  $d = $stack->last(2);
4115  if (!preg_match('/^' . self::CALCULATION_REGEXP_FUNCTION . '$/miu', $d['value'], $matches)) {
4116  return $this->raiseFormulaError('Formula Error: Unexpected ,');
4117  }
4118  $d = $stack->pop();
4119  $itemStoreKey = $d['storeKey'] ?? null;
4120  $itemOnlyIf = $d['onlyIf'] ?? null;
4121  $itemOnlyIfNot = $d['onlyIfNot'] ?? null;
4122  $stack->push($d['type'], ++$d['value'], $d['reference'], $itemStoreKey, $itemOnlyIf, $itemOnlyIfNot); // increment the argument count
4123  $stack->push('Brace', '(', null, $itemStoreKey, $itemOnlyIf, $itemOnlyIfNot); // put the ( back on, we'll need to pop back to it again
4124  $expectingOperator = false;
4125  $expectingOperand = true;
4126  ++$index;
4127  } elseif ($opCharacter == '(' && !$expectingOperator) {
4128  if (!empty($pendingStoreKey)) { // Branch pruning: we go deeper
4129  ++$parenthesisDepthMap[$pendingStoreKey];
4130  }
4131  $stack->push('Brace', '(', null, $currentCondition, $currentOnlyIf, $currentOnlyIf);
4132  ++$index;
4133  } elseif ($isOperandOrFunction && !$expectingOperator) { // do we now have a function/variable/number?
4134  $expectingOperator = true;
4135  $expectingOperand = false;
4136  $val = $match[1];
4137  $length = strlen($val);
4138 
4139  if (preg_match('/^' . self::CALCULATION_REGEXP_FUNCTION . '$/miu', $val, $matches)) {
4140  $val = preg_replace('/\s/u', '', $val);
4141  if (isset(self::$phpSpreadsheetFunctions[strtoupper($matches[1])]) || isset(self::$controlFunctions[strtoupper($matches[1])])) { // it's a function
4142  $valToUpper = strtoupper($val);
4143  } else {
4144  $valToUpper = 'NAME.ERROR(';
4145  }
4146  // here $matches[1] will contain values like "IF"
4147  // and $val "IF("
4148  if ($this->branchPruningEnabled && ($valToUpper == 'IF(')) { // we handle a new if
4149  $pendingStoreKey = $this->getUnusedBranchStoreKey();
4150  $pendingStoreKeysStack[] = $pendingStoreKey;
4151  $expectingConditionMap[$pendingStoreKey] = true;
4152  $parenthesisDepthMap[$pendingStoreKey] = 0;
4153  } else { // this is not an if but we go deeper
4154  if (!empty($pendingStoreKey) && array_key_exists($pendingStoreKey, $parenthesisDepthMap)) {
4155  ++$parenthesisDepthMap[$pendingStoreKey];
4156  }
4157  }
4158 
4159  $stack->push('Function', $valToUpper, null, $currentCondition, $currentOnlyIf, $currentOnlyIfNot);
4160  // tests if the function is closed right after opening
4161  $ax = preg_match('/^\s*\)/u', substr($formula, $index + $length));
4162  if ($ax) {
4163  $stack->push('Operand Count for Function ' . $valToUpper . ')', 0, null, $currentCondition, $currentOnlyIf, $currentOnlyIfNot);
4164  $expectingOperator = true;
4165  } else {
4166  $stack->push('Operand Count for Function ' . $valToUpper . ')', 1, null, $currentCondition, $currentOnlyIf, $currentOnlyIfNot);
4167  $expectingOperator = false;
4168  }
4169  $stack->push('Brace', '(');
4170  } elseif (preg_match('/^' . self::CALCULATION_REGEXP_CELLREF . '$/i', $val, $matches)) {
4171  // Watch for this case-change when modifying to allow cell references in different worksheets...
4172  // Should only be applied to the actual cell column, not the worksheet name
4173  // If the last entry on the stack was a : operator, then we have a cell range reference
4174  $testPrevOp = $stack->last(1);
4175  if ($testPrevOp !== null && $testPrevOp['value'] === ':') {
4176  // If we have a worksheet reference, then we're playing with a 3D reference
4177  if ($matches[2] == '') {
4178  // Otherwise, we 'inherit' the worksheet reference from the start cell reference
4179  // The start of the cell range reference should be the last entry in $output
4180  $rangeStartCellRef = $output[count($output) - 1]['value'];
4181  preg_match('/^' . self::CALCULATION_REGEXP_CELLREF . '$/i', $rangeStartCellRef, $rangeStartMatches);
4182  if ($rangeStartMatches[2] > '') {
4183  $val = $rangeStartMatches[2] . '!' . $val;
4184  }
4185  } else {
4186  $rangeStartCellRef = $output[count($output) - 1]['value'];
4187  preg_match('/^' . self::CALCULATION_REGEXP_CELLREF . '$/i', $rangeStartCellRef, $rangeStartMatches);
4188  if ($rangeStartMatches[2] !== $matches[2]) {
4189  return $this->raiseFormulaError('3D Range references are not yet supported');
4190  }
4191  }
4192  } elseif (strpos($val, '!') === false && $pCellParent !== null) {
4193  $worksheet = $pCellParent->getTitle();
4194  $val = "'{$worksheet}'!{$val}";
4195  }
4196 
4197  $outputItem = $stack->getStackItem('Cell Reference', $val, $val, $currentCondition, $currentOnlyIf, $currentOnlyIfNot);
4198 
4199  $output[] = $outputItem;
4200  } else { // it's a variable, constant, string, number or boolean
4201  $localeConstant = false;
4202  $stackItemType = 'Value';
4203  $stackItemReference = null;
4204 
4205  // If the last entry on the stack was a : operator, then we may have a row or column range reference
4206  $testPrevOp = $stack->last(1);
4207  if ($testPrevOp !== null && $testPrevOp['value'] === ':') {
4208  $stackItemType = 'Cell Reference';
4209  $startRowColRef = $output[count($output) - 1]['value'];
4210  [$rangeWS1, $startRowColRef] = Worksheet::extractSheetTitle($startRowColRef, true);
4211  $rangeSheetRef = $rangeWS1;
4212  if ($rangeWS1 !== '') {
4213  $rangeWS1 .= '!';
4214  }
4215  $rangeSheetRef = trim($rangeSheetRef, "'");
4216  [$rangeWS2, $val] = Worksheet::extractSheetTitle($val, true);
4217  if ($rangeWS2 !== '') {
4218  $rangeWS2 .= '!';
4219  } else {
4220  $rangeWS2 = $rangeWS1;
4221  }
4222 
4223  $refSheet = $pCellParent;
4224  if ($pCellParent !== null && $rangeSheetRef !== '' && $rangeSheetRef !== $pCellParent->getTitle()) {
4225  $refSheet = $pCellParent->getParent()->getSheetByName($rangeSheetRef);
4226  }
4227 
4228  if (ctype_digit($val) && $val <= 1048576) {
4229  // Row range
4230  $stackItemType = 'Row Reference';
4231  $endRowColRef = ($refSheet !== null) ? $refSheet->getHighestDataColumn($val) : 'XFD'; // Max 16,384 columns for Excel2007
4232  $val = "{$rangeWS2}{$endRowColRef}{$val}";
4233  } elseif (ctype_alpha($val) && strlen($val) <= 3) {
4234  // Column range
4235  $stackItemType = 'Column Reference';
4236  $endRowColRef = ($refSheet !== null) ? $refSheet->getHighestDataRow($val) : 1048576; // Max 1,048,576 rows for Excel2007
4237  $val = "{$rangeWS2}{$val}{$endRowColRef}";
4238  }
4239  $stackItemReference = $val;
4240  } elseif ($opCharacter == self::FORMULA_STRING_QUOTE) {
4241  // UnEscape any quotes within the string
4242  $val = self::wrapResult(str_replace('""', self::FORMULA_STRING_QUOTE, self::unwrapResult($val)));
4243  } elseif (isset(self::$excelConstants[trim(strtoupper($val))])) {
4244  $stackItemType = 'Constant';
4245  $excelConstant = trim(strtoupper($val));
4246  $val = self::$excelConstants[$excelConstant];
4247  } elseif (($localeConstant = array_search(trim(strtoupper($val)), self::$localeBoolean)) !== false) {
4248  $stackItemType = 'Constant';
4249  $val = self::$excelConstants[$localeConstant];
4250  } elseif (
4251  preg_match('/^' . self::CALCULATION_REGEXP_ROW_RANGE . '/miu', substr($formula, $index), $rowRangeReference)
4252  ) {
4253  $val = $rowRangeReference[1];
4254  $length = strlen($rowRangeReference[1]);
4255  $stackItemType = 'Row Reference';
4256  $column = 'A';
4257  if (($testPrevOp !== null && $testPrevOp['value'] === ':') && $pCellParent !== null) {
4258  $column = $pCellParent->getHighestDataColumn($val);
4259  }
4260  $val = "{$rowRangeReference[2]}{$column}{$rowRangeReference[7]}";
4261  $stackItemReference = $val;
4262  } elseif (
4263  preg_match('/^' . self::CALCULATION_REGEXP_COLUMN_RANGE . '/miu', substr($formula, $index), $columnRangeReference)
4264  ) {
4265  $val = $columnRangeReference[1];
4266  $length = strlen($val);
4267  $stackItemType = 'Column Reference';
4268  $row = '1';
4269  if (($testPrevOp !== null && $testPrevOp['value'] === ':') && $pCellParent !== null) {
4270  $row = $pCellParent->getHighestDataRow($val);
4271  }
4272  $val = "{$val}{$row}";
4273  $stackItemReference = $val;
4274  } elseif (preg_match('/^' . self::CALCULATION_REGEXP_DEFINEDNAME . '.*/miu', $val, $match)) {
4275  $stackItemType = 'Defined Name';
4276  $stackItemReference = $val;
4277  } elseif (is_numeric($val)) {
4278  if ((strpos($val, '.') !== false) || (stripos($val, 'e') !== false) || ($val > PHP_INT_MAX) || ($val < -PHP_INT_MAX)) {
4279  $val = (float) $val;
4280  } else {
4281  $val = (int) $val;
4282  }
4283  }
4284 
4285  $details = $stack->getStackItem($stackItemType, $val, $stackItemReference, $currentCondition, $currentOnlyIf, $currentOnlyIfNot);
4286  if ($localeConstant) {
4287  $details['localeValue'] = $localeConstant;
4288  }
4289  $output[] = $details;
4290  }
4291  $index += $length;
4292  } elseif ($opCharacter == '$') { // absolute row or column range
4293  ++$index;
4294  } elseif ($opCharacter == ')') { // miscellaneous error checking
4295  if ($expectingOperand) {
4296  $output[] = ['type' => 'NULL Value', 'value' => self::$excelConstants['NULL'], 'reference' => null];
4297  $expectingOperand = false;
4298  $expectingOperator = true;
4299  } else {
4300  return $this->raiseFormulaError("Formula Error: Unexpected ')'");
4301  }
4302  } elseif (isset(self::$operators[$opCharacter]) && !$expectingOperator) {
4303  return $this->raiseFormulaError("Formula Error: Unexpected operator '$opCharacter'");
4304  } else { // I don't even want to know what you did to get here
4305  return $this->raiseFormulaError('Formula Error: An unexpected error occurred');
4306  }
4307  // Test for end of formula string
4308  if ($index == strlen($formula)) {
4309  // Did we end with an operator?.
4310  // Only valid for the % unary operator
4311  if ((isset(self::$operators[$opCharacter])) && ($opCharacter != '%')) {
4312  return $this->raiseFormulaError("Formula Error: Operator '$opCharacter' has no operands");
4313  }
4314 
4315  break;
4316  }
4317  // Ignore white space
4318  while (($formula[$index] == "\n") || ($formula[$index] == "\r")) {
4319  ++$index;
4320  }
4321 
4322  if ($formula[$index] == ' ') {
4323  while ($formula[$index] == ' ') {
4324  ++$index;
4325  }
4326 
4327  // If we're expecting an operator, but only have a space between the previous and next operands (and both are
4328  // Cell References) then we have an INTERSECTION operator
4329  if (
4330  ($expectingOperator) &&
4331  ((preg_match('/^' . self::CALCULATION_REGEXP_CELLREF . '.*/Ui', substr($formula, $index), $match)) &&
4332  ($output[count($output) - 1]['type'] == 'Cell Reference') ||
4333  (preg_match('/^' . self::CALCULATION_REGEXP_DEFINEDNAME . '.*/miu', substr($formula, $index), $match)) &&
4334  ($output[count($output) - 1]['type'] == 'Defined Name' || $output[count($output) - 1]['type'] == 'Value')
4335  )
4336  ) {
4337  while (
4338  $stack->count() > 0 &&
4339  ($o2 = $stack->last()) &&
4340  isset(self::$operators[$o2['value']]) &&
4341  @(self::$operatorAssociativity[$opCharacter] ? self::$operatorPrecedence[$opCharacter] < self::$operatorPrecedence[$o2['value']] : self::$operatorPrecedence[$opCharacter] <= self::$operatorPrecedence[$o2['value']])
4342  ) {
4343  $output[] = $stack->pop(); // Swap operands and higher precedence operators from the stack to the output
4344  }
4345  $stack->push('Binary Operator', '|'); // Put an Intersect Operator on the stack
4346  $expectingOperator = false;
4347  }
4348  }
4349  }
4350 
4351  while (($op = $stack->pop()) !== null) { // pop everything off the stack and push onto output
4352  if ((is_array($op) && $op['value'] == '(') || ($op === '(')) {
4353  return $this->raiseFormulaError("Formula Error: Expecting ')'"); // if there are any opening braces on the stack, then braces were unbalanced
4354  }
4355  $output[] = $op;
4356  }
4357 
4358  return $output;
4359  }
static extractSheetTitle($pRange, $returnRange=false)
Extract worksheet title from range.
Definition: Worksheet.php:2719
$index
Definition: metadata.php:60
$row
for($i=6; $i< 13; $i++) for($i=1; $i< 13; $i++) $d
Definition: date.php:296
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ isImplemented()

PhpOffice\PhpSpreadsheet\Calculation\Calculation::isImplemented (   $pFunction)

Is a specific function implemented?

Parameters
string$pFunctionFunction Name
Returns
bool

Definition at line 5356 of file Calculation.php.

Referenced by PhpOffice\PhpSpreadsheet\Calculation\Calculation\getImplementedFunctionNames().

5357  {
5358  $pFunction = strtoupper($pFunction);
5359  $notImplemented = !isset(self::$phpSpreadsheetFunctions[$pFunction]) || (is_array(self::$phpSpreadsheetFunctions[$pFunction]['functionCall']) && self::$phpSpreadsheetFunctions[$pFunction]['functionCall'][1] === 'DUMMY');
5360 
5361  return !$notImplemented;
5362  }
+ Here is the caller graph for this function:

◆ loadLocales()

static PhpOffice\PhpSpreadsheet\Calculation\Calculation::loadLocales ( )
staticprivate

Definition at line 2768 of file Calculation.php.

References $filename.

2768  : void
2769  {
2770  $localeFileDirectory = __DIR__ . '/locale/';
2771  foreach (glob($localeFileDirectory . '*', GLOB_ONLYDIR) as $filename) {
2772  $filename = substr($filename, strlen($localeFileDirectory));
2773  if ($filename != 'en') {
2774  self::$validLocaleLanguages[] = $filename;
2775  }
2776  }
2777  }
$filename
Definition: buildRTE.php:89

◆ localeFunc()

static PhpOffice\PhpSpreadsheet\Calculation\Calculation::localeFunc (   $function)
static

Definition at line 3220 of file Calculation.php.

Referenced by PhpOffice\PhpSpreadsheet\Calculation\Token\Stack\push().

3221  {
3222  if (self::$localeLanguage !== 'en_us') {
3223  $functionName = trim($function, '(');
3224  if (isset(self::$localeFunctions[$functionName])) {
3225  $brace = ($functionName != $function);
3226  $function = self::$localeFunctions[$functionName];
3227  if ($brace) {
3228  $function .= '(';
3229  }
3230  }
3231  }
3232 
3233  return $function;
3234  }
+ Here is the caller graph for this function:

◆ parseFormula()

PhpOffice\PhpSpreadsheet\Calculation\Calculation::parseFormula (   $formula)

Validate and parse a formula string.

Parameters
string$formulaFormula to parse
Returns
array|bool

Definition at line 3384 of file Calculation.php.

References PhpOffice\PhpSpreadsheet\Calculation\Calculation\internalParseFormula().

3385  {
3386  // Basic validation that this is indeed a formula
3387  // We return an empty array if not
3388  $formula = trim($formula);
3389  if ((!isset($formula[0])) || ($formula[0] != '=')) {
3390  return [];
3391  }
3392  $formula = ltrim(substr($formula, 1));
3393  if (!isset($formula[0])) {
3394  return [];
3395  }
3396 
3397  // Parse the formula and return the token stack
3398  return $this->internalParseFormula($formula);
3399  }
static static static internalParseFormula($formula, ?Cell $pCell=null)
+ Here is the call graph for this function:

◆ processTokenStack()

PhpOffice\PhpSpreadsheet\Calculation\Calculation::processTokenStack (   $tokens,
  $cellID = null,
?Cell  $pCell = null 
)
private
Parameters
mixed$tokens
null | string$cellID
Returns
array<int, mixed>|false

Definition at line 4385 of file Calculation.php.

References $data, $i, $matrix, Sabre\VObject\$output, $result, $row, PHPMailer\PHPMailer\$token, PhpOffice\PhpSpreadsheet\Calculation\Calculation\addCellReference(), PhpOffice\PhpSpreadsheet\Cell\Coordinate\columnIndexFromString(), PhpOffice\PhpSpreadsheet\Cell\Coordinate\coordinateFromString(), PhpOffice\PhpSpreadsheet\Calculation\Calculation\evaluateDefinedName(), PhpOffice\PhpSpreadsheet\Calculation\Calculation\executeBinaryComparisonOperation(), PhpOffice\PhpSpreadsheet\Calculation\Calculation\executeNumericBinaryOperation(), PhpOffice\PhpSpreadsheet\Calculation\Calculation\extractCellRange(), PhpOffice\PhpSpreadsheet\Worksheet\Worksheet\extractSheetTitle(), PhpOffice\PhpSpreadsheet\Calculation\Functions\flattenArray(), PhpOffice\PhpSpreadsheet\Calculation\Functions\flattenSingleValue(), PhpOffice\PhpSpreadsheet\Calculation\Functions\isError(), PhpOffice\PhpSpreadsheet\Calculation\Functions\null(), PhpOffice\PhpSpreadsheet\Calculation\Calculation\raiseFormulaError(), PhpOffice\PhpSpreadsheet\Calculation\Functions\REF(), PhpOffice\PhpSpreadsheet\DefinedName\resolveName(), PhpOffice\PhpSpreadsheet\Calculation\Calculation\showTypeDetails(), PhpOffice\PhpSpreadsheet\Calculation\Calculation\showValue(), and PhpOffice\PhpSpreadsheet\Cell\Coordinate\stringFromColumnIndex().

Referenced by PhpOffice\PhpSpreadsheet\Calculation\Calculation\_calculateFormulaValue().

4386  {
4387  if ($tokens == false) {
4388  return false;
4389  }
4390 
4391  // If we're using cell caching, then $pCell may well be flushed back to the cache (which detaches the parent cell collection),
4392  // so we store the parent cell collection so that we can re-attach it when necessary
4393  $pCellWorksheet = ($pCell !== null) ? $pCell->getWorksheet() : null;
4394  $pCellParent = ($pCell !== null) ? $pCell->getParent() : null;
4395  $stack = new Stack();
4396 
4397  // Stores branches that have been pruned
4398  $fakedForBranchPruning = [];
4399  // help us to know when pruning ['branchTestId' => true/false]
4400  $branchStore = [];
4401  // Loop through each token in turn
4402  foreach ($tokens as $tokenData) {
4403  $token = $tokenData['value'];
4404 
4405  // Branch pruning: skip useless resolutions
4406  $storeKey = $tokenData['storeKey'] ?? null;
4407  if ($this->branchPruningEnabled && isset($tokenData['onlyIf'])) {
4408  $onlyIfStoreKey = $tokenData['onlyIf'];
4409  $storeValue = $branchStore[$onlyIfStoreKey] ?? null;
4410  $storeValueAsBool = ($storeValue === null) ?
4411  true : (bool) Functions::flattenSingleValue($storeValue);
4412  if (is_array($storeValue)) {
4413  $wrappedItem = end($storeValue);
4414  $storeValue = end($wrappedItem);
4415  }
4416 
4417  if (
4418  isset($storeValue)
4419  && (
4420  !$storeValueAsBool
4421  || Functions::isError($storeValue)
4422  || ($storeValue === 'Pruned branch')
4423  )
4424  ) {
4425  // If branching value is not true, we don't need to compute
4426  if (!isset($fakedForBranchPruning['onlyIf-' . $onlyIfStoreKey])) {
4427  $stack->push('Value', 'Pruned branch (only if ' . $onlyIfStoreKey . ') ' . $token);
4428  $fakedForBranchPruning['onlyIf-' . $onlyIfStoreKey] = true;
4429  }
4430 
4431  if (isset($storeKey)) {
4432  // We are processing an if condition
4433  // We cascade the pruning to the depending branches
4434  $branchStore[$storeKey] = 'Pruned branch';
4435  $fakedForBranchPruning['onlyIfNot-' . $storeKey] = true;
4436  $fakedForBranchPruning['onlyIf-' . $storeKey] = true;
4437  }
4438 
4439  continue;
4440  }
4441  }
4442 
4443  if ($this->branchPruningEnabled && isset($tokenData['onlyIfNot'])) {
4444  $onlyIfNotStoreKey = $tokenData['onlyIfNot'];
4445  $storeValue = $branchStore[$onlyIfNotStoreKey] ?? null;
4446  $storeValueAsBool = ($storeValue === null) ?
4447  true : (bool) Functions::flattenSingleValue($storeValue);
4448  if (is_array($storeValue)) {
4449  $wrappedItem = end($storeValue);
4450  $storeValue = end($wrappedItem);
4451  }
4452  if (
4453  isset($storeValue)
4454  && (
4455  $storeValueAsBool
4456  || Functions::isError($storeValue)
4457  || ($storeValue === 'Pruned branch'))
4458  ) {
4459  // If branching value is true, we don't need to compute
4460  if (!isset($fakedForBranchPruning['onlyIfNot-' . $onlyIfNotStoreKey])) {
4461  $stack->push('Value', 'Pruned branch (only if not ' . $onlyIfNotStoreKey . ') ' . $token);
4462  $fakedForBranchPruning['onlyIfNot-' . $onlyIfNotStoreKey] = true;
4463  }
4464 
4465  if (isset($storeKey)) {
4466  // We are processing an if condition
4467  // We cascade the pruning to the depending branches
4468  $branchStore[$storeKey] = 'Pruned branch';
4469  $fakedForBranchPruning['onlyIfNot-' . $storeKey] = true;
4470  $fakedForBranchPruning['onlyIf-' . $storeKey] = true;
4471  }
4472 
4473  continue;
4474  }
4475  }
4476 
4477  // 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
4478  if (isset(self::$binaryOperators[$token])) {
4479  // We must have two operands, error if we don't
4480  if (($operand2Data = $stack->pop()) === null) {
4481  return $this->raiseFormulaError('Internal error - Operand value missing from stack');
4482  }
4483  if (($operand1Data = $stack->pop()) === null) {
4484  return $this->raiseFormulaError('Internal error - Operand value missing from stack');
4485  }
4486 
4487  $operand1 = self::dataTestReference($operand1Data);
4488  $operand2 = self::dataTestReference($operand2Data);
4489 
4490  // Log what we're doing
4491  if ($token == ':') {
4492  $this->debugLog->writeDebugLog('Evaluating Range ', $this->showValue($operand1Data['reference']), ' ', $token, ' ', $this->showValue($operand2Data['reference']));
4493  } else {
4494  $this->debugLog->writeDebugLog('Evaluating ', $this->showValue($operand1), ' ', $token, ' ', $this->showValue($operand2));
4495  }
4496 
4497  // Process the operation in the appropriate manner
4498  switch ($token) {
4499  // Comparison (Boolean) Operators
4500  case '>': // Greater than
4501  case '<': // Less than
4502  case '>=': // Greater than or Equal to
4503  case '<=': // Less than or Equal to
4504  case '=': // Equality
4505  case '<>': // Inequality
4506  $result = $this->executeBinaryComparisonOperation($cellID, $operand1, $operand2, $token, $stack);
4507  if (isset($storeKey)) {
4508  $branchStore[$storeKey] = $result;
4509  }
4510 
4511  break;
4512  // Binary Operators
4513  case ':': // Range
4514  if (strpos($operand1Data['reference'], '!') !== false) {
4515  [$sheet1, $operand1Data['reference']] = Worksheet::extractSheetTitle($operand1Data['reference'], true);
4516  } else {
4517  $sheet1 = ($pCellParent !== null) ? $pCellWorksheet->getTitle() : '';
4518  }
4519 
4520  [$sheet2, $operand2Data['reference']] = Worksheet::extractSheetTitle($operand2Data['reference'], true);
4521  if (empty($sheet2)) {
4522  $sheet2 = $sheet1;
4523  }
4524 
4525  if ($sheet1 == $sheet2) {
4526  if ($operand1Data['reference'] === null) {
4527  if ((trim($operand1Data['value']) != '') && (is_numeric($operand1Data['value']))) {
4528  $operand1Data['reference'] = $pCell->getColumn() . $operand1Data['value'];
4529  } elseif (trim($operand1Data['reference']) == '') {
4530  $operand1Data['reference'] = $pCell->getCoordinate();
4531  } else {
4532  $operand1Data['reference'] = $operand1Data['value'] . $pCell->getRow();
4533  }
4534  }
4535  if ($operand2Data['reference'] === null) {
4536  if ((trim($operand2Data['value']) != '') && (is_numeric($operand2Data['value']))) {
4537  $operand2Data['reference'] = $pCell->getColumn() . $operand2Data['value'];
4538  } elseif (trim($operand2Data['reference']) == '') {
4539  $operand2Data['reference'] = $pCell->getCoordinate();
4540  } else {
4541  $operand2Data['reference'] = $operand2Data['value'] . $pCell->getRow();
4542  }
4543  }
4544 
4545  $oData = array_merge(explode(':', $operand1Data['reference']), explode(':', $operand2Data['reference']));
4546  $oCol = $oRow = [];
4547  foreach ($oData as $oDatum) {
4548  $oCR = Coordinate::coordinateFromString($oDatum);
4549  $oCol[] = Coordinate::columnIndexFromString($oCR[0]) - 1;
4550  $oRow[] = $oCR[1];
4551  }
4552  $cellRef = Coordinate::stringFromColumnIndex(min($oCol) + 1) . min($oRow) . ':' . Coordinate::stringFromColumnIndex(max($oCol) + 1) . max($oRow);
4553  if ($pCellParent !== null) {
4554  $cellValue = $this->extractCellRange($cellRef, $this->spreadsheet->getSheetByName($sheet1), false);
4555  } else {
4556  return $this->raiseFormulaError('Unable to access Cell Reference');
4557  }
4558 
4559  $stack->push('Cell Reference', $cellValue, $cellRef);
4560  } else {
4561  $stack->push('Error', Functions::REF(), null);
4562  }
4563 
4564  break;
4565  case '+': // Addition
4566  $result = $this->executeNumericBinaryOperation($operand1, $operand2, $token, 'plusEquals', $stack);
4567  if (isset($storeKey)) {
4568  $branchStore[$storeKey] = $result;
4569  }
4570 
4571  break;
4572  case '-': // Subtraction
4573  $result = $this->executeNumericBinaryOperation($operand1, $operand2, $token, 'minusEquals', $stack);
4574  if (isset($storeKey)) {
4575  $branchStore[$storeKey] = $result;
4576  }
4577 
4578  break;
4579  case '*': // Multiplication
4580  $result = $this->executeNumericBinaryOperation($operand1, $operand2, $token, 'arrayTimesEquals', $stack);
4581  if (isset($storeKey)) {
4582  $branchStore[$storeKey] = $result;
4583  }
4584 
4585  break;
4586  case '/': // Division
4587  $result = $this->executeNumericBinaryOperation($operand1, $operand2, $token, 'arrayRightDivide', $stack);
4588  if (isset($storeKey)) {
4589  $branchStore[$storeKey] = $result;
4590  }
4591 
4592  break;
4593  case '^': // Exponential
4594  $result = $this->executeNumericBinaryOperation($operand1, $operand2, $token, 'power', $stack);
4595  if (isset($storeKey)) {
4596  $branchStore[$storeKey] = $result;
4597  }
4598 
4599  break;
4600  case '&': // Concatenation
4601  // If either of the operands is a matrix, we need to treat them both as matrices
4602  // (converting the other operand to a matrix if need be); then perform the required
4603  // matrix operation
4604  if (is_bool($operand1)) {
4605  $operand1 = ($operand1) ? self::$localeBoolean['TRUE'] : self::$localeBoolean['FALSE'];
4606  }
4607  if (is_bool($operand2)) {
4608  $operand2 = ($operand2) ? self::$localeBoolean['TRUE'] : self::$localeBoolean['FALSE'];
4609  }
4610  if ((is_array($operand1)) || (is_array($operand2))) {
4611  // Ensure that both operands are arrays/matrices
4612  self::checkMatrixOperands($operand1, $operand2, 2);
4613 
4614  try {
4615  // Convert operand 1 from a PHP array to a matrix
4616  $matrix = new Shared\JAMA\Matrix($operand1);
4617  // Perform the required operation against the operand 1 matrix, passing in operand 2
4618  $matrixResult = $matrix->concat($operand2);
4619  $result = $matrixResult->getArray();
4620  } catch (\Exception $ex) {
4621  $this->debugLog->writeDebugLog('JAMA Matrix Exception: ', $ex->getMessage());
4622  $result = '#VALUE!';
4623  }
4624  } else {
4625  $result = self::FORMULA_STRING_QUOTE . str_replace('""', self::FORMULA_STRING_QUOTE, self::unwrapResult($operand1) . self::unwrapResult($operand2)) . self::FORMULA_STRING_QUOTE;
4626  }
4627  $this->debugLog->writeDebugLog('Evaluation Result is ', $this->showTypeDetails($result));
4628  $stack->push('Value', $result);
4629 
4630  if (isset($storeKey)) {
4631  $branchStore[$storeKey] = $result;
4632  }
4633 
4634  break;
4635  case '|': // Intersect
4636  $rowIntersect = array_intersect_key($operand1, $operand2);
4637  $cellIntersect = $oCol = $oRow = [];
4638  foreach (array_keys($rowIntersect) as $row) {
4639  $oRow[] = $row;
4640  foreach ($rowIntersect[$row] as $col => $data) {
4641  $oCol[] = Coordinate::columnIndexFromString($col) - 1;
4642  $cellIntersect[$row] = array_intersect_key($operand1[$row], $operand2[$row]);
4643  }
4644  }
4645  if (count(Functions::flattenArray($cellIntersect)) === 0) {
4646  $this->debugLog->writeDebugLog('Evaluation Result is ', $this->showTypeDetails($cellIntersect));
4647  $stack->push('Error', Functions::null(), null);
4648  } else {
4649  $cellRef = Coordinate::stringFromColumnIndex(min($oCol) + 1) . min($oRow) . ':' .
4650  Coordinate::stringFromColumnIndex(max($oCol) + 1) . max($oRow);
4651  $this->debugLog->writeDebugLog('Evaluation Result is ', $this->showTypeDetails($cellIntersect));
4652  $stack->push('Value', $cellIntersect, $cellRef);
4653  }
4654 
4655  break;
4656  }
4657 
4658  // if the token is a unary operator, pop one value off the stack, do the operation, and push it back on
4659  } elseif (($token === '~') || ($token === '%')) {
4660  if (($arg = $stack->pop()) === null) {
4661  return $this->raiseFormulaError('Internal error - Operand value missing from stack');
4662  }
4663  $arg = $arg['value'];
4664  if ($token === '~') {
4665  $this->debugLog->writeDebugLog('Evaluating Negation of ', $this->showValue($arg));
4666  $multiplier = -1;
4667  } else {
4668  $this->debugLog->writeDebugLog('Evaluating Percentile of ', $this->showValue($arg));
4669  $multiplier = 0.01;
4670  }
4671  if (is_array($arg)) {
4672  self::checkMatrixOperands($arg, $multiplier, 2);
4673 
4674  try {
4675  $matrix1 = new Shared\JAMA\Matrix($arg);
4676  $matrixResult = $matrix1->arrayTimesEquals($multiplier);
4677  $result = $matrixResult->getArray();
4678  } catch (\Exception $ex) {
4679  $this->debugLog->writeDebugLog('JAMA Matrix Exception: ', $ex->getMessage());
4680  $result = '#VALUE!';
4681  }
4682  $this->debugLog->writeDebugLog('Evaluation Result is ', $this->showTypeDetails($result));
4683  $stack->push('Value', $result);
4684  if (isset($storeKey)) {
4685  $branchStore[$storeKey] = $result;
4686  }
4687  } else {
4688  $this->executeNumericBinaryOperation($multiplier, $arg, '*', 'arrayTimesEquals', $stack);
4689  }
4690  } elseif (preg_match('/^' . self::CALCULATION_REGEXP_CELLREF . '$/i', $token ?? '', $matches)) {
4691  $cellRef = null;
4692 
4693  if (isset($matches[8])) {
4694  if ($pCell === null) {
4695  // We can't access the range, so return a REF error
4696  $cellValue = Functions::REF();
4697  } else {
4698  $cellRef = $matches[6] . $matches[7] . ':' . $matches[9] . $matches[10];
4699  if ($matches[2] > '') {
4700  $matches[2] = trim($matches[2], "\"'");
4701  if ((strpos($matches[2], '[') !== false) || (strpos($matches[2], ']') !== false)) {
4702  // It's a Reference to an external spreadsheet (not currently supported)
4703  return $this->raiseFormulaError('Unable to access External Workbook');
4704  }
4705  $matches[2] = trim($matches[2], "\"'");
4706  $this->debugLog->writeDebugLog('Evaluating Cell Range ', $cellRef, ' in worksheet ', $matches[2]);
4707  if ($pCellParent !== null) {
4708  $cellValue = $this->extractCellRange($cellRef, $this->spreadsheet->getSheetByName($matches[2]), false);
4709  } else {
4710  return $this->raiseFormulaError('Unable to access Cell Reference');
4711  }
4712  $this->debugLog->writeDebugLog('Evaluation Result for cells ', $cellRef, ' in worksheet ', $matches[2], ' is ', $this->showTypeDetails($cellValue));
4713  } else {
4714  $this->debugLog->writeDebugLog('Evaluating Cell Range ', $cellRef, ' in current worksheet');
4715  if ($pCellParent !== null) {
4716  $cellValue = $this->extractCellRange($cellRef, $pCellWorksheet, false);
4717  } else {
4718  return $this->raiseFormulaError('Unable to access Cell Reference');
4719  }
4720  $this->debugLog->writeDebugLog('Evaluation Result for cells ', $cellRef, ' is ', $this->showTypeDetails($cellValue));
4721  }
4722  }
4723  } else {
4724  if ($pCell === null) {
4725  // We can't access the cell, so return a REF error
4726  $cellValue = Functions::REF();
4727  } else {
4728  $cellRef = $matches[6] . $matches[7];
4729  if ($matches[2] > '') {
4730  $matches[2] = trim($matches[2], "\"'");
4731  if ((strpos($matches[2], '[') !== false) || (strpos($matches[2], ']') !== false)) {
4732  // It's a Reference to an external spreadsheet (not currently supported)
4733  return $this->raiseFormulaError('Unable to access External Workbook');
4734  }
4735  $this->debugLog->writeDebugLog('Evaluating Cell ', $cellRef, ' in worksheet ', $matches[2]);
4736  if ($pCellParent !== null) {
4737  $cellSheet = $this->spreadsheet->getSheetByName($matches[2]);
4738  if ($cellSheet && $cellSheet->cellExists($cellRef)) {
4739  $cellValue = $this->extractCellRange($cellRef, $this->spreadsheet->getSheetByName($matches[2]), false);
4740  $pCell->attach($pCellParent);
4741  } else {
4742  $cellRef = ($cellSheet !== null) ? "{$matches[2]}!{$cellRef}" : $cellRef;
4743  $cellValue = null;
4744  }
4745  } else {
4746  return $this->raiseFormulaError('Unable to access Cell Reference');
4747  }
4748  $this->debugLog->writeDebugLog('Evaluation Result for cell ', $cellRef, ' in worksheet ', $matches[2], ' is ', $this->showTypeDetails($cellValue));
4749  } else {
4750  $this->debugLog->writeDebugLog('Evaluating Cell ', $cellRef, ' in current worksheet');
4751  if ($pCellParent->has($cellRef)) {
4752  $cellValue = $this->extractCellRange($cellRef, $pCellWorksheet, false);
4753  $pCell->attach($pCellParent);
4754  } else {
4755  $cellValue = null;
4756  }
4757  $this->debugLog->writeDebugLog('Evaluation Result for cell ', $cellRef, ' is ', $this->showTypeDetails($cellValue));
4758  }
4759  }
4760  }
4761 
4762  $stack->push('Cell Value', $cellValue, $cellRef);
4763  if (isset($storeKey)) {
4764  $branchStore[$storeKey] = $cellValue;
4765  }
4766 
4767  // if the token is a function, pop arguments off the stack, hand them to the function, and push the result back on
4768  } elseif (preg_match('/^' . self::CALCULATION_REGEXP_FUNCTION . '$/miu', $token ?? '', $matches)) {
4769  if ($pCellParent) {
4770  $pCell->attach($pCellParent);
4771  }
4772 
4773  $functionName = $matches[1];
4774  $argCount = $stack->pop();
4775  $argCount = $argCount['value'];
4776  if ($functionName != 'MKMATRIX') {
4777  $this->debugLog->writeDebugLog('Evaluating Function ', self::localeFunc($functionName), '() with ', (($argCount == 0) ? 'no' : $argCount), ' argument', (($argCount == 1) ? '' : 's'));
4778  }
4779  if ((isset(self::$phpSpreadsheetFunctions[$functionName])) || (isset(self::$controlFunctions[$functionName]))) { // function
4780  $passByReference = false;
4781  $passCellReference = false;
4782  $functionCall = null;
4783  if (isset(self::$phpSpreadsheetFunctions[$functionName])) {
4784  $functionCall = self::$phpSpreadsheetFunctions[$functionName]['functionCall'];
4785  $passByReference = isset(self::$phpSpreadsheetFunctions[$functionName]['passByReference']);
4786  $passCellReference = isset(self::$phpSpreadsheetFunctions[$functionName]['passCellReference']);
4787  } elseif (isset(self::$controlFunctions[$functionName])) {
4788  $functionCall = self::$controlFunctions[$functionName]['functionCall'];
4789  $passByReference = isset(self::$controlFunctions[$functionName]['passByReference']);
4790  $passCellReference = isset(self::$controlFunctions[$functionName]['passCellReference']);
4791  }
4792  // get the arguments for this function
4793  $args = $argArrayVals = [];
4794  for ($i = 0; $i < $argCount; ++$i) {
4795  $arg = $stack->pop();
4796  $a = $argCount - $i - 1;
4797  if (
4798  ($passByReference) &&
4799  (isset(self::$phpSpreadsheetFunctions[$functionName]['passByReference'][$a])) &&
4800  (self::$phpSpreadsheetFunctions[$functionName]['passByReference'][$a])
4801  ) {
4802  if ($arg['reference'] === null) {
4803  $args[] = $cellID;
4804  if ($functionName != 'MKMATRIX') {
4805  $argArrayVals[] = $this->showValue($cellID);
4806  }
4807  } else {
4808  $args[] = $arg['reference'];
4809  if ($functionName != 'MKMATRIX') {
4810  $argArrayVals[] = $this->showValue($arg['reference']);
4811  }
4812  }
4813  } else {
4814  $args[] = self::unwrapResult($arg['value']);
4815  if ($functionName != 'MKMATRIX') {
4816  $argArrayVals[] = $this->showValue($arg['value']);
4817  }
4818  }
4819  }
4820 
4821  // Reverse the order of the arguments
4822  krsort($args);
4823 
4824  if (($passByReference) && ($argCount == 0)) {
4825  $args[] = $cellID;
4826  $argArrayVals[] = $this->showValue($cellID);
4827  }
4828 
4829  if ($functionName != 'MKMATRIX') {
4830  if ($this->debugLog->getWriteDebugLog()) {
4831  krsort($argArrayVals);
4832  $this->debugLog->writeDebugLog('Evaluating ', self::localeFunc($functionName), '( ', implode(self::$localeArgumentSeparator . ' ', Functions::flattenArray($argArrayVals)), ' )');
4833  }
4834  }
4835 
4836  // Process the argument with the appropriate function call
4837  $args = $this->addCellReference($args, $passCellReference, $functionCall, $pCell);
4838 
4839  if (!is_array($functionCall)) {
4840  foreach ($args as &$arg) {
4841  $arg = Functions::flattenSingleValue($arg);
4842  }
4843  unset($arg);
4844  }
4845 
4846  $result = call_user_func_array($functionCall, $args);
4847 
4848  if ($functionName != 'MKMATRIX') {
4849  $this->debugLog->writeDebugLog('Evaluation Result for ', self::localeFunc($functionName), '() function call is ', $this->showTypeDetails($result));
4850  }
4851  $stack->push('Value', self::wrapResult($result));
4852  if (isset($storeKey)) {
4853  $branchStore[$storeKey] = $result;
4854  }
4855  }
4856  } else {
4857  // if the token is a number, boolean, string or an Excel error, push it onto the stack
4858  if (isset(self::$excelConstants[strtoupper($token ?? '')])) {
4859  $excelConstant = strtoupper($token);
4860  $stack->push('Constant Value', self::$excelConstants[$excelConstant]);
4861  if (isset($storeKey)) {
4862  $branchStore[$storeKey] = self::$excelConstants[$excelConstant];
4863  }
4864  $this->debugLog->writeDebugLog('Evaluating Constant ', $excelConstant, ' as ', $this->showTypeDetails(self::$excelConstants[$excelConstant]));
4865  } elseif ((is_numeric($token)) || ($token === null) || (is_bool($token)) || ($token == '') || ($token[0] == self::FORMULA_STRING_QUOTE) || ($token[0] == '#')) {
4866  $stack->push('Value', $token);
4867  if (isset($storeKey)) {
4868  $branchStore[$storeKey] = $token;
4869  }
4870  // if the token is a named range or formula, evaluate it and push the result onto the stack
4871  } elseif (preg_match('/^' . self::CALCULATION_REGEXP_DEFINEDNAME . '$/miu', $token, $matches)) {
4872  $definedName = $matches[6];
4873  if ($pCell === null || $pCellWorksheet === null) {
4874  return $this->raiseFormulaError("undefined name '$token'");
4875  }
4876 
4877  $this->debugLog->writeDebugLog('Evaluating Defined Name ', $definedName);
4878  $namedRange = DefinedName::resolveName($definedName, $pCellWorksheet);
4879  if ($namedRange === null) {
4880  return $this->raiseFormulaError("undefined name '$definedName'");
4881  }
4882 
4883  $result = $this->evaluateDefinedName($pCell, $namedRange, $pCellWorksheet, $stack);
4884  if (isset($storeKey)) {
4885  $branchStore[$storeKey] = $result;
4886  }
4887  } else {
4888  return $this->raiseFormulaError("undefined name '$token'");
4889  }
4890  }
4891  }
4892  // when we're out of tokens, the stack should have a single element, the final result
4893  if ($stack->count() != 1) {
4894  return $this->raiseFormulaError('internal error');
4895  }
4896  $output = $stack->pop();
4897  $output = $output['value'];
4898 
4899  return $output;
4900  }
$result
static extractSheetTitle($pRange, $returnRange=false)
Extract worksheet title from range.
Definition: Worksheet.php:2719
showValue($value)
Format details of an operand for display in the log (based on operand type).
executeBinaryComparisonOperation($cellID, $operand1, $operand2, $operation, Stack &$stack, $recursingArrays=false)
static flattenArray($array)
Convert a multi-dimensional array to a simple 1-dimensional array.
Definition: Functions.php:583
executeNumericBinaryOperation($operand1, $operand2, $operation, $matrixFunction, &$stack)
addCellReference(array $args, $passCellReference, $functionCall, ?Cell $pCell=null)
Add cell reference if needed while making sure that it is the last argument.
static resolveName(string $pDefinedName, Worksheet $pSheet, string $sheetName='')
Resolve a named range to a regular cell range or formula.
extractCellRange(&$pRange='A1', ?Worksheet $pSheet=null, $resetLog=true)
Extract range values.
$matrix
Definition: test.php:18
$row
static coordinateFromString($pCoordinateString)
Coordinate from string.
Definition: Coordinate.php:32
evaluateDefinedName(Cell $pCell, DefinedName $namedRange, Worksheet $pCellWorksheet, Stack $stack)
$i
Definition: disco.tpl.php:19
static flattenSingleValue($value='')
Convert an array to a single scalar value by extracting the first element.
Definition: Functions.php:649
showTypeDetails($value)
Format type and details of an operand for display in the log (based on operand type).
static columnIndexFromString($pString)
Column index from string.
Definition: Coordinate.php:265
static stringFromColumnIndex($columnIndex)
String from column index.
Definition: Coordinate.php:313
$data
Definition: bench.php:6
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ raiseFormulaError()

PhpOffice\PhpSpreadsheet\Calculation\Calculation::raiseFormulaError (   $errorMessage)
protected

Definition at line 5219 of file Calculation.php.

Referenced by PhpOffice\PhpSpreadsheet\Calculation\Calculation\_calculateFormulaValue(), PhpOffice\PhpSpreadsheet\Calculation\Calculation\convertMatrixReferences(), PhpOffice\PhpSpreadsheet\Calculation\Calculation\internalParseFormula(), and PhpOffice\PhpSpreadsheet\Calculation\Calculation\processTokenStack().

5220  {
5221  $this->formulaError = $errorMessage;
5222  $this->cyclicReferenceStack->clear();
5223  if (!$this->suppressFormulaErrors) {
5224  throw new Exception($errorMessage);
5225  }
5226  trigger_error($errorMessage, E_USER_ERROR);
5227 
5228  return false;
5229  }
+ Here is the caller graph for this function:

◆ renameCalculationCacheForWorksheet()

PhpOffice\PhpSpreadsheet\Calculation\Calculation::renameCalculationCacheForWorksheet (   $fromWorksheetName,
  $toWorksheetName 
)

Rename calculation cache for a specified worksheet.

Parameters
string$fromWorksheetName
string$toWorksheetName

Definition at line 2944 of file Calculation.php.

2944  : void
2945  {
2946  if (isset($this->calculationCache[$fromWorksheetName])) {
2947  $this->calculationCache[$toWorksheetName] = &$this->calculationCache[$fromWorksheetName];
2948  unset($this->calculationCache[$fromWorksheetName]);
2949  }
2950  }

◆ resizeMatricesExtend()

static PhpOffice\PhpSpreadsheet\Calculation\Calculation::resizeMatricesExtend ( $matrix1,
$matrix2,
  $matrix1Rows,
  $matrix1Columns,
  $matrix2Rows,
  $matrix2Columns 
)
staticprivate

Ensure that paired matrix operands are both matrices of the same size.

Parameters
mixed$matrix1First matrix operand
mixed$matrix2Second matrix operand
int$matrix1RowsRow size of first matrix operand
int$matrix1ColumnsColumn size of first matrix operand
int$matrix2RowsRow size of second matrix operand
int$matrix2ColumnsColumn size of second matrix operand

Definition at line 3674 of file Calculation.php.

References $i, and $x.

3674  : void
3675  {
3676  if (($matrix2Columns < $matrix1Columns) || ($matrix2Rows < $matrix1Rows)) {
3677  if ($matrix2Columns < $matrix1Columns) {
3678  for ($i = 0; $i < $matrix2Rows; ++$i) {
3679  $x = $matrix2[$i][$matrix2Columns - 1];
3680  for ($j = $matrix2Columns; $j < $matrix1Columns; ++$j) {
3681  $matrix2[$i][$j] = $x;
3682  }
3683  }
3684  }
3685  if ($matrix2Rows < $matrix1Rows) {
3686  $x = $matrix2[$matrix2Rows - 1];
3687  for ($i = 0; $i < $matrix1Rows; ++$i) {
3688  $matrix2[$i] = $x;
3689  }
3690  }
3691  }
3692 
3693  if (($matrix1Columns < $matrix2Columns) || ($matrix1Rows < $matrix2Rows)) {
3694  if ($matrix1Columns < $matrix2Columns) {
3695  for ($i = 0; $i < $matrix1Rows; ++$i) {
3696  $x = $matrix1[$i][$matrix1Columns - 1];
3697  for ($j = $matrix1Columns; $j < $matrix2Columns; ++$j) {
3698  $matrix1[$i][$j] = $x;
3699  }
3700  }
3701  }
3702  if ($matrix1Rows < $matrix2Rows) {
3703  $x = $matrix1[$matrix1Rows - 1];
3704  for ($i = 0; $i < $matrix2Rows; ++$i) {
3705  $matrix1[$i] = $x;
3706  }
3707  }
3708  }
3709  }
$i
Definition: disco.tpl.php:19
$x
Definition: complexTest.php:9

◆ resizeMatricesShrink()

static PhpOffice\PhpSpreadsheet\Calculation\Calculation::resizeMatricesShrink ( $matrix1,
$matrix2,
  $matrix1Rows,
  $matrix1Columns,
  $matrix2Rows,
  $matrix2Columns 
)
staticprivate

Ensure that paired matrix operands are both matrices of the same size.

Parameters
mixed$matrix1First matrix operand
mixed$matrix2Second matrix operand
int$matrix1RowsRow size of first matrix operand
int$matrix1ColumnsColumn size of first matrix operand
int$matrix2RowsRow size of second matrix operand
int$matrix2ColumnsColumn size of second matrix operand

Definition at line 3631 of file Calculation.php.

References $i.

3631  : void
3632  {
3633  if (($matrix2Columns < $matrix1Columns) || ($matrix2Rows < $matrix1Rows)) {
3634  if ($matrix2Rows < $matrix1Rows) {
3635  for ($i = $matrix2Rows; $i < $matrix1Rows; ++$i) {
3636  unset($matrix1[$i]);
3637  }
3638  }
3639  if ($matrix2Columns < $matrix1Columns) {
3640  for ($i = 0; $i < $matrix1Rows; ++$i) {
3641  for ($j = $matrix2Columns; $j < $matrix1Columns; ++$j) {
3642  unset($matrix1[$i][$j]);
3643  }
3644  }
3645  }
3646  }
3647 
3648  if (($matrix1Columns < $matrix2Columns) || ($matrix1Rows < $matrix2Rows)) {
3649  if ($matrix1Rows < $matrix2Rows) {
3650  for ($i = $matrix1Rows; $i < $matrix2Rows; ++$i) {
3651  unset($matrix2[$i]);
3652  }
3653  }
3654  if ($matrix1Columns < $matrix2Columns) {
3655  for ($i = 0; $i < $matrix2Rows; ++$i) {
3656  for ($j = $matrix1Columns; $j < $matrix2Columns; ++$j) {
3657  unset($matrix2[$i][$j]);
3658  }
3659  }
3660  }
3661  }
3662  }
$i
Definition: disco.tpl.php:19

◆ saveValueToCache()

PhpOffice\PhpSpreadsheet\Calculation\Calculation::saveValueToCache (   $cellReference,
  $cellValue 
)
Parameters
string$cellReference
mixed$cellValue

Definition at line 3466 of file Calculation.php.

Referenced by PhpOffice\PhpSpreadsheet\Calculation\Calculation\_calculateFormulaValue().

3466  : void
3467  {
3468  if ($this->calculationCacheEnabled) {
3469  $this->calculationCache[$cellReference] = $cellValue;
3470  }
3471  }
+ Here is the caller graph for this function:

◆ setArrayReturnType()

static PhpOffice\PhpSpreadsheet\Calculation\Calculation::setArrayReturnType (   $returnType)
static

Set the Array Return Type (Array or Value of first element in the array).

Parameters
string$returnTypeArray return type
Returns
bool Success or failure

Definition at line 2856 of file Calculation.php.

Referenced by PhpOffice\PhpSpreadsheet\Writer\Html\generateHtmlAll(), and PhpOffice\PhpSpreadsheet\Writer\Csv\save().

2857  {
2858  if (
2859  ($returnType == self::RETURN_ARRAY_AS_VALUE) ||
2860  ($returnType == self::RETURN_ARRAY_AS_ERROR) ||
2861  ($returnType == self::RETURN_ARRAY_AS_ARRAY)
2862  ) {
2863  self::$returnArrayAsType = $returnType;
2864 
2865  return true;
2866  }
2867 
2868  return false;
2869  }
+ Here is the caller graph for this function:

◆ setBranchPruningEnabled()

PhpOffice\PhpSpreadsheet\Calculation\Calculation::setBranchPruningEnabled (   $enabled)

Enable/disable calculation cache.

Parameters
mixed$enabled

Definition at line 2957 of file Calculation.php.

Referenced by PhpOffice\PhpSpreadsheet\Calculation\Calculation\disableBranchPruning(), and PhpOffice\PhpSpreadsheet\Calculation\Calculation\enableBranchPruning().

2957  : void
2958  {
2959  $this->branchPruningEnabled = $enabled;
2960  }
+ Here is the caller graph for this function:

◆ setCalculationCacheEnabled()

PhpOffice\PhpSpreadsheet\Calculation\Calculation::setCalculationCacheEnabled (   $pValue)

Enable/disable calculation cache.

Parameters
bool$pValue

Definition at line 2896 of file Calculation.php.

References PhpOffice\PhpSpreadsheet\Calculation\Calculation\clearCalculationCache().

Referenced by PhpOffice\PhpSpreadsheet\Calculation\Calculation\disableCalculationCache(), and PhpOffice\PhpSpreadsheet\Calculation\Calculation\enableCalculationCache().

2896  : void
2897  {
2898  $this->calculationCacheEnabled = $pValue;
2899  $this->clearCalculationCache();
2900  }
clearCalculationCache()
Clear calculation cache.
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ setLocale()

PhpOffice\PhpSpreadsheet\Calculation\Calculation::setLocale ( string  $locale)

Set the locale code.

Parameters
string$localeThe locale to use for formula translation, eg: 'en_us'
Returns
bool

Definition at line 3009 of file Calculation.php.

References $language, PhpOffice\PhpSpreadsheet\Calculation\Calculation\$localeFunctions, and PhpOffice\PhpSpreadsheet\Calculation\Calculation\getLocaleFile().

3010  {
3011  // Identify our locale and language
3012  $language = $locale = strtolower($locale);
3013  if (strpos($locale, '_') !== false) {
3014  [$language] = explode('_', $locale);
3015  }
3016  if (count(self::$validLocaleLanguages) == 1) {
3017  self::loadLocales();
3018  }
3019 
3020  // Test whether we have any language data for this language (any locale)
3021  if (in_array($language, self::$validLocaleLanguages)) {
3022  // initialise language/locale settings
3023  self::$localeFunctions = [];
3024  self::$localeArgumentSeparator = ',';
3025  self::$localeBoolean = ['TRUE' => 'TRUE', 'FALSE' => 'FALSE', 'NULL' => 'NULL'];
3026 
3027  // Default is US English, if user isn't requesting US english, then read the necessary data from the locale files
3028  if ($locale !== 'en_us') {
3029  $localeDir = implode(DIRECTORY_SEPARATOR, [__DIR__, 'locale', null]);
3030  // Search for a file with a list of function names for locale
3031  try {
3032  $functionNamesFile = $this->getLocaleFile($localeDir, $locale, $language, 'functions');
3033  } catch (Exception $e) {
3034  return false;
3035  }
3036 
3037  // Retrieve the list of locale or language specific function names
3038  $localeFunctions = file($functionNamesFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
3039  foreach ($localeFunctions as $localeFunction) {
3040  [$localeFunction] = explode('##', $localeFunction); // Strip out comments
3041  if (strpos($localeFunction, '=') !== false) {
3042  [$fName, $lfName] = array_map('trim', explode('=', $localeFunction));
3043  if ((isset(self::$phpSpreadsheetFunctions[$fName])) && ($lfName != '') && ($fName != $lfName)) {
3044  self::$localeFunctions[$fName] = $lfName;
3045  }
3046  }
3047  }
3048  // Default the TRUE and FALSE constants to the locale names of the TRUE() and FALSE() functions
3049  if (isset(self::$localeFunctions['TRUE'])) {
3050  self::$localeBoolean['TRUE'] = self::$localeFunctions['TRUE'];
3051  }
3052  if (isset(self::$localeFunctions['FALSE'])) {
3053  self::$localeBoolean['FALSE'] = self::$localeFunctions['FALSE'];
3054  }
3055 
3056  try {
3057  $configFile = $this->getLocaleFile($localeDir, $locale, $language, 'config');
3058  } catch (Exception $e) {
3059  return false;
3060  }
3061 
3062  $localeSettings = file($configFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
3063  foreach ($localeSettings as $localeSetting) {
3064  [$localeSetting] = explode('##', $localeSetting); // Strip out comments
3065  if (strpos($localeSetting, '=') !== false) {
3066  [$settingName, $settingValue] = array_map('trim', explode('=', $localeSetting));
3067  $settingName = strtoupper($settingName);
3068  if ($settingValue !== '') {
3069  switch ($settingName) {
3070  case 'ARGUMENTSEPARATOR':
3071  self::$localeArgumentSeparator = $settingValue;
3072 
3073  break;
3074  }
3075  }
3076  }
3077  }
3078  }
3079 
3080  self::$functionReplaceFromExcel = self::$functionReplaceToExcel =
3081  self::$functionReplaceFromLocale = self::$functionReplaceToLocale = null;
3082  self::$localeLanguage = $locale;
3083 
3084  return true;
3085  }
3086 
3087  return false;
3088  }
getLocaleFile(string $localeDir, string $locale, string $language, string $file)
+ Here is the call graph for this function:

◆ showTypeDetails()

PhpOffice\PhpSpreadsheet\Calculation\Calculation::showTypeDetails (   $value)
private

Format type and details of an operand for display in the log (based on operand type).

Parameters
mixed$valueFirst matrix operand
Returns
null|string

Definition at line 3756 of file Calculation.php.

References PhpOffice\PhpSpreadsheet\Calculation\Functions\flattenArray(), and PhpOffice\PhpSpreadsheet\Calculation\Calculation\showValue().

Referenced by PhpOffice\PhpSpreadsheet\Calculation\Calculation\executeArrayComparison(), PhpOffice\PhpSpreadsheet\Calculation\Calculation\executeBinaryComparisonOperation(), PhpOffice\PhpSpreadsheet\Calculation\Calculation\executeNumericBinaryOperation(), PhpOffice\PhpSpreadsheet\Calculation\Calculation\processTokenStack(), and PhpOffice\PhpSpreadsheet\Calculation\Calculation\validateBinaryOperand().

3757  {
3758  if ($this->debugLog->getWriteDebugLog()) {
3759  $testArray = Functions::flattenArray($value);
3760  if (count($testArray) == 1) {
3761  $value = array_pop($testArray);
3762  }
3763 
3764  if ($value === null) {
3765  return 'a NULL value';
3766  } elseif (is_float($value)) {
3767  $typeString = 'a floating point number';
3768  } elseif (is_int($value)) {
3769  $typeString = 'an integer number';
3770  } elseif (is_bool($value)) {
3771  $typeString = 'a boolean';
3772  } elseif (is_array($value)) {
3773  $typeString = 'a matrix';
3774  } else {
3775  if ($value == '') {
3776  return 'an empty string';
3777  } elseif ($value[0] == '#') {
3778  return 'a ' . $value . ' error';
3779  }
3780  $typeString = 'a string';
3781  }
3782 
3783  return $typeString . ' with a value of ' . $this->showValue($value);
3784  }
3785 
3786  return null;
3787  }
showValue($value)
Format details of an operand for display in the log (based on operand type).
static flattenArray($array)
Convert a multi-dimensional array to a simple 1-dimensional array.
Definition: Functions.php:583
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ showValue()

PhpOffice\PhpSpreadsheet\Calculation\Calculation::showValue (   $value)
private

Format details of an operand for display in the log (based on operand type).

Parameters
mixed$valueFirst matrix operand
Returns
mixed

Definition at line 3718 of file Calculation.php.

References $row, PhpOffice\PhpSpreadsheet\Calculation\Functions\flattenArray(), and PhpOffice\PhpSpreadsheet\Calculation\Functions\flattenSingleValue().

Referenced by PhpOffice\PhpSpreadsheet\Calculation\Calculation\executeArrayComparison(), PhpOffice\PhpSpreadsheet\Calculation\Calculation\processTokenStack(), and PhpOffice\PhpSpreadsheet\Calculation\Calculation\showTypeDetails().

3719  {
3720  if ($this->debugLog->getWriteDebugLog()) {
3721  $testArray = Functions::flattenArray($value);
3722  if (count($testArray) == 1) {
3723  $value = array_pop($testArray);
3724  }
3725 
3726  if (is_array($value)) {
3727  $returnMatrix = [];
3728  $pad = $rpad = ', ';
3729  foreach ($value as $row) {
3730  if (is_array($row)) {
3731  $returnMatrix[] = implode($pad, array_map([$this, 'showValue'], $row));
3732  $rpad = '; ';
3733  } else {
3734  $returnMatrix[] = $this->showValue($row);
3735  }
3736  }
3737 
3738  return '{ ' . implode($rpad, $returnMatrix) . ' }';
3739  } elseif (is_string($value) && (trim($value, self::FORMULA_STRING_QUOTE) == $value)) {
3740  return self::FORMULA_STRING_QUOTE . $value . self::FORMULA_STRING_QUOTE;
3741  } elseif (is_bool($value)) {
3742  return ($value) ? self::$localeBoolean['TRUE'] : self::$localeBoolean['FALSE'];
3743  }
3744  }
3745 
3746  return Functions::flattenSingleValue($value);
3747  }
showValue($value)
Format details of an operand for display in the log (based on operand type).
static flattenArray($array)
Convert a multi-dimensional array to a simple 1-dimensional array.
Definition: Functions.php:583
$row
static flattenSingleValue($value='')
Convert an array to a single scalar value by extracting the first element.
Definition: Functions.php:649
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ strcmpAllowNull()

PhpOffice\PhpSpreadsheet\Calculation\Calculation::strcmpAllowNull (   $str1,
  $str2 
)
private

PHP8.1 deprecates passing null to strcmp.

Parameters
null | string$str1First string value for the comparison
null | string$str2Second string value for the comparison
Returns
int

Definition at line 5120 of file Calculation.php.

Referenced by PhpOffice\PhpSpreadsheet\Calculation\Calculation\executeBinaryComparisonOperation().

5121  {
5122  return strcmp($str1 ?? '', $str2 ?? '');
5123  }
+ Here is the caller graph for this function:

◆ strcmpLowercaseFirst()

PhpOffice\PhpSpreadsheet\Calculation\Calculation::strcmpLowercaseFirst (   $str1,
  $str2 
)
private

Compare two strings in the same way as strcmp() except that lowercase come before uppercase letters.

Parameters
null | string$str1First string value for the comparison
null | string$str2Second string value for the comparison
Returns
int

Definition at line 5104 of file Calculation.php.

References PhpOffice\PhpSpreadsheet\Shared\StringHelper\strCaseReverse().

Referenced by PhpOffice\PhpSpreadsheet\Calculation\Calculation\executeBinaryComparisonOperation().

5105  {
5106  $inversedStr1 = Shared\StringHelper::strCaseReverse($str1);
5107  $inversedStr2 = Shared\StringHelper::strCaseReverse($str2);
5108 
5109  return strcmp($inversedStr1 ?? '', $inversedStr2 ?? '');
5110  }
static strCaseReverse($pValue)
Reverse the case of a string, so that all uppercase characters become lowercase and all lowercase cha...
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ translateFormula()

static PhpOffice\PhpSpreadsheet\Calculation\Calculation::translateFormula ( array  $from,
array  $to,
  $formula,
  $fromSeparator,
  $toSeparator 
)
staticprivate
Parameters
string[]$from
string[]$to
string$formula
string$fromSeparator
string$toSeparator
Returns
string

Definition at line 3131 of file Calculation.php.

References $i.

3132  {
3133  // Convert any Excel function names to the required language
3134  if (self::$localeLanguage !== 'en_us') {
3135  $inBraces = false;
3136  // If there is the possibility of braces within a quoted string, then we don't treat those as matrix indicators
3137  if (strpos($formula, self::FORMULA_STRING_QUOTE) !== false) {
3138  // So instead we skip replacing in any quoted strings by only replacing in every other array element after we've exploded
3139  // the formula
3140  $temp = explode(self::FORMULA_STRING_QUOTE, $formula);
3141  $i = false;
3142  foreach ($temp as &$value) {
3143  // Only count/replace in alternating array entries
3144  if ($i = !$i) {
3145  $value = preg_replace($from, $to, $value);
3146  $value = self::translateSeparator($fromSeparator, $toSeparator, $value, $inBraces);
3147  }
3148  }
3149  unset($value);
3150  // Then rebuild the formula string
3151  $formula = implode(self::FORMULA_STRING_QUOTE, $temp);
3152  } else {
3153  // If there's no quoted strings, then we do a simple count/replace
3154  $formula = preg_replace($from, $to, $formula);
3155  $formula = self::translateSeparator($fromSeparator, $toSeparator, $formula, $inBraces);
3156  }
3157  }
3158 
3159  return $formula;
3160  }
$from
$i
Definition: disco.tpl.php:19

◆ translateSeparator()

static PhpOffice\PhpSpreadsheet\Calculation\Calculation::translateSeparator (   $fromSeparator,
  $toSeparator,
  $formula,
$inBraces 
)
static
Parameters
string$fromSeparator
string$toSeparator
string$formula
bool$inBraces
Returns
string

Definition at line 3098 of file Calculation.php.

References $i.

Referenced by PhpOffice\PhpSpreadsheet\Reader\Ods\BaseReader\convertToExcelFormulaValue(), and PhpOffice\PhpSpreadsheet\Reader\Ods\convertToExcelFormulaValue().

3099  {
3100  $strlen = mb_strlen($formula);
3101  for ($i = 0; $i < $strlen; ++$i) {
3102  $chr = mb_substr($formula, $i, 1);
3103  switch ($chr) {
3104  case self::FORMULA_OPEN_FUNCTION_BRACE:
3105  $inBraces = true;
3106 
3107  break;
3108  case self::FORMULA_CLOSE_FUNCTION_BRACE:
3109  $inBraces = false;
3110 
3111  break;
3112  case $fromSeparator:
3113  if (!$inBraces) {
3114  $formula = mb_substr($formula, 0, $i) . $toSeparator . mb_substr($formula, $i + 1);
3115  }
3116  }
3117  }
3118 
3119  return $formula;
3120  }
$i
Definition: disco.tpl.php:19
+ Here is the caller graph for this function:

◆ unwrapResult()

static PhpOffice\PhpSpreadsheet\Calculation\Calculation::unwrapResult (   $value)
static

Remove quotes used as a wrapper to identify string values.

Parameters
mixed$value
Returns
mixed

Definition at line 3269 of file Calculation.php.

References PhpOffice\PhpSpreadsheet\Calculation\Functions\NAN().

Referenced by PhpOffice\PhpSpreadsheet\Reader\Slk\processCFinal(), PhpOffice\PhpSpreadsheet\Reader\Slk\processCRecord(), and PhpOffice\PhpSpreadsheet\Chart\DataSeriesValues\refresh().

3270  {
3271  if (is_string($value)) {
3272  if ((isset($value[0])) && ($value[0] == self::FORMULA_STRING_QUOTE) && (substr($value, -1) == self::FORMULA_STRING_QUOTE)) {
3273  return substr($value, 1, -1);
3274  }
3275  // Convert numeric errors to NAN error
3276  } elseif ((is_float($value)) && ((is_nan($value)) || (is_infinite($value)))) {
3277  return Functions::NAN();
3278  }
3279 
3280  return $value;
3281  }
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ validateBinaryOperand()

PhpOffice\PhpSpreadsheet\Calculation\Calculation::validateBinaryOperand ( $operand,
$stack 
)
private

Definition at line 4902 of file Calculation.php.

References PhpOffice\PhpSpreadsheet\Shared\StringHelper\convertToNumberIfFraction(), and PhpOffice\PhpSpreadsheet\Calculation\Calculation\showTypeDetails().

Referenced by PhpOffice\PhpSpreadsheet\Calculation\Calculation\executeNumericBinaryOperation().

4903  {
4904  if (is_array($operand)) {
4905  if ((count($operand, COUNT_RECURSIVE) - count($operand)) == 1) {
4906  do {
4907  $operand = array_pop($operand);
4908  } while (is_array($operand));
4909  }
4910  }
4911  // Numbers, matrices and booleans can pass straight through, as they're already valid
4912  if (is_string($operand)) {
4913  // We only need special validations for the operand if it is a string
4914  // Start by stripping off the quotation marks we use to identify true excel string values internally
4915  if ($operand > '' && $operand[0] == self::FORMULA_STRING_QUOTE) {
4916  $operand = self::unwrapResult($operand);
4917  }
4918  // If the string is a numeric value, we treat it as a numeric, so no further testing
4919  if (!is_numeric($operand)) {
4920  // If not a numeric, test to see if the value is an Excel error, and so can't be used in normal binary operations
4921  if ($operand > '' && $operand[0] == '#') {
4922  $stack->push('Value', $operand);
4923  $this->debugLog->writeDebugLog('Evaluation Result is ', $this->showTypeDetails($operand));
4924 
4925  return false;
4926  } elseif (!Shared\StringHelper::convertToNumberIfFraction($operand)) {
4927  // If not a numeric or a fraction, then it's a text string, and so can't be used in mathematical binary operations
4928  $stack->push('Error', '#VALUE!');
4929  $this->debugLog->writeDebugLog('Evaluation Result is a ', $this->showTypeDetails('#VALUE!'));
4930 
4931  return false;
4932  }
4933  }
4934  }
4935 
4936  // return a true if the value of the operand is one that we can use in normal binary operations
4937  return true;
4938  }
static convertToNumberIfFraction(&$operand)
Identify whether a string contains a fractional numeric value, and convert it to a numeric if it is...
showTypeDetails($value)
Format type and details of an operand for display in the log (based on operand type).
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ wrapResult()

static PhpOffice\PhpSpreadsheet\Calculation\Calculation::wrapResult (   $value)
static

Wrap string values in quotes.

Parameters
mixed$value
Returns
mixed

Definition at line 3243 of file Calculation.php.

References PhpOffice\PhpSpreadsheet\Calculation\Functions\NAN().

Referenced by PhpOffice\PhpSpreadsheet\Calculation\Functions\ifCondition(), and PhpOffice\PhpSpreadsheet\Calculation\Database\DatabaseAbstract\processCondition().

3244  {
3245  if (is_string($value)) {
3246  // Error values cannot be "wrapped"
3247  if (preg_match('/^' . self::CALCULATION_REGEXP_ERROR . '$/i', $value, $match)) {
3248  // Return Excel errors "as is"
3249  return $value;
3250  }
3251 
3252  // Return strings wrapped in quotes
3253  return self::FORMULA_STRING_QUOTE . $value . self::FORMULA_STRING_QUOTE;
3254  } elseif ((is_float($value)) && ((is_nan($value)) || (is_infinite($value)))) {
3255  // Convert numeric errors to NaN error
3256  return Functions::NAN();
3257  }
3258 
3259  return $value;
3260  }
+ Here is the call graph for this function:
+ Here is the caller graph for this function:

Field Documentation

◆ $binaryOperators

PhpOffice\PhpSpreadsheet\Calculation\Calculation::$binaryOperators
staticprivate
Initial value:
= [
'+' => true

Definition at line 110 of file Calculation.php.

◆ $branchPruningEnabled

PhpOffice\PhpSpreadsheet\Calculation\Calculation::$branchPruningEnabled = true
private

Definition at line 90 of file Calculation.php.

◆ $branchStoreKeyCounter

PhpOffice\PhpSpreadsheet\Calculation\Calculation::$branchStoreKeyCounter = 0
private

◆ $calculationCache

PhpOffice\PhpSpreadsheet\Calculation\Calculation::$calculationCache = []
private

Definition at line 74 of file Calculation.php.

◆ $calculationCacheEnabled

PhpOffice\PhpSpreadsheet\Calculation\Calculation::$calculationCacheEnabled = true
private

◆ $cellStack

PhpOffice\PhpSpreadsheet\Calculation\Calculation::$cellStack = []
private

Definition at line 154 of file Calculation.php.

◆ $comparisonOperators

static PhpOffice\PhpSpreadsheet\Calculation\Calculation::$comparisonOperators = ['>' => true
staticprivate

Definition at line 3859 of file Calculation.php.

◆ $controlFunctions

static static PhpOffice\PhpSpreadsheet\Calculation\Calculation::$controlFunctions
staticprivate
Initial value:
= [
'MKMATRIX' => [
'argumentCount' => '*'

Definition at line 2743 of file Calculation.php.

◆ $cyclicFormulaCell

PhpOffice\PhpSpreadsheet\Calculation\Calculation::$cyclicFormulaCell = ''
private

Definition at line 165 of file Calculation.php.

◆ $cyclicFormulaCount

PhpOffice\PhpSpreadsheet\Calculation\Calculation::$cyclicFormulaCount = 1

Definition at line 172 of file Calculation.php.

◆ $cyclicFormulaCounter

PhpOffice\PhpSpreadsheet\Calculation\Calculation::$cyclicFormulaCounter = 1
private

◆ $cyclicReferenceStack

PhpOffice\PhpSpreadsheet\Calculation\Calculation::$cyclicReferenceStack
private

Definition at line 152 of file Calculation.php.

◆ $debugLog

PhpOffice\PhpSpreadsheet\Calculation\Calculation::$debugLog
private

◆ $delta

PhpOffice\PhpSpreadsheet\Calculation\Calculation::$delta = 0.1e-12
private

◆ $excelConstants

PhpOffice\PhpSpreadsheet\Calculation\Calculation::$excelConstants
staticprivate
Initial value:
= [
'TRUE' => true

Definition at line 224 of file Calculation.php.

◆ $formulaError

PhpOffice\PhpSpreadsheet\Calculation\Calculation::$formulaError

Definition at line 138 of file Calculation.php.

◆ $functionReplaceFromExcel

PhpOffice\PhpSpreadsheet\Calculation\Calculation::$functionReplaceFromExcel = null
staticprivate

Definition at line 3162 of file Calculation.php.

◆ $functionReplaceFromLocale

PhpOffice\PhpSpreadsheet\Calculation\Calculation::$functionReplaceFromLocale = null
staticprivate

Definition at line 3191 of file Calculation.php.

◆ $functionReplaceToExcel

PhpOffice\PhpSpreadsheet\Calculation\Calculation::$functionReplaceToExcel = null
staticprivate

Definition at line 3193 of file Calculation.php.

◆ $functionReplaceToLocale

PhpOffice\PhpSpreadsheet\Calculation\Calculation::$functionReplaceToLocale = null
staticprivate

Definition at line 3164 of file Calculation.php.

◆ $instance

◆ $localeArgumentSeparator

PhpOffice\PhpSpreadsheet\Calculation\Calculation::$localeArgumentSeparator = ','
staticprivate

Definition at line 203 of file Calculation.php.

◆ $localeBoolean

PhpOffice\PhpSpreadsheet\Calculation\Calculation::$localeBoolean
static
Initial value:
= [
'TRUE' => 'TRUE'

Definition at line 212 of file Calculation.php.

Referenced by PhpOffice\PhpSpreadsheet\Calculation\Calculation\_translateFormulaToLocale().

◆ $localeFunctions

PhpOffice\PhpSpreadsheet\Calculation\Calculation::$localeFunctions = []
staticprivate

◆ $localeLanguage

PhpOffice\PhpSpreadsheet\Calculation\Calculation::$localeLanguage = 'en_us'
staticprivate

Definition at line 186 of file Calculation.php.

◆ $operatorAssociativity

PhpOffice\PhpSpreadsheet\Calculation\Calculation::$operatorAssociativity
staticprivate
Initial value:
= [
'^' => 0

Definition at line 3848 of file Calculation.php.

◆ $operatorPrecedence

static static PhpOffice\PhpSpreadsheet\Calculation\Calculation::$operatorPrecedence
staticprivate
Initial value:
= [
':' => 8

Definition at line 3864 of file Calculation.php.

◆ $operators

PhpOffice\PhpSpreadsheet\Calculation\Calculation::$operators
staticprivate
Initial value:
= [
'+' => true

Definition at line 98 of file Calculation.php.

◆ $phpSpreadsheetFunctions

static PhpOffice\PhpSpreadsheet\Calculation\Calculation::$phpSpreadsheetFunctions
staticprivate
Initial value:
= [
'ABS' => [

Definition at line 231 of file Calculation.php.

◆ $referenceHelper

PhpOffice\PhpSpreadsheet\Calculation\Calculation::$referenceHelper
staticprivate

Definition at line 145 of file Calculation.php.

◆ $returnArrayAsType

PhpOffice\PhpSpreadsheet\Calculation\Calculation::$returnArrayAsType = self::RETURN_ARRAY_AS_VALUE
staticprivate

◆ $spreadsheet

◆ $suppressFormulaErrors

PhpOffice\PhpSpreadsheet\Calculation\Calculation::$suppressFormulaErrors = false

Definition at line 131 of file Calculation.php.

◆ $validLocaleLanguages

PhpOffice\PhpSpreadsheet\Calculation\Calculation::$validLocaleLanguages
staticprivate
Initial value:
= [
'en',
]

Definition at line 194 of file Calculation.php.

◆ CALCULATION_REGEXP_CELLREF

const PhpOffice\PhpSpreadsheet\Calculation\Calculation::CALCULATION_REGEXP_CELLREF = '((([^\s,!&%^\/\*\+<>=-]*)|(\'[^\']*\')|(\"[^\"]*\"))!)?\$?\b([a-z]{1,3})\$?(\d{1,7})(?![\w.])'

◆ CALCULATION_REGEXP_CELLREF_RELATIVE

const PhpOffice\PhpSpreadsheet\Calculation\Calculation::CALCULATION_REGEXP_CELLREF_RELATIVE = '((([^\s\(,!&%^\/\*\+<>=-]*)|(\'[^\']*\')|(\"[^\"]*\"))!)?(\$?\b[a-z]{1,3})(\$?\d{1,7})(?![\w.])'

◆ CALCULATION_REGEXP_COLUMN_RANGE

const PhpOffice\PhpSpreadsheet\Calculation\Calculation::CALCULATION_REGEXP_COLUMN_RANGE = '(((([^\s\(,!&%^\/\*\+<>=-]*)|(\'[^\']*\')|(\"[^\"]*\"))!)?(\$?[a-z]{1,3})):(?![.*])'

Definition at line 33 of file Calculation.php.

◆ CALCULATION_REGEXP_COLUMNRANGE_RELATIVE

const PhpOffice\PhpSpreadsheet\Calculation\Calculation::CALCULATION_REGEXP_COLUMNRANGE_RELATIVE = '(\$?[a-z]{1,3}):(\$?[a-z]{1,3})'

◆ CALCULATION_REGEXP_DEFINEDNAME

const PhpOffice\PhpSpreadsheet\Calculation\Calculation::CALCULATION_REGEXP_DEFINEDNAME = '((([^\s,!&%^\/\*\+<>=-]*)|(\'[^\']*\')|(\"[^\"]*\"))!)?([_\p{L}][_\p{L}\p{N}\.]*)'

◆ CALCULATION_REGEXP_ERROR

const PhpOffice\PhpSpreadsheet\Calculation\Calculation::CALCULATION_REGEXP_ERROR = '\#[A-Z][A-Z0_\/]*[!\?]?'

Definition at line 42 of file Calculation.php.

◆ CALCULATION_REGEXP_FUNCTION

const PhpOffice\PhpSpreadsheet\Calculation\Calculation::CALCULATION_REGEXP_FUNCTION = '@?(?:_xlfn\.)?([\p{L}][\p{L}\p{N}\.]*)[\s]*\('

Definition at line 28 of file Calculation.php.

◆ CALCULATION_REGEXP_NUMBER

const PhpOffice\PhpSpreadsheet\Calculation\Calculation::CALCULATION_REGEXP_NUMBER = '[-+]?\d*\.?\d+(e[-+]?\d+)?'

Constants.

Regular Expressions

Definition at line 22 of file Calculation.php.

◆ CALCULATION_REGEXP_OPENBRACE

const PhpOffice\PhpSpreadsheet\Calculation\Calculation::CALCULATION_REGEXP_OPENBRACE = '\('

Definition at line 26 of file Calculation.php.

◆ CALCULATION_REGEXP_ROW_RANGE

const PhpOffice\PhpSpreadsheet\Calculation\Calculation::CALCULATION_REGEXP_ROW_RANGE = '(((([^\s\(,!&%^\/\*\+<>=-]*)|(\'[^\']*\')|(\"[^\"]*\"))!)?(\$?[1-9][0-9]{0,6})):(?![.*])'

Definition at line 34 of file Calculation.php.

◆ CALCULATION_REGEXP_ROWRANGE_RELATIVE

const PhpOffice\PhpSpreadsheet\Calculation\Calculation::CALCULATION_REGEXP_ROWRANGE_RELATIVE = '(\$?\d{1,7}):(\$?\d{1,7})'

◆ CALCULATION_REGEXP_STRING

const PhpOffice\PhpSpreadsheet\Calculation\Calculation::CALCULATION_REGEXP_STRING = '"(?:[^"]|"")*"'

Definition at line 24 of file Calculation.php.

◆ FORMULA_CLOSE_FUNCTION_BRACE

const PhpOffice\PhpSpreadsheet\Calculation\Calculation::FORMULA_CLOSE_FUNCTION_BRACE = '}'

Definition at line 50 of file Calculation.php.

◆ FORMULA_OPEN_FUNCTION_BRACE

const PhpOffice\PhpSpreadsheet\Calculation\Calculation::FORMULA_OPEN_FUNCTION_BRACE = '{'

Definition at line 49 of file Calculation.php.

◆ FORMULA_STRING_QUOTE

const PhpOffice\PhpSpreadsheet\Calculation\Calculation::FORMULA_STRING_QUOTE = '"'

Definition at line 51 of file Calculation.php.

◆ RETURN_ARRAY_AS_ARRAY

const PhpOffice\PhpSpreadsheet\Calculation\Calculation::RETURN_ARRAY_AS_ARRAY = 'array'

Definition at line 47 of file Calculation.php.

◆ RETURN_ARRAY_AS_ERROR

const PhpOffice\PhpSpreadsheet\Calculation\Calculation::RETURN_ARRAY_AS_ERROR = 'error'

constants

Definition at line 45 of file Calculation.php.

◆ RETURN_ARRAY_AS_VALUE

const PhpOffice\PhpSpreadsheet\Calculation\Calculation::RETURN_ARRAY_AS_VALUE = 'value'

The documentation for this class was generated from the following file: