Skip to content

Commit 39e806b

Browse files
Progi1984andomiell
andauthored
HTML Writer : Fixed rowspan for tables (#2659)
Co-authored-by: Evgeniya Gryaznova <andomiell@mail.ru>
1 parent a7b9030 commit 39e806b

File tree

6 files changed

+118
-51
lines changed

6 files changed

+118
-51
lines changed

docs/changes/2.x/2.0.0.md renamed to docs/changes/1.x/1.3.0.md

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
# [2.0.0](https://github.com/PHPOffice/PHPWord/tree/2.0.0) (WIP)
1+
# [1.3.0](https://github.com/PHPOffice/PHPWord/tree/1.3.0) (WIP)
22

3-
[Full Changelog](https://github.com/PHPOffice/PHPWord/compare/1.2.0...2.0.0)
3+
[Full Changelog](https://github.com/PHPOffice/PHPWord/compare/1.2.0...1.3.0)
44

55
## Enhancements
66

@@ -27,6 +27,7 @@
2727
- 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)
2828
- 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)
2929
- 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)
30+
- HTML Writer : Fixed rowspan for tables by [@andomiell](https://github.com/andomiell) in [#2659](https://github.com/PHPOffice/PHPWord/pull/2659)
3031

3132
### Miscellaneous
3233

mkdocs.yml

+1-2
Original file line numberDiff line numberDiff line change
@@ -86,9 +86,8 @@ nav:
8686
- How to: 'howto.md'
8787
- Credits: 'credits.md'
8888
- Releases:
89-
- '2.x':
90-
- '2.0.0 (WIP)': 'changes/2.x/2.0.0.md'
9189
- '1.x':
90+
- '1.3.0 (WIP)': 'changes/1.x/1.3.0.md'
9291
- '1.2.0': 'changes/1.x/1.2.0.md'
9392
- '1.1.0': 'changes/1.x/1.1.0.md'
9493
- '1.0.0': 'changes/1.x/1.0.0.md'

phpstan-baseline.neon

+2-7
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

+19-22
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();
@@ -77,25 +79,20 @@
7779
$phpWord->addTableStyle($spanTableStyleName, $fancyTableStyle);
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, ['gridSpan' => 2])->addText('B');
85+
$row1->addCell(500, ['vMerge' => 'restart'])->addText('C');
9186

92-
$table->addCell(2000, $cellRowSpan)->addText('E', null, $cellHCentered);
87+
$row2 = $table->addRow();
88+
$row2->addCell(1500, ['gridSpan' => 3])->addText('D');
89+
$row2->addCell(null, ['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, ['vMerge' => 'continue']);
9996

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

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

+56-9
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,58 @@ 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+
*/
133+
private function calculateCellRowSpan(array $rows, int $rowIndex, int $colIndex): int
134+
{
135+
$currentRow = $rows[$rowIndex];
136+
$currentRowCells = $currentRow->getCells();
137+
$shiftedColIndex = 0;
138+
139+
foreach ($currentRowCells as $cell) {
140+
if ($cell === $currentRowCells[$colIndex]) {
141+
break;
142+
}
143+
144+
$colSpan = 1;
145+
146+
if ($cell->getStyle()->getGridSpan() !== null) {
147+
$colSpan = $cell->getStyle()->getGridSpan();
148+
}
149+
150+
$shiftedColIndex += $colSpan;
151+
}
152+
153+
$rowCount = count($rows);
154+
$rowSpan = 1;
155+
156+
for ($i = $rowIndex + 1; $i < $rowCount; ++$i) {
157+
$rowCells = $rows[$i]->getCells();
158+
$colIndex = 0;
159+
160+
foreach ($rowCells as $cell) {
161+
if ($colIndex === $shiftedColIndex) {
162+
if ($cell->getStyle()->getVMerge() === 'continue') {
163+
++$rowSpan;
164+
}
165+
166+
break;
167+
}
168+
169+
$colSpan = 1;
170+
171+
if ($cell->getStyle()->getGridSpan() !== null) {
172+
$colSpan = $cell->getStyle()->getGridSpan();
173+
}
174+
175+
$colIndex += $colSpan;
176+
}
177+
}
178+
179+
return $rowSpan;
180+
}
134181
}

tests/PhpWordTests/Writer/HTML/ElementTest.php

+37-9
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, ['gridSpan' => 2])->addText('B');
154+
$row1->addCell(500, ['vMerge' => 'restart'])->addText('C');
155+
156+
$row2 = $table->addRow();
157+
$row2->addCell(1500, ['gridSpan' => 3])->addText('D');
158+
$row2->addCell(null, ['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, ['vMerge' => 'continue']);
165+
166+
$dom = Helper::getAsHTML($phpWord);
167+
$xpath = new DOMXPath($dom);
168+
169+
self::assertEquals(3, $xpath->query('/html/body/div/table/tr[1]/td')->length);
170+
self::assertEquals('2', $xpath->query('/html/body/div/table/tr[1]/td[2]')->item(0)->attributes->getNamedItem('colspan')->textContent);
171+
self::assertEquals('3', $xpath->query('/html/body/div/table/tr[1]/td[3]')->item(0)->attributes->getNamedItem('rowspan')->textContent);
172+
173+
self::assertEquals(1, $xpath->query('/html/body/div/table/tr[2]/td')->length);
174+
self::assertEquals('3', $xpath->query('/html/body/div/table/tr[2]/td[1]')->item(0)->attributes->getNamedItem('colspan')->textContent);
147175

148-
return $dom;
176+
self::assertEquals(3, $xpath->query('/html/body/div/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)