diff --git a/composer.lock b/composer.lock index b04f0a2..814ca6a 100644 --- a/composer.lock +++ b/composer.lock @@ -63,7 +63,7 @@ "source": { "type": "git", "url": "mensbeam-gitea:MensBeam/HTML-Parser.git", - "reference": "5495e7c81e36409bebb998962f8624b5744a1b30" + "reference": "afaf10d602ee04d7eba437d3a08ef10ebef4f5b0" }, "require": { "ext-dom": "*", @@ -130,7 +130,7 @@ "parsing", "whatwg" ], - "time": "2021-11-18T17:39:20+00:00" + "time": "2021-11-22T01:55:09+00:00" }, { "name": "mensbeam/intl", diff --git a/lib/Attr.php b/lib/Attr.php index 449237d..2a14cfe 100644 --- a/lib/Attr.php +++ b/lib/Attr.php @@ -25,14 +25,18 @@ class Attr extends Node { } protected function __get_namespaceURI(): ?string { - $namespace = $this->innerNode->namespaceURI; - return (!$this->ownerDocument instanceof XMLDocument && $namespace === null) ? Node::HTML_NAMESPACE : $namespace; + return $this->innerNode->namespaceURI; } - protected function __get_ownerElement(): Element { + protected function __get_ownerElement(): ?Element { // PHP's DOM does this correctly already. - $wrapperNode = $this->innerNode->ownerDocument->getWrapperNode($this->innerNode->ownerElement); - return $wrapperNode; + $innerOwnerElement = $this->innerNode->ownerElement; + + if ($innerOwnerElement === null) { + return null; + } + + return $this->innerNode->ownerDocument->getWrapperNode($this->innerNode->ownerElement); } protected function __get_prefix(): string { diff --git a/lib/DOMException.php b/lib/DOMException.php index 08e61c3..b0211ed 100644 --- a/lib/DOMException.php +++ b/lib/DOMException.php @@ -18,6 +18,7 @@ class DOMException extends Exception { public const NO_MODIFICATION_ALLOWED = 7; public const NOT_FOUND = 8; public const NOT_SUPPORTED = 9; + public const IN_USE_ATTRIBUTE = 10; public const SYNTAX_ERROR = 12; public const INVALID_MODIFICATION = 13; public const NAMESPACE_ERROR = 14; @@ -35,6 +36,7 @@ class DOMException extends Exception { 7 => 'Modification not allowed here', 8 => 'Not found error', 9 => 'Feature is not supported', + 10 => 'The attribute is in use', 12 => 'Syntax error', 13 => 'Invalid modification error', 14 => 'Namespace error', diff --git a/lib/Element.php b/lib/Element.php index b936a2d..75272ee 100644 --- a/lib/Element.php +++ b/lib/Element.php @@ -22,16 +22,28 @@ class Element extends Node { return Reflection::createFromProtectedConstructor(__NAMESPACE__ . '\\NamedNodeMap', $this, ($this instanceof Document) ? $this->innerNode : $this->innerNode->ownerDocument, $this->innerNode->attributes); } + protected function __get_className(): string { + # The className attribute must reflect the "class" content attribute. + # Return the result of running get an attribute value given this and name. + return $this->getAttribute('class') ?? ''; + } + + protected function __set_className(string $value): void { + # The className attribute must reflect the "class" content attribute. + # Set an attribute value for this using name and the given value. + $this->setAttribute('class', $value); + } + protected function __get_id(): string { # The id attribute must reflect the "id" content attribute. # Return the result of running get an attribute value given this and name. return $this->getAttribute('id') ?? ''; } - protected function __set_id(string $value): string { + protected function __set_id(string $value): void { # The id attribute must reflect the "id" content attribute. # Set an attribute value for this using name and the given value. - return $this->setAttribute('id', $value); + $this->setAttribute('id', $value); } protected function __get_localName(): ?string { @@ -39,14 +51,13 @@ class Element extends Node { return $this->innerNode->localName; } - protected function __get_namespaceURI(): string { + protected function __get_namespaceURI(): ?string { // PHP's DOM uses null incorrectly for the HTML namespace, and if you attempt to // use the HTML namespace anyway it has additional bugs we don't have to work // around because of the wrapper classes; So, use the null namespace internally // but print out the HTML namespace instead. $namespace = $this->innerNode->namespaceURI; - $doc = $this->ownerDocument; - return (!$doc instanceof XMLDocument && $namespace === null) ? self::HTML_NAMESPACE : $namespace; + return (!$this->ownerDocument instanceof XMLDocument && $namespace === null) ? self::HTML_NAMESPACE : $namespace; } protected function __get_prefix(): ?string { @@ -94,42 +105,6 @@ class Element extends Node { return $list; } - public function getAttributeNodeNS(?string $namespace, string $localName): ?Attr { - # The getAttributeNodeNS(namespace, localName) method steps are to return the - # result of getting an attribute given namespace, localName, and 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; - } - - # 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 = $this->innerNode->getAttributeNodeNS($namespace, $localName); - if (!$value) { - // Replace any offending characters with "UHHHHHH" where H are the uppercase - // hexadecimal digits of the character's code point - $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. - $attributes = $this->innerNode->attributes; - foreach ($attributes as $a) { - if ($a->namespaceURI === $namespace && $a->localName === $localName) { - return $this->innerNode->ownerDocument->getWrapperNode($a); - } - } - return null; - } - - return ($value !== false) ? $this->innerNode->ownerDocument->getWrapperNode($value) : null; - } - public function getAttributeNode(string $qualifiedName): ?Attr { # The getAttributeNode(qualifiedName) method steps are to return the result of # getting an attribute given qualifiedName and this. @@ -139,31 +114,56 @@ class Element extends Node { # # 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 instanceof XMLDocument && $this->namespaceURI === self::HTML_NAMESPACE) { $qualifiedName = strtolower($qualifiedName); } + $qualifiedName = $this->coerceName($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 = $this->innerNode->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); + // Manually going through the attributes because PHP DOM has issues, returning + // odd "DOMNamespaceNode" objects sometimes instead of the actual attributes... + // yeah... + $attributes = $this->innerNode->attributes; + foreach ($attributes as $attr) { + if ($attr->nodeName === $qualifiedName) { + return $this->innerNode->ownerDocument->getWrapperNode($attr); + } + } - $attributes = $this->innerNode->attributes; - foreach ($attributes as $a) { - if ($a->nodeName === $qualifiedName) { - return $this->innerNode->ownerDocument->getWrapperNode($a); - } + return null; + } + + public function getAttributeNodeNS(?string $namespace, string $localName): ?Attr { + static $count = 0; + $count++; + # The getAttributeNodeNS(namespace, localName) method steps are to return the + # result of getting an attribute given namespace, localName, and 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; + } + + $localName = $this->coerceName($localName); + + # 2. Return the attribute in element’s attribute list whose namespace is namespace + # and local name is localName, if any; otherwise null. + // Manually going through the attributes because PHP DOM has issues, returning + // odd "DOMNamespaceNode" objects sometimes instead of the actual attributes... + // yeah... + $attributes = $this->innerNode->attributes; + foreach ($attributes as $attr) { + if ($attr->namespaceURI === $namespace && $attr->localName === $localName) { + return $this->innerNode->ownerDocument->getWrapperNode($attr); } - return null; } - return ($attr !== false) ? $this->innerNode->ownerDocument->getWrapperNode($attr) : null; + return null; } public function getAttributeNS(?string $namespace, string $localName): ?string { @@ -183,13 +183,6 @@ class Element extends Node { return $attr->value; } - public function hasAttributes(): bool { - # The hasAttributes() method steps are to return false if this’s attribute list - # is empty; otherwise true. - // PHP's DOM does this correctly already. - return $this->innerNode->hasAttributes(); - } - public function hasAttribute(string $qualifiedName): bool { # The hasAttribute(qualifiedName) method steps are: # @@ -215,16 +208,47 @@ class Element extends Node { return $value; } + public function hasAttributeNS(?string $namespace, string $localName): bool { + # The hasAttributeNS(namespace, localName) method steps are: + # + # 1. If namespace is the empty string, then set it to null. + if ($namespace === '') { + $namespace = null; + } + + # 2. Return true if this has an attribute whose namespace is namespace and local + # name is localName; otherwise false. + # An element has an attribute A if its attribute list contains A. + // Going to try to handle this by getting the PHP DOM to do the heavy lifting + // when we can because it's faster. + $value = $this->innerNode->hasAttributeNS($namespace, $localName); + if (!$value) { + // 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); + } + + return $value; + } + + public function hasAttributes(): bool { + # The hasAttributes() method steps are to return false if this’s attribute list + # is empty; otherwise true. + // PHP's DOM does this correctly already. + return $this->innerNode->hasAttributes(); + } + public function setAttribute(string $qualifiedName, string $value): void { # 1. If qualifiedName does not match the Name production in XML, then throw an # "InvalidCharacterError" DOMException. - if (preg_match(InnerDocument::NAME_PRODUCTION_REGEX, $qualifiedName) !== 1) { + if (!preg_match(InnerDocument::NAME_PRODUCTION_REGEX, $qualifiedName)) { throw new DOMException(DOMException::INVALID_CHARACTER); } # 2. If this is in the HTML namespace and its node document is an HTML document, # then set qualifiedName to qualifiedName in ASCII lowercase. - if (!$this instanceof XMLDocument) { + if (!$this->ownerDocument instanceof XMLDocument && $this->namespaceURI === Node::HTML_NAMESPACE) { $qualifiedName = strtolower($qualifiedName); } @@ -253,20 +277,41 @@ class Element extends Node { } public function setAttributeNode(Attr $attr): ?Attr { - $innerNode = $this->innerNode; - $innerAttr = $this->getInnerNode($attr); - # The setAttributeNode(attr) and setAttributeNodeNS(attr) methods steps are to # return the result of setting an attribute given attr and this. - $this->innerNode->setAttributeNode($innerAttr); + # + # To set an attribute given an attr and element, run these steps: + # + # 1. If attr’s element is neither null nor element, throw an + # "InUseAttributeError" DOMException. + $ownerElement = $attr->ownerElement; + if ($ownerElement !== null && $ownerElement !== $this) { + throw new DOMException(DOMException::IN_USE_ATTRIBUTE); + } - // If you create an id attribute it won't be used by PHP in getElementById, so - // let's fix that. - if ($innerAttr->namespaceURI === null && $innerAttr->localName === 'id') { - $innerNode->setIdAttributeNode($attr, true); + // PHP's DOM doesn't do this method correctly. It returns the old node if it is + // replaced and null otherwise. It's always supposed to return a node. + + $oldAttr = $this->getAttributeNodeNS($attr->namespaceURI, $attr->localName); + + # 3. If oldAttr is attr, return attr. + if ($oldAttr === $attr) { + return $attr; } - return $attr; + # 4. If oldAttr is non-null, then replace oldAttr with attr. + if ($oldAttr !== null) { + $this->innerNode->setAttributeNode($this->getInnerNode($attr)); + return $attr; + } + # 5. Otherwise, append attr to element. + else { + $this->innerNode->appendChild($this->getInnerNode($attr)); + return $attr; + } + + # 6. Return oldAttr. + return $oldAttr; } public function setAttributeNodeNS(Attr $attr): ?Attr { @@ -277,10 +322,25 @@ class Element extends Node { # 1. Let namespace, prefix, and localName be the result of passing namespace and # qualifiedName to validate and extract. [ 'namespace' => $namespace, 'prefix' => $prefix, 'localName' => $localName ] = $this->validateAndExtract($qualifiedName, $namespace); + + $prefix = ($prefix === null) ? null : $this->coerceName($prefix); + $localName = $this->coerceName($localName); $qualifiedName = ($prefix === null || $prefix === '') ? $localName : "{$prefix}:{$localName}"; # 2. Set an attribute value for this using localName, value, and also prefix and # namespace. + // NOTE: We create attribute nodes so that xmlns attributes don't get lost; + // otherwise they cannot be serialized + if ($namespace === self::XMLNS_NAMESPACE) { + $attr = $this->ownerDocument->createAttributeNS($namespace, $qualifiedName); + $attr->value = $this->escapeString($value, true); + $this->setAttributeNodeNS($attr); + } else { + $this->innerNode->setAttributeNS($namespace, $qualifiedName, $value); + } + + /*# 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. // NOTE: We create attribute nodes so that xmlns attributes don't get lost; @@ -310,14 +370,12 @@ class Element extends Node { $this->innerNode->setAttributeNS($namespace, $qualifiedName, $value); } - } + }*/ - if ($namespace === null) { - // If you create an id attribute this way it won't be used by PHP in - // getElementById, so let's fix that. - if ($qualifiedName === 'id') { - $this->innerNode->setIdAttribute($qualifiedName, true); - } + // If you create an id attribute this way it won't be used by PHP in + // getElementById, so let's fix that. + if ($namespace === null && $qualifiedName === 'id') { + $this->innerNode->setIdAttribute($qualifiedName, true); } } } \ No newline at end of file diff --git a/tests/cases/TestElement.php b/tests/cases/TestElement.php index b016958..9ed00e5 100644 --- a/tests/cases/TestElement.php +++ b/tests/cases/TestElement.php @@ -121,7 +121,7 @@ class TestElement extends \PHPUnit\Framework\TestCase { * @covers \MensBeam\HTML\DOM\Inner\Reflection::getProtectedProperty */ public function testMethod_getAttributeNS() { - $d = new Document(''); + $d = new Document('', 'UTF-8'); $svg = $d->getElementsByTagNameNS(Node::SVG_NAMESPACE, 'svg')[0]; // Parser per the spec doesn't parse xmlns prefixed attributes except xlink, so let's add one manually instead to test coercion. $svg->setAttributeNS(Node::XMLNS_NAMESPACE, 'xmlns:poop💩', 'https://poop💩.poop'); @@ -137,4 +137,419 @@ class TestElement extends \PHPUnit\Framework\TestCase { // empty string namespace $this->assertSame('0 0 42 42', $svg->getAttributeNS('', 'viewBox')); } + + + /** + * @covers \MensBeam\HTML\DOM\Element::hasAttribute + * + * @covers \MensBeam\HTML\DOM\Collection::__construct + * @covers \MensBeam\HTML\DOM\Collection::item + * @covers \MensBeam\HTML\DOM\Document::__construct + * @covers \MensBeam\HTML\DOM\Document::__get_documentElement + * @covers \MensBeam\HTML\DOM\Document::load + * @covers \MensBeam\HTML\DOM\DocumentOrElement::getElementsByTagNameNS + * @covers \MensBeam\HTML\DOM\DOMImplementation::__construct + * @covers \MensBeam\HTML\DOM\Element::__construct + * @covers \MensBeam\HTML\DOM\Element::__get_namespaceURI + * @covers \MensBeam\HTML\DOM\Element::getAttributeNode + * @covers \MensBeam\HTML\DOM\HTMLCollection::item + * @covers \MensBeam\HTML\DOM\HTMLCollection::offsetGet + * @covers \MensBeam\HTML\DOM\Node::__construct + * @covers \MensBeam\HTML\DOM\Node::__get_ownerDocument + * @covers \MensBeam\HTML\DOM\Node::getInnerDocument + * @covers \MensBeam\HTML\DOM\Node::hasChildNodes + * @covers \MensBeam\HTML\DOM\Inner\Document::__construct + * @covers \MensBeam\HTML\DOM\Inner\Document::__get_wrapperNode + * @covers \MensBeam\HTML\DOM\Inner\Document::getWrapperNode + * @covers \MensBeam\HTML\DOM\Inner\NodeCache::get + * @covers \MensBeam\HTML\DOM\Inner\NodeCache::has + * @covers \MensBeam\HTML\DOM\Inner\NodeCache::key + * @covers \MensBeam\HTML\DOM\Inner\NodeCache::set + * @covers \MensBeam\HTML\DOM\Inner\Reflection::createFromProtectedConstructor + */ + public function testMethod_hasAttribute() { + $d = new Document('', 'UTF-8'); + $documentElement = $d->documentElement; + $svg = $d->getElementsByTagNameNS(Node::SVG_NAMESPACE, 'svg')[0]; + + $this->assertTrue($documentElement->hasAttribute('id')); + $this->assertTrue($documentElement->hasAttribute('poop💩')); + } + + + /** + * @covers \MensBeam\HTML\DOM\Element::hasAttributeNS + * + * @covers \MensBeam\HTML\DOM\Attr::__set_value + * @covers \MensBeam\HTML\DOM\Collection::__construct + * @covers \MensBeam\HTML\DOM\Collection::item + * @covers \MensBeam\HTML\DOM\Document::__construct + * @covers \MensBeam\HTML\DOM\Document::__get_documentElement + * @covers \MensBeam\HTML\DOM\Document::createAttributeNS + * @covers \MensBeam\HTML\DOM\Document::load + * @covers \MensBeam\HTML\DOM\DocumentOrElement::getElementsByTagNameNS + * @covers \MensBeam\HTML\DOM\DocumentOrElement::validateAndExtract + * @covers \MensBeam\HTML\DOM\DOMImplementation::__construct + * @covers \MensBeam\HTML\DOM\Element::__construct + * @covers \MensBeam\HTML\DOM\Element::getAttributeNodeNS + * @covers \MensBeam\HTML\DOM\Element::setAttributeNode + * @covers \MensBeam\HTML\DOM\Element::setAttributeNodeNS + * @covers \MensBeam\HTML\DOM\Element::setAttributeNS + * @covers \MensBeam\HTML\DOM\HTMLCollection::item + * @covers \MensBeam\HTML\DOM\HTMLCollection::offsetGet + * @covers \MensBeam\HTML\DOM\Node::__construct + * @covers \MensBeam\HTML\DOM\Node::__get_ownerDocument + * @covers \MensBeam\HTML\DOM\Node::getInnerDocument + * @covers \MensBeam\HTML\DOM\Node::getInnerNode + * @covers \MensBeam\HTML\DOM\Node::hasChildNodes + * @covers \MensBeam\HTML\DOM\Inner\Document::__construct + * @covers \MensBeam\HTML\DOM\Inner\Document::__get_wrapperNode + * @covers \MensBeam\HTML\DOM\Inner\Document::getWrapperNode + * @covers \MensBeam\HTML\DOM\Inner\NodeCache::get + * @covers \MensBeam\HTML\DOM\Inner\NodeCache::has + * @covers \MensBeam\HTML\DOM\Inner\NodeCache::key + * @covers \MensBeam\HTML\DOM\Inner\NodeCache::set + * @covers \MensBeam\HTML\DOM\Inner\Reflection::createFromProtectedConstructor + * @covers \MensBeam\HTML\DOM\Inner\Reflection::getProtectedProperty + */ + public function testMethod_hasAttributeNS() { + $d = new Document('', 'UTF-8'); + $svg = $d->getElementsByTagNameNS(Node::SVG_NAMESPACE, 'svg')[0]; + // Parser per the spec doesn't parse xmlns prefixed attributes except xlink, so let's add one manually instead to test coercion. + $svg->setAttributeNS(Node::XMLNS_NAMESPACE, 'xmlns:poop💩', 'https://poop💩.poop'); + + // xmlns attribute + $this->assertTrue($svg->hasAttributeNS(Node::XMLNS_NAMESPACE, 'xmlns')); + // xmlns xlink attribute + $this->assertTrue($svg->hasAttributeNS(Node::XMLNS_NAMESPACE, 'xlink')); + // coerced namespaced attribute + $this->assertTrue($svg->hasAttributeNS(Node::XMLNS_NAMESPACE, 'poop💩')); + // nonexistent namespaced attribute + $this->assertFalse($svg->hasAttributeNS(Node::XMLNS_NAMESPACE, 'ook')); + // empty string namespace + $this->assertTrue($svg->hasAttributeNS('', 'viewBox')); + } + + + /** + * @covers \MensBeam\HTML\DOM\Element::hasAttributes + * + * @covers \MensBeam\HTML\DOM\Collection::__construct + * @covers \MensBeam\HTML\DOM\Collection::item + * @covers \MensBeam\HTML\DOM\Document::__construct + * @covers \MensBeam\HTML\DOM\Document::load + * @covers \MensBeam\HTML\DOM\DocumentOrElement::getElementsByTagNameNS + * @covers \MensBeam\HTML\DOM\DOMImplementation::__construct + * @covers \MensBeam\HTML\DOM\Element::__construct + * @covers \MensBeam\HTML\DOM\HTMLCollection::item + * @covers \MensBeam\HTML\DOM\HTMLCollection::offsetGet + * @covers \MensBeam\HTML\DOM\Node::__construct + * @covers \MensBeam\HTML\DOM\Node::getInnerDocument + * @covers \MensBeam\HTML\DOM\Node::hasChildNodes + * @covers \MensBeam\HTML\DOM\Inner\Document::__construct + * @covers \MensBeam\HTML\DOM\Inner\Document::getWrapperNode + * @covers \MensBeam\HTML\DOM\Inner\NodeCache::get + * @covers \MensBeam\HTML\DOM\Inner\NodeCache::has + * @covers \MensBeam\HTML\DOM\Inner\NodeCache::key + * @covers \MensBeam\HTML\DOM\Inner\NodeCache::set + * @covers \MensBeam\HTML\DOM\Inner\Reflection::createFromProtectedConstructor + */ + public function testMethod_hasAttributes() { + $d = new Document('', 'UTF-8'); + $svg = $d->getElementsByTagNameNS(Node::SVG_NAMESPACE, 'svg')[0]; + $this->assertTrue($svg->hasAttributes()); + } + + + /** + * @covers \MensBeam\HTML\DOM\Element::setAttribute + * + * @covers \MensBeam\HTML\DOM\Document::__construct + * @covers \MensBeam\HTML\DOM\Document::__get_documentElement + * @covers \MensBeam\HTML\DOM\Document::createElement + * @covers \MensBeam\HTML\DOM\DOMImplementation::__construct + * @covers \MensBeam\HTML\DOM\Element::__construct + * @covers \MensBeam\HTML\DOM\Element::__get_namespaceURI + * @covers \MensBeam\HTML\DOM\Element::getAttributeNode + * @covers \MensBeam\HTML\DOM\Element::hasAttribute + * @covers \MensBeam\HTML\DOM\Node::__construct + * @covers \MensBeam\HTML\DOM\Node::__get_ownerDocument + * @covers \MensBeam\HTML\DOM\Node::appendChild + * @covers \MensBeam\HTML\DOM\Node::getInnerDocument + * @covers \MensBeam\HTML\DOM\Node::getInnerNode + * @covers \MensBeam\HTML\DOM\Node::getRootNode + * @covers \MensBeam\HTML\DOM\Node::postInsertionBugFixes + * @covers \MensBeam\HTML\DOM\Node::preInsertionBugFixes + * @covers \MensBeam\HTML\DOM\Node::preInsertionValidity + * @covers \MensBeam\HTML\DOM\Inner\Document::__construct + * @covers \MensBeam\HTML\DOM\Inner\Document::__get_wrapperNode + * @covers \MensBeam\HTML\DOM\Inner\Document::getWrapperNode + * @covers \MensBeam\HTML\DOM\Inner\NodeCache::get + * @covers \MensBeam\HTML\DOM\Inner\NodeCache::has + * @covers \MensBeam\HTML\DOM\Inner\NodeCache::key + * @covers \MensBeam\HTML\DOM\Inner\NodeCache::set + * @covers \MensBeam\HTML\DOM\Inner\Reflection::createFromProtectedConstructor + * @covers \MensBeam\HTML\DOM\Inner\Reflection::getProtectedProperty + */ + public function testMethod_setAttribute() { + $d = new Document(); + $d->appendChild($d->createElement('html')); + $documentElement = $d->documentElement; + // Just need to test coerced attributes; everything else has been covered + // elsewhere + $documentElement->setAttribute('poop💩', 'jeff'); + $this->assertTrue($documentElement->hasAttribute('poop💩')); + } + + + /** + * @covers \MensBeam\HTML\DOM\Element::setAttribute + * @covers \MensBeam\HTML\DOM\Document::__construct + * @covers \MensBeam\HTML\DOM\Document::__get_documentElement + * @covers \MensBeam\HTML\DOM\Document::createElement + * @covers \MensBeam\HTML\DOM\DOMException::__construct + * @covers \MensBeam\HTML\DOM\DOMImplementation::__construct + * @covers \MensBeam\HTML\DOM\Element::__construct + * @covers \MensBeam\HTML\DOM\Node::__construct + * @covers \MensBeam\HTML\DOM\Node::appendChild + * @covers \MensBeam\HTML\DOM\Node::getInnerDocument + * @covers \MensBeam\HTML\DOM\Node::getInnerNode + * @covers \MensBeam\HTML\DOM\Node::getRootNode + * @covers \MensBeam\HTML\DOM\Node::postInsertionBugFixes + * @covers \MensBeam\HTML\DOM\Node::preInsertionBugFixes + * @covers \MensBeam\HTML\DOM\Node::preInsertionValidity + * @covers \MensBeam\HTML\DOM\Inner\Document::__construct + * @covers \MensBeam\HTML\DOM\Inner\Document::getWrapperNode + * @covers \MensBeam\HTML\DOM\Inner\NodeCache::get + * @covers \MensBeam\HTML\DOM\Inner\NodeCache::has + * @covers \MensBeam\HTML\DOM\Inner\NodeCache::key + * @covers \MensBeam\HTML\DOM\Inner\NodeCache::set + * @covers \MensBeam\HTML\DOM\Inner\Reflection::createFromProtectedConstructor + * @covers \MensBeam\HTML\DOM\Inner\Reflection::getProtectedProperty + */ + public function testMethod_setAttribute_errors() { + $this->expectException(DOMException::class); + $this->expectExceptionCode(DOMException::INVALID_CHARACTER); + $d = new Document(); + $d->appendChild($d->createElement('html')); + $d->documentElement->setAttribute('this will fail', 'fail'); + } + + + /** + * @covers \MensBeam\HTML\DOM\Element::setAttributeNode + * @covers \MensBeam\HTML\DOM\Attr::__get_localName + * @covers \MensBeam\HTML\DOM\Attr::__get_ownerElement + * @covers \MensBeam\HTML\DOM\Attr::__get_value + * @covers \MensBeam\HTML\DOM\Attr::__set_value + * @covers \MensBeam\HTML\DOM\Document::__construct + * @covers \MensBeam\HTML\DOM\Document::__get_documentElement + * @covers \MensBeam\HTML\DOM\Document::createAttribute + * @covers \MensBeam\HTML\DOM\Document::createElement + * @covers \MensBeam\HTML\DOM\DOMImplementation::__construct + * @covers \MensBeam\HTML\DOM\Element::__construct + * @covers \MensBeam\HTML\DOM\Element::__get_namespaceURI + * @covers \MensBeam\HTML\DOM\Element::getAttribute + * @covers \MensBeam\HTML\DOM\Element::getAttributeNode + * @covers \MensBeam\HTML\DOM\Node::__construct + * @covers \MensBeam\HTML\DOM\Node::__get_ownerDocument + * @covers \MensBeam\HTML\DOM\Node::appendChild + * @covers \MensBeam\HTML\DOM\Node::getInnerDocument + * @covers \MensBeam\HTML\DOM\Node::getInnerNode + * @covers \MensBeam\HTML\DOM\Node::getRootNode + * @covers \MensBeam\HTML\DOM\Node::postInsertionBugFixes + * @covers \MensBeam\HTML\DOM\Node::preInsertionBugFixes + * @covers \MensBeam\HTML\DOM\Node::preInsertionValidity + * @covers \MensBeam\HTML\DOM\Inner\Document::__construct + * @covers \MensBeam\HTML\DOM\Inner\Document::__get_wrapperNode + * @covers \MensBeam\HTML\DOM\Inner\Document::getWrapperNode + * @covers \MensBeam\HTML\DOM\Inner\NodeCache::get + * @covers \MensBeam\HTML\DOM\Inner\NodeCache::has + * @covers \MensBeam\HTML\DOM\Inner\NodeCache::key + * @covers \MensBeam\HTML\DOM\Inner\NodeCache::set + * @covers \MensBeam\HTML\DOM\Inner\Reflection::createFromProtectedConstructor + * @covers \MensBeam\HTML\DOM\Inner\Reflection::getProtectedProperty + */ + public function testMethod_setAttributeNode() { + $d = new Document(); + $d->appendChild($d->createElement('html')); + $documentElement = $d->documentElement; + $attr = $d->createAttribute('poop💩'); + $attr->value = 'jeff'; + $documentElement->setAttributeNode($attr); + + // Almost everything is covered. Just need to test same attributes and + // replacements. + $documentElement->setAttributeNode($attr); + $this->assertSame('jeff', $documentElement->getAttribute('poop💩')); + $attr = $d->createAttribute('poop💩'); + $attr->value = 'jeff'; + $documentElement->setAttributeNode($attr); + $this->assertSame('jeff', $documentElement->getAttribute('poop💩')); + } + + + /** + * @covers \MensBeam\HTML\DOM\Element::setAttributeNode + * + * @covers \MensBeam\HTML\DOM\Attr::__get_localName + * @covers \MensBeam\HTML\DOM\Attr::__get_ownerElement + * @covers \MensBeam\HTML\DOM\Attr::__set_value + * @covers \MensBeam\HTML\DOM\Document::__construct + * @covers \MensBeam\HTML\DOM\Document::__get_documentElement + * @covers \MensBeam\HTML\DOM\Document::createAttribute + * @covers \MensBeam\HTML\DOM\Document::createElement + * @covers \MensBeam\HTML\DOM\DOMException::__construct + * @covers \MensBeam\HTML\DOM\DOMImplementation::__construct + * @covers \MensBeam\HTML\DOM\Element::__construct + * @covers \MensBeam\HTML\DOM\Element::__get_namespaceURI + * @covers \MensBeam\HTML\DOM\Element::getAttributeNode + * @covers \MensBeam\HTML\DOM\Node::__construct + * @covers \MensBeam\HTML\DOM\Node::__get_ownerDocument + * @covers \MensBeam\HTML\DOM\Node::appendChild + * @covers \MensBeam\HTML\DOM\Node::getInnerDocument + * @covers \MensBeam\HTML\DOM\Node::getInnerNode + * @covers \MensBeam\HTML\DOM\Node::getRootNode + * @covers \MensBeam\HTML\DOM\Node::postInsertionBugFixes + * @covers \MensBeam\HTML\DOM\Node::preInsertionBugFixes + * @covers \MensBeam\HTML\DOM\Node::preInsertionValidity + * @covers \MensBeam\HTML\DOM\Inner\Document::__construct + * @covers \MensBeam\HTML\DOM\Inner\Document::__get_wrapperNode + * @covers \MensBeam\HTML\DOM\Inner\Document::getWrapperNode + * @covers \MensBeam\HTML\DOM\Inner\NodeCache::get + * @covers \MensBeam\HTML\DOM\Inner\NodeCache::has + * @covers \MensBeam\HTML\DOM\Inner\NodeCache::key + * @covers \MensBeam\HTML\DOM\Inner\NodeCache::set + * @covers \MensBeam\HTML\DOM\Inner\Reflection::createFromProtectedConstructor + * @covers \MensBeam\HTML\DOM\Inner\Reflection::getProtectedProperty + */ + public function testMethod_setAttributeNode_errors() { + $this->expectException(DOMException::class); + $this->expectExceptionCode(DOMException::IN_USE_ATTRIBUTE); + $d = new Document(); + $documentElement = $d->appendChild($d->createElement('html')); + $body = $documentElement->appendChild($d->createElement('body')); + $attr = $d->createAttribute('ook'); + $attr->value = 'eek'; + $body->setAttributeNode($attr); + $documentElement->setAttributeNode($attr); + } + + /** + * @covers \MensBeam\HTML\DOM\Element::setAttributeNS + * + * @covers \MensBeam\HTML\DOM\Attr::__get_value + * @covers \MensBeam\HTML\DOM\Document::__construct + * @covers \MensBeam\HTML\DOM\Document::__get_documentElement + * @covers \MensBeam\HTML\DOM\Document::createElement + * @covers \MensBeam\HTML\DOM\DocumentOrElement::validateAndExtract + * @covers \MensBeam\HTML\DOM\DOMImplementation::__construct + * @covers \MensBeam\HTML\DOM\Element::__construct + * @covers \MensBeam\HTML\DOM\Element::getAttributeNodeNS + * @covers \MensBeam\HTML\DOM\Element::getAttributeNS + * @covers \MensBeam\HTML\DOM\Node::__construct + * @covers \MensBeam\HTML\DOM\Node::appendChild + * @covers \MensBeam\HTML\DOM\Node::getInnerDocument + * @covers \MensBeam\HTML\DOM\Node::getInnerNode + * @covers \MensBeam\HTML\DOM\Node::getRootNode + * @covers \MensBeam\HTML\DOM\Node::postInsertionBugFixes + * @covers \MensBeam\HTML\DOM\Node::preInsertionBugFixes + * @covers \MensBeam\HTML\DOM\Node::preInsertionValidity + * @covers \MensBeam\HTML\DOM\NonElementParentNode::getElementById + * @covers \MensBeam\HTML\DOM\Inner\Document::__construct + * @covers \MensBeam\HTML\DOM\Inner\Document::getWrapperNode + * @covers \MensBeam\HTML\DOM\Inner\NodeCache::get + * @covers \MensBeam\HTML\DOM\Inner\NodeCache::has + * @covers \MensBeam\HTML\DOM\Inner\NodeCache::key + * @covers \MensBeam\HTML\DOM\Inner\NodeCache::set + * @covers \MensBeam\HTML\DOM\Inner\Reflection::createFromProtectedConstructor + * @covers \MensBeam\HTML\DOM\Inner\Reflection::getProtectedProperty + */ + public function testMethod_setAttributeNS() { + $d = new Document(); + $d->appendChild($d->createElement('html')); + $documentElement = $d->documentElement; + + // Just need to test setting of a bullshit namespaced attribute and id + // attribute; everything else is covered already. + $documentElement->setAttributeNS('https://poop💩.poop💩', 'poop💩:poop💩', 'jeff'); + $this->assertSame('jeff', $documentElement->getAttributeNS('https://poop💩.poop💩', 'poop💩')); + $documentElement->setAttributeNS(null, 'id', 'ook'); + $this->assertSame($documentElement, $d->getElementById('ook')); + } + + + /** + * @covers \MensBeam\HTML\DOM\Element::__get_attributes + * + * @covers \MensBeam\HTML\DOM\Attr::__get_value + * @covers \MensBeam\HTML\DOM\Collection::__get_length + * @covers \MensBeam\HTML\DOM\Collection::count + * @covers \MensBeam\HTML\DOM\Document::__construct + * @covers \MensBeam\HTML\DOM\Document::__get_documentElement + * @covers \MensBeam\HTML\DOM\Document::load + * @covers \MensBeam\HTML\DOM\DOMImplementation::__construct + * @covers \MensBeam\HTML\DOM\Element::__construct + * @covers \MensBeam\HTML\DOM\Element::__get_namespaceURI + * @covers \MensBeam\HTML\DOM\Element::getAttribute + * @covers \MensBeam\HTML\DOM\Element::getAttributeNode + * @covers \MensBeam\HTML\DOM\NamedNodeMap::__construct + * @covers \MensBeam\HTML\DOM\Node::__construct + * @covers \MensBeam\HTML\DOM\Node::__get_ownerDocument + * @covers \MensBeam\HTML\DOM\Node::hasChildNodes + * @covers \MensBeam\HTML\DOM\Inner\Document::__construct + * @covers \MensBeam\HTML\DOM\Inner\Document::__get_wrapperNode + * @covers \MensBeam\HTML\DOM\Inner\Document::getWrapperNode + * @covers \MensBeam\HTML\DOM\Inner\NodeCache::get + * @covers \MensBeam\HTML\DOM\Inner\NodeCache::has + * @covers \MensBeam\HTML\DOM\Inner\NodeCache::key + * @covers \MensBeam\HTML\DOM\Inner\NodeCache::set + * @covers \MensBeam\HTML\DOM\Inner\Reflection::createFromProtectedConstructor + */ + public function testProperty_attributes() { + $d = new Document('', 'UTF-8'); + $documentElement = $d->documentElement; + $this->assertEquals(6, $documentElement->attributes->length); + $this->assertSame(Node::HTML_NAMESPACE, $documentElement->getAttribute('xmlns')); + } + + + /** + * @covers \MensBeam\HTML\DOM\Element::__get_id + * @covers \MensBeam\HTML\DOM\Element::__set_id + * + * @covers \MensBeam\HTML\DOM\Attr::__get_value + * @covers \MensBeam\HTML\DOM\Document::__construct + * @covers \MensBeam\HTML\DOM\Document::createElement + * @covers \MensBeam\HTML\DOM\DOMImplementation::__construct + * @covers \MensBeam\HTML\DOM\Element::__construct + * @covers \MensBeam\HTML\DOM\Element::__get_namespaceURI + * @covers \MensBeam\HTML\DOM\Element::getAttribute + * @covers \MensBeam\HTML\DOM\Element::getAttributeNode + * @covers \MensBeam\HTML\DOM\Element::setAttribute + * @covers \MensBeam\HTML\DOM\Node::__construct + * @covers \MensBeam\HTML\DOM\Node::__get_ownerDocument + * @covers \MensBeam\HTML\DOM\Node::appendChild + * @covers \MensBeam\HTML\DOM\Node::getInnerDocument + * @covers \MensBeam\HTML\DOM\Node::getInnerNode + * @covers \MensBeam\HTML\DOM\Node::getRootNode + * @covers \MensBeam\HTML\DOM\Node::postInsertionBugFixes + * @covers \MensBeam\HTML\DOM\Node::preInsertionBugFixes + * @covers \MensBeam\HTML\DOM\Node::preInsertionValidity + * @covers \MensBeam\HTML\DOM\Inner\Document::__construct + * @covers \MensBeam\HTML\DOM\Inner\Document::__get_wrapperNode + * @covers \MensBeam\HTML\DOM\Inner\Document::getWrapperNode + * @covers \MensBeam\HTML\DOM\Inner\NodeCache::get + * @covers \MensBeam\HTML\DOM\Inner\NodeCache::has + * @covers \MensBeam\HTML\DOM\Inner\NodeCache::key + * @covers \MensBeam\HTML\DOM\Inner\NodeCache::set + * @covers \MensBeam\HTML\DOM\Inner\Reflection::createFromProtectedConstructor + * @covers \MensBeam\HTML\DOM\Inner\Reflection::getProtectedProperty + */ + public function testProperty_id() { + $d = new Document(); + $documentElement = $d->appendChild($d->createElement('html')); + $documentElement->id = 'ook'; + $this->assertSame('ook', $documentElement->id); + } } \ No newline at end of file diff --git a/vendor-bin/phpunit/composer.lock b/vendor-bin/phpunit/composer.lock index 73a609c..8b462ee 100644 --- a/vendor-bin/phpunit/composer.lock +++ b/vendor-bin/phpunit/composer.lock @@ -529,16 +529,16 @@ }, { "name": "phpunit/php-code-coverage", - "version": "9.2.8", + "version": "9.2.9", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "cf04e88a2e3c56fc1a65488afd493325b4c1bc3e" + "reference": "f301eb1453c9e7a1bc912ee8b0ea9db22c60223b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/cf04e88a2e3c56fc1a65488afd493325b4c1bc3e", - "reference": "cf04e88a2e3c56fc1a65488afd493325b4c1bc3e", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/f301eb1453c9e7a1bc912ee8b0ea9db22c60223b", + "reference": "f301eb1453c9e7a1bc912ee8b0ea9db22c60223b", "shasum": "" }, "require": { @@ -594,7 +594,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.8" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.9" }, "funding": [ { @@ -602,7 +602,7 @@ "type": "github" } ], - "time": "2021-10-30T08:01:38+00:00" + "time": "2021-11-19T15:21:02+00:00" }, { "name": "phpunit/php-file-iterator",