From 27d429a85f359b9c87bf9807e4dea33f7092308a Mon Sep 17 00:00:00 2001 From: Tim Ledbetter Date: Sun, 14 Jul 2024 22:50:38 +0100 Subject: [PATCH] LibWeb: Implement `Node.lookupNamespaceURI()` This method takes a prefix and returns the namespace URI associated with it on the given node, or null if no namespace is found. --- .../expected/DOM/Node-lookupNamespaceURI.txt | 20 ++++ .../input/DOM/Node-lookupNamespaceURI.html | 43 ++++++++ Userland/Libraries/LibWeb/DOM/Node.cpp | 99 +++++++++++++++++++ Userland/Libraries/LibWeb/DOM/Node.h | 3 + Userland/Libraries/LibWeb/DOM/Node.idl | 2 +- 5 files changed, 166 insertions(+), 1 deletion(-) create mode 100644 Tests/LibWeb/Text/expected/DOM/Node-lookupNamespaceURI.txt create mode 100644 Tests/LibWeb/Text/input/DOM/Node-lookupNamespaceURI.html diff --git a/Tests/LibWeb/Text/expected/DOM/Node-lookupNamespaceURI.txt b/Tests/LibWeb/Text/expected/DOM/Node-lookupNamespaceURI.txt new file mode 100644 index 00000000000..d186fdc0459 --- /dev/null +++ b/Tests/LibWeb/Text/expected/DOM/Node-lookupNamespaceURI.txt @@ -0,0 +1,20 @@ +documentFragment.lookupNamespaceURI(null): null +documentFragment.lookupNamespaceURI(""): null +documentFragment.lookupNamespaceURI("foo"): null +documentFragment.lookupNamespaceURI("xml"): null +documentFragment.lookupNamespaceURI("xmlns"): null +docType.lookupNamespaceURI(null): null +docType.lookupNamespaceURI(""): null +docType.lookupNamespaceURI("foo"): null +docType.lookupNamespaceURI("xml"): null +docType.lookupNamespaceURI("xmlns"): null +element.lookupNamespaceURI(null): null +element.lookupNamespaceURI(""): null +element.lookupNamespaceURI("foo"): null +element.lookupNamespaceURI("xml"): http://www.w3.org/XML/1998/namespace +element.lookupNamespaceURI("xmlns"): http://www.w3.org/2000/xmlns/ +After setting element attribute xmlns:bar='exampleNamespaceURI' +element.lookupNamespaceURI(null): null +element.lookupNamespaceURI(""): null +element.lookupNamespaceURI("foo"): null +element.lookupNamespaceURI("bar"): exampleNamespaceURI \ No newline at end of file diff --git a/Tests/LibWeb/Text/input/DOM/Node-lookupNamespaceURI.html b/Tests/LibWeb/Text/input/DOM/Node-lookupNamespaceURI.html new file mode 100644 index 00000000000..6513f5587b7 --- /dev/null +++ b/Tests/LibWeb/Text/input/DOM/Node-lookupNamespaceURI.html @@ -0,0 +1,43 @@ + + + diff --git a/Userland/Libraries/LibWeb/DOM/Node.cpp b/Userland/Libraries/LibWeb/DOM/Node.cpp index 25e329e80e2..fbfb089decd 100644 --- a/Userland/Libraries/LibWeb/DOM/Node.cpp +++ b/Userland/Libraries/LibWeb/DOM/Node.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -43,6 +44,7 @@ #include #include #include +#include #include #include @@ -1634,6 +1636,103 @@ bool Node::is_equal_node(Node const* other_node) const return true; } +// https://dom.spec.whatwg.org/#locate-a-namespace +Optional Node::locate_a_namespace(Optional const& prefix) const +{ + // To locate a namespace for a node using prefix, switch on the interface node implements: + + // Element + if (is(*this)) { + // 1. If prefix is "xml", then return the XML namespace. + if (prefix == "xml") + return Web::Namespace::XML.to_string(); + + // 2. If prefix is "xmlns", then return the XMLNS namespace. + if (prefix == "xmlns") + return Web::Namespace::XMLNS.to_string(); + + // 3. If its namespace is non-null and its namespace prefix is prefix, then return namespace. + auto& element = verify_cast(*this); + if (element.namespace_uri().has_value() && element.prefix() == prefix) + return element.namespace_uri()->to_string(); + + // 4. If it has an attribute whose namespace is the XMLNS namespace, namespace prefix is "xmlns", and local name is prefix, + // or if prefix is null and it has an attribute whose namespace is the XMLNS namespace, namespace prefix is null, + // and local name is "xmlns", then return its value if it is not the empty string, and null otherwise. + if (auto* attributes = element.attributes()) { + for (size_t i = 0; i < attributes->length(); ++i) { + auto& attr = *attributes->item(i); + if (attr.namespace_uri() == Web::Namespace::XMLNS) { + if ((attr.prefix() == "xmlns" && attr.local_name() == prefix) || (!prefix.has_value() && !attr.prefix().has_value() && attr.local_name() == "xmlns")) { + auto value = attr.value(); + if (!value.is_empty()) + return value; + + return {}; + } + } + } + } + + // 5. If its parent element is null, then return null. + auto* parent_element = element.parent_element(); + if (!element.parent_element()) + return {}; + + // 6. Return the result of running locate a namespace on its parent element using prefix. + return parent_element->locate_a_namespace(prefix); + } + + // Document + if (is(*this)) { + // 1. If its document element is null, then return null. + auto* document_element = verify_cast(*this).document_element(); + if (!document_element) + return {}; + + // 2. Return the result of running locate a namespace on its document element using prefix. + return document_element->locate_a_namespace(prefix); + } + + // DocumentType + // DocumentFragment + if (is(*this) || is(*this)) { + // Return null. + return {}; + } + + // Attr + if (is(*this)) { + // 1. If its element is null, then return null. + auto* element = verify_cast(*this).owner_element(); + if (!element) + return {}; + + // 2. Return the result of running locate a namespace on its element using prefix. + return element->locate_a_namespace(prefix); + } + + // Otherwise + // 1. If its parent element is null, then return null. + auto* parent_element = this->parent_element(); + if (!parent_element) + return {}; + + // 2. Return the result of running locate a namespace on its parent element using prefix. + return parent_element->locate_a_namespace(prefix); +} + +// https://dom.spec.whatwg.org/#dom-node-lookupnamespaceuri +Optional Node::lookup_namespace_uri(Optional prefix) const +{ + // 1. If prefix is the empty string, then set it to null. + if (prefix.has_value() && prefix->is_empty()) + prefix = {}; + + // 2. Return the result of running locate a namespace for this using prefix. + return locate_a_namespace(prefix); +} + // https://dom.spec.whatwg.org/#in-a-document-tree bool Node::in_a_document_tree() const { diff --git a/Userland/Libraries/LibWeb/DOM/Node.h b/Userland/Libraries/LibWeb/DOM/Node.h index 14fd18c2ee0..1efa4f5cc77 100644 --- a/Userland/Libraries/LibWeb/DOM/Node.h +++ b/Userland/Libraries/LibWeb/DOM/Node.h @@ -695,6 +695,9 @@ public: ErrorOr accessible_name(Document const&) const; ErrorOr accessible_description(Document const&) const; + Optional locate_a_namespace(Optional const& prefix) const; + Optional lookup_namespace_uri(Optional prefix) const; + protected: Node(JS::Realm&, Document&, NodeType); Node(Document&, NodeType); diff --git a/Userland/Libraries/LibWeb/DOM/Node.idl b/Userland/Libraries/LibWeb/DOM/Node.idl index d6c81d5356a..0b6e88f95e6 100644 --- a/Userland/Libraries/LibWeb/DOM/Node.idl +++ b/Userland/Libraries/LibWeb/DOM/Node.idl @@ -55,7 +55,7 @@ interface Node : EventTarget { boolean contains(Node? other); [FIXME] DOMString? lookupPrefix(DOMString? namespace); - [FIXME] DOMString? lookupNamespaceURI(DOMString? prefix); + DOMString? lookupNamespaceURI(DOMString? prefix); [FIXME] boolean isDefaultNamespace(DOMString? namespace); [ImplementedAs=pre_insert, CEReactions] Node insertBefore(Node node, Node? child);