Skip to content

Commit c8a1a5f

Browse files
authored
Merge pull request #4645 from oleibman/issue4584
Wrapped Cells and Default Row Height
2 parents f467ac5 + fff2373 commit c8a1a5f

File tree

12 files changed

+352
-48
lines changed

12 files changed

+352
-48
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ and this project adheres to [Semantic Versioning](https://semver.org). Thia is a
3434
- Minor improvements to Calculation coverage. [PR #4624](https://github.com/PHPOffice/PhpSpreadsheet/pull/4624)
3535
- Conditional formatting in extLst. [Issue #4629](https://github.com/PHPOffice/PhpSpreadsheet/issues/4629) [PR #4633](https://github.com/PHPOffice/PhpSpreadsheet/pull/4633)
3636
- Php8.5 deprecates use of null as array index. [PR #4634](https://github.com/PHPOffice/PhpSpreadsheet/pull/4634)
37+
- Wrapped cells and default row height. [Issue #4584](https://github.com/PHPOffice/PhpSpreadsheet/issues/4584) [PR #4645](https://github.com/PHPOffice/PhpSpreadsheet/pull/4645)
3738
- For Php8.5, replace one of our two uses of `__wakeup` with `__unserialize`, and eliminate the other. [PR #4639](https://github.com/PHPOffice/PhpSpreadsheet/pull/4639)
3839
- Use prefix _xlfn for BASE function. [Issue #4638](https://github.com/PHPOffice/PhpSpreadsheet/issues/4638) [PR #4641](https://github.com/PHPOffice/PhpSpreadsheet/pull/4641)
3940
- Additional support for union and intersection. [PR #4596](https://github.com/PHPOffice/PhpSpreadsheet/pull/4596)

src/PhpSpreadsheet/Reader/Xls.php

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2647,7 +2647,14 @@ protected function readRow(): void
26472647
$useDefaultHeight = (0x8000 & self::getUInt2d($recordData, 6)) >> 15;
26482648

26492649
if (!$useDefaultHeight) {
2650-
$this->phpSheet->getRowDimension($r + 1)->setRowHeight($height / 20);
2650+
if (
2651+
$this->phpSheet->getDefaultRowDimension()->getRowHeight() > 0
2652+
) {
2653+
$this->phpSheet->getRowDimension($r + 1)
2654+
->setCustomFormat(true, ($height === 255) ? -1 : ($height / 20));
2655+
} else {
2656+
$this->phpSheet->getRowDimension($r + 1)->setRowHeight($height / 20);
2657+
}
26512658
}
26522659

26532660
// offset: 8; size: 2; not used

src/PhpSpreadsheet/Reader/Xlsx/ColumnAndRowAttributes.php

Lines changed: 38 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -50,32 +50,40 @@ private function setColumnAttributes(string $columnAddress, array $columnAttribu
5050
* Set Worksheet row attributes by attributes array passed.
5151
*
5252
* @param int $rowNumber 1, 2, 3, ... 99, ...
53-
* @param array{xfIndex?: int, visible?: bool, collapsed?: bool, collapsed?: bool, outlineLevel?: int, rowHeight?: float} $rowAttributes array of attributes (indexes are attribute name, values are value)
53+
* @param array{xfIndex?: int, visible?: bool, collapsed?: bool, collapsed?: bool, outlineLevel?: int, rowHeight?: float, customFormat?: bool, ht?: float} $rowAttributes array of attributes (indexes are attribute name, values are value)
5454
* 'xfIndex', 'visible', 'collapsed', 'outlineLevel', 'rowHeight', ... ?
5555
*/
5656
private function setRowAttributes(int $rowNumber, array $rowAttributes): void
5757
{
5858
if (isset($rowAttributes['xfIndex'])) {
59-
$this->worksheet->getRowDimension($rowNumber)->setXfIndex($rowAttributes['xfIndex']);
59+
$this->worksheet->getRowDimension($rowNumber)
60+
->setXfIndex($rowAttributes['xfIndex']);
6061
}
6162
if (isset($rowAttributes['visible'])) {
62-
$this->worksheet->getRowDimension($rowNumber)->setVisible($rowAttributes['visible']);
63+
$this->worksheet->getRowDimension($rowNumber)
64+
->setVisible($rowAttributes['visible']);
6365
}
6466
if (isset($rowAttributes['collapsed'])) {
65-
$this->worksheet->getRowDimension($rowNumber)->setCollapsed($rowAttributes['collapsed']);
67+
$this->worksheet->getRowDimension($rowNumber)
68+
->setCollapsed($rowAttributes['collapsed']);
6669
}
6770
if (isset($rowAttributes['outlineLevel'])) {
68-
$this->worksheet->getRowDimension($rowNumber)->setOutlineLevel($rowAttributes['outlineLevel']);
71+
$this->worksheet->getRowDimension($rowNumber)
72+
->setOutlineLevel($rowAttributes['outlineLevel']);
6973
}
70-
if (isset($rowAttributes['rowHeight'])) {
71-
$this->worksheet->getRowDimension($rowNumber)->setRowHeight($rowAttributes['rowHeight']);
74+
if (isset($rowAttributes['customFormat'], $rowAttributes['rowHeight'])) {
75+
$this->worksheet->getRowDimension($rowNumber)
76+
->setCustomFormat($rowAttributes['customFormat'], $rowAttributes['rowHeight']);
77+
} elseif (isset($rowAttributes['rowHeight'])) {
78+
$this->worksheet->getRowDimension($rowNumber)
79+
->setRowHeight($rowAttributes['rowHeight']);
7280
}
7381
}
7482

75-
public function load(?IReadFilter $readFilter = null, bool $readDataOnly = false, bool $ignoreRowsWithNoCells = false): void
83+
public function load(?IReadFilter $readFilter = null, bool $readDataOnly = false, bool $ignoreRowsWithNoCells = false): bool
7684
{
7785
if ($this->worksheetXml === null) {
78-
return;
86+
return false;
7987
}
8088
if ($readFilter !== null && $readFilter::class === DefaultReadFilter::class) {
8189
$readFilter = null;
@@ -119,6 +127,8 @@ public function load(?IReadFilter $readFilter = null, bool $readDataOnly = false
119127
}
120128
}
121129
}
130+
131+
return true;
122132
}
123133

124134
/** @param mixed[] $rowsAttributes */
@@ -203,20 +213,25 @@ private function readRowAttributes(SimpleXMLElement $worksheetRow, bool $readDat
203213
$row = $rowx->attributes();
204214
if ($row !== null && (!$ignoreRowsWithNoCells || isset($rowx->c))) {
205215
$rowIndex = (int) $row['r'];
206-
if (isset($row['ht']) && !$readDataOnly) {
207-
$rowAttributes[$rowIndex]['rowHeight'] = (float) $row['ht'];
208-
}
209-
if (isset($row['hidden']) && self::boolean($row['hidden'])) {
210-
$rowAttributes[$rowIndex]['visible'] = false;
211-
}
212-
if (isset($row['collapsed']) && self::boolean($row['collapsed'])) {
213-
$rowAttributes[$rowIndex]['collapsed'] = true;
214-
}
215-
if (isset($row['outlineLevel']) && (int) $row['outlineLevel'] > 0) {
216-
$rowAttributes[$rowIndex]['outlineLevel'] = (int) $row['outlineLevel'];
217-
}
218-
if (isset($row['s']) && !$readDataOnly) {
219-
$rowAttributes[$rowIndex]['xfIndex'] = (int) $row['s'];
216+
if (!$readDataOnly) {
217+
if (isset($row['ht'])) {
218+
$rowAttributes[$rowIndex]['rowHeight'] = (float) $row['ht'];
219+
}
220+
if (isset($row['customFormat']) && self::boolean($row['customFormat'])) {
221+
$rowAttributes[$rowIndex]['customFormat'] = true;
222+
}
223+
if (isset($row['hidden']) && self::boolean($row['hidden'])) {
224+
$rowAttributes[$rowIndex]['visible'] = false;
225+
}
226+
if (isset($row['collapsed']) && self::boolean($row['collapsed'])) {
227+
$rowAttributes[$rowIndex]['collapsed'] = true;
228+
}
229+
if (isset($row['outlineLevel']) && (int) $row['outlineLevel'] > 0) {
230+
$rowAttributes[$rowIndex]['outlineLevel'] = (int) $row['outlineLevel'];
231+
}
232+
if (isset($row['s'])) {
233+
$rowAttributes[$rowIndex]['xfIndex'] = (int) $row['s'];
234+
}
220235
}
221236
if ($readFilterIsNotNull && empty($rowAttributes[$rowIndex])) {
222237
$rowAttributes[$rowIndex]['exists'] = true;

src/PhpSpreadsheet/Worksheet/RowDimension.php

Lines changed: 18 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,6 @@
66

77
class RowDimension extends Dimension
88
{
9-
/**
10-
* Row index.
11-
*/
129
private ?int $rowIndex;
1310

1411
/**
@@ -23,9 +20,9 @@ class RowDimension extends Dimension
2320
*/
2421
private bool $zeroHeight = false;
2522

23+
private bool $customFormat = false;
24+
2625
/**
27-
* Create a new RowDimension.
28-
*
2926
* @param ?int $index Numeric row index
3027
*/
3128
public function __construct(?int $index = 0)
@@ -37,19 +34,11 @@ public function __construct(?int $index = 0)
3734
parent::__construct(null);
3835
}
3936

40-
/**
41-
* Get Row Index.
42-
*/
4337
public function getRowIndex(): ?int
4438
{
4539
return $this->rowIndex;
4640
}
4741

48-
/**
49-
* Set Row Index.
50-
*
51-
* @return $this
52-
*/
5342
public function setRowIndex(int $index): static
5443
{
5544
$this->rowIndex = $index;
@@ -76,35 +65,41 @@ public function getRowHeight(?string $unitOfMeasure = null): float
7665
* @param float $height in points. A value of -1 tells Excel to display this column in its default height.
7766
* By default, this will be the passed argument value; but this method also accepts an optional unit of measure
7867
* argument, and will convert the passed argument value to points from the specified UoM
79-
*
80-
* @return $this
8168
*/
8269
public function setRowHeight(float $height, ?string $unitOfMeasure = null): static
8370
{
8471
$this->height = ($unitOfMeasure === null || $height < 0)
8572
? $height
8673
: (new CssDimension("{$height}{$unitOfMeasure}"))->height();
74+
$this->customFormat = false;
8775

8876
return $this;
8977
}
9078

91-
/**
92-
* Get ZeroHeight.
93-
*/
9479
public function getZeroHeight(): bool
9580
{
9681
return $this->zeroHeight;
9782
}
9883

99-
/**
100-
* Set ZeroHeight.
101-
*
102-
* @return $this
103-
*/
10484
public function setZeroHeight(bool $zeroHeight): static
10585
{
10686
$this->zeroHeight = $zeroHeight;
10787

10888
return $this;
10989
}
90+
91+
public function getCustomFormat(): bool
92+
{
93+
return $this->customFormat;
94+
}
95+
96+
public function setCustomFormat(bool $customFormat, ?float $height = -1): self
97+
{
98+
$this->customFormat = $customFormat;
99+
if ($height !== null) {
100+
$this->height = $height;
101+
}
102+
103+
return $this;
104+
}
110105
}

src/PhpSpreadsheet/Writer/Ods/Cell/Style.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,21 @@ public function writeRowStyles(RowDimension $rowDimension, int $sheetId): void
302302
$this->writer->endElement(); // Close style:style
303303
}
304304

305+
public function writeDefaultRowStyle(RowDimension $rowDimension, int $sheetId): void
306+
{
307+
$this->writer->startElement('style:style');
308+
$this->writer->writeAttribute('style:family', 'table-row');
309+
$this->writer->writeAttribute(
310+
'style:name',
311+
sprintf('%s%d', self::ROW_STYLE_PREFIX, $sheetId)
312+
);
313+
314+
$this->writeRowProperties($rowDimension);
315+
316+
// End
317+
$this->writer->endElement(); // Close style:style
318+
}
319+
305320
public function writeTableStyle(Worksheet $worksheet, int $sheetId): void
306321
{
307322
$this->writer->startElement('style:style');

src/PhpSpreadsheet/Writer/Ods/Content.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,11 @@ private function writeRows(XMLWriter $objWriter, Worksheet $sheet, int $sheetInd
174174
'table:style-name',
175175
sprintf('%s_%d_%d', Style::ROW_STYLE_PREFIX, $sheetIndex, $row->getRowIndex())
176176
);
177+
} elseif ($sheet->getDefaultRowDimension()->getRowHeight() > 0.0 && !$sheet->getRowDimension($row->getRowIndex())->getCustomFormat()) {
178+
$objWriter->writeAttribute(
179+
'table:style-name',
180+
sprintf('%s%d', Style::ROW_STYLE_PREFIX, $sheetIndex)
181+
);
177182
}
178183
$this->writeCells($objWriter, $cellIterator);
179184
$objWriter->endElement();
@@ -323,6 +328,10 @@ private function writeXfStyles(XMLWriter $writer, Spreadsheet $spreadsheet): voi
323328
}
324329
for ($i = 0; $i < $sheetCount; ++$i) {
325330
$worksheet = $spreadsheet->getSheet($i);
331+
$default = $worksheet->getDefaultRowDimension();
332+
if ($default->getRowHeight() > 0.0) {
333+
$styleWriter->writeDefaultRowStyle($default, $i);
334+
}
326335
foreach ($worksheet->getRowDimensions() as $rowDimension) {
327336
if ($rowDimension->getRowHeight() > 0.0) {
328337
$styleWriter->writeRowStyles($rowDimension, $i);

src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1398,6 +1398,16 @@ private function writeSheetData(XMLWriter $objWriter, PhpspreadsheetWorksheet $w
13981398
}
13991399
}
14001400

1401+
$customHeightNeeded = false;
1402+
if ($worksheet->getDefaultRowDimension()->getRowHeight() >= 0) {
1403+
foreach ($worksheet->getRowDimensions() as $rowDimension) {
1404+
if ($rowDimension->getCustomFormat()) {
1405+
$customHeightNeeded = true;
1406+
1407+
break;
1408+
}
1409+
}
1410+
}
14011411
$currentRow = 0;
14021412
$emptyDimension = new RowDimension();
14031413
while ($currentRow++ < $highestRow) {
@@ -1411,6 +1421,7 @@ private function writeSheetData(XMLWriter $objWriter, PhpspreadsheetWorksheet $w
14111421

14121422
if ($writeCurrentRow) {
14131423
// Start a new row
1424+
$customFormatWritten = false;
14141425
$objWriter->startElement('row');
14151426
$objWriter->writeAttribute('r', "$currentRow");
14161427
$objWriter->writeAttribute('spans', '1:' . $colCount);
@@ -1419,6 +1430,12 @@ private function writeSheetData(XMLWriter $objWriter, PhpspreadsheetWorksheet $w
14191430
if ($rowDimension->getRowHeight() >= 0) {
14201431
$objWriter->writeAttribute('customHeight', '1');
14211432
$objWriter->writeAttribute('ht', StringHelper::formatNumber($rowDimension->getRowHeight()));
1433+
} elseif ($rowDimension->getCustomFormat()) {
1434+
$objWriter->writeAttribute('customFormat', '1');
1435+
$customFormatWritten = true;
1436+
$objWriter->writeAttribute('ht', StringHelper::formatNumber($rowDimension->getRowHeight()));
1437+
} elseif ($customHeightNeeded) {
1438+
$objWriter->writeAttribute('customHeight', '1');
14221439
}
14231440

14241441
// Row visibility
@@ -1439,7 +1456,9 @@ private function writeSheetData(XMLWriter $objWriter, PhpspreadsheetWorksheet $w
14391456
// Style
14401457
if ($rowDimension->getXfIndex() !== null) {
14411458
$objWriter->writeAttribute('s', (string) $rowDimension->getXfIndex());
1442-
$objWriter->writeAttribute('customFormat', '1');
1459+
if (!$customFormatWritten) {
1460+
$objWriter->writeAttribute('customFormat', '1');
1461+
}
14431462
}
14441463

14451464
// Write cells
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PhpOffice\PhpSpreadsheetTests\Reader\Xlsx;
6+
7+
use PhpOffice\PhpSpreadsheet\Reader\Xlsx as XlsxReader;
8+
use PhpOffice\PhpSpreadsheet\Writer\Xlsx as XlsxWriter;
9+
use PHPUnit\Framework\TestCase;
10+
11+
class OutlineTest extends TestCase
12+
{
13+
public function testOutline(): void
14+
{
15+
$filename = 'tests/data/Reader/XLSX/outline.xlsx';
16+
$reader = new XlsxReader();
17+
$spreadsheet = $reader->load($filename);
18+
$sheet = $spreadsheet->getActiveSheet();
19+
self::assertSame(0, $sheet->getRowDimension(1)->getOutlineLevel());
20+
self::assertSame(2, $sheet->getRowDimension(2)->getOutlineLevel());
21+
self::assertFalse($sheet->getRowDimension(2)->getCollapsed());
22+
self::assertTrue($sheet->getRowDimension(2)->getVisible());
23+
self::assertSame('=SUBTOTAL(9,B2:B4)', $sheet->getCell('B5')->getValue());
24+
self::assertSame(2, $sheet->getRowDimension(6)->getOutlineLevel());
25+
self::assertFalse($sheet->getRowDimension(6)->getCollapsed());
26+
self::assertFalse($sheet->getRowDimension(6)->getVisible());
27+
self::assertSame(1, $sheet->getRowDimension(8)->getOutlineLevel());
28+
self::assertTrue($sheet->getRowDimension(8)->getCollapsed());
29+
self::assertTrue($sheet->getRowDimension(8)->getVisible());
30+
$fake = new XlsxReader\ColumnAndRowAttributes($sheet, null);
31+
self::assertFalse($fake->load());
32+
$writer = new XlsxWriter($spreadsheet);
33+
$writerWorksheet = new XlsxWriter\Worksheet($writer);
34+
$data = $writerWorksheet->writeWorksheet($sheet, []);
35+
self::assertStringContainsString('<row r="7" spans="1:2" hidden="true" outlineLevel="2">', $data);
36+
self::assertStringContainsString('<row r="8" spans="1:2" collapsed="true" outlineLevel="1">', $data);
37+
$spreadsheet->disconnectWorksheets();
38+
}
39+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PhpOffice\PhpSpreadsheetTests\Writer\Ods;
6+
7+
use PhpOffice\PhpSpreadsheet\Spreadsheet;
8+
use PhpOffice\PhpSpreadsheet\Writer\Ods as OdsWriter;
9+
use PHPUnit\Framework\TestCase;
10+
11+
class Issue4584Test extends TestCase
12+
{
13+
public function testWriteRowDimensions(): void
14+
{
15+
$spreadsheet = new Spreadsheet();
16+
$sheet = $spreadsheet->getActiveSheet();
17+
$sheet->getDefaultRowDimension()->setRowHeight(20);
18+
$sheet->setCellValue('A1', 'hello there world 1');
19+
$sheet->getStyle('A1')->getAlignment()->setWrapText(true);
20+
$sheet->getRowDimension(1)->setCustomFormat(true);
21+
$sheet->setCellValue('A2', 'hello there world 2');
22+
$sheet->setCellValue('A4', 'hello there world 4');
23+
$writer = new OdsWriter($spreadsheet);
24+
$writerWorksheet = new OdsWriter\Content($writer);
25+
$data = $writerWorksheet->write();
26+
self::assertStringContainsString(
27+
'<style:style style:family="table-row" style:name="ro0"><style:table-row-properties style:row-height="0.706cm" style:use-optimal-row-height="false" fo:break-before="auto"/></style:style>',
28+
$data
29+
);
30+
self::assertStringContainsString(
31+
'<table:table-row><table:table-cell table:style-name="ce1" office:value-type="string"><text:p>hello there world 1</text:p></table:table-cell></table:table-row>',
32+
$data
33+
);
34+
self::assertStringContainsString(
35+
'<table:table-row table:style-name="ro0"><table:table-cell table:style-name="ce0" office:value-type="string"><text:p>hello there world 2</text:p></table:table-cell></table:table-row>',
36+
$data
37+
);
38+
self::assertStringContainsString(
39+
'<table:table-row table:number-rows-repeated="1"/><table:table-row table:style-name="ro0"><table:table-cell table:style-name="ce0" office:value-type="string"><text:p>hello there world 4</text:p></table:table-cell></table:table-row>',
40+
$data
41+
);
42+
$spreadsheet->disconnectWorksheets();
43+
}
44+
}

0 commit comments

Comments
 (0)