Skip to content

Commit af07ad1

Browse files
authored
Merge pull request #4271 from oleibman/issue4269
Add forceFullCalc Option to Xlsx Writer
2 parents 08e2260 + 22c4956 commit af07ad1

File tree

5 files changed

+100
-5
lines changed

5 files changed

+100
-5
lines changed

CHANGELOG.md

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

2626
### Fixed
2727

28+
- Add forceFullCalc option to Xlsx Writer. [Issue #4269](https://github.com/PHPOffice/PhpSpreadsheet/issues/4269) [PR #4271](https://github.com/PHPOffice/PhpSpreadsheet/pull/4271)
2829
- More context options may be needed for http(s) image. [Php issue 17121](https://github.com/php/php-src/issues/17121) [PR #4276](https://github.com/PHPOffice/PhpSpreadsheet/pull/4276)
2930
- Several fixed to ODS Writer. [Issue #4261](https://github.com/PHPOffice/PhpSpreadsheet/issues/4261) [PR #4263](https://github.com/PHPOffice/PhpSpreadsheet/pull/4263) [PR #4264](https://github.com/PHPOffice/PhpSpreadsheet/pull/4264) [PR #4266](https://github.com/PHPOffice/PhpSpreadsheet/pull/4266)
3031

docs/topics/reading-and-writing-to-file.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,12 @@ $writer->save("05featuredemo.xlsx");
169169
**Note** Formulas will still be calculated in any column set to be autosized
170170
even if pre-calculated is set to false
171171

172+
**Note** Prior to release 3.7.0, the use of this feature will cause Excel to be used in a mode where opening a sheet saved in this manner *might* not automatically recalculate a cell's formula when a cell used it the formula changes. Furthermore, that behavior might be applied to all spreadsheets open at the time. To avoid this behavior, add the following statement after `setPreCalculateFormulas` above:
173+
```php
174+
$writer->setForceFullCalc(false);
175+
```
176+
In a future release, the property's default may change to `false` and that statement may no longer be required.
177+
172178
#### Office 2003 compatibility pack
173179

174180
Because of a bug in the Office2003 compatibility pack, there can be some

src/PhpSpreadsheet/Writer/Xlsx.php

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,8 @@ class Xlsx extends BaseWriter
140140

141141
private bool $useDynamicArray = false;
142142

143+
private ?bool $forceFullCalc = null;
144+
143145
/**
144146
* Create a new Xlsx Writer.
145147
*/
@@ -342,7 +344,7 @@ public function save($filename, int $flags = 0): void
342344
$zipContent['xl/styles.xml'] = $this->getWriterPartStyle()->writeStyles($this->spreadSheet);
343345

344346
// Add workbook to ZIP file
345-
$zipContent['xl/workbook.xml'] = $this->getWriterPartWorkbook()->writeWorkbook($this->spreadSheet, $this->preCalculateFormulas);
347+
$zipContent['xl/workbook.xml'] = $this->getWriterPartWorkbook()->writeWorkbook($this->spreadSheet, $this->preCalculateFormulas, $this->forceFullCalc);
346348

347349
$chartCount = 0;
348350
// Add worksheets
@@ -747,4 +749,20 @@ private function determineUseDynamicArrays(): void
747749
{
748750
$this->useDynamicArray = $this->preCalculateFormulas && Calculation::getInstance($this->spreadSheet)->getInstanceArrayReturnType() === Calculation::RETURN_ARRAY_AS_ARRAY && !$this->useCSEArrays;
749751
}
752+
753+
/**
754+
* If this is set when a spreadsheet is opened,
755+
* values may not be automatically re-calculated,
756+
* and a button will be available to force re-calculation.
757+
* This may apply to all spreadsheets open at that time.
758+
* If null, this will be set to the opposite of $preCalculateFormulas.
759+
* It is likely that false is the desired setting, although
760+
* cases have been reported where true is required (issue #456).
761+
*/
762+
public function setForceFullCalc(?bool $forceFullCalc): self
763+
{
764+
$this->forceFullCalc = $forceFullCalc;
765+
766+
return $this;
767+
}
750768
}

src/PhpSpreadsheet/Writer/Xlsx/Workbook.php

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,11 @@ class Workbook extends WriterPart
1515
* Write workbook to XML format.
1616
*
1717
* @param bool $preCalculateFormulas If true, formulas will be calculated before writing
18+
* @param ?bool $forceFullCalc If null, !$preCalculateFormulas
1819
*
1920
* @return string XML Output
2021
*/
21-
public function writeWorkbook(Spreadsheet $spreadsheet, bool $preCalculateFormulas = false): string
22+
public function writeWorkbook(Spreadsheet $spreadsheet, bool $preCalculateFormulas = false, ?bool $forceFullCalc = null): string
2223
{
2324
// Create XML writer
2425
if ($this->getParentWriter()->getUseDiskCaching()) {
@@ -57,7 +58,7 @@ public function writeWorkbook(Spreadsheet $spreadsheet, bool $preCalculateFormul
5758
(new DefinedNamesWriter($objWriter, $spreadsheet))->write();
5859

5960
// calcPr
60-
$this->writeCalcPr($objWriter, $preCalculateFormulas);
61+
$this->writeCalcPr($objWriter, $preCalculateFormulas, $forceFullCalc);
6162

6263
$objWriter->endElement();
6364

@@ -148,7 +149,7 @@ private function writeWorkbookProtection(XMLWriter $objWriter, Spreadsheet $spre
148149
*
149150
* @param bool $preCalculateFormulas If true, formulas will be calculated before writing
150151
*/
151-
private function writeCalcPr(XMLWriter $objWriter, bool $preCalculateFormulas = true): void
152+
private function writeCalcPr(XMLWriter $objWriter, bool $preCalculateFormulas, ?bool $forceFullCalc): void
152153
{
153154
$objWriter->startElement('calcPr');
154155

@@ -160,7 +161,11 @@ private function writeCalcPr(XMLWriter $objWriter, bool $preCalculateFormulas =
160161
// fullCalcOnLoad isn't needed if we will calculate before writing
161162
$objWriter->writeAttribute('calcCompleted', ($preCalculateFormulas) ? '1' : '0');
162163
$objWriter->writeAttribute('fullCalcOnLoad', ($preCalculateFormulas) ? '0' : '1');
163-
$objWriter->writeAttribute('forceFullCalc', ($preCalculateFormulas) ? '0' : '1');
164+
if ($forceFullCalc === null) {
165+
$objWriter->writeAttribute('forceFullCalc', $preCalculateFormulas ? '0' : '1');
166+
} else {
167+
$objWriter->writeAttribute('forceFullCalc', $forceFullCalc ? '1' : '0');
168+
}
164169

165170
$objWriter->endElement();
166171
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PhpOffice\PhpSpreadsheetTests\Writer\Xlsx;
6+
7+
use PhpOffice\PhpSpreadsheet\Shared\File;
8+
use PhpOffice\PhpSpreadsheet\Spreadsheet;
9+
use PhpOffice\PhpSpreadsheet\Writer\Xlsx as XlsxWriter;
10+
use PHPUnit\Framework\Attributes\DataProvider;
11+
use PHPUnit\Framework\TestCase;
12+
13+
class Issue4269Test extends TestCase
14+
{
15+
private string $outputFile = '';
16+
17+
protected function tearDown(): void
18+
{
19+
if ($this->outputFile !== '') {
20+
unlink($this->outputFile);
21+
$this->outputFile = '';
22+
}
23+
}
24+
25+
#[DataProvider('validationProvider')]
26+
public function testWriteArrayFormulaTextJoin(
27+
bool $preCalculateFormulas,
28+
?bool $forceFullCalc,
29+
string $expected
30+
): void {
31+
$spreadsheet = new Spreadsheet();
32+
$sheet = $spreadsheet->getActiveSheet();
33+
$sheet->setCellValue('A1', '=A2*2');
34+
$sheet->setCellValue('A2', 0);
35+
36+
$writer = new XlsxWriter($spreadsheet);
37+
$writer->setPreCalculateFormulas($preCalculateFormulas);
38+
if ($forceFullCalc !== null) {
39+
$writer->setForceFullCalc($forceFullCalc);
40+
}
41+
$this->outputFile = File::temporaryFilename();
42+
$writer->save($this->outputFile);
43+
$spreadsheet->disconnectWorksheets();
44+
45+
$file = 'zip://';
46+
$file .= $this->outputFile;
47+
$file .= '#xl/workbook.xml';
48+
$data = file_get_contents($file);
49+
if ($data === false) {
50+
self::fail('Unable to read file');
51+
} else {
52+
self::assertStringContainsString($expected, $data);
53+
}
54+
}
55+
56+
public static function validationProvider(): array
57+
{
58+
return [
59+
'normal case' => [true, null, 'calcMode="auto" calcCompleted="1" fullCalcOnLoad="0" forceFullCalc="0"'],
60+
'issue 456' => [false, null, 'calcMode="auto" calcCompleted="0" fullCalcOnLoad="1" forceFullCalc="1"'],
61+
'better choice for no precalc' => [false, false, 'calcMode="auto" calcCompleted="0" fullCalcOnLoad="1" forceFullCalc="0"'],
62+
'unlikely use case' => [true, true, 'calcMode="auto" calcCompleted="1" fullCalcOnLoad="0" forceFullCalc="1"'],
63+
];
64+
}
65+
}

0 commit comments

Comments
 (0)