diff --git a/lib/Attr.php b/lib/Attr.php index 2a14cfe..be3870e 100644 --- a/lib/Attr.php +++ b/lib/Attr.php @@ -18,9 +18,10 @@ class Attr extends Node { } protected function __get_name(): string { - // PHP's DOM does this correctly already. + // PHP's DOM incorrectly returns the local name instead of the qualified name + // per the specification. // Need to uncoerce string if necessary. - $name = $this->innerNode->name; + $name = $this->innerNode->nodeName; return (!str_contains(needle: 'U', haystack: $name)) ? $name : $this->uncoerceName($name); } diff --git a/lib/HTMLCollection.php b/lib/HTMLCollection.php index 4748363..6ae41f3 100644 --- a/lib/HTMLCollection.php +++ b/lib/HTMLCollection.php @@ -45,4 +45,8 @@ class HTMLCollection extends Collection { public function offsetGet($offset): ?Element { return (is_int($offset)) ? $this->item($offset) : $this->namedItem($offset); } + + public function offsetExists($offset): bool { + return (((is_int($offset)) ? $this->item($offset) : $this->namedItem($offset)) !== null); + } } \ No newline at end of file diff --git a/lib/NamedNodeMap.php b/lib/NamedNodeMap.php index c31c635..b1a8a3b 100644 --- a/lib/NamedNodeMap.php +++ b/lib/NamedNodeMap.php @@ -7,10 +7,16 @@ declare(strict_types=1); namespace MensBeam\HTML\DOM; -use MensBeam\HTML\DOM\Inner\Document as InnerDocument; +use MensBeam\HTML\DOM\Inner\{ + Document as InnerDocument, + Reflection +}; +use MensBeam\HTML\Parser\NameCoercion; class NamedNodeMap extends Collection { + use NameCoercion; + # A NamedNodeMap has an associated element (an element). protected Element $element; @@ -43,6 +49,53 @@ class NamedNodeMap extends Collection { return parent::item($index); } + public function offsetGet($offset): ?Attr { + if (is_int($offset)) { + return $this->item($offset); + } + + # A NamedNodeMap object’s supported property names are the return value of running + # these steps: + # 1. Let names be the qualified names of the attributes in this NamedNodeMap object’s + # attribute list, with duplicates omitted, in order. + // The spec is extremely vague as to what to do here, but it seems to expect + // this to be some sort of live private property that the class will poll to + // check for valid property names when trying to access them. This is + // inefficient. Going to do basically the same thing but not return a list of + // every one. It will just search the element's attribute list instead using the + // same process. + + # 2. If this NamedNodeMap object’s element is in the HTML namespace and its node + # document is an HTML document, then for each name in names: + # 1. Let lowercaseName be name, in ASCII lowercase. + # 2. If lowercaseName is not equal to name, remove name from names. + # 3. Return names. + + $innerElement = Reflection::getProtectedProperty($this->element, 'innerNode'); + $innerDocument = $innerElement->ownerDocument; + $attributes = $innerElement->attributes; + if ($attributes->length > 0) { + $coercedOffset = $this->coerceName($offset); + + foreach ($attributes as $attr) { + $name = $attr->nodeName; + if ($this->element->namespaceURI === Node::HTML_NAMESPACE && $name !== strtolower($name)) { + continue; + } + + if ($name === $offset || $name === $coercedOffset) { + return $innerDocument->getWrapperNode($attr); + } + } + } + + return null; + } + + public function offsetExists($offset): bool { + return (((is_int($offset)) ? $this->item($offset) : $this->getNamedItem($offset)) !== null); + } + public function removeNamedItem(string $qualifiedName): ?Attr { return $this->removeNamedItemNS(null, $qualifiedName); } diff --git a/tests/cases/TestAttr.php b/tests/cases/TestAttr.php index 8665faa..8a5bfb7 100644 --- a/tests/cases/TestAttr.php +++ b/tests/cases/TestAttr.php @@ -27,7 +27,7 @@ class TestAttr extends \PHPUnit\Framework\TestCase { // Coerced name $this->assertSame('poop💩', $body->getAttributeNode('poop💩')->name); // Foreign attribute name - $this->assertSame('xlink', $svg->getAttributeNodeNS(Node::XMLNS_NAMESPACE, 'xlink')->name); + $this->assertSame('xmlns:xlink', $svg->getAttributeNodeNS(Node::XMLNS_NAMESPACE, 'xlink')->name); } public function testProperty_prefix(): void { diff --git a/tests/cases/TestCollection.php b/tests/cases/TestCollection.php new file mode 100644 index 0000000..8295061 --- /dev/null +++ b/tests/cases/TestCollection.php @@ -0,0 +1,94 @@ +