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 {