Refactor common Document and Element methods into Node

This commit is contained in:
Karl Seguin
2026-02-12 12:31:05 +08:00
parent 060e2db351
commit badfe39a3d
3 changed files with 71 additions and 104 deletions

View File

@@ -219,64 +219,16 @@ pub fn getElementById(self: *Document, id: []const u8, page: *Page) ?*Element {
return null; return null;
} }
const GetElementsByTagNameResult = union(enum) { pub fn getElementsByTagName(self: *Document, tag_name: []const u8, page: *Page) !Node.GetElementsByTagNameResult {
tag: collections.NodeLive(.tag), return self.asNode().getElementsByTagName(tag_name, page);
tag_name: collections.NodeLive(.tag_name),
all_elements: collections.NodeLive(.all_elements),
};
pub fn getElementsByTagName(self: *Document, tag_name: []const u8, page: *Page) !GetElementsByTagNameResult {
if (tag_name.len > 256) {
// 256 seems generous.
return error.InvalidTagName;
}
if (std.mem.eql(u8, tag_name, "*")) {
return .{
.all_elements = collections.NodeLive(.all_elements).init(self.asNode(), {}, page),
};
}
const lower = std.ascii.lowerString(&page.buf, tag_name);
if (Node.Element.Tag.parseForMatch(lower)) |known| {
// optimized for known tag names, comparis
return .{
.tag = collections.NodeLive(.tag).init(self.asNode(), known, page),
};
}
const arena = page.arena;
const filter = try String.init(arena, lower, .{});
return .{ .tag_name = collections.NodeLive(.tag_name).init(self.asNode(), filter, page) };
} }
pub fn getElementsByTagNameNS(self: *Document, namespace: ?[]const u8, local_name: []const u8, page: *Page) !collections.NodeLive(.tag_name_ns) { pub fn getElementsByTagNameNS(self: *Document, namespace: ?[]const u8, local_name: []const u8, page: *Page) !collections.NodeLive(.tag_name_ns) {
if (local_name.len > 256) { return self.asNode().getElementsByTagNameNS(namespace, local_name, page);
return error.InvalidTagName;
}
// Parse namespace - "*" means wildcard (null), null means Element.Namespace.null
const ns: ?Element.Namespace = if (namespace) |ns_str|
if (std.mem.eql(u8, ns_str, "*")) null else Element.Namespace.parse(ns_str)
else
Element.Namespace.null;
return collections.NodeLive(.tag_name_ns).init(self.asNode(), .{
.namespace = ns,
.local_name = try String.init(page.arena, local_name, .{}),
}, page);
} }
pub fn getElementsByClassName(self: *Document, class_name: []const u8, page: *Page) !collections.NodeLive(.class_name) { pub fn getElementsByClassName(self: *Document, class_name: []const u8, page: *Page) !collections.NodeLive(.class_name) {
const arena = page.arena; return self.asNode().getElementsByClassName(class_name, page);
// Parse space-separated class names
var class_names: std.ArrayList([]const u8) = .empty;
var it = std.mem.tokenizeAny(u8, class_name, "\t\n\x0C\r ");
while (it.next()) |name| {
try class_names.append(arena, try page.dupeString(name));
}
return collections.NodeLive(.class_name).init(self.asNode(), class_names.items, page);
} }
pub fn getElementsByName(self: *Document, name: []const u8, page: *Page) !collections.NodeLive(.name) { pub fn getElementsByName(self: *Document, name: []const u8, page: *Page) !collections.NodeLive(.name) {

View File

@@ -1105,64 +1105,16 @@ fn calculateSiblingPosition(node: *Node) f64 {
return position * 5.0; // 5px per node return position * 5.0; // 5px per node
} }
const GetElementsByTagNameResult = union(enum) { pub fn getElementsByTagName(self: *Element, tag_name: []const u8, page: *Page) !Node.GetElementsByTagNameResult {
tag: collections.NodeLive(.tag), return self.asNode().getElementsByTagName(tag_name, page);
tag_name: collections.NodeLive(.tag_name),
all_elements: collections.NodeLive(.all_elements),
};
pub fn getElementsByTagName(self: *Element, tag_name: []const u8, page: *Page) !GetElementsByTagNameResult {
if (tag_name.len > 256) {
// 256 seems generous.
return error.InvalidTagName;
}
if (std.mem.eql(u8, tag_name, "*")) {
return .{
.all_elements = collections.NodeLive(.all_elements).init(self.asNode(), {}, page),
};
}
const lower = std.ascii.lowerString(&page.buf, tag_name);
if (Tag.parseForMatch(lower)) |known| {
// optimized for known tag names
return .{
.tag = collections.NodeLive(.tag).init(self.asNode(), known, page),
};
}
const arena = page.arena;
const filter = try String.init(arena, lower, .{});
return .{ .tag_name = collections.NodeLive(.tag_name).init(self.asNode(), filter, page) };
} }
pub fn getElementsByTagNameNS(self: *Element, namespace: ?[]const u8, local_name: []const u8, page: *Page) !collections.NodeLive(.tag_name_ns) { pub fn getElementsByTagNameNS(self: *Element, namespace: ?[]const u8, local_name: []const u8, page: *Page) !collections.NodeLive(.tag_name_ns) {
if (local_name.len > 256) { return self.asNode().getElementsByTagNameNS(namespace, local_name, page);
return error.InvalidTagName;
}
// Parse namespace - "*" means wildcard (null), null means Namespace.null
const ns: ?Namespace = if (namespace) |ns_str|
if (std.mem.eql(u8, ns_str, "*")) null else Namespace.parse(ns_str)
else
Namespace.null;
return collections.NodeLive(.tag_name_ns).init(self.asNode(), .{
.namespace = ns,
.local_name = try String.init(page.arena, local_name, .{}),
}, page);
} }
pub fn getElementsByClassName(self: *Element, class_name: []const u8, page: *Page) !collections.NodeLive(.class_name) { pub fn getElementsByClassName(self: *Element, class_name: []const u8, page: *Page) !collections.NodeLive(.class_name) {
const arena = page.arena; return self.asNode().getElementsByClassName(class_name, page);
// Parse space-separated class names
var class_names: std.ArrayList([]const u8) = .empty;
var it = std.mem.tokenizeAny(u8, class_name, "\t\n\x0C\r ");
while (it.next()) |name| {
try class_names.append(arena, try page.dupeString(name));
}
return collections.NodeLive(.class_name).init(self.asNode(), class_names.items, page);
} }
pub fn clone(self: *Element, deep: bool, page: *Page) !*Node { pub fn clone(self: *Element, deep: bool, page: *Page) !*Node {

View File

@@ -845,6 +845,69 @@ fn _normalize(self: *Node, allocator: Allocator, buffer: *std.ArrayList(u8), pag
} }
} }
pub const GetElementsByTagNameResult = union(enum) {
tag: collections.NodeLive(.tag),
tag_name: collections.NodeLive(.tag_name),
all_elements: collections.NodeLive(.all_elements),
};
// Not exposed in the WebAPI, but used by both Element and Document
pub fn getElementsByTagName(self: *Node, tag_name: []const u8, page: *Page) !GetElementsByTagNameResult {
if (tag_name.len > 256) {
// 256 seems generous.
return error.InvalidTagName;
}
if (std.mem.eql(u8, tag_name, "*")) {
return .{
.all_elements = collections.NodeLive(.all_elements).init(self, {}, page),
};
}
const lower = std.ascii.lowerString(&page.buf, tag_name);
if (Node.Element.Tag.parseForMatch(lower)) |known| {
// optimized for known tag names, comparis
return .{
.tag = collections.NodeLive(.tag).init(self, known, page),
};
}
const arena = page.arena;
const filter = try String.init(arena, lower, .{});
return .{ .tag_name = collections.NodeLive(.tag_name).init(self, filter, page) };
}
// Not exposed in the WebAPI, but used by both Element and Document
pub fn getElementsByTagNameNS(self: *Node, namespace: ?[]const u8, local_name: []const u8, page: *Page) !collections.NodeLive(.tag_name_ns) {
if (local_name.len > 256) {
return error.InvalidTagName;
}
// Parse namespace - "*" means wildcard (null), null means Element.Namespace.null
const ns: ?Element.Namespace = if (namespace) |ns_str|
if (std.mem.eql(u8, ns_str, "*")) null else Element.Namespace.parse(ns_str)
else
Element.Namespace.null;
return collections.NodeLive(.tag_name_ns).init(self, .{
.namespace = ns,
.local_name = try String.init(page.arena, local_name, .{}),
}, page);
}
// Not exposed in the WebAPI, but used by both Element and Document
pub fn getElementsByClassName(self: *Node, class_name: []const u8, page: *Page) !collections.NodeLive(.class_name) {
const arena = page.arena;
// Parse space-separated class names
var class_names: std.ArrayList([]const u8) = .empty;
var it = std.mem.tokenizeAny(u8, class_name, "\t\n\x0C\r ");
while (it.next()) |name| {
try class_names.append(arena, try page.dupeString(name));
}
return collections.NodeLive(.class_name).init(self, class_names.items, page);
}
// Writes a JSON representation of the node and its children // Writes a JSON representation of the node and its children
pub fn jsonStringify(self: *const Node, writer: *std.json.Stringify) !void { pub fn jsonStringify(self: *const Node, writer: *std.json.Stringify) !void {
// stupid json api requires this to be const, // stupid json api requires this to be const,