From d9c76aa13e4defe1e83b7ace2f926601a0fe9536 Mon Sep 17 00:00:00 2001 From: Pierre Tachoire Date: Fri, 15 Mar 2024 09:06:34 +0100 Subject: [PATCH] css: extract public api on its own file --- src/css/css.zig | 127 +++++++++++++++++++++++++++++++++++++++++++++ src/css/parser.zig | 127 ++------------------------------------------- 2 files changed, 132 insertions(+), 122 deletions(-) create mode 100644 src/css/css.zig diff --git a/src/css/css.zig b/src/css/css.zig new file mode 100644 index 00000000..6092b1d0 --- /dev/null +++ b/src/css/css.zig @@ -0,0 +1,127 @@ +// CSS Selector parser and query +// This package is a rewrite in Zig of Cascadia CSS Selector parser. +// see https://github.com/andybalholm/cascadia +const std = @import("std"); +const Selector = @import("selector.zig").Selector; +const parser = @import("parser.zig"); + +// Parse parse a selector string and returns the parsed result or an error. +pub fn Parse(alloc: std.mem.Allocator, s: []const u8, opts: parser.ParseOptions) parser.ParseError!Selector { + var p = parser.Parser{ .s = s, .i = 0, .opts = opts }; + return p.parse(alloc); +} + +test "Parse" { + const alloc = std.testing.allocator; + + const testcases = [_][]const u8{ + "address", + "*", + "#foo", + "li#t1", + "*#t4", + ".t1", + "p.t1", + "div.teST", + ".t1.fail", + "p.t1.t2", + "p.--t1", + "p.--t1.--t2", + "p[title]", + "div[class=\"red\" i]", + "address[title=\"foo\"]", + "address[title=\"FoOIgnoRECaSe\" i]", + "address[title!=\"foo\"]", + "address[title!=\"foo\" i]", + "p[title!=\"FooBarUFoo\" i]", + "[ \t title ~= foo ]", + "p[title~=\"FOO\" i]", + "p[title~=toofoo i]", + "[title~=\"hello world\"]", + "[title~=\"hello\" i]", + "[title~=\"hello\" I]", + "[lang|=\"en\"]", + "[lang|=\"EN\" i]", + "[lang|=\"EN\" i]", + "[title^=\"foo\"]", + "[title^=\"foo\" i]", + "[title$=\"bar\"]", + "[title$=\"BAR\" i]", + "[title*=\"bar\"]", + "[title*=\"BaRu\" i]", + "[title*=\"BaRu\" I]", + "p[class$=\" \"]", + "p[class$=\"\"]", + "p[class^=\" \"]", + "p[class^=\"\"]", + "p[class*=\" \"]", + "p[class*=\"\"]", + "input[name=Sex][value=F]", + "table[border=\"0\"][cellpadding=\"0\"][cellspacing=\"0\"]", + ".t1:not(.t2)", + "div:not(.t1)", + "div:not([class=\"t2\"])", + "li:nth-child(odd)", + "li:nth-child(even)", + "li:nth-child(-n+2)", + "li:nth-child(3n+1)", + "li:nth-last-child(odd)", + "li:nth-last-child(even)", + "li:nth-last-child(-n+2)", + "li:nth-last-child(3n+1)", + "span:first-child", + "span:last-child", + "p:nth-of-type(2)", + "p:nth-last-of-type(2)", + "p:last-of-type", + "p:first-of-type", + "p:only-child", + "p:only-of-type", + ":empty", + "div p", + "div table p", + "div > p", + "p ~ p", + "p + p", + "li, p", + "p +/*This is a comment*/ p", + "p:contains(\"that wraps\")", + "p:containsOwn(\"that wraps\")", + ":containsOwn(\"inner\")", + "p:containsOwn(\"block\")", + "div:has(#p1)", + "div:has(:containsOwn(\"2\"))", + "body :has(:containsOwn(\"2\"))", + "body :haschild(:containsOwn(\"2\"))", + "p:matches([\\d])", + "p:matches([a-z])", + "p:matches([a-zA-Z])", + "p:matches([^\\d])", + "p:matches(^(0|a))", + "p:matches(^\\d+$)", + "p:not(:matches(^\\d+$))", + "div :matchesOwn(^\\d+$)", + "[href#=(fina)]:not([href#=(\\/\\/[^\\/]+untrusted)])", + "[href#=(^https:\\/\\/[^\\/]*\\/?news)]", + ":input", + ":root", + "*:root", + "html:nth-child(1)", + "*:root:first-child", + "*:root:nth-child(1)", + "a:not(:root)", + "body > *:nth-child(3n+2)", + "input:disabled", + ":disabled", + ":enabled", + "div.class1, div.class2", + }; + + for (testcases) |tc| { + const s = Parse(alloc, tc, .{}) catch |e| { + std.debug.print("query {s}", .{tc}); + return e; + }; + defer s.deinit(alloc); + } +} diff --git a/src/css/parser.zig b/src/css/parser.zig index d6905bec..6bec4cbd 100644 --- a/src/css/parser.zig +++ b/src/css/parser.zig @@ -50,18 +50,16 @@ pub const ParseOptions = struct { accept_pseudo_elts: bool = true, }; -// Parse parse a selector string and returns the parsed result or an error. -pub fn Parse(alloc: std.mem.Allocator, s: []const u8, opts: ParseOptions) ParseError!Selector { - var p = Parser{ .s = s, .i = 0, .opts = opts }; - return p.parseSelector(alloc); -} - -const Parser = struct { +pub const Parser = struct { s: []const u8, // string to parse i: usize = 0, // current position opts: ParseOptions, + pub fn parse(p: *Parser, alloc: std.mem.Allocator) ParseError!Selector { + return p.parseSelector(alloc); + } + // skipWhitespace consumes whitespace characters and comments. // It returns true if there was actually anything to skip. fn skipWhitespace(p: *Parser) bool { @@ -894,118 +892,3 @@ test "parser.parseString" { }; } } - -test "parser." { - const alloc = std.testing.allocator; - - const testcases = [_][]const u8{ - "address", - "*", - "#foo", - "li#t1", - "*#t4", - ".t1", - "p.t1", - "div.teST", - ".t1.fail", - "p.t1.t2", - "p.--t1", - "p.--t1.--t2", - "p[title]", - "div[class=\"red\" i]", - "address[title=\"foo\"]", - "address[title=\"FoOIgnoRECaSe\" i]", - "address[title!=\"foo\"]", - "address[title!=\"foo\" i]", - "p[title!=\"FooBarUFoo\" i]", - "[ \t title ~= foo ]", - "p[title~=\"FOO\" i]", - "p[title~=toofoo i]", - "[title~=\"hello world\"]", - "[title~=\"hello\" i]", - "[title~=\"hello\" I]", - "[lang|=\"en\"]", - "[lang|=\"EN\" i]", - "[lang|=\"EN\" i]", - "[title^=\"foo\"]", - "[title^=\"foo\" i]", - "[title$=\"bar\"]", - "[title$=\"BAR\" i]", - "[title*=\"bar\"]", - "[title*=\"BaRu\" i]", - "[title*=\"BaRu\" I]", - "p[class$=\" \"]", - "p[class$=\"\"]", - "p[class^=\" \"]", - "p[class^=\"\"]", - "p[class*=\" \"]", - "p[class*=\"\"]", - "input[name=Sex][value=F]", - "table[border=\"0\"][cellpadding=\"0\"][cellspacing=\"0\"]", - ".t1:not(.t2)", - "div:not(.t1)", - "div:not([class=\"t2\"])", - "li:nth-child(odd)", - "li:nth-child(even)", - "li:nth-child(-n+2)", - "li:nth-child(3n+1)", - "li:nth-last-child(odd)", - "li:nth-last-child(even)", - "li:nth-last-child(-n+2)", - "li:nth-last-child(3n+1)", - "span:first-child", - "span:last-child", - "p:nth-of-type(2)", - "p:nth-last-of-type(2)", - "p:last-of-type", - "p:first-of-type", - "p:only-child", - "p:only-of-type", - ":empty", - "div p", - "div table p", - "div > p", - "p ~ p", - "p + p", - "li, p", - "p +/*This is a comment*/ p", - "p:contains(\"that wraps\")", - "p:containsOwn(\"that wraps\")", - ":containsOwn(\"inner\")", - "p:containsOwn(\"block\")", - "div:has(#p1)", - "div:has(:containsOwn(\"2\"))", - "body :has(:containsOwn(\"2\"))", - "body :haschild(:containsOwn(\"2\"))", - "p:matches([\\d])", - "p:matches([a-z])", - "p:matches([a-zA-Z])", - "p:matches([^\\d])", - "p:matches(^(0|a))", - "p:matches(^\\d+$)", - "p:not(:matches(^\\d+$))", - "div :matchesOwn(^\\d+$)", - "[href#=(fina)]:not([href#=(\\/\\/[^\\/]+untrusted)])", - "[href#=(^https:\\/\\/[^\\/]*\\/?news)]", - ":input", - ":root", - "*:root", - "html:nth-child(1)", - "*:root:first-child", - "*:root:nth-child(1)", - "a:not(:root)", - "body > *:nth-child(3n+2)", - "input:disabled", - ":disabled", - ":enabled", - "div.class1, div.class2", - }; - - for (testcases) |tc| { - const s = Parse(alloc, tc, .{}) catch |e| { - std.debug.print("query {s}", .{tc}); - return e; - }; - defer s.deinit(alloc); - } -}