diff --git a/src/browser/markdown.zig b/src/browser/markdown.zig index 91e04196..f0ccd56e 100644 --- a/src/browser/markdown.zig +++ b/src/browser/markdown.zig @@ -20,6 +20,7 @@ const std = @import("std"); const Page = @import("Page.zig"); const URL = @import("URL.zig"); +const TreeWalker = @import("webapi/TreeWalker.zig"); const CData = @import("webapi/CData.zig"); const Element = @import("webapi/Element.zig"); const Node = @import("webapi/Node.zig"); @@ -114,26 +115,24 @@ fn isAllWhitespace(text: []const u8) bool { } else true; } -fn hasBlockDescendant(node: *Node) bool { - var it = node.childrenIterator(); - return while (it.next()) |child| { - if (child.is(Element)) |el| { - if (isBlock(el.getTag())) break true; - if (hasBlockDescendant(child)) break true; - } - } else false; +fn hasBlockDescendant(root: *Node) bool { + var tw = TreeWalker.FullExcludeSelf.Elements.init(root, .{}); + while (tw.next()) |el| { + if (isBlock(el.getTag())) return true; + } + return false; } -fn hasVisibleContent(node: *Node) bool { - var it = node.childrenIterator(); - while (it.next()) |child| { - if (isSignificantText(child)) return true; - if (child.is(Element)) |el| { - if (!isVisibleElement(el)) continue; - // Images are visible - if (el.getTag() == .img) return true; - // Recursive check - if (hasVisibleContent(child)) return true; +fn hasVisibleContent(root: *Node) bool { + var tw = TreeWalker.FullExcludeSelf.init(root, .{}); + while (tw.next()) |node| { + if (isSignificantText(node)) return true; + if (node.is(Element)) |el| { + if (!isVisibleElement(el)) { + tw.skipChildren(); + } else if (el.getTag() == .img) { + return true; + } } } return false; diff --git a/src/browser/webapi/TreeWalker.zig b/src/browser/webapi/TreeWalker.zig index b6df32fd..e4c19de7 100644 --- a/src/browser/webapi/TreeWalker.zig +++ b/src/browser/webapi/TreeWalker.zig @@ -31,6 +31,7 @@ const Mode = enum { pub fn TreeWalker(comptime mode: Mode) type { return struct { + _current: ?*Node = null, _next: ?*Node, _root: *Node, @@ -47,37 +48,74 @@ pub fn TreeWalker(comptime mode: Mode) type { pub fn next(self: *Self) ?*Node { const node = self._next orelse return null; + self._current = node; if (comptime mode == .children) { self._next = Node.linkToNodeOrNull(node._child_link.next); return node; } - if (node._children) |children| { - self._next = children.first(); - } else if (node._child_link.next) |n| { - self._next = Node.linkToNode(n); - } else { - // No children, no next sibling - walk up until we find a next sibling or hit root - var current = node._parent; - while (current) |parent| { - if (parent == self._root) { - self._next = null; - break; - } - if (parent._child_link.next) |next_sibling| { - self._next = Node.linkToNode(next_sibling); - break; - } - current = parent._parent; - } else { - self._next = null; - } - } + self._next = self.computeNextInDocumentOrder(node); return node; } + pub fn skipChildren(self: *Self) void { + if (comptime mode == .children) return; + const current = self._current orelse return; + self._next = self.computeNextSiblingOrUncle(current); + } + + pub fn nextSibling(self: *Self) ?*Node { + const current = self._current orelse return null; + const sibling = Node.linkToNodeOrNull(current._child_link.next) orelse return null; + + self._current = sibling; + if (comptime mode == .children) { + self._next = Node.linkToNodeOrNull(sibling._child_link.next); + } else { + self._next = self.computeNextInDocumentOrder(sibling); + } + return sibling; + } + + pub fn previousSibling(self: *Self) ?*Node { + const current = self._current orelse return null; + const sibling = Node.linkToNodeOrNull(current._child_link.prev) orelse return null; + + self._current = sibling; + if (comptime mode == .children) { + self._next = Node.linkToNodeOrNull(sibling._child_link.next); + } else { + self._next = self.computeNextInDocumentOrder(sibling); + } + return sibling; + } + + fn computeNextInDocumentOrder(self: *Self, node: *Node) ?*Node { + if (node._children) |children| { + return children.first(); + } + return self.computeNextSiblingOrUncle(node); + } + + fn computeNextSiblingOrUncle(self: *Self, node: *Node) ?*Node { + if (node._child_link.next) |n| { + return Node.linkToNode(n); + } + + var current = node._parent; + while (current) |parent| { + if (parent == self._root) return null; + if (parent._child_link.next) |next_sibling| { + return Node.linkToNode(next_sibling); + } + current = parent._parent; + } + return null; + } + pub fn reset(self: *Self) void { + self._current = null; self._next = firstNext(self._root); } @@ -147,3 +185,67 @@ pub fn TreeWalker(comptime mode: Mode) type { }; }; } + +test "TreeWalker: skipChildren" { + const testing = @import("../../testing.zig"); + const page = try testing.test_session.createPage(); + defer testing.test_session.removePage(); + const doc = page.window._document; + + //
+ // + // A + // + //

B

+ //
+ const div = try doc.createElement("div", null, page); + const span = try doc.createElement("span", null, page); + const b = try doc.createElement("b", null, page); + const p = try doc.createElement("p", null, page); + _ = try span.asNode().appendChild(b.asNode(), page); + _ = try div.asNode().appendChild(span.asNode(), page); + _ = try div.asNode().appendChild(p.asNode(), page); + + var tw = Full.init(div.asNode(), .{}); + + // root (div) + try testing.expect(tw.next() == div.asNode()); + + // span + try testing.expect(tw.next() == span.asNode()); + + // skip children of span (should jump over to

) + tw.skipChildren(); + try testing.expect(tw.next() == p.asNode()); + + try testing.expect(tw.next() == null); +} + +test "TreeWalker: sibling navigation" { + const testing = @import("../../testing.zig"); + const page = try testing.test_session.createPage(); + defer testing.test_session.removePage(); + const doc = page.window._document; + + //

+ // A + //

B

+ //
+ const div = try doc.createElement("div", null, page); + const span = try doc.createElement("span", null, page); + const p = try doc.createElement("p", null, page); + _ = try div.asNode().appendChild(span.asNode(), page); + _ = try div.asNode().appendChild(p.asNode(), page); + + var tw = Full.init(div.asNode(), .{}); + + // Move to span + _ = tw.next(); // div + _ = tw.next(); // span + + // nextSibling -> p + try testing.expect(tw.nextSibling() == p.asNode()); + + // previousSibling -> span + try testing.expect(tw.previousSibling() == span.asNode()); +}