SemanticTree: add checked state to node data and output

This commit is contained in:
Adrià Arrufat
2026-03-31 06:19:40 +02:00
parent 16f17ead9a
commit fc057a3bb3

View File

@@ -95,10 +95,11 @@ const NodeData = struct {
name: ?[]const u8, name: ?[]const u8,
value: ?[]const u8, value: ?[]const u8,
options: ?[]OptionData = null, options: ?[]OptionData = null,
checked: ?bool = null,
xpath: []const u8, xpath: []const u8,
is_interactive: bool, interactive: bool,
is_disabled: bool, disabled: bool,
node_name: []const u8, tag_name: []const u8,
}; };
const WalkContext = struct { const WalkContext = struct {
@@ -152,13 +153,17 @@ fn walk(
var is_disabled = false; var is_disabled = false;
var value: ?[]const u8 = null; var value: ?[]const u8 = null;
var options: ?[]OptionData = null; var options: ?[]OptionData = null;
var node_name: []const u8 = "text"; var checked: ?bool = null;
var tag_name: []const u8 = "text";
if (node.is(Element)) |el| { if (node.is(Element)) |el| {
node_name = el.getTagNameLower(); tag_name = el.getTagNameLower();
if (el.is(Element.Html.Input)) |input| { if (el.is(Element.Html.Input)) |input| {
value = input.getValue(); value = input.getValue();
if (input._input_type == .checkbox or input._input_type == .radio) {
checked = input.getChecked();
}
if (el.getAttributeSafe(comptime .wrap("list"))) |list_id| { if (el.getAttributeSafe(comptime .wrap("list"))) |list_id| {
options = try extractDataListOptions(list_id, self.page, self.arena); options = try extractDataListOptions(list_id, self.page, self.arena);
} }
@@ -177,7 +182,7 @@ fn walk(
is_disabled = el.isDisabled(); is_disabled = el.isDisabled();
} else if (node._type == .document or node._type == .document_fragment) { } else if (node._type == .document or node._type == .document_fragment) {
node_name = "root"; tag_name = "root";
} }
const initial_xpath_len = ctx.xpath_buffer.items.len; const initial_xpath_len = ctx.xpath_buffer.items.len;
@@ -238,10 +243,11 @@ fn walk(
.name = name, .name = name,
.value = value, .value = value,
.options = options, .options = options,
.checked = checked,
.xpath = xpath, .xpath = xpath,
.is_interactive = is_interactive, .interactive = is_interactive,
.is_disabled = is_disabled, .disabled = is_disabled,
.node_name = node_name, .tag_name = tag_name,
}; };
if (should_visit) { if (should_visit) {
@@ -340,7 +346,7 @@ const JsonVisitor = struct {
try self.jw.write(data.id); try self.jw.write(data.id);
try self.jw.objectField("nodeName"); try self.jw.objectField("nodeName");
try self.jw.write(data.node_name); try self.jw.write(data.tag_name);
try self.jw.objectField("xpath"); try self.jw.objectField("xpath");
try self.jw.write(data.xpath); try self.jw.write(data.xpath);
@@ -350,9 +356,9 @@ const JsonVisitor = struct {
try self.jw.write(1); try self.jw.write(1);
try self.jw.objectField("isInteractive"); try self.jw.objectField("isInteractive");
try self.jw.write(data.is_interactive); try self.jw.write(data.interactive);
if (data.is_disabled) { if (data.disabled) {
try self.jw.objectField("isDisabled"); try self.jw.objectField("isDisabled");
try self.jw.write(true); try self.jw.write(true);
} }
@@ -383,6 +389,11 @@ const JsonVisitor = struct {
try self.jw.endObject(); try self.jw.endObject();
} }
if (data.checked) |checked| {
try self.jw.objectField("checked");
try self.jw.write(checked);
}
if (data.options) |options| { if (data.options) |options| {
try self.jw.objectField("options"); try self.jw.objectField("options");
try self.jw.beginArray(); try self.jw.beginArray();
@@ -469,8 +480,8 @@ const TextVisitor = struct {
const is_text_only = std.mem.eql(u8, data.role, "StaticText") or std.mem.eql(u8, data.role, "none") or std.mem.eql(u8, data.role, "generic"); const is_text_only = std.mem.eql(u8, data.role, "StaticText") or std.mem.eql(u8, data.role, "none") or std.mem.eql(u8, data.role, "generic");
try self.writer.print("{d}", .{data.id}); try self.writer.print("{d}", .{data.id});
if (data.is_interactive) { if (data.interactive) {
try self.writer.writeAll(if (data.is_disabled) " [i:disabled]" else " [i]"); try self.writer.writeAll(if (data.disabled) " [i:disabled]" else " [i]");
} }
if (!is_text_only) { if (!is_text_only) {
try self.writer.print(" {s}", .{data.role}); try self.writer.print(" {s}", .{data.role});
@@ -485,6 +496,14 @@ const TextVisitor = struct {
} }
} }
if (data.checked) |c| {
if (c) {
try self.writer.writeAll(" [checked]");
} else {
try self.writer.writeAll(" [unchecked]");
}
}
if (data.options) |options| { if (data.options) |options| {
try self.writer.writeAll(" options=["); try self.writer.writeAll(" options=[");
for (options, 0..) |opt, i| { for (options, 0..) |opt, i| {
@@ -527,8 +546,8 @@ pub const NodeDetails = struct {
tag_name: []const u8, tag_name: []const u8,
role: []const u8, role: []const u8,
name: ?[]const u8, name: ?[]const u8,
is_interactive: bool, interactive: bool,
is_disabled: bool, disabled: bool,
value: ?[]const u8 = null, value: ?[]const u8 = null,
input_type: ?[]const u8 = null, input_type: ?[]const u8 = null,
placeholder: ?[]const u8 = null, placeholder: ?[]const u8 = null,
@@ -556,9 +575,9 @@ pub const NodeDetails = struct {
} }
try jw.objectField("isInteractive"); try jw.objectField("isInteractive");
try jw.write(self.is_interactive); try jw.write(self.interactive);
if (self.is_disabled) { if (self.disabled) {
try jw.objectField("isDisabled"); try jw.objectField("isDisabled");
try jw.write(true); try jw.write(true);
} }
@@ -620,13 +639,18 @@ pub const NodeDetails = struct {
} }
}; };
pub fn getNodeDetails(node: *Node, registry: *CDPNode.Registry, page: *Page, arena: std.mem.Allocator) !NodeDetails { pub fn getNodeDetails(
node: *Node,
registry: *CDPNode.Registry,
page: *Page,
arena: std.mem.Allocator,
) !NodeDetails {
const cdp_node = try registry.register(node); const cdp_node = try registry.register(node);
const axn = AXNode.fromNode(node); const axn = AXNode.fromNode(node);
const role = try axn.getRole(); const role = try axn.getRole();
const name = try axn.getName(page, arena); const name = try axn.getName(page, arena);
var is_interactive_val = false; var is_interactive = false;
var is_disabled = false; var is_disabled = false;
var tag_name: []const u8 = "text"; var tag_name: []const u8 = "text";
var value: ?[]const u8 = null; var value: ?[]const u8 = null;
@@ -670,7 +694,7 @@ pub fn getNodeDetails(node: *Node, registry: *CDPNode.Registry, page: *Page, are
const listener_targets = try interactive.buildListenerTargetMap(page, arena); const listener_targets = try interactive.buildListenerTargetMap(page, arena);
var pointer_events_cache: Element.PointerEventsCache = .empty; var pointer_events_cache: Element.PointerEventsCache = .empty;
if (interactive.classifyInteractivity(page, el, html_el, listener_targets, &pointer_events_cache) != null) { if (interactive.classifyInteractivity(page, el, html_el, listener_targets, &pointer_events_cache) != null) {
is_interactive_val = true; is_interactive = true;
} }
} }
} }
@@ -680,8 +704,8 @@ pub fn getNodeDetails(node: *Node, registry: *CDPNode.Registry, page: *Page, are
.tag_name = tag_name, .tag_name = tag_name,
.role = role, .role = role,
.name = name, .name = name,
.is_interactive = is_interactive_val, .interactive = is_interactive,
.is_disabled = is_disabled, .disabled = is_disabled,
.value = value, .value = value,
.input_type = input_type, .input_type = input_type,
.placeholder = placeholder, .placeholder = placeholder,