@@ -33,11 +33,11 @@ class Calculation
3333 // Function (allow for the old @ symbol that could be used to prefix a function, but we'll ignore it)
3434 const CALCULATION_REGEXP_FUNCTION = '@?(?:_xlfn\.)?([\p{L}][\p{L}\p{N}\.]*)[\s]*\( ' ;
3535 // Cell reference (cell or range of cells, with or without a sheet reference)
36- const CALCULATION_REGEXP_CELLREF = '((([^\s,!&%^\/\*\+<>=-]*)|( \'.*? \')|(\".*?\"))!)?\$?\b([a-z]{1,3})\$?(\d{1,7})(?![\w.]) ' ;
36+ const CALCULATION_REGEXP_CELLREF = '((([^\s,!&%^\/\*\+<>=:` -]*)|( \'.*? \')|(\".*?\"))!)?\$?\b([a-z]{1,3})\$?(\d{1,7})(?![\w.]) ' ;
3737 // Cell reference (with or without a sheet reference) ensuring absolute/relative
38- const CALCULATION_REGEXP_CELLREF_RELATIVE = '((([^\s\(,!&%^\/\*\+<>=-]*)|( \'.*? \')|(\".*?\"))!)?(\$?\b[a-z]{1,3})(\$?\d{1,7})(?![\w.]) ' ;
39- const CALCULATION_REGEXP_COLUMN_RANGE = '(((([^\s\(,!&%^\/\*\+<>=-]*)|( \'.*? \')|(\".*?\"))!)?(\$?[a-z]{1,3})):(?![.*]) ' ;
40- const CALCULATION_REGEXP_ROW_RANGE = '(((([^\s\(,!&%^\/\*\+<>=-]*)|( \'.*? \')|(\".*?\"))!)?(\$?[1-9][0-9]{0,6})):(?![.*]) ' ;
38+ const CALCULATION_REGEXP_CELLREF_RELATIVE = '((([^\s\(,!&%^\/\*\+<>=:` -]*)|( \'.*? \')|(\".*?\"))!)?(\$?\b[a-z]{1,3})(\$?\d{1,7})(?![\w.]) ' ;
39+ const CALCULATION_REGEXP_COLUMN_RANGE = '(((([^\s\(,!&%^\/\*\+<>=:` -]*)|( \'.*? \')|(\".*?\"))!)?(\$?[a-z]{1,3})):(?![.*]) ' ;
40+ const CALCULATION_REGEXP_ROW_RANGE = '(((([^\s\(,!&%^\/\*\+<>=:` -]*)|( \'.*? \')|(\".*?\"))!)?(\$?[1-9][0-9]{0,6})):(?![.*]) ' ;
4141 // Cell reference (with or without a sheet reference) ensuring absolute/relative
4242 // Cell ranges ensuring absolute/relative
4343 const CALCULATION_REGEXP_COLUMNRANGE_RELATIVE = '(\$?[a-z]{1,3}):(\$?[a-z]{1,3}) ' ;
@@ -4135,17 +4135,25 @@ private function internalParseFormula($formula, ?Cell $cell = null)
41354135 $ testPrevOp = $ stack ->last (1 );
41364136 if ($ testPrevOp !== null && $ testPrevOp ['value ' ] === ': ' ) {
41374137 // If we have a worksheet reference, then we're playing with a 3D reference
4138- if ($ matches [2 ] == '' ) {
4138+ if ($ matches [2 ] === '' ) {
41394139 // Otherwise, we 'inherit' the worksheet reference from the start cell reference
41404140 // The start of the cell range reference should be the last entry in $output
41414141 $ rangeStartCellRef = $ output [count ($ output ) - 1 ]['value ' ];
4142- preg_match ('/^ ' . self ::CALCULATION_REGEXP_CELLREF . '$/i ' , $ rangeStartCellRef , $ rangeStartMatches );
4142+ if ($ rangeStartCellRef === ': ' ) {
4143+ // Do we have chained range operators?
4144+ $ rangeStartCellRef = $ output [count ($ output ) - 2 ]['value ' ];
4145+ }
4146+ preg_match ('/^ ' . self ::CALCULATION_REGEXP_CELLREF . '$/miu ' , $ rangeStartCellRef , $ rangeStartMatches );
41434147 if ($ rangeStartMatches [2 ] > '' ) {
41444148 $ val = $ rangeStartMatches [2 ] . '! ' . $ val ;
41454149 }
41464150 } else {
41474151 $ rangeStartCellRef = $ output [count ($ output ) - 1 ]['value ' ];
4148- preg_match ('/^ ' . self ::CALCULATION_REGEXP_CELLREF . '$/i ' , $ rangeStartCellRef , $ rangeStartMatches );
4152+ if ($ rangeStartCellRef === ': ' ) {
4153+ // Do we have chained range operators?
4154+ $ rangeStartCellRef = $ output [count ($ output ) - 2 ]['value ' ];
4155+ }
4156+ preg_match ('/^ ' . self ::CALCULATION_REGEXP_CELLREF . '$/miu ' , $ rangeStartCellRef , $ rangeStartMatches );
41494157 if ($ rangeStartMatches [2 ] !== $ matches [2 ]) {
41504158 return $ this ->raiseFormulaError ('3D Range references are not yet supported ' );
41514159 }
@@ -4159,7 +4167,7 @@ private function internalParseFormula($formula, ?Cell $cell = null)
41594167 $ outputItem = $ stack ->getStackItem ('Cell Reference ' , $ val , $ val );
41604168
41614169 $ output [] = $ outputItem ;
4162- } else { // it's a variable, constant, string, number or boolean
4170+ } else { // it's a variable, constant, string, number or boolean
41634171 $ localeConstant = false ;
41644172 $ stackItemType = 'Value ' ;
41654173 $ stackItemReference = null ;
@@ -4168,39 +4176,62 @@ private function internalParseFormula($formula, ?Cell $cell = null)
41684176 $ testPrevOp = $ stack ->last (1 );
41694177 if ($ testPrevOp !== null && $ testPrevOp ['value ' ] === ': ' ) {
41704178 $ stackItemType = 'Cell Reference ' ;
4171- $ startRowColRef = $ output [count ($ output ) - 1 ]['value ' ];
4172- [$ rangeWS1 , $ startRowColRef ] = Worksheet::extractSheetTitle ($ startRowColRef , true );
4173- $ rangeSheetRef = $ rangeWS1 ;
4174- if ($ rangeWS1 !== '' ) {
4175- $ rangeWS1 .= '! ' ;
4176- }
4177- $ rangeSheetRef = trim ($ rangeSheetRef , "' " );
4178- [$ rangeWS2 , $ val ] = Worksheet::extractSheetTitle ($ val , true );
4179- if ($ rangeWS2 !== '' ) {
4180- $ rangeWS2 .= '! ' ;
4179+ if (
4180+ (preg_match ('/^ ' . self ::CALCULATION_REGEXP_DEFINEDNAME . '$/mui ' , $ val ) !== false ) &&
4181+ ($ this ->spreadsheet ->getNamedRange ($ val ) !== null )
4182+ ) {
4183+ $ namedRange = $ this ->spreadsheet ->getNamedRange ($ val );
4184+ if ($ namedRange !== null ) {
4185+ $ stackItemType = 'Defined Name ' ;
4186+ $ address = str_replace ('$ ' , '' , $ namedRange ->getValue ());
4187+ $ stackItemReference = $ val ;
4188+ if (strpos ($ address , ': ' ) !== false ) {
4189+ // We'll need to manipulate the stack for an actual named range rather than a named cell
4190+ $ fromTo = explode (': ' , $ address );
4191+ $ to = array_pop ($ fromTo );
4192+ foreach ($ fromTo as $ from ) {
4193+ $ output [] = $ stack ->getStackItem ($ stackItemType , $ from , $ stackItemReference );
4194+ $ output [] = $ stack ->getStackItem ('Binary Operator ' , ': ' );
4195+ }
4196+ $ address = $ to ;
4197+ }
4198+ $ val = $ address ;
4199+ }
41814200 } else {
4182- $ rangeWS2 = $ rangeWS1 ;
4183- }
4201+ $ startRowColRef = $ output [count ($ output ) - 1 ]['value ' ];
4202+ [$ rangeWS1 , $ startRowColRef ] = Worksheet::extractSheetTitle ($ startRowColRef , true );
4203+ $ rangeSheetRef = $ rangeWS1 ;
4204+ if ($ rangeWS1 !== '' ) {
4205+ $ rangeWS1 .= '! ' ;
4206+ }
4207+ $ rangeSheetRef = trim ($ rangeSheetRef , "' " );
4208+ [$ rangeWS2 , $ val ] = Worksheet::extractSheetTitle ($ val , true );
4209+ if ($ rangeWS2 !== '' ) {
4210+ $ rangeWS2 .= '! ' ;
4211+ } else {
4212+ $ rangeWS2 = $ rangeWS1 ;
4213+ }
41844214
4185- $ refSheet = $ pCellParent ;
4186- if ($ pCellParent !== null && $ rangeSheetRef !== '' && $ rangeSheetRef !== $ pCellParent ->getTitle ()) {
4187- $ refSheet = $ pCellParent ->getParent ()->getSheetByName ($ rangeSheetRef );
4188- }
4215+ $ refSheet = $ pCellParent ;
4216+ if ($ pCellParent !== null && $ rangeSheetRef !== '' && $ rangeSheetRef !== $ pCellParent ->getTitle ()) {
4217+ $ refSheet = $ pCellParent ->getParent ()->getSheetByName ($ rangeSheetRef );
4218+ }
41894219
4190- if (ctype_digit ($ val ) && $ val <= 1048576 ) {
4191- // Row range
4192- $ stackItemType = 'Row Reference ' ;
4193- /** @var int $valx */
4194- $ valx = $ val ;
4195- $ endRowColRef = ($ refSheet !== null ) ? $ refSheet ->getHighestDataColumn ($ valx ) : 'XFD ' ; // Max 16,384 columns for Excel2007
4196- $ val = "{$ rangeWS2 }{$ endRowColRef }{$ val }" ;
4197- } elseif (ctype_alpha ($ val ) && strlen ($ val ) <= 3 ) {
4198- // Column range
4199- $ stackItemType = 'Column Reference ' ;
4200- $ endRowColRef = ($ refSheet !== null ) ? $ refSheet ->getHighestDataRow ($ val ) : 1048576 ; // Max 1,048,576 rows for Excel2007
4201- $ val = "{$ rangeWS2 }{$ val }{$ endRowColRef }" ;
4220+ if (ctype_digit ($ val ) && $ val <= 1048576 ) {
4221+ // Row range
4222+ $ stackItemType = 'Row Reference ' ;
4223+ /** @var int $valx */
4224+ $ valx = $ val ;
4225+ $ endRowColRef = ($ refSheet !== null ) ? $ refSheet ->getHighestDataColumn ($ valx ) : 'XFD ' ; // Max 16,384 columns for Excel2007
4226+ $ val = "{$ rangeWS2 }{$ endRowColRef }{$ val }" ;
4227+ } elseif (ctype_alpha ($ val ) && strlen ($ val ) <= 3 ) {
4228+ // Column range
4229+ $ stackItemType = 'Column Reference ' ;
4230+ $ endRowColRef = ($ refSheet !== null ) ? $ refSheet ->getHighestDataRow ($ val ) : 1048576 ; // Max 1,048,576 rows for Excel2007
4231+ $ val = "{$ rangeWS2 }{$ val }{$ endRowColRef }" ;
4232+ }
4233+ $ stackItemReference = $ val ;
42024234 }
4203- $ stackItemReference = $ val ;
42044235 } elseif ($ opCharacter == self ::FORMULA_STRING_QUOTE ) {
42054236 // UnEscape any quotes within the string
42064237 $ val = self ::wrapResult (str_replace ('"" ' , self ::FORMULA_STRING_QUOTE , self ::unwrapResult ($ val )));
@@ -4461,21 +4492,29 @@ private function processTokenStack($tokens, $cellID = null, ?Cell $cell = null)
44614492
44624493 // Process the operation in the appropriate manner
44634494 switch ($ token ) {
4464- // Comparison (Boolean) Operators
4465- case '> ' : // Greater than
4466- case '< ' : // Less than
4467- case '>= ' : // Greater than or Equal to
4468- case '<= ' : // Less than or Equal to
4469- case '= ' : // Equality
4470- case '<> ' : // Inequality
4495+ // Comparison (Boolean) Operators
4496+ case '> ' : // Greater than
4497+ case '< ' : // Less than
4498+ case '>= ' : // Greater than or Equal to
4499+ case '<= ' : // Less than or Equal to
4500+ case '= ' : // Equality
4501+ case '<> ' : // Inequality
44714502 $ result = $ this ->executeBinaryComparisonOperation ($ operand1 , $ operand2 , (string ) $ token , $ stack );
44724503 if (isset ($ storeKey )) {
44734504 $ branchStore [$ storeKey ] = $ result ;
44744505 }
44754506
44764507 break ;
4477- // Binary Operators
4478- case ': ' : // Range
4508+ // Binary Operators
4509+ case ': ' : // Range
4510+ if ($ operand1Data ['type ' ] === 'Defined Name ' ) {
4511+ if (preg_match ('/$ ' . self ::CALCULATION_REGEXP_DEFINEDNAME . '^/mui ' , $ operand1Data ['reference ' ]) !== false ) {
4512+ $ definedName = $ this ->spreadsheet ->getNamedRange ($ operand1Data ['reference ' ]);
4513+ if ($ definedName !== null ) {
4514+ $ operand1Data ['reference ' ] = $ operand1Data ['value ' ] = str_replace ('$ ' , '' , $ definedName ->getValue ());
4515+ }
4516+ }
4517+ }
44794518 if (strpos ($ operand1Data ['reference ' ], '! ' ) !== false ) {
44804519 [$ sheet1 , $ operand1Data ['reference ' ]] = Worksheet::extractSheetTitle ($ operand1Data ['reference ' ], true );
44814520 } else {
0 commit comments