Skip to content

Commit 0c1fa3a

Browse files
andomiellProgi1984
authored andcommitted
HTML Writer : Fixed rowspan for tables
1 parent a7b9030 commit 0c1fa3a

File tree

4 files changed

+119
-48
lines changed

4 files changed

+119
-48
lines changed

phpstan-baseline.neon

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1787,17 +1787,12 @@ parameters:
17871787

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

17931793
-
17941794
message: "#^Cannot call method item\\(\\) on DOMNodeList\\<DOMNode\\>\\|false\\.$#"
1795-
count: 8
1796-
path: tests/PhpWordTests/Writer/HTML/ElementTest.php
1797-
1798-
-
1799-
message: "#^Method PhpOffice\\\\PhpWordTests\\\\Writer\\\\HTML\\\\ElementTest\\:\\:getAsHTML\\(\\) has no return type specified\\.$#"
1800-
count: 1
1795+
count: 11
18011796
path: tests/PhpWordTests/Writer/HTML/ElementTest.php
18021797

18031798
-

samples/Sample_09_Tables.php

Lines changed: 20 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -56,11 +56,13 @@
5656

5757
/*
5858
* 3. colspan (gridSpan) and rowspan (vMerge)
59-
* ---------------------
60-
* | | B | |
61-
* | A |--------| E |
62-
* | | C | D | |
63-
* ---------------------
59+
* -------------------------
60+
* | A | B | C |
61+
* |-----|-----------| |
62+
* | D | |
63+
* ------|-----------| |
64+
* | E | F | G | |
65+
* -------------------------
6466
*/
6567

6668
$section->addPageBreak();
@@ -74,28 +76,23 @@
7476
$cellVCentered = ['valign' => 'center'];
7577

7678
$spanTableStyleName = 'Colspan Rowspan';
77-
$phpWord->addTableStyle($spanTableStyleName, $fancyTableStyle);
79+
$phpWord->addTableStyle($spanTableStyleName, $styleTable);
7880
$table = $section->addTable($spanTableStyleName);
7981

80-
$table->addRow();
81-
82-
$cell1 = $table->addCell(2000, $cellRowSpan);
83-
$textrun1 = $cell1->addTextRun($cellHCentered);
84-
$textrun1->addText('A');
85-
$textrun1->addFootnote()->addText('Row span');
86-
87-
$cell2 = $table->addCell(4000, $cellColSpan);
88-
$textrun2 = $cell2->addTextRun($cellHCentered);
89-
$textrun2->addText('B');
90-
$textrun2->addFootnote()->addText('Column span');
82+
$row1 = $table->addRow();
83+
$row1->addCell(500)->addText('A');
84+
$row1->addCell(1000, array('gridSpan' => 2))->addText('B');
85+
$row1->addCell(500, array('vMerge' => 'restart'))->addText('C');
9186

92-
$table->addCell(2000, $cellRowSpan)->addText('E', null, $cellHCentered);
87+
$row2 = $table->addRow();
88+
$row2->addCell(1500, array('gridSpan' => 3))->addText('D');
89+
$row2->addCell(null, array('vMerge' => 'continue'));
9390

94-
$table->addRow();
95-
$table->addCell(null, $cellRowContinue);
96-
$table->addCell(2000, $cellVCentered)->addText('C', null, $cellHCentered);
97-
$table->addCell(2000, $cellVCentered)->addText('D', null, $cellHCentered);
98-
$table->addCell(null, $cellRowContinue);
91+
$row3 = $table->addRow();
92+
$row3->addCell(500)->addText('E');
93+
$row3->addCell(500)->addText('F');
94+
$row3->addCell(500)->addText('G');
95+
$row3->addCell(null, array('vMerge' => 'continue'));
9996

10097
/*
10198
* 4. colspan (gridSpan) and rowspan (vMerge)

src/PhpWord/Writer/HTML/Element/Table.php

Lines changed: 60 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -65,16 +65,9 @@ public function write()
6565
$cellColSpan = $cellStyle->getGridSpan();
6666
$cellRowSpan = 1;
6767
$cellVMerge = $cellStyle->getVMerge();
68-
// If this is the first cell of the vertical merge, find out how man rows it spans
68+
// If this is the first cell of the vertical merge, find out how many rows it spans
6969
if ($cellVMerge === 'restart') {
70-
for ($k = $i + 1; $k < $rowCount; ++$k) {
71-
$kRowCells = $rows[$k]->getCells();
72-
if (isset($kRowCells[$j]) && $kRowCells[$j]->getStyle()->getVMerge() === 'continue') {
73-
++$cellRowSpan;
74-
} else {
75-
break;
76-
}
77-
}
70+
$cellRowSpan = $this->calculateCellRowSpan($rows, $i, $j);
7871
}
7972
// Ignore cells that are merged vertically with previous rows
8073
if ($cellVMerge !== 'continue') {
@@ -131,4 +124,62 @@ private function getTableStyle($tableStyle = null): string
131124

132125
return ' style="' . $style . '"';
133126
}
127+
128+
/**
129+
* Calculates cell rowspan.
130+
*
131+
* @param \PhpOffice\PhpWord\Element\Row[] $rows
132+
* @param int $rowIndex
133+
* @param int $colIndex
134+
*
135+
* @return int
136+
*/
137+
private function calculateCellRowSpan(array $rows, int $rowIndex, int $colIndex): int
138+
{
139+
$currentRow = $rows[$rowIndex];
140+
$currentRowCells = $currentRow->getCells();
141+
$shiftedColIndex = 0;
142+
143+
foreach ($currentRowCells as $cell) {
144+
if ($cell === $currentRowCells[$colIndex]) {
145+
break;
146+
}
147+
148+
$colSpan = 1;
149+
150+
if ($cell->getStyle()->getGridSpan() !== null) {
151+
$colSpan = $cell->getStyle()->getGridSpan();
152+
}
153+
154+
$shiftedColIndex += $colSpan;
155+
}
156+
157+
$rowCount = count($rows);
158+
$rowSpan = 1;
159+
160+
for ($i = $rowIndex + 1; $i < $rowCount; $i++) {
161+
$rowCells = $rows[$i]->getCells();
162+
$colIndex = 0;
163+
164+
foreach ($rowCells as $cell) {
165+
if ($colIndex === $shiftedColIndex) {
166+
if ($cell->getStyle()->getVMerge() === 'continue') {
167+
$rowSpan++;
168+
}
169+
170+
break;
171+
}
172+
173+
$colSpan = 1;
174+
175+
if ($cell->getStyle()->getGridSpan() !== null) {
176+
$colSpan = $cell->getStyle()->getGridSpan();
177+
}
178+
179+
$colIndex += $colSpan;
180+
}
181+
}
182+
183+
return $rowSpan;
184+
}
134185
}

