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

 __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)
 
 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 $phpSpreadsheetFunctions
 
static $controlFunctions
 
static $functionReplaceFromExcel = null
 
static $functionReplaceToLocale = null
 
static $functionReplaceFromLocale = null
 
static $functionReplaceToExcel = null
 
static $operatorAssociativity
 
static $comparisonOperators = ['>' => true, '<' => true, '=' => true, '>=' => true, '<=' => true, '<>' => true]
 
static $operatorPrecedence
 

Detailed Description

Definition at line 17 of file Calculation.php.

Constructor & Destructor Documentation

◆ __construct()

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

Definition at line 2758 of file Calculation.php.

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.

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

+ 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.

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)
internalParseFormula($formula, ?Cell $pCell=null)
getValueFromCache(string $cellReference, &$cellValue)
static wrapResult($value)
Wrap string values in quotes.

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(), PhpOffice\PhpSpreadsheet\Calculation\Calculation\saveValueToCache(), and PhpOffice\PhpSpreadsheet\Calculation\Calculation\wrapResult().

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

+ 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 }
static translateFormula(array $from, array $to, $formula, $fromSeparator, $toSeparator)

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

+ Here is the call graph for this function:

◆ _translateFormulaToLocale()

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

Definition at line 3166 of file Calculation.php.

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 }

References PhpOffice\PhpSpreadsheet\Calculation\Calculation\$localeBoolean, and PhpOffice\PhpSpreadsheet\Calculation\Calculation\translateFormula().

+ Here is the call graph for this function:

◆ 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.

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 }

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

+ 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.

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.

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

+ 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.

3309 {
3310 if ($pCell === null) {
3311 return null;
3312 }
3313
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
_calculateFormulaValue($formula, $cellID=null, ?Cell $pCell=null)
Parse a cell formula and calculate its value.
static unwrapResult($value)
Remove quotes used as a wrapper to identify string values.
static flattenArray($array)
Convert a multi-dimensional array to a simple 1-dimensional array.
Definition: Functions.php:583
$r
Definition: example_031.php:79

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

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

+ 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.

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 }
getCalculationCacheEnabled()
Is calculation caching enabled?

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

+ 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 }
static resizeMatricesExtend(&$matrix1, &$matrix2, $matrix1Rows, $matrix1Columns, $matrix2Rows, $matrix2Columns)
Ensure that paired matrix operands are both matrices of the same size.
static resizeMatricesShrink(&$matrix1, &$matrix2, $matrix1Rows, $matrix1Columns, $matrix2Rows, $matrix2Columns)
Ensure that paired matrix operands are both matrices of the same size.
static getMatrixDimensions(array &$matrix)
Read the dimensions of a matrix, and re-index it with straight numeric keys starting from row 0,...

References PhpOffice\PhpSpreadsheet\Calculation\Calculation\getMatrixDimensions(), PhpOffice\PhpSpreadsheet\Calculation\Calculation\resizeMatricesExtend(), and PhpOffice\PhpSpreadsheet\Calculation\Calculation\resizeMatricesShrink().

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

+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ clearBranchStore()

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

Definition at line 2972 of file Calculation.php.

2972 : void
2973 {
2974 $this->branchStoreKeyCounter = 0;
2975 }

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

+ 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.

2921 : void
2922 {
2923 $this->calculationCache = [];
2924 }

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

+ 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.

3795 {
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

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

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

+ 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 }

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

+ Here is the caller graph for this function:

◆ disableBranchPruning()

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

Definition at line 2967 of file Calculation.php.

2967 : void
2968 {
2969 $this->setBranchPruningEnabled(false);
2970 }
setBranchPruningEnabled($enabled)
Enable/disable calculation cache.

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

+ 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.

2913 : void
2914 {
2915 $this->setCalculationCacheEnabled(false);
2916 }
setCalculationCacheEnabled($pValue)
Enable/disable calculation cache.

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

+ Here is the call graph for this function:

◆ enableBranchPruning()

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

Definition at line 2962 of file Calculation.php.

2962 : void
2963 {
2964 $this->setBranchPruningEnabled(true);
2965 }

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

+ 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.

2905 : void
2906 {
2907 $this->setCalculationCacheEnabled(true);
2908 }

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

+ 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.

