css: implement :only-child and :only-of-type

This commit is contained in:
Pierre Tachoire
2024-03-25 10:25:46 +01:00
parent db5d933285
commit bd899111d5
2 changed files with 115 additions and 2 deletions

View File

@@ -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;

View File

@@ -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 {