Skip to content

Commit e1bcab6

Browse files
committed
IF Empty Arguments
Fix #3875. Even better, fix #2146, which has been open for 2.5 years. Empty arguments are improperly placed on the stack; in particular, they are added without `onlyIf` and `onlyIfNot` attributes.This results in problems described in 3875. IF has a somewhat unexpected design. In Excel, `IF(false, valueIfTrue)` evaluates as `false`, but `IF(false, valueIfTrue,)` evaluates as 0. This means that IF empty arguments should be handled in the same manner as MIN/MAX/MINA/MAXA, but you need to be careful to distinguish empty from omitted. Also note that IF requires 2 operands - `IF(true)` is an error, but `IF(true,)` evaluates to 0.
1 parent 570c86f commit e1bcab6

File tree

3 files changed

+51
-6
lines changed

3 files changed

+51
-6
lines changed

CHANGELOG.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,28 @@ and this project adheres to [Semantic Versioning](https://semver.org).
77

88
## Unreleased - TBD
99

10+
### Added
11+
12+
- Nothing
13+
14+
### Changed
15+
16+
- Nothing
17+
18+
### Deprecated
19+
20+
- Nothing
21+
22+
### Removed
23+
24+
- Nothing
25+
26+
### Fixed
27+
28+
- IF Empty Arguments. [Issue #3875](https://github.com/PHPOffice/PhpSpreadsheet/issues/3875) [Issue #2146](https://github.com/PHPOffice/PhpSpreadsheet/issues/2146) [PR #3879](https://github.com/PHPOffice/PhpSpreadsheet/pull/3879)
29+
30+
## 2.0.0 - 2024-01-04
31+
1032
### BREAKING CHANGE
1133

1234
- Typing was strengthened by leveraging native typing. This should not change any behavior. However, if you implement

src/PhpSpreadsheet/Calculation/Calculation.php

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1307,7 +1307,7 @@ public static function getExcelConstants(string $key): bool|null
13071307
'IF' => [
13081308
'category' => Category::CATEGORY_LOGICAL,
13091309
'functionCall' => [Logical\Conditional::class, 'statementIf'],
1310-
'argumentCount' => '1-3',
1310+
'argumentCount' => '2-3',
13111311
],
13121312
'IFERROR' => [
13131313
'category' => Category::CATEGORY_LOGICAL,
@@ -4189,7 +4189,7 @@ private function internalParseFormula(string $formula, ?Cell $cell = null): bool
41894189
// If we've a comma when we're expecting an operand, then what we actually have is a null operand;
41904190
// so push a null onto the stack
41914191
if (($expectingOperand) || (!$expectingOperator)) {
4192-
$output[] = ['type' => 'Empty Argument', 'value' => self::$excelConstants['NULL'], 'reference' => 'NULL'];
4192+
$output[] = $stack->getStackItem('Empty Argument', null, 'NULL');
41934193
}
41944194
// make sure there was a function
41954195
$d = $stack->last(2);
@@ -4436,7 +4436,7 @@ private function internalParseFormula(string $formula, ?Cell $cell = null): bool
44364436
++$index;
44374437
} elseif ($opCharacter === ')') { // miscellaneous error checking
44384438
if ($expectingOperand) {
4439-
$output[] = ['type' => 'Empty Argument', 'value' => self::$excelConstants['NULL'], 'reference' => 'NULL'];
4439+
$output[] = $stack->getStackItem('Empty Argument', null, 'NULL');
44404440
$expectingOperand = false;
44414441
$expectingOperator = true;
44424442
} else {
@@ -4996,11 +4996,12 @@ private function processTokenStack(mixed $tokens, ?string $cellID = null, ?Cell
49964996
}
49974997
}
49984998
} else {
4999-
$emptyArguments[] = ($arg['type'] === 'Empty Argument');
5000-
if ($arg['type'] === 'Empty Argument' && in_array($functionName, ['MIN', 'MINA', 'MAX', 'MAXA'], true)) {
4999+
if ($arg['type'] === 'Empty Argument' && in_array($functionName, ['MIN', 'MINA', 'MAX', 'MAXA', 'IF'], true)) {
5000+
$emptyArguments[] = false;
50015001
$args[] = $arg['value'] = 0;
50025002
$this->debugLog->writeDebugLog('Empty Argument reevaluated as 0');
50035003
} else {
5004+
$emptyArguments[] = $arg['type'] === 'Empty Argument';
50045005
$args[] = self::unwrapResult($arg['value']);
50055006
}
50065007
if ($functionName !== 'MKMATRIX') {

tests/PhpSpreadsheetTests/Calculation/MissingArgumentsTest.php

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
namespace PhpOffice\PhpSpreadsheetTests\Calculation;
66

7+
use PhpOffice\PhpSpreadsheet\Calculation\Exception as CalcExp;
78
use PhpOffice\PhpSpreadsheet\Spreadsheet;
89
use PHPUnit\Framework\TestCase;
910

@@ -17,7 +18,14 @@ public function testMissingArguments(mixed $expected, string $formula): void
1718
$spreadsheet = new Spreadsheet();
1819
$sheet = $spreadsheet->getActiveSheet();
1920
$sheet->getCell('A1')->setValue($formula);
20-
self::assertSame($expected, $sheet->getCell('A1')->getCalculatedValue());
21+
$sheet->getCell('B1')->setValue(1);
22+
23+
try {
24+
self::assertSame($expected, $sheet->getCell('A1')->getCalculatedValue());
25+
} catch (CalcExp $e) {
26+
self::assertSame('exception', $expected);
27+
}
28+
2129
$spreadsheet->disconnectWorksheets();
2230
}
2331

@@ -35,6 +43,20 @@ public static function providerMissingArguments(): array
3543
'product ignores null argument' => [6.0, '=product(3,2,)'],
3644
'embedded function' => [5, '=sum(3,2,min(3,2,))'],
3745
'unaffected embedded function' => [8, '=sum(3,2,max(3,2,))'],
46+
'if true missing at end' => [0, '=if(b1=1,min(3,2,),product(3,2,))'],
47+
'if false missing at end' => [6.0, '=if(b1=2,min(3,2,),product(3,2,))'],
48+
'if true missing in middle' => [0, '=if(b1=1,min(3,,2),product(3,,2))'],
49+
'if false missing in middle' => [6.0, '=if(b1=2,min(3,,2),product(3,,2))'],
50+
'if true missing at beginning' => [0, '=if(b1=1,min(,3,2),product(,3,2))'],
51+
'if false missing at beginning' => [6.0, '=if(b1=2,min(,3,2),product(,3,2))'],
52+
'if true nothing missing' => [2, '=if(b1=1,min(3,2),product(3,2))'],
53+
'if false nothing missing' => [6.0, '=if(b1=2,min(3,2),product(3,2))'],
54+
'if true empty arg' => [0, '=if(b1=1,)'],
55+
'if true omitted args' => ['exception', '=if(b1=1)'],
56+
'if true missing arg' => [0, '=if(b1=1,,6)'],
57+
'if false missing arg' => [0, '=if(b1=2,6,)'],
58+
'if false omitted arg' => [false, '=if(b1=2,6)'],
59+
'multiple ifs and omissions' => [0, '=IF(0<9,,IF(0=0,,1))'],
3860
];
3961
}
4062
}

0 commit comments

Comments
 (0)