@projectwallace/css-parser 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/arena.d.ts +24 -11
- package/dist/{string-utils-tMt2O9RW.js → css-node-BzCSxoLM.js} +263 -90
- package/dist/css-node.d.ts +11 -4
- package/dist/index.d.ts +6 -3
- package/dist/index.js +6 -5
- package/dist/{lexer-DXablYMZ.js → lexer-CtBKgfVv.js} +30 -0
- package/dist/lexer.d.ts +20 -0
- package/dist/parse-anplusb.d.ts +22 -0
- package/dist/parse-anplusb.js +220 -0
- package/dist/parse-atrule-prelude.d.ts +24 -0
- package/dist/parse-atrule-prelude.js +452 -3
- package/dist/parse-selector.d.ts +30 -0
- package/dist/parse-selector.js +614 -3
- package/dist/parse-utils.d.ts +56 -0
- package/dist/parse-value.d.ts +22 -0
- package/dist/parse-value.js +142 -0
- package/dist/parse.d.ts +34 -2
- package/dist/parse.js +51 -194
- package/dist/string-utils.d.ts +14 -13
- package/dist/tokenize.js +1 -1
- package/dist/walk.d.ts +1 -1
- package/package.json +9 -1
- package/dist/at-rule-prelude-parser-Cj8ecgQp.js +0 -467
- package/dist/at-rule-prelude-parser.d.ts +0 -24
- package/dist/parser.d.ts +0 -34
- package/dist/selector-parser-C-u1epDB.js +0 -370
- package/dist/selector-parser.d.ts +0 -25
- package/dist/value-parser.d.ts +0 -19
package/dist/parse-selector.js
CHANGED
|
@@ -1,6 +1,617 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { L as Lexer, q as TOKEN_COMMA, h as TOKEN_DELIM, y as TOKEN_EOF, l as TOKEN_WHITESPACE, a as TOKEN_FUNCTION, o as TOKEN_COLON, r as TOKEN_LEFT_BRACKET, c as TOKEN_HASH, T as TOKEN_IDENT, s as TOKEN_RIGHT_BRACKET, t as TOKEN_LEFT_PAREN, u as TOKEN_RIGHT_PAREN, d as TOKEN_STRING } from './lexer-CtBKgfVv.js';
|
|
2
|
+
import { T as CSSDataArena, s as NODE_SELECTOR_LIST, C as CSSNode, j as NODE_SELECTOR, $ as CHAR_GREATER_THAN, a0 as CHAR_PLUS, a1 as CHAR_TILDE, a2 as CHAR_PERIOD, a3 as CHAR_ASTERISK, a4 as CHAR_AMPERSAND, a5 as is_whitespace, a6 as is_combinator, u as NODE_SELECTOR_CLASS, w as NODE_SELECTOR_ATTRIBUTE, a7 as skip_whitespace_and_comments_forward, a8 as skip_whitespace_and_comments_backward, a9 as CHAR_EQUALS, aa as CHAR_PIPE, ab as CHAR_CARET, ac as CHAR_DOLLAR, A as ATTR_OPERATOR_NONE, a as ATTR_OPERATOR_EQUAL, b as ATTR_OPERATOR_TILDE_EQUAL, c as ATTR_OPERATOR_PIPE_EQUAL, d as ATTR_OPERATOR_CARET_EQUAL, e as ATTR_OPERATOR_DOLLAR_EQUAL, f as ATTR_OPERATOR_STAR_EQUAL, ad as CHAR_SINGLE_QUOTE, ae as CHAR_DOUBLE_QUOTE, af as CHAR_COLON, y as NODE_SELECTOR_PSEUDO_ELEMENT, x as NODE_SELECTOR_PSEUDO_CLASS, X as is_vendor_prefixed, Y as FLAG_VENDOR_PREFIXED, _ as NODE_SELECTOR_LANG, ag as skip_whitespace_forward, F as NODE_SELECTOR_NTH_OF, t as NODE_SELECTOR_TYPE, v as NODE_SELECTOR_ID, B as NODE_SELECTOR_UNIVERSAL, D as NODE_SELECTOR_NESTING, z as NODE_SELECTOR_COMBINATOR } from './css-node-BzCSxoLM.js';
|
|
3
|
+
import { ANplusBParser } from './parse-anplusb.js';
|
|
3
4
|
|
|
5
|
+
class SelectorParser {
|
|
6
|
+
lexer;
|
|
7
|
+
arena;
|
|
8
|
+
source;
|
|
9
|
+
selector_end;
|
|
10
|
+
constructor(arena, source) {
|
|
11
|
+
this.arena = arena;
|
|
12
|
+
this.source = source;
|
|
13
|
+
this.lexer = new Lexer(source, false);
|
|
14
|
+
this.selector_end = 0;
|
|
15
|
+
}
|
|
16
|
+
// Parse a selector range into selector nodes
|
|
17
|
+
// Always returns a NODE_SELECTOR_LIST with selector components as children
|
|
18
|
+
parse_selector(start, end, line = 1, column = 1, allow_relative = false) {
|
|
19
|
+
this.selector_end = end;
|
|
20
|
+
this.lexer.pos = start;
|
|
21
|
+
this.lexer.line = line;
|
|
22
|
+
this.lexer.column = column;
|
|
23
|
+
return this.parse_selector_list(allow_relative);
|
|
24
|
+
}
|
|
25
|
+
// Parse comma-separated selectors
|
|
26
|
+
parse_selector_list(allow_relative = false) {
|
|
27
|
+
let selectors = [];
|
|
28
|
+
let list_start = this.lexer.pos;
|
|
29
|
+
let list_line = this.lexer.line;
|
|
30
|
+
let list_column = this.lexer.column;
|
|
31
|
+
while (this.lexer.pos < this.selector_end) {
|
|
32
|
+
let selector_start = this.lexer.pos;
|
|
33
|
+
let selector_line = this.lexer.line;
|
|
34
|
+
let selector_column = this.lexer.column;
|
|
35
|
+
let complex_selector = this.parse_complex_selector(allow_relative);
|
|
36
|
+
if (complex_selector !== null) {
|
|
37
|
+
let selector_wrapper = this.arena.create_node();
|
|
38
|
+
this.arena.set_type(selector_wrapper, NODE_SELECTOR);
|
|
39
|
+
this.arena.set_start_offset(selector_wrapper, selector_start);
|
|
40
|
+
this.arena.set_length(selector_wrapper, this.lexer.pos - selector_start);
|
|
41
|
+
this.arena.set_start_line(selector_wrapper, selector_line);
|
|
42
|
+
this.arena.set_start_column(selector_wrapper, selector_column);
|
|
43
|
+
let last_component = complex_selector;
|
|
44
|
+
while (this.arena.get_next_sibling(last_component) !== 0) {
|
|
45
|
+
last_component = this.arena.get_next_sibling(last_component);
|
|
46
|
+
}
|
|
47
|
+
this.arena.set_first_child(selector_wrapper, complex_selector);
|
|
48
|
+
this.arena.set_last_child(selector_wrapper, last_component);
|
|
49
|
+
selectors.push(selector_wrapper);
|
|
50
|
+
}
|
|
51
|
+
this.skip_whitespace();
|
|
52
|
+
if (this.lexer.pos >= this.selector_end) break;
|
|
53
|
+
this.lexer.next_token_fast(false);
|
|
54
|
+
let token_type = this.lexer.token_type;
|
|
55
|
+
if (token_type === TOKEN_COMMA) {
|
|
56
|
+
this.skip_whitespace();
|
|
57
|
+
continue;
|
|
58
|
+
} else {
|
|
59
|
+
break;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
if (selectors.length >= 1) {
|
|
63
|
+
let list_node = this.arena.create_node();
|
|
64
|
+
this.arena.set_type(list_node, NODE_SELECTOR_LIST);
|
|
65
|
+
this.arena.set_start_offset(list_node, list_start);
|
|
66
|
+
this.arena.set_length(list_node, this.lexer.pos - list_start);
|
|
67
|
+
this.arena.set_start_line(list_node, list_line);
|
|
68
|
+
this.arena.set_start_column(list_node, list_column);
|
|
69
|
+
this.arena.set_first_child(list_node, selectors[0]);
|
|
70
|
+
this.arena.set_last_child(list_node, selectors[selectors.length - 1]);
|
|
71
|
+
for (let i = 0; i < selectors.length - 1; i++) {
|
|
72
|
+
this.arena.set_next_sibling(selectors[i], selectors[i + 1]);
|
|
73
|
+
}
|
|
74
|
+
return list_node;
|
|
75
|
+
}
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
// Parse a complex selector (with combinators)
|
|
79
|
+
// e.g., "div.class > p + span"
|
|
80
|
+
parse_complex_selector(allow_relative = false) {
|
|
81
|
+
let components = [];
|
|
82
|
+
this.skip_whitespace();
|
|
83
|
+
if (allow_relative && this.lexer.pos < this.selector_end) {
|
|
84
|
+
const saved = this.lexer.save_position();
|
|
85
|
+
this.lexer.next_token_fast(false);
|
|
86
|
+
let token_type = this.lexer.token_type;
|
|
87
|
+
if (token_type === TOKEN_DELIM) {
|
|
88
|
+
let ch = this.source.charCodeAt(this.lexer.token_start);
|
|
89
|
+
if (ch === CHAR_GREATER_THAN || ch === CHAR_PLUS || ch === CHAR_TILDE) {
|
|
90
|
+
let combinator = this.create_combinator(this.lexer.token_start, this.lexer.token_end);
|
|
91
|
+
components.push(combinator);
|
|
92
|
+
this.skip_whitespace();
|
|
93
|
+
} else {
|
|
94
|
+
this.lexer.restore_position(saved);
|
|
95
|
+
}
|
|
96
|
+
} else {
|
|
97
|
+
this.lexer.restore_position(saved);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
while (this.lexer.pos < this.selector_end) {
|
|
101
|
+
if (this.lexer.pos >= this.selector_end) break;
|
|
102
|
+
let compound = this.parse_compound_selector();
|
|
103
|
+
if (compound !== null) {
|
|
104
|
+
components.push(compound);
|
|
105
|
+
} else {
|
|
106
|
+
break;
|
|
107
|
+
}
|
|
108
|
+
let combinator = this.try_parse_combinator();
|
|
109
|
+
if (combinator !== null) {
|
|
110
|
+
components.push(combinator);
|
|
111
|
+
this.skip_whitespace();
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
const saved = this.lexer.save_position();
|
|
115
|
+
this.skip_whitespace();
|
|
116
|
+
if (this.lexer.pos >= this.selector_end) break;
|
|
117
|
+
this.lexer.next_token_fast(false);
|
|
118
|
+
let token_type = this.lexer.token_type;
|
|
119
|
+
if (token_type === TOKEN_COMMA || this.lexer.pos >= this.selector_end) {
|
|
120
|
+
this.lexer.restore_position(saved);
|
|
121
|
+
break;
|
|
122
|
+
}
|
|
123
|
+
this.lexer.restore_position(saved);
|
|
124
|
+
break;
|
|
125
|
+
}
|
|
126
|
+
if (components.length === 0) return null;
|
|
127
|
+
for (let i = 0; i < components.length - 1; i++) {
|
|
128
|
+
let last_node = components[i];
|
|
129
|
+
while (this.arena.get_next_sibling(last_node) !== 0) {
|
|
130
|
+
last_node = this.arena.get_next_sibling(last_node);
|
|
131
|
+
}
|
|
132
|
+
this.arena.set_next_sibling(last_node, components[i + 1]);
|
|
133
|
+
}
|
|
134
|
+
return components[0];
|
|
135
|
+
}
|
|
136
|
+
// Parse a compound selector (no combinators)
|
|
137
|
+
// e.g., "div.class#id[attr]:hover"
|
|
138
|
+
parse_compound_selector() {
|
|
139
|
+
let parts = [];
|
|
140
|
+
while (this.lexer.pos < this.selector_end) {
|
|
141
|
+
const saved = this.lexer.save_position();
|
|
142
|
+
this.lexer.next_token_fast(false);
|
|
143
|
+
if (this.lexer.token_start >= this.selector_end) break;
|
|
144
|
+
let token_type = this.lexer.token_type;
|
|
145
|
+
if (token_type === TOKEN_EOF) break;
|
|
146
|
+
let part = this.parse_simple_selector();
|
|
147
|
+
if (part !== null) {
|
|
148
|
+
parts.push(part);
|
|
149
|
+
} else {
|
|
150
|
+
this.lexer.restore_position(saved);
|
|
151
|
+
break;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
if (parts.length === 0) return null;
|
|
155
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
156
|
+
this.arena.set_next_sibling(parts[i], parts[i + 1]);
|
|
157
|
+
}
|
|
158
|
+
return parts[0];
|
|
159
|
+
}
|
|
160
|
+
// Parse a simple selector (single component)
|
|
161
|
+
parse_simple_selector() {
|
|
162
|
+
let token_type = this.lexer.token_type;
|
|
163
|
+
let start = this.lexer.token_start;
|
|
164
|
+
let end = this.lexer.token_end;
|
|
165
|
+
switch (token_type) {
|
|
166
|
+
case TOKEN_IDENT:
|
|
167
|
+
return this.create_type_selector(start, end);
|
|
168
|
+
case TOKEN_HASH:
|
|
169
|
+
return this.create_id_selector(start, end);
|
|
170
|
+
case TOKEN_DELIM:
|
|
171
|
+
let ch = this.source.charCodeAt(start);
|
|
172
|
+
if (ch === CHAR_PERIOD) {
|
|
173
|
+
return this.parse_class_selector(start);
|
|
174
|
+
} else if (ch === CHAR_ASTERISK) {
|
|
175
|
+
return this.create_universal_selector(start, end);
|
|
176
|
+
} else if (ch === CHAR_AMPERSAND) {
|
|
177
|
+
return this.create_nesting_selector(start, end);
|
|
178
|
+
}
|
|
179
|
+
return null;
|
|
180
|
+
case TOKEN_LEFT_BRACKET:
|
|
181
|
+
return this.parse_attribute_selector(start);
|
|
182
|
+
case TOKEN_COLON:
|
|
183
|
+
return this.parse_pseudo(start);
|
|
184
|
+
case TOKEN_FUNCTION:
|
|
185
|
+
return this.parse_pseudo_function(start, end);
|
|
186
|
+
case TOKEN_WHITESPACE:
|
|
187
|
+
case TOKEN_COMMA:
|
|
188
|
+
return null;
|
|
189
|
+
default:
|
|
190
|
+
return null;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
// Parse combinator (>, +, ~, or descendant space)
|
|
194
|
+
try_parse_combinator() {
|
|
195
|
+
let whitespace_start = this.lexer.pos;
|
|
196
|
+
let has_whitespace = false;
|
|
197
|
+
while (this.lexer.pos < this.selector_end) {
|
|
198
|
+
let ch = this.source.charCodeAt(this.lexer.pos);
|
|
199
|
+
if (is_whitespace(ch)) {
|
|
200
|
+
has_whitespace = true;
|
|
201
|
+
this.lexer.pos++;
|
|
202
|
+
} else {
|
|
203
|
+
break;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
if (this.lexer.pos >= this.selector_end) return null;
|
|
207
|
+
this.lexer.next_token_fast(false);
|
|
208
|
+
if (this.lexer.token_type === TOKEN_DELIM) {
|
|
209
|
+
let ch = this.source.charCodeAt(this.lexer.token_start);
|
|
210
|
+
if (is_combinator(ch)) {
|
|
211
|
+
return this.create_combinator(this.lexer.token_start, this.lexer.token_end);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
if (has_whitespace) {
|
|
215
|
+
this.lexer.pos = whitespace_start;
|
|
216
|
+
while (this.lexer.pos < this.selector_end) {
|
|
217
|
+
let ch = this.source.charCodeAt(this.lexer.pos);
|
|
218
|
+
if (is_whitespace(ch)) {
|
|
219
|
+
this.lexer.pos++;
|
|
220
|
+
} else {
|
|
221
|
+
break;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
return this.create_combinator(whitespace_start, this.lexer.pos);
|
|
225
|
+
}
|
|
226
|
+
this.lexer.pos = whitespace_start;
|
|
227
|
+
return null;
|
|
228
|
+
}
|
|
229
|
+
// Parse class selector (.classname)
|
|
230
|
+
parse_class_selector(dot_pos) {
|
|
231
|
+
const saved = this.lexer.save_position();
|
|
232
|
+
this.lexer.next_token_fast(false);
|
|
233
|
+
if (this.lexer.token_type !== TOKEN_IDENT) {
|
|
234
|
+
this.lexer.restore_position(saved);
|
|
235
|
+
return null;
|
|
236
|
+
}
|
|
237
|
+
let node = this.arena.create_node();
|
|
238
|
+
this.arena.set_type(node, NODE_SELECTOR_CLASS);
|
|
239
|
+
this.arena.set_start_offset(node, dot_pos);
|
|
240
|
+
this.arena.set_length(node, this.lexer.token_end - dot_pos);
|
|
241
|
+
this.arena.set_start_line(node, this.lexer.line);
|
|
242
|
+
this.arena.set_start_column(node, this.lexer.column);
|
|
243
|
+
this.arena.set_content_start(node, this.lexer.token_start);
|
|
244
|
+
this.arena.set_content_length(node, this.lexer.token_end - this.lexer.token_start);
|
|
245
|
+
return node;
|
|
246
|
+
}
|
|
247
|
+
// Parse attribute selector ([attr], [attr=value], etc.)
|
|
248
|
+
parse_attribute_selector(start) {
|
|
249
|
+
let bracket_depth = 1;
|
|
250
|
+
let end = this.lexer.token_end;
|
|
251
|
+
let content_start = start + 1;
|
|
252
|
+
let content_end = content_start;
|
|
253
|
+
while (this.lexer.pos < this.selector_end && bracket_depth > 0) {
|
|
254
|
+
this.lexer.next_token_fast(false);
|
|
255
|
+
let token_type = this.lexer.token_type;
|
|
256
|
+
if (token_type === TOKEN_LEFT_BRACKET) {
|
|
257
|
+
bracket_depth++;
|
|
258
|
+
} else if (token_type === TOKEN_RIGHT_BRACKET) {
|
|
259
|
+
bracket_depth--;
|
|
260
|
+
if (bracket_depth === 0) {
|
|
261
|
+
content_end = this.lexer.token_start;
|
|
262
|
+
end = this.lexer.token_end;
|
|
263
|
+
break;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
let node = this.arena.create_node();
|
|
268
|
+
this.arena.set_type(node, NODE_SELECTOR_ATTRIBUTE);
|
|
269
|
+
this.arena.set_start_offset(node, start);
|
|
270
|
+
this.arena.set_length(node, end - start);
|
|
271
|
+
this.arena.set_start_line(node, this.lexer.line);
|
|
272
|
+
this.arena.set_start_column(node, this.lexer.column);
|
|
273
|
+
this.parse_attribute_content(node, content_start, content_end);
|
|
274
|
+
return node;
|
|
275
|
+
}
|
|
276
|
+
// Parse attribute content to extract name, operator, and value
|
|
277
|
+
parse_attribute_content(node, start, end) {
|
|
278
|
+
start = skip_whitespace_and_comments_forward(this.source, start, end);
|
|
279
|
+
end = skip_whitespace_and_comments_backward(this.source, end, start);
|
|
280
|
+
if (start >= end) return;
|
|
281
|
+
let name_start = start;
|
|
282
|
+
let name_end = start;
|
|
283
|
+
let operator_end = -1;
|
|
284
|
+
let value_start = -1;
|
|
285
|
+
let value_end = -1;
|
|
286
|
+
while (name_end < end) {
|
|
287
|
+
let ch2 = this.source.charCodeAt(name_end);
|
|
288
|
+
if (is_whitespace(ch2) || ch2 === CHAR_EQUALS || ch2 === CHAR_TILDE || ch2 === CHAR_PIPE || ch2 === CHAR_CARET || ch2 === CHAR_DOLLAR || ch2 === CHAR_ASTERISK) {
|
|
289
|
+
break;
|
|
290
|
+
}
|
|
291
|
+
name_end++;
|
|
292
|
+
}
|
|
293
|
+
if (name_end > name_start) {
|
|
294
|
+
this.arena.set_content_start(node, name_start);
|
|
295
|
+
this.arena.set_content_length(node, name_end - name_start);
|
|
296
|
+
}
|
|
297
|
+
let pos = skip_whitespace_and_comments_forward(this.source, name_end, end);
|
|
298
|
+
if (pos >= end) {
|
|
299
|
+
this.arena.set_attr_operator(node, ATTR_OPERATOR_NONE);
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
let ch1 = this.source.charCodeAt(pos);
|
|
303
|
+
if (ch1 === CHAR_EQUALS) {
|
|
304
|
+
operator_end = pos + 1;
|
|
305
|
+
this.arena.set_attr_operator(node, ATTR_OPERATOR_EQUAL);
|
|
306
|
+
} else if (ch1 === CHAR_TILDE && pos + 1 < end && this.source.charCodeAt(pos + 1) === CHAR_EQUALS) {
|
|
307
|
+
operator_end = pos + 2;
|
|
308
|
+
this.arena.set_attr_operator(node, ATTR_OPERATOR_TILDE_EQUAL);
|
|
309
|
+
} else if (ch1 === CHAR_PIPE && pos + 1 < end && this.source.charCodeAt(pos + 1) === CHAR_EQUALS) {
|
|
310
|
+
operator_end = pos + 2;
|
|
311
|
+
this.arena.set_attr_operator(node, ATTR_OPERATOR_PIPE_EQUAL);
|
|
312
|
+
} else if (ch1 === CHAR_CARET && pos + 1 < end && this.source.charCodeAt(pos + 1) === CHAR_EQUALS) {
|
|
313
|
+
operator_end = pos + 2;
|
|
314
|
+
this.arena.set_attr_operator(node, ATTR_OPERATOR_CARET_EQUAL);
|
|
315
|
+
} else if (ch1 === CHAR_DOLLAR && pos + 1 < end && this.source.charCodeAt(pos + 1) === CHAR_EQUALS) {
|
|
316
|
+
operator_end = pos + 2;
|
|
317
|
+
this.arena.set_attr_operator(node, ATTR_OPERATOR_DOLLAR_EQUAL);
|
|
318
|
+
} else if (ch1 === CHAR_ASTERISK && pos + 1 < end && this.source.charCodeAt(pos + 1) === CHAR_EQUALS) {
|
|
319
|
+
operator_end = pos + 2;
|
|
320
|
+
this.arena.set_attr_operator(node, ATTR_OPERATOR_STAR_EQUAL);
|
|
321
|
+
} else {
|
|
322
|
+
this.arena.set_attr_operator(node, ATTR_OPERATOR_NONE);
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
pos = skip_whitespace_and_comments_forward(this.source, operator_end, end);
|
|
326
|
+
if (pos >= end) {
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
value_start = pos;
|
|
330
|
+
let ch = this.source.charCodeAt(pos);
|
|
331
|
+
if (ch === CHAR_SINGLE_QUOTE || ch === CHAR_DOUBLE_QUOTE) {
|
|
332
|
+
let quote = ch;
|
|
333
|
+
value_start = pos;
|
|
334
|
+
pos++;
|
|
335
|
+
while (pos < end) {
|
|
336
|
+
let c = this.source.charCodeAt(pos);
|
|
337
|
+
if (c === quote) {
|
|
338
|
+
pos++;
|
|
339
|
+
break;
|
|
340
|
+
}
|
|
341
|
+
if (c === 92) {
|
|
342
|
+
pos += 2;
|
|
343
|
+
} else {
|
|
344
|
+
pos++;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
value_end = pos;
|
|
348
|
+
} else {
|
|
349
|
+
while (pos < end) {
|
|
350
|
+
let c = this.source.charCodeAt(pos);
|
|
351
|
+
if (is_whitespace(c)) {
|
|
352
|
+
break;
|
|
353
|
+
}
|
|
354
|
+
pos++;
|
|
355
|
+
}
|
|
356
|
+
value_end = pos;
|
|
357
|
+
}
|
|
358
|
+
if (value_end > value_start) {
|
|
359
|
+
this.arena.set_value_start(node, value_start);
|
|
360
|
+
this.arena.set_value_length(node, value_end - value_start);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
// Parse pseudo-class or pseudo-element (:hover, ::before)
|
|
364
|
+
parse_pseudo(start) {
|
|
365
|
+
const saved = this.lexer.save_position();
|
|
366
|
+
let is_pseudo_element = false;
|
|
367
|
+
if (this.lexer.pos < this.selector_end && this.source.charCodeAt(this.lexer.pos) === CHAR_COLON) {
|
|
368
|
+
is_pseudo_element = true;
|
|
369
|
+
this.lexer.pos++;
|
|
370
|
+
}
|
|
371
|
+
this.lexer.next_token_fast(false);
|
|
372
|
+
let token_type = this.lexer.token_type;
|
|
373
|
+
if (token_type === TOKEN_IDENT) {
|
|
374
|
+
let node = this.arena.create_node();
|
|
375
|
+
this.arena.set_type(node, is_pseudo_element ? NODE_SELECTOR_PSEUDO_ELEMENT : NODE_SELECTOR_PSEUDO_CLASS);
|
|
376
|
+
this.arena.set_start_offset(node, start);
|
|
377
|
+
this.arena.set_length(node, this.lexer.token_end - start);
|
|
378
|
+
this.arena.set_start_line(node, this.lexer.line);
|
|
379
|
+
this.arena.set_start_column(node, this.lexer.column);
|
|
380
|
+
this.arena.set_content_start(node, this.lexer.token_start);
|
|
381
|
+
this.arena.set_content_length(node, this.lexer.token_end - this.lexer.token_start);
|
|
382
|
+
if (is_vendor_prefixed(this.source, this.lexer.token_start, this.lexer.token_end)) {
|
|
383
|
+
this.arena.set_flag(node, FLAG_VENDOR_PREFIXED);
|
|
384
|
+
}
|
|
385
|
+
return node;
|
|
386
|
+
} else if (token_type === TOKEN_FUNCTION) {
|
|
387
|
+
return this.parse_pseudo_function_after_colon(start, is_pseudo_element);
|
|
388
|
+
}
|
|
389
|
+
this.lexer.restore_position(saved);
|
|
390
|
+
return null;
|
|
391
|
+
}
|
|
392
|
+
// Parse pseudo-class function (:nth-child(), :is(), etc.)
|
|
393
|
+
parse_pseudo_function(_start, _end) {
|
|
394
|
+
return null;
|
|
395
|
+
}
|
|
396
|
+
// Parse pseudo-class function after we've seen the colon
|
|
397
|
+
parse_pseudo_function_after_colon(start, is_pseudo_element) {
|
|
398
|
+
let func_name_start = this.lexer.token_start;
|
|
399
|
+
let func_name_end = this.lexer.token_end - 1;
|
|
400
|
+
let content_start = this.lexer.pos;
|
|
401
|
+
let content_end = content_start;
|
|
402
|
+
let paren_depth = 1;
|
|
403
|
+
let end = this.lexer.token_end;
|
|
404
|
+
while (this.lexer.pos < this.selector_end && paren_depth > 0) {
|
|
405
|
+
this.lexer.next_token_fast(false);
|
|
406
|
+
let token_type = this.lexer.token_type;
|
|
407
|
+
if (token_type === TOKEN_LEFT_PAREN || token_type === TOKEN_FUNCTION) {
|
|
408
|
+
paren_depth++;
|
|
409
|
+
} else if (token_type === TOKEN_RIGHT_PAREN) {
|
|
410
|
+
paren_depth--;
|
|
411
|
+
if (paren_depth === 0) {
|
|
412
|
+
content_end = this.lexer.token_start;
|
|
413
|
+
end = this.lexer.token_end;
|
|
414
|
+
break;
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
let node = this.arena.create_node();
|
|
419
|
+
this.arena.set_type(node, is_pseudo_element ? NODE_SELECTOR_PSEUDO_ELEMENT : NODE_SELECTOR_PSEUDO_CLASS);
|
|
420
|
+
this.arena.set_start_offset(node, start);
|
|
421
|
+
this.arena.set_length(node, end - start);
|
|
422
|
+
this.arena.set_start_line(node, this.lexer.line);
|
|
423
|
+
this.arena.set_start_column(node, this.lexer.column);
|
|
424
|
+
this.arena.set_content_start(node, func_name_start);
|
|
425
|
+
this.arena.set_content_length(node, func_name_end - func_name_start);
|
|
426
|
+
if (is_vendor_prefixed(this.source, func_name_start, func_name_end)) {
|
|
427
|
+
this.arena.set_flag(node, FLAG_VENDOR_PREFIXED);
|
|
428
|
+
}
|
|
429
|
+
if (content_end > content_start) {
|
|
430
|
+
let func_name = this.source.substring(func_name_start, func_name_end).toLowerCase();
|
|
431
|
+
if (this.is_nth_pseudo(func_name)) {
|
|
432
|
+
let child = this.parse_nth_expression(content_start, content_end);
|
|
433
|
+
if (child !== null) {
|
|
434
|
+
this.arena.set_first_child(node, child);
|
|
435
|
+
this.arena.set_last_child(node, child);
|
|
436
|
+
}
|
|
437
|
+
} else if (func_name === "lang") {
|
|
438
|
+
this.parse_lang_identifiers(content_start, content_end, node);
|
|
439
|
+
} else {
|
|
440
|
+
let saved_selector_end = this.selector_end;
|
|
441
|
+
const saved = this.lexer.save_position();
|
|
442
|
+
let allow_relative = func_name === "has";
|
|
443
|
+
let child_selector = this.parse_selector(content_start, content_end, this.lexer.line, this.lexer.column, allow_relative);
|
|
444
|
+
this.selector_end = saved_selector_end;
|
|
445
|
+
this.lexer.restore_position(saved);
|
|
446
|
+
if (child_selector !== null) {
|
|
447
|
+
this.arena.set_first_child(node, child_selector);
|
|
448
|
+
this.arena.set_last_child(node, child_selector);
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
return node;
|
|
453
|
+
}
|
|
454
|
+
// Check if pseudo-class name is an nth-* pseudo
|
|
455
|
+
is_nth_pseudo(name) {
|
|
456
|
+
return name === "nth-child" || name === "nth-last-child" || name === "nth-of-type" || name === "nth-last-of-type" || name === "nth-col" || name === "nth-last-col";
|
|
457
|
+
}
|
|
458
|
+
// Parse :lang() content - comma-separated language identifiers
|
|
459
|
+
// Accepts both quoted strings: :lang("en", "fr") and unquoted: :lang(en, fr)
|
|
460
|
+
parse_lang_identifiers(start, end, parent_node) {
|
|
461
|
+
let saved_selector_end = this.selector_end;
|
|
462
|
+
const saved = this.lexer.save_position();
|
|
463
|
+
this.lexer.pos = start;
|
|
464
|
+
this.selector_end = end;
|
|
465
|
+
let first_child = null;
|
|
466
|
+
let last_child = null;
|
|
467
|
+
while (this.lexer.pos < end) {
|
|
468
|
+
this.lexer.next_token_fast(false);
|
|
469
|
+
let token_type = this.lexer.token_type;
|
|
470
|
+
let token_start = this.lexer.token_start;
|
|
471
|
+
let token_end = this.lexer.token_end;
|
|
472
|
+
if (token_type === TOKEN_WHITESPACE) {
|
|
473
|
+
continue;
|
|
474
|
+
}
|
|
475
|
+
if (token_type === TOKEN_COMMA) {
|
|
476
|
+
continue;
|
|
477
|
+
}
|
|
478
|
+
if (token_type === TOKEN_STRING || token_type === TOKEN_IDENT) {
|
|
479
|
+
let lang_node = this.arena.create_node();
|
|
480
|
+
this.arena.set_type(lang_node, NODE_SELECTOR_LANG);
|
|
481
|
+
this.arena.set_start_offset(lang_node, token_start);
|
|
482
|
+
this.arena.set_length(lang_node, token_end - token_start);
|
|
483
|
+
this.arena.set_start_line(lang_node, this.lexer.line);
|
|
484
|
+
this.arena.set_start_column(lang_node, this.lexer.column);
|
|
485
|
+
if (first_child === null) {
|
|
486
|
+
first_child = lang_node;
|
|
487
|
+
}
|
|
488
|
+
if (last_child !== null) {
|
|
489
|
+
this.arena.set_next_sibling(last_child, lang_node);
|
|
490
|
+
}
|
|
491
|
+
last_child = lang_node;
|
|
492
|
+
}
|
|
493
|
+
if (this.lexer.pos >= end) {
|
|
494
|
+
break;
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
if (first_child !== null) {
|
|
498
|
+
this.arena.set_first_child(parent_node, first_child);
|
|
499
|
+
}
|
|
500
|
+
if (last_child !== null) {
|
|
501
|
+
this.arena.set_last_child(parent_node, last_child);
|
|
502
|
+
}
|
|
503
|
+
this.selector_end = saved_selector_end;
|
|
504
|
+
this.lexer.restore_position(saved);
|
|
505
|
+
}
|
|
506
|
+
// Parse An+B expression for nth-* pseudo-classes
|
|
507
|
+
// Handles both simple An+B and "An+B of S" syntax
|
|
508
|
+
parse_nth_expression(start, end) {
|
|
509
|
+
let of_index = this.find_of_keyword(start, end);
|
|
510
|
+
if (of_index !== -1) {
|
|
511
|
+
let anplusb_parser = new ANplusBParser(this.arena, this.source);
|
|
512
|
+
let anplusb_node = anplusb_parser.parse_anplusb(start, of_index, this.lexer.line);
|
|
513
|
+
let selector_start = of_index + 2;
|
|
514
|
+
selector_start = skip_whitespace_forward(this.source, selector_start, end);
|
|
515
|
+
let saved_selector_end = this.selector_end;
|
|
516
|
+
const saved = this.lexer.save_position();
|
|
517
|
+
this.selector_end = end;
|
|
518
|
+
this.lexer.pos = selector_start;
|
|
519
|
+
let selector_list = this.parse_selector_list();
|
|
520
|
+
this.selector_end = saved_selector_end;
|
|
521
|
+
this.lexer.restore_position(saved);
|
|
522
|
+
let of_node = this.arena.create_node();
|
|
523
|
+
this.arena.set_type(of_node, NODE_SELECTOR_NTH_OF);
|
|
524
|
+
this.arena.set_start_offset(of_node, start);
|
|
525
|
+
this.arena.set_length(of_node, end - start);
|
|
526
|
+
this.arena.set_start_line(of_node, this.lexer.line);
|
|
527
|
+
if (anplusb_node !== null && selector_list !== null) {
|
|
528
|
+
this.arena.set_first_child(of_node, anplusb_node);
|
|
529
|
+
this.arena.set_last_child(of_node, selector_list);
|
|
530
|
+
this.arena.set_next_sibling(anplusb_node, selector_list);
|
|
531
|
+
} else if (anplusb_node !== null) {
|
|
532
|
+
this.arena.set_first_child(of_node, anplusb_node);
|
|
533
|
+
this.arena.set_last_child(of_node, anplusb_node);
|
|
534
|
+
}
|
|
535
|
+
return of_node;
|
|
536
|
+
} else {
|
|
537
|
+
let anplusb_parser = new ANplusBParser(this.arena, this.source);
|
|
538
|
+
return anplusb_parser.parse_anplusb(start, end, this.lexer.line);
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
// Find the position of standalone "of" keyword
|
|
542
|
+
find_of_keyword(start, end) {
|
|
543
|
+
for (let i = start; i < end - 1; i++) {
|
|
544
|
+
if (this.source.charCodeAt(i) === 111 && this.source.charCodeAt(i + 1) === 102) {
|
|
545
|
+
let before_ok = i === start || is_whitespace(this.source.charCodeAt(i - 1));
|
|
546
|
+
let after_ok = i + 2 >= end || is_whitespace(this.source.charCodeAt(i + 2));
|
|
547
|
+
if (before_ok && after_ok) {
|
|
548
|
+
return i;
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
return -1;
|
|
553
|
+
}
|
|
554
|
+
// Create simple selector nodes
|
|
555
|
+
create_type_selector(start, end) {
|
|
556
|
+
let node = this.arena.create_node();
|
|
557
|
+
this.arena.set_type(node, NODE_SELECTOR_TYPE);
|
|
558
|
+
this.arena.set_start_offset(node, start);
|
|
559
|
+
this.arena.set_length(node, end - start);
|
|
560
|
+
this.arena.set_start_line(node, this.lexer.line);
|
|
561
|
+
this.arena.set_start_column(node, this.lexer.column);
|
|
562
|
+
this.arena.set_content_start(node, start);
|
|
563
|
+
this.arena.set_content_length(node, end - start);
|
|
564
|
+
return node;
|
|
565
|
+
}
|
|
566
|
+
create_id_selector(start, end) {
|
|
567
|
+
let node = this.arena.create_node();
|
|
568
|
+
this.arena.set_type(node, NODE_SELECTOR_ID);
|
|
569
|
+
this.arena.set_start_offset(node, start);
|
|
570
|
+
this.arena.set_length(node, end - start);
|
|
571
|
+
this.arena.set_start_line(node, this.lexer.line);
|
|
572
|
+
this.arena.set_start_column(node, this.lexer.column);
|
|
573
|
+
this.arena.set_content_start(node, start + 1);
|
|
574
|
+
this.arena.set_content_length(node, end - start - 1);
|
|
575
|
+
return node;
|
|
576
|
+
}
|
|
577
|
+
create_universal_selector(start, end) {
|
|
578
|
+
let node = this.arena.create_node();
|
|
579
|
+
this.arena.set_type(node, NODE_SELECTOR_UNIVERSAL);
|
|
580
|
+
this.arena.set_start_offset(node, start);
|
|
581
|
+
this.arena.set_length(node, end - start);
|
|
582
|
+
this.arena.set_start_line(node, this.lexer.line);
|
|
583
|
+
this.arena.set_start_column(node, this.lexer.column);
|
|
584
|
+
this.arena.set_content_start(node, start);
|
|
585
|
+
this.arena.set_content_length(node, end - start);
|
|
586
|
+
return node;
|
|
587
|
+
}
|
|
588
|
+
create_nesting_selector(start, end) {
|
|
589
|
+
let node = this.arena.create_node();
|
|
590
|
+
this.arena.set_type(node, NODE_SELECTOR_NESTING);
|
|
591
|
+
this.arena.set_start_offset(node, start);
|
|
592
|
+
this.arena.set_length(node, end - start);
|
|
593
|
+
this.arena.set_start_line(node, this.lexer.line);
|
|
594
|
+
this.arena.set_start_column(node, this.lexer.column);
|
|
595
|
+
this.arena.set_content_start(node, start);
|
|
596
|
+
this.arena.set_content_length(node, end - start);
|
|
597
|
+
return node;
|
|
598
|
+
}
|
|
599
|
+
create_combinator(start, end) {
|
|
600
|
+
let node = this.arena.create_node();
|
|
601
|
+
this.arena.set_type(node, NODE_SELECTOR_COMBINATOR);
|
|
602
|
+
this.arena.set_start_offset(node, start);
|
|
603
|
+
this.arena.set_length(node, end - start);
|
|
604
|
+
this.arena.set_start_line(node, this.lexer.line);
|
|
605
|
+
this.arena.set_start_column(node, this.lexer.column);
|
|
606
|
+
this.arena.set_content_start(node, start);
|
|
607
|
+
this.arena.set_content_length(node, end - start);
|
|
608
|
+
return node;
|
|
609
|
+
}
|
|
610
|
+
// Helper to skip whitespace
|
|
611
|
+
skip_whitespace() {
|
|
612
|
+
this.lexer.pos = skip_whitespace_forward(this.source, this.lexer.pos, this.selector_end);
|
|
613
|
+
}
|
|
614
|
+
}
|
|
4
615
|
function parse_selector(source) {
|
|
5
616
|
const arena = new CSSDataArena(CSSDataArena.capacity_for_source(source.length));
|
|
6
617
|
const selector_parser = new SelectorParser(arena, source);
|
|
@@ -16,4 +627,4 @@ function parse_selector(source) {
|
|
|
16
627
|
return new CSSNode(arena, source, selector_index);
|
|
17
628
|
}
|
|
18
629
|
|
|
19
|
-
export { parse_selector };
|
|
630
|
+
export { SelectorParser, parse_selector };
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parse a dimension string into numeric value and unit
|
|
3
|
+
*
|
|
4
|
+
* @param text - Dimension text like "100px", "50%", "1.5em"
|
|
5
|
+
* @returns Object with value (number) and unit (string)
|
|
6
|
+
*
|
|
7
|
+
* Examples:
|
|
8
|
+
* - "100px" → { value: 100, unit: "px" }
|
|
9
|
+
* - "50%" → { value: 50, unit: "%" }
|
|
10
|
+
* - "1.5em" → { value: 1.5, unit: "em" }
|
|
11
|
+
* - "-10rem" → { value: -10, unit: "rem" }
|
|
12
|
+
*/
|
|
13
|
+
export declare function parse_dimension(text: string): {
|
|
14
|
+
value: number;
|
|
15
|
+
unit: string;
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Skip whitespace forward from a position
|
|
19
|
+
*
|
|
20
|
+
* @param source - The source string
|
|
21
|
+
* @param pos - Starting position
|
|
22
|
+
* @param end - End boundary (exclusive)
|
|
23
|
+
* @returns New position after skipping whitespace
|
|
24
|
+
*/
|
|
25
|
+
export declare function skip_whitespace_forward(source: string, pos: number, end: number): number;
|
|
26
|
+
/**
|
|
27
|
+
* Skip whitespace and comments forward from a position
|
|
28
|
+
*
|
|
29
|
+
* @param source - The source string
|
|
30
|
+
* @param pos - Starting position
|
|
31
|
+
* @param end - End boundary (exclusive)
|
|
32
|
+
* @returns New position after skipping whitespace/comments
|
|
33
|
+
*/
|
|
34
|
+
export declare function skip_whitespace_and_comments_forward(source: string, pos: number, end: number): number;
|
|
35
|
+
/**
|
|
36
|
+
* Skip whitespace and comments backward from a position
|
|
37
|
+
*
|
|
38
|
+
* @param source - The source string
|
|
39
|
+
* @param pos - Starting position (exclusive, scanning backward from pos-1)
|
|
40
|
+
* @param start - Start boundary (inclusive, won't go before this)
|
|
41
|
+
* @returns New position after skipping whitespace/comments backward
|
|
42
|
+
*/
|
|
43
|
+
export declare function skip_whitespace_and_comments_backward(source: string, pos: number, start: number): number;
|
|
44
|
+
/**
|
|
45
|
+
* Trim whitespace and comments from both ends of a string range
|
|
46
|
+
*
|
|
47
|
+
* @param source - The source string
|
|
48
|
+
* @param start - Start offset in source
|
|
49
|
+
* @param end - End offset in source
|
|
50
|
+
* @returns [trimmed_start, trimmed_end] or null if all whitespace/comments
|
|
51
|
+
*
|
|
52
|
+
* Skips whitespace (space, tab, newline, CR, FF) and CSS comments from both ends
|
|
53
|
+
* of the specified range. Returns the trimmed boundaries or null if the range
|
|
54
|
+
* contains only whitespace and comments.
|
|
55
|
+
*/
|
|
56
|
+
export declare function trim_boundaries(source: string, start: number, end: number): [number, number] | null;
|