Skip to content

Commit

Permalink
Merge branch 'master' into biffcover
Browse files Browse the repository at this point in the history
  • Loading branch information
oleibman authored Dec 23, 2024
2 parents 06a6549 + af07ad1 commit c3c1aca
Show file tree
Hide file tree
Showing 16 changed files with 319 additions and 82 deletions.
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
6 changes: 6 additions & 0 deletions docs/topics/reading-and-writing-to-file.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 5 additions & 0 deletions src/PhpSpreadsheet/Collection/Memory/SimpleCache1.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down
21 changes: 16 additions & 5 deletions src/PhpSpreadsheet/Writer/Ods/Cell/Style.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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',
};
}
Expand Down Expand Up @@ -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) {
Expand All @@ -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();
}
}
Expand Down Expand Up @@ -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');

Expand Down
59 changes: 21 additions & 38 deletions src/PhpSpreadsheet/Writer/Ods/Content.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,6 @@
*/
class Content extends WriterPart
{
const NUMBER_COLS_REPEATED_MAX = 1024;
const NUMBER_ROWS_REPEATED_MAX = 1048576;

private Formula $formulaConvertor;

/**
Expand Down Expand Up @@ -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);
Expand All @@ -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;
}
}
}
Expand All @@ -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 */
Expand Down Expand Up @@ -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');
}
}
}

/**
Expand Down
13 changes: 11 additions & 2 deletions src/PhpSpreadsheet/Writer/Ods/Styles.php
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
20 changes: 19 additions & 1 deletion src/PhpSpreadsheet/Writer/Xlsx.php
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,8 @@ class Xlsx extends BaseWriter

private bool $useDynamicArray = false;

private ?bool $forceFullCalc = null;

/**
* Create a new Xlsx Writer.
*/
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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;
}
}
13 changes: 9 additions & 4 deletions src/PhpSpreadsheet/Writer/Xlsx/Workbook.php
Original file line number Diff line number Diff line change
Expand Up @@ -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()) {
Expand Down Expand Up @@ -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();

Expand Down Expand Up @@ -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');

Expand All @@ -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();
}
Expand Down
6 changes: 6 additions & 0 deletions src/PhpSpreadsheet/Writer/ZipStream2.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<?php

declare(strict_types=1);

namespace PhpOffice\PhpSpreadsheetTests\Reader\Ods;

use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheetTests\Functional\AbstractFunctional;

class RepeatEmptyCellsAndRowsTest extends AbstractFunctional
{
public function testSaveAndLoadHyperlinks(): void
{
$spreadsheetOld = new Spreadsheet();
$oldSheet = $spreadsheetOld->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();
}
}
Loading

0 comments on commit c3c1aca

Please sign in to comment.