Skip to content

Commit b80cdb3

Browse files
authored
Merge pull request #240 from Masterminds/self-closing-table-elements
add support for optional end tags for table elements
2 parents f47dcf3 + de52ce1 commit b80cdb3

File tree

3 files changed

+82
-0
lines changed

3 files changed

+82
-0
lines changed

src/HTML5/Elements.php

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,24 @@ class Elements
7171
*/
7272
const BLOCK_ONLY_INLINE = 128;
7373

74+
/**
75+
* Elements with optional end tags that cause auto-closing of previous and parent tags,
76+
* as example most of the table related tags, see https://www.w3.org/TR/html401/struct/tables.html
77+
* Structure is as follows:
78+
* TAG-NAME => [PARENT-TAG-NAME-TO-CLOSE1, PARENT-TAG-NAME-TO-CLOSE2, ...].
79+
*
80+
* Order is important, after auto-closing one parent with might have to close also their parent.
81+
*
82+
* @var array<string, string[]>
83+
*/
84+
public static $optionalEndElementsParentsToClose = array(
85+
'tr' => array('td', 'tr'),
86+
'td' => array('td', 'th'),
87+
'th' => array('td', 'th'),
88+
'tfoot' => array('td', 'th', 'tr', 'tbody', 'thead'),
89+
'tbody' => array('td', 'th', 'tr', 'thead'),
90+
);
91+
7492
/**
7593
* The HTML5 elements as defined in http://dev.w3.org/html5/markup/elements.html.
7694
*

src/HTML5/Parser/DOMTreeBuilder.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -359,6 +359,16 @@ public function startTag($name, $attributes = array(), $selfClosing = false)
359359
$this->onlyInline = null;
360360
}
361361

362+
// some elements as table related tags might have optional end tags that force us to auto close multiple tags
363+
// https://www.w3.org/TR/html401/struct/tables.html
364+
if ($this->current instanceof \DOMElement && isset(Elements::$optionalEndElementsParentsToClose[$lname])) {
365+
foreach (Elements::$optionalEndElementsParentsToClose[$lname] as $parentElName) {
366+
if ($this->current instanceof \DOMElement && $this->current->tagName === $parentElName) {
367+
$this->autoclose($parentElName);
368+
}
369+
}
370+
}
371+
362372
try {
363373
$prefix = ($pos = strpos($lname, ':')) ? substr($lname, 0, $pos) : '';
364374

test/HTML5/Html5Test.php

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,60 @@ public function testImageTagsInSvg()
5656
$this->assertEmpty($this->html5->getErrors());
5757
}
5858

59+
public function testSelfClosingTableHierarchyElements()
60+
{
61+
$html = '
62+
<table>
63+
<thead>
64+
<tr>
65+
<th>0
66+
<tbody>
67+
<tr>
68+
<td>A
69+
<tr>
70+
<td>B1
71+
<td>B2
72+
<tr>
73+
<td>C
74+
<tfoot>
75+
<tr>
76+
<th>1
77+
<td>2
78+
</table>';
79+
80+
$expected = '<table>
81+
<thead>
82+
<tr>
83+
<th>0</th>
84+
</tr>
85+
</thead>
86+
<tbody>
87+
<tr>
88+
<td>A</td>
89+
</tr>
90+
<tr>
91+
<td>B1</td>
92+
<td>B2</td>
93+
</tr>
94+
<tr>
95+
<td>C</td>
96+
</tr>
97+
</tbody>
98+
<tfoot>
99+
<tr>
100+
<th>1</th>
101+
<td>2</td>
102+
</tr>
103+
</tfoot>
104+
</table>';
105+
106+
$doc = $this->html5->loadHTMLFragment($html);
107+
$this->assertSame(
108+
preg_replace('/\s+/', '', $expected),
109+
preg_replace('/\s+/', '', $this->html5->saveHTML($doc))
110+
);
111+
}
112+
59113
public function testLoadOptions()
60114
{
61115
// doc

0 commit comments

Comments
 (0)