diff --git a/src/PhpSpreadsheet/Calculation/Calculation.php b/src/PhpSpreadsheet/Calculation/Calculation.php index c4d59d39..6fbf5da4 100644 --- a/src/PhpSpreadsheet/Calculation/Calculation.php +++ b/src/PhpSpreadsheet/Calculation/Calculation.php @@ -33,17 +33,17 @@ class Calculation // Function (allow for the old @ symbol that could be used to prefix a function, but we'll ignore it) const CALCULATION_REGEXP_FUNCTION = '@?(?:_xlfn\.)?([\p{L}][\p{L}\p{N}\.]*)[\s]*\('; // Cell reference (cell or range of cells, with or without a sheet reference) - const CALCULATION_REGEXP_CELLREF = '((([^\s,!&%^\/\*\+<>=:`-]*)|(\'(?:[^\']|\'\')+?\')|(\"(?:[^\"]|\"\")+?\"))!)?\$?\b([a-z]{1,3})\$?(\d{1,7})(?![\w.])'; + const CALCULATION_REGEXP_CELLREF = '((([^\s,!&%^\/\*\+<>=:`-]*)|(\'(?:[^\']|\'[^!])+?\')|(\"(?:[^\"]|\"[^!])+?\"))!)?\$?\b([a-z]{1,3})\$?(\d{1,7})(?![\w.])'; // Cell reference (with or without a sheet reference) ensuring absolute/relative - 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_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})):(?![.*])'; // Cell reference (with or without a sheet reference) ensuring absolute/relative // Cell ranges ensuring absolute/relative const CALCULATION_REGEXP_COLUMNRANGE_RELATIVE = '(\$?[a-z]{1,3}):(\$?[a-z]{1,3})'; const CALCULATION_REGEXP_ROWRANGE_RELATIVE = '(\$?\d{1,7}):(\$?\d{1,7})'; // Defined Names: Named Range of cells, or Named Formulae - const CALCULATION_REGEXP_DEFINEDNAME = '((([^\s,!&%^\/\*\+<>=-]*)|(\'(?:[^\']|\'\')+?\')|(\"(?:[^\"]|\"\")+?\"))!)?([_\p{L}][_\p{L}\p{N}\.]*)'; + const CALCULATION_REGEXP_DEFINEDNAME = '((([^\s,!&%^\/\*\+<>=-]*)|(\'(?:[^\']|\'[^!])+?\')|(\"(?:[^\"]|\"[^!])+?\"))!)?([_\p{L}][_\p{L}\p{N}\.]*)'; // Error const CALCULATION_REGEXP_ERROR = '\#[A-Z][A-Z0_\/]*[!\?]?'; diff --git a/tests/PhpSpreadsheetTests/Calculation/ParseFormulaTest.php b/tests/PhpSpreadsheetTests/Calculation/ParseFormulaTest.php index 57b9271b..a9b79710 100644 --- a/tests/PhpSpreadsheetTests/Calculation/ParseFormulaTest.php +++ b/tests/PhpSpreadsheetTests/Calculation/ParseFormulaTest.php @@ -170,10 +170,34 @@ class ParseFormulaTest extends TestCase ['type' => 'Operand Count for Function MIN()', 'value' => 1, 'reference' => null], ['type' => 'Function', 'value' => 'MIN(', 'reference' => null], ['type' => 'Cell Reference', 'value' => "'sheet1'!A1", 'reference' => "'sheet1'!A1"], - ['type' => 'Binary Operator', 'value' => '+','reference' => null], + ['type' => 'Binary Operator', 'value' => '+', 'reference' => null], ], "=MIN('sheet1'!A:A) + 'sheet1'!A1", ], + 'Combined Cell Reference and Column Range with quote' => [ + [ + ['type' => 'Column Reference', 'value' => "'Mark''s sheet1'!A1", 'reference' => "'Mark''s sheet1'!A1"], + ['type' => 'Column Reference', 'value' => "'Mark''s sheet1'!A1048576", 'reference' => "'Mark''s sheet1'!A1048576"], + ['type' => 'Binary Operator', 'value' => ':', 'reference' => null], + ['type' => 'Operand Count for Function MIN()', 'value' => 1, 'reference' => null], + ['type' => 'Function', 'value' => 'MIN(', 'reference' => null], + ['type' => 'Cell Reference', 'value' => "'Mark's sheet1'!A1", 'reference' => "'Mark's sheet1'!A1"], + ['type' => 'Binary Operator', 'value' => '+', 'reference' => null], + ], + "=MIN('Mark''s sheet1'!A:A) + 'Mark''s sheet1'!A1", + ], + 'Combined Cell Reference and Column Range with unescaped quote' => [ + [ + ['type' => 'Column Reference', 'value' => "'Mark's sheet1'!A1", 'reference' => "'Mark's sheet1'!A1"], + ['type' => 'Column Reference', 'value' => "'Mark's sheet1'!A1048576", 'reference' => "'Mark's sheet1'!A1048576"], + ['type' => 'Binary Operator', 'value' => ':', 'reference' => null], + ['type' => 'Operand Count for Function MIN()', 'value' => 1, 'reference' => null], + ['type' => 'Function', 'value' => 'MIN(', 'reference' => null], + ['type' => 'Cell Reference', 'value' => "'Mark's sheet1'!A1", 'reference' => "'Mark's sheet1'!A1"], + ['type' => 'Binary Operator', 'value' => '+', 'reference' => null], + ], + "=MIN('Mark's sheet1'!A:A) + 'Mark's sheet1'!A1", + ], 'Combined Column Range and Cell Reference' => [ [ ['type' => 'Cell Reference', 'value' => "'sheet1'!A1", 'reference' => "'sheet1'!A1"], @@ -182,10 +206,34 @@ class ParseFormulaTest extends TestCase ['type' => 'Binary Operator', 'value' => ':', 'reference' => null], ['type' => 'Operand Count for Function MIN()', 'value' => 1, 'reference' => null], ['type' => 'Function', 'value' => 'MIN(', 'reference' => null], - ['type' => 'Binary Operator', 'value' => '+','reference' => null], + ['type' => 'Binary Operator', 'value' => '+', 'reference' => null], ], "='sheet1'!A1 + MIN('sheet1'!A:A)", ], + 'Combined Column Range and Cell Reference with quote' => [ + [ + ['type' => 'Cell Reference', 'value' => "'Mark's sheet1'!A1", 'reference' => "'Mark's sheet1'!A1"], + ['type' => 'Column Reference', 'value' => "'Mark''s sheet1'!A1", 'reference' => "'Mark''s sheet1'!A1"], + ['type' => 'Column Reference', 'value' => "'Mark''s sheet1'!A1048576", 'reference' => "'Mark''s sheet1'!A1048576"], + ['type' => 'Binary Operator', 'value' => ':', 'reference' => null], + ['type' => 'Operand Count for Function MIN()', 'value' => 1, 'reference' => null], + ['type' => 'Function', 'value' => 'MIN(', 'reference' => null], + ['type' => 'Binary Operator', 'value' => '+', 'reference' => null], + ], + "='Mark''s sheet1'!A1 + MIN('Mark''s sheet1'!A:A)", + ], + 'Combined Column Range and Cell Reference with unescaped quote' => [ + [ + ['type' => 'Cell Reference', 'value' => "'Mark's sheet1'!A1", 'reference' => "'Mark's sheet1'!A1"], + ['type' => 'Column Reference', 'value' => "'Mark's sheet1'!A1", 'reference' => "'Mark's sheet1'!A1"], + ['type' => 'Column Reference', 'value' => "'Mark's sheet1'!A1048576", 'reference' => "'Mark's sheet1'!A1048576"], + ['type' => 'Binary Operator', 'value' => ':', 'reference' => null], + ['type' => 'Operand Count for Function MIN()', 'value' => 1, 'reference' => null], + ['type' => 'Function', 'value' => 'MIN(', 'reference' => null], + ['type' => 'Binary Operator', 'value' => '+', 'reference' => null], + ], + "='Mark's sheet1'!A1 + MIN('Mark's sheet1'!A:A)", + ], 'Range with Defined Names' => [ [ ['type' => 'Defined Name', 'value' => 'GROUP1', 'reference' => 'GROUP1'],