Repository: PHPOffice/PhpSpreadsheet (php)

Issue:
XLS 2003 VSLOOKUP parsing error
Tested with Version 3.9.0

The Following Code produces the error: 
"PhpOffice\PhpSpreadsheet\Writer\Xls\Parser::convertFunction(): Argument #2 ($num_args) must be of type int, string given"

Code:

```
        $reader = IOFactory::createReader(IOFactory::READER_XLS);
        $reader->setReadDataOnly(true);
        $excel = $reader->load(dirname(__FILE__).'/VSLOOKUP_example_2003.xls');
        $writer = IOFactory::createWriter($excel, IOFactory::WRITER_XLS);
        $writer->save(dirname(__FILE__).'/VSLOOKUP_output_2003.xls');
```

File (German XLS File with German Function Name 'SVERWEIS' for 'VSLOOKUP':
[VSLOOKUP_example_2003.xls](https://github.com/user-attachments/files/18570613/VSLOOKUP_example_2003.xls)

Pull request description:
Xls Writer Parser Mis-handling TRUE/FALSE As VLOOKUP Arguments
Fix #4331. Parser treats TRUE/FALSE as functions rather than constants, which is nominally harmless, but it then expects an argument count of 0 and instead sees null-string. Changed to recognize this situation and leave TRUE/FALSE/TRUE()/FALSE() unchanged.

This is:

- [x] a bugfix
- [ ] a new feature
- [ ] refactoring
- [ ] additional unit tests

Checklist:

- [x] Changes are covered by unit tests
  - [ ] Changes are covered by existing unit tests
  - [x] New unit tests have been added
- [x] Code style is respected
- [x] Commit message explains **why** the change is made (see https://github.com/erlang/otp/wiki/Writing-good-commit-messages)
- [ ] CHANGELOG.md contains a short summary of the change and a link to the pull request if applicable
- [ ] Documentation is updated as necessary


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 641a12c81..c73f33a8d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -25,7 +25,7 @@ and this project adheres to [Semantic Versioning](https://semver.org).
 
 ### Fixed
 
-- Nothing yet.
+- Xls writer Parser Mishandling True/False Argument. [Issue #4331](https://github.com/PHPOffice/PhpSpreadsheet/issues/4331) [PR #4333](https://github.com/PHPOffice/PhpSpreadsheet/pull/4333)
 
 ## 2025-01-26 - 3.9.0
 
diff --git a/src/PhpSpreadsheet/Writer/Xls/Parser.php b/src/PhpSpreadsheet/Writer/Xls/Parser.php
index 52b4ad353..ec1114a4a 100644
--- a/src/PhpSpreadsheet/Writer/Xls/Parser.php
+++ b/src/PhpSpreadsheet/Writer/Xls/Parser.php
@@ -1624,7 +1624,9 @@ class Parser
             }
 
             // add its left subtree and return.
-            return $left_tree . $this->convertFunction($tree['value'], $tree['right']);
+            if ($left_tree !== '' || $tree['right'] !== '') {
+                return $left_tree . $this->convertFunction($tree['value'], $tree['right'] ?: 0);
+            }
         }
         $converted_tree = $this->convert($tree['value']);
 


Test patch:
diff --git a/tests/PhpSpreadsheetTests/Writer/Xls/Issue4331Test.php b/tests/PhpSpreadsheetTests/Writer/Xls/Issue4331Test.php
new file mode 100644
index 000000000..77841d829
--- /dev/null
+++ b/tests/PhpSpreadsheetTests/Writer/Xls/Issue4331Test.php
@@ -0,0 +1,56 @@
+<?php
+
+declare(strict_types=1);
+
+namespace PhpOffice\PhpSpreadsheetTests\Writer\Xls;
+
+use PhpOffice\PhpSpreadsheet\Spreadsheet;
+use PhpOffice\PhpSpreadsheetTests\Functional\AbstractFunctional;
+
+class Issue4331Test extends AbstractFunctional
+{
+    public function testIssue4331(): void
+    {
+        $spreadsheet = new Spreadsheet();
+        $sheet = $spreadsheet->getActiveSheet();
+        $c3 = '=VLOOKUP(B3,$B$10:$C$13,2,FALSE)';
+        $d3 = '=VLOOKUP("intermediate",$B$10:$C$13,2,TRUE)';
+        $c4 = '=VLOOKUP(B3,$B$10:$C$13,2,FALSE())';
+        $d4 = '=VLOOKUP("intermediate",$B$10:$C$13,2,TRUE())';
+        $sheet->fromArray(
+            [
+                ['level', 'result'],
+                ['medium', $c3, $d3],
+                [null, $c4, $d4],
+            ],
+            null,
+            'B2',
+            true
+        );
+        $sheet->fromArray(
+            [
+                ['high', 6],
+                ['low', 2],
+                ['medium', 4],
+                ['none', 0],
+            ],
+            null,
+            'B10',
+            true
+        );
+
+        $reloadedSpreadsheet = $this->writeAndReload($spreadsheet, 'Xls');
+        $spreadsheet->disconnectWorksheets();
+
+        $worksheet = $reloadedSpreadsheet->getActiveSheet();
+        self::assertSame($c3, $worksheet->getCell('C3')->getValue());
+        self::assertSame(4, $worksheet->getCell('C3')->getCalculatedValue());
+        self::assertSame($d3, $worksheet->getCell('D3')->getValue());
+        self::assertSame(6, $worksheet->getCell('D3')->getCalculatedValue());
+        self::assertSame($c4, $worksheet->getCell('C4')->getValue());
+        self::assertSame(4, $worksheet->getCell('C4')->getCalculatedValue());
+        self::assertSame($d4, $worksheet->getCell('D4')->getValue());
+        self::assertSame(6, $worksheet->getCell('D4')->getCalculatedValue());
+        $reloadedSpreadsheet->disconnectWorksheets();
+    }
+}