diff --git a/src/HTML5/Elements.php b/src/HTML5/Elements.php index 1632dcf..5d8cfd4 100644 --- a/src/HTML5/Elements.php +++ b/src/HTML5/Elements.php @@ -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 + */ + 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. * diff --git a/src/HTML5/Parser/DOMTreeBuilder.php b/src/HTML5/Parser/DOMTreeBuilder.php index 293d83e..d165b66 100644 --- a/src/HTML5/Parser/DOMTreeBuilder.php +++ b/src/HTML5/Parser/DOMTreeBuilder.php @@ -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) : ''; diff --git a/test/HTML5/Html5Test.php b/test/HTML5/Html5Test.php index 1887a8d..62f36e1 100644 --- a/test/HTML5/Html5Test.php +++ b/test/HTML5/Html5Test.php @@ -56,6 +56,60 @@ public function testImageTagsInSvg() $this->assertEmpty($this->html5->getErrors()); } + public function testSelfClosingTableHierarchyElements() + { + $html = ' + + + + + + + + + +
0 +
A +
B1 + B2 +
C +
1 + 2 +
'; + + $expected = ' + + + + + + + + + + + + + + + + + + + + + + + +
0
A
B1B2
C
12
'; + + $doc = $this->html5->loadHTMLFragment($html); + $this->assertSame( + preg_replace('/\s+/', '', $expected), + preg_replace('/\s+/', '', $this->html5->saveHTML($doc)) + ); + } + public function testLoadOptions() { // doc