Browse Source

Fix xmlns attributes

This exposes the attributes in the DOM via a workaround, making it
possible to serialize them like any other attribute
split-manual
J. King 3 years ago
parent
commit
4c7953ddc8
  1. 8
      lib/DOM/Document.php
  2. 73
      lib/DOM/Element.php
  3. 10
      lib/TreeBuilder.php
  4. 3
      tests/cases/TestSerializer.php
  5. 2
      tests/cases/tree-construction/mensbeam01.dat

8
lib/DOM/Document.php

@ -50,15 +50,19 @@ class Document extends \DOMDocument {
public function createAttributeNS($namespaceURI, $qualifiedName) {
try {
return parent::createAttributeNS($namespaceURI, $qualifiedName);
$out = @parent::createAttributeNS($namespaceURI, $qualifiedName);
} catch (\DOMException $e) {
// The element 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->mangledAttributes = true;
$qualifiedName = $this->coerceName($qualifiedName);
return parent::createAttributeNS($namespaceURI, $qualifiedName);
$out = parent::createAttributeNS($namespaceURI, $qualifiedName);
}
if ($out === false) {
throw new \DOMException("Document element must be inserted first");
}
return $out;
}
public function createElement($name, $value = "") {

73
lib/DOM/Element.php

@ -50,52 +50,54 @@ class Element extends \DOMElement {
}
public function setAttribute($name, $value) {
try {
// If setting a class attribute and classList has been invoked use classList to
// set it.
if ($this->_classList !== null && $name === 'class') {
$this->_classList->value = $value;
} else {
// 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);
}
} 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);
if ($name === "id") {
$this->setIdAttribute($name, true);
}
}
}
public function setAttributeNS($namespaceURI, $qualifiedName, $value) {
try {
// If setting a class attribute and classList has been invoked use classList to
// set it.
if ($this->_classList !== null && $namespaceURI === null && $qualifiedName === 'class') {
$this->_classList->value = $value;
} else {
parent::setAttributeNS($namespaceURI, $qualifiedName, $value);
// If setting a class attribute and classList has been invoked use classList to
// set it.
if ($qualifiedName === 'class' && $namespaceURI === null && $this->_classList !== null) {
$this->_classList->value = $value;
} else {
try {
// NOTE: We create attribute nodes so that xmlns attributes don't get lost
$a = $this->ownerDocument->createAttributeNS($namespaceURI, $qualifiedName);
} 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;
$qualifiedName = $this->coerceName($qualifiedName);
$a = $this->ownerDocument->createAttributeNS($namespaceURI, $qualifiedName);
}
$a->value = $this->escapeString($value, true);
$this->appendChild($a);
if ($qualifiedName === "id" && $namespaceURI === null) {
$this->setIdAttribute($qualifiedName, true);
}
} 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;
$qualifiedName = $this->coerceName($qualifiedName);
parent::setAttributeNS($namespaceURI, $qualifiedName, $value);
}
if ($qualifiedName === "id" && $namespaceURI === null) {
$this->setIdAttribute($qualifiedName, true);
}
}
public function setAttributeNode(\DOMAttr $attribute) {
parent::setAttributeNode($attribute);
if ($attribute->name === 'id') {
$this->setIdAttribute($attribute->name, true);
}
@ -103,8 +105,7 @@ class Element extends \DOMElement {
public function setAttributeNodeNS(\DOMAttr $attribute) {
parent::setAttributeNodeNS($attribute);
if ($attribute->name === 'id') {
if ($attribute->name === 'id' && $attribute->namespaceURI === null) {
$this->setIdAttribute($attribute->name, true);
}
}

10
lib/TreeBuilder.php

@ -4150,6 +4150,12 @@ class TreeBuilder {
# Let element be the result of creating an element given document,
# localName, given namespace, null, and is.
$element = $this->DOM->createElementNS($namespace, $token->name);
// DEVIATION: If there is no document (root) element yet, temporarily
// insert this element so that creating attributes for it does not
// fail due to a PHP DOM limitation
if (!$this->DOM->documentElement) {
$this->DOM->appendChild($element);
}
# Append each attribute in the given token to element.
foreach ($token->attributes as $attr) {
# If element has an xmlns attribute in the XMLNS namespace whose value
@ -4168,6 +4174,10 @@ class TreeBuilder {
$element->setAttributeNS($attr->namespace, $attr->name, $attr->value);
}
}
if ($this->DOM->documentElement && $this->DOM->documentElement->isSameNode($element)) {
// Pop off the document element if it was inserted above
$this->DOM->removeChild($element);
}
# Return element.
return $element;
}

3
tests/cases/TestSerializer.php

@ -17,7 +17,7 @@ use MensBeam\HTML\Parser;
* @covers \MensBeam\HTML\Comment
* @covers \MensBeam\HTML\Text
*/
class TestTreeConstructor extends \PHPUnit\Framework\TestCase {
class TestSerializer extends \PHPUnit\Framework\TestCase {
use \MensBeam\HTML\EscapeString;
protected $out;
@ -85,6 +85,7 @@ class TestTreeConstructor extends \PHPUnit\Framework\TestCase {
protected function buildTree(array $data, bool $fragment): \DOMNode {
$document = new Document;
$document->appendChild($document->createElement("html"));
$out = $fragment ? $document->createDocumentFragment() : $document;
$cur = $out;
$pad = 2;

2
tests/cases/tree-construction/mensbeam01.dat

@ -7,6 +7,7 @@
| <head>
| <body>
| <svg svg>
| xmlns xmlns="http://www.w3.org/2000/svg"
#data
<!DOCTYPE html><svg xmlns="http://www.w3.org/1999/xlink"/>
@ -28,6 +29,7 @@
| <head>
| <body>
| <svg svg>
| xmlns xlink="http://www.w3.org/1999/xlink"
#data
<!DOCTYPE html><svg xlink:href="http://example.com/"/>

Loading…
Cancel
Save