Skip to content

Commit b53a4b7

Browse files
committed
Xlsx Reader Use Dimensions from Functions With Array Results
Thinking about #3958 - user wondered if unsupported formulas with array results could be handled better. I said that the answer was "no", but I think Xlsx Reader can make use of the dimensions of the result after all, so the answer is actually "sometimes". This is an initial attempt to do that. Implementing it revealed a bug in how Xlsx Reader handles array formula attributes, and that is now corrected. Likewise, Xlsx Writer did not indicate a value for the first cell in the array, and does now.
1 parent be25c44 commit b53a4b7

File tree

4 files changed

+61
-7
lines changed

4 files changed

+61
-7
lines changed

src/PhpSpreadsheet/Reader/Xlsx.php

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -891,9 +891,16 @@ protected function loadSpreadsheetFromFile(string $filename): Spreadsheet
891891
} else {
892892
// Formula
893893
$this->castToFormula($c, $r, $cellDataType, $value, $calculatedValue, 'castToString');
894-
if (isset($c->f['t'])) {
895-
$attributes = $c->f['t'];
896-
$docSheet->getCell($r)->setFormulaAttributes(['t' => (string) $attributes]);
894+
$formulaAttributes = [];
895+
$attributes = $c->f->attributes();
896+
if (isset($attributes['t'])) {
897+
$formulaAttributes['t'] = (string) $attributes['t'];
898+
}
899+
if (isset($attributes['ref'])) {
900+
$formulaAttributes['ref'] = (string) $attributes['ref'];
901+
}
902+
if (!empty($formulaAttributes)) {
903+
$docSheet->getCell($r)->setFormulaAttributes($formulaAttributes);
897904
}
898905
}
899906

src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1401,8 +1401,8 @@ private function writeCellFormula(XMLWriter $objWriter, string $cellValue, Cell
14011401
$calculatedValue = (int) $calculatedValue;
14021402
}
14031403

1404-
$attributes = $cell->getFormulaAttributes();
1405-
$ref = $cell->getCoordinate();
1404+
$attributes = $cell->getFormulaAttributes() ?? [];
1405+
$ref = array_key_exists('ref', $attributes) ? $attributes['ref'] : $cell->getCoordinate();
14061406
if (is_array($calculatedValue)) {
14071407
$attributes['t'] = 'array';
14081408
$rows = max(1, count($calculatedValue));
@@ -1424,6 +1424,17 @@ private function writeCellFormula(XMLWriter $objWriter, string $cellValue, Cell
14241424
$objWriter->writeAttribute('ca', '1');
14251425
$objWriter->text(FunctionPrefix::addFunctionPrefixStripEquals($cellValue));
14261426
$objWriter->endElement();
1427+
$result = $calculatedValue;
1428+
while (is_array($result)) {
1429+
$result = array_shift($result);
1430+
}
1431+
if (
1432+
is_scalar($result)
1433+
&& $this->getParentWriter()->getOffice2003Compatibility() === false
1434+
&& $this->getParentWriter()->getPreCalculateFormulas()
1435+
) {
1436+
$objWriter->writeElement('v', (string) $result);
1437+
}
14271438
} else {
14281439
$objWriter->writeElement('f', FunctionPrefix::addFunctionPrefixStripEquals($cellValue));
14291440
self::writeElementIf(

tests/PhpSpreadsheetTests/Writer/Xlsx/ArrayFunctionsTest.php

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,8 +120,44 @@ public function testArrayOutput(): void
120120
if ($data === false) {
121121
self::fail('Unable to read file');
122122
} else {
123-
self::assertStringContainsString('<c r="C1"><f t="array" ref="C1:C15" aca="1" ca="1">_xlfn.UNIQUE(A1:A19)</f></c>', $data, '15 results for UNIQUE');
124-
self::assertStringContainsString('<c r="D1"><f t="array" ref="D1:D19" aca="1" ca="1">_xlfn._xlws.SORT(A1:A19)</f></c>', $data, '19 results for SORT');
123+
self::assertStringContainsString('<c r="C1"><f t="array" ref="C1:C15" aca="1" ca="1">_xlfn.UNIQUE(A1:A19)</f><v>41</v></c>', $data, '15 results for UNIQUE');
124+
self::assertStringContainsString('<c r="D1"><f t="array" ref="D1:D19" aca="1" ca="1">_xlfn._xlws.SORT(A1:A19)</f><v>26</v></c>', $data, '19 results for SORT');
125+
}
126+
}
127+
128+
public function testUnimplementedArrayOutput(): void
129+
{
130+
//Calculation::setArrayReturnType(Calculation::RETURN_ARRAY_AS_ARRAY); // not required for this test
131+
$reader = new XlsxReader();
132+
$spreadsheet = $reader->load('tests/data/Reader/XLSX/atsign.choosecols.xlsx');
133+
$writer = new XlsxWriter($spreadsheet);
134+
$this->outputFile = File::temporaryFilename();
135+
$writer->save($this->outputFile);
136+
$spreadsheet->disconnectWorksheets();
137+
138+
$reader = new XlsxReader();
139+
$spreadsheet2 = $reader->load($this->outputFile);
140+
$sheet2 = $spreadsheet2->getActiveSheet();
141+
self::assertSame('=_xlfn.CHOOSECOLS(A1:C5,3,1)', $sheet2->getCell('F1')->getValue());
142+
$expectedFG = [
143+
['11', '1'],
144+
['12', '2'],
145+
['13', '3'],
146+
['14', '4'],
147+
['15', '5'],
148+
];
149+
$actualFG = $sheet2->rangeToArray('F1:G5');
150+
self::assertSame($expectedFG, $actualFG);
151+
$spreadsheet2->disconnectWorksheets();
152+
153+
$file = 'zip://';
154+
$file .= $this->outputFile;
155+
$file .= '#xl/worksheets/sheet1.xml';
156+
$data = file_get_contents($file);
157+
if ($data === false) {
158+
self::fail('Unable to read file');
159+
} else {
160+
self::assertStringContainsString('<f t="array" ref="F1:G5" aca="1" ca="1">_xlfn.CHOOSECOLS(A1:C5,3,1)</f><v>11</v>', $data);
125161
}
126162
}
127163
}
9.33 KB
Binary file not shown.

0 commit comments

Comments
 (0)