Merge pull request #617 from lightpanda-io/before_and_after

Add before and after functions
This commit is contained in:
Pierre Tachoire
2025-05-09 12:00:39 +02:00
committed by GitHub
4 changed files with 167 additions and 56 deletions

View File

@@ -112,6 +112,16 @@ pub const CharacterData = struct {
return true;
}
pub fn _before(self: *parser.CharacterData, nodes: []const Node.NodeOrText) !void {
const ref_node = parser.characterDataToNode(self);
return Node.before(ref_node, nodes);
}
pub fn _after(self: *parser.CharacterData, nodes: []const Node.NodeOrText) !void {
const ref_node = parser.characterDataToNode(self);
return Node.after(ref_node, nodes);
}
};
// Tests

View File

@@ -227,28 +227,17 @@ pub const Document = struct {
return css.querySelectorAll(allocator, parser.documentToNode(self), selector);
}
// TODO according with https://dom.spec.whatwg.org/#parentnode, the
// function must accept either node or string.
// blocked by https://github.com/lightpanda-io/jsruntime-lib/issues/114
pub fn _prepend(self: *parser.Document, nodes: []const *parser.Node) !void {
pub fn _prepend(self: *parser.Document, nodes: []const Node.NodeOrText) !void {
return Node.prepend(parser.documentToNode(self), nodes);
}
// TODO according with https://dom.spec.whatwg.org/#parentnode, the
// function must accept either node or string.
// blocked by https://github.com/lightpanda-io/jsruntime-lib/issues/114
pub fn _append(self: *parser.Document, nodes: []const *parser.Node) !void {
pub fn _append(self: *parser.Document, nodes: []const Node.NodeOrText) !void {
return Node.append(parser.documentToNode(self), nodes);
}
// TODO according with https://dom.spec.whatwg.org/#parentnode, the
// function must accept either node or string.
// blocked by https://github.com/lightpanda-io/jsruntime-lib/issues/114
pub fn _replaceChildren(self: *parser.Document, nodes: []const *parser.Node) !void {
pub fn _replaceChildren(self: *parser.Document, nodes: []const Node.NodeOrText) !void {
return Node.replaceChildren(parser.documentToNode(self), nodes);
}
pub fn deinit(_: *parser.Document, _: std.mem.Allocator) void {}
};
const testing = @import("../../testing.zig");

View File

@@ -313,24 +313,25 @@ pub const Element = struct {
return css.querySelectorAll(state.arena, parser.elementToNode(self), selector);
}
// TODO according with https://dom.spec.whatwg.org/#parentnode, the
// function must accept either node or string.
// blocked by https://github.com/lightpanda-io/jsruntime-lib/issues/114
pub fn _prepend(self: *parser.Element, nodes: []const *parser.Node) !void {
pub fn _prepend(self: *parser.Element, nodes: []const Node.NodeOrText) !void {
return Node.prepend(parser.elementToNode(self), nodes);
}
// TODO according with https://dom.spec.whatwg.org/#parentnode, the
// function must accept either node or string.
// blocked by https://github.com/lightpanda-io/jsruntime-lib/issues/114
pub fn _append(self: *parser.Element, nodes: []const *parser.Node) !void {
pub fn _append(self: *parser.Element, nodes: []const Node.NodeOrText) !void {
return Node.append(parser.elementToNode(self), nodes);
}
// TODO according with https://dom.spec.whatwg.org/#parentnode, the
// function must accept either node or string.
// blocked by https://github.com/lightpanda-io/jsruntime-lib/issues/114
pub fn _replaceChildren(self: *parser.Element, nodes: []const *parser.Node) !void {
pub fn _before(self: *parser.Element, nodes: []const Node.NodeOrText) !void {
const ref_node = parser.elementToNode(self);
return Node.before(ref_node, nodes);
}
pub fn _after(self: *parser.Element, nodes: []const Node.NodeOrText) !void {
const ref_node = parser.elementToNode(self);
return Node.after(ref_node, nodes);
}
pub fn _replaceChildren(self: *parser.Element, nodes: []const Node.NodeOrText) !void {
return Node.replaceChildren(parser.elementToNode(self), nodes);
}
@@ -529,4 +530,28 @@ test "Browser.DOM.Element" {
.{ "el.matches('#9000')", "false" },
.{ "el.matches('.notok')", "false" },
}, .{});
// before
try runner.testCases(&.{
.{ "const before_container = document.createElement('div');", "undefined" },
.{ "document.append(before_container);", "undefined" },
.{ "const b1 = document.createElement('div');", "undefined" },
.{ "before_container.append(b1);", "undefined" },
.{ "const b1_a = document.createElement('p');", "undefined" },
.{ "b1.before(b1_a, 'over 9000');", "undefined" },
.{ "before_container.innerHTML", "<p></p>over 9000<div></div>" },
}, .{});
// after
try runner.testCases(&.{
.{ "const after_container = document.createElement('div');", "undefined" },
.{ "document.append(after_container);", "undefined" },
.{ "const a1 = document.createElement('div');", "undefined" },
.{ "after_container.append(a1);", "undefined" },
.{ "const a1_a = document.createElement('p');", "undefined" },
.{ "a1.after('over 9000', a1_a);", "undefined" },
.{ "after_container.innerHTML", "<div></div>over 9000<p></p>" },
}, .{});
}

View File

@@ -337,65 +337,72 @@ pub const Node = struct {
// For now, it checks only if new nodes are not self.
// TODO implements the others contraints.
// see https://dom.spec.whatwg.org/#concept-node-tree
pub fn hierarchy(self: *parser.Node, nodes: []const *parser.Node) !bool {
if (nodes.len == 0) return true;
for (nodes) |node| if (self == node) return false;
pub fn hierarchy(self: *parser.Node, nodes: []const NodeOrText) bool {
for (nodes) |n| {
if (n.is(self)) {
return false;
}
}
return true;
}
// TODO according with https://dom.spec.whatwg.org/#parentnode, the
// function must accept either node or string.
// blocked by https://github.com/lightpanda-io/jsruntime-lib/issues/114
pub fn prepend(self: *parser.Node, nodes: []const *parser.Node) !void {
if (nodes.len == 0) return;
pub fn prepend(self: *parser.Node, nodes: []const NodeOrText) !void {
if (nodes.len == 0) {
return;
}
// check hierarchy
if (!try hierarchy(self, nodes)) return parser.DOMError.HierarchyRequest;
if (!hierarchy(self, nodes)) {
return parser.DOMError.HierarchyRequest;
}
const first = try parser.nodeFirstChild(self);
if (first == null) {
const doc = (try parser.nodeOwnerDocument(self)) orelse return;
if (try parser.nodeFirstChild(self)) |first| {
for (nodes) |node| {
_ = try parser.nodeAppendChild(self, node);
_ = try parser.nodeInsertBefore(self, try node.toNode(doc), first);
}
return;
}
for (nodes) |node| {
_ = try parser.nodeInsertBefore(self, node, first.?);
_ = try parser.nodeAppendChild(self, try node.toNode(doc));
}
}
// TODO according with https://dom.spec.whatwg.org/#parentnode, the
// function must accept either node or string.
// blocked by https://github.com/lightpanda-io/jsruntime-lib/issues/114
pub fn append(self: *parser.Node, nodes: []const *parser.Node) !void {
if (nodes.len == 0) return;
pub fn append(self: *parser.Node, nodes: []const NodeOrText) !void {
if (nodes.len == 0) {
return;
}
// check hierarchy
if (!try hierarchy(self, nodes)) return parser.DOMError.HierarchyRequest;
if (!hierarchy(self, nodes)) {
return parser.DOMError.HierarchyRequest;
}
const doc = (try parser.nodeOwnerDocument(self)) orelse return;
for (nodes) |node| {
_ = try parser.nodeAppendChild(self, node);
_ = try parser.nodeAppendChild(self, try node.toNode(doc));
}
}
// TODO according with https://dom.spec.whatwg.org/#parentnode, the
// function must accept either node or string.
// blocked by https://github.com/lightpanda-io/jsruntime-lib/issues/114
pub fn replaceChildren(self: *parser.Node, nodes: []const *parser.Node) !void {
if (nodes.len == 0) return;
pub fn replaceChildren(self: *parser.Node, nodes: []const NodeOrText) !void {
if (nodes.len == 0) {
return;
}
// check hierarchy
if (!try hierarchy(self, nodes)) return parser.DOMError.HierarchyRequest;
if (!hierarchy(self, nodes)) {
return parser.DOMError.HierarchyRequest;
}
// remove existing children
try removeChildren(self);
const doc = (try parser.nodeOwnerDocument(self)) orelse return;
// add new children
for (nodes) |node| {
_ = try parser.nodeAppendChild(self, node);
_ = try parser.nodeAppendChild(self, try node.toNode(doc));
}
}
@@ -414,7 +421,87 @@ pub const Node = struct {
}
}
pub fn deinit(_: *parser.Node, _: std.mem.Allocator) void {}
pub fn before(self: *parser.Node, nodes: []const NodeOrText) !void {
const parent = try parser.nodeParentNode(self) orelse return;
const doc = (try parser.nodeOwnerDocument(parent)) orelse return;
var sibling: ?*parser.Node = self;
// have to find the first sibling that isn't in nodes
CHECK: while (sibling) |s| {
for (nodes) |n| {
if (n.is(s)) {
sibling = try parser.nodePreviousSibling(s);
continue :CHECK;
}
}
break;
}
if (sibling == null) {
sibling = try parser.nodeFirstChild(parent);
}
if (sibling) |ref_node| {
for (nodes) |node| {
_ = try parser.nodeInsertBefore(parent, try node.toNode(doc), ref_node);
}
return;
}
return Node.prepend(self, nodes);
}
pub fn after(self: *parser.Node, nodes: []const NodeOrText) !void {
const parent = try parser.nodeParentNode(self) orelse return;
const doc = (try parser.nodeOwnerDocument(parent)) orelse return;
// have to find the first sibling that isn't in nodes
var sibling = try parser.nodeNextSibling(self);
CHECK: while (sibling) |s| {
for (nodes) |n| {
if (n.is(s)) {
sibling = try parser.nodeNextSibling(s);
continue :CHECK;
}
}
break;
}
if (sibling) |ref_node| {
for (nodes) |node| {
_ = try parser.nodeInsertBefore(parent, try node.toNode(doc), ref_node);
}
return;
}
for (nodes) |node| {
_ = try parser.nodeAppendChild(parent, try node.toNode(doc));
}
}
// A lot of functions take either a node or text input.
// The text input is to be converted into a Text node.
pub const NodeOrText = union(enum) {
text: []const u8,
node: *parser.Node,
fn toNode(self: NodeOrText, doc: *parser.Document) !*parser.Node {
return switch (self) {
.node => |n| n,
.text => |txt| @ptrCast(try parser.documentCreateTextNode(doc, txt)),
};
}
// Whether the node represented by the NodeOrText is the same as the
// given Node. Always false for text values as these represent as-of-yet
// created Text nodes.
fn is(self: NodeOrText, other: *parser.Node) bool {
return switch (self) {
.text => false,
.node => |n| n == other,
};
}
};
};
const testing = @import("../../testing.zig");