Restore imperfect array formula values in xlsx writer

oleibman said:

The results of uncommenting the statements will often not be successful.
In Excel, if I enter `=MINVERSE({2,0;0,1})` into cell A1, you will get a
`dynamic array` (which we do not yet support) - A1 will contain 0.5, A2
and B1 will contain 0, and B2 will contain 1. There are also `spill`
implications for such a formula. The XML for these cells will be:

``` xml
<row r="1" spans="1:2" x14ac:dyDescent="0.3">
<c r="A1" cm="1">
<f t="array" ref="A1:B2">MINVERSE({2,0;0,1})</f>
<v>0.5</v>
</c>
<c r="B1">
<v>0</v>
</c>
</row>
<row r="2" spans="1:2" x14ac:dyDescent="0.3">
<c r="A2">
<v>0</v>
</c>
<c r="B2">
<v>1</v>
</c>
</row>
```

I believe that the PhpSpreadsheet equivalent of doing this (with the statements
uncommented) is:

```php
        $spreadsheet = new Spreadsheet();
        $calculation = Calculation::getInstance($spreadsheet);
        $calculation::setArrayReturnType(Calculation::RETURN_ARRAY_AS_ARRAY);
        $sheet = $spreadsheet->getActiveSheet();
        $sheet->getCell('A1')->setValue('=MINVERSE({2,0;0,1})');
        $writer = new Xlsx($spreadsheet);
        $oufil = 'issue.2343.xlsx';
        $writer->save($oufil);
```

But our output file only fills in A1:

```xml
<row r="1" spans="1:1">
<c r="A1">
<f>MINVERSE({2,0;0,1})</f>
<v>0.5</v>
</c>
</row>
```

And, even though A1 has its correct value, note that its `f` tag does not have
a `t` attribute. This is because we never set any formula attributes, except
in Xlsx Reader (see next paragraph), so we do not encounter the `'array'`
condtion for a formula newly added to a spreadsheet.

We do slightly better when we read the first file (as opposed to creating a new
spreadsheet), but we succeed only by accident. Because B1, A2, and B2 are
assigned values in the XML, all 4 cells will have the expected values. But they
are now independent of each other, not part of a dynamic array. When we write
this out, it is almost correct:

```xml
<row r="1" spans="1:2">
<c r="A1">
<f>MINVERSE({2,0;0,1})</f>
<v>0.5</v>
</c>
<c r="B1">
<v>0</v>
</c>
</row>
<row r="2" spans="1:2">
<c r="A2">
<v>0</v>
</c>
<c r="B2">
<v>1</v>
</c>
</row>
```

Again, the `f` tag has no `t` attribute, and it doesn't seem to matter whether we set
RETURN_TYPE_ARRAY or not. I think this particular aspect of the problem might be
relatively easy to fix.
This commit is contained in:
Roman Devman 2021-10-19 18:13:57 +03:00 committed by Adrien Crivelli
parent 0c93bbaaa3
commit a2be574f36
1 changed files with 20 additions and 21 deletions

View File

@ -1205,27 +1205,26 @@ class Worksheet extends WriterPart
$objWriter->writeAttribute('t', 'b');
$calculatedValue = (int) $calculatedValue;
}
// array values are not yet supported
//$attributes = $pCell->getFormulaAttributes();
//if (($attributes['t'] ?? null) === 'array') {
// $objWriter->startElement('f');
// $objWriter->writeAttribute('t', 'array');
// $objWriter->writeAttribute('ref', $pCellAddress);
// $objWriter->writeAttribute('aca', '1');
// $objWriter->writeAttribute('ca', '1');
// $objWriter->text(substr($cellValue, 1));
// $objWriter->endElement();
//} else {
// $objWriter->writeElement('f', Xlfn::addXlfnStripEquals($cellValue));
//}
$objWriter->writeElement('f', Xlfn::addXlfnStripEquals($cellValue));
self::writeElementIf(
$objWriter,
$this->getParentWriter()->getOffice2003Compatibility() === false,
'v',
($this->getParentWriter()->getPreCalculateFormulas() && !is_array($calculatedValue) && substr($calculatedValue ?? '', 0, 1) !== '#')
? StringHelper::formatNumber($calculatedValue) : '0'
);
$attributes = $cell->getFormulaAttributes();
if (($attributes['t'] ?? null) === 'array') {
$objWriter->startElement('f');
$objWriter->writeAttribute('t', 'array');
$objWriter->writeAttribute('ref', $cell->getCoordinate());
$objWriter->writeAttribute('aca', '1');
$objWriter->writeAttribute('ca', '1');
$objWriter->text(substr($cellValue, 1));
$objWriter->endElement();
} else {
$objWriter->writeElement('f', Xlfn::addXlfnStripEquals($cellValue));
self::writeElementIf(
$objWriter,
$this->getParentWriter()->getOffice2003Compatibility() === false,
'v',
($this->getParentWriter()->getPreCalculateFormulas() && !is_array($calculatedValue) && substr($calculatedValue, 0, 1) !== '#')
? StringHelper::formatNumber($calculatedValue) : '0'
);
}
}
/**