|
|
@ -15,15 +15,8 @@ class Element extends \DOMElement { |
|
|
|
use ChildNode, DocumentOrElement, MagicProperties, Moonwalk, ParentNode, ToString, Walk; |
|
|
|
|
|
|
|
|
|
|
|
protected ?TokenList $_classList = null; |
|
|
|
|
|
|
|
protected function __get_classList(): TokenList { |
|
|
|
// Only create the class list if it is actually used. |
|
|
|
if ($this->_classList === null) { |
|
|
|
$this->_classList = new TokenList($this, 'class'); |
|
|
|
} |
|
|
|
|
|
|
|
return $this->_classList; |
|
|
|
return new TokenList($this, 'class'); |
|
|
|
} |
|
|
|
|
|
|
|
protected function __get_innerHTML(): string { |
|
|
@ -153,7 +146,7 @@ class Element extends \DOMElement { |
|
|
|
# The getAttribute(qualifiedName) method steps are: |
|
|
|
# |
|
|
|
# 1. Let attr be the result of getting an attribute given qualifiedName and this. |
|
|
|
$attr = $this->_getAttributeNode($qualifiedName); |
|
|
|
$attr = $this->getAttributeNode($qualifiedName); |
|
|
|
# 2. If attr is null, return null. |
|
|
|
if ($attr === null) { |
|
|
|
return null; |
|
|
@ -176,32 +169,72 @@ class Element extends \DOMElement { |
|
|
|
public function getAttributeNode(string $qualifiedName): ?Attr { |
|
|
|
# The getAttributeNode(qualifiedName) method steps are to return the result of |
|
|
|
# getting an attribute given qualifiedName and this. |
|
|
|
$result = $this->_getAttributeNode($qualifiedName); |
|
|
|
// More classlist bullshit. Since we cannot extend \DOMAttr in a way that will |
|
|
|
// allow us to set the classList if a class attribute's value is modified we |
|
|
|
// will instead remove the classList and force it to be recreated when a class |
|
|
|
// attribute is requested. |
|
|
|
if ($result !== null && $result->name === 'class') { |
|
|
|
$this->_classList = null; |
|
|
|
# |
|
|
|
# To get an attribute by name given a qualifiedName and element element, run |
|
|
|
# these steps: |
|
|
|
# |
|
|
|
# 1. If element is in the HTML namespace and its node document is an HTML document, |
|
|
|
# then set qualifiedName to qualifiedName in ASCII lowercase. |
|
|
|
// Document will always be an HTML document |
|
|
|
if ($this->isHTMLNamespace()) { |
|
|
|
$qualifiedName = strtolower($qualifiedName); |
|
|
|
} |
|
|
|
|
|
|
|
return $result; |
|
|
|
# 2. Return the first attribute in element’s attribute list whose qualified name is |
|
|
|
# qualifiedName; otherwise null. |
|
|
|
// Going to try to handle this by getting the PHP DOM to do the heavy lifting |
|
|
|
// when we can because it's faster. |
|
|
|
$attr = parent::getAttributeNode($qualifiedName); |
|
|
|
if ($attr === false) { |
|
|
|
// Replace any offending characters with "UHHHHHH" where H are the uppercase |
|
|
|
// hexadecimal digits of the character's code point |
|
|
|
$qualifiedName = $this->coerceName($qualifiedName); |
|
|
|
|
|
|
|
foreach ($this->attributes as $a) { |
|
|
|
if ($a->nodeName === $qualifiedName) { |
|
|
|
return $a; |
|
|
|
} |
|
|
|
} |
|
|
|
return null; |
|
|
|
} |
|
|
|
|
|
|
|
return ($attr !== false) ? $attr : null; |
|
|
|
} |
|
|
|
|
|
|
|
public function getAttributeNodeNS(?string $namespace = null, string $localName): ?Attr { |
|
|
|
# The getAttributeNodeNS(namespace, localName) method steps are to return the |
|
|
|
# result of getting an attribute given namespace, localName, and this. |
|
|
|
$result = $this->_getAttributeNodeNS($namespace, $localName); |
|
|
|
// More classlist bullshit. Since we cannot extend \DOMAttr in a way that will |
|
|
|
// allow us to set the classList if a class attribute's value is modified we |
|
|
|
// will instead remove the classList and force it to be recreated when a class |
|
|
|
// attribute is requested. |
|
|
|
if ($result !== null && $result->name === 'class') { |
|
|
|
$this->_classList = null; |
|
|
|
ElementMap::delete($this); |
|
|
|
# |
|
|
|
# To get an attribute by namespace and local name given a namespace, localName, |
|
|
|
# and element element, run these steps: |
|
|
|
# |
|
|
|
# 1. If namespace is the empty string, then set it to null. |
|
|
|
if ($namespace === '') { |
|
|
|
$namespace = null; |
|
|
|
} |
|
|
|
|
|
|
|
return $result; |
|
|
|
# 2. Return the attribute in element’s attribute list whose namespace is namespace |
|
|
|
# and local name is localName, if any; otherwise null. |
|
|
|
// Going to try to handle this by getting the PHP DOM to do the heavy lifting |
|
|
|
// when we can because it's faster. |
|
|
|
$value = parent::getAttributeNodeNS($namespace, $localName); |
|
|
|
if (!$value) { |
|
|
|
// Replace any offending characters with "UHHHHHH" where H are the uppercase |
|
|
|
// hexadecimal digits of the character's code point |
|
|
|
$namespace = $this->coerceName($namespace ?? ''); |
|
|
|
$localName = $this->coerceName($localName); |
|
|
|
|
|
|
|
// The PHP DOM does not acknowledge the presence of XMLNS-namespace attributes |
|
|
|
// sometimes, too... so this will get those as well in those circumstances. |
|
|
|
foreach ($this->attributes as $a) { |
|
|
|
if ($a->namespaceURI === $namespace && $a->localName === $localName) { |
|
|
|
return $a; |
|
|
|
} |
|
|
|
} |
|
|
|
return null; |
|
|
|
} |
|
|
|
|
|
|
|
return ($value !== false) ? $value : null; |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
@ -210,7 +243,7 @@ class Element extends \DOMElement { |
|
|
|
# |
|
|
|
# 1. Let attr be the result of getting an attribute given namespace, localName, |
|
|
|
# and this. |
|
|
|
$attr = $this->_getAttributeNodeNS($namespace, $localName); |
|
|
|
$attr = $this->getAttributeNodeNS($namespace, $localName); |
|
|
|
|
|
|
|
# 2. If attr is null, return null. |
|
|
|
if ($attr === null) { |
|
|
@ -242,7 +275,7 @@ class Element extends \DOMElement { |
|
|
|
// The PHP DOM does not acknowledge the presence of XMLNS-namespace attributes, |
|
|
|
// so try it again just in case; getAttributeNode will coerce names if |
|
|
|
// necessary, too. |
|
|
|
$value = ($this->_getAttributeNode($qualifiedName) !== null); |
|
|
|
$value = ($this->getAttributeNode($qualifiedName) !== null); |
|
|
|
} |
|
|
|
|
|
|
|
return $value; |
|
|
@ -266,7 +299,7 @@ class Element extends \DOMElement { |
|
|
|
// The PHP DOM does not acknowledge the presence of XMLNS-namespace attributes, |
|
|
|
// so try it again just in case; getAttributeNode will coerce names if |
|
|
|
// necessary, too. |
|
|
|
$value = ($this->_getAttributeNodeNS($namespace, $localName) !== null); |
|
|
|
$value = ($this->getAttributeNodeNS($namespace, $localName) !== null); |
|
|
|
} |
|
|
|
|
|
|
|
return $value; |
|
|
@ -280,7 +313,7 @@ class Element extends \DOMElement { |
|
|
|
## these steps: |
|
|
|
## |
|
|
|
## 1. Let attr be the result of getting an attribute given qualifiedName and element. |
|
|
|
$attr = $this->_getAttributeNode($qualifiedName); |
|
|
|
$attr = $this->getAttributeNode($qualifiedName); |
|
|
|
## 2. If attr is non-null, then remove attr. |
|
|
|
if ($attr !== null) { |
|
|
|
// Going to try to handle this by getting the PHP DOM to do the heavy lifting |
|
|
@ -288,8 +321,7 @@ class Element extends \DOMElement { |
|
|
|
parent::removeAttributeNode($attr); |
|
|
|
|
|
|
|
// ClassList stuff because php garbage collection is... garbage. |
|
|
|
if ($qualifiedName === 'class' && $this->_classList !== null) { |
|
|
|
$this->_classList = null; |
|
|
|
if ($qualifiedName === 'class') { |
|
|
|
ElementMap::delete($this); |
|
|
|
} |
|
|
|
} |
|
|
@ -304,12 +336,17 @@ class Element extends \DOMElement { |
|
|
|
## To remove an attribute by namespace and local name given a namespace, localName, and element element, run these steps: |
|
|
|
## |
|
|
|
## 1. Let attr be the result of getting an attribute given namespace, localName, and element. |
|
|
|
$attr = $this->_getAttributeNodeNS($namespace, $localName); |
|
|
|
$attr = $this->getAttributeNodeNS($namespace, $localName); |
|
|
|
## 2. If attr is non-null, then remove attr. |
|
|
|
if ($attr !== null) { |
|
|
|
// Going to try to handle this by getting the PHP DOM to do the heavy lifting |
|
|
|
// when we can because it's faster. |
|
|
|
parent::removeAttributeNode($attr); |
|
|
|
|
|
|
|
// ClassList stuff because php garbage collection is... garbage. |
|
|
|
if ($qualifiedName === 'class') { |
|
|
|
ElementMap::delete($this); |
|
|
|
} |
|
|
|
} |
|
|
|
## 3. Return attr. |
|
|
|
// Supposed to return undefined in the end, so let's skip this. |
|
|
@ -336,18 +373,11 @@ class Element extends \DOMElement { |
|
|
|
# attribute to this, and then return. |
|
|
|
# 5. Change attribute to value. |
|
|
|
// Going to try to handle this by getting the PHP DOM to do the heavy lifting |
|
|
|
// when we can because it's faster. But, first, we must work around PHP's |
|
|
|
// garbage garbage collection. |
|
|
|
if ($qualifiedName === 'class' && $this->_classList !== null) { |
|
|
|
if ($value !== '') { |
|
|
|
$this->_classList->value = $value; |
|
|
|
return; |
|
|
|
} else { |
|
|
|
$this->_classList = null; |
|
|
|
ElementMap::delete($this); |
|
|
|
} |
|
|
|
// when we can because it's faster. |
|
|
|
// ClassList stuff because php garbage collection is... garbage. |
|
|
|
if ($qualifiedName === 'class' && $value === '') { |
|
|
|
ElementMap::delete($this); |
|
|
|
} |
|
|
|
|
|
|
|
try { |
|
|
|
parent::setAttributeNS(null, $qualifiedName, $value); |
|
|
|
} catch (\DOMException $e) { |
|
|
@ -362,6 +392,16 @@ class Element extends \DOMElement { |
|
|
|
if ($qualifiedName === 'id' && $namespaceURI === null) { |
|
|
|
$this->setIdAttribute($qualifiedName, true); |
|
|
|
} |
|
|
|
|
|
|
|
// ClassList stuff because php garbage collection is... garbage. |
|
|
|
if ($qualifiedName === 'class') { |
|
|
|
ElementMap::delete($this); |
|
|
|
} |
|
|
|
// If you create an id attribute this way it won't be used by PHP in |
|
|
|
// getElementById, so let's fix that. |
|
|
|
elseif ($qualifiedName === 'id') { |
|
|
|
$this->setIdAttribute($qualifiedName, true); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
public function setAttributeNS(?string $namespace, string $qualifiedName, string $value): void { |
|
|
@ -373,18 +413,7 @@ class Element extends \DOMElement { |
|
|
|
# 2. Set an attribute value for this using localName, value, and also prefix and |
|
|
|
# namespace. |
|
|
|
// Going to try to handle this by getting the PHP DOM to do the heavy lifting |
|
|
|
// when we can because it's faster. But, first, we must work around a couple of |
|
|
|
// PHP bugs and its garbage garbage collection. |
|
|
|
if ($qualifiedName === 'class' && $this->_classList !== null) { |
|
|
|
if ($value !== '') { |
|
|
|
$this->_classList->value = $value; |
|
|
|
return; |
|
|
|
} else { |
|
|
|
$this->_classList = null; |
|
|
|
ElementMap::delete($this); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// when we can because it's faster. |
|
|
|
if ($namespace === Parser::XMLNS_NAMESPACE) { |
|
|
|
// NOTE: We create attribute nodes so that xmlns attributes |
|
|
|
// don't get lost; otherwise they cannot be serialized |
|
|
@ -414,74 +443,16 @@ class Element extends \DOMElement { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
if ($qualifiedName === 'id' && $namespaceURI === null) { |
|
|
|
$this->setIdAttribute($qualifiedName, true); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
protected function _getAttributeNode(string $qualifiedName): ?Attr { |
|
|
|
# To get an attribute by name given a qualifiedName and element element, run |
|
|
|
# these steps: |
|
|
|
# |
|
|
|
# 1. If element is in the HTML namespace and its node document is an HTML document, |
|
|
|
# then set qualifiedName to qualifiedName in ASCII lowercase. |
|
|
|
// Document will always be an HTML document |
|
|
|
if ($this->isHTMLNamespace()) { |
|
|
|
$qualifiedName = strtolower($qualifiedName); |
|
|
|
} |
|
|
|
|
|
|
|
# 2. Return the first attribute in element’s attribute list whose qualified name is |
|
|
|
# qualifiedName; otherwise null. |
|
|
|
// Going to try to handle this by getting the PHP DOM to do the heavy lifting |
|
|
|
// when we can because it's faster. |
|
|
|
$attr = parent::getAttributeNode($qualifiedName); |
|
|
|
if ($attr === false) { |
|
|
|
// Replace any offending characters with "UHHHHHH" where H are the uppercase |
|
|
|
// hexadecimal digits of the character's code point |
|
|
|
$qualifiedName = $this->coerceName($qualifiedName); |
|
|
|
|
|
|
|
foreach ($this->attributes as $a) { |
|
|
|
if ($a->nodeName === $qualifiedName) { |
|
|
|
return $a; |
|
|
|
} |
|
|
|
if ($namespace === null) { |
|
|
|
// ClassList stuff because php garbage collection is... garbage. |
|
|
|
if ($qualifiedName === 'class') { |
|
|
|
ElementMap::delete($this); |
|
|
|
} |
|
|
|
return null; |
|
|
|
} |
|
|
|
|
|
|
|
return ($attr !== false) ? $attr : null; |
|
|
|
} |
|
|
|
|
|
|
|
protected function _getAttributeNodeNS(?string $namespace = null, string $localName): ?Attr { |
|
|
|
# To get an attribute by namespace and local name given a namespace, localName, |
|
|
|
# and element element, run these steps: |
|
|
|
# |
|
|
|
# 1. If namespace is the empty string, then set it to null. |
|
|
|
if ($namespace === '') { |
|
|
|
$namespace = null; |
|
|
|
} |
|
|
|
|
|
|
|
# 2. Return the attribute in element’s attribute list whose namespace is namespace |
|
|
|
# and local name is localName, if any; otherwise null. |
|
|
|
// Going to try to handle this by getting the PHP DOM to do the heavy lifting |
|
|
|
// when we can because it's faster. |
|
|
|
$value = parent::getAttributeNodeNS($namespace, $localName); |
|
|
|
if (!$value) { |
|
|
|
// Replace any offending characters with "UHHHHHH" where H are the uppercase |
|
|
|
// hexadecimal digits of the character's code point |
|
|
|
$namespace = $this->coerceName($namespace ?? ''); |
|
|
|
$localName = $this->coerceName($localName); |
|
|
|
|
|
|
|
// The PHP DOM does not acknowledge the presence of XMLNS-namespace attributes |
|
|
|
// sometimes, too... so this will get those as well in those circumstances. |
|
|
|
foreach ($this->attributes as $a) { |
|
|
|
if ($a->namespaceURI === $namespace && $a->localName === $localName) { |
|
|
|
return $a; |
|
|
|
} |
|
|
|
// If you create an id attribute this way it won't be used by PHP in |
|
|
|
// getElementById, so let's fix that. |
|
|
|
elseif ($qualifiedName === 'id') { |
|
|
|
$this->setIdAttribute($qualifiedName, true); |
|
|
|
} |
|
|
|
return null; |
|
|
|
} |
|
|
|
|
|
|
|
return ($value !== false) ? $value : null; |
|
|
|
} |
|
|
|
} |
|
|
|