Skip to content

Commit 150b384

Browse files
committed
Create Uninitialized Cell If Used in Calculation
Fix #4558. Fix #4530. 4558 deals with a problem with COUNTA. Excel does not count empty cells, but it does count literal nulls (usually empty parameters) in the argument list. PhpSpreadsheet has till now not created empty cells during calculation, and so winds up treating them as literals. It is changed to create the cell when appropriate. 4530 has a similar problem. When INDIRECT winds up evaluating an uninitialized cell, it treats it as a null literal, leading to incorrect results. It is now changed to create the missing cell. A number of functions, almost all of them Financial, have a related problem. They test for a null literal when initializing some of their parameters, but they should be testing for null literal or cell containing null. They are changed to do the right thing. SUMIF and related functions are slightly affected by this change, and are changed so that they are no longer affected. New tests have been added. Only one existing test had to change. MergeBehaviorTest for `[12, '=5+1', '=A1/A2']`, where cell A2 was uninitialized formerly returned one row, but now returns a second all-null row because A2, by virtue of being used in a calculation, now winds up defined. I do not consider this a significant difference.
1 parent 7d1562a commit 150b384

File tree

16 files changed

+120
-78
lines changed

16 files changed

+120
-78
lines changed

src/PhpSpreadsheet/Calculation/Calculation.php

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1999,6 +1999,9 @@ private function processTokenStack(false|array $tokens, ?string $cellID = null,
19991999
$this->debugLog->writeDebugLog('Evaluating Cell %s in worksheet %s', $cellRef, $matches[2]);
20002000
if ($pCellParent !== null && $this->spreadsheet !== null) {
20012001
$cellSheet = $this->spreadsheet->getSheetByName($matches[2]);
2002+
if ($cellSheet && !$cellSheet->cellExists($cellRef)) {
2003+
$cellSheet->setCellValue($cellRef, null);
2004+
}
20022005
if ($cellSheet && $cellSheet->cellExists($cellRef)) {
20032006
$cellValue = $this->extractCellRange($cellRef, $this->spreadsheet->getSheetByName($matches[2]), false);
20042007
$cell->attach($pCellParent);
@@ -2503,7 +2506,7 @@ protected function raiseFormulaError(string $errorMessage, int $code = 0, ?Throw
25032506
*
25042507
* @return mixed[] Array of values in range if range contains more than one element. Otherwise, a single value is returned.
25052508
*/
2506-
public function extractCellRange(string &$range = 'A1', ?Worksheet $worksheet = null, bool $resetLog = true): array
2509+
public function extractCellRange(string &$range = 'A1', ?Worksheet $worksheet = null, bool $resetLog = true, bool $createCell = false): array
25072510
{
25082511
// Return value
25092512
/** @var mixed[][] */
@@ -2525,6 +2528,9 @@ public function extractCellRange(string &$range = 'A1', ?Worksheet $worksheet =
25252528
if (!isset($aReferences[1])) {
25262529
// Single cell in range
25272530
sscanf($aReferences[0], '%[A-Z]%d', $currentCol, $currentRow);
2531+
if ($createCell && $worksheet !== null && !$worksheet->cellExists($aReferences[0])) {
2532+
$worksheet->setCellValue($aReferences[0], null);
2533+
}
25282534
if ($worksheet !== null && $worksheet->cellExists($aReferences[0])) {
25292535
$temp = $worksheet->getCell($aReferences[0])->getCalculatedValue($resetLog);
25302536
if ($this->getInstanceArrayReturnType() === self::RETURN_ARRAY_AS_ARRAY) {
@@ -2541,6 +2547,9 @@ public function extractCellRange(string &$range = 'A1', ?Worksheet $worksheet =
25412547
foreach ($aReferences as $reference) {
25422548
// Extract range
25432549
sscanf($reference, '%[A-Z]%d', $currentCol, $currentRow);
2550+
if ($createCell && $worksheet !== null && !$worksheet->cellExists($reference)) {
2551+
$worksheet->setCellValue($reference, null);
2552+
}
25442553
if ($worksheet !== null && $worksheet->cellExists($reference)) {
25452554
$temp = $worksheet->getCell($reference)->getCalculatedValue($resetLog);
25462555
if ($this->getInstanceArrayReturnType() === self::RETURN_ARRAY_AS_ARRAY) {

src/PhpSpreadsheet/Calculation/Financial/Amortization.php

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -54,9 +54,7 @@ public static function AMORDEGRC(
5454
$salvage = Functions::flattenSingleValue($salvage);
5555
$period = Functions::flattenSingleValue($period);
5656
$rate = Functions::flattenSingleValue($rate);
57-
$basis = ($basis === null)
58-
? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD
59-
: Functions::flattenSingleValue($basis);
57+
$basis = Functions::flattenSingleValue($basis) ?? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD;
6058

6159
try {
6260
$cost = FinancialValidations::validateFloat($cost);
@@ -141,9 +139,7 @@ public static function AMORLINC(
141139
$salvage = Functions::flattenSingleValue($salvage);
142140
$period = Functions::flattenSingleValue($period);
143141
$rate = Functions::flattenSingleValue($rate);
144-
$basis = ($basis === null)
145-
? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD
146-
: Functions::flattenSingleValue($basis);
142+
$basis = Functions::flattenSingleValue($basis) ?? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD;
147143

148144
try {
149145
$cost = FinancialValidations::validateFloat($cost);

src/PhpSpreadsheet/Calculation/Financial/CashFlow/Constant/Periodic.php

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,9 @@ public static function futureValue(
3838
): string|float {
3939
$rate = Functions::flattenSingleValue($rate);
4040
$numberOfPeriods = Functions::flattenSingleValue($numberOfPeriods);
41-
$payment = ($payment === null) ? 0.0 : Functions::flattenSingleValue($payment);
42-
$presentValue = ($presentValue === null) ? 0.0 : Functions::flattenSingleValue($presentValue);
43-
$type = ($type === null) ? FinancialConstants::PAYMENT_END_OF_PERIOD : Functions::flattenSingleValue($type);
41+
$payment = Functions::flattenSingleValue($payment) ?? 0.0;
42+
$presentValue = Functions::flattenSingleValue($presentValue) ?? 0.0;
43+
$type = Functions::flattenSingleValue($type) ?? FinancialConstants::PAYMENT_END_OF_PERIOD;
4444

4545
try {
4646
$rate = CashFlowValidations::validateRate($rate);
@@ -77,9 +77,9 @@ public static function presentValue(
7777
): string|float {
7878
$rate = Functions::flattenSingleValue($rate);
7979
$numberOfPeriods = Functions::flattenSingleValue($numberOfPeriods);
80-
$payment = ($payment === null) ? 0.0 : Functions::flattenSingleValue($payment);
81-
$futureValue = ($futureValue === null) ? 0.0 : Functions::flattenSingleValue($futureValue);
82-
$type = ($type === null) ? FinancialConstants::PAYMENT_END_OF_PERIOD : Functions::flattenSingleValue($type);
80+
$payment = Functions::flattenSingleValue($payment) ?? 0.0;
81+
$futureValue = Functions::flattenSingleValue($futureValue) ?? 0.0;
82+
$type = Functions::flattenSingleValue($type) ?? FinancialConstants::PAYMENT_END_OF_PERIOD;
8383

8484
try {
8585
$rate = CashFlowValidations::validateRate($rate);
@@ -122,8 +122,8 @@ public static function periods(
122122
$rate = Functions::flattenSingleValue($rate);
123123
$payment = Functions::flattenSingleValue($payment);
124124
$presentValue = Functions::flattenSingleValue($presentValue);
125-
$futureValue = ($futureValue === null) ? 0.0 : Functions::flattenSingleValue($futureValue);
126-
$type = ($type === null) ? FinancialConstants::PAYMENT_END_OF_PERIOD : Functions::flattenSingleValue($type);
125+
$futureValue = Functions::flattenSingleValue($futureValue) ?? 0.0;
126+
$type = Functions::flattenSingleValue($type) ?? FinancialConstants::PAYMENT_END_OF_PERIOD;
127127

128128
try {
129129
$rate = CashFlowValidations::validateRate($rate);

src/PhpSpreadsheet/Calculation/Financial/CashFlow/Constant/Periodic/Cumulative.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ public static function interest(
4141
$presentValue = Functions::flattenSingleValue($presentValue);
4242
$start = Functions::flattenSingleValue($start);
4343
$end = Functions::flattenSingleValue($end);
44-
$type = ($type === null) ? FinancialConstants::PAYMENT_END_OF_PERIOD : Functions::flattenSingleValue($type);
44+
$type = Functions::flattenSingleValue($type) ?? FinancialConstants::PAYMENT_END_OF_PERIOD;
4545

4646
try {
4747
$rate = CashFlowValidations::validateRate($rate);
@@ -104,7 +104,7 @@ public static function principal(
104104
$presentValue = Functions::flattenSingleValue($presentValue);
105105
$start = Functions::flattenSingleValue($start);
106106
$end = Functions::flattenSingleValue($end);
107-
$type = ($type === null) ? FinancialConstants::PAYMENT_END_OF_PERIOD : Functions::flattenSingleValue($type);
107+
$type = Functions::flattenSingleValue($type) ?? FinancialConstants::PAYMENT_END_OF_PERIOD;
108108

109109
try {
110110
$rate = CashFlowValidations::validateRate($rate);

src/PhpSpreadsheet/Calculation/Financial/CashFlow/Constant/Periodic/Interest.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ public static function payment(
4343
$numberOfPeriods = Functions::flattenSingleValue($numberOfPeriods);
4444
$presentValue = Functions::flattenSingleValue($presentValue);
4545
$futureValue = ($futureValue === null) ? 0.0 : Functions::flattenSingleValue($futureValue);
46-
$type = ($type === null) ? FinancialConstants::PAYMENT_END_OF_PERIOD : Functions::flattenSingleValue($type);
46+
$type = Functions::flattenSingleValue($type) ?? FinancialConstants::PAYMENT_END_OF_PERIOD;
4747

4848
try {
4949
$interestRate = CashFlowValidations::validateRate($interestRate);
@@ -160,9 +160,9 @@ public static function rate(
160160
$numberOfPeriods = Functions::flattenSingleValue($numberOfPeriods);
161161
$payment = Functions::flattenSingleValue($payment);
162162
$presentValue = Functions::flattenSingleValue($presentValue);
163-
$futureValue = ($futureValue === null) ? 0.0 : Functions::flattenSingleValue($futureValue);
164-
$type = ($type === null) ? FinancialConstants::PAYMENT_END_OF_PERIOD : Functions::flattenSingleValue($type);
165-
$guess = ($guess === null) ? 0.1 : Functions::flattenSingleValue($guess);
163+
$futureValue = Functions::flattenSingleValue($futureValue) ?? 0.0;
164+
$type = Functions::flattenSingleValue($type) ?? FinancialConstants::PAYMENT_END_OF_PERIOD;
165+
$guess = Functions::flattenSingleValue($guess) ?? 0.1;
166166

167167
try {
168168
$numberOfPeriods = CashFlowValidations::validateFloat($numberOfPeriods);

src/PhpSpreadsheet/Calculation/Financial/CashFlow/Constant/Periodic/Payments.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,14 +27,14 @@ public static function annuity(
2727
mixed $interestRate,
2828
mixed $numberOfPeriods,
2929
mixed $presentValue,
30-
mixed $futureValue = 0,
30+
mixed $futureValue = 0.0,
3131
mixed $type = FinancialConstants::PAYMENT_END_OF_PERIOD
3232
): string|float {
3333
$interestRate = Functions::flattenSingleValue($interestRate);
3434
$numberOfPeriods = Functions::flattenSingleValue($numberOfPeriods);
3535
$presentValue = Functions::flattenSingleValue($presentValue);
36-
$futureValue = ($futureValue === null) ? 0.0 : Functions::flattenSingleValue($futureValue);
37-
$type = ($type === null) ? FinancialConstants::PAYMENT_END_OF_PERIOD : Functions::flattenSingleValue($type);
36+
$futureValue = Functions::flattenSingleValue($futureValue) ?? 0.0;
37+
$type = Functions::flattenSingleValue($type) ?? FinancialConstants::PAYMENT_END_OF_PERIOD;
3838

3939
try {
4040
$interestRate = CashFlowValidations::validateRate($interestRate);
@@ -83,7 +83,7 @@ public static function interestPayment(
8383
$numberOfPeriods = Functions::flattenSingleValue($numberOfPeriods);
8484
$presentValue = Functions::flattenSingleValue($presentValue);
8585
$futureValue = ($futureValue === null) ? 0.0 : Functions::flattenSingleValue($futureValue);
86-
$type = ($type === null) ? FinancialConstants::PAYMENT_END_OF_PERIOD : Functions::flattenSingleValue($type);
86+
$type = Functions::flattenSingleValue($type) ?? FinancialConstants::PAYMENT_END_OF_PERIOD;
8787

8888
try {
8989
$interestRate = CashFlowValidations::validateRate($interestRate);

src/PhpSpreadsheet/Calculation/Financial/Coupons.php

Lines changed: 6 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,7 @@ public static function COUPDAYBS(
4949
$settlement = Functions::flattenSingleValue($settlement);
5050
$maturity = Functions::flattenSingleValue($maturity);
5151
$frequency = Functions::flattenSingleValue($frequency);
52-
$basis = ($basis === null)
53-
? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD
54-
: Functions::flattenSingleValue($basis);
52+
$basis = Functions::flattenSingleValue($basis) ?? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD;
5553

5654
try {
5755
$settlement = FinancialValidations::validateSettlementDate($settlement);
@@ -110,9 +108,7 @@ public static function COUPDAYS(
110108
$settlement = Functions::flattenSingleValue($settlement);
111109
$maturity = Functions::flattenSingleValue($maturity);
112110
$frequency = Functions::flattenSingleValue($frequency);
113-
$basis = ($basis === null)
114-
? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD
115-
: Functions::flattenSingleValue($basis);
111+
$basis = Functions::flattenSingleValue($basis) ?? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD;
116112

117113
try {
118114
$settlement = FinancialValidations::validateSettlementDate($settlement);
@@ -179,9 +175,7 @@ public static function COUPDAYSNC(
179175
$settlement = Functions::flattenSingleValue($settlement);
180176
$maturity = Functions::flattenSingleValue($maturity);
181177
$frequency = Functions::flattenSingleValue($frequency);
182-
$basis = ($basis === null)
183-
? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD
184-
: Functions::flattenSingleValue($basis);
178+
$basis = Functions::flattenSingleValue($basis) ?? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD;
185179

186180
try {
187181
$settlement = FinancialValidations::validateSettlementDate($settlement);
@@ -244,9 +238,7 @@ public static function COUPNCD(
244238
$settlement = Functions::flattenSingleValue($settlement);
245239
$maturity = Functions::flattenSingleValue($maturity);
246240
$frequency = Functions::flattenSingleValue($frequency);
247-
$basis = ($basis === null)
248-
? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD
249-
: Functions::flattenSingleValue($basis);
241+
$basis = Functions::flattenSingleValue($basis) ?? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD;
250242

251243
try {
252244
$settlement = FinancialValidations::validateSettlementDate($settlement);
@@ -296,9 +288,7 @@ public static function COUPNUM(
296288
$settlement = Functions::flattenSingleValue($settlement);
297289
$maturity = Functions::flattenSingleValue($maturity);
298290
$frequency = Functions::flattenSingleValue($frequency);
299-
$basis = ($basis === null)
300-
? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD
301-
: Functions::flattenSingleValue($basis);
291+
$basis = Functions::flattenSingleValue($basis) ?? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD;
302292

303293
try {
304294
$settlement = FinancialValidations::validateSettlementDate($settlement);
@@ -355,9 +345,7 @@ public static function COUPPCD(
355345
$settlement = Functions::flattenSingleValue($settlement);
356346
$maturity = Functions::flattenSingleValue($maturity);
357347
$frequency = Functions::flattenSingleValue($frequency);
358-
$basis = ($basis === null)
359-
? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD
360-
: Functions::flattenSingleValue($basis);
348+
$basis = Functions::flattenSingleValue($basis) ?? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD;
361349

362350
try {
363351
$settlement = FinancialValidations::validateSettlementDate($settlement);

src/PhpSpreadsheet/Calculation/Financial/Securities/AccruedInterest.php

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -60,12 +60,8 @@ public static function periodic(
6060
$settlement = Functions::flattenSingleValue($settlement);
6161
$rate = Functions::flattenSingleValue($rate);
6262
$parValue = ($parValue === null) ? 1000 : Functions::flattenSingleValue($parValue);
63-
$frequency = ($frequency === null)
64-
? FinancialConstants::FREQUENCY_ANNUAL
65-
: Functions::flattenSingleValue($frequency);
66-
$basis = ($basis === null)
67-
? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD
68-
: Functions::flattenSingleValue($basis);
63+
$frequency = Functions::flattenSingleValue($frequency) ?? FinancialConstants::FREQUENCY_ANNUAL;
64+
$basis = Functions::flattenSingleValue($basis) ?? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD;
6965

7066
try {
7167
$issue = SecurityValidations::validateIssueDate($issue);
@@ -126,9 +122,7 @@ public static function atMaturity(
126122
$settlement = Functions::flattenSingleValue($settlement);
127123
$rate = Functions::flattenSingleValue($rate);
128124
$parValue = ($parValue === null) ? 1000 : Functions::flattenSingleValue($parValue);
129-
$basis = ($basis === null)
130-
? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD
131-
: Functions::flattenSingleValue($basis);
125+
$basis = Functions::flattenSingleValue($basis) ?? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD;
132126

133127
try {
134128
$issue = SecurityValidations::validateIssueDate($issue);

src/PhpSpreadsheet/Calculation/Financial/Securities/Price.php

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,7 @@ public static function price(
5353
$yield = Functions::flattenSingleValue($yield);
5454
$redemption = Functions::flattenSingleValue($redemption);
5555
$frequency = Functions::flattenSingleValue($frequency);
56-
$basis = ($basis === null)
57-
? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD
58-
: Functions::flattenSingleValue($basis);
56+
$basis = Functions::flattenSingleValue($basis) ?? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD;
5957

6058
try {
6159
$settlement = SecurityValidations::validateSettlementDate($settlement);
@@ -120,9 +118,7 @@ public static function priceDiscounted(
120118
$maturity = Functions::flattenSingleValue($maturity);
121119
$discount = Functions::flattenSingleValue($discount);
122120
$redemption = Functions::flattenSingleValue($redemption);
123-
$basis = ($basis === null)
124-
? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD
125-
: Functions::flattenSingleValue($basis);
121+
$basis = Functions::flattenSingleValue($basis) ?? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD;
126122

127123
try {
128124
$settlement = SecurityValidations::validateSettlementDate($settlement);
@@ -179,9 +175,7 @@ public static function priceAtMaturity(
179175
$issue = Functions::flattenSingleValue($issue);
180176
$rate = Functions::flattenSingleValue($rate);
181177
$yield = Functions::flattenSingleValue($yield);
182-
$basis = ($basis === null)
183-
? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD
184-
: Functions::flattenSingleValue($basis);
178+
$basis = Functions::flattenSingleValue($basis) ?? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD;
185179

186180
try {
187181
$settlement = SecurityValidations::validateSettlementDate($settlement);
@@ -255,9 +249,7 @@ public static function received(
255249
$maturity = Functions::flattenSingleValue($maturity);
256250
$investment = Functions::flattenSingleValue($investment);
257251
$discount = Functions::flattenSingleValue($discount);
258-
$basis = ($basis === null)
259-
? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD
260-
: Functions::flattenSingleValue($basis);
252+
$basis = Functions::flattenSingleValue($basis) ?? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD;
261253

262254
try {
263255
$settlement = SecurityValidations::validateSettlementDate($settlement);

src/PhpSpreadsheet/Calculation/Financial/Securities/Rates.php

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,7 @@ public static function discount(
4444
$maturity = Functions::flattenSingleValue($maturity);
4545
$price = Functions::flattenSingleValue($price);
4646
$redemption = Functions::flattenSingleValue($redemption);
47-
$basis = ($basis === null)
48-
? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD
49-
: Functions::flattenSingleValue($basis);
47+
$basis = Functions::flattenSingleValue($basis) ?? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD;
5048

5149
try {
5250
$settlement = SecurityValidations::validateSettlementDate($settlement);
@@ -105,9 +103,7 @@ public static function interest(
105103
$maturity = Functions::flattenSingleValue($maturity);
106104
$investment = Functions::flattenSingleValue($investment);
107105
$redemption = Functions::flattenSingleValue($redemption);
108-
$basis = ($basis === null)
109-
? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD
110-
: Functions::flattenSingleValue($basis);
106+
$basis = Functions::flattenSingleValue($basis) ?? FinancialConstants::BASIS_DAYS_PER_YEAR_NASD;
111107

112108
try {
113109
$settlement = SecurityValidations::validateSettlementDate($settlement);

0 commit comments

Comments
 (0)