From 9c997ec86d5b667e30edfb14ef6cda1d3c4f71dd Mon Sep 17 00:00:00 2001 From: Pierre Tachoire Date: Tue, 19 Mar 2024 09:25:52 +0100 Subject: [PATCH] css: add pseudo class relative match --- src/css/match_test.zig | 50 ++++++++++++++++++++++++++++++++++++++++++ src/css/selector.zig | 32 +++++++++++++++++++++++++++ 2 files changed, 82 insertions(+) diff --git a/src/css/match_test.zig b/src/css/match_test.zig index 22694961..254e9156 100644 --- a/src/css/match_test.zig +++ b/src/css/match_test.zig @@ -176,6 +176,31 @@ test "matchFirst" { } } } }, .exp = 1, }, + .{ + .q = ":not(p)", + .n = .{ .child = &.{ .name = "p", .child = &.{ .name = "a" }, .sibling = &.{ .name = "strong" } } }, + .exp = 1, + }, + .{ + .q = "p:has(a)", + .n = .{ .child = &.{ .name = "p", .child = &.{ .name = "a" }, .sibling = &.{ .name = "strong" } } }, + .exp = 1, + }, + .{ + .q = "p:has(strong)", + .n = .{ .child = &.{ .name = "p", .child = &.{ .name = "a" }, .sibling = &.{ .name = "strong" } } }, + .exp = 0, + }, + .{ + .q = "p:haschild(a)", + .n = .{ .child = &.{ .name = "p", .child = &.{ .name = "a" }, .sibling = &.{ .name = "strong" } } }, + .exp = 1, + }, + .{ + .q = "p:haschild(strong)", + .n = .{ .child = &.{ .name = "p", .child = &.{ .name = "a" }, .sibling = &.{ .name = "strong" } } }, + .exp = 0, + }, }; for (testcases) |tc| { @@ -315,6 +340,31 @@ test "matchAll" { } } } }, .exp = 1, }, + .{ + .q = ":not(p)", + .n = .{ .child = &.{ .name = "p", .child = &.{ .name = "a" }, .sibling = &.{ .name = "strong" } } }, + .exp = 2, + }, + .{ + .q = "p:has(a)", + .n = .{ .child = &.{ .name = "p", .child = &.{ .name = "a" }, .sibling = &.{ .name = "strong" } } }, + .exp = 1, + }, + .{ + .q = "p:has(strong)", + .n = .{ .child = &.{ .name = "p", .child = &.{ .name = "a" }, .sibling = &.{ .name = "strong" } } }, + .exp = 0, + }, + .{ + .q = "p:haschild(a)", + .n = .{ .child = &.{ .name = "p", .child = &.{ .name = "a" }, .sibling = &.{ .name = "strong" } } }, + .exp = 1, + }, + .{ + .q = "p:haschild(strong)", + .n = .{ .child = &.{ .name = "p", .child = &.{ .name = "a" }, .sibling = &.{ .name = "strong" } } }, + .exp = 0, + }, }; for (testcases) |tc| { diff --git a/src/css/selector.zig b/src/css/selector.zig index f94144b5..df0788b0 100644 --- a/src/css/selector.zig +++ b/src/css/selector.zig @@ -143,6 +143,7 @@ pub const PseudoClass = enum { pub const Selector = union(enum) { pub const Error = error{ UnknownCombinedCombinator, + UnsupportedRelativePseudoClass, }; compound: struct { @@ -289,10 +290,41 @@ pub const Selector = union(enum) { }; }, .never_match => return false, + .pseudo_class_relative => |v| { + if (!n.isElement()) return false; + + return switch (v.pseudo_class) { + .not => !try v.match.match(n), + .has => try hasDescendantMatch(v.match, n), + .haschild => try hasChildMatch(v.match, n), + else => Error.UnsupportedRelativePseudoClass, + }; + }, else => false, }; } + fn hasDescendantMatch(s: *const Selector, n: anytype) anyerror!bool { + var c = try n.firstChild(); + while (c != null) { + if (try s.match(c.?)) return true; + if (c.?.isElement() and try hasDescendantMatch(s, c.?)) return true; + c = try c.?.nextSibling(); + } + + return false; + } + + fn hasChildMatch(s: *const Selector, n: anytype) anyerror!bool { + var c = try n.firstChild(); + while (c != null) { + if (try s.match(c.?)) return true; + c = try c.?.nextSibling(); + } + + return false; + } + pub fn deinit(sel: Selector, alloc: std.mem.Allocator) void { switch (sel) { .group => |v| {