Skip to content

Commit d06230e

Browse files
committed
Allow ANCHORARRAY as Valid DataValidation List
Fix #4197. Overlooked in the introduction of Dynamic Arrays, Data Validation can specify a list to be a result of the spill operator, which is implemented via the ANCHORARRAY function. It appears that function `DataValidator::isValid` will not work if the list is specified in this manner, nor if it is specified as a defined name. Fixing those situations will be difficult (defined names probably easier than ANCHORARRAY), and there is no reason to delay this change waiting for those to be fixed. I will open a new issue when this PR is merged.
1 parent 8799a04 commit d06230e

File tree

5 files changed

+101
-11
lines changed

5 files changed

+101
-11
lines changed

src/PhpSpreadsheet/Reader/Xlsx.php

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -304,15 +304,19 @@ private static function castToString(?SimpleXMLElement $c): ?string
304304
return isset($c, $c->v) ? (string) $c->v : null;
305305
}
306306

307+
public static function replacePrefixes(string $formula): string
308+
{
309+
return str_replace(['_xlfn.', '_xlws.'], '', $formula);
310+
}
311+
307312
private function castToFormula(?SimpleXMLElement $c, string $r, string &$cellDataType, mixed &$value, mixed &$calculatedValue, string $castBaseType, bool $updateSharedCells = true): void
308313
{
309314
if ($c === null) {
310315
return;
311316
}
312317
$attr = $c->f->attributes();
313318
$cellDataType = DataType::TYPE_FORMULA;
314-
$formula = (string) $c->f;
315-
$formula = str_replace(['_xlfn.', '_xlws.'], '', $formula);
319+
$formula = self::replacePrefixes((string) $c->f);
316320
$value = "=$formula";
317321
$calculatedValue = self::$castBaseType($c);
318322

src/PhpSpreadsheet/Reader/Xlsx/DataValidations.php

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace PhpOffice\PhpSpreadsheet\Reader\Xlsx;
44

55
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
6+
use PhpOffice\PhpSpreadsheet\Reader\Xlsx;
67
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
78
use SimpleXMLElement;
89

@@ -55,8 +56,8 @@ public function load(): void
5556
$docValidation->setError((string) $dataValidation['error']);
5657
$docValidation->setPromptTitle((string) $dataValidation['promptTitle']);
5758
$docValidation->setPrompt((string) $dataValidation['prompt']);
58-
$docValidation->setFormula1((string) $dataValidation->formula1);
59-
$docValidation->setFormula2((string) $dataValidation->formula2);
59+
$docValidation->setFormula1(Xlsx::replacePrefixes((string) $dataValidation->formula1));
60+
$docValidation->setFormula2(Xlsx::replacePrefixes((string) $dataValidation->formula2));
6061
$docValidation->setSqref($range);
6162
}
6263
}

