diff --git a/CHANGELOG.md b/CHANGELOG.md index fedd65221e..4c5f311e57 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,8 +25,10 @@ and this project adheres to [Semantic Versioning](https://semver.org). ### Fixed -- 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) +- Add forceFullCalc option to Xlsx Writer. [Issue #4269](https://github.com/PHPOffice/PhpSpreadsheet/issues/4269) [PR #4271](https://github.com/PHPOffice/PhpSpreadsheet/pull/4271) +- 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) - Coverage-related tweaks to Xls Reader. [PR #4277](https://github.com/PHPOffice/PhpSpreadsheet/pull/4277) +- 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) ## 2024-12-08 - 3.6.0 diff --git a/docs/topics/reading-and-writing-to-file.md b/docs/topics/reading-and-writing-to-file.md index 19fe8a8ed9..28bc3b173e 100644 --- a/docs/topics/reading-and-writing-to-file.md +++ b/docs/topics/reading-and-writing-to-file.md @@ -169,6 +169,12 @@ $writer->save("05featuredemo.xlsx"); **Note** Formulas will still be calculated in any column set to be autosized even if pre-calculated is set to false +**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: +```php +$writer->setForceFullCalc(false); +``` +In a future release, the property's default may change to `false` and that statement may no longer be required. + #### Office 2003 compatibility pack Because of a bug in the Office2003 compatibility pack, there can be some diff --git a/src/PhpSpreadsheet/Collection/Memory/SimpleCache1.php b/src/PhpSpreadsheet/Collection/Memory/SimpleCache1.php index b8918c9153..58463ebea0 100644 --- a/src/PhpSpreadsheet/Collection/Memory/SimpleCache1.php +++ b/src/PhpSpreadsheet/Collection/Memory/SimpleCache1.php @@ -9,6 +9,11 @@ * * Alternative implementation should leverage off-memory, non-volatile storage * to reduce overall memory usage. + * + * Either SimpleCache1 or SimpleCache3, but not both, may be used. + * For code coverage testing, it will always be SimpleCache3. + * + * @codeCoverageIgnore */ class SimpleCache1 implements CacheInterface { diff --git a/src/PhpSpreadsheet/Writer/Ods/Cell/Style.php b/src/PhpSpreadsheet/Writer/Ods/Cell/Style.php index 7573230689..92fc497e77 100644 --- a/src/PhpSpreadsheet/Writer/Ods/Cell/Style.php +++ b/src/PhpSpreadsheet/Writer/Ods/Cell/Style.php @@ -20,6 +20,7 @@ class Style public const COLUMN_STYLE_PREFIX = 'co'; public const ROW_STYLE_PREFIX = 'ro'; public const TABLE_STYLE_PREFIX = 'ta'; + public const INDENT_TO_INCHES = 0.1043; // undocumented, used trial and error private XMLWriter $writer; @@ -28,12 +29,13 @@ public function __construct(XMLWriter $writer) $this->writer = $writer; } - private function mapHorizontalAlignment(string $horizontalAlignment): string + private function mapHorizontalAlignment(?string $horizontalAlignment): string { return match ($horizontalAlignment) { Alignment::HORIZONTAL_CENTER, Alignment::HORIZONTAL_CENTER_CONTINUOUS, Alignment::HORIZONTAL_DISTRIBUTED => 'center', Alignment::HORIZONTAL_RIGHT => 'end', Alignment::HORIZONTAL_FILL, Alignment::HORIZONTAL_JUSTIFY => 'justify', + Alignment::HORIZONTAL_GENERAL, '', null => '', default => 'start', }; } @@ -145,8 +147,10 @@ private function writeCellProperties(CellStyle $style): void { // Align $hAlign = $style->getAlignment()->getHorizontal(); + $hAlign = $this->mapHorizontalAlignment($hAlign); $vAlign = $style->getAlignment()->getVertical(); $wrap = $style->getAlignment()->getWrapText(); + $indent = $style->getAlignment()->getIndent(); $this->writer->startElement('style:table-cell-properties'); if (!empty($vAlign) || $wrap) { @@ -168,10 +172,16 @@ private function writeCellProperties(CellStyle $style): void $this->writer->endElement(); - if (!empty($hAlign)) { - $hAlign = $this->mapHorizontalAlignment($hAlign); - $this->writer->startElement('style:paragraph-properties'); - $this->writer->writeAttribute('fo:text-align', $hAlign); + if ($hAlign !== '' || !empty($indent)) { + $this->writer + ->startElement('style:paragraph-properties'); + if ($hAlign !== '') { + $this->writer->writeAttribute('fo:text-align', $hAlign); + } + if (!empty($indent)) { + $indentString = sprintf('%.4f', $indent * self::INDENT_TO_INCHES) . 'in'; + $this->writer->writeAttribute('fo:margin-left', $indentString); + } $this->writer->endElement(); } } @@ -289,6 +299,7 @@ public function writeTableStyle(Worksheet $worksheet, int $sheetId): void 'style:name', sprintf('%s%d', self::TABLE_STYLE_PREFIX, $sheetId) ); + $this->writer->writeAttribute('style:master-page-name', 'Default'); $this->writer->startElement('style:table-properties'); diff --git a/src/PhpSpreadsheet/Writer/Ods/Content.php b/src/PhpSpreadsheet/Writer/Ods/Content.php index 91ac5d1d21..3b9cc82458 100644 --- a/src/PhpSpreadsheet/Writer/Ods/Content.php +++ b/src/PhpSpreadsheet/Writer/Ods/Content.php @@ -20,9 +20,6 @@ */ class Content extends WriterPart { - const NUMBER_COLS_REPEATED_MAX = 1024; - const NUMBER_ROWS_REPEATED_MAX = 1048576; - private Formula $formulaConvertor; /** @@ -142,7 +139,6 @@ private function writeSheets(XMLWriter $objWriter): void sprintf('%s_%d_%d', Style::COLUMN_STYLE_PREFIX, $sheetIndex, $columnDimension->getColumnNumeric()) ); $objWriter->writeAttribute('table:default-cell-style-name', 'ce0'); -// $objWriter->writeAttribute('table:number-columns-repeated', self::NUMBER_COLS_REPEATED_MAX); $objWriter->endElement(); } $this->writeRows($objWriter, $spreadsheet->getSheet($sheetIndex), $sheetIndex); @@ -155,34 +151,33 @@ private function writeSheets(XMLWriter $objWriter): void */ private function writeRows(XMLWriter $objWriter, Worksheet $sheet, int $sheetIndex): void { - $numberRowsRepeated = self::NUMBER_ROWS_REPEATED_MAX; - $span_row = 0; + $spanRow = 0; $rows = $sheet->getRowIterator(); foreach ($rows as $row) { - $cellIterator = $row->getCellIterator(); - --$numberRowsRepeated; - if ($cellIterator->valid()) { - $objWriter->startElement('table:table-row'); - if ($span_row) { - if ($span_row > 1) { - $objWriter->writeAttribute('table:number-rows-repeated', (string) $span_row); - } - $objWriter->startElement('table:table-cell'); - $objWriter->writeAttribute('table:number-columns-repeated', (string) self::NUMBER_COLS_REPEATED_MAX); + $cellIterator = $row->getCellIterator(iterateOnlyExistingCells: true); + $cellIterator->rewind(); + $rowStyleExists = $sheet->rowDimensionExists($row->getRowIndex()) && $sheet->getRowDimension($row->getRowIndex())->getRowHeight() > 0; + if ($cellIterator->valid() || $rowStyleExists) { + if ($spanRow) { + $objWriter->startElement('table:table-row'); + $objWriter->writeAttribute( + 'table:number-rows-repeated', + (string) $spanRow + ); $objWriter->endElement(); - $span_row = 0; - } else { - if ($sheet->rowDimensionExists($row->getRowIndex()) && $sheet->getRowDimension($row->getRowIndex())->getRowHeight() > 0) { - $objWriter->writeAttribute( - 'table:style-name', - sprintf('%s_%d_%d', Style::ROW_STYLE_PREFIX, $sheetIndex, $row->getRowIndex()) - ); - } - $this->writeCells($objWriter, $cellIterator); + $spanRow = 0; + } + $objWriter->startElement('table:table-row'); + if ($rowStyleExists) { + $objWriter->writeAttribute( + 'table:style-name', + sprintf('%s_%d_%d', Style::ROW_STYLE_PREFIX, $sheetIndex, $row->getRowIndex()) + ); } + $this->writeCells($objWriter, $cellIterator); $objWriter->endElement(); } else { - ++$span_row; + ++$spanRow; } } } @@ -192,7 +187,6 @@ private function writeRows(XMLWriter $objWriter, Worksheet $sheet, int $sheetInd */ private function writeCells(XMLWriter $objWriter, RowCellIterator $cells): void { - $numberColsRepeated = self::NUMBER_COLS_REPEATED_MAX; $prevColumn = -1; foreach ($cells as $cell) { /** @var Cell $cell */ @@ -293,17 +287,6 @@ private function writeCells(XMLWriter $objWriter, RowCellIterator $cells): void $objWriter->endElement(); $prevColumn = $column; } - - $numberColsRepeated = $numberColsRepeated - $prevColumn - 1; - if ($numberColsRepeated > 0) { - if ($numberColsRepeated > 1) { - $objWriter->startElement('table:table-cell'); - $objWriter->writeAttribute('table:number-columns-repeated', (string) $numberColsRepeated); - $objWriter->endElement(); - } else { - $objWriter->writeElement('table:table-cell'); - } - } } /** diff --git a/src/PhpSpreadsheet/Writer/Ods/Styles.php b/src/PhpSpreadsheet/Writer/Ods/Styles.php index 448b1eff13..6bfdfb1b33 100644 --- a/src/PhpSpreadsheet/Writer/Ods/Styles.php +++ b/src/PhpSpreadsheet/Writer/Ods/Styles.php @@ -56,8 +56,17 @@ public function write(): string $objWriter->writeElement('office:font-face-decls'); $objWriter->writeElement('office:styles'); - $objWriter->writeElement('office:automatic-styles'); - $objWriter->writeElement('office:master-styles'); + $objWriter->startElement('office:automatic-styles'); + $objWriter->startElement('style:page-layout'); + $objWriter->writeAttribute('style:name', 'Mpm1'); + $objWriter->endElement(); // style:page-layout + $objWriter->endElement(); // office:automatic-styles + $objWriter->startElement('office:master-styles'); + $objWriter->startElement('style:master-page'); + $objWriter->writeAttribute('style:name', 'Default'); + $objWriter->writeAttribute('style:page-layout-name', 'Mpm1'); + $objWriter->endElement(); //style:master-page + $objWriter->endElement(); //office:master-styles $objWriter->endElement(); return $objWriter->getData(); diff --git a/src/PhpSpreadsheet/Writer/Xlsx.php b/src/PhpSpreadsheet/Writer/Xlsx.php index a9d06db581..b74d810502 100644 --- a/src/PhpSpreadsheet/Writer/Xlsx.php +++ b/src/PhpSpreadsheet/Writer/Xlsx.php @@ -140,6 +140,8 @@ class Xlsx extends BaseWriter private bool $useDynamicArray = false; + private ?bool $forceFullCalc = null; + /** * Create a new Xlsx Writer. */ @@ -342,7 +344,7 @@ public function save($filename, int $flags = 0): void $zipContent['xl/styles.xml'] = $this->getWriterPartStyle()->writeStyles($this->spreadSheet); // Add workbook to ZIP file - $zipContent['xl/workbook.xml'] = $this->getWriterPartWorkbook()->writeWorkbook($this->spreadSheet, $this->preCalculateFormulas); + $zipContent['xl/workbook.xml'] = $this->getWriterPartWorkbook()->writeWorkbook($this->spreadSheet, $this->preCalculateFormulas, $this->forceFullCalc); $chartCount = 0; // Add worksheets @@ -747,4 +749,20 @@ private function determineUseDynamicArrays(): void { $this->useDynamicArray = $this->preCalculateFormulas && Calculation::getInstance($this->spreadSheet)->getInstanceArrayReturnType() === Calculation::RETURN_ARRAY_AS_ARRAY && !$this->useCSEArrays; } + + /** + * If this is set when a spreadsheet is opened, + * values may not be automatically re-calculated, + * and a button will be available to force re-calculation. + * This may apply to all spreadsheets open at that time. + * If null, this will be set to the opposite of $preCalculateFormulas. + * It is likely that false is the desired setting, although + * cases have been reported where true is required (issue #456). + */ + public function setForceFullCalc(?bool $forceFullCalc): self + { + $this->forceFullCalc = $forceFullCalc; + + return $this; + } } diff --git a/src/PhpSpreadsheet/Writer/Xlsx/Workbook.php b/src/PhpSpreadsheet/Writer/Xlsx/Workbook.php index 24a2eeb064..c907e100b7 100644 --- a/src/PhpSpreadsheet/Writer/Xlsx/Workbook.php +++ b/src/PhpSpreadsheet/Writer/Xlsx/Workbook.php @@ -15,10 +15,11 @@ class Workbook extends WriterPart * Write workbook to XML format. * * @param bool $preCalculateFormulas If true, formulas will be calculated before writing + * @param ?bool $forceFullCalc If null, !$preCalculateFormulas * * @return string XML Output */ - public function writeWorkbook(Spreadsheet $spreadsheet, bool $preCalculateFormulas = false): string + public function writeWorkbook(Spreadsheet $spreadsheet, bool $preCalculateFormulas = false, ?bool $forceFullCalc = null): string { // Create XML writer if ($this->getParentWriter()->getUseDiskCaching()) { @@ -57,7 +58,7 @@ public function writeWorkbook(Spreadsheet $spreadsheet, bool $preCalculateFormul (new DefinedNamesWriter($objWriter, $spreadsheet))->write(); // calcPr - $this->writeCalcPr($objWriter, $preCalculateFormulas); + $this->writeCalcPr($objWriter, $preCalculateFormulas, $forceFullCalc); $objWriter->endElement(); @@ -148,7 +149,7 @@ private function writeWorkbookProtection(XMLWriter $objWriter, Spreadsheet $spre * * @param bool $preCalculateFormulas If true, formulas will be calculated before writing */ - private function writeCalcPr(XMLWriter $objWriter, bool $preCalculateFormulas = true): void + private function writeCalcPr(XMLWriter $objWriter, bool $preCalculateFormulas, ?bool $forceFullCalc): void { $objWriter->startElement('calcPr'); @@ -160,7 +161,11 @@ private function writeCalcPr(XMLWriter $objWriter, bool $preCalculateFormulas = // fullCalcOnLoad isn't needed if we will calculate before writing $objWriter->writeAttribute('calcCompleted', ($preCalculateFormulas) ? '1' : '0'); $objWriter->writeAttribute('fullCalcOnLoad', ($preCalculateFormulas) ? '0' : '1'); - $objWriter->writeAttribute('forceFullCalc', ($preCalculateFormulas) ? '0' : '1'); + if ($forceFullCalc === null) { + $objWriter->writeAttribute('forceFullCalc', $preCalculateFormulas ? '0' : '1'); + } else { + $objWriter->writeAttribute('forceFullCalc', $forceFullCalc ? '1' : '0'); + } $objWriter->endElement(); } diff --git a/src/PhpSpreadsheet/Writer/ZipStream2.php b/src/PhpSpreadsheet/Writer/ZipStream2.php index ed21a7e399..e03e1d0cf9 100644 --- a/src/PhpSpreadsheet/Writer/ZipStream2.php +++ b/src/PhpSpreadsheet/Writer/ZipStream2.php @@ -5,6 +5,12 @@ use ZipStream\Option\Archive; use ZipStream\ZipStream; +/** + * Either ZipStream2 or ZipStream3, but not both, may be used. + * For code coverage testing, it will always be ZipStream3. + * + * @codeCoverageIgnore + */ class ZipStream2 { /** diff --git a/tests/PhpSpreadsheetTests/Reader/Ods/RepeatEmptyCellsAndRowsTest.php b/tests/PhpSpreadsheetTests/Reader/Ods/RepeatEmptyCellsAndRowsTest.php new file mode 100644 index 0000000000..b64f8fd1dc --- /dev/null +++ b/tests/PhpSpreadsheetTests/Reader/Ods/RepeatEmptyCellsAndRowsTest.php @@ -0,0 +1,48 @@ +getActiveSheet(); + $oldSheet->setCellValue('C1', 'xx'); + $oldSheet->setCellValue('G1', 'aa'); + $oldSheet->setCellValue('BB1', 'bb'); + $oldSheet->setCellValue('A6', 'aaa'); + $oldSheet->setCellValue('B7', 'bbb'); + $oldSheet->getRowDimension(10)->setRowHeight(12); + $oldSheet->setCellValue('A12', 'this is A12'); + $style = $oldSheet->getStyle('B14:D14'); + $style->getFont()->setBold(true); + $oldSheet->getCell('E15')->setValue('X'); + $oldSheet->mergeCells('E15:G16'); + $oldSheet->getCell('J15')->setValue('j15'); + $oldSheet->getCell('J16')->setValue('j16'); + $oldSheet->getCell('A19')->setValue('lastrow'); + $spreadsheet = $this->writeAndReload($spreadsheetOld, 'Ods'); + $spreadsheetOld->disconnectWorksheets(); + + $sheet = $spreadsheet->getActiveSheet(); + self::assertSame('xx', $sheet->getCell('C1')->getValue()); + self::assertSame('aa', $sheet->getCell('G1')->getValue()); + self::assertSame('bb', $sheet->getCell('BB1')->getValue()); + self::assertSame('aaa', $sheet->getCell('A6')->getValue()); + self::assertSame('bbb', $sheet->getCell('B7')->getValue()); + self::assertSame('this is A12', $sheet->getCell('A12')->getValue()); + // Read styles, including row height, not yet implemented for ODS + self::assertSame('j15', $sheet->getCell('J15')->getValue()); + self::assertSame('j16', $sheet->getCell('J16')->getValue()); + self::assertSame(['E15:G16' => 'E15:G16'], $sheet->getMergeCells()); + self::assertSame('lastrow', $sheet->getCell('A19')->getValue()); + + $spreadsheet->disconnectWorksheets(); + } +} diff --git a/tests/PhpSpreadsheetTests/Writer/Ods/IndentTest.php b/tests/PhpSpreadsheetTests/Writer/Ods/IndentTest.php new file mode 100644 index 0000000000..7b6a62baa2 --- /dev/null +++ b/tests/PhpSpreadsheetTests/Writer/Ods/IndentTest.php @@ -0,0 +1,62 @@ +compatibilityMode = Functions::getCompatibilityMode(); + Functions::setCompatibilityMode( + Functions::COMPATIBILITY_OPENOFFICE + ); + } + + protected function tearDown(): void + { + parent::tearDown(); + Functions::setCompatibilityMode($this->compatibilityMode); + } + + public function testWriteSpreadsheet(): void + { + $spreadsheet = new Spreadsheet(); + $sheet = $spreadsheet->getActiveSheet(); + $sheet->setCellValue('A1', 'aa'); + $sheet->setCellValue('B1', 'bb'); + $sheet->setCellValue('A2', 'cc'); + $sheet->setCellValue('B2', 'dd'); + $sheet->getStyle('A1')->getAlignment()->setIndent(2); + $writer = new Ods($spreadsheet); + $content = new Content($writer); + $xml = $content->write(); + self::assertStringContainsString( + '' + . '' + . '' + . '', + $xml + ); + self::assertStringContainsString( + '' + . '' + . '' // fo:margin-left is what we're looking for + . '' + . '', + $xml + ); + self::assertSame(3, substr_count($xml, 'table:style-name="ce0"')); + self::assertSame(1, substr_count($xml, 'table:style-name="ce1"')); + $spreadsheet->disconnectWorksheets(); + } +} diff --git a/tests/PhpSpreadsheetTests/Writer/Xlsx/Issue4269Test.php b/tests/PhpSpreadsheetTests/Writer/Xlsx/Issue4269Test.php new file mode 100644 index 0000000000..ffe9e30ae0 --- /dev/null +++ b/tests/PhpSpreadsheetTests/Writer/Xlsx/Issue4269Test.php @@ -0,0 +1,65 @@ +outputFile !== '') { + unlink($this->outputFile); + $this->outputFile = ''; + } + } + + #[DataProvider('validationProvider')] + public function testWriteArrayFormulaTextJoin( + bool $preCalculateFormulas, + ?bool $forceFullCalc, + string $expected + ): void { + $spreadsheet = new Spreadsheet(); + $sheet = $spreadsheet->getActiveSheet(); + $sheet->setCellValue('A1', '=A2*2'); + $sheet->setCellValue('A2', 0); + + $writer = new XlsxWriter($spreadsheet); + $writer->setPreCalculateFormulas($preCalculateFormulas); + if ($forceFullCalc !== null) { + $writer->setForceFullCalc($forceFullCalc); + } + $this->outputFile = File::temporaryFilename(); + $writer->save($this->outputFile); + $spreadsheet->disconnectWorksheets(); + + $file = 'zip://'; + $file .= $this->outputFile; + $file .= '#xl/workbook.xml'; + $data = file_get_contents($file); + if ($data === false) { + self::fail('Unable to read file'); + } else { + self::assertStringContainsString($expected, $data); + } + } + + public static function validationProvider(): array + { + return [ + 'normal case' => [true, null, 'calcMode="auto" calcCompleted="1" fullCalcOnLoad="0" forceFullCalc="0"'], + 'issue 456' => [false, null, 'calcMode="auto" calcCompleted="0" fullCalcOnLoad="1" forceFullCalc="1"'], + 'better choice for no precalc' => [false, false, 'calcMode="auto" calcCompleted="0" fullCalcOnLoad="1" forceFullCalc="0"'], + 'unlikely use case' => [true, true, 'calcMode="auto" calcCompleted="1" fullCalcOnLoad="0" forceFullCalc="1"'], + ]; + } +} diff --git a/tests/data/Writer/Ods/content-arrays.xml b/tests/data/Writer/Ods/content-arrays.xml index a33b7dbfca..8e72601a59 100644 --- a/tests/data/Writer/Ods/content-arrays.xml +++ b/tests/data/Writer/Ods/content-arrays.xml @@ -1,2 +1,44 @@ -11133 \ No newline at end of file + + + + + + + + + + + + + + + + + + + +1 + + +1 + + + + +1 + + +3 + + + + +3 + + + + + + + \ No newline at end of file diff --git a/tests/data/Writer/Ods/content-empty.xml b/tests/data/Writer/Ods/content-empty.xml index 84f4c23977..e9015af896 100644 --- a/tests/data/Writer/Ods/content-empty.xml +++ b/tests/data/Writer/Ods/content-empty.xml @@ -3,12 +3,11 @@ - + - @@ -17,10 +16,6 @@ - - - - diff --git a/tests/data/Writer/Ods/content-hidden-worksheet.xml b/tests/data/Writer/Ods/content-hidden-worksheet.xml index 88a53257a1..b583466701 100644 --- a/tests/data/Writer/Ods/content-hidden-worksheet.xml +++ b/tests/data/Writer/Ods/content-hidden-worksheet.xml @@ -3,15 +3,14 @@ - + - + - @@ -24,7 +23,6 @@ 1 - @@ -33,7 +31,6 @@ 2 - diff --git a/tests/data/Writer/Ods/content-with-data.xml b/tests/data/Writer/Ods/content-with-data.xml index db7d75a747..bde23e29d3 100644 --- a/tests/data/Writer/Ods/content-with-data.xml +++ b/tests/data/Writer/Ods/content-with-data.xml @@ -3,65 +3,54 @@ - + - + - - - - - - - - - - - @@ -92,7 +81,6 @@ Lorem ipsum - @@ -107,10 +95,6 @@ 42798.572060185 - - - - @@ -119,7 +103,6 @@ 2 -