Fix Remainder of Calculation vs. Phpstan Issues (#3108)
* Fix Remainder of Calculation vs. Phpstan Issues I had tried to include these changes as part of an earlier effort, but something about them broke Phpstan. I removed them until I could determine the actual cause, which is ... The array `$phpSpreadsheetFunctions` is a very large and very complicated array, typehinted as `array`. It is declared as private static; however, its content never changes (at least not now - there are some outstanding proposals that might change that). I have had some success with changing unmodifiable private static to private const. However, such a change here causes Phpstan to perform a lot more processing and eventually time out. So, leave it as static. Having identified the cause of the problem, none of the other changes were problematic, so this PR applies the rest of them. * Scrutinizer One problem. * Scrutinizer False Positives Without Suggested Annotation See if minor code changes can make these go away. * Scrutinizer Still Experimenting Trying again.
This commit is contained in:
parent
de975e650f
commit
befbc564f4
File diff suppressed because it is too large
Load Diff
|
|
@ -64,14 +64,14 @@ class Calculation
|
||||||
/**
|
/**
|
||||||
* Instance of this class.
|
* Instance of this class.
|
||||||
*
|
*
|
||||||
* @var Calculation
|
* @var ?Calculation
|
||||||
*/
|
*/
|
||||||
private static $instance;
|
private static $instance;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Instance of the spreadsheet this Calculation Engine is using.
|
* Instance of the spreadsheet this Calculation Engine is using.
|
||||||
*
|
*
|
||||||
* @var Spreadsheet
|
* @var ?Spreadsheet
|
||||||
*/
|
*/
|
||||||
private $spreadsheet;
|
private $spreadsheet;
|
||||||
|
|
||||||
|
|
@ -102,10 +102,8 @@ class Calculation
|
||||||
/**
|
/**
|
||||||
* List of operators that can be used within formulae
|
* List of operators that can be used within formulae
|
||||||
* The true/false value indicates whether it is a binary operator or a unary operator.
|
* The true/false value indicates whether it is a binary operator or a unary operator.
|
||||||
*
|
|
||||||
* @var array
|
|
||||||
*/
|
*/
|
||||||
private static $operators = [
|
private const CALCULATION_OPERATORS = [
|
||||||
'+' => true, '-' => true, '*' => true, '/' => true,
|
'+' => true, '-' => true, '*' => true, '/' => true,
|
||||||
'^' => true, '&' => true, '%' => false, '~' => false,
|
'^' => true, '&' => true, '%' => false, '~' => false,
|
||||||
'>' => true, '<' => true, '=' => true, '>=' => true,
|
'>' => true, '<' => true, '=' => true, '>=' => true,
|
||||||
|
|
@ -115,10 +113,8 @@ class Calculation
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* List of binary operators (those that expect two operands).
|
* List of binary operators (those that expect two operands).
|
||||||
*
|
|
||||||
* @var array
|
|
||||||
*/
|
*/
|
||||||
private static $binaryOperators = [
|
private const BINARY_OPERATORS = [
|
||||||
'+' => true, '-' => true, '*' => true, '/' => true,
|
'+' => true, '-' => true, '*' => true, '/' => true,
|
||||||
'^' => true, '&' => true, '>' => true, '<' => true,
|
'^' => true, '&' => true, '>' => true, '<' => true,
|
||||||
'=' => true, '>=' => true, '<=' => true, '<>' => true,
|
'=' => true, '>=' => true, '<=' => true, '<>' => true,
|
||||||
|
|
@ -221,25 +217,47 @@ class Calculation
|
||||||
*
|
*
|
||||||
* @var array<string, string>
|
* @var array<string, string>
|
||||||
*/
|
*/
|
||||||
public static $localeBoolean = [
|
private static $localeBoolean = [
|
||||||
'TRUE' => 'TRUE',
|
'TRUE' => 'TRUE',
|
||||||
'FALSE' => 'FALSE',
|
'FALSE' => 'FALSE',
|
||||||
'NULL' => 'NULL',
|
'NULL' => 'NULL',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
public static function getLocaleBoolean(string $index): string
|
||||||
|
{
|
||||||
|
return self::$localeBoolean[$index];
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Excel constant string translations to their PHP equivalents
|
* Excel constant string translations to their PHP equivalents
|
||||||
* Constant conversion from text name/value to actual (datatyped) value.
|
* Constant conversion from text name/value to actual (datatyped) value.
|
||||||
*
|
*
|
||||||
* @var array<string, mixed>
|
* @var array<string, mixed>
|
||||||
*/
|
*/
|
||||||
public static $excelConstants = [
|
private static $excelConstants = [
|
||||||
'TRUE' => true,
|
'TRUE' => true,
|
||||||
'FALSE' => false,
|
'FALSE' => false,
|
||||||
'NULL' => null,
|
'NULL' => null,
|
||||||
];
|
];
|
||||||
|
|
||||||
/** @var array */
|
public static function keyInExcelConstants(string $key): bool
|
||||||
|
{
|
||||||
|
return array_key_exists($key, self::$excelConstants);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return mixed */
|
||||||
|
public static function getExcelConstants(string $key)
|
||||||
|
{
|
||||||
|
return self::$excelConstants[$key];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Array of functions usable on Spreadsheet.
|
||||||
|
* In theory, this could be const rather than static;
|
||||||
|
* however, Phpstan breaks trying to analyze it when attempted.
|
||||||
|
*
|
||||||
|
*@var array
|
||||||
|
*/
|
||||||
private static $phpSpreadsheetFunctions = [
|
private static $phpSpreadsheetFunctions = [
|
||||||
'ABS' => [
|
'ABS' => [
|
||||||
'category' => Category::CATEGORY_MATH_AND_TRIG,
|
'category' => Category::CATEGORY_MATH_AND_TRIG,
|
||||||
|
|
@ -2855,7 +2873,8 @@ class Calculation
|
||||||
private static function loadLocales(): void
|
private static function loadLocales(): void
|
||||||
{
|
{
|
||||||
$localeFileDirectory = __DIR__ . '/locale/';
|
$localeFileDirectory = __DIR__ . '/locale/';
|
||||||
foreach (glob($localeFileDirectory . '*', GLOB_ONLYDIR) as $filename) {
|
$localeFileNames = glob($localeFileDirectory . '*', GLOB_ONLYDIR) ?: [];
|
||||||
|
foreach ($localeFileNames as $filename) {
|
||||||
$filename = substr($filename, strlen($localeFileDirectory));
|
$filename = substr($filename, strlen($localeFileDirectory));
|
||||||
if ($filename != 'en') {
|
if ($filename != 'en') {
|
||||||
self::$validLocaleLanguages[] = $filename;
|
self::$validLocaleLanguages[] = $filename;
|
||||||
|
|
@ -3118,7 +3137,7 @@ class Calculation
|
||||||
}
|
}
|
||||||
|
|
||||||
// Retrieve the list of locale or language specific function names
|
// Retrieve the list of locale or language specific function names
|
||||||
$localeFunctions = file($functionNamesFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
|
$localeFunctions = file($functionNamesFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES) ?: [];
|
||||||
foreach ($localeFunctions as $localeFunction) {
|
foreach ($localeFunctions as $localeFunction) {
|
||||||
[$localeFunction] = explode('##', $localeFunction); // Strip out comments
|
[$localeFunction] = explode('##', $localeFunction); // Strip out comments
|
||||||
if (strpos($localeFunction, '=') !== false) {
|
if (strpos($localeFunction, '=') !== false) {
|
||||||
|
|
@ -3142,7 +3161,7 @@ class Calculation
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
$localeSettings = file($configFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
|
$localeSettings = file($configFile, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES) ?: [];
|
||||||
foreach ($localeSettings as $localeSetting) {
|
foreach ($localeSettings as $localeSetting) {
|
||||||
[$localeSetting] = explode('##', $localeSetting); // Strip out comments
|
[$localeSetting] = explode('##', $localeSetting); // Strip out comments
|
||||||
if (strpos($localeSetting, '=') !== false) {
|
if (strpos($localeSetting, '=') !== false) {
|
||||||
|
|
@ -3239,7 +3258,7 @@ class Calculation
|
||||||
$notWithinQuotes = false;
|
$notWithinQuotes = false;
|
||||||
foreach ($temp as &$value) {
|
foreach ($temp as &$value) {
|
||||||
// Only adjust in alternating array entries
|
// Only adjust in alternating array entries
|
||||||
$notWithinQuotes = !$notWithinQuotes;
|
$notWithinQuotes = self::logicalNot($notWithinQuotes);
|
||||||
if ($notWithinQuotes === true) {
|
if ($notWithinQuotes === true) {
|
||||||
$value = self::translateFormulaBlock($from, $to, $value, $inFunctionBracesLevel, $inMatrixBracesLevel, $fromSeparator, $toSeparator);
|
$value = self::translateFormulaBlock($from, $to, $value, $inFunctionBracesLevel, $inMatrixBracesLevel, $fromSeparator, $toSeparator);
|
||||||
}
|
}
|
||||||
|
|
@ -3453,12 +3472,19 @@ class Calculation
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$result = self::unwrapResult($this->_calculateFormulaValue($cell->getValue(), $cell->getCoordinate(), $cell));
|
$result = self::unwrapResult($this->_calculateFormulaValue($cell->getValue(), $cell->getCoordinate(), $cell));
|
||||||
|
if ($this->spreadsheet === null) {
|
||||||
|
throw new Exception('null spreadsheet in calculateCellValue');
|
||||||
|
}
|
||||||
$cellAddress = array_pop($this->cellStack);
|
$cellAddress = array_pop($this->cellStack);
|
||||||
$this->spreadsheet->getSheetByName($cellAddress['sheet'])->getCell($cellAddress['cell']);
|
if ($cellAddress === null) {
|
||||||
|
throw new Exception('null cellAddress in calculateCellValue');
|
||||||
|
}
|
||||||
|
$testSheet = $this->spreadsheet->getSheetByName($cellAddress['sheet']);
|
||||||
|
if ($testSheet === null) {
|
||||||
|
throw new Exception('worksheet not found in calculateCellValue');
|
||||||
|
}
|
||||||
|
$testSheet->getCell($cellAddress['cell']);
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
$cellAddress = array_pop($this->cellStack);
|
|
||||||
$this->spreadsheet->getSheetByName($cellAddress['sheet'])->getCell($cellAddress['cell']);
|
|
||||||
|
|
||||||
throw new Exception($e->getMessage());
|
throw new Exception($e->getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -3933,7 +3959,7 @@ class Calculation
|
||||||
$notWithinQuotes = false;
|
$notWithinQuotes = false;
|
||||||
foreach ($temp as &$value) {
|
foreach ($temp as &$value) {
|
||||||
// Only count/replace in alternating array entries
|
// Only count/replace in alternating array entries
|
||||||
$notWithinQuotes = !$notWithinQuotes;
|
$notWithinQuotes = self::logicalNot($notWithinQuotes);
|
||||||
if ($notWithinQuotes === true) {
|
if ($notWithinQuotes === true) {
|
||||||
$openCount += substr_count($value, self::FORMULA_OPEN_MATRIX_BRACE);
|
$openCount += substr_count($value, self::FORMULA_OPEN_MATRIX_BRACE);
|
||||||
$closeCount += substr_count($value, self::FORMULA_CLOSE_MATRIX_BRACE);
|
$closeCount += substr_count($value, self::FORMULA_CLOSE_MATRIX_BRACE);
|
||||||
|
|
@ -4077,11 +4103,11 @@ class Calculation
|
||||||
++$index; // Drop the redundant plus symbol
|
++$index; // Drop the redundant plus symbol
|
||||||
} elseif ((($opCharacter == '~') || ($opCharacter == '∩') || ($opCharacter == '∪')) && (!$isOperandOrFunction)) { // We have to explicitly deny a tilde, union or intersect because they are legal
|
} elseif ((($opCharacter == '~') || ($opCharacter == '∩') || ($opCharacter == '∪')) && (!$isOperandOrFunction)) { // We have to explicitly deny a tilde, union or intersect because they are legal
|
||||||
return $this->raiseFormulaError("Formula Error: Illegal character '~'"); // on the stack but not in the input expression
|
return $this->raiseFormulaError("Formula Error: Illegal character '~'"); // on the stack but not in the input expression
|
||||||
} elseif ((isset(self::$operators[$opCharacter]) || $isOperandOrFunction) && $expectingOperator) { // Are we putting an operator on the stack?
|
} elseif ((isset(self::CALCULATION_OPERATORS[$opCharacter]) || $isOperandOrFunction) && $expectingOperator) { // Are we putting an operator on the stack?
|
||||||
while (
|
while (
|
||||||
$stack->count() > 0 &&
|
$stack->count() > 0 &&
|
||||||
($o2 = $stack->last()) &&
|
($o2 = $stack->last()) &&
|
||||||
isset(self::$operators[$o2['value']]) &&
|
isset(self::CALCULATION_OPERATORS[$o2['value']]) &&
|
||||||
@(self::$operatorAssociativity[$opCharacter] ? self::$operatorPrecedence[$opCharacter] < self::$operatorPrecedence[$o2['value']] : self::$operatorPrecedence[$opCharacter] <= self::$operatorPrecedence[$o2['value']])
|
@(self::$operatorAssociativity[$opCharacter] ? self::$operatorPrecedence[$opCharacter] < self::$operatorPrecedence[$o2['value']] : self::$operatorPrecedence[$opCharacter] <= self::$operatorPrecedence[$o2['value']])
|
||||||
) {
|
) {
|
||||||
$output[] = $stack->pop(); // Swap operands and higher precedence operators from the stack to the output
|
$output[] = $stack->pop(); // Swap operands and higher precedence operators from the stack to the output
|
||||||
|
|
@ -4095,9 +4121,6 @@ class Calculation
|
||||||
} elseif ($opCharacter == ')' && $expectingOperator) { // Are we expecting to close a parenthesis?
|
} elseif ($opCharacter == ')' && $expectingOperator) { // Are we expecting to close a parenthesis?
|
||||||
$expectingOperand = false;
|
$expectingOperand = false;
|
||||||
while (($o2 = $stack->pop()) && $o2['value'] !== '(') { // Pop off the stack back to the last (
|
while (($o2 = $stack->pop()) && $o2['value'] !== '(') { // Pop off the stack back to the last (
|
||||||
if ($o2 === null) {
|
|
||||||
return $this->raiseFormulaError('Formula Error: Unexpected closing brace ")"');
|
|
||||||
}
|
|
||||||
$output[] = $o2;
|
$output[] = $o2;
|
||||||
}
|
}
|
||||||
$d = $stack->last(2);
|
$d = $stack->last(2);
|
||||||
|
|
@ -4121,10 +4144,14 @@ class Calculation
|
||||||
$output[] = $stack->pop(); // Pop the function and push onto the output
|
$output[] = $stack->pop(); // Pop the function and push onto the output
|
||||||
if (isset(self::$controlFunctions[$functionName])) {
|
if (isset(self::$controlFunctions[$functionName])) {
|
||||||
$expectedArgumentCount = self::$controlFunctions[$functionName]['argumentCount'];
|
$expectedArgumentCount = self::$controlFunctions[$functionName]['argumentCount'];
|
||||||
|
// Scrutinizer says functionCall is unused after this assignment.
|
||||||
|
// It might be right, but I'm too lazy to confirm.
|
||||||
$functionCall = self::$controlFunctions[$functionName]['functionCall'];
|
$functionCall = self::$controlFunctions[$functionName]['functionCall'];
|
||||||
|
self::doNothing($functionCall);
|
||||||
} elseif (isset(self::$phpSpreadsheetFunctions[$functionName])) {
|
} elseif (isset(self::$phpSpreadsheetFunctions[$functionName])) {
|
||||||
$expectedArgumentCount = self::$phpSpreadsheetFunctions[$functionName]['argumentCount'];
|
$expectedArgumentCount = self::$phpSpreadsheetFunctions[$functionName]['argumentCount'];
|
||||||
$functionCall = self::$phpSpreadsheetFunctions[$functionName]['functionCall'];
|
$functionCall = self::$phpSpreadsheetFunctions[$functionName]['functionCall'];
|
||||||
|
self::doNothing($functionCall);
|
||||||
} else { // did we somehow push a non-function on the stack? this should never happen
|
} else { // did we somehow push a non-function on the stack? this should never happen
|
||||||
return $this->raiseFormulaError('Formula Error: Internal error, non-function on stack');
|
return $this->raiseFormulaError('Formula Error: Internal error, non-function on stack');
|
||||||
}
|
}
|
||||||
|
|
@ -4145,6 +4172,7 @@ class Calculation
|
||||||
}
|
}
|
||||||
} elseif ($expectedArgumentCount != '*') {
|
} elseif ($expectedArgumentCount != '*') {
|
||||||
$isOperandOrFunction = preg_match('/(\d*)([-+,])(\d*)/', $expectedArgumentCount, $argMatch);
|
$isOperandOrFunction = preg_match('/(\d*)([-+,])(\d*)/', $expectedArgumentCount, $argMatch);
|
||||||
|
self::doNothing($isOperandOrFunction);
|
||||||
switch ($argMatch[2]) {
|
switch ($argMatch[2]) {
|
||||||
case '+':
|
case '+':
|
||||||
if ($argumentCount < $argMatch[1]) {
|
if ($argumentCount < $argMatch[1]) {
|
||||||
|
|
@ -4182,9 +4210,6 @@ class Calculation
|
||||||
}
|
}
|
||||||
|
|
||||||
while (($o2 = $stack->pop()) && $o2['value'] !== '(') { // Pop off the stack back to the last (
|
while (($o2 = $stack->pop()) && $o2['value'] !== '(') { // Pop off the stack back to the last (
|
||||||
if ($o2 === null) {
|
|
||||||
return $this->raiseFormulaError('Formula Error: Unexpected ,');
|
|
||||||
}
|
|
||||||
$output[] = $o2; // pop the argument expression stuff and push onto the output
|
$output[] = $o2; // pop the argument expression stuff and push onto the output
|
||||||
}
|
}
|
||||||
// If we've a comma when we're expecting an operand, then what we actually have is a null operand;
|
// If we've a comma when we're expecting an operand, then what we actually have is a null operand;
|
||||||
|
|
@ -4256,20 +4281,20 @@ class Calculation
|
||||||
if ($matches[2] === '') {
|
if ($matches[2] === '') {
|
||||||
// Otherwise, we 'inherit' the worksheet reference from the start cell reference
|
// Otherwise, we 'inherit' the worksheet reference from the start cell reference
|
||||||
// The start of the cell range reference should be the last entry in $output
|
// The start of the cell range reference should be the last entry in $output
|
||||||
$rangeStartCellRef = $output[count($output) - 1]['value'];
|
$rangeStartCellRef = $output[count($output) - 1]['value'] ?? '';
|
||||||
if ($rangeStartCellRef === ':') {
|
if ($rangeStartCellRef === ':') {
|
||||||
// Do we have chained range operators?
|
// Do we have chained range operators?
|
||||||
$rangeStartCellRef = $output[count($output) - 2]['value'];
|
$rangeStartCellRef = $output[count($output) - 2]['value'] ?? '';
|
||||||
}
|
}
|
||||||
preg_match('/^' . self::CALCULATION_REGEXP_CELLREF . '$/miu', $rangeStartCellRef, $rangeStartMatches);
|
preg_match('/^' . self::CALCULATION_REGEXP_CELLREF . '$/miu', $rangeStartCellRef, $rangeStartMatches);
|
||||||
if ($rangeStartMatches[2] > '') {
|
if ($rangeStartMatches[2] > '') {
|
||||||
$val = $rangeStartMatches[2] . '!' . $val;
|
$val = $rangeStartMatches[2] . '!' . $val;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
$rangeStartCellRef = $output[count($output) - 1]['value'];
|
$rangeStartCellRef = $output[count($output) - 1]['value'] ?? '';
|
||||||
if ($rangeStartCellRef === ':') {
|
if ($rangeStartCellRef === ':') {
|
||||||
// Do we have chained range operators?
|
// Do we have chained range operators?
|
||||||
$rangeStartCellRef = $output[count($output) - 2]['value'];
|
$rangeStartCellRef = $output[count($output) - 2]['value'] ?? '';
|
||||||
}
|
}
|
||||||
preg_match('/^' . self::CALCULATION_REGEXP_CELLREF . '$/miu', $rangeStartCellRef, $rangeStartMatches);
|
preg_match('/^' . self::CALCULATION_REGEXP_CELLREF . '$/miu', $rangeStartCellRef, $rangeStartMatches);
|
||||||
if ($rangeStartMatches[2] !== $matches[2]) {
|
if ($rangeStartMatches[2] !== $matches[2]) {
|
||||||
|
|
@ -4299,9 +4324,9 @@ class Calculation
|
||||||
!is_numeric($val) &&
|
!is_numeric($val) &&
|
||||||
((ctype_alpha($val) === false || strlen($val) > 3)) &&
|
((ctype_alpha($val) === false || strlen($val) > 3)) &&
|
||||||
(preg_match('/^' . self::CALCULATION_REGEXP_DEFINEDNAME . '$/mui', $val) !== false) &&
|
(preg_match('/^' . self::CALCULATION_REGEXP_DEFINEDNAME . '$/mui', $val) !== false) &&
|
||||||
($this->spreadsheet->getNamedRange($val) !== null)
|
($this->spreadsheet === null || $this->spreadsheet->getNamedRange($val) !== null)
|
||||||
) {
|
) {
|
||||||
$namedRange = $this->spreadsheet->getNamedRange($val);
|
$namedRange = ($this->spreadsheet === null) ? null : $this->spreadsheet->getNamedRange($val);
|
||||||
if ($namedRange !== null) {
|
if ($namedRange !== null) {
|
||||||
$stackItemType = 'Defined Name';
|
$stackItemType = 'Defined Name';
|
||||||
$address = str_replace('$', '', $namedRange->getValue());
|
$address = str_replace('$', '', $namedRange->getValue());
|
||||||
|
|
@ -4319,7 +4344,7 @@ class Calculation
|
||||||
$val = $address;
|
$val = $address;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
$startRowColRef = $output[count($output) - 1]['value'];
|
$startRowColRef = $output[count($output) - 1]['value'] ?? '';
|
||||||
[$rangeWS1, $startRowColRef] = Worksheet::extractSheetTitle($startRowColRef, true);
|
[$rangeWS1, $startRowColRef] = Worksheet::extractSheetTitle($startRowColRef, true);
|
||||||
$rangeSheetRef = $rangeWS1;
|
$rangeSheetRef = $rangeWS1;
|
||||||
if ($rangeWS1 !== '') {
|
if ($rangeWS1 !== '') {
|
||||||
|
|
@ -4395,7 +4420,7 @@ class Calculation
|
||||||
$stackItemType = 'Defined Name';
|
$stackItemType = 'Defined Name';
|
||||||
$stackItemReference = $val;
|
$stackItemReference = $val;
|
||||||
} elseif (is_numeric($val)) {
|
} elseif (is_numeric($val)) {
|
||||||
if ((strpos($val, '.') !== false) || (stripos($val, 'e') !== false) || ($val > PHP_INT_MAX) || ($val < -PHP_INT_MAX)) {
|
if ((strpos((string) $val, '.') !== false) || (stripos((string) $val, 'e') !== false) || ($val > PHP_INT_MAX) || ($val < -PHP_INT_MAX)) {
|
||||||
$val = (float) $val;
|
$val = (float) $val;
|
||||||
} else {
|
} else {
|
||||||
$val = (int) $val;
|
$val = (int) $val;
|
||||||
|
|
@ -4419,7 +4444,7 @@ class Calculation
|
||||||
} else {
|
} else {
|
||||||
return $this->raiseFormulaError("Formula Error: Unexpected ')'");
|
return $this->raiseFormulaError("Formula Error: Unexpected ')'");
|
||||||
}
|
}
|
||||||
} elseif (isset(self::$operators[$opCharacter]) && !$expectingOperator) {
|
} elseif (isset(self::CALCULATION_OPERATORS[$opCharacter]) && !$expectingOperator) {
|
||||||
return $this->raiseFormulaError("Formula Error: Unexpected operator '$opCharacter'");
|
return $this->raiseFormulaError("Formula Error: Unexpected operator '$opCharacter'");
|
||||||
} else { // I don't even want to know what you did to get here
|
} else { // I don't even want to know what you did to get here
|
||||||
return $this->raiseFormulaError('Formula Error: An unexpected error occurred');
|
return $this->raiseFormulaError('Formula Error: An unexpected error occurred');
|
||||||
|
|
@ -4428,7 +4453,7 @@ class Calculation
|
||||||
if ($index == strlen($formula)) {
|
if ($index == strlen($formula)) {
|
||||||
// Did we end with an operator?.
|
// Did we end with an operator?.
|
||||||
// Only valid for the % unary operator
|
// Only valid for the % unary operator
|
||||||
if ((isset(self::$operators[$opCharacter])) && ($opCharacter != '%')) {
|
if ((isset(self::CALCULATION_OPERATORS[$opCharacter])) && ($opCharacter != '%')) {
|
||||||
return $this->raiseFormulaError("Formula Error: Operator '$opCharacter' has no operands");
|
return $this->raiseFormulaError("Formula Error: Operator '$opCharacter' has no operands");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -4462,7 +4487,7 @@ class Calculation
|
||||||
while (
|
while (
|
||||||
$stack->count() > 0 &&
|
$stack->count() > 0 &&
|
||||||
($o2 = $stack->last()) &&
|
($o2 = $stack->last()) &&
|
||||||
isset(self::$operators[$o2['value']]) &&
|
isset(self::CALCULATION_OPERATORS[$o2['value']]) &&
|
||||||
@(self::$operatorAssociativity[$opCharacter] ? self::$operatorPrecedence[$opCharacter] < self::$operatorPrecedence[$o2['value']] : self::$operatorPrecedence[$opCharacter] <= self::$operatorPrecedence[$o2['value']])
|
@(self::$operatorAssociativity[$opCharacter] ? self::$operatorPrecedence[$opCharacter] < self::$operatorPrecedence[$o2['value']] : self::$operatorPrecedence[$opCharacter] <= self::$operatorPrecedence[$o2['value']])
|
||||||
) {
|
) {
|
||||||
$output[] = $stack->pop(); // Swap operands and higher precedence operators from the stack to the output
|
$output[] = $stack->pop(); // Swap operands and higher precedence operators from the stack to the output
|
||||||
|
|
@ -4475,7 +4500,7 @@ class Calculation
|
||||||
|
|
||||||
while (($op = $stack->pop()) !== null) {
|
while (($op = $stack->pop()) !== null) {
|
||||||
// pop everything off the stack and push onto output
|
// pop everything off the stack and push onto output
|
||||||
if ((is_array($op) && $op['value'] == '(') || ($op === '(')) {
|
if ((is_array($op) && $op['value'] == '(')) {
|
||||||
return $this->raiseFormulaError("Formula Error: Expecting ')'"); // if there are any opening braces on the stack, then braces were unbalanced
|
return $this->raiseFormulaError("Formula Error: Expecting ')'"); // if there are any opening braces on the stack, then braces were unbalanced
|
||||||
}
|
}
|
||||||
$output[] = $op;
|
$output[] = $op;
|
||||||
|
|
@ -4605,7 +4630,7 @@ class Calculation
|
||||||
}
|
}
|
||||||
|
|
||||||
// if the token is a binary operator, pop the top two values off the stack, do the operation, and push the result back on the stack
|
// if the token is a binary operator, pop the top two values off the stack, do the operation, and push the result back on the stack
|
||||||
if (!is_numeric($token) && isset(self::$binaryOperators[$token])) {
|
if (!is_numeric($token) && isset(self::BINARY_OPERATORS[$token])) {
|
||||||
// We must have two operands, error if we don't
|
// We must have two operands, error if we don't
|
||||||
if (($operand2Data = $stack->pop()) === null) {
|
if (($operand2Data = $stack->pop()) === null) {
|
||||||
return $this->raiseFormulaError('Internal error - Operand value missing from stack');
|
return $this->raiseFormulaError('Internal error - Operand value missing from stack');
|
||||||
|
|
@ -4642,7 +4667,7 @@ class Calculation
|
||||||
// Binary Operators
|
// Binary Operators
|
||||||
case ':': // Range
|
case ':': // Range
|
||||||
if ($operand1Data['type'] === 'Defined Name') {
|
if ($operand1Data['type'] === 'Defined Name') {
|
||||||
if (preg_match('/$' . self::CALCULATION_REGEXP_DEFINEDNAME . '^/mui', $operand1Data['reference']) !== false) {
|
if (preg_match('/$' . self::CALCULATION_REGEXP_DEFINEDNAME . '^/mui', $operand1Data['reference']) !== false && $this->spreadsheet !== null) {
|
||||||
$definedName = $this->spreadsheet->getNamedRange($operand1Data['reference']);
|
$definedName = $this->spreadsheet->getNamedRange($operand1Data['reference']);
|
||||||
if ($definedName !== null) {
|
if ($definedName !== null) {
|
||||||
$operand1Data['reference'] = $operand1Data['value'] = str_replace('$', '', $definedName->getValue());
|
$operand1Data['reference'] = $operand1Data['value'] = str_replace('$', '', $definedName->getValue());
|
||||||
|
|
@ -4692,7 +4717,7 @@ class Calculation
|
||||||
$oRow[] = $oCR[1];
|
$oRow[] = $oCR[1];
|
||||||
}
|
}
|
||||||
$cellRef = Coordinate::stringFromColumnIndex(min($oCol) + 1) . min($oRow) . ':' . Coordinate::stringFromColumnIndex(max($oCol) + 1) . max($oRow);
|
$cellRef = Coordinate::stringFromColumnIndex(min($oCol) + 1) . min($oRow) . ':' . Coordinate::stringFromColumnIndex(max($oCol) + 1) . max($oRow);
|
||||||
if ($pCellParent !== null) {
|
if ($pCellParent !== null && $this->spreadsheet !== null) {
|
||||||
$cellValue = $this->extractCellRange($cellRef, $this->spreadsheet->getSheetByName($sheet1), false);
|
$cellValue = $this->extractCellRange($cellRef, $this->spreadsheet->getSheetByName($sheet1), false);
|
||||||
} else {
|
} else {
|
||||||
return $this->raiseFormulaError('Unable to access Cell Reference');
|
return $this->raiseFormulaError('Unable to access Cell Reference');
|
||||||
|
|
@ -4853,7 +4878,7 @@ class Calculation
|
||||||
}
|
}
|
||||||
$matches[2] = trim($matches[2], "\"'");
|
$matches[2] = trim($matches[2], "\"'");
|
||||||
$this->debugLog->writeDebugLog('Evaluating Cell Range %s in worksheet %s', $cellRef, $matches[2]);
|
$this->debugLog->writeDebugLog('Evaluating Cell Range %s in worksheet %s', $cellRef, $matches[2]);
|
||||||
if ($pCellParent !== null) {
|
if ($pCellParent !== null && $this->spreadsheet !== null) {
|
||||||
$cellValue = $this->extractCellRange($cellRef, $this->spreadsheet->getSheetByName($matches[2]), false);
|
$cellValue = $this->extractCellRange($cellRef, $this->spreadsheet->getSheetByName($matches[2]), false);
|
||||||
} else {
|
} else {
|
||||||
return $this->raiseFormulaError('Unable to access Cell Reference');
|
return $this->raiseFormulaError('Unable to access Cell Reference');
|
||||||
|
|
@ -4882,7 +4907,7 @@ class Calculation
|
||||||
return $this->raiseFormulaError('Unable to access External Workbook');
|
return $this->raiseFormulaError('Unable to access External Workbook');
|
||||||
}
|
}
|
||||||
$this->debugLog->writeDebugLog('Evaluating Cell %s in worksheet %s', $cellRef, $matches[2]);
|
$this->debugLog->writeDebugLog('Evaluating Cell %s in worksheet %s', $cellRef, $matches[2]);
|
||||||
if ($pCellParent !== null) {
|
if ($pCellParent !== null && $this->spreadsheet !== null) {
|
||||||
$cellSheet = $this->spreadsheet->getSheetByName($matches[2]);
|
$cellSheet = $this->spreadsheet->getSheetByName($matches[2]);
|
||||||
if ($cellSheet && $cellSheet->cellExists($cellRef)) {
|
if ($cellSheet && $cellSheet->cellExists($cellRef)) {
|
||||||
$cellValue = $this->extractCellRange($cellRef, $this->spreadsheet->getSheetByName($matches[2]), false);
|
$cellValue = $this->extractCellRange($cellRef, $this->spreadsheet->getSheetByName($matches[2]), false);
|
||||||
|
|
@ -5272,7 +5297,7 @@ class Calculation
|
||||||
{
|
{
|
||||||
$this->formulaError = $errorMessage;
|
$this->formulaError = $errorMessage;
|
||||||
$this->cyclicReferenceStack->clear();
|
$this->cyclicReferenceStack->clear();
|
||||||
$suppress = $this->suppressFormulaErrors ?? $this->suppressFormulaErrorsNew;
|
$suppress = /** @scrutinizer ignore-deprecated */ $this->suppressFormulaErrors ?? $this->suppressFormulaErrorsNew;
|
||||||
if (!$suppress) {
|
if (!$suppress) {
|
||||||
throw new Exception($errorMessage);
|
throw new Exception($errorMessage);
|
||||||
}
|
}
|
||||||
|
|
@ -5299,16 +5324,18 @@ class Calculation
|
||||||
|
|
||||||
if (strpos($range, '!') !== false) {
|
if (strpos($range, '!') !== false) {
|
||||||
[$worksheetName, $range] = Worksheet::extractSheetTitle($range, true);
|
[$worksheetName, $range] = Worksheet::extractSheetTitle($range, true);
|
||||||
$worksheet = $this->spreadsheet->getSheetByName($worksheetName);
|
$worksheet = ($this->spreadsheet === null) ? null : $this->spreadsheet->getSheetByName($worksheetName);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extract range
|
// Extract range
|
||||||
$aReferences = Coordinate::extractAllCellReferencesInRange($range);
|
$aReferences = Coordinate::extractAllCellReferencesInRange($range);
|
||||||
$range = "'" . $worksheetName . "'" . '!' . $range;
|
$range = "'" . $worksheetName . "'" . '!' . $range;
|
||||||
|
$currentCol = '';
|
||||||
|
$currentRow = 0;
|
||||||
if (!isset($aReferences[1])) {
|
if (!isset($aReferences[1])) {
|
||||||
// Single cell in range
|
// Single cell in range
|
||||||
sscanf($aReferences[0], '%[A-Z]%d', $currentCol, $currentRow);
|
sscanf($aReferences[0], '%[A-Z]%d', $currentCol, $currentRow);
|
||||||
if ($worksheet->cellExists($aReferences[0])) {
|
if ($worksheet !== null && $worksheet->cellExists($aReferences[0])) {
|
||||||
$returnValue[$currentRow][$currentCol] = $worksheet->getCell($aReferences[0])->getCalculatedValue($resetLog);
|
$returnValue[$currentRow][$currentCol] = $worksheet->getCell($aReferences[0])->getCalculatedValue($resetLog);
|
||||||
} else {
|
} else {
|
||||||
$returnValue[$currentRow][$currentCol] = null;
|
$returnValue[$currentRow][$currentCol] = null;
|
||||||
|
|
@ -5318,7 +5345,7 @@ class Calculation
|
||||||
foreach ($aReferences as $reference) {
|
foreach ($aReferences as $reference) {
|
||||||
// Extract range
|
// Extract range
|
||||||
sscanf($reference, '%[A-Z]%d', $currentCol, $currentRow);
|
sscanf($reference, '%[A-Z]%d', $currentCol, $currentRow);
|
||||||
if ($worksheet->cellExists($reference)) {
|
if ($worksheet !== null && $worksheet->cellExists($reference)) {
|
||||||
$returnValue[$currentRow][$currentCol] = $worksheet->getCell($reference)->getCalculatedValue($resetLog);
|
$returnValue[$currentRow][$currentCol] = $worksheet->getCell($reference)->getCalculatedValue($resetLog);
|
||||||
} else {
|
} else {
|
||||||
$returnValue[$currentRow][$currentCol] = null;
|
$returnValue[$currentRow][$currentCol] = null;
|
||||||
|
|
@ -5347,11 +5374,11 @@ class Calculation
|
||||||
if ($worksheet !== null) {
|
if ($worksheet !== null) {
|
||||||
if (strpos($range, '!') !== false) {
|
if (strpos($range, '!') !== false) {
|
||||||
[$worksheetName, $range] = Worksheet::extractSheetTitle($range, true);
|
[$worksheetName, $range] = Worksheet::extractSheetTitle($range, true);
|
||||||
$worksheet = $this->spreadsheet->getSheetByName($worksheetName);
|
$worksheet = ($this->spreadsheet === null) ? null : $this->spreadsheet->getSheetByName($worksheetName);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Named range?
|
// Named range?
|
||||||
$namedRange = DefinedName::resolveName($range, /** @scrutinizer ignore-type */ $worksheet);
|
$namedRange = ($worksheet === null) ? null : DefinedName::resolveName($range, $worksheet);
|
||||||
if ($namedRange === null) {
|
if ($namedRange === null) {
|
||||||
return Information\ExcelError::REF();
|
return Information\ExcelError::REF();
|
||||||
}
|
}
|
||||||
|
|
@ -5360,10 +5387,10 @@ class Calculation
|
||||||
$range = $namedRange->getValue();
|
$range = $namedRange->getValue();
|
||||||
$splitRange = Coordinate::splitRange($range);
|
$splitRange = Coordinate::splitRange($range);
|
||||||
// Convert row and column references
|
// Convert row and column references
|
||||||
if (ctype_alpha($splitRange[0][0])) {
|
if ($worksheet !== null && ctype_alpha($splitRange[0][0])) {
|
||||||
$range = $splitRange[0][0] . '1:' . $splitRange[0][1] . $namedRange->getWorksheet()->getHighestRow();
|
$range = $splitRange[0][0] . '1:' . $splitRange[0][1] . $worksheet->getHighestRow();
|
||||||
} elseif (ctype_digit($splitRange[0][0])) {
|
} elseif ($worksheet !== null && ctype_digit($splitRange[0][0])) {
|
||||||
$range = 'A' . $splitRange[0][0] . ':' . $namedRange->getWorksheet()->getHighestColumn() . $splitRange[0][1];
|
$range = 'A' . $splitRange[0][0] . ':' . $worksheet->getHighestColumn() . $splitRange[0][1];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extract range
|
// Extract range
|
||||||
|
|
@ -5371,7 +5398,7 @@ class Calculation
|
||||||
if (!isset($aReferences[1])) {
|
if (!isset($aReferences[1])) {
|
||||||
// Single cell (or single column or row) in range
|
// Single cell (or single column or row) in range
|
||||||
[$currentCol, $currentRow] = Coordinate::coordinateFromString($aReferences[0]);
|
[$currentCol, $currentRow] = Coordinate::coordinateFromString($aReferences[0]);
|
||||||
if ($worksheet->cellExists($aReferences[0])) {
|
if ($worksheet !== null && $worksheet->cellExists($aReferences[0])) {
|
||||||
$returnValue[$currentRow][$currentCol] = $worksheet->getCell($aReferences[0])->getCalculatedValue($resetLog);
|
$returnValue[$currentRow][$currentCol] = $worksheet->getCell($aReferences[0])->getCalculatedValue($resetLog);
|
||||||
} else {
|
} else {
|
||||||
$returnValue[$currentRow][$currentCol] = null;
|
$returnValue[$currentRow][$currentCol] = null;
|
||||||
|
|
@ -5381,7 +5408,7 @@ class Calculation
|
||||||
foreach ($aReferences as $reference) {
|
foreach ($aReferences as $reference) {
|
||||||
// Extract range
|
// Extract range
|
||||||
[$currentCol, $currentRow] = Coordinate::coordinateFromString($reference);
|
[$currentCol, $currentRow] = Coordinate::coordinateFromString($reference);
|
||||||
if ($worksheet->cellExists($reference)) {
|
if ($worksheet !== null && $worksheet->cellExists($reference)) {
|
||||||
$returnValue[$currentRow][$currentCol] = $worksheet->getCell($reference)->getCalculatedValue($resetLog);
|
$returnValue[$currentRow][$currentCol] = $worksheet->getCell($reference)->getCalculatedValue($resetLog);
|
||||||
} else {
|
} else {
|
||||||
$returnValue[$currentRow][$currentCol] = null;
|
$returnValue[$currentRow][$currentCol] = null;
|
||||||
|
|
@ -5593,4 +5620,15 @@ class Calculation
|
||||||
{
|
{
|
||||||
return $this->suppressFormulaErrorsNew;
|
return $this->suppressFormulaErrorsNew;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static function logicalNot(bool $arg): bool
|
||||||
|
{
|
||||||
|
return $arg === false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @param mixed $arg */
|
||||||
|
private static function doNothing($arg): bool
|
||||||
|
{
|
||||||
|
return (bool) $arg;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -236,7 +236,7 @@ class Format
|
||||||
$value = ($format === true) ? Calculation::wrapResult($value) : $value;
|
$value = ($format === true) ? Calculation::wrapResult($value) : $value;
|
||||||
$value = str_replace("\n", '', $value);
|
$value = str_replace("\n", '', $value);
|
||||||
} elseif (is_bool($value)) {
|
} elseif (is_bool($value)) {
|
||||||
$value = Calculation::$localeBoolean[$value === true ? 'TRUE' : 'FALSE'];
|
$value = Calculation::getLocaleBoolean($value ? 'TRUE' : 'FALSE');
|
||||||
}
|
}
|
||||||
|
|
||||||
return (string) $value;
|
return (string) $value;
|
||||||
|
|
|
||||||
|
|
@ -232,7 +232,7 @@ class Text
|
||||||
private static function formatValueMode0($cellValue): string
|
private static function formatValueMode0($cellValue): string
|
||||||
{
|
{
|
||||||
if (is_bool($cellValue)) {
|
if (is_bool($cellValue)) {
|
||||||
return ($cellValue) ? Calculation::$localeBoolean['TRUE'] : Calculation::$localeBoolean['FALSE'];
|
return Calculation::getLocaleBoolean($cellValue ? 'TRUE' : 'FALSE');
|
||||||
}
|
}
|
||||||
|
|
||||||
return (string) $cellValue;
|
return (string) $cellValue;
|
||||||
|
|
@ -246,7 +246,7 @@ class Text
|
||||||
if (is_string($cellValue) && Functions::isError($cellValue) === false) {
|
if (is_string($cellValue) && Functions::isError($cellValue) === false) {
|
||||||
return Calculation::FORMULA_STRING_QUOTE . $cellValue . Calculation::FORMULA_STRING_QUOTE;
|
return Calculation::FORMULA_STRING_QUOTE . $cellValue . Calculation::FORMULA_STRING_QUOTE;
|
||||||
} elseif (is_bool($cellValue)) {
|
} elseif (is_bool($cellValue)) {
|
||||||
return ($cellValue) ? Calculation::$localeBoolean['TRUE'] : Calculation::$localeBoolean['FALSE'];
|
return Calculation::getLocaleBoolean($cellValue ? 'TRUE' : 'FALSE');
|
||||||
}
|
}
|
||||||
|
|
||||||
return (string) $cellValue;
|
return (string) $cellValue;
|
||||||
|
|
|
||||||
|
|
@ -140,8 +140,8 @@ class CellValue extends WizardAbstract implements WizardInterface
|
||||||
// Best-guess to try and identify if the text is a string literal, a cell reference or a formula?
|
// Best-guess to try and identify if the text is a string literal, a cell reference or a formula?
|
||||||
$operandValueType = Wizard::VALUE_TYPE_LITERAL;
|
$operandValueType = Wizard::VALUE_TYPE_LITERAL;
|
||||||
if (is_string($condition)) {
|
if (is_string($condition)) {
|
||||||
if (array_key_exists($condition, Calculation::$excelConstants)) {
|
if (Calculation::keyInExcelConstants($condition)) {
|
||||||
$condition = Calculation::$excelConstants[$condition];
|
$condition = Calculation::getExcelConstants($condition);
|
||||||
} elseif (preg_match('/^' . Calculation::CALCULATION_REGEXP_CELLREF_RELATIVE . '$/i', $condition)) {
|
} elseif (preg_match('/^' . Calculation::CALCULATION_REGEXP_CELLREF_RELATIVE . '$/i', $condition)) {
|
||||||
$operandValueType = Wizard::VALUE_TYPE_CELL;
|
$operandValueType = Wizard::VALUE_TYPE_CELL;
|
||||||
$condition = self::reverseAdjustCellRef($condition, $cellRange);
|
$condition = self::reverseAdjustCellRef($condition, $cellRange);
|
||||||
|
|
|
||||||
|
|
@ -129,9 +129,7 @@ class TextValue extends WizardAbstract implements WizardInterface
|
||||||
// Best-guess to try and identify if the text is a string literal, a cell reference or a formula?
|
// Best-guess to try and identify if the text is a string literal, a cell reference or a formula?
|
||||||
$wizard->operandValueType = Wizard::VALUE_TYPE_LITERAL;
|
$wizard->operandValueType = Wizard::VALUE_TYPE_LITERAL;
|
||||||
$condition = $conditional->getText();
|
$condition = $conditional->getText();
|
||||||
if (is_string($condition) && array_key_exists($condition, Calculation::$excelConstants)) {
|
if (preg_match('/^' . Calculation::CALCULATION_REGEXP_CELLREF_RELATIVE . '$/i', $condition)) {
|
||||||
$condition = Calculation::$excelConstants[$condition];
|
|
||||||
} elseif (preg_match('/^' . Calculation::CALCULATION_REGEXP_CELLREF_RELATIVE . '$/i', $condition)) {
|
|
||||||
$wizard->operandValueType = Wizard::VALUE_TYPE_CELL;
|
$wizard->operandValueType = Wizard::VALUE_TYPE_CELL;
|
||||||
$condition = self::reverseAdjustCellRef($condition, $cellRange);
|
$condition = self::reverseAdjustCellRef($condition, $cellRange);
|
||||||
} elseif (
|
} elseif (
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue