Skip to content

Commit 0972710

Browse files
committed
various fixes according to PR review
1 parent 450e345 commit 0972710

File tree

3 files changed

+650
-161
lines changed

3 files changed

+650
-161
lines changed

src/browser/cssom/css_parser.zig

Lines changed: 112 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,44 @@
1+
// Copyright (C) 2023-2024 Lightpanda (Selecy SAS)
2+
//
3+
// Francis Bouvier <[email protected]>
4+
// Pierre Tachoire <[email protected]>
5+
//
6+
// This program is free software: you can redistribute it and/or modify
7+
// it under the terms of the GNU Affero General Public License as
8+
// published by the Free Software Foundation, either version 3 of the
9+
// License, or (at your option) any later version.
10+
//
11+
// This program is distributed in the hope that it will be useful,
12+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
// GNU Affero General Public License for more details.
15+
//
16+
// You should have received a copy of the GNU Affero General Public License
17+
// along with this program. If not, see <https://www.gnu.org/licenses/>.
18+
119
const std = @import("std");
220

321
const CSSConstants = struct {
4-
const IMPORTANT_KEYWORD = "!important";
5-
const IMPORTANT_LENGTH = IMPORTANT_KEYWORD.len;
22+
const IMPORTANT = "!important";
623
const URL_PREFIX = "url(";
7-
const URL_PREFIX_LENGTH = URL_PREFIX.len;
824
};
925

1026
pub const CSSParserState = enum {
11-
seekName,
12-
inName,
13-
seekColon,
14-
seekValue,
15-
inValue,
16-
inQuotedValue,
17-
inSingleQuotedValue,
18-
inUrl,
19-
inImportant,
27+
seek_name,
28+
in_name,
29+
seek_colon,
30+
seek_value,
31+
in_value,
32+
in_quoted_value,
33+
in_single_quoted_value,
34+
in_url,
35+
in_important,
2036
};
2137

2238
pub const CSSDeclaration = struct {
2339
name: []const u8,
2440
value: []const u8,
2541
is_important: bool,
26-
27-
pub fn init(name: []const u8, value: []const u8, is_important: bool) CSSDeclaration {
28-
return .{
29-
.name = name,
30-
.value = value,
31-
.is_important = is_important,
32-
};
33-
}
3442
};
3543

3644
pub const CSSParser = struct {
@@ -44,7 +52,7 @@ pub const CSSParser = struct {
4452

4553
pub fn init() CSSParser {
4654
return .{
47-
.state = .seekName,
55+
.state = .seek_name,
4856
.name_start = 0,
4957
.name_end = 0,
5058
.value_start = 0,
@@ -54,59 +62,56 @@ pub const CSSParser = struct {
5462
};
5563
}
5664

57-
pub fn parseDeclarations(text: []const u8, allocator: std.mem.Allocator) ![]CSSDeclaration {
65+
pub fn parseDeclarations(allocator: std.mem.Allocator, text: []const u8) ![]CSSDeclaration {
5866
var parser = init();
59-
var declarations = std.ArrayList(CSSDeclaration).init(allocator);
60-
errdefer declarations.deinit();
67+
var declarations = std.ArrayListUnmanaged(CSSDeclaration){};
6168

6269
while (parser.position < text.len) {
6370
const c = text[parser.position];
6471

6572
switch (parser.state) {
66-
.seekName => {
73+
.seek_name => {
6774
if (!std.ascii.isWhitespace(c)) {
6875
parser.name_start = parser.position;
69-
parser.state = .inName;
76+
parser.state = .in_name;
7077
continue;
7178
}
7279
},
73-
.inName => {
80+
.in_name => {
7481
if (c == ':') {
7582
parser.name_end = parser.position;
76-
parser.state = .seekValue;
83+
parser.state = .seek_value;
7784
} else if (std.ascii.isWhitespace(c)) {
7885
parser.name_end = parser.position;
79-
parser.state = .seekColon;
86+
parser.state = .seek_colon;
8087
}
8188
},
82-
.seekColon => {
89+
.seek_colon => {
8390
if (c == ':') {
84-
parser.state = .seekValue;
91+
parser.state = .seek_value;
8592
} else if (!std.ascii.isWhitespace(c)) {
86-
parser.state = .seekName;
93+
parser.state = .seek_name;
8794
continue;
8895
}
8996
},
90-
.seekValue => {
97+
.seek_value => {
9198
if (!std.ascii.isWhitespace(c)) {
9299
parser.value_start = parser.position;
93100
if (c == '"') {
94-
parser.state = .inQuotedValue;
101+
parser.state = .in_quoted_value;
95102
} else if (c == '\'') {
96-
parser.state = .inSingleQuotedValue;
97-
} else if (c == 'u' and parser.position + 3 < text.len and
98-
std.mem.eql(u8, text[parser.position .. parser.position + 4], CSSConstants.URL_PREFIX))
99-
{
100-
parser.state = .inUrl;
103+
parser.state = .in_single_quoted_value;
104+
} else if (c == 'u' and parser.position + CSSConstants.URL_PREFIX.len <= text.len and std.mem.startsWith(u8, text[parser.position..], CSSConstants.URL_PREFIX)) {
105+
parser.state = .in_url;
101106
parser.paren_depth = 1;
102107
parser.position += 3;
103108
} else {
104-
parser.state = .inValue;
109+
parser.state = .in_value;
105110
continue;
106111
}
107112
}
108113
},
109-
.inValue => {
114+
.in_value => {
110115
if (parser.escape_next) {
111116
parser.escape_next = false;
112117
} else if (c == '\\') {
@@ -116,29 +121,29 @@ pub const CSSParser = struct {
116121
} else if (c == ')' and parser.paren_depth > 0) {
117122
parser.paren_depth -= 1;
118123
} else if (c == ';' and parser.paren_depth == 0) {
119-
try parser.finishDeclaration(text, &declarations);
120-
parser.state = .seekName;
124+
try parser.finishDeclaration(allocator, &declarations, text);
125+
parser.state = .seek_name;
121126
}
122127
},
123-
.inQuotedValue => {
128+
.in_quoted_value => {
124129
if (parser.escape_next) {
125130
parser.escape_next = false;
126131
} else if (c == '\\') {
127132
parser.escape_next = true;
128133
} else if (c == '"') {
129-
parser.state = .inValue;
134+
parser.state = .in_value;
130135
}
131136
},
132-
.inSingleQuotedValue => {
137+
.in_single_quoted_value => {
133138
if (parser.escape_next) {
134139
parser.escape_next = false;
135140
} else if (c == '\\') {
136141
parser.escape_next = true;
137142
} else if (c == '\'') {
138-
parser.state = .inValue;
143+
parser.state = .in_value;
139144
}
140145
},
141-
.inUrl => {
146+
.in_url => {
142147
if (parser.escape_next) {
143148
parser.escape_next = false;
144149
} else if (c == '\\') {
@@ -148,22 +153,22 @@ pub const CSSParser = struct {
148153
} else if (c == ')') {
149154
parser.paren_depth -= 1;
150155
if (parser.paren_depth == 0) {
151-
parser.state = .inValue;
156+
parser.state = .in_value;
152157
}
153158
}
154159
},
155-
.inImportant => {},
160+
.in_important => {},
156161
}
157162

158163
parser.position += 1;
159164
}
160165

161-
try parser.finalize(text, &declarations);
166+
try parser.finalize(allocator, &declarations, text);
162167

163-
return declarations.toOwnedSlice();
168+
return declarations.items;
164169
}
165170

166-
fn finishDeclaration(self: *CSSParser, text: []const u8, declarations: *std.ArrayList(CSSDeclaration)) !void {
171+
fn finishDeclaration(self: *CSSParser, allocator: std.mem.Allocator, declarations: *std.ArrayListUnmanaged(CSSDeclaration), text: []const u8) !void {
167172
const name = std.mem.trim(u8, text[self.name_start..self.name_end], &std.ascii.whitespace);
168173
if (name.len == 0) return;
169174

@@ -173,17 +178,20 @@ pub const CSSParser = struct {
173178
var final_value = value;
174179
var is_important = false;
175180

176-
if (std.mem.endsWith(u8, value, CSSConstants.IMPORTANT_KEYWORD)) {
181+
if (std.mem.endsWith(u8, value, CSSConstants.IMPORTANT)) {
177182
is_important = true;
178-
final_value = std.mem.trim(u8, value[0 .. value.len - CSSConstants.IMPORTANT_LENGTH], &std.ascii.whitespace);
183+
final_value = std.mem.trim(u8, value[0 .. value.len - CSSConstants.IMPORTANT.len], &std.ascii.whitespace);
179184
}
180185

181-
const declaration = CSSDeclaration.init(name, final_value, is_important);
182-
try declarations.append(declaration);
186+
try declarations.append(allocator, .{
187+
.name = name,
188+
.value = final_value,
189+
.is_important = is_important,
190+
});
183191
}
184192

185-
fn finalize(self: *CSSParser, text: []const u8, declarations: *std.ArrayList(CSSDeclaration)) !void {
186-
if (self.state == .inValue) {
193+
fn finalize(self: *CSSParser, allocator: std.mem.Allocator, declarations: *std.ArrayListUnmanaged(CSSDeclaration), text: []const u8) !void {
194+
if (self.state == .in_value) {
187195
const name = text[self.name_start..self.name_end];
188196
const trimmed_name = std.mem.trim(u8, name, &std.ascii.whitespace);
189197

@@ -193,13 +201,16 @@ pub const CSSParser = struct {
193201

194202
var final_value = value;
195203
var is_important = false;
196-
if (std.mem.endsWith(u8, value, CSSConstants.IMPORTANT_KEYWORD)) {
204+
if (std.mem.endsWith(u8, value, CSSConstants.IMPORTANT)) {
197205
is_important = true;
198-
final_value = std.mem.trim(u8, value[0 .. value.len - CSSConstants.IMPORTANT_LENGTH], &std.ascii.whitespace);
206+
final_value = std.mem.trim(u8, value[0 .. value.len - CSSConstants.IMPORTANT.len], &std.ascii.whitespace);
199207
}
200208

201-
const declaration = CSSDeclaration.init(trimmed_name, final_value, is_important);
202-
try declarations.append(declaration);
209+
try declarations.append(allocator, .{
210+
.name = trimmed_name,
211+
.value = final_value,
212+
.is_important = is_important,
213+
});
203214
}
204215
}
205216
}
@@ -217,92 +228,93 @@ pub const CSSParser = struct {
217228
}
218229
};
219230

220-
const testing = std.testing;
231+
const testing = @import("../../testing.zig");
221232

222233
test "CSSParser - Simple property" {
234+
defer testing.reset();
235+
223236
const text = "color: red;";
224-
const allocator = testing.allocator;
237+
const allocator = testing.arena_allocator;
225238

226-
const declarations = try CSSParser.parseDeclarations(text, allocator);
227-
defer allocator.free(declarations);
239+
const declarations = try CSSParser.parseDeclarations(allocator, text);
228240

229241
try testing.expect(declarations.len == 1);
230-
try testing.expectEqualStrings("color", declarations[0].name);
231-
try testing.expectEqualStrings("red", declarations[0].value);
242+
try testing.expectEqual("color", declarations[0].name);
243+
try testing.expectEqual("red", declarations[0].value);
232244
try testing.expect(!declarations[0].is_important);
233245
}
234246

235247
test "CSSParser - Property with !important" {
248+
defer testing.reset();
236249
const text = "margin: 10px !important;";
237-
const allocator = testing.allocator;
250+
const allocator = testing.arena_allocator;
238251

239-
const declarations = try CSSParser.parseDeclarations(text, allocator);
240-
defer allocator.free(declarations);
252+
const declarations = try CSSParser.parseDeclarations(allocator, text);
241253

242254
try testing.expect(declarations.len == 1);
243-
try testing.expectEqualStrings("margin", declarations[0].name);
244-
try testing.expectEqualStrings("10px", declarations[0].value);
255+
try testing.expectEqual("margin", declarations[0].name);
256+
try testing.expectEqual("10px", declarations[0].value);
245257
try testing.expect(declarations[0].is_important);
246258
}
247259

248260
test "CSSParser - Multiple properties" {
261+
defer testing.reset();
249262
const text = "color: red; font-size: 12px; margin: 5px !important;";
250-
const allocator = testing.allocator;
263+
const allocator = testing.arena_allocator;
251264

252-
const declarations = try CSSParser.parseDeclarations(text, allocator);
253-
defer allocator.free(declarations);
265+
const declarations = try CSSParser.parseDeclarations(allocator, text);
254266

255267
try testing.expect(declarations.len == 3);
256268

257-
try testing.expectEqualStrings("color", declarations[0].name);
258-
try testing.expectEqualStrings("red", declarations[0].value);
269+
try testing.expectEqual("color", declarations[0].name);
270+
try testing.expectEqual("red", declarations[0].value);
259271
try testing.expect(!declarations[0].is_important);
260272

261-
try testing.expectEqualStrings("font-size", declarations[1].name);
262-
try testing.expectEqualStrings("12px", declarations[1].value);
273+
try testing.expectEqual("font-size", declarations[1].name);
274+
try testing.expectEqual("12px", declarations[1].value);
263275
try testing.expect(!declarations[1].is_important);
264276

265-
try testing.expectEqualStrings("margin", declarations[2].name);
266-
try testing.expectEqualStrings("5px", declarations[2].value);
277+
try testing.expectEqual("margin", declarations[2].name);
278+
try testing.expectEqual("5px", declarations[2].value);
267279
try testing.expect(declarations[2].is_important);
268280
}
269281

270282
test "CSSParser - Quoted value with semicolon" {
283+
defer testing.reset();
271284
const text = "content: \"Hello; world!\";";
272-
const allocator = testing.allocator;
285+
const allocator = testing.arena_allocator;
273286

274-
const declarations = try CSSParser.parseDeclarations(text, allocator);
275-
defer allocator.free(declarations);
287+
const declarations = try CSSParser.parseDeclarations(allocator, text);
276288

277289
try testing.expect(declarations.len == 1);
278-
try testing.expectEqualStrings("content", declarations[0].name);
279-
try testing.expectEqualStrings("\"Hello; world!\"", declarations[0].value);
290+
try testing.expectEqual("content", declarations[0].name);
291+
try testing.expectEqual("\"Hello; world!\"", declarations[0].value);
280292
try testing.expect(!declarations[0].is_important);
281293
}
282294

283295
test "CSSParser - URL value" {
296+
defer testing.reset();
284297
const text = "background-image: url(\"test.png\");";
285-
const allocator = testing.allocator;
298+
const allocator = testing.arena_allocator;
286299

287-
const declarations = try CSSParser.parseDeclarations(text, allocator);
288-
defer allocator.free(declarations);
300+
const declarations = try CSSParser.parseDeclarations(allocator, text);
289301

290302
try testing.expect(declarations.len == 1);
291-
try testing.expectEqualStrings("background-image", declarations[0].name);
292-
try testing.expectEqualStrings("url(\"test.png\")", declarations[0].value);
303+
try testing.expectEqual("background-image", declarations[0].name);
304+
try testing.expectEqual("url(\"test.png\")", declarations[0].value);
293305
try testing.expect(!declarations[0].is_important);
294306
}
295307

296308
test "CSSParser - Whitespace handling" {
309+
defer testing.reset();
297310
const text = " color : purple ; margin : 10px ; ";
298-
const allocator = testing.allocator;
311+
const allocator = testing.arena_allocator;
299312

300-
const declarations = try CSSParser.parseDeclarations(text, allocator);
301-
defer allocator.free(declarations);
313+
const declarations = try CSSParser.parseDeclarations(allocator, text);
302314

303315
try testing.expect(declarations.len == 2);
304-
try testing.expectEqualStrings("color", declarations[0].name);
305-
try testing.expectEqualStrings("purple", declarations[0].value);
306-
try testing.expectEqualStrings("margin", declarations[1].name);
307-
try testing.expectEqualStrings("10px", declarations[1].value);
316+
try testing.expectEqual("color", declarations[0].name);
317+
try testing.expectEqual("purple", declarations[0].value);
318+
try testing.expectEqual("margin", declarations[1].name);
319+
try testing.expectEqual("10px", declarations[1].value);
308320
}

0 commit comments

Comments
 (0)