@@ -33,11 +33,11 @@ class Calculation
33
33
// Function (allow for the old @ symbol that could be used to prefix a function, but we'll ignore it)
34
34
const CALCULATION_REGEXP_FUNCTION = '@?(?:_xlfn\.)?([\p{L}][\p{L}\p{N}\.]*)[\s]*\( ' ;
35
35
// 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.]) ' ;
37
37
// 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})):(?![.*]) ' ;
41
41
// Cell reference (with or without a sheet reference) ensuring absolute/relative
42
42
// Cell ranges ensuring absolute/relative
43
43
const CALCULATION_REGEXP_COLUMNRANGE_RELATIVE = '(\$?[a-z]{1,3}):(\$?[a-z]{1,3}) ' ;
@@ -4135,17 +4135,25 @@ private function internalParseFormula($formula, ?Cell $cell = null)
4135
4135
$ testPrevOp = $ stack ->last (1 );
4136
4136
if ($ testPrevOp !== null && $ testPrevOp ['value ' ] === ': ' ) {
4137
4137
// If we have a worksheet reference, then we're playing with a 3D reference
4138
- if ($ matches [2 ] == '' ) {
4138
+ if ($ matches [2 ] === '' ) {
4139
4139
// Otherwise, we 'inherit' the worksheet reference from the start cell reference
4140
4140
// The start of the cell range reference should be the last entry in $output
4141
4141
$ 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 );
4143
4147
if ($ rangeStartMatches [2 ] > '' ) {
4144
4148
$ val = $ rangeStartMatches [2 ] . '! ' . $ val ;
4145
4149
}
4146
4150
} else {
4147
4151
$ 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 );
4149
4157
if ($ rangeStartMatches [2 ] !== $ matches [2 ]) {
4150
4158
return $ this ->raiseFormulaError ('3D Range references are not yet supported ' );
4151
4159
}
@@ -4159,7 +4167,7 @@ private function internalParseFormula($formula, ?Cell $cell = null)
4159
4167
$ outputItem = $ stack ->getStackItem ('Cell Reference ' , $ val , $ val );
4160
4168
4161
4169
$ output [] = $ outputItem ;
4162
- } else { // it's a variable, constant, string, number or boolean
4170
+ } else { // it's a variable, constant, string, number or boolean
4163
4171
$ localeConstant = false ;
4164
4172
$ stackItemType = 'Value ' ;
4165
4173
$ stackItemReference = null ;
@@ -4168,39 +4176,62 @@ private function internalParseFormula($formula, ?Cell $cell = null)
4168
4176
$ testPrevOp = $ stack ->last (1 );
4169
4177
if ($ testPrevOp !== null && $ testPrevOp ['value ' ] === ': ' ) {
4170
4178
$ 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
+ }
4181
4200
} 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
+ }
4184
4214
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
+ }
4189
4219
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 ;
4202
4234
}
4203
- $ stackItemReference = $ val ;
4204
4235
} elseif ($ opCharacter == self ::FORMULA_STRING_QUOTE ) {
4205
4236
// UnEscape any quotes within the string
4206
4237
$ 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)
4461
4492
4462
4493
// Process the operation in the appropriate manner
4463
4494
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
4471
4502
$ result = $ this ->executeBinaryComparisonOperation ($ operand1 , $ operand2 , (string ) $ token , $ stack );
4472
4503
if (isset ($ storeKey )) {
4473
4504
$ branchStore [$ storeKey ] = $ result ;
4474
4505
}
4475
4506
4476
4507
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
+ }
4479
4518
if (strpos ($ operand1Data ['reference ' ], '! ' ) !== false ) {
4480
4519
[$ sheet1 , $ operand1Data ['reference ' ]] = Worksheet::extractSheetTitle ($ operand1Data ['reference ' ], true );
4481
4520
} else {
0 commit comments