5443 {
5444 $definedNameScope = $namedRange->getScope();
5445 if ($definedNameScope !== null && $definedNameScope !== $pCellWorksheet) {
5446 // The defined name isn't in our current scope, so #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,
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 }
getDebugLog()
Get the Logger for this calculation engine instance.
push( $type, $value, $reference=null, $storeKey=null, $onlyIf=null, $onlyIfNot=null)
Push a new entry onto the stack.
Definition: Stack.php:45
getColumn()
Get cell coordinate column.
Definition: Cell.php:120
getCoordinate()
Get cell coordinate.
Definition: Cell.php:140
getRow()
Get cell coordinate row.
Definition: Cell.php:130
static columnIndexFromString($pString)
Column index from string.
Definition: Coordinate.php:265
getValue()
Get range or formula value.
isFormula()
Identify whether this is a named range or a named formula.

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().

+ 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.

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 }
executeBinaryComparisonOperation($cellID, $operand1, $operand2, $operation, Stack &$stack, $recursingArrays=false)
showValue($value)
Format details of an operand for display in the log (based on operand type).
static checkMatrixOperands(&$operand1, &$operand2, $resize=1)
Ensure that paired matrix operands are both matrices and of the same size.
showTypeDetails($value)
Format type and details of an operand for display in the log (based on operand type).
pop()
Pop the last entry from the stack.
Definition: Stack.php:99
$x
Definition: complexTest.php:9

References $r, $result, $x, PhpOffice\PhpSpreadsheet\Calculation\Calculation\checkMatrixOperands(), 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().

+ 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.

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 }
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)
strcmpAllowNull($str1, $str2)
PHP8.1 deprecates passing null to strcmp.
static getCompatibilityMode()
Return the current Compatibility Mode.
Definition: Functions.php:93
static strToUpper($pValue)
Convert a UTF-8 encoded string to upper case.

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(), PhpOffice\PhpSpreadsheet\Shared\StringHelper\strToUpper(), and PhpOffice\PhpSpreadsheet\Calculation\Calculation\unwrapResult().

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

+ 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.

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 }
$matrix
Definition: test.php:18

References $matrix, $result, PhpOffice\PhpSpreadsheet\Calculation\Calculation\checkMatrixOperands(), 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().

+ 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.

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 extractAllCellReferencesInRange($cellRange)
Extract all cell references in range, which may be comprised of multiple cell ranges.
Definition: Coordinate.php:338

References PhpOffice\PhpSpreadsheet\Cell\Coordinate\extractAllCellReferencesInRange().

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

+ 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.

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 coordinateFromString($pCoordinateString)
Coordinate from string.
Definition: Coordinate.php:32
static splitRange($pRange)
Split range into coordinate strings.
Definition: Coordinate.php:140
static resolveName(string $pDefinedName, Worksheet $pSheet, string $sheetName='')
Resolve a named range to a regular cell range or formula.

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

+ 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.

2805 : void
2806 {
2807 $this->clearCalculationCache();
2808 $this->clearBranchStore();
2809 }

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

+ 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.

2877 {
2879 }

References PhpOffice\PhpSpreadsheet\Calculation\Calculation\$returnArrayAsType.

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

+ 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 {
5370 }

References PhpOffice\PhpSpreadsheet\Calculation\Calculation\$phpSpreadsheetFunctions.

◆ getImplementedFunctionNames()

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

Get a list of implemented Excel function names.

Returns
array

Definition at line 5377 of file Calculation.php.

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?

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

+ 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.

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 }

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

Referenced by 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().

+ 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.

References PhpOffice\PhpSpreadsheet\Calculation\Calculation\$localeLanguage.

◆ getLocaleFile()

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

Definition at line 2987 of file Calculation.php.

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 }

References $language.

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

+ 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.

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 }

References $matrix.

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

+ Here is the caller graph for this function:

◆ getTokensAsString()

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

Definition at line 5425 of file Calculation.php.

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 }

References PHPMailer\PHPMailer\$token.

◆ getTRUE()

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

◆ getUnusedBranchStoreKey()

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

Definition at line 5417 of file Calculation.php.

5418 {
5419 $storeKeyValue = 'storeKey-' . $this->branchStoreKeyCounter;
5421
5422 return $storeKeyValue;
5423 }

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

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

+ 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.

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 }

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

+ Here is the caller graph for this function:

◆ internalParseFormula()

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.

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 }
for( $i=6;$i< 13;$i++) for($i=1; $i< 13; $i++) $d
Definition: date.php:296
$index
Definition: metadata.php:60
$row

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

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

+ 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.

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 }

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

+ Here is the caller graph for this function:

◆ loadLocales()

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

Definition at line 2768 of file Calculation.php.

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

References $filename.

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

+ Here is the caller graph for this function:

◆ localeFunc()

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

Definition at line 3220 of file Calculation.php.

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 }

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

+ 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.

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 }

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

+ 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.

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 }
executeNumericBinaryOperation($operand1, $operand2, $operation, $matrixFunction, &$stack)
evaluateDefinedName(Cell $pCell, DefinedName $namedRange, Worksheet $pCellWorksheet, Stack $stack)
addCellReference(array $args, $passCellReference, $functionCall, ?Cell $pCell=null)
Add cell reference if needed while making sure that it is the last argument.
extractCellRange(&$pRange='A1', ?Worksheet $pSheet=null, $resetLog=true)
Extract range values.
static flattenSingleValue($value='')
Convert an array to a single scalar value by extracting the first element.
Definition: Functions.php:649
static stringFromColumnIndex($columnIndex)
String from column index.
Definition: Coordinate.php:313
$data
Definition: bench.php:6

References $data, $i, $matrix, Sabre\VObject\$output, $result, $row, PHPMailer\PHPMailer\$token, PhpOffice\PhpSpreadsheet\Calculation\Calculation\addCellReference(), PhpOffice\PhpSpreadsheet\Calculation\Calculation\checkMatrixOperands(), PhpOffice\PhpSpreadsheet\Cell\Coordinate\columnIndexFromString(), PhpOffice\PhpSpreadsheet\Cell\Coordinate\coordinateFromString(), PhpOffice\PhpSpreadsheet\Calculation\Calculation\dataTestReference(), PhpOffice\PhpSpreadsheet\Calculation\Calculation\evaluateDefinedName(), PhpOffice\PhpSpreadsheet\Calculation\Calculation\executeBinaryComparisonOperation(), PhpOffice\PhpSpreadsheet\Calculation\Calculation\executeNumericBinaryOperation(), PhpOffice\PhpSpreadsheet\Calculation\Calculation\extractCellRange(), PhpOffice\PhpSpreadsheet\Calculation\Functions\flattenArray(), PhpOffice\PhpSpreadsheet\Calculation\Functions\flattenSingleValue(), PhpOffice\PhpSpreadsheet\Calculation\Calculation\FORMULA_STRING_QUOTE, 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(), PhpOffice\PhpSpreadsheet\Cell\Coordinate\stringFromColumnIndex(), and PhpOffice\PhpSpreadsheet\Calculation\Calculation\unwrapResult().

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

+ 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.

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 }

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

+ 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.

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 }

References $i, and $x.

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

+ Here is the caller graph for this function:

◆ 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.

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 }

References $i.

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

+ Here is the caller graph for this function:

◆ saveValueToCache()

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

Definition at line 3466 of file Calculation.php.

3466 : void
3467 {
3468 if ($this->calculationCacheEnabled) {
3469 $this->calculationCache[$cellReference] = $cellValue;
3470 }
3471 }

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

+ 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.

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 }

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

+ 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.

2957 : void
2958 {
2959 $this->branchPruningEnabled = $enabled;
2960 }

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

+ 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.

2896 : void
2897 {
2898 $this->calculationCacheEnabled = $pValue;
2899 $this->clearCalculationCache();
2900 }

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

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

+ 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.

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) {
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)

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

+ 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.

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 }

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().

+ 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.

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 }

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

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

+ 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.

5121 {
5122 return strcmp($str1 ?? '', $str2 ?? '');
5123 }

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

+ 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.

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...

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

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

+ 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.

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 }
static translateSeparator($fromSeparator, $toSeparator, $formula, &$inBraces)
$from

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

Referenced by PhpOffice\PhpSpreadsheet\Calculation\Calculation\_translateFormulaToEnglish(), and PhpOffice\PhpSpreadsheet\Calculation\Calculation\_translateFormulaToLocale().

+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ 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.

3099 {
3100 $strlen = mb_strlen($formula);
3101 for ($i = 0; $i < $strlen; ++$i) {
3102 $chr = mb_substr($formula, $i, 1);
3103 switch ($chr) {
3105 $inBraces = true;
3106
3107 break;
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 }

References $i, PhpOffice\PhpSpreadsheet\Calculation\Calculation\FORMULA_CLOSE_FUNCTION_BRACE, and PhpOffice\PhpSpreadsheet\Calculation\Calculation\FORMULA_OPEN_FUNCTION_BRACE.

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

+ 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.

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 }

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

Referenced by PhpOffice\PhpSpreadsheet\Calculation\Calculation\calculateCellValue(), PhpOffice\PhpSpreadsheet\Calculation\Calculation\calculateFormula(), PhpOffice\PhpSpreadsheet\Calculation\Calculation\executeBinaryComparisonOperation(), PhpOffice\PhpSpreadsheet\Reader\Slk\processCFinal(), PhpOffice\PhpSpreadsheet\Reader\Slk\processCRecord(), PhpOffice\PhpSpreadsheet\Calculation\Calculation\processTokenStack(), PhpOffice\PhpSpreadsheet\Chart\DataSeriesValues\refresh(), and PhpOffice\PhpSpreadsheet\Calculation\Calculation\validateBinaryOperand().

+ 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.

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.

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

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

+ 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.

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 }

References PhpOffice\PhpSpreadsheet\Calculation\Calculation\FORMULA_STRING_QUOTE, and PhpOffice\PhpSpreadsheet\Calculation\Functions\NAN().

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

+ 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, '-' => true, '*' => true, '/' => true,
'^' => true, '&' => true, '>' => true, '<' => true,
'=' => true, '>=' => true, '<=' => true, '<>' => true,
'|' => true, ':' => 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

PhpOffice\PhpSpreadsheet\Calculation\Calculation::$comparisonOperators = ['>' => true, '<' => true, '=' => true, '>=' => true, '<=' => true, '<>' => true]
staticprivate

Definition at line 3859 of file Calculation.php.

◆ $controlFunctions

PhpOffice\PhpSpreadsheet\Calculation\Calculation::$controlFunctions
staticprivate
Initial value:
= [
'MKMATRIX' => [
'argumentCount' => '*',
'functionCall' => [Internal\MakeMatrix::class, 'make'],
],
'NAME.ERROR' => [
'argumentCount' => '*',
'functionCall' => [Functions::class, 'NAME'],
],
'WILDCARDMATCH' => [
'argumentCount' => '2',
'functionCall' => [Internal\WildcardMatch::class, 'compare'],
],
]

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,
'FALSE' => false,
'NULL' => null,
]

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',
'FALSE' => 'FALSE',
'NULL' => 'NULL',
]

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

◆ $operatorAssociativity

PhpOffice\PhpSpreadsheet\Calculation\Calculation::$operatorAssociativity
staticprivate
Initial value:
= [
'^' => 0,
'*' => 0, '/' => 0,
'+' => 0, '-' => 0,
'&' => 0,
'|' => 0, ':' => 0,
'>' => 0, '<' => 0, '=' => 0, '>=' => 0, '<=' => 0, '<>' => 0,
]

Definition at line 3848 of file Calculation.php.

◆ $operatorPrecedence

PhpOffice\PhpSpreadsheet\Calculation\Calculation::$operatorPrecedence
staticprivate
Initial value:
= [
':' => 8,
'|' => 7,
'~' => 6,
'%' => 5,
'^' => 4,
'*' => 3, '/' => 3,
'+' => 2, '-' => 2,
'&' => 1,
'>' => 0, '<' => 0, '=' => 0, '>=' => 0, '<=' => 0, '<>' => 0,
]

Definition at line 3864 of file Calculation.php.

◆ $operators

PhpOffice\PhpSpreadsheet\Calculation\Calculation::$operators
staticprivate
Initial value:
= [
'+' => true, '-' => true, '*' => true, '/' => true,
'^' => true, '&' => true, '%' => false, '~' => false,
'>' => true, '<' => true, '=' => true, '>=' => true,
'<=' => true, '<>' => true, '|' => true, ':' => true,
]

Definition at line 98 of file Calculation.php.

◆ $phpSpreadsheetFunctions

PhpOffice\PhpSpreadsheet\Calculation\Calculation::$phpSpreadsheetFunctions
staticprivate

◆ $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 = '}'

◆ FORMULA_OPEN_FUNCTION_BRACE

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

◆ FORMULA_STRING_QUOTE

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

◆ RETURN_ARRAY_AS_ARRAY

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

◆ 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: