diff --git a/src/css/match_test.zig b/src/css/match_test.zig index 9aaeedbd..8e659043 100644 --- a/src/css/match_test.zig +++ b/src/css/match_test.zig @@ -399,6 +399,70 @@ test "matchAll" { } } +test "pseudo class" { + const alloc = std.testing.allocator; + + var matcher = Matcher.init(alloc); + defer matcher.deinit(); + + var p1: Node = .{ .name = "p" }; + var p2: Node = .{ .name = "p" }; + var a1: Node = .{ .name = "a" }; + + p1.sibling = &p2; + p2.prev = &p1; + + p2.sibling = &a1; + a1.prev = &p2; + + var root: Node = .{ .child = &p1, .last = &a1 }; + p1.par = &root; + p2.par = &root; + a1.par = &root; + + const testcases = [_]struct { + q: []const u8, + n: Node, + exp: ?*const Node, + }{ + .{ .q = "p:only-child", .n = root, .exp = null }, + .{ .q = "a:only-of-type", .n = root, .exp = &a1 }, + }; + + for (testcases) |tc| { + matcher.reset(); + + const s = try css.parse(alloc, tc.q, .{}); + defer s.deinit(alloc); + + css.matchAll(s, &tc.n, &matcher) catch |e| { + std.debug.print("query: {s}, parsed selector: {any}\n", .{ tc.q, s }); + return e; + }; + + if (tc.exp) |exp_n| { + const exp: usize = 1; + std.testing.expectEqual(exp, matcher.nodes.items.len) catch |e| { + std.debug.print("query: {s}, parsed selector: {any}\n", .{ tc.q, s }); + return e; + }; + + std.testing.expectEqual(exp_n, matcher.nodes.items[0]) catch |e| { + std.debug.print("query: {s}, parsed selector: {any}\n", .{ tc.q, s }); + return e; + }; + + continue; + } + + const exp: usize = 0; + std.testing.expectEqual(exp, matcher.nodes.items.len) catch |e| { + std.debug.print("query: {s}, parsed selector: {any}\n", .{ tc.q, s }); + return e; + }; + } +} + test "nth pseudo class" { const alloc = std.testing.allocator; diff --git a/src/css/selector.zig b/src/css/selector.zig index 381b1d67..a0c5d4d0 100644 --- a/src/css/selector.zig +++ b/src/css/selector.zig @@ -145,6 +145,7 @@ pub const Selector = union(enum) { UnknownCombinedCombinator, UnsupportedRelativePseudoClass, UnsupportedContainsPseudoClass, + UnsupportedPseudoClass, UnsupportedRegexpPseudoClass, UnsupportedAttrRegexpOperator, }; @@ -315,13 +316,61 @@ pub const Selector = union(enum) { } return nthChildMatch(v.a, v.b, v.last, v.of_type, n); }, - .pseudo_class => return false, - .pseudo_class_only_child => return false, + .pseudo_class => |v| { + switch (v) { + .input => return Error.UnsupportedPseudoClass, + .empty => return Error.UnsupportedPseudoClass, + .root => return Error.UnsupportedPseudoClass, + .link => return Error.UnsupportedPseudoClass, + .enabled => return Error.UnsupportedPseudoClass, + .disabled => return Error.UnsupportedPseudoClass, + .checked => return Error.UnsupportedPseudoClass, + .visited => return Error.UnsupportedPseudoClass, + .hover => return Error.UnsupportedPseudoClass, + .active => return Error.UnsupportedPseudoClass, + .focus => return Error.UnsupportedPseudoClass, + .target => return Error.UnsupportedPseudoClass, + + // all others pseudo class are handled by specialized + // pseudo_class_X selectors. + else => return Error.UnsupportedPseudoClass, + } + }, + .pseudo_class_only_child => |v| onlyChildMatch(v, n), .pseudo_class_lang => return false, .pseudo_element => return false, }; } + // onlyChildMatch implements :only-child + // If `ofType` is true, it implements :only-of-type instead. + fn onlyChildMatch(of_type: bool, n: anytype) anyerror!bool { + if (!n.isElement()) return false; + + const p = try n.parent(); + if (p == null) return false; + + const ntag = try n.tag(); + + var count: usize = 0; + var c = try p.?.firstChild(); + // loop hover all n siblings. + while (c != null) { + // ignore non elements or others tags if of-type is true. + if (!c.?.isElement() or (of_type and !std.mem.eql(u8, ntag, try c.?.tag()))) { + c = try c.?.nextSibling(); + continue; + } + + count += 1; + if (count > 1) return false; + + c = try c.?.nextSibling(); + } + + return count == 1; + } + // simpleNthLastChildMatch implements :nth-last-child(b). // If ofType is true, implements :nth-last-of-type instead. fn simpleNthLastChildMatch(b: isize, of_type: bool, n: anytype) anyerror!bool {