src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -983,10 +983,10 @@ private function writeDataValidations(XMLWriter $objWriter, PhpspreadsheetWorksh
983983
$objWriter->writeAttribute('sqref', $dv->getSqref() ?? $coordinate);
984984

985985
if ($dv->getFormula1() !== '') {
986-
$objWriter->writeElement('formula1', $dv->getFormula1());
986+
$objWriter->writeElement('formula1', FunctionPrefix::addFunctionPrefix($dv->getFormula1()));
987987
}
988988
if ($dv->getFormula2() !== '') {
989-
$objWriter->writeElement('formula2', $dv->getFormula2());
989+
$objWriter->writeElement('formula2', FunctionPrefix::addFunctionPrefix($dv->getFormula2()));
990990
}
991991

992992
$objWriter->endElement();

tests/PhpSpreadsheetTests/Writer/Xlsx/ArrayFormulaPrefixTest.php

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,6 @@
1111

1212
class ArrayFormulaPrefixTest extends AbstractFunctional
1313
{
14-
protected function setUp(): void
15-
{
16-
parent::setUp();
17-
}
18-
1914
/**
2015
* Test to ensure that xlfn prefix is being added to functions
2116
* included in an array formula, if appropriate.
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PhpOffice\PhpSpreadsheetTests\Writer\Xlsx;
6+
7+
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
8+
use PhpOffice\PhpSpreadsheet\Cell\DataValidation;
9+
use PhpOffice\PhpSpreadsheet\Reader\Xlsx as XlsxReader;
10+
use PhpOffice\PhpSpreadsheet\Shared\File;
11+
use PhpOffice\PhpSpreadsheet\Spreadsheet;
12+
use PhpOffice\PhpSpreadsheet\Writer\Xlsx as XlsxWriter;
13+
use PHPUnit\Framework\TestCase;
14+
15+
class ArrayFormulaValidationTest extends TestCase
16+
{
17+
private string $outputFile = '';
18+
19+
protected function tearDown(): void
20+
{
21+
if ($this->outputFile !== '') {
22+
unlink($this->outputFile);
23+
$this->outputFile = '';
24+
}
25+
}
26+
27+
/**
28+
* @dataProvider validationProvider
29+
*/
30+
public function testWriteArrayFormulaValidation(string $formula): void
31+
{
32+
$spreadsheet = new Spreadsheet();
33+
$worksheet = $spreadsheet->getActiveSheet();
34+
35+
$spreadsheet = new Spreadsheet();
36+
Calculation::getInstance($spreadsheet)
37+
->setInstanceArrayReturnType(
38+
Calculation::RETURN_ARRAY_AS_ARRAY
39+
);
40+
$sheet = $spreadsheet->getActiveSheet();
41+
$sheet->setCellValue('C1', 1);
42+
$sheet->setCellValue('C2', 2);
43+
$sheet->setCellValue('C3', 3);
44+
$sheet->setCellValue('C4', 3);
45+
$sheet->setCellValue('C5', 5);
46+
$sheet->setCellValue('C6', 6);
47+
$sheet->setCellValue('B1', '=UNIQUE(C1:C6)');
48+
49+
$validation = $sheet->getCell('A1')->getDataValidation();
50+
$validation->setType(DataValidation::TYPE_LIST);
51+
$validation->setErrorStyle(DataValidation::STYLE_STOP);
52+
$validation->setAllowBlank(true);
53+
$validation->setShowDropDown(true);
54+
$validation->setShowErrorMessage(true);
55+
$validation->setError('Invalid input');
56+
$validation->setFormula1($formula);
57+
$sheet->getCell('A1')->setDataValidation($validation);
58+
59+
$this->outputFile = File::temporaryFilename();
60+
$writer = new XlsxWriter($spreadsheet);
61+
$writer->save($this->outputFile);
62+
$spreadsheet->disconnectWorksheets();
63+
64+
$reader = new XlsxReader();
65+
$spreadsheet2 = $reader->load($this->outputFile);
66+
Calculation::getInstance($spreadsheet2)->setInstanceArrayReturnType(Calculation::RETURN_ARRAY_AS_ARRAY);
67+
$sheet2 = $spreadsheet2->getActiveSheet();
68+
$validation2 = $sheet2->getCell('A1')->getDataValidation();
69+
self::assertSame('ANCHORARRAY($B$1)', $validation2->getFormula1());
70+
$spreadsheet2->disconnectWorksheets();
71+
72+
$file = 'zip://';
73+
$file .= $this->outputFile;
74+
$file .= '#xl/worksheets/sheet1.xml';
75+
$data = file_get_contents($file);
76+
if ($data === false) {
77+
self::fail('Unable to read file');
78+
} else {
79+
self::assertStringContainsString('<formula1>_xlfn.ANCHORARRAY($B$1)</formula1>', $data);
80+
}
81+
}
82+
83+
public static function validationProvider(): array
84+
{
85+
return [
86+
['ANCHORARRAY($B$1)'],
87+
['$B$1#'],
88+
];
89+
}
90+
}

0 commit comments

Comments
 (0)