mirror of
https://github.com/lightpanda-io/browser.git
synced 2025-10-29 07:03:29 +00:00
css: implement group, compound and start combined match
This commit is contained in:
@@ -20,6 +20,13 @@ pub const Node = struct {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn parent(n: Node) !?Node {
|
||||||
|
const c = try parser.nodeParentNode(n.node);
|
||||||
|
if (c) |cc| return .{ .node = cc };
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
pub fn isElement(n: Node) bool {
|
pub fn isElement(n: Node) bool {
|
||||||
const t = parser.nodeType(n.node) catch return false;
|
const t = parser.nodeType(n.node) catch return false;
|
||||||
return t == .element;
|
return t == .element;
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ const css = @import("css.zig");
|
|||||||
pub const Node = struct {
|
pub const Node = struct {
|
||||||
child: ?*const Node = null,
|
child: ?*const Node = null,
|
||||||
sibling: ?*const Node = null,
|
sibling: ?*const Node = null,
|
||||||
|
par: ?*const Node = null,
|
||||||
|
|
||||||
name: []const u8 = "",
|
name: []const u8 = "",
|
||||||
att: ?[]const u8 = null,
|
att: ?[]const u8 = null,
|
||||||
@@ -17,6 +18,10 @@ pub const Node = struct {
|
|||||||
return n.sibling;
|
return n.sibling;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn parent(n: *const Node) !?*const Node {
|
||||||
|
return n.par;
|
||||||
|
}
|
||||||
|
|
||||||
pub fn isElement(_: *const Node) bool {
|
pub fn isElement(_: *const Node) bool {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -153,6 +158,24 @@ test "matchFirst" {
|
|||||||
.n = .{ .child = &.{ .name = "p", .sibling = &.{ .name = "p", .att = "ba" } } },
|
.n = .{ .child = &.{ .name = "p", .sibling = &.{ .name = "p", .att = "ba" } } },
|
||||||
.exp = 0,
|
.exp = 0,
|
||||||
},
|
},
|
||||||
|
.{
|
||||||
|
.q = "strong, a",
|
||||||
|
.n = .{ .child = &.{ .name = "p", .child = &.{ .name = "a" }, .sibling = &.{ .name = "strong" } } },
|
||||||
|
.exp = 1,
|
||||||
|
},
|
||||||
|
.{
|
||||||
|
.q = "p a",
|
||||||
|
.n = .{ .child = &.{ .name = "p", .child = &.{ .name = "a", .par = &.{ .name = "p" } }, .sibling = &.{ .name = "a" } } },
|
||||||
|
.exp = 1,
|
||||||
|
},
|
||||||
|
.{
|
||||||
|
.q = "p a",
|
||||||
|
.n = .{ .child = &.{ .name = "p", .child = &.{ .name = "span", .child = &.{
|
||||||
|
.name = "a",
|
||||||
|
.par = &.{ .name = "span", .par = &.{ .name = "p" } },
|
||||||
|
} } } },
|
||||||
|
.exp = 1,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
for (testcases) |tc| {
|
for (testcases) |tc| {
|
||||||
@@ -161,8 +184,15 @@ test "matchFirst" {
|
|||||||
const s = try css.parse(alloc, tc.q, .{});
|
const s = try css.parse(alloc, tc.q, .{});
|
||||||
defer s.deinit(alloc);
|
defer s.deinit(alloc);
|
||||||
|
|
||||||
_ = try css.matchFirst(s, &tc.n, &matcher);
|
_ = css.matchFirst(s, &tc.n, &matcher) catch |e| {
|
||||||
try std.testing.expectEqual(tc.exp, matcher.nodes.items.len);
|
std.debug.print("query: {s}, parsed selector: {any}\n", .{ tc.q, s });
|
||||||
|
return e;
|
||||||
|
};
|
||||||
|
|
||||||
|
std.testing.expectEqual(tc.exp, matcher.nodes.items.len) catch |e| {
|
||||||
|
std.debug.print("query: {s}, parsed selector: {any}\n", .{ tc.q, s });
|
||||||
|
return e;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -267,6 +297,24 @@ test "matchAll" {
|
|||||||
.n = .{ .child = &.{ .name = "p", .sibling = &.{ .name = "p", .att = "ba" } } },
|
.n = .{ .child = &.{ .name = "p", .sibling = &.{ .name = "p", .att = "ba" } } },
|
||||||
.exp = 0,
|
.exp = 0,
|
||||||
},
|
},
|
||||||
|
.{
|
||||||
|
.q = "strong, a",
|
||||||
|
.n = .{ .child = &.{ .name = "p", .child = &.{ .name = "a" }, .sibling = &.{ .name = "strong" } } },
|
||||||
|
.exp = 2,
|
||||||
|
},
|
||||||
|
.{
|
||||||
|
.q = "p a",
|
||||||
|
.n = .{ .child = &.{ .name = "p", .child = &.{ .name = "a", .par = &.{ .name = "p" } }, .sibling = &.{ .name = "a" } } },
|
||||||
|
.exp = 1,
|
||||||
|
},
|
||||||
|
.{
|
||||||
|
.q = "p a",
|
||||||
|
.n = .{ .child = &.{ .name = "p", .child = &.{ .name = "span", .child = &.{
|
||||||
|
.name = "a",
|
||||||
|
.par = &.{ .name = "span", .par = &.{ .name = "p" } },
|
||||||
|
} } } },
|
||||||
|
.exp = 1,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
for (testcases) |tc| {
|
for (testcases) |tc| {
|
||||||
@@ -275,7 +323,14 @@ test "matchAll" {
|
|||||||
const s = try css.parse(alloc, tc.q, .{});
|
const s = try css.parse(alloc, tc.q, .{});
|
||||||
defer s.deinit(alloc);
|
defer s.deinit(alloc);
|
||||||
|
|
||||||
_ = try css.matchAll(s, &tc.n, &matcher);
|
_ = css.matchAll(s, &tc.n, &matcher) catch |e| {
|
||||||
try std.testing.expectEqual(tc.exp, matcher.nodes.items.len);
|
std.debug.print("query: {s}, parsed selector: {any}\n", .{ tc.q, s });
|
||||||
|
return e;
|
||||||
|
};
|
||||||
|
|
||||||
|
std.testing.expectEqual(tc.exp, matcher.nodes.items.len) catch |e| {
|
||||||
|
std.debug.print("query: {s}, parsed selector: {any}\n", .{ tc.q, s });
|
||||||
|
return e;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ const selector = @import("selector.zig");
|
|||||||
const Selector = selector.Selector;
|
const Selector = selector.Selector;
|
||||||
const PseudoClass = selector.PseudoClass;
|
const PseudoClass = selector.PseudoClass;
|
||||||
const AttributeOP = selector.AttributeOP;
|
const AttributeOP = selector.AttributeOP;
|
||||||
|
const Combinator = selector.Combinator;
|
||||||
|
|
||||||
pub const ParseError = error{
|
pub const ParseError = error{
|
||||||
ExpectedSelector,
|
ExpectedSelector,
|
||||||
@@ -44,7 +45,7 @@ pub const ParseError = error{
|
|||||||
NotHandled,
|
NotHandled,
|
||||||
UnknownPseudoSelector,
|
UnknownPseudoSelector,
|
||||||
InvalidNthExpression,
|
InvalidNthExpression,
|
||||||
} || PseudoClass.Error || std.mem.Allocator.Error;
|
} || PseudoClass.Error || Combinator.Error || std.mem.Allocator.Error;
|
||||||
|
|
||||||
pub const ParseOptions = struct {
|
pub const ParseOptions = struct {
|
||||||
accept_pseudo_elts: bool = true,
|
accept_pseudo_elts: bool = true,
|
||||||
@@ -594,9 +595,9 @@ pub const Parser = struct {
|
|||||||
var s = try p.parseSimpleSelectorSequence(alloc);
|
var s = try p.parseSimpleSelectorSequence(alloc);
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
var combinator: u8 = undefined;
|
var combinator: Combinator = .empty;
|
||||||
if (p.skipWhitespace()) {
|
if (p.skipWhitespace()) {
|
||||||
combinator = ' ';
|
combinator = .descendant;
|
||||||
}
|
}
|
||||||
if (p.i >= p.s.len) {
|
if (p.i >= p.s.len) {
|
||||||
return s;
|
return s;
|
||||||
@@ -604,16 +605,18 @@ pub const Parser = struct {
|
|||||||
|
|
||||||
switch (p.s[p.i]) {
|
switch (p.s[p.i]) {
|
||||||
'+', '>', '~' => {
|
'+', '>', '~' => {
|
||||||
combinator = p.s[p.i];
|
combinator = try Combinator.parse(p.s[p.i]);
|
||||||
p.i += 1;
|
p.i += 1;
|
||||||
_ = p.skipWhitespace();
|
_ = p.skipWhitespace();
|
||||||
},
|
},
|
||||||
// These characters can't begin a selector, but they can legally occur after one.
|
// These characters can't begin a selector, but they can legally occur after one.
|
||||||
',', ')' => return s,
|
',', ')' => {
|
||||||
|
return s;
|
||||||
|
},
|
||||||
else => {},
|
else => {},
|
||||||
}
|
}
|
||||||
|
|
||||||
if (combinator == 0) {
|
if (combinator == .empty) {
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,28 @@ pub const AttributeOP = enum {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub const Combinator = enum {
|
||||||
|
empty,
|
||||||
|
descendant, // space
|
||||||
|
child, // >
|
||||||
|
next_sibling, // +
|
||||||
|
subsequent_sibling, // ~
|
||||||
|
|
||||||
|
pub const Error = error{
|
||||||
|
InvalidCombinator,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn parse(c: u8) Error!Combinator {
|
||||||
|
return switch (c) {
|
||||||
|
' ' => .descendant,
|
||||||
|
'>' => .child,
|
||||||
|
'+' => .next_sibling,
|
||||||
|
'~' => .subsequent_sibling,
|
||||||
|
else => Error.InvalidCombinator,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
pub const PseudoClass = enum {
|
pub const PseudoClass = enum {
|
||||||
not,
|
not,
|
||||||
has,
|
has,
|
||||||
@@ -119,6 +141,10 @@ pub const PseudoClass = enum {
|
|||||||
};
|
};
|
||||||
|
|
||||||
pub const Selector = union(enum) {
|
pub const Selector = union(enum) {
|
||||||
|
pub const Error = error{
|
||||||
|
UnknownCombinedCombinator,
|
||||||
|
};
|
||||||
|
|
||||||
compound: struct {
|
compound: struct {
|
||||||
selectors: []Selector,
|
selectors: []Selector,
|
||||||
pseudo_elt: ?PseudoClass,
|
pseudo_elt: ?PseudoClass,
|
||||||
@@ -137,7 +163,7 @@ pub const Selector = union(enum) {
|
|||||||
combined: struct {
|
combined: struct {
|
||||||
first: *Selector,
|
first: *Selector,
|
||||||
second: *Selector,
|
second: *Selector,
|
||||||
combinator: u8,
|
combinator: Combinator,
|
||||||
},
|
},
|
||||||
|
|
||||||
never_match: PseudoClass,
|
never_match: PseudoClass,
|
||||||
@@ -200,6 +226,40 @@ pub const Selector = union(enum) {
|
|||||||
.tag => |v| n.isElement() and std.ascii.eqlIgnoreCase(v, try n.tag()),
|
.tag => |v| n.isElement() and std.ascii.eqlIgnoreCase(v, try n.tag()),
|
||||||
.id => |v| return n.isElement() and std.mem.eql(u8, v, try n.attr("id") orelse return false),
|
.id => |v| return n.isElement() and std.mem.eql(u8, v, try n.attr("id") orelse return false),
|
||||||
.class => |v| return n.isElement() and word(try n.attr("class") orelse return false, v, false),
|
.class => |v| return n.isElement() and word(try n.attr("class") orelse return false, v, false),
|
||||||
|
.group => |v| {
|
||||||
|
for (v) |sel| {
|
||||||
|
if (try sel.match(n)) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
.compound => |v| {
|
||||||
|
if (v.selectors.len == 0) return n.isElement();
|
||||||
|
|
||||||
|
for (v.selectors) |sel| {
|
||||||
|
if (!try sel.match(n)) return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
.combined => |v| {
|
||||||
|
return switch (v.combinator) {
|
||||||
|
.empty => try v.first.match(n),
|
||||||
|
.descendant => {
|
||||||
|
if (!try v.second.match(n)) return false;
|
||||||
|
|
||||||
|
// The first must match a ascendent.
|
||||||
|
var p = try n.parent();
|
||||||
|
while (p != null) {
|
||||||
|
if (try v.first.match(p.?)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
p = try p.?.parent();
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
else => return Error.UnknownCombinedCombinator,
|
||||||
|
};
|
||||||
|
},
|
||||||
.attribute => |v| {
|
.attribute => |v| {
|
||||||
const attr = try n.attr(v.key);
|
const attr = try n.attr(v.key);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user