Skip to content

Commit

Permalink
Merge pull request #240 from Masterminds/self-closing-table-elements
Browse files Browse the repository at this point in the history
add support for optional end tags for table elements
  • Loading branch information
goetas committed Oct 14, 2023
2 parents f47dcf3 + de52ce1 commit b80cdb3
Show file tree
Hide file tree
Showing 3 changed files with 82 additions and 0 deletions.
18 changes: 18 additions & 0 deletions src/HTML5/Elements.php
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,24 @@ class Elements
*/
const BLOCK_ONLY_INLINE = 128;

/**
* Elements with optional end tags that cause auto-closing of previous and parent tags,
* as example most of the table related tags, see https://www.w3.org/TR/html401/struct/tables.html
* Structure is as follows:
* TAG-NAME => [PARENT-TAG-NAME-TO-CLOSE1, PARENT-TAG-NAME-TO-CLOSE2, ...].
*
* Order is important, after auto-closing one parent with might have to close also their parent.
*
* @var array<string, string[]>
*/
public static $optionalEndElementsParentsToClose = array(
'tr' => array('td', 'tr'),
'td' => array('td', 'th'),
'th' => array('td', 'th'),
'tfoot' => array('td', 'th', 'tr', 'tbody', 'thead'),
'tbody' => array('td', 'th', 'tr', 'thead'),
);

/**
* The HTML5 elements as defined in http://dev.w3.org/html5/markup/elements.html.
*
Expand Down
10 changes: 10 additions & 0 deletions src/HTML5/Parser/DOMTreeBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -359,6 +359,16 @@ public function startTag($name, $attributes = array(), $selfClosing = false)
$this->onlyInline = null;
}

// some elements as table related tags might have optional end tags that force us to auto close multiple tags
// https://www.w3.org/TR/html401/struct/tables.html
if ($this->current instanceof \DOMElement && isset(Elements::$optionalEndElementsParentsToClose[$lname])) {
foreach (Elements::$optionalEndElementsParentsToClose[$lname] as $parentElName) {
if ($this->current instanceof \DOMElement && $this->current->tagName === $parentElName) {
$this->autoclose($parentElName);
}
}
}

try {
$prefix = ($pos = strpos($lname, ':')) ? substr($lname, 0, $pos) : '';

Expand Down
54 changes: 54 additions & 0 deletions test/HTML5/Html5Test.php
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,60 @@ public function testImageTagsInSvg()
$this->assertEmpty($this->html5->getErrors());
}

public function testSelfClosingTableHierarchyElements()
{
$html = '
<table>
<thead>
<tr>
<th>0
<tbody>
<tr>
<td>A
<tr>
<td>B1
<td>B2
<tr>
<td>C
<tfoot>
<tr>
<th>1
<td>2
</table>';

$expected = '<table>
<thead>
<tr>
<th>0</th>
</tr>
</thead>
<tbody>
<tr>
<td>A</td>
</tr>
<tr>
<td>B1</td>
<td>B2</td>
</tr>
<tr>
<td>C</td>
</tr>
</tbody>
<tfoot>
<tr>
<th>1</th>
<td>2</td>
</tr>
</tfoot>
</table>';

$doc = $this->html5->loadHTMLFragment($html);
$this->assertSame(
preg_replace('/\s+/', '', $expected),
preg_replace('/\s+/', '', $this->html5->saveHTML($doc))
);
}

public function testLoadOptions()
{
// doc
Expand Down

0 comments on commit b80cdb3

Please sign in to comment.