From 716964eeecb1ad61dad749f886f2003393e4d1b3 Mon Sep 17 00:00:00 2001 From: MarkBaker Date: Fri, 15 Apr 2022 14:51:31 +0200 Subject: [PATCH] Resolve Calculation Engine bug with row and column ranges being identified as named ranges, adding overhead with the additional validation to process that named range --- .../Calculation/Calculation.php | 3 + .../Calculation/ParseFormulaTest.php | 98 ++++++++++++++++++- 2 files changed, 100 insertions(+), 1 deletion(-) diff --git a/src/PhpSpreadsheet/Calculation/Calculation.php b/src/PhpSpreadsheet/Calculation/Calculation.php index 00621e41..26ef7946 100644 --- a/src/PhpSpreadsheet/Calculation/Calculation.php +++ b/src/PhpSpreadsheet/Calculation/Calculation.php @@ -4178,7 +4178,10 @@ class Calculation $testPrevOp = $stack->last(1); if ($testPrevOp !== null && $testPrevOp['value'] === ':') { $stackItemType = 'Cell Reference'; + if ( + !is_numeric($val) && + ((ctype_alpha($val) === false || strlen($val) > 3)) && (preg_match('/^' . self::CALCULATION_REGEXP_DEFINEDNAME . '$/mui', $val) !== false) && ($this->spreadsheet->getNamedRange($val) !== null) ) { diff --git a/tests/PhpSpreadsheetTests/Calculation/ParseFormulaTest.php b/tests/PhpSpreadsheetTests/Calculation/ParseFormulaTest.php index a0c627ab..92330b7d 100644 --- a/tests/PhpSpreadsheetTests/Calculation/ParseFormulaTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/ParseFormulaTest.php @@ -3,6 +3,8 @@ namespace PhpOffice\PhpSpreadsheetTests\Calculation; use PhpOffice\PhpSpreadsheet\Calculation\Calculation; +use PhpOffice\PhpSpreadsheet\NamedRange; +use PhpOffice\PhpSpreadsheet\Spreadsheet; use PHPUnit\Framework\TestCase; class ParseFormulaTest extends TestCase @@ -12,7 +14,11 @@ class ParseFormulaTest extends TestCase */ public function testParseOperations(array $expectedStack, string $formula): void { - $parser = Calculation::getInstance(); + $spreadsheet = new Spreadsheet(); + $spreadsheet->addNamedRange(new NamedRange('GROUP1', $spreadsheet->getActiveSheet(), 'B2:D4')); + $spreadsheet->addNamedRange(new NamedRange('GROUP2', $spreadsheet->getActiveSheet(), 'D4:F6')); + + $parser = Calculation::getInstance($spreadsheet); $stack = $parser->parseFormula($formula); self::assertSame($expectedStack, $stack); } @@ -80,6 +86,36 @@ class ParseFormulaTest extends TestCase ], '=-DEFINED_NAME%', ], + 'Integer Numbers with Operator' => [ + [ + ['type' => 'Value', 'value' => 2, 'reference' => null], + ['type' => 'Value', 'value' => 3, 'reference' => null], + ['type' => 'Binary Operator', 'value' => '*', 'reference' => null], + ], + '=2*3', + ], + 'Float Numbers with Operator' => [ + [ + ['type' => 'Value', 'value' => 2.5, 'reference' => null], + ['type' => 'Value', 'value' => 3.5, 'reference' => null], + ['type' => 'Binary Operator', 'value' => '*', 'reference' => null], + ], + '=2.5*3.5', + ], + 'Strings with Operator' => [ + [ + ['type' => 'Value', 'value' => '"HELLO"', 'reference' => null], + ['type' => 'Value', 'value' => '"WORLD"', 'reference' => null], + ['type' => 'Binary Operator', 'value' => '&', 'reference' => null], + ], + '="HELLO"&"WORLD"', + ], + 'Error' => [ + [ + ['type' => 'Value', 'value' => '#DIV0!', 'reference' => null], + ], + '=#DIV0!', + ], 'Cell Range' => [ [ ['type' => 'Cell Reference', 'value' => 'A1', 'reference' => 'A1'], @@ -88,6 +124,16 @@ class ParseFormulaTest extends TestCase ], '=A1:C3', ], + 'Chained Cell Range' => [ + [ + ['type' => 'Cell Reference', 'value' => 'A1', 'reference' => 'A1'], + ['type' => 'Cell Reference', 'value' => 'C3', 'reference' => 'C3'], + ['type' => 'Binary Operator', 'value' => ':', 'reference' => null], + ['type' => 'Cell Reference', 'value' => 'E5', 'reference' => 'E5'], + ['type' => 'Binary Operator', 'value' => ':', 'reference' => null], + ], + '=A1:C3:E5', + ], 'Cell Range Intersection' => [ [ ['type' => 'Cell Reference', 'value' => 'A1', 'reference' => 'A1'], @@ -100,6 +146,40 @@ class ParseFormulaTest extends TestCase ], '=A1:C3 B2:D4', ], + 'Row Range' => [ + [ + ['type' => 'Row Reference', 'value' => 'A2', 'reference' => 'A2'], + ['type' => 'Row Reference', 'value' => 'XFD3', 'reference' => 'XFD3'], + ['type' => 'Binary Operator', 'value' => ':', 'reference' => null], + ], + '=2:3', + ], + 'Column Range' => [ + [ + ['type' => 'Column Reference', 'value' => 'B1', 'reference' => 'B1'], + ['type' => 'Column Reference', 'value' => 'C1048576', 'reference' => 'C1048576'], + ['type' => 'Binary Operator', 'value' => ':', 'reference' => null], + ], + '=B:C', + ], + 'Range with Defined Names' => [ + [ + ['type' => 'Defined Name', 'value' => 'GROUP1', 'reference' => 'GROUP1'], + ['type' => 'Defined Name', 'value' => 'D4', 'reference' => 'GROUP2'], + ['type' => 'Binary Operator', 'value' => ':', 'reference' => null], + ['type' => 'Defined Name', 'value' => 'F6', 'reference' => 'GROUP2'], + ['type' => 'Binary Operator', 'value' => ':', 'reference' => null], + ], + '=GROUP1:GROUP2', + ], + 'Named Range with Binary Operator' => [ + [ + ['type' => 'Defined Name', 'value' => 'DEFINED_NAME_1', 'reference' => 'DEFINED_NAME_1'], + ['type' => 'Defined Name', 'value' => 'DEFINED_NAME_2', 'reference' => 'DEFINED_NAME_2'], + ['type' => 'Binary Operator', 'value' => '/', 'reference' => null], + ], + '=DEFINED_NAME_1/DEFINED_NAME_2', + ], 'Named Range Intersection' => [ [ ['type' => 'Defined Name', 'value' => 'DEFINED_NAME_1', 'reference' => 'DEFINED_NAME_1'], @@ -108,6 +188,22 @@ class ParseFormulaTest extends TestCase ], '=DEFINED_NAME_1 DEFINED_NAME_2', ], + // 'Structured Reference Arithmetic' => [ + // [ + // ['type' => 'Structured Reference', 'value' => '[@Quantity]', 'reference' => null], + // ['type' => 'Structured Reference', 'value' => '[@[Unit Price]]', 'reference' => null], + // ['type' => 'Binary Operator', 'value' => '*', 'reference' => null], + // ], + // '=[@Quantity]*[@[Unit Price]]', + // ], + // 'Structured Reference Intersection' => [ + // [ + // ['type' => 'Structured Reference', 'value' => 'DeptSales[[Sales Person]:[Sales Amount]]', 'reference' => null], + // ['type' => 'Structured Reference', 'value' => 'DeptSales[[Region]:[% Commission]]', 'reference' => null], + // ['type' => 'Binary Operator', 'value' => '∩', 'reference' => null], + // ], + // '=DeptSales[[Sales Person]:[Sales Amount]] DeptSales[[Region]:[% Commission]]', + // ], // 'Cell Range Union' => [ // [ // ['type' => 'Cell Reference', 'value' => 'A1', 'reference' => 'A1'],