@@ -30,6 +30,9 @@ class Calculation
30
30
const CALCULATION_REGEXP_CELLREF = '((([^\s,!&%^\/\*\+<>=-]*)|( \'[^ \']* \')|(\"[^\"]*\"))!)?\$?\b([a-z]{1,3})\$?(\d{1,7})(?![\w.]) ' ;
31
31
// Cell reference (with or without a sheet reference) ensuring absolute/relative
32
32
const CALCULATION_REGEXP_CELLREF_RELATIVE = '((([^\s\(,!&%^\/\*\+<>=-]*)|( \'[^ \']* \')|(\"[^\"]*\"))!)?(\$?\b[a-z]{1,3})(\$?\d{1,7})(?![\w.]) ' ;
33
+ const CALCULATION_REGEXP_COLUMN_RANGE = '(((([^\s\(,!&%^\/\*\+<>=-]*)|( \'[^ \']* \')|(\"[^\"]*\"))!)?(\$?[a-z]{1,3})):(?![.*]) ' ;
34
+ const CALCULATION_REGEXP_ROW_RANGE = '(((([^\s\(,!&%^\/\*\+<>=-]*)|( \'[^ \']* \')|(\"[^\"]*\"))!)?(\$?[1-9][0-9]{0,6})):(?![.*]) ' ;
35
+ // Cell reference (with or without a sheet reference) ensuring absolute/relative
33
36
// Cell ranges ensuring absolute/relative
34
37
const CALCULATION_REGEXP_COLUMNRANGE_RELATIVE = '(\$?[a-z]{1,3}):(\$?[a-z]{1,3}) ' ;
35
38
const CALCULATION_REGEXP_ROWRANGE_RELATIVE = '(\$?\d{1,7}):(\$?\d{1,7}) ' ;
@@ -3798,6 +3801,8 @@ private function internalParseFormula($formula, ?Cell $pCell = null)
3798
3801
3799
3802
$ regexpMatchString = '/^( ' . self ::CALCULATION_REGEXP_FUNCTION .
3800
3803
'| ' . self ::CALCULATION_REGEXP_CELLREF .
3804
+ '| ' . self ::CALCULATION_REGEXP_COLUMN_RANGE .
3805
+ '| ' . self ::CALCULATION_REGEXP_ROW_RANGE .
3801
3806
'| ' . self ::CALCULATION_REGEXP_NUMBER .
3802
3807
'| ' . self ::CALCULATION_REGEXP_STRING .
3803
3808
'| ' . self ::CALCULATION_REGEXP_OPENBRACE .
@@ -3866,7 +3871,8 @@ private function internalParseFormula($formula, ?Cell $pCell = null)
3866
3871
$ opCharacter .= $ formula [++$ index ];
3867
3872
}
3868
3873
// Find out if we're currently at the beginning of a number, variable, cell reference, function, parenthesis or operand
3869
- $ isOperandOrFunction = preg_match ($ regexpMatchString , substr ($ formula , $ index ), $ match );
3874
+ $ isOperandOrFunction = (bool ) preg_match ($ regexpMatchString , substr ($ formula , $ index ), $ match );
3875
+
3870
3876
if ($ opCharacter == '- ' && !$ expectingOperator ) { // Is it a negation instead of a minus?
3871
3877
// Put a negation on the stack
3872
3878
$ stack ->push ('Unary Operator ' , '~ ' , null , $ currentCondition , $ currentOnlyIf , $ currentOnlyIfNot );
@@ -4038,6 +4044,7 @@ private function internalParseFormula($formula, ?Cell $pCell = null)
4038
4044
$ expectingOperand = false ;
4039
4045
$ val = $ match [1 ];
4040
4046
$ length = strlen ($ val );
4047
+
4041
4048
if (preg_match ('/^ ' . self ::CALCULATION_REGEXP_FUNCTION . '$/miu ' , $ val , $ matches )) {
4042
4049
$ val = preg_replace ('/\s/u ' , '' , $ val );
4043
4050
if (isset (self ::$ phpSpreadsheetFunctions [strtoupper ($ matches [1 ])]) || isset (self ::$ controlFunctions [strtoupper ($ matches [1 ])])) { // it's a function
@@ -4074,7 +4081,7 @@ private function internalParseFormula($formula, ?Cell $pCell = null)
4074
4081
// Should only be applied to the actual cell column, not the worksheet name
4075
4082
// If the last entry on the stack was a : operator, then we have a cell range reference
4076
4083
$ testPrevOp = $ stack ->last (1 );
4077
- if ($ testPrevOp !== null && $ testPrevOp ['value ' ] == ': ' ) {
4084
+ if ($ testPrevOp !== null && $ testPrevOp ['value ' ] === ': ' ) {
4078
4085
// If we have a worksheet reference, then we're playing with a 3D reference
4079
4086
if ($ matches [2 ] == '' ) {
4080
4087
// Otherwise, we 'inherit' the worksheet reference from the start cell reference
@@ -4091,73 +4098,99 @@ private function internalParseFormula($formula, ?Cell $pCell = null)
4091
4098
return $ this ->raiseFormulaError ('3D Range references are not yet supported ' );
4092
4099
}
4093
4100
}
4101
+ } elseif (strpos ($ val , '! ' ) === false && $ pCellParent !== null ) {
4102
+ $ worksheet = $ pCellParent ->getTitle ();
4103
+ $ val = "' {$ worksheet }'! {$ val }" ;
4094
4104
}
4095
4105
4096
4106
$ outputItem = $ stack ->getStackItem ('Cell Reference ' , $ val , $ val , $ currentCondition , $ currentOnlyIf , $ currentOnlyIfNot );
4097
4107
4098
4108
$ output [] = $ outputItem ;
4099
4109
} else { // it's a variable, constant, string, number or boolean
4110
+ $ localeConstant = false ;
4111
+ $ stackItemType = 'Value ' ;
4112
+ $ stackItemReference = null ;
4113
+
4100
4114
// If the last entry on the stack was a : operator, then we may have a row or column range reference
4101
4115
$ testPrevOp = $ stack ->last (1 );
4102
4116
if ($ testPrevOp !== null && $ testPrevOp ['value ' ] === ': ' ) {
4117
+ $ stackItemType = 'Cell Reference ' ;
4103
4118
$ startRowColRef = $ output [count ($ output ) - 1 ]['value ' ];
4104
4119
[$ rangeWS1 , $ startRowColRef ] = Worksheet::extractSheetTitle ($ startRowColRef , true );
4105
4120
$ rangeSheetRef = $ rangeWS1 ;
4106
- if ($ rangeWS1 != '' ) {
4121
+ if ($ rangeWS1 !== '' ) {
4107
4122
$ rangeWS1 .= '! ' ;
4108
4123
}
4124
+ $ rangeSheetRef = trim ($ rangeSheetRef , "' " );
4109
4125
[$ rangeWS2 , $ val ] = Worksheet::extractSheetTitle ($ val , true );
4110
- if ($ rangeWS2 != '' ) {
4126
+ if ($ rangeWS2 !== '' ) {
4111
4127
$ rangeWS2 .= '! ' ;
4112
4128
} else {
4113
4129
$ rangeWS2 = $ rangeWS1 ;
4114
4130
}
4131
+
4115
4132
$ refSheet = $ pCellParent ;
4116
- if ($ pCellParent !== null && $ rangeSheetRef !== $ pCellParent ->getTitle ()) {
4133
+ if ($ pCellParent !== null && $ rangeSheetRef !== '' && $ rangeSheetRef !== $ pCellParent ->getTitle ()) {
4117
4134
$ refSheet = $ pCellParent ->getParent ()->getSheetByName ($ rangeSheetRef );
4118
4135
}
4119
- if (
4120
- (is_int ($ startRowColRef )) && (ctype_digit ($ val )) &&
4121
- ($ startRowColRef <= 1048576 ) && ($ val <= 1048576 )
4122
- ) {
4136
+
4137
+ if (ctype_digit ($ val ) && $ val <= 1048576 ) {
4123
4138
// Row range
4124
- $ endRowColRef = ($ refSheet !== null ) ? $ refSheet ->getHighestColumn () : 'XFD ' ; // Max 16,384 columns for Excel2007
4125
- $ output [count ($ output ) - 1 ]['value ' ] = $ rangeWS1 . 'A ' . $ startRowColRef ;
4126
- $ val = $ rangeWS2 . $ endRowColRef . $ val ;
4127
- } elseif (
4128
- (ctype_alpha ($ startRowColRef )) && (ctype_alpha ($ val )) &&
4129
- (strlen ($ startRowColRef ) <= 3 ) && (strlen ($ val ) <= 3 )
4130
- ) {
4139
+ $ stackItemType = 'Row Reference ' ;
4140
+ $ endRowColRef = ($ refSheet !== null ) ? $ refSheet ->getHighestDataColumn ($ val ) : 'XFD ' ; // Max 16,384 columns for Excel2007
4141
+ $ val = "{$ rangeWS2 }{$ endRowColRef }{$ val }" ;
4142
+ } elseif (ctype_alpha ($ val ) && strlen ($ val ) <= 3 ) {
4131
4143
// Column range
4132
- $ endRowColRef = ( $ refSheet !== null ) ? $ refSheet -> getHighestRow () : 1048576 ; // Max 1,048,576 rows for Excel2007
4133
- $ output [ count ( $ output ) - 1 ][ ' value ' ] = $ rangeWS1 . strtoupper ( $ startRowColRef ) . ' 1 ' ;
4134
- $ val = $ rangeWS2 . $ val . $ endRowColRef ;
4144
+ $ stackItemType = ' Column Reference ' ;
4145
+ $ endRowColRef = ( $ refSheet !== null ) ? $ refSheet -> getHighestDataRow ( $ val ) : 1048576 ; // Max 1,048,576 rows for Excel2007
4146
+ $ val = "{ $ rangeWS2}{ $ val}{ $ endRowColRef}" ;
4135
4147
}
4136
- }
4137
-
4138
- $ localeConstant = false ;
4139
- $ stackItemType = 'Value ' ;
4140
- $ stackItemReference = null ;
4141
- if ($ opCharacter == self ::FORMULA_STRING_QUOTE ) {
4148
+ $ stackItemReference = $ val ;
4149
+ } elseif ($ opCharacter == self ::FORMULA_STRING_QUOTE ) {
4142
4150
// UnEscape any quotes within the string
4143
4151
$ val = self ::wrapResult (str_replace ('"" ' , self ::FORMULA_STRING_QUOTE , self ::unwrapResult ($ val )));
4144
- } elseif (is_numeric ($ val )) {
4145
- if ((strpos ($ val , '. ' ) !== false ) || (stripos ($ val , 'e ' ) !== false ) || ($ val > PHP_INT_MAX ) || ($ val < -PHP_INT_MAX )) {
4146
- $ val = (float ) $ val ;
4147
- } else {
4148
- $ val = (int ) $ val ;
4149
- }
4150
4152
} elseif (isset (self ::$ excelConstants [trim (strtoupper ($ val ))])) {
4151
4153
$ stackItemType = 'Constant ' ;
4152
4154
$ excelConstant = trim (strtoupper ($ val ));
4153
4155
$ val = self ::$ excelConstants [$ excelConstant ];
4154
4156
} elseif (($ localeConstant = array_search (trim (strtoupper ($ val )), self ::$ localeBoolean )) !== false ) {
4155
4157
$ stackItemType = 'Constant ' ;
4156
4158
$ val = self ::$ excelConstants [$ localeConstant ];
4159
+ } elseif (
4160
+ preg_match ('/^ ' . self ::CALCULATION_REGEXP_ROW_RANGE . '/miu ' , substr ($ formula , $ index ), $ rowRangeReference )
4161
+ ) {
4162
+ $ val = $ rowRangeReference [1 ];
4163
+ $ length = strlen ($ rowRangeReference [1 ]);
4164
+ $ stackItemType = 'Row Reference ' ;
4165
+ $ column = 'A ' ;
4166
+ if (($ testPrevOp !== null && $ testPrevOp ['value ' ] === ': ' ) && $ pCellParent !== null ) {
4167
+ $ column = $ pCellParent ->getHighestDataColumn ($ val );
4168
+ }
4169
+ $ val = "{$ rowRangeReference [2 ]}{$ column }{$ rowRangeReference [7 ]}" ;
4170
+ $ stackItemReference = $ val ;
4171
+ } elseif (
4172
+ preg_match ('/^ ' . self ::CALCULATION_REGEXP_COLUMN_RANGE . '/miu ' , substr ($ formula , $ index ), $ columnRangeReference )
4173
+ ) {
4174
+ $ val = $ columnRangeReference [1 ];
4175
+ $ length = strlen ($ val );
4176
+ $ stackItemType = 'Column Reference ' ;
4177
+ $ row = '1 ' ;
4178
+ if (($ testPrevOp !== null && $ testPrevOp ['value ' ] === ': ' ) && $ pCellParent !== null ) {
4179
+ $ row = $ pCellParent ->getHighestDataRow ($ val );
4180
+ }
4181
+ $ val = "{$ val }{$ row }" ;
4182
+ $ stackItemReference = $ val ;
4157
4183
} elseif (preg_match ('/^ ' . self ::CALCULATION_REGEXP_DEFINEDNAME . '.*/miu ' , $ val , $ match )) {
4158
4184
$ stackItemType = 'Defined Name ' ;
4159
4185
$ stackItemReference = $ val ;
4186
+ } elseif (is_numeric ($ val )) {
4187
+ if ((strpos ($ val , '. ' ) !== false ) || (stripos ($ val , 'e ' ) !== false ) || ($ val > PHP_INT_MAX ) || ($ val < -PHP_INT_MAX )) {
4188
+ $ val = (float ) $ val ;
4189
+ } else {
4190
+ $ val = (int ) $ val ;
4191
+ }
4160
4192
}
4193
+
4161
4194
$ details = $ stack ->getStackItem ($ stackItemType , $ val , $ stackItemReference , $ currentCondition , $ currentOnlyIf , $ currentOnlyIfNot );
4162
4195
if ($ localeConstant ) {
4163
4196
$ details ['localeValue ' ] = $ localeConstant ;
@@ -4431,6 +4464,7 @@ private function processTokenStack($tokens, $cellID = null, ?Cell $pCell = null)
4431
4464
} else {
4432
4465
return $ this ->raiseFormulaError ('Unable to access Cell Reference ' );
4433
4466
}
4467
+
4434
4468
$ stack ->push ('Cell Reference ' , $ cellValue , $ cellRef );
4435
4469
} else {
4436
4470
$ stack ->push ('Error ' , Functions::REF (), null );
@@ -4564,6 +4598,7 @@ private function processTokenStack($tokens, $cellID = null, ?Cell $pCell = null)
4564
4598
}
4565
4599
} elseif (preg_match ('/^ ' . self ::CALCULATION_REGEXP_CELLREF . '$/i ' , $ token , $ matches )) {
4566
4600
$ cellRef = null ;
4601
+
4567
4602
if (isset ($ matches [8 ])) {
4568
4603
if ($ pCell === null ) {
4569
4604
// We can't access the range, so return a REF error
@@ -4596,7 +4631,7 @@ private function processTokenStack($tokens, $cellID = null, ?Cell $pCell = null)
4596
4631
}
4597
4632
} else {
4598
4633
if ($ pCell === null ) {
4599
- // We can't access the cell, so return a REF error
4634
+ // We can't access the cell, so return a REF error
4600
4635
$ cellValue = Functions::REF ();
4601
4636
} else {
4602
4637
$ cellRef = $ matches [6 ] . $ matches [7 ];
@@ -4613,6 +4648,7 @@ private function processTokenStack($tokens, $cellID = null, ?Cell $pCell = null)
4613
4648
$ cellValue = $ this ->extractCellRange ($ cellRef , $ this ->spreadsheet ->getSheetByName ($ matches [2 ]), false );
4614
4649
$ pCell ->attach ($ pCellParent );
4615
4650
} else {
4651
+ $ cellRef = ($ cellSheet !== null ) ? "{$ matches [2 ]}! {$ cellRef }" : $ cellRef ;
4616
4652
$ cellValue = null ;
4617
4653
}
4618
4654
} else {
@@ -4631,7 +4667,8 @@ private function processTokenStack($tokens, $cellID = null, ?Cell $pCell = null)
4631
4667
}
4632
4668
}
4633
4669
}
4634
- $ stack ->push ('Value ' , $ cellValue , $ cellRef );
4670
+
4671
+ $ stack ->push ('Cell Value ' , $ cellValue , $ cellRef );
4635
4672
if (isset ($ storeKey )) {
4636
4673
$ branchStore [$ storeKey ] = $ cellValue ;
4637
4674
}
@@ -5116,6 +5153,7 @@ public function extractCellRange(&$pRange = 'A1', ?Worksheet $pSheet = null, $re
5116
5153
5117
5154
if ($ pSheet !== null ) {
5118
5155
$ pSheetName = $ pSheet ->getTitle ();
5156
+
5119
5157
if (strpos ($ pRange , '! ' ) !== false ) {
5120
5158
[$ pSheetName , $ pRange ] = Worksheet::extractSheetTitle ($ pRange , true );
5121
5159
$ pSheet = $ this ->spreadsheet ->getSheetByName ($ pSheetName );
0 commit comments