Merge pull request #2751 from PHPOffice/CalcEngine-Bugfix-Row-Column-Ranges

Resolve Calculation Engine bug with row and column ranges being identified as named ranges
This commit is contained in:
Mark Baker 2022-04-15 15:10:38 +02:00 committed by GitHub
commit de173d4705
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 100 additions and 1 deletions

View File

@ -4178,7 +4178,10 @@ class Calculation
$testPrevOp = $stack->last(1); $testPrevOp = $stack->last(1);
if ($testPrevOp !== null && $testPrevOp['value'] === ':') { if ($testPrevOp !== null && $testPrevOp['value'] === ':') {
$stackItemType = 'Cell Reference'; $stackItemType = 'Cell Reference';
if ( if (
!is_numeric($val) &&
((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->getNamedRange($val) !== null)
) { ) {

View File

@ -3,6 +3,8 @@
namespace PhpOffice\PhpSpreadsheetTests\Calculation; namespace PhpOffice\PhpSpreadsheetTests\Calculation;
use PhpOffice\PhpSpreadsheet\Calculation\Calculation; use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
use PhpOffice\PhpSpreadsheet\NamedRange;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
class ParseFormulaTest extends TestCase class ParseFormulaTest extends TestCase
@ -12,7 +14,11 @@ class ParseFormulaTest extends TestCase
*/ */
public function testParseOperations(array $expectedStack, string $formula): void 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); $stack = $parser->parseFormula($formula);
self::assertSame($expectedStack, $stack); self::assertSame($expectedStack, $stack);
} }
@ -80,6 +86,36 @@ class ParseFormulaTest extends TestCase
], ],
'=-DEFINED_NAME%', '=-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' => [ 'Cell Range' => [
[ [
['type' => 'Cell Reference', 'value' => 'A1', 'reference' => 'A1'], ['type' => 'Cell Reference', 'value' => 'A1', 'reference' => 'A1'],
@ -88,6 +124,16 @@ class ParseFormulaTest extends TestCase
], ],
'=A1:C3', '=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' => [ 'Cell Range Intersection' => [
[ [
['type' => 'Cell Reference', 'value' => 'A1', 'reference' => 'A1'], ['type' => 'Cell Reference', 'value' => 'A1', 'reference' => 'A1'],
@ -100,6 +146,40 @@ class ParseFormulaTest extends TestCase
], ],
'=A1:C3 B2:D4', '=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' => [ 'Named Range Intersection' => [
[ [
['type' => 'Defined Name', 'value' => 'DEFINED_NAME_1', 'reference' => 'DEFINED_NAME_1'], ['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', '=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' => [ // 'Cell Range Union' => [
// [ // [
// ['type' => 'Cell Reference', 'value' => 'A1', 'reference' => 'A1'], // ['type' => 'Cell Reference', 'value' => 'A1', 'reference' => 'A1'],