@projectwallace/css-parser 0.6.7 → 0.7.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 +36 -91
- package/dist/arena.js +280 -0
- package/dist/char-types.js +55 -0
- package/dist/constants.d.ts +1 -0
- package/dist/css-node.d.ts +2 -2
- package/dist/css-node.js +455 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +4 -24
- package/dist/lexer.d.ts +1 -37
- package/dist/{lexer-CtBKgfVv.js → lexer.js} +3 -81
- package/dist/parse-anplusb.d.ts +0 -20
- package/dist/parse-anplusb.js +28 -30
- package/dist/parse-atrule-prelude.d.ts +0 -25
- package/dist/parse-atrule-prelude.js +36 -42
- package/dist/parse-selector.d.ts +0 -30
- package/dist/parse-selector.js +64 -66
- package/dist/parse-utils.d.ts +0 -40
- package/dist/parse-utils.js +92 -0
- package/dist/parse-value.d.ts +0 -16
- package/dist/parse-value.js +50 -49
- package/dist/parse.d.ts +0 -27
- package/dist/parse.js +96 -73
- package/dist/string-utils.d.ts +0 -6
- package/dist/string-utils.js +63 -0
- package/dist/token-types.js +28 -0
- package/dist/tokenize.js +2 -1
- package/dist/walk.js +21 -0
- package/package.json +1 -1
- package/dist/css-node-GOEvp2OO.js +0 -876
package/dist/parse-selector.js
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { Lexer } from './lexer.js';
|
|
2
|
+
import { CSSDataArena, SELECTOR_LIST, SELECTOR, COMBINATOR, NESTING_SELECTOR, ID_SELECTOR, TYPE_SELECTOR, UNIVERSAL_SELECTOR, CLASS_SELECTOR, ATTRIBUTE_SELECTOR, ATTR_OPERATOR_NONE, ATTR_FLAG_NONE, ATTR_OPERATOR_EQUAL, ATTR_OPERATOR_TILDE_EQUAL, ATTR_OPERATOR_PIPE_EQUAL, ATTR_OPERATOR_CARET_EQUAL, ATTR_OPERATOR_DOLLAR_EQUAL, ATTR_OPERATOR_STAR_EQUAL, ATTR_FLAG_CASE_INSENSITIVE, ATTR_FLAG_CASE_SENSITIVE, PSEUDO_ELEMENT_SELECTOR, PSEUDO_CLASS_SELECTOR, FLAG_VENDOR_PREFIXED, FLAG_HAS_PARENS, LANG_SELECTOR, NTH_OF_SELECTOR } from './arena.js';
|
|
3
|
+
import { TOKEN_COMMA, TOKEN_DELIM, TOKEN_EOF, TOKEN_WHITESPACE, TOKEN_FUNCTION, TOKEN_COLON, TOKEN_LEFT_BRACKET, TOKEN_HASH, TOKEN_IDENT, TOKEN_RIGHT_BRACKET, TOKEN_LEFT_PAREN, TOKEN_RIGHT_PAREN, TOKEN_STRING } from './token-types.js';
|
|
4
|
+
import { skip_whitespace_and_comments_forward, skip_whitespace_and_comments_backward, skip_whitespace_forward } from './parse-utils.js';
|
|
5
|
+
import { CHAR_GREATER_THAN, CHAR_PLUS, CHAR_TILDE, CHAR_PERIOD, CHAR_ASTERISK, CHAR_AMPERSAND, CHAR_PIPE, CHAR_SPACE, CHAR_NEWLINE, CHAR_CARRIAGE_RETURN, CHAR_FORM_FEED, is_combinator, is_whitespace, CHAR_EQUALS, CHAR_CARET, CHAR_DOLLAR, CHAR_SINGLE_QUOTE, CHAR_DOUBLE_QUOTE, CHAR_COLON, is_vendor_prefixed } from './string-utils.js';
|
|
3
6
|
import { ANplusBParser } from './parse-anplusb.js';
|
|
7
|
+
import { CSSNode } from './css-node.js';
|
|
4
8
|
|
|
5
9
|
class SelectorParser {
|
|
6
10
|
lexer;
|
|
@@ -32,10 +36,12 @@ class SelectorParser {
|
|
|
32
36
|
let selector_start = this.lexer.pos;
|
|
33
37
|
let complex_selector = this.parse_complex_selector(allow_relative);
|
|
34
38
|
if (complex_selector !== null) {
|
|
35
|
-
let selector_wrapper = this.create_node(
|
|
39
|
+
let selector_wrapper = this.create_node(SELECTOR, selector_start, this.lexer.pos);
|
|
36
40
|
let last_component = complex_selector;
|
|
37
|
-
|
|
38
|
-
|
|
41
|
+
let next_sibling = this.arena.get_next_sibling(last_component);
|
|
42
|
+
while (next_sibling !== 0) {
|
|
43
|
+
last_component = next_sibling;
|
|
44
|
+
next_sibling = this.arena.get_next_sibling(last_component);
|
|
39
45
|
}
|
|
40
46
|
this.arena.set_first_child(selector_wrapper, complex_selector);
|
|
41
47
|
this.arena.set_last_child(selector_wrapper, last_component);
|
|
@@ -53,17 +59,14 @@ class SelectorParser {
|
|
|
53
59
|
}
|
|
54
60
|
}
|
|
55
61
|
if (selectors.length >= 1) {
|
|
56
|
-
let list_node = this.arena.create_node(
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
this.arena.
|
|
64
|
-
for (let i = 0; i < selectors.length - 1; i++) {
|
|
65
|
-
this.arena.set_next_sibling(selectors[i], selectors[i + 1]);
|
|
66
|
-
}
|
|
62
|
+
let list_node = this.arena.create_node(
|
|
63
|
+
SELECTOR_LIST,
|
|
64
|
+
list_start,
|
|
65
|
+
this.lexer.pos - list_start,
|
|
66
|
+
list_line,
|
|
67
|
+
list_column
|
|
68
|
+
);
|
|
69
|
+
this.arena.append_children(list_node, selectors);
|
|
67
70
|
return list_node;
|
|
68
71
|
}
|
|
69
72
|
return null;
|
|
@@ -81,7 +84,7 @@ class SelectorParser {
|
|
|
81
84
|
if (token_type === TOKEN_DELIM) {
|
|
82
85
|
let ch = this.source.charCodeAt(this.lexer.token_start);
|
|
83
86
|
if (ch === CHAR_GREATER_THAN || ch === CHAR_PLUS || ch === CHAR_TILDE) {
|
|
84
|
-
let combinator = this.create_node(
|
|
87
|
+
let combinator = this.create_node(COMBINATOR, this.lexer.token_start, this.lexer.token_end);
|
|
85
88
|
components.push(combinator);
|
|
86
89
|
this.skip_whitespace();
|
|
87
90
|
} else {
|
|
@@ -92,7 +95,6 @@ class SelectorParser {
|
|
|
92
95
|
}
|
|
93
96
|
}
|
|
94
97
|
while (this.lexer.pos < this.selector_end) {
|
|
95
|
-
if (this.lexer.pos >= this.selector_end) break;
|
|
96
98
|
let compound = this.parse_compound_selector();
|
|
97
99
|
if (compound !== null) {
|
|
98
100
|
components.push(compound);
|
|
@@ -160,7 +162,7 @@ class SelectorParser {
|
|
|
160
162
|
case TOKEN_IDENT:
|
|
161
163
|
return this.parse_type_or_namespace_selector(start, end);
|
|
162
164
|
case TOKEN_HASH:
|
|
163
|
-
return this.create_node(
|
|
165
|
+
return this.create_node(ID_SELECTOR, start, end);
|
|
164
166
|
case TOKEN_DELIM:
|
|
165
167
|
let ch = this.source.charCodeAt(start);
|
|
166
168
|
if (ch === CHAR_PERIOD) {
|
|
@@ -168,7 +170,7 @@ class SelectorParser {
|
|
|
168
170
|
} else if (ch === CHAR_ASTERISK) {
|
|
169
171
|
return this.parse_universal_or_namespace_selector(start, end);
|
|
170
172
|
} else if (ch === CHAR_AMPERSAND) {
|
|
171
|
-
return this.create_node(
|
|
173
|
+
return this.create_node(NESTING_SELECTOR, start, end);
|
|
172
174
|
} else if (ch === CHAR_PIPE) {
|
|
173
175
|
return this.parse_empty_namespace_selector(start);
|
|
174
176
|
}
|
|
@@ -193,15 +195,15 @@ class SelectorParser {
|
|
|
193
195
|
this.lexer.next_token_fast(false);
|
|
194
196
|
let node_type;
|
|
195
197
|
if (this.lexer.token_type === TOKEN_IDENT) {
|
|
196
|
-
node_type =
|
|
198
|
+
node_type = TYPE_SELECTOR;
|
|
197
199
|
} else if (this.lexer.token_type === TOKEN_DELIM && this.source.charCodeAt(this.lexer.token_start) === CHAR_ASTERISK) {
|
|
198
|
-
node_type =
|
|
200
|
+
node_type = UNIVERSAL_SELECTOR;
|
|
199
201
|
} else {
|
|
200
202
|
this.lexer.restore_position(saved);
|
|
201
203
|
return null;
|
|
202
204
|
}
|
|
203
205
|
let node = this.create_node(node_type, selector_start, this.lexer.token_end);
|
|
204
|
-
this.arena.
|
|
206
|
+
this.arena.set_content_start_delta(node, namespace_start - selector_start);
|
|
205
207
|
this.arena.set_content_length(node, namespace_length);
|
|
206
208
|
return node;
|
|
207
209
|
}
|
|
@@ -214,7 +216,7 @@ class SelectorParser {
|
|
|
214
216
|
if (node !== null) return node;
|
|
215
217
|
this.lexer.pos = end;
|
|
216
218
|
}
|
|
217
|
-
return this.create_node(
|
|
219
|
+
return this.create_node(TYPE_SELECTOR, start, end);
|
|
218
220
|
}
|
|
219
221
|
// Parse universal selector or namespace selector (*|E or *|*)
|
|
220
222
|
// Called when we've seen a * DELIM token
|
|
@@ -225,7 +227,7 @@ class SelectorParser {
|
|
|
225
227
|
if (node !== null) return node;
|
|
226
228
|
this.lexer.pos = end;
|
|
227
229
|
}
|
|
228
|
-
return this.create_node(
|
|
230
|
+
return this.create_node(UNIVERSAL_SELECTOR, start, end);
|
|
229
231
|
}
|
|
230
232
|
// Parse empty namespace selector (|E or |*)
|
|
231
233
|
// Called when we've seen a | DELIM token at the start
|
|
@@ -238,7 +240,7 @@ class SelectorParser {
|
|
|
238
240
|
let has_whitespace = false;
|
|
239
241
|
while (this.lexer.pos < this.selector_end) {
|
|
240
242
|
let ch = this.source.charCodeAt(this.lexer.pos);
|
|
241
|
-
if (
|
|
243
|
+
if (ch === CHAR_SPACE || ch === CHAR_NEWLINE || ch === CHAR_CARRIAGE_RETURN || ch === CHAR_FORM_FEED) {
|
|
242
244
|
has_whitespace = true;
|
|
243
245
|
this.lexer.pos++;
|
|
244
246
|
} else {
|
|
@@ -250,20 +252,20 @@ class SelectorParser {
|
|
|
250
252
|
if (this.lexer.token_type === TOKEN_DELIM) {
|
|
251
253
|
let ch = this.source.charCodeAt(this.lexer.token_start);
|
|
252
254
|
if (is_combinator(ch)) {
|
|
253
|
-
return this.create_node(
|
|
255
|
+
return this.create_node(COMBINATOR, this.lexer.token_start, this.lexer.token_end);
|
|
254
256
|
}
|
|
255
257
|
}
|
|
256
258
|
if (has_whitespace) {
|
|
257
259
|
this.lexer.pos = whitespace_start;
|
|
258
260
|
while (this.lexer.pos < this.selector_end) {
|
|
259
261
|
let ch = this.source.charCodeAt(this.lexer.pos);
|
|
260
|
-
if (
|
|
262
|
+
if (ch === CHAR_SPACE || ch === CHAR_NEWLINE || ch === CHAR_CARRIAGE_RETURN || ch === CHAR_FORM_FEED) {
|
|
261
263
|
this.lexer.pos++;
|
|
262
264
|
} else {
|
|
263
265
|
break;
|
|
264
266
|
}
|
|
265
267
|
}
|
|
266
|
-
return this.create_node(
|
|
268
|
+
return this.create_node(COMBINATOR, whitespace_start, this.lexer.pos);
|
|
267
269
|
}
|
|
268
270
|
this.lexer.pos = whitespace_start;
|
|
269
271
|
return null;
|
|
@@ -276,7 +278,7 @@ class SelectorParser {
|
|
|
276
278
|
this.lexer.restore_position(saved);
|
|
277
279
|
return null;
|
|
278
280
|
}
|
|
279
|
-
return this.create_node(
|
|
281
|
+
return this.create_node(CLASS_SELECTOR, dot_pos, this.lexer.token_end);
|
|
280
282
|
}
|
|
281
283
|
// Parse attribute selector ([attr], [attr=value], etc.)
|
|
282
284
|
parse_attribute_selector(start) {
|
|
@@ -298,7 +300,7 @@ class SelectorParser {
|
|
|
298
300
|
}
|
|
299
301
|
}
|
|
300
302
|
}
|
|
301
|
-
let node = this.create_node(
|
|
303
|
+
let node = this.create_node(ATTRIBUTE_SELECTOR, start, end);
|
|
302
304
|
this.parse_attribute_content(node, content_start, content_end);
|
|
303
305
|
return node;
|
|
304
306
|
}
|
|
@@ -313,14 +315,14 @@ class SelectorParser {
|
|
|
313
315
|
let value_start = -1;
|
|
314
316
|
let value_end = -1;
|
|
315
317
|
while (name_end < end) {
|
|
316
|
-
let
|
|
317
|
-
if (is_whitespace(
|
|
318
|
+
let ch3 = this.source.charCodeAt(name_end);
|
|
319
|
+
if (is_whitespace(ch3) || ch3 === CHAR_EQUALS || ch3 === CHAR_TILDE || ch3 === CHAR_PIPE || ch3 === CHAR_CARET || ch3 === CHAR_DOLLAR || ch3 === CHAR_ASTERISK) {
|
|
318
320
|
break;
|
|
319
321
|
}
|
|
320
322
|
name_end++;
|
|
321
323
|
}
|
|
322
324
|
if (name_end > name_start) {
|
|
323
|
-
this.arena.
|
|
325
|
+
this.arena.set_content_start_delta(node, name_start - this.arena.get_start_offset(node));
|
|
324
326
|
this.arena.set_content_length(node, name_end - name_start);
|
|
325
327
|
}
|
|
326
328
|
let pos = skip_whitespace_and_comments_forward(this.source, name_end, end);
|
|
@@ -330,22 +332,23 @@ class SelectorParser {
|
|
|
330
332
|
return;
|
|
331
333
|
}
|
|
332
334
|
let ch1 = this.source.charCodeAt(pos);
|
|
335
|
+
let ch2 = pos + 1 < end ? this.source.charCodeAt(pos + 1) : 0;
|
|
333
336
|
if (ch1 === CHAR_EQUALS) {
|
|
334
337
|
operator_end = pos + 1;
|
|
335
338
|
this.arena.set_attr_operator(node, ATTR_OPERATOR_EQUAL);
|
|
336
|
-
} else if (ch1 === CHAR_TILDE &&
|
|
339
|
+
} else if (ch1 === CHAR_TILDE && ch2 === CHAR_EQUALS) {
|
|
337
340
|
operator_end = pos + 2;
|
|
338
341
|
this.arena.set_attr_operator(node, ATTR_OPERATOR_TILDE_EQUAL);
|
|
339
|
-
} else if (ch1 === CHAR_PIPE &&
|
|
342
|
+
} else if (ch1 === CHAR_PIPE && ch2 === CHAR_EQUALS) {
|
|
340
343
|
operator_end = pos + 2;
|
|
341
344
|
this.arena.set_attr_operator(node, ATTR_OPERATOR_PIPE_EQUAL);
|
|
342
|
-
} else if (ch1 === CHAR_CARET &&
|
|
345
|
+
} else if (ch1 === CHAR_CARET && ch2 === CHAR_EQUALS) {
|
|
343
346
|
operator_end = pos + 2;
|
|
344
347
|
this.arena.set_attr_operator(node, ATTR_OPERATOR_CARET_EQUAL);
|
|
345
|
-
} else if (ch1 === CHAR_DOLLAR &&
|
|
348
|
+
} else if (ch1 === CHAR_DOLLAR && ch2 === CHAR_EQUALS) {
|
|
346
349
|
operator_end = pos + 2;
|
|
347
350
|
this.arena.set_attr_operator(node, ATTR_OPERATOR_DOLLAR_EQUAL);
|
|
348
|
-
} else if (ch1 === CHAR_ASTERISK &&
|
|
351
|
+
} else if (ch1 === CHAR_ASTERISK && ch2 === CHAR_EQUALS) {
|
|
349
352
|
operator_end = pos + 2;
|
|
350
353
|
this.arena.set_attr_operator(node, ATTR_OPERATOR_STAR_EQUAL);
|
|
351
354
|
} else {
|
|
@@ -388,7 +391,7 @@ class SelectorParser {
|
|
|
388
391
|
value_end = pos;
|
|
389
392
|
}
|
|
390
393
|
if (value_end > value_start) {
|
|
391
|
-
this.arena.
|
|
394
|
+
this.arena.set_value_start_delta(node, value_start - this.arena.get_start_offset(node));
|
|
392
395
|
this.arena.set_value_length(node, value_end - value_start);
|
|
393
396
|
}
|
|
394
397
|
pos = skip_whitespace_and_comments_forward(this.source, value_end, end);
|
|
@@ -416,12 +419,8 @@ class SelectorParser {
|
|
|
416
419
|
this.lexer.next_token_fast(false);
|
|
417
420
|
let token_type = this.lexer.token_type;
|
|
418
421
|
if (token_type === TOKEN_IDENT) {
|
|
419
|
-
let node = this.create_node(
|
|
420
|
-
|
|
421
|
-
start,
|
|
422
|
-
this.lexer.token_end
|
|
423
|
-
);
|
|
424
|
-
this.arena.set_content_start(node, this.lexer.token_start);
|
|
422
|
+
let node = this.create_node(is_pseudo_element ? PSEUDO_ELEMENT_SELECTOR : PSEUDO_CLASS_SELECTOR, start, this.lexer.token_end);
|
|
423
|
+
this.arena.set_content_start_delta(node, this.lexer.token_start - start);
|
|
425
424
|
this.arena.set_content_length(node, this.lexer.token_end - this.lexer.token_start);
|
|
426
425
|
if (is_vendor_prefixed(this.source, this.lexer.token_start, this.lexer.token_end)) {
|
|
427
426
|
this.arena.set_flag(node, FLAG_VENDOR_PREFIXED);
|
|
@@ -459,8 +458,8 @@ class SelectorParser {
|
|
|
459
458
|
}
|
|
460
459
|
}
|
|
461
460
|
}
|
|
462
|
-
let node = this.create_node(is_pseudo_element ?
|
|
463
|
-
this.arena.
|
|
461
|
+
let node = this.create_node(is_pseudo_element ? PSEUDO_ELEMENT_SELECTOR : PSEUDO_CLASS_SELECTOR, start, end);
|
|
462
|
+
this.arena.set_content_start_delta(node, func_name_start - start);
|
|
464
463
|
this.arena.set_content_length(node, func_name_end - func_name_start);
|
|
465
464
|
this.arena.set_flag(node, FLAG_HAS_PARENS);
|
|
466
465
|
if (is_vendor_prefixed(this.source, func_name_start, func_name_end)) {
|
|
@@ -516,7 +515,7 @@ class SelectorParser {
|
|
|
516
515
|
continue;
|
|
517
516
|
}
|
|
518
517
|
if (token_type === TOKEN_STRING || token_type === TOKEN_IDENT) {
|
|
519
|
-
let lang_node = this.create_node(
|
|
518
|
+
let lang_node = this.create_node(LANG_SELECTOR, token_start, token_end);
|
|
520
519
|
if (first_child === null) {
|
|
521
520
|
first_child = lang_node;
|
|
522
521
|
}
|
|
@@ -554,11 +553,13 @@ class SelectorParser {
|
|
|
554
553
|
let selector_list = this.parse_selector_list();
|
|
555
554
|
this.selector_end = saved_selector_end;
|
|
556
555
|
this.lexer.restore_position(saved);
|
|
557
|
-
let of_node = this.arena.create_node(
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
556
|
+
let of_node = this.arena.create_node(
|
|
557
|
+
NTH_OF_SELECTOR,
|
|
558
|
+
start,
|
|
559
|
+
end - start,
|
|
560
|
+
this.lexer.line,
|
|
561
|
+
1
|
|
562
|
+
);
|
|
562
563
|
if (anplusb_node !== null && selector_list !== null) {
|
|
563
564
|
this.arena.set_first_child(of_node, anplusb_node);
|
|
564
565
|
this.arena.set_last_child(of_node, selector_list);
|
|
@@ -587,13 +588,14 @@ class SelectorParser {
|
|
|
587
588
|
return -1;
|
|
588
589
|
}
|
|
589
590
|
create_node(type, start, end) {
|
|
590
|
-
let node = this.arena.create_node(
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
591
|
+
let node = this.arena.create_node(
|
|
592
|
+
type,
|
|
593
|
+
start,
|
|
594
|
+
end - start,
|
|
595
|
+
this.lexer.line,
|
|
596
|
+
this.lexer.column
|
|
597
|
+
);
|
|
598
|
+
this.arena.set_content_start_delta(node, 0);
|
|
597
599
|
this.arena.set_content_length(node, end - start);
|
|
598
600
|
return node;
|
|
599
601
|
}
|
|
@@ -607,11 +609,7 @@ function parse_selector(source) {
|
|
|
607
609
|
const selector_parser = new SelectorParser(arena, source);
|
|
608
610
|
const selector_index = selector_parser.parse_selector(0, source.length);
|
|
609
611
|
if (selector_index === null) {
|
|
610
|
-
const empty = arena.create_node();
|
|
611
|
-
arena.set_type(empty, NODE_SELECTOR_LIST);
|
|
612
|
-
arena.set_start_offset(empty, 0);
|
|
613
|
-
arena.set_length(empty, 0);
|
|
614
|
-
arena.set_start_line(empty, 1);
|
|
612
|
+
const empty = arena.create_node(SELECTOR_LIST, 0, 0, 1, 1);
|
|
615
613
|
return new CSSNode(arena, source, empty);
|
|
616
614
|
}
|
|
617
615
|
return new CSSNode(arena, source, selector_index);
|
package/dist/parse-utils.d.ts
CHANGED
|
@@ -14,43 +14,3 @@ export declare function parse_dimension(text: string): {
|
|
|
14
14
|
value: number;
|
|
15
15
|
unit: string;
|
|
16
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;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { is_digit, CHAR_PERIOD, CHAR_MINUS_HYPHEN, CHAR_PLUS, is_whitespace, CHAR_FORWARD_SLASH, CHAR_ASTERISK } from './string-utils.js';
|
|
2
|
+
|
|
3
|
+
function parse_dimension(text) {
|
|
4
|
+
let num_end = 0;
|
|
5
|
+
for (let i = 0; i < text.length; i++) {
|
|
6
|
+
let ch = text.charCodeAt(i);
|
|
7
|
+
if (ch === 101 || ch === 69) {
|
|
8
|
+
if (i + 1 < text.length) {
|
|
9
|
+
let next_ch = text.charCodeAt(i + 1);
|
|
10
|
+
if (is_digit(next_ch)) {
|
|
11
|
+
num_end = i + 1;
|
|
12
|
+
continue;
|
|
13
|
+
}
|
|
14
|
+
if ((next_ch === 43 || next_ch === 45) && i + 2 < text.length) {
|
|
15
|
+
let afterSign = text.charCodeAt(i + 2);
|
|
16
|
+
if (is_digit(afterSign)) {
|
|
17
|
+
num_end = i + 1;
|
|
18
|
+
continue;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
break;
|
|
23
|
+
}
|
|
24
|
+
if (is_digit(ch) || ch === CHAR_PERIOD || ch === CHAR_MINUS_HYPHEN || ch === CHAR_PLUS) {
|
|
25
|
+
num_end = i + 1;
|
|
26
|
+
} else {
|
|
27
|
+
break;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
let num_str = text.substring(0, num_end);
|
|
31
|
+
let unit = text.substring(num_end);
|
|
32
|
+
let value = num_str ? parseFloat(num_str) : 0;
|
|
33
|
+
return { value, unit };
|
|
34
|
+
}
|
|
35
|
+
function skip_whitespace_forward(source, pos, end) {
|
|
36
|
+
while (pos < end && is_whitespace(source.charCodeAt(pos))) {
|
|
37
|
+
pos++;
|
|
38
|
+
}
|
|
39
|
+
return pos;
|
|
40
|
+
}
|
|
41
|
+
function skip_whitespace_and_comments_forward(source, pos, end) {
|
|
42
|
+
while (pos < end) {
|
|
43
|
+
let ch = source.charCodeAt(pos);
|
|
44
|
+
if (is_whitespace(ch)) {
|
|
45
|
+
pos++;
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
if (ch === CHAR_FORWARD_SLASH && pos + 1 < end && source.charCodeAt(pos + 1) === CHAR_ASTERISK) {
|
|
49
|
+
pos += 2;
|
|
50
|
+
while (pos < end) {
|
|
51
|
+
if (source.charCodeAt(pos) === CHAR_ASTERISK && pos + 1 < end && source.charCodeAt(pos + 1) === CHAR_FORWARD_SLASH) {
|
|
52
|
+
pos += 2;
|
|
53
|
+
break;
|
|
54
|
+
}
|
|
55
|
+
pos++;
|
|
56
|
+
}
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
break;
|
|
60
|
+
}
|
|
61
|
+
return pos;
|
|
62
|
+
}
|
|
63
|
+
function skip_whitespace_and_comments_backward(source, pos, start) {
|
|
64
|
+
while (pos > start) {
|
|
65
|
+
let ch = source.charCodeAt(pos - 1);
|
|
66
|
+
if (is_whitespace(ch)) {
|
|
67
|
+
pos--;
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
if (pos >= 2 && ch === CHAR_FORWARD_SLASH && source.charCodeAt(pos - 2) === CHAR_ASTERISK) {
|
|
71
|
+
pos -= 2;
|
|
72
|
+
while (pos > start) {
|
|
73
|
+
if (pos >= 2 && source.charCodeAt(pos - 2) === CHAR_FORWARD_SLASH && source.charCodeAt(pos - 1) === CHAR_ASTERISK) {
|
|
74
|
+
pos -= 2;
|
|
75
|
+
break;
|
|
76
|
+
}
|
|
77
|
+
pos--;
|
|
78
|
+
}
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
break;
|
|
82
|
+
}
|
|
83
|
+
return pos;
|
|
84
|
+
}
|
|
85
|
+
function trim_boundaries(source, start, end) {
|
|
86
|
+
start = skip_whitespace_and_comments_forward(source, start, end);
|
|
87
|
+
end = skip_whitespace_and_comments_backward(source, end, start);
|
|
88
|
+
if (start >= end) return null;
|
|
89
|
+
return [start, end];
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export { parse_dimension, skip_whitespace_and_comments_backward, skip_whitespace_and_comments_forward, skip_whitespace_forward, trim_boundaries };
|
package/dist/parse-value.d.ts
CHANGED
|
@@ -1,20 +1,4 @@
|
|
|
1
|
-
import { CSSDataArena } from './arena';
|
|
2
1
|
import { CSSNode } from './css-node';
|
|
3
|
-
export declare class ValueParser {
|
|
4
|
-
private lexer;
|
|
5
|
-
private arena;
|
|
6
|
-
private source;
|
|
7
|
-
private value_end;
|
|
8
|
-
constructor(arena: CSSDataArena, source: string);
|
|
9
|
-
parse_value(start: number, end: number): number[];
|
|
10
|
-
private is_whitespace_token;
|
|
11
|
-
private parse_value_node;
|
|
12
|
-
private create_node;
|
|
13
|
-
private create_operator_node;
|
|
14
|
-
private parse_operator_node;
|
|
15
|
-
private parse_function_node;
|
|
16
|
-
private parse_parenthesis_node;
|
|
17
|
-
}
|
|
18
2
|
/**
|
|
19
3
|
* Parse a CSS declaration value string and return an array of value AST nodes
|
|
20
4
|
* @param value_string - The CSS value to parse (e.g., "1px solid red")
|
package/dist/parse-value.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { Lexer } from './lexer.js';
|
|
2
|
+
import { CSSDataArena, OPERATOR, HASH, STRING, DIMENSION, NUMBER, IDENTIFIER, URL, FUNCTION, PARENTHESIS } from './arena.js';
|
|
3
|
+
import { TOKEN_EOF, TOKEN_LEFT_PAREN, TOKEN_COMMA, TOKEN_DELIM, TOKEN_FUNCTION, TOKEN_HASH, TOKEN_STRING, TOKEN_DIMENSION, TOKEN_PERCENTAGE, TOKEN_NUMBER, TOKEN_IDENT, TOKEN_RIGHT_PAREN } from './token-types.js';
|
|
4
|
+
import { is_whitespace, CHAR_PLUS, CHAR_MINUS_HYPHEN, CHAR_ASTERISK, CHAR_FORWARD_SLASH } from './string-utils.js';
|
|
5
|
+
import { CSSNode } from './css-node.js';
|
|
3
6
|
|
|
4
7
|
class ValueParser {
|
|
5
8
|
lexer;
|
|
@@ -24,7 +27,7 @@ class ValueParser {
|
|
|
24
27
|
if (this.lexer.token_start >= this.value_end) break;
|
|
25
28
|
let token_type = this.lexer.token_type;
|
|
26
29
|
if (token_type === TOKEN_EOF) break;
|
|
27
|
-
if (this.
|
|
30
|
+
if (this.is_whitespace_inline()) {
|
|
28
31
|
continue;
|
|
29
32
|
}
|
|
30
33
|
let node = this.parse_value_node();
|
|
@@ -34,13 +37,11 @@ class ValueParser {
|
|
|
34
37
|
}
|
|
35
38
|
return nodes;
|
|
36
39
|
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
let ch = this.source.charCodeAt(i);
|
|
43
|
-
if (!is_whitespace(ch)) {
|
|
40
|
+
// Helper to check if token is all whitespace (inline for hot paths)
|
|
41
|
+
is_whitespace_inline() {
|
|
42
|
+
if (this.lexer.token_start >= this.lexer.token_end) return false;
|
|
43
|
+
for (let i = this.lexer.token_start; i < this.lexer.token_end; i++) {
|
|
44
|
+
if (!is_whitespace(this.source.charCodeAt(i))) {
|
|
44
45
|
return false;
|
|
45
46
|
}
|
|
46
47
|
}
|
|
@@ -52,22 +53,22 @@ class ValueParser {
|
|
|
52
53
|
let end = this.lexer.token_end;
|
|
53
54
|
switch (token_type) {
|
|
54
55
|
case TOKEN_IDENT:
|
|
55
|
-
return this.create_node(
|
|
56
|
+
return this.create_node(IDENTIFIER, start, end);
|
|
56
57
|
case TOKEN_NUMBER:
|
|
57
|
-
return this.create_node(
|
|
58
|
+
return this.create_node(NUMBER, start, end);
|
|
58
59
|
case TOKEN_PERCENTAGE:
|
|
59
60
|
case TOKEN_DIMENSION:
|
|
60
|
-
return this.create_node(
|
|
61
|
+
return this.create_node(DIMENSION, start, end);
|
|
61
62
|
case TOKEN_STRING:
|
|
62
|
-
return this.create_node(
|
|
63
|
+
return this.create_node(STRING, start, end);
|
|
63
64
|
case TOKEN_HASH:
|
|
64
|
-
return this.create_node(
|
|
65
|
+
return this.create_node(HASH, start, end);
|
|
65
66
|
case TOKEN_FUNCTION:
|
|
66
67
|
return this.parse_function_node(start, end);
|
|
67
68
|
case TOKEN_DELIM:
|
|
68
69
|
return this.parse_operator_node(start, end);
|
|
69
70
|
case TOKEN_COMMA:
|
|
70
|
-
return this.create_node(
|
|
71
|
+
return this.create_node(OPERATOR, start, end);
|
|
71
72
|
case TOKEN_LEFT_PAREN:
|
|
72
73
|
return this.parse_parenthesis_node(start, end);
|
|
73
74
|
default:
|
|
@@ -75,16 +76,18 @@ class ValueParser {
|
|
|
75
76
|
}
|
|
76
77
|
}
|
|
77
78
|
create_node(node_type, start, end) {
|
|
78
|
-
let node = this.arena.create_node(
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
79
|
+
let node = this.arena.create_node(
|
|
80
|
+
node_type,
|
|
81
|
+
start,
|
|
82
|
+
end - start,
|
|
83
|
+
this.lexer.line,
|
|
84
|
+
this.lexer.column
|
|
85
|
+
);
|
|
83
86
|
this.arena.set_content_length(node, end - start);
|
|
84
87
|
return node;
|
|
85
88
|
}
|
|
86
89
|
create_operator_node(start, end) {
|
|
87
|
-
return this.create_node(
|
|
90
|
+
return this.create_node(OPERATOR, start, end);
|
|
88
91
|
}
|
|
89
92
|
parse_operator_node(start, end) {
|
|
90
93
|
let ch = this.source.charCodeAt(start);
|
|
@@ -94,17 +97,22 @@ class ValueParser {
|
|
|
94
97
|
return null;
|
|
95
98
|
}
|
|
96
99
|
parse_function_node(start, end) {
|
|
97
|
-
let node = this.arena.create_node();
|
|
98
|
-
this.arena.set_type(node, NODE_VALUE_FUNCTION);
|
|
99
|
-
this.arena.set_start_offset(node, start);
|
|
100
100
|
let name_end = end - 1;
|
|
101
|
-
this.arena.set_content_start(node, start);
|
|
102
|
-
this.arena.set_content_length(node, name_end - start);
|
|
103
101
|
let func_name = this.source.substring(start, name_end).toLowerCase();
|
|
102
|
+
let node = this.arena.create_node(
|
|
103
|
+
func_name === "url" ? URL : FUNCTION,
|
|
104
|
+
start,
|
|
105
|
+
0,
|
|
106
|
+
// length unknown yet
|
|
107
|
+
this.lexer.line,
|
|
108
|
+
this.lexer.column
|
|
109
|
+
);
|
|
110
|
+
this.arena.set_content_start_delta(node, 0);
|
|
111
|
+
this.arena.set_content_length(node, name_end - start);
|
|
104
112
|
if (func_name === "url" || func_name === "src") {
|
|
105
113
|
let save_pos = this.lexer.save_position();
|
|
106
114
|
this.lexer.next_token_fast(false);
|
|
107
|
-
while (this.
|
|
115
|
+
while (this.is_whitespace_inline() && this.lexer.pos < this.value_end) {
|
|
108
116
|
this.lexer.next_token_fast(false);
|
|
109
117
|
}
|
|
110
118
|
let first_token_type = this.lexer.token_type;
|
|
@@ -130,7 +138,7 @@ class ValueParser {
|
|
|
130
138
|
}
|
|
131
139
|
}
|
|
132
140
|
this.arena.set_length(node, func_end2 - start);
|
|
133
|
-
this.arena.
|
|
141
|
+
this.arena.set_value_start_delta(node, content_start2 - start);
|
|
134
142
|
this.arena.set_value_length(node, content_end2 - content_start2);
|
|
135
143
|
return node;
|
|
136
144
|
}
|
|
@@ -155,28 +163,27 @@ class ValueParser {
|
|
|
155
163
|
break;
|
|
156
164
|
}
|
|
157
165
|
}
|
|
158
|
-
if (this.
|
|
166
|
+
if (this.is_whitespace_inline()) continue;
|
|
159
167
|
let arg_node = this.parse_value_node();
|
|
160
168
|
if (arg_node !== null) {
|
|
161
169
|
args.push(arg_node);
|
|
162
170
|
}
|
|
163
171
|
}
|
|
164
172
|
this.arena.set_length(node, func_end - start);
|
|
165
|
-
this.arena.
|
|
173
|
+
this.arena.set_value_start_delta(node, content_start - start);
|
|
166
174
|
this.arena.set_value_length(node, content_end - content_start);
|
|
167
|
-
|
|
168
|
-
this.arena.set_first_child(node, args[0]);
|
|
169
|
-
this.arena.set_last_child(node, args[args.length - 1]);
|
|
170
|
-
for (let i = 0; i < args.length - 1; i++) {
|
|
171
|
-
this.arena.set_next_sibling(args[i], args[i + 1]);
|
|
172
|
-
}
|
|
173
|
-
}
|
|
175
|
+
this.arena.append_children(node, args);
|
|
174
176
|
return node;
|
|
175
177
|
}
|
|
176
178
|
parse_parenthesis_node(start, end) {
|
|
177
|
-
let node = this.arena.create_node(
|
|
178
|
-
|
|
179
|
-
|
|
179
|
+
let node = this.arena.create_node(
|
|
180
|
+
PARENTHESIS,
|
|
181
|
+
start,
|
|
182
|
+
0,
|
|
183
|
+
// length unknown yet
|
|
184
|
+
this.lexer.line,
|
|
185
|
+
this.lexer.column
|
|
186
|
+
);
|
|
180
187
|
let children = [];
|
|
181
188
|
let paren_depth = 1;
|
|
182
189
|
let paren_end = end;
|
|
@@ -192,20 +199,14 @@ class ValueParser {
|
|
|
192
199
|
break;
|
|
193
200
|
}
|
|
194
201
|
}
|
|
195
|
-
if (this.
|
|
202
|
+
if (this.is_whitespace_inline()) continue;
|
|
196
203
|
let child_node = this.parse_value_node();
|
|
197
204
|
if (child_node !== null) {
|
|
198
205
|
children.push(child_node);
|
|
199
206
|
}
|
|
200
207
|
}
|
|
201
208
|
this.arena.set_length(node, paren_end - start);
|
|
202
|
-
|
|
203
|
-
this.arena.set_first_child(node, children[0]);
|
|
204
|
-
this.arena.set_last_child(node, children[children.length - 1]);
|
|
205
|
-
for (let i = 0; i < children.length - 1; i++) {
|
|
206
|
-
this.arena.set_next_sibling(children[i], children[i + 1]);
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
+
this.arena.append_children(node, children);
|
|
209
210
|
return node;
|
|
210
211
|
}
|
|
211
212
|
}
|