Skip to content

Commit

Permalink
HTML Writer : Fixed rowspan for tables
Browse files Browse the repository at this point in the history
  • Loading branch information
andomiell authored and Progi1984 committed Aug 26, 2024
1 parent a7b9030 commit e80deed
Show file tree
Hide file tree
Showing 3 changed files with 122 additions and 37 deletions.
43 changes: 20 additions & 23 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 @@ -74,28 +76,23 @@
$cellVCentered = ['valign' => 'center'];

$spanTableStyleName = 'Colspan Rowspan';
$phpWord->addTableStyle($spanTableStyleName, $fancyTableStyle);
$phpWord->addTableStyle($spanTableStyleName, $styleTable);
$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, array('gridSpan' => 2))->addText('B');
$row1->addCell(500, array('vMerge' => 'restart'))->addText('C');

$table->addCell(2000, $cellRowSpan)->addText('E', null, $cellHCentered);
$row2 = $table->addRow();
$row2->addCell(1500, array('gridSpan' => 3))->addText('D');
$row2->addCell(null, array('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, array('vMerge' => 'continue'));

/*
* 4. colspan (gridSpan) and rowspan (vMerge)
Expand Down
69 changes: 60 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,62 @@ private function getTableStyle($tableStyle = null): string

return ' style="' . $style . '"';
}

/**
* Calculates cell rowspan.
*
* @param \PhpOffice\PhpWord\Element\Row[] $rows
* @param int $rowIndex
* @param int $colIndex
*
* @return int
*/
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;
}
}
47 changes: 42 additions & 5 deletions tests/PhpWordTests/Writer/HTML/ElementTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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();

return $dom;
$row1 = $table->addRow();
$row1->addCell(500)->addText('A');
$row1->addCell(1000, array('gridSpan' => 2))->addText('B');
$row1->addCell(500, array('vMerge' => 'restart'))->addText('C');

$row2 = $table->addRow();
$row2->addCell(1500, array('gridSpan' => 3))->addText('D');
$row2->addCell(null, array('vMerge' => 'continue'));

$row3 = $table->addRow();
$row3->addCell(500)->addText('E');
$row3->addCell(500)->addText('F');
$row3->addCell(500)->addText('G');
$row3->addCell(null, array('vMerge' => 'continue'));

$dom = $this->getAsHTML($phpWord);
$xpath = new \DOMXPath($dom);

$this->assertEquals(3, $xpath->query('/html/body/table/tr[1]/td')->length);

Check failure on line 169 in tests/PhpWordTests/Writer/HTML/ElementTest.php

View workflow job for this annotation

GitHub Actions / phpstan

Cannot access property $length on DOMNodeList<DOMNode>|false.

Check failure on line 169 in tests/PhpWordTests/Writer/HTML/ElementTest.php

View workflow job for this annotation

GitHub Actions / PHP 8.2

Failed asserting that 0 matches expected 3.

Check failure on line 169 in tests/PhpWordTests/Writer/HTML/ElementTest.php

View workflow job for this annotation

GitHub Actions / PHP 8.1

Failed asserting that 0 matches expected 3.

Check failure on line 169 in tests/PhpWordTests/Writer/HTML/ElementTest.php

View workflow job for this annotation

GitHub Actions / PHP 8.3

Failed asserting that 0 matches expected 3.

Check failure on line 169 in tests/PhpWordTests/Writer/HTML/ElementTest.php

View workflow job for this annotation

GitHub Actions / PHP 7.1.3

Failed asserting that 0 matches expected 3.

Check failure on line 169 in tests/PhpWordTests/Writer/HTML/ElementTest.php

View workflow job for this annotation

GitHub Actions / PHP 7.3

Failed asserting that 0 matches expected 3.

Check failure on line 169 in tests/PhpWordTests/Writer/HTML/ElementTest.php

View workflow job for this annotation

GitHub Actions / PHP 8.0

Failed asserting that 0 matches expected 3.

Check failure on line 169 in tests/PhpWordTests/Writer/HTML/ElementTest.php

View workflow job for this annotation

GitHub Actions / PHP 7.4

Failed asserting that 0 matches expected 3.
$this->assertEquals('2', $xpath->query('/html/body/table/tr[1]/td[2]')->item(0)->attributes->getNamedItem('colspan')->textContent);
$this->assertEquals('3', $xpath->query('/html/body/table/tr[1]/td[3]')->item(0)->attributes->getNamedItem('rowspan')->textContent);

$this->assertEquals(1, $xpath->query('/html/body/table/tr[2]/td')->length);

Check failure on line 173 in tests/PhpWordTests/Writer/HTML/ElementTest.php

View workflow job for this annotation

GitHub Actions / phpstan

Cannot access property $length on DOMNodeList<DOMNode>|false.
$this->assertEquals('3', $xpath->query('/html/body/table/tr[2]/td[1]')->item(0)->attributes->getNamedItem('colspan')->textContent);

Check failure on line 174 in tests/PhpWordTests/Writer/HTML/ElementTest.php

View workflow job for this annotation

GitHub Actions / phpstan

Cannot call method item() on DOMNodeList<DOMNode>|false.

$this->assertEquals(3, $xpath->query('/html/body/table/tr[3]/td')->length);

Check failure on line 176 in tests/PhpWordTests/Writer/HTML/ElementTest.php

View workflow job for this annotation

GitHub Actions / phpstan

Cannot access property $length on DOMNodeList<DOMNode>|false.
}

public function testWriteTitleTextRun(): void
Expand Down Expand Up @@ -214,4 +242,13 @@ public function testWriteTableLayout(): void
self::assertEquals('table-layout: fixed;', $xpath->query('/html/body/div/table[1]')->item(0)->attributes->getNamedItem('style')->textContent);

Check failure on line 242 in tests/PhpWordTests/Writer/HTML/ElementTest.php

View workflow job for this annotation

GitHub Actions / phpstan

Cannot call method item() on DOMNodeList<DOMNode>|false.
self::assertEquals('table-layout: auto;', $xpath->query('/html/body/div/table[2]')->item(0)->attributes->getNamedItem('style')->textContent);

Check failure on line 243 in tests/PhpWordTests/Writer/HTML/ElementTest.php

View workflow job for this annotation

GitHub Actions / phpstan

Cannot call method item() on DOMNodeList<DOMNode>|false.
}

private function getAsHTML(PhpWord $phpWord): DOMDocument
{
$htmlWriter = new HTML($phpWord);
$dom = new DOMDocument();
$dom->loadHTML($htmlWriter->getContent());

return $dom;
}
}

0 comments on commit e80deed

Please sign in to comment.