diff --git a/docs/changes/2.x/2.0.0.md b/docs/changes/1.x/1.3.0.md similarity index 96% rename from docs/changes/2.x/2.0.0.md rename to docs/changes/1.x/1.3.0.md index f1e5583e7c..7b07ed2406 100644 --- a/docs/changes/2.x/2.0.0.md +++ b/docs/changes/1.x/1.3.0.md @@ -1,6 +1,6 @@ -# [2.0.0](https://github.com/PHPOffice/PHPWord/tree/2.0.0) (WIP) +# [1.3.0](https://github.com/PHPOffice/PHPWord/tree/1.3.0) (WIP) -[Full Changelog](https://github.com/PHPOffice/PHPWord/compare/1.2.0...2.0.0) +[Full Changelog](https://github.com/PHPOffice/PHPWord/compare/1.2.0...1.3.0) ## Enhancements @@ -27,6 +27,7 @@ - Template Processor : Fixed bad naming of variables fixing [#2586](https://github.com/PHPOffice/PHPWord/issues/2586) by [@Progi1984](https://github.com/Progi1984) in [#2655](https://github.com/PHPOffice/PHPWord/pull/2655) - Word2007 Writer : Fix first footnote appearing as separator [#2634](https://github.com/PHPOffice/PHPWord/issues/2634) by [@jacksleight](https://github.com/jacksleight) in [#2635](https://github.com/PHPOffice/PHPWord/pull/2635) - Template Processor : Fixed images with transparent backgrounds displaying a white background by [@ElwynVdb](https://github.com/ElwynVdb) in [#2638](https://github.com/PHPOffice/PHPWord/pull/2638) +- HTML Writer : Fixed rowspan for tables by [@andomiell](https://github.com/andomiell) in [#2659](https://github.com/PHPOffice/PHPWord/pull/2659) ### Miscellaneous diff --git a/mkdocs.yml b/mkdocs.yml index 6eb4d42911..dfabbb195a 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -86,9 +86,8 @@ nav: - How to: 'howto.md' - Credits: 'credits.md' - Releases: - - '2.x': - - '2.0.0 (WIP)': 'changes/2.x/2.0.0.md' - '1.x': + - '1.3.0 (WIP)': 'changes/1.x/1.3.0.md' - '1.2.0': 'changes/1.x/1.2.0.md' - '1.1.0': 'changes/1.x/1.1.0.md' - '1.0.0': 'changes/1.x/1.0.0.md' diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 026fd8b59a..909718b83b 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -1787,17 +1787,12 @@ parameters: - message: "#^Cannot access property \\$length on DOMNodeList\\\\|false\\.$#" - count: 6 + count: 9 path: tests/PhpWordTests/Writer/HTML/ElementTest.php - message: "#^Cannot call method item\\(\\) on DOMNodeList\\\\|false\\.$#" - count: 8 - path: tests/PhpWordTests/Writer/HTML/ElementTest.php - - - - message: "#^Method PhpOffice\\\\PhpWordTests\\\\Writer\\\\HTML\\\\ElementTest\\:\\:getAsHTML\\(\\) has no return type specified\\.$#" - count: 1 + count: 11 path: tests/PhpWordTests/Writer/HTML/ElementTest.php - diff --git a/samples/Sample_09_Tables.php b/samples/Sample_09_Tables.php index f1b67c9ecc..877072d898 100644 --- a/samples/Sample_09_Tables.php +++ b/samples/Sample_09_Tables.php @@ -56,11 +56,13 @@ /* * 3. colspan (gridSpan) and rowspan (vMerge) - * --------------------- - * | | B | | - * | A |--------| E | - * | | C | D | | - * --------------------- + * ------------------------- + * | A | B | C | + * |-----|-----------| | + * | D | | + * ------|-----------| | + * | E | F | G | | + * ------------------------- */ $section->addPageBreak(); @@ -77,25 +79,20 @@ $phpWord->addTableStyle($spanTableStyleName, $fancyTableStyle); $table = $section->addTable($spanTableStyleName); -$table->addRow(); - -$cell1 = $table->addCell(2000, $cellRowSpan); -$textrun1 = $cell1->addTextRun($cellHCentered); -$textrun1->addText('A'); -$textrun1->addFootnote()->addText('Row span'); - -$cell2 = $table->addCell(4000, $cellColSpan); -$textrun2 = $cell2->addTextRun($cellHCentered); -$textrun2->addText('B'); -$textrun2->addFootnote()->addText('Column span'); +$row1 = $table->addRow(); +$row1->addCell(500)->addText('A'); +$row1->addCell(1000, ['gridSpan' => 2])->addText('B'); +$row1->addCell(500, ['vMerge' => 'restart'])->addText('C'); -$table->addCell(2000, $cellRowSpan)->addText('E', null, $cellHCentered); +$row2 = $table->addRow(); +$row2->addCell(1500, ['gridSpan' => 3])->addText('D'); +$row2->addCell(null, ['vMerge' => 'continue']); -$table->addRow(); -$table->addCell(null, $cellRowContinue); -$table->addCell(2000, $cellVCentered)->addText('C', null, $cellHCentered); -$table->addCell(2000, $cellVCentered)->addText('D', null, $cellHCentered); -$table->addCell(null, $cellRowContinue); +$row3 = $table->addRow(); +$row3->addCell(500)->addText('E'); +$row3->addCell(500)->addText('F'); +$row3->addCell(500)->addText('G'); +$row3->addCell(null, ['vMerge' => 'continue']); /* * 4. colspan (gridSpan) and rowspan (vMerge) diff --git a/src/PhpWord/Writer/HTML/Element/Table.php b/src/PhpWord/Writer/HTML/Element/Table.php index f4acc8cdf4..7f8d0bcc3c 100644 --- a/src/PhpWord/Writer/HTML/Element/Table.php +++ b/src/PhpWord/Writer/HTML/Element/Table.php @@ -65,16 +65,9 @@ public function write() $cellColSpan = $cellStyle->getGridSpan(); $cellRowSpan = 1; $cellVMerge = $cellStyle->getVMerge(); - // If this is the first cell of the vertical merge, find out how man rows it spans + // If this is the first cell of the vertical merge, find out how many rows it spans if ($cellVMerge === 'restart') { - for ($k = $i + 1; $k < $rowCount; ++$k) { - $kRowCells = $rows[$k]->getCells(); - if (isset($kRowCells[$j]) && $kRowCells[$j]->getStyle()->getVMerge() === 'continue') { - ++$cellRowSpan; - } else { - break; - } - } + $cellRowSpan = $this->calculateCellRowSpan($rows, $i, $j); } // Ignore cells that are merged vertically with previous rows if ($cellVMerge !== 'continue') { @@ -131,4 +124,58 @@ private function getTableStyle($tableStyle = null): string return ' style="' . $style . '"'; } + + /** + * Calculates cell rowspan. + * + * @param \PhpOffice\PhpWord\Element\Row[] $rows + */ + private function calculateCellRowSpan(array $rows, int $rowIndex, int $colIndex): int + { + $currentRow = $rows[$rowIndex]; + $currentRowCells = $currentRow->getCells(); + $shiftedColIndex = 0; + + foreach ($currentRowCells as $cell) { + if ($cell === $currentRowCells[$colIndex]) { + break; + } + + $colSpan = 1; + + if ($cell->getStyle()->getGridSpan() !== null) { + $colSpan = $cell->getStyle()->getGridSpan(); + } + + $shiftedColIndex += $colSpan; + } + + $rowCount = count($rows); + $rowSpan = 1; + + for ($i = $rowIndex + 1; $i < $rowCount; ++$i) { + $rowCells = $rows[$i]->getCells(); + $colIndex = 0; + + foreach ($rowCells as $cell) { + if ($colIndex === $shiftedColIndex) { + if ($cell->getStyle()->getVMerge() === 'continue') { + ++$rowSpan; + } + + break; + } + + $colSpan = 1; + + if ($cell->getStyle()->getGridSpan() !== null) { + $colSpan = $cell->getStyle()->getGridSpan(); + } + + $colIndex += $colSpan; + } + } + + return $rowSpan; + } } diff --git a/tests/PhpWordTests/Writer/HTML/ElementTest.php b/tests/PhpWordTests/Writer/HTML/ElementTest.php index 64fa5a31ff..3fee31a69b 100644 --- a/tests/PhpWordTests/Writer/HTML/ElementTest.php +++ b/tests/PhpWordTests/Writer/HTML/ElementTest.php @@ -73,7 +73,7 @@ public function testWriteTrackChanges(): void $text2 = $section->addText('my other text'); $text2->setTrackChange(new TrackChange(TrackChange::DELETED, 'another author', new DateTime())); - $dom = $this->getAsHTML($phpWord); + $dom = Helper::getAsHTML($phpWord); $xpath = new DOMXPath($dom); self::assertEquals(1, $xpath->query('/html/body/div/p[1]/ins')->length); @@ -97,7 +97,7 @@ public function testWriteColSpan(): void $cell22 = $row2->addCell(500); $cell22->addText('second cell'); - $dom = $this->getAsHTML($phpWord); + $dom = Helper::getAsHTML($phpWord); $xpath = new DOMXPath($dom); self::assertEquals(1, $xpath->query('/html/body/div/table/tr[1]/td')->length); @@ -131,7 +131,7 @@ public function testWriteRowSpan(): void $row3->addCell(null, ['vMerge' => 'continue']); $row3->addCell(500)->addText('third cell being spanned'); - $dom = $this->getAsHTML($phpWord); + $dom = Helper::getAsHTML($phpWord); $xpath = new DOMXPath($dom); self::assertEquals(2, $xpath->query('/html/body/div/table/tr[1]/td')->length); @@ -139,13 +139,41 @@ public function testWriteRowSpan(): void self::assertEquals(1, $xpath->query('/html/body/div/table/tr[2]/td')->length); } - private function getAsHTML(PhpWord $phpWord) + /** + * Tests writing table with rowspan and colspan. + */ + public function testWriteRowSpanAndColSpan(): void { - $htmlWriter = new HTML($phpWord); - $dom = new DOMDocument(); - $dom->loadHTML($htmlWriter->getContent()); + $phpWord = new PhpWord(); + $section = $phpWord->addSection(); + $table = $section->addTable(); + + $row1 = $table->addRow(); + $row1->addCell(500)->addText('A'); + $row1->addCell(1000, ['gridSpan' => 2])->addText('B'); + $row1->addCell(500, ['vMerge' => 'restart'])->addText('C'); + + $row2 = $table->addRow(); + $row2->addCell(1500, ['gridSpan' => 3])->addText('D'); + $row2->addCell(null, ['vMerge' => 'continue']); + + $row3 = $table->addRow(); + $row3->addCell(500)->addText('E'); + $row3->addCell(500)->addText('F'); + $row3->addCell(500)->addText('G'); + $row3->addCell(null, ['vMerge' => 'continue']); + + $dom = Helper::getAsHTML($phpWord); + $xpath = new DOMXPath($dom); + + self::assertEquals(3, $xpath->query('/html/body/div/table/tr[1]/td')->length); + self::assertEquals('2', $xpath->query('/html/body/div/table/tr[1]/td[2]')->item(0)->attributes->getNamedItem('colspan')->textContent); + self::assertEquals('3', $xpath->query('/html/body/div/table/tr[1]/td[3]')->item(0)->attributes->getNamedItem('rowspan')->textContent); + + self::assertEquals(1, $xpath->query('/html/body/div/table/tr[2]/td')->length); + self::assertEquals('3', $xpath->query('/html/body/div/table/tr[2]/td[1]')->item(0)->attributes->getNamedItem('colspan')->textContent); - return $dom; + self::assertEquals(3, $xpath->query('/html/body/div/table/tr[3]/td')->length); } public function testWriteTitleTextRun(): void @@ -208,7 +236,7 @@ public function testWriteTableLayout(): void $row2 = $table2->addRow(); $row2->addCell()->addText('auto layout table'); - $dom = $this->getAsHTML($phpWord); + $dom = Helper::getAsHTML($phpWord); $xpath = new DOMXPath($dom); self::assertEquals('table-layout: fixed;', $xpath->query('/html/body/div/table[1]')->item(0)->attributes->getNamedItem('style')->textContent);