diff --git a/lib/DOM/Element.php b/lib/DOM/Element.php index 8af4a39..1198678 100644 --- a/lib/DOM/Element.php +++ b/lib/DOM/Element.php @@ -63,28 +63,7 @@ class Element extends \DOMElement { } public function setAttribute($name, $value) { - // Normalize the attribute name per modern DOM specifications. - $name = strtolower(trim($name)); - - // If setting a class attribute and classList has been invoked use classList to - // set it. - if ($name === 'class' && $this->_classList !== null) { - $this->_classList->value = $value; - } else { - try { - parent::setAttribute($name, $value); - } catch (\DOMException $e) { - // The attribute name is invalid for XML - // Replace any offending characters with "UHHHHHH" where H are the - // uppercase hexadecimal digits of the character's code point - $this->ownerDocument->mangledAttributes = true; - $name = $this->coerceName($name); - parent::setAttribute($name, $value); - } - if ($name === "id") { - $this->setIdAttribute($name, true); - } - } + $this->setAttributeNS(null, $name, $value); } public function setAttributeNS($namespaceURI, $qualifiedName, $value) { @@ -93,7 +72,9 @@ class Element extends \DOMElement { $namespaceURI = trim($namespaceURI); } $qualifiedName = trim($qualifiedName); - + if ($namespaceURI === null && ($this->namespaceURI ?? Parser::HTML_NAMESPACE) === Parser::HTML_NAMESPACE && !$this->hasAttributeNS($namespaceURI, $qualifiedName)) { + $qualifiedName = trim(strtolower($qualifiedName)); + } // If setting a class attribute and classList has been invoked use classList to // set it. if ($qualifiedName === 'class' && $namespaceURI === null && $this->_classList !== null) { diff --git a/lib/TreeBuilder.php b/lib/TreeBuilder.php index ace1ae4..c123c9b 100644 --- a/lib/TreeBuilder.php +++ b/lib/TreeBuilder.php @@ -359,14 +359,6 @@ class TreeBuilder { return true; })()); - // If attribute name coercison has occurred at some earlier point, - // we must coerce all attributes on html and body start tags in - // case they are relocated to existing elements - if ($token instanceof StartTagToken && $this->DOM->mangledAttributes && in_array($token->name, ["html", "body"])) { - foreach ($token->attributes as $attr) { - $attr->name = $this->coerceName($attr->name); - } - } # 13.2.6.4. The rules for parsing tokens in HTML content // OPTIMIZATION: Evaluation the "in body" mode first is // faster for typical documents @@ -386,7 +378,10 @@ class TreeBuilder { # not, add the attribute and its corresponding value to that element. $top = $this->stack[0]; foreach ($token->attributes as $a) { - if (!$top->hasAttributeNS(null, $a->name)) { + // If attribute name coercison has occurred at some earlier point, + // we must coerce all attributes on html and body start tags in + // case they are relocated to existing elements + if (!$top->hasAttributeNS(null, $this->DOM->mangledAttributes ? $this->coerceName($a->name) : $a->name)) { $top->setAttributeNS(null, $a->name, $a->value); } } @@ -414,7 +409,10 @@ class TreeBuilder { $this->framesetOk = false; $body = $this->stack[1]; foreach ($token->attributes as $a) { - if (!$body->hasAttributeNS(null, $a->name)) { + // If attribute name coercison has occurred at some earlier point, + // we must coerce all attributes on html and body start tags in + // case they are relocated to existing elements + if (!$body->hasAttributeNS(null, $this->DOM->mangledAttributes ? $this->coerceName($a->name) : $a->name)) { $body->setAttributeNS(null, $a->name, $a->value); } } diff --git a/tests/cases/TestDOM.php b/tests/cases/TestDOM.php index 0cbb241..7fe227d 100644 --- a/tests/cases/TestDOM.php +++ b/tests/cases/TestDOM.php @@ -122,4 +122,30 @@ class TestDOM extends \PHPUnit\Framework\TestCase { ["TEST:TEST", "TESTU00003ATEST"], ]; } + + /** @dataProvider provideNamespacedAttributeSettings */ + public function testSetNamespoacedAttributes(?string $elementNS, ?string $attrNS, string $nameIn, string $nameOut): void { + $d = new Document; + $e = $d->createElementNS($elementNS, "test"); + $e->setAttributeNS($attrNS, $nameIn, "test"); + $this->assertTrue($e->hasAttributeNS($attrNS, $nameOut)); + } + + public function provideNamespacedAttributeSettings(): iterable { + return [ + [null, null, "test", "test"], + [null, null, "TEST", "test"], + ["http://www.w3.org/1999/xhtml", null, "test", "test"], + ["http://www.w3.org/1999/xhtml", null, "TEST", "test"], + [null, null, "test:test", "testU00003Atest"], + [null, null, "TEST:TEST", "testU00003Atest"], + ["http://www.w3.org/1999/xhtml", null, "test:test", "testU00003Atest"], + ["http://www.w3.org/1999/xhtml", null, "TEST:TEST", "testU00003Atest"], + [null, "http://www.w3.org/1999/xhtml", "test:test", "test"], + [null, "http://www.w3.org/1999/xhtml", "TEST:TEST", "TEST"], + ["http://www.w3.org/1998/Math/MathML", null, "test", "test"], + ["http://www.w3.org/1998/Math/MathML", null, "TEST", "TEST"], + [null, "http://www.w3.org/2000/xmlns/", "xmlns:xlink", "xlink"], + ]; + } }