Skip to content

Commit

Permalink
HTML Writer : Fixed rowspan for tables (#2659)
Browse files Browse the repository at this point in the history
Co-authored-by: Evgeniya Gryaznova <[email protected]>
  • Loading branch information
Progi1984 and andomiell authored Aug 26, 2024
1 parent a7b9030 commit 39e806b
Show file tree
Hide file tree
Showing 6 changed files with 118 additions and 51 deletions.
5 changes: 3 additions & 2 deletions docs/changes/2.x/2.0.0.md → docs/changes/1.x/1.3.0.md
Original file line number Diff line number Diff line change
@@ -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

Expand All @@ -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

Expand Down
3 changes: 1 addition & 2 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
9 changes: 2 additions & 7 deletions phpstan-baseline.neon
Original file line number Diff line number Diff line change
Expand Up @@ -1787,17 +1787,12 @@ parameters:

-
message: "#^Cannot access property \\$length on DOMNodeList\\<DOMNode\\>\\|false\\.$#"
count: 6
count: 9
path: tests/PhpWordTests/Writer/HTML/ElementTest.php

-
message: "#^Cannot call method item\\(\\) on DOMNodeList\\<DOMNode\\>\\|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

-
Expand Down
41 changes: 19 additions & 22 deletions samples/Sample_09_Tables.php
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,13 @@

/*
* 3. colspan (gridSpan) and rowspan (vMerge)
* ---------------------
* | | B | |
* | A |--------| E |
* | | C | D | |
* ---------------------
* -------------------------
* | A | B | C |
* |-----|-----------| |
* | D | |
* ------|-----------| |
* | E | F | G | |
* -------------------------
*/

$section->addPageBreak();
Expand All @@ -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)
Expand Down
65 changes: 56 additions & 9 deletions src/PhpWord/Writer/HTML/Element/Table.php
Original file line number Diff line number Diff line change
Expand Up @@ -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') {
Expand Down Expand Up @@ -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;
}
}
46 changes: 37 additions & 9 deletions tests/PhpWordTests/Writer/HTML/ElementTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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);
Expand Down Expand Up @@ -131,21 +131,49 @@ 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);
self::assertEquals('3', $xpath->query('/html/body/div/table/tr[1]/td[1]')->item(0)->attributes->getNamedItem('rowspan')->textContent);
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
Expand Down Expand Up @@ -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);
Expand Down

0 comments on commit 39e806b

Please sign in to comment.