mirror of
https://github.com/lightpanda-io/browser.git
synced 2026-03-22 04:34:44 +00:00
Merge pull request #1718 from lightpanda-io/enhance-treewalker
Enhance TreeWalker
This commit is contained in:
@@ -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;
|
||||
fn hasBlockDescendant(root: *Node) bool {
|
||||
var tw = TreeWalker.FullExcludeSelf.Elements.init(root, .{});
|
||||
while (tw.next()) |el| {
|
||||
if (isBlock(el.getTag())) return true;
|
||||
}
|
||||
} else false;
|
||||
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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
// <div>
|
||||
// <span>
|
||||
// <b>A</b>
|
||||
// </span>
|
||||
// <p>B</p>
|
||||
// </div>
|
||||
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 <b> to <p>)
|
||||
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;
|
||||
|
||||
// <div>
|
||||
// <span>A</span>
|
||||
// <p>B</p>
|
||||
// </div>
|
||||
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());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user