From e830d1da4ed120de78413ff2417034d8f1742940 Mon Sep 17 00:00:00 2001 From: Rohit <71192000+rohitsux@users.noreply.github.com> Date: Wed, 3 Jun 2026 21:03:27 +0530 Subject: [PATCH] feat(webapi): implement Node.lookupPrefix Implement the Node.lookupPrefix(namespace) method per the WHATWG DOM "locate a namespace prefix" algorithm: match the element's own namespace and prefix, then scan its xmlns: attribute declarations for one whose value is the namespace, then recurse to the parent element. This is the inverse of the existing lookupNamespaceURI and completes the namespace-introspection trio alongside isDefaultNamespace. --- src/browser/tests/node/lookup_prefix.html | 46 +++++++++++++++++++++++ src/browser/webapi/Element.zig | 28 ++++++++++++++ src/browser/webapi/Node.zig | 23 ++++++++++++ 3 files changed, 97 insertions(+) create mode 100644 src/browser/tests/node/lookup_prefix.html diff --git a/src/browser/tests/node/lookup_prefix.html b/src/browser/tests/node/lookup_prefix.html new file mode 100644 index 00000000..38af8cf2 --- /dev/null +++ b/src/browser/tests/node/lookup_prefix.html @@ -0,0 +1,46 @@ + + + +
+ +
+ + + + + + diff --git a/src/browser/webapi/Element.zig b/src/browser/webapi/Element.zig index d898dbd7..c8cdbfa2 100644 --- a/src/browser/webapi/Element.zig +++ b/src/browser/webapi/Element.zig @@ -422,6 +422,34 @@ pub fn lookupNamespaceURIForElement(self: *Element, prefix: ?[]const u8, frame: return parent.lookupNamespaceURIForElement(prefix, frame); } +// Locate a namespace prefix: the inverse of lookupNamespaceURIForElement. +// Given a namespace URI, find the prefix that declares it. +pub fn lookupPrefixForElement(self: *Element, namespace: []const u8, frame: *Frame) ?[]const u8 { + // Step 1: element's own namespace/prefix + if (self.getNamespaceUri(frame)) |ns_uri| { + if (self._prefix()) |el_prefix| { + if (std.mem.eql(u8, ns_uri, namespace)) { + return el_prefix; + } + } + } + + // Step 2: search xmlns: attribute declarations for one whose value is the namespace + if (self._attributes) |attrs| { + var iter = attrs.iterator(); + while (iter.next()) |entry| { + const name = entry._name.str(); + if (std.mem.startsWith(u8, name, "xmlns:") and std.mem.eql(u8, entry._value.str(), namespace)) { + return name["xmlns:".len..]; + } + } + } + + // Step 3: recurse to parent element + const parent = self.asNode().parentElement() orelse return null; + return parent.lookupPrefixForElement(namespace, frame); +} + fn _prefix(self: *const Element) ?[]const u8 { const name = self.getTagNameLower(); if (std.mem.indexOfPos(u8, name, 0, ":")) |pos| { diff --git a/src/browser/webapi/Node.zig b/src/browser/webapi/Node.zig index c57913aa..c4e0fe0a 100644 --- a/src/browser/webapi/Node.zig +++ b/src/browser/webapi/Node.zig @@ -379,6 +379,28 @@ pub fn lookupNamespaceURI(self: *Node, prefix_arg: ?[]const u8, frame: *Frame) ? } } +pub fn lookupPrefix(self: *Node, namespace_arg: ?[]const u8, frame: *Frame) ?[]const u8 { + const namespace = namespace_arg orelse return null; + if (namespace.len == 0) return null; + + switch (self._type) { + .element => |el| return el.lookupPrefixForElement(namespace, frame), + .document => |doc| { + const de = doc.getDocumentElement() orelse return null; + return de.lookupPrefixForElement(namespace, frame); + }, + .document_type, .document_fragment => return null, + .attribute => |attr| { + const owner = attr.getOwnerElement() orelse return null; + return owner.lookupPrefixForElement(namespace, frame); + }, + .cdata => { + const parent = self.parentElement() orelse return null; + return parent.lookupPrefixForElement(namespace, frame); + }, + } +} + pub fn isDefaultNamespace(self: *Node, namespace_arg: ?[]const u8, frame: *Frame) bool { const namespace: ?[]const u8 = if (namespace_arg) |ns| (if (ns.len == 0) null else ns) else null; const default_ns = self.lookupNamespaceURI(null, frame); @@ -1197,6 +1219,7 @@ pub const JsApi = struct { pub const getRootNode = bridge.function(Node.getRootNode, .{}); pub const isEqualNode = bridge.function(Node.isEqualNode, .{}); pub const lookupNamespaceURI = bridge.function(Node.lookupNamespaceURI, .{}); + pub const lookupPrefix = bridge.function(Node.lookupPrefix, .{}); pub const isDefaultNamespace = bridge.function(Node.isDefaultNamespace, .{}); fn _baseURI(_: *Node, frame: *const Frame) []const u8 {