@projectwallace/css-parser 0.7.1 → 0.7.3
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/css-node.js +21 -2
- package/dist/parse-anplusb.js +4 -4
- package/dist/parse-selector.js +6 -6
- package/dist/parse-value.js +4 -4
- package/dist/string-utils.d.ts +21 -0
- package/dist/string-utils.js +47 -1
- package/package.json +1 -1
package/dist/css-node.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { DIMENSION, NUMBER, DECLARATION, FLAG_IMPORTANT, FLAG_VENDOR_PREFIXED, FLAG_HAS_ERROR, FLAG_HAS_BLOCK, FLAG_HAS_DECLARATIONS, STYLE_RULE, BLOCK, AT_RULE, COMMENT, PSEUDO_CLASS_SELECTOR, PSEUDO_ELEMENT_SELECTOR, FLAG_HAS_PARENS, NTH_SELECTOR, NTH_OF_SELECTOR, SELECTOR_LIST, SELECTOR, COMBINATOR, ATTRIBUTE_SELECTOR, PRELUDE_OPERATOR, LAYER_NAME, SUPPORTS_QUERY, CONTAINER_QUERY, MEDIA_TYPE, MEDIA_FEATURE, MEDIA_QUERY, LANG_SELECTOR, NESTING_SELECTOR, UNIVERSAL_SELECTOR, ID_SELECTOR, CLASS_SELECTOR, TYPE_SELECTOR,
|
|
2
|
-
import { is_whitespace, CHAR_MINUS_HYPHEN, CHAR_PLUS } from './string-utils.js';
|
|
1
|
+
import { URL, STRING, DIMENSION, NUMBER, DECLARATION, FLAG_IMPORTANT, FLAG_VENDOR_PREFIXED, FLAG_HAS_ERROR, FLAG_HAS_BLOCK, FLAG_HAS_DECLARATIONS, STYLE_RULE, BLOCK, AT_RULE, COMMENT, PSEUDO_CLASS_SELECTOR, PSEUDO_ELEMENT_SELECTOR, FLAG_HAS_PARENS, NTH_SELECTOR, NTH_OF_SELECTOR, SELECTOR_LIST, SELECTOR, COMBINATOR, ATTRIBUTE_SELECTOR, PRELUDE_OPERATOR, LAYER_NAME, SUPPORTS_QUERY, CONTAINER_QUERY, MEDIA_TYPE, MEDIA_FEATURE, MEDIA_QUERY, LANG_SELECTOR, NESTING_SELECTOR, UNIVERSAL_SELECTOR, ID_SELECTOR, CLASS_SELECTOR, TYPE_SELECTOR, PARENTHESIS, OPERATOR, FUNCTION, HASH, IDENTIFIER, STYLESHEET } from './arena.js';
|
|
2
|
+
import { str_starts_with, is_whitespace, CHAR_MINUS_HYPHEN, CHAR_PLUS } from './string-utils.js';
|
|
3
3
|
import { parse_dimension } from './parse-utils.js';
|
|
4
4
|
|
|
5
5
|
const TYPE_NAMES = {
|
|
@@ -82,7 +82,26 @@ class CSSNode {
|
|
|
82
82
|
// Get the value text (for declarations: "blue" in "color: blue")
|
|
83
83
|
// For dimension/number nodes: returns the numeric value as a number
|
|
84
84
|
// For string nodes: returns the string content without quotes
|
|
85
|
+
// For URL nodes with quoted string: returns the string with quotes (consistent with STRING node)
|
|
86
|
+
// For URL nodes with unquoted URL: returns the URL content without quotes
|
|
85
87
|
get value() {
|
|
88
|
+
if (this.type === URL) {
|
|
89
|
+
const firstChild = this.first_child;
|
|
90
|
+
if (firstChild && firstChild.type === STRING) {
|
|
91
|
+
return firstChild.text;
|
|
92
|
+
}
|
|
93
|
+
const text = this.text;
|
|
94
|
+
if (str_starts_with(text, "url(")) {
|
|
95
|
+
const openParen = text.indexOf("(");
|
|
96
|
+
const closeParen = text.lastIndexOf(")");
|
|
97
|
+
if (openParen !== -1 && closeParen !== -1 && closeParen > openParen) {
|
|
98
|
+
let content = text.substring(openParen + 1, closeParen).trim();
|
|
99
|
+
return content;
|
|
100
|
+
}
|
|
101
|
+
} else if (text.startsWith('"') || text.startsWith("'")) {
|
|
102
|
+
return text;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
86
105
|
if (this.type === DIMENSION || this.type === NUMBER) {
|
|
87
106
|
return parse_dimension(this.text).value;
|
|
88
107
|
}
|
package/dist/parse-anplusb.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Lexer } from './lexer.js';
|
|
2
2
|
import { NTH_SELECTOR, CSSDataArena } from './arena.js';
|
|
3
3
|
import { TOKEN_IDENT, TOKEN_DELIM, TOKEN_DIMENSION, TOKEN_NUMBER } from './token-types.js';
|
|
4
|
-
import { CHAR_MINUS_HYPHEN, CHAR_PLUS } from './string-utils.js';
|
|
4
|
+
import { str_equals, CHAR_MINUS_HYPHEN, CHAR_PLUS, str_index_of } from './string-utils.js';
|
|
5
5
|
import { skip_whitespace_forward } from './parse-utils.js';
|
|
6
6
|
import { CSSNode } from './css-node.js';
|
|
7
7
|
|
|
@@ -36,8 +36,8 @@ class ANplusBParser {
|
|
|
36
36
|
}
|
|
37
37
|
this.lexer.next_token_fast(true);
|
|
38
38
|
if (this.lexer.token_type === TOKEN_IDENT) {
|
|
39
|
-
const text = this.source.substring(this.lexer.token_start, this.lexer.token_end)
|
|
40
|
-
if (
|
|
39
|
+
const text = this.source.substring(this.lexer.token_start, this.lexer.token_end);
|
|
40
|
+
if (str_equals("odd", text) || str_equals("even", text)) {
|
|
41
41
|
a_start = this.lexer.token_start;
|
|
42
42
|
a_end = this.lexer.token_end;
|
|
43
43
|
return this.create_anplusb_node(node_start, a_start, a_end, 0, 0);
|
|
@@ -118,7 +118,7 @@ class ANplusBParser {
|
|
|
118
118
|
}
|
|
119
119
|
if (this.lexer.token_type === TOKEN_DIMENSION) {
|
|
120
120
|
const token_text = this.source.substring(this.lexer.token_start, this.lexer.token_end);
|
|
121
|
-
const n_index = token_text
|
|
121
|
+
const n_index = str_index_of(token_text, "n");
|
|
122
122
|
if (n_index !== -1) {
|
|
123
123
|
a_start = this.lexer.token_start;
|
|
124
124
|
a_end = this.lexer.token_start + n_index + 1;
|
package/dist/parse-selector.js
CHANGED
|
@@ -2,7 +2,7 @@ import { Lexer } from './lexer.js';
|
|
|
2
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
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
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';
|
|
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, str_equals } from './string-utils.js';
|
|
6
6
|
import { ANplusBParser } from './parse-anplusb.js';
|
|
7
7
|
import { CSSNode } from './css-node.js';
|
|
8
8
|
|
|
@@ -466,19 +466,19 @@ class SelectorParser {
|
|
|
466
466
|
this.arena.set_flag(node, FLAG_VENDOR_PREFIXED);
|
|
467
467
|
}
|
|
468
468
|
if (content_end > content_start) {
|
|
469
|
-
let
|
|
470
|
-
if (this.is_nth_pseudo(
|
|
469
|
+
let func_name_substr = this.source.substring(func_name_start, func_name_end);
|
|
470
|
+
if (this.is_nth_pseudo(func_name_substr)) {
|
|
471
471
|
let child = this.parse_nth_expression(content_start, content_end);
|
|
472
472
|
if (child !== null) {
|
|
473
473
|
this.arena.set_first_child(node, child);
|
|
474
474
|
this.arena.set_last_child(node, child);
|
|
475
475
|
}
|
|
476
|
-
} else if (
|
|
476
|
+
} else if (str_equals("lang", func_name_substr)) {
|
|
477
477
|
this.parse_lang_identifiers(content_start, content_end, node);
|
|
478
478
|
} else {
|
|
479
479
|
let saved_selector_end = this.selector_end;
|
|
480
480
|
const saved = this.lexer.save_position();
|
|
481
|
-
let allow_relative =
|
|
481
|
+
let allow_relative = str_equals("has", func_name_substr);
|
|
482
482
|
let child_selector = this.parse_selector(content_start, content_end, this.lexer.line, this.lexer.column, allow_relative);
|
|
483
483
|
this.selector_end = saved_selector_end;
|
|
484
484
|
this.lexer.restore_position(saved);
|
|
@@ -492,7 +492,7 @@ class SelectorParser {
|
|
|
492
492
|
}
|
|
493
493
|
// Check if pseudo-class name is an nth-* pseudo
|
|
494
494
|
is_nth_pseudo(name) {
|
|
495
|
-
return
|
|
495
|
+
return str_equals("nth-child", name) || str_equals("nth-last-child", name) || str_equals("nth-of-type", name) || str_equals("nth-last-of-type", name) || str_equals("nth-col", name) || str_equals("nth-last-col", name);
|
|
496
496
|
}
|
|
497
497
|
// Parse :lang() content - comma-separated language identifiers
|
|
498
498
|
// Accepts both quoted strings: :lang("en", "fr") and unquoted: :lang(en, fr)
|
package/dist/parse-value.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Lexer } from './lexer.js';
|
|
2
2
|
import { CSSDataArena, OPERATOR, HASH, STRING, DIMENSION, NUMBER, IDENTIFIER, URL, FUNCTION, PARENTHESIS } from './arena.js';
|
|
3
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';
|
|
4
|
+
import { is_whitespace, CHAR_PLUS, CHAR_MINUS_HYPHEN, CHAR_ASTERISK, CHAR_FORWARD_SLASH, str_equals } from './string-utils.js';
|
|
5
5
|
import { CSSNode } from './css-node.js';
|
|
6
6
|
|
|
7
7
|
class ValueParser {
|
|
@@ -98,9 +98,9 @@ class ValueParser {
|
|
|
98
98
|
}
|
|
99
99
|
parse_function_node(start, end) {
|
|
100
100
|
let name_end = end - 1;
|
|
101
|
-
let
|
|
101
|
+
let func_name_substr = this.source.substring(start, name_end);
|
|
102
102
|
let node = this.arena.create_node(
|
|
103
|
-
|
|
103
|
+
str_equals("url", func_name_substr) ? URL : FUNCTION,
|
|
104
104
|
start,
|
|
105
105
|
0,
|
|
106
106
|
// length unknown yet
|
|
@@ -109,7 +109,7 @@ class ValueParser {
|
|
|
109
109
|
);
|
|
110
110
|
this.arena.set_content_start_delta(node, 0);
|
|
111
111
|
this.arena.set_content_length(node, name_end - start);
|
|
112
|
-
if (
|
|
112
|
+
if (str_equals("url", func_name_substr) || str_equals("src", func_name_substr)) {
|
|
113
113
|
let save_pos = this.lexer.save_position();
|
|
114
114
|
this.lexer.next_token_fast(false);
|
|
115
115
|
while (this.is_whitespace_inline() && this.lexer.pos < this.value_end) {
|
package/dist/string-utils.d.ts
CHANGED
|
@@ -23,6 +23,27 @@ export declare const CHAR_COLON = 58;
|
|
|
23
23
|
* @param b Compare string
|
|
24
24
|
*/
|
|
25
25
|
export declare function str_equals(a: string, b: string): boolean;
|
|
26
|
+
/**
|
|
27
|
+
* Case-insensitive ASCII prefix check without allocations
|
|
28
|
+
* Returns true if string `str` starts with prefix (case-insensitive)
|
|
29
|
+
*
|
|
30
|
+
* IMPORTANT: prefix MUST be lowercase for correct comparison
|
|
31
|
+
*
|
|
32
|
+
* @param str - The string to check
|
|
33
|
+
* @param prefix - The lowercase prefix to match against
|
|
34
|
+
*/
|
|
35
|
+
export declare function str_starts_with(str: string, prefix: string): boolean;
|
|
36
|
+
/**
|
|
37
|
+
* Case-insensitive character/substring search without allocations
|
|
38
|
+
* Returns the index of the first occurrence of searchChar (case-insensitive)
|
|
39
|
+
*
|
|
40
|
+
* IMPORTANT: searchChar MUST be lowercase for correct comparison
|
|
41
|
+
*
|
|
42
|
+
* @param str - The string to search in
|
|
43
|
+
* @param searchChar - The lowercase character/substring to find
|
|
44
|
+
* @returns The index of the first match, or -1 if not found
|
|
45
|
+
*/
|
|
46
|
+
export declare function str_index_of(str: string, searchChar: string): number;
|
|
26
47
|
/**
|
|
27
48
|
* Check if a string range has a vendor prefix
|
|
28
49
|
*
|
package/dist/string-utils.js
CHANGED
|
@@ -41,6 +41,52 @@ function str_equals(a, b) {
|
|
|
41
41
|
}
|
|
42
42
|
return true;
|
|
43
43
|
}
|
|
44
|
+
function str_starts_with(str, prefix) {
|
|
45
|
+
if (str.length < prefix.length) {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
for (let i = 0; i < prefix.length; i++) {
|
|
49
|
+
let ca = str.charCodeAt(i);
|
|
50
|
+
let cb = prefix.charCodeAt(i);
|
|
51
|
+
if (ca >= 65 && ca <= 90) ca |= 32;
|
|
52
|
+
if (ca !== cb) {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
function str_index_of(str, searchChar) {
|
|
59
|
+
if (searchChar.length === 0) {
|
|
60
|
+
return -1;
|
|
61
|
+
}
|
|
62
|
+
if (searchChar.length === 1) {
|
|
63
|
+
const searchCode = searchChar.charCodeAt(0);
|
|
64
|
+
for (let i = 0; i < str.length; i++) {
|
|
65
|
+
let ca = str.charCodeAt(i);
|
|
66
|
+
if (ca >= 65 && ca <= 90) ca |= 32;
|
|
67
|
+
if (ca === searchCode) {
|
|
68
|
+
return i;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return -1;
|
|
72
|
+
}
|
|
73
|
+
for (let i = 0; i <= str.length - searchChar.length; i++) {
|
|
74
|
+
let match = true;
|
|
75
|
+
for (let j = 0; j < searchChar.length; j++) {
|
|
76
|
+
let ca = str.charCodeAt(i + j);
|
|
77
|
+
let cb = searchChar.charCodeAt(j);
|
|
78
|
+
if (ca >= 65 && ca <= 90) ca |= 32;
|
|
79
|
+
if (ca !== cb) {
|
|
80
|
+
match = false;
|
|
81
|
+
break;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
if (match) {
|
|
85
|
+
return i;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return -1;
|
|
89
|
+
}
|
|
44
90
|
function is_vendor_prefixed(source, start, end) {
|
|
45
91
|
if (source.charCodeAt(start) !== CHAR_MINUS_HYPHEN) {
|
|
46
92
|
return false;
|
|
@@ -60,4 +106,4 @@ function is_vendor_prefixed(source, start, end) {
|
|
|
60
106
|
return false;
|
|
61
107
|
}
|
|
62
108
|
|
|
63
|
-
export { CHAR_AMPERSAND, CHAR_ASTERISK, CHAR_CARET, CHAR_CARRIAGE_RETURN, CHAR_COLON, CHAR_DOLLAR, CHAR_DOUBLE_QUOTE, CHAR_EQUALS, CHAR_FORM_FEED, CHAR_FORWARD_SLASH, CHAR_GREATER_THAN, CHAR_MINUS_HYPHEN, CHAR_NEWLINE, CHAR_PERIOD, CHAR_PIPE, CHAR_PLUS, CHAR_SINGLE_QUOTE, CHAR_SPACE, CHAR_TAB, CHAR_TILDE, is_combinator, is_digit, is_vendor_prefixed, is_whitespace, str_equals };
|
|
109
|
+
export { CHAR_AMPERSAND, CHAR_ASTERISK, CHAR_CARET, CHAR_CARRIAGE_RETURN, CHAR_COLON, CHAR_DOLLAR, CHAR_DOUBLE_QUOTE, CHAR_EQUALS, CHAR_FORM_FEED, CHAR_FORWARD_SLASH, CHAR_GREATER_THAN, CHAR_MINUS_HYPHEN, CHAR_NEWLINE, CHAR_PERIOD, CHAR_PIPE, CHAR_PLUS, CHAR_SINGLE_QUOTE, CHAR_SPACE, CHAR_TAB, CHAR_TILDE, is_combinator, is_digit, is_vendor_prefixed, is_whitespace, str_equals, str_index_of, str_starts_with };
|
package/package.json
CHANGED