tests/PhpWordTests/Writer/HTML/ElementTest.php

Lines changed: 37 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ public function testWriteTrackChanges(): void
7373
$text2 = $section->addText('my other text');
7474
$text2->setTrackChange(new TrackChange(TrackChange::DELETED, 'another author', new DateTime()));
7575

76-
$dom = $this->getAsHTML($phpWord);
76+
$dom = Helper::getAsHTML($phpWord);
7777
$xpath = new DOMXPath($dom);
7878

7979
self::assertEquals(1, $xpath->query('/html/body/div/p[1]/ins')->length);
@@ -97,7 +97,7 @@ public function testWriteColSpan(): void
9797
$cell22 = $row2->addCell(500);
9898
$cell22->addText('second cell');
9999

100-
$dom = $this->getAsHTML($phpWord);
100+
$dom = Helper::getAsHTML($phpWord);
101101
$xpath = new DOMXPath($dom);
102102

103103
self::assertEquals(1, $xpath->query('/html/body/div/table/tr[1]/td')->length);
@@ -131,21 +131,49 @@ public function testWriteRowSpan(): void
131131
$row3->addCell(null, ['vMerge' => 'continue']);
132132
$row3->addCell(500)->addText('third cell being spanned');
133133

134-
$dom = $this->getAsHTML($phpWord);
134+
$dom = Helper::getAsHTML($phpWord);
135135
$xpath = new DOMXPath($dom);
136136

137137
self::assertEquals(2, $xpath->query('/html/body/div/table/tr[1]/td')->length);
138138
self::assertEquals('3', $xpath->query('/html/body/div/table/tr[1]/td[1]')->item(0)->attributes->getNamedItem('rowspan')->textContent);
139139
self::assertEquals(1, $xpath->query('/html/body/div/table/tr[2]/td')->length);
140140
}
141141

142-
private function getAsHTML(PhpWord $phpWord)
142+
/**
143+
* Tests writing table with rowspan and colspan
144+
*/
145+
public function testWriteRowSpanAndColSpan(): void
143146
{
144-
$htmlWriter = new HTML($phpWord);
145-
$dom = new DOMDocument();
146-
$dom->loadHTML($htmlWriter->getContent());
147+
$phpWord = new PhpWord();
148+
$section = $phpWord->addSection();
149+
$table = $section->addTable();
150+
151+
$row1 = $table->addRow();
152+
$row1->addCell(500)->addText('A');
153+
$row1->addCell(1000, array('gridSpan' => 2))->addText('B');
154+
$row1->addCell(500, array('vMerge' => 'restart'))->addText('C');
155+
156+
$row2 = $table->addRow();
157+
$row2->addCell(1500, array('gridSpan' => 3))->addText('D');
158+
$row2->addCell(null, array('vMerge' => 'continue'));
159+
160+
$row3 = $table->addRow();
161+
$row3->addCell(500)->addText('E');
162+
$row3->addCell(500)->addText('F');
163+
$row3->addCell(500)->addText('G');
164+
$row3->addCell(null, array('vMerge' => 'continue'));
165+
166+
$dom = Helper::getAsHTML($phpWord);
167+
$xpath = new \DOMXPath($dom);
168+
169+
$this->assertEquals(3, $xpath->query('/html/body/table/tr[1]/td')->length);
170+
$this->assertEquals('2', $xpath->query('/html/body/table/tr[1]/td[2]')->item(0)->attributes->getNamedItem('colspan')->textContent);
171+
$this->assertEquals('3', $xpath->query('/html/body/table/tr[1]/td[3]')->item(0)->attributes->getNamedItem('rowspan')->textContent);
172+
173+
$this->assertEquals(1, $xpath->query('/html/body/table/tr[2]/td')->length);
174+
$this->assertEquals('3', $xpath->query('/html/body/table/tr[2]/td[1]')->item(0)->attributes->getNamedItem('colspan')->textContent);
147175

148-
return $dom;
176+
$this->assertEquals(3, $xpath->query('/html/body/table/tr[3]/td')->length);
149177
}
150178

151179
public function testWriteTitleTextRun(): void
@@ -208,7 +236,7 @@ public function testWriteTableLayout(): void
208236
$row2 = $table2->addRow();
209237
$row2->addCell()->addText('auto layout table');
210238

211-
$dom = $this->getAsHTML($phpWord);
239+
$dom = Helper::getAsHTML($phpWord);
212240
$xpath = new DOMXPath($dom);
213241

214242
self::assertEquals('table-layout: fixed;', $xpath->query('/html/body/div/table[1]')->item(0)->attributes->getNamedItem('style')->textContent);

0 commit comments

Comments
 (0)