@ismail-elkorchi/css-parser 0.1.1 → 0.2.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/README.md +64 -78
- package/dist/internal/cssom/declarations.d.ts +36 -0
- package/dist/internal/cssom/declarations.js +177 -0
- package/dist/internal/generated/css-data.d.ts +2 -0
- package/dist/internal/generated/css-data.js +16229 -0
- package/dist/internal/grammar/catalog-types.d.ts +26 -0
- package/dist/internal/grammar/value-definition.d.ts +63 -0
- package/dist/internal/grammar/value-definition.js +439 -0
- package/dist/internal/properties/matcher.d.ts +29 -0
- package/dist/internal/properties/matcher.js +791 -0
- package/dist/internal/properties/registry.d.ts +24 -0
- package/dist/internal/properties/registry.js +52 -0
- package/dist/internal/selectors/matcher.d.ts +96 -0
- package/dist/internal/selectors/matcher.js +616 -0
- package/dist/internal/selectors/parser.d.ts +2 -0
- package/dist/internal/selectors/parser.js +702 -0
- package/dist/internal/selectors/specificity.d.ts +3 -0
- package/dist/internal/selectors/specificity.js +77 -0
- package/dist/internal/selectors/types.d.ts +110 -0
- package/dist/internal/syntax/ast.d.ts +70 -0
- package/dist/internal/syntax/ast.js +1 -0
- package/dist/internal/syntax/characters.d.ts +8 -0
- package/dist/internal/syntax/characters.js +45 -0
- package/dist/internal/syntax/encoding.d.ts +15 -0
- package/dist/internal/syntax/encoding.js +161 -0
- package/dist/internal/syntax/input.d.ts +23 -0
- package/dist/internal/syntax/input.js +184 -0
- package/dist/internal/syntax/parser.d.ts +26 -0
- package/dist/internal/syntax/parser.js +581 -0
- package/dist/internal/syntax/resources.d.ts +28 -0
- package/dist/internal/syntax/resources.js +135 -0
- package/dist/internal/syntax/serialize.d.ts +10 -0
- package/dist/internal/syntax/serialize.js +630 -0
- package/dist/internal/syntax/token-stream.d.ts +16 -0
- package/dist/internal/syntax/token-stream.js +66 -0
- package/dist/internal/syntax/tokenizer.d.ts +21 -0
- package/dist/internal/syntax/tokenizer.js +571 -0
- package/dist/internal/syntax/tokens.d.ts +115 -0
- package/dist/internal/syntax/tokens.js +1 -0
- package/dist/internal/syntax/types.d.ts +46 -0
- package/dist/internal/syntax/types.js +1 -0
- package/dist/mod.d.ts +7 -1
- package/dist/mod.js +7 -1
- package/dist/public/edits.d.ts +12 -0
- package/dist/public/edits.js +195 -0
- package/dist/public/mod.d.ts +13 -69
- package/dist/public/mod.js +12 -1794
- package/dist/public/parse.d.ts +37 -0
- package/dist/public/parse.js +298 -0
- package/dist/public/traversal.d.ts +13 -0
- package/dist/public/traversal.js +96 -0
- package/dist/public/types.d.ts +78 -264
- package/package.json +32 -56
- package/THIRD_PARTY_NOTICES.md +0 -19
- package/dist/internal/csstree-runtime.d.ts +0 -20
- package/dist/internal/csstree-runtime.js +0 -21
- package/dist/internal/encoding/mod.d.ts +0 -1
- package/dist/internal/encoding/mod.js +0 -1
- package/dist/internal/encoding/sniff.d.ts +0 -14
- package/dist/internal/encoding/sniff.js +0 -95
- package/dist/internal/serializer/mod.d.ts +0 -1
- package/dist/internal/serializer/mod.js +0 -1
- package/dist/internal/serializer/serialize.d.ts +0 -3
- package/dist/internal/serializer/serialize.js +0 -89
- package/dist/internal/tokenizer/mod.d.ts +0 -2
- package/dist/internal/tokenizer/mod.js +0 -1
- package/dist/internal/tokenizer/tokenize.d.ts +0 -2
- package/dist/internal/tokenizer/tokenize.js +0 -39
- package/dist/internal/tokenizer/tokens.d.ts +0 -23
- package/dist/internal/tree/build.d.ts +0 -2
- package/dist/internal/tree/build.js +0 -85
- package/dist/internal/tree/mod.d.ts +0 -2
- package/dist/internal/tree/mod.js +0 -1
- package/dist/internal/tree/types.d.ts +0 -25
- package/dist/internal/vendor/csstree/LICENSE +0 -19
- package/dist/internal/vendor/csstree/csstree.esm.js +0 -12
- package/dist/internal/version.d.ts +0 -1
- package/dist/internal/version.js +0 -1
- package/dist/public/index.d.ts +0 -1
- package/dist/public/index.js +0 -1
- /package/dist/internal/{tokenizer/tokens.js → grammar/catalog-types.js} +0 -0
- /package/dist/internal/{tree → selectors}/types.js +0 -0
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
const ZERO = Object.freeze({ a: 0, b: 0, c: 0 });
|
|
2
|
+
function add(left, right) {
|
|
3
|
+
return Object.freeze({
|
|
4
|
+
a: left.a + right.a,
|
|
5
|
+
b: left.b + right.b,
|
|
6
|
+
c: left.c + right.c
|
|
7
|
+
});
|
|
8
|
+
}
|
|
9
|
+
function compare(left, right) {
|
|
10
|
+
return left.a - right.a || left.b - right.b || left.c - right.c;
|
|
11
|
+
}
|
|
12
|
+
function maximum(selectors, options) {
|
|
13
|
+
let best = ZERO;
|
|
14
|
+
for (const selector of selectors) {
|
|
15
|
+
const candidate = specificityOfComplexSelector(selector, options);
|
|
16
|
+
if (compare(candidate, best) > 0)
|
|
17
|
+
best = candidate;
|
|
18
|
+
}
|
|
19
|
+
return best;
|
|
20
|
+
}
|
|
21
|
+
function pseudoClassSpecificity(pseudo, options) {
|
|
22
|
+
if (pseudo.name === "where" &&
|
|
23
|
+
pseudo.argument.kind === "selector-list") {
|
|
24
|
+
return ZERO;
|
|
25
|
+
}
|
|
26
|
+
if ((pseudo.name === "is" ||
|
|
27
|
+
pseudo.name === "not" ||
|
|
28
|
+
pseudo.name === "has") &&
|
|
29
|
+
pseudo.argument.kind === "selector-list") {
|
|
30
|
+
return maximum(pseudo.argument.selectors, options);
|
|
31
|
+
}
|
|
32
|
+
const own = Object.freeze({ a: 0, b: 1, c: 0 });
|
|
33
|
+
if ((pseudo.name === "nth-child" ||
|
|
34
|
+
pseudo.name === "nth-last-child") &&
|
|
35
|
+
pseudo.argument.kind === "nth") {
|
|
36
|
+
return add(own, maximum(pseudo.argument.of, options));
|
|
37
|
+
}
|
|
38
|
+
return own;
|
|
39
|
+
}
|
|
40
|
+
function pseudoElementSpecificity(pseudo, options) {
|
|
41
|
+
const own = Object.freeze({ a: 0, b: 0, c: 1 });
|
|
42
|
+
if (pseudo.name === "slotted" &&
|
|
43
|
+
pseudo.argument.kind === "selector-list") {
|
|
44
|
+
return add(own, maximum(pseudo.argument.selectors, options));
|
|
45
|
+
}
|
|
46
|
+
return own;
|
|
47
|
+
}
|
|
48
|
+
function simpleSpecificity(simple, options) {
|
|
49
|
+
switch (simple.kind) {
|
|
50
|
+
case "id":
|
|
51
|
+
return Object.freeze({ a: 1, b: 0, c: 0 });
|
|
52
|
+
case "class":
|
|
53
|
+
case "attribute":
|
|
54
|
+
return Object.freeze({ a: 0, b: 1, c: 0 });
|
|
55
|
+
case "pseudo-class":
|
|
56
|
+
return pseudoClassSpecificity(simple, options);
|
|
57
|
+
case "pseudo-element":
|
|
58
|
+
return pseudoElementSpecificity(simple, options);
|
|
59
|
+
case "nesting":
|
|
60
|
+
return options.nesting ?? ZERO;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
export function specificityOfComplexSelector(selector, options = {}) {
|
|
64
|
+
let result = ZERO;
|
|
65
|
+
for (const compound of selector.compounds) {
|
|
66
|
+
if (compound.type !== null && !compound.type.universal) {
|
|
67
|
+
result = add(result, Object.freeze({ a: 0, b: 0, c: 1 }));
|
|
68
|
+
}
|
|
69
|
+
for (const simple of compound.simples) {
|
|
70
|
+
result = add(result, simpleSpecificity(simple, options));
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return result;
|
|
74
|
+
}
|
|
75
|
+
export function specificitiesOfSelectorList(list, options = {}) {
|
|
76
|
+
return Object.freeze(list.selectors.map((selector) => specificityOfComplexSelector(selector, options)));
|
|
77
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import type { ComponentValue, SyntaxDiagnostic } from "../syntax/ast.js";
|
|
2
|
+
import type { ParserResourceLimits, ResourceUsage, SourceSpan } from "../syntax/types.js";
|
|
3
|
+
export type SelectorCombinator = " " | ">" | "+" | "~";
|
|
4
|
+
export interface SelectorType {
|
|
5
|
+
readonly kind: "type";
|
|
6
|
+
readonly namespace: string | null;
|
|
7
|
+
readonly name: string;
|
|
8
|
+
readonly universal: boolean;
|
|
9
|
+
readonly span: SourceSpan;
|
|
10
|
+
}
|
|
11
|
+
export interface SelectorId {
|
|
12
|
+
readonly kind: "id";
|
|
13
|
+
readonly value: string;
|
|
14
|
+
readonly span: SourceSpan;
|
|
15
|
+
}
|
|
16
|
+
export interface SelectorClass {
|
|
17
|
+
readonly kind: "class";
|
|
18
|
+
readonly value: string;
|
|
19
|
+
readonly span: SourceSpan;
|
|
20
|
+
}
|
|
21
|
+
export type SelectorAttributeMatcher = "=" | "~=" | "|=" | "^=" | "$=" | "*=";
|
|
22
|
+
export interface SelectorAttribute {
|
|
23
|
+
readonly kind: "attribute";
|
|
24
|
+
readonly namespace: string | null;
|
|
25
|
+
readonly name: string;
|
|
26
|
+
readonly matcher: SelectorAttributeMatcher | null;
|
|
27
|
+
readonly value: string | null;
|
|
28
|
+
readonly modifier: "i" | "s" | null;
|
|
29
|
+
readonly span: SourceSpan;
|
|
30
|
+
}
|
|
31
|
+
export type SelectorPseudoArgument = {
|
|
32
|
+
readonly kind: "none";
|
|
33
|
+
} | {
|
|
34
|
+
readonly kind: "raw";
|
|
35
|
+
readonly value: readonly ComponentValue[];
|
|
36
|
+
} | {
|
|
37
|
+
readonly kind: "selector-list";
|
|
38
|
+
readonly selectors: readonly ComplexSelector[];
|
|
39
|
+
readonly forgiving: boolean;
|
|
40
|
+
readonly relative: boolean;
|
|
41
|
+
} | {
|
|
42
|
+
readonly kind: "nth";
|
|
43
|
+
readonly a: number;
|
|
44
|
+
readonly b: number;
|
|
45
|
+
readonly of: readonly ComplexSelector[];
|
|
46
|
+
};
|
|
47
|
+
export interface SelectorPseudoClass {
|
|
48
|
+
readonly kind: "pseudo-class";
|
|
49
|
+
readonly name: string;
|
|
50
|
+
readonly argument: SelectorPseudoArgument;
|
|
51
|
+
readonly span: SourceSpan;
|
|
52
|
+
}
|
|
53
|
+
export interface SelectorPseudoElement {
|
|
54
|
+
readonly kind: "pseudo-element";
|
|
55
|
+
readonly name: string;
|
|
56
|
+
readonly argument: SelectorPseudoArgument;
|
|
57
|
+
readonly span: SourceSpan;
|
|
58
|
+
}
|
|
59
|
+
export interface SelectorNesting {
|
|
60
|
+
readonly kind: "nesting";
|
|
61
|
+
readonly span: SourceSpan;
|
|
62
|
+
}
|
|
63
|
+
export type SimpleSelector = SelectorId | SelectorClass | SelectorAttribute | SelectorPseudoClass | SelectorPseudoElement | SelectorNesting;
|
|
64
|
+
export interface CompoundSelector {
|
|
65
|
+
readonly type: SelectorType | null;
|
|
66
|
+
readonly simples: readonly SimpleSelector[];
|
|
67
|
+
readonly span: SourceSpan;
|
|
68
|
+
}
|
|
69
|
+
export interface ComplexSelector {
|
|
70
|
+
readonly leadingCombinator: SelectorCombinator | null;
|
|
71
|
+
readonly compounds: readonly CompoundSelector[];
|
|
72
|
+
readonly combinators: readonly SelectorCombinator[];
|
|
73
|
+
readonly span: SourceSpan;
|
|
74
|
+
}
|
|
75
|
+
export interface SelectorList {
|
|
76
|
+
readonly selectors: readonly ComplexSelector[];
|
|
77
|
+
readonly span: SourceSpan;
|
|
78
|
+
}
|
|
79
|
+
export type SelectorDiagnosticCode = "empty-selector" | "invalid-selector" | "invalid-combinator" | "invalid-attribute" | "invalid-pseudo" | "invalid-nth";
|
|
80
|
+
export interface SelectorDiagnostic {
|
|
81
|
+
readonly kind: "selector";
|
|
82
|
+
readonly code: SelectorDiagnosticCode;
|
|
83
|
+
readonly message: string;
|
|
84
|
+
readonly span: SourceSpan;
|
|
85
|
+
readonly specRef: string;
|
|
86
|
+
}
|
|
87
|
+
export interface SelectorParseSuccess {
|
|
88
|
+
readonly ok: true;
|
|
89
|
+
readonly value: SelectorList;
|
|
90
|
+
readonly errors: readonly (SyntaxDiagnostic | SelectorDiagnostic)[];
|
|
91
|
+
readonly usage: ResourceUsage;
|
|
92
|
+
}
|
|
93
|
+
export interface SelectorParseFailure {
|
|
94
|
+
readonly ok: false;
|
|
95
|
+
readonly errors: readonly (SyntaxDiagnostic | SelectorDiagnostic)[];
|
|
96
|
+
readonly usage: ResourceUsage;
|
|
97
|
+
}
|
|
98
|
+
export type SelectorParseResult = SelectorParseSuccess | SelectorParseFailure;
|
|
99
|
+
export interface SelectorParserOptions {
|
|
100
|
+
readonly limits?: ParserResourceLimits;
|
|
101
|
+
readonly signal?: AbortSignal;
|
|
102
|
+
}
|
|
103
|
+
export interface SelectorSpecificity {
|
|
104
|
+
readonly a: number;
|
|
105
|
+
readonly b: number;
|
|
106
|
+
readonly c: number;
|
|
107
|
+
}
|
|
108
|
+
export interface SelectorSpecificityOptions {
|
|
109
|
+
readonly nesting?: SelectorSpecificity;
|
|
110
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import type { AtKeywordToken, BadStringToken, BadUrlToken, CdcToken, CdoToken, CloseCurlyToken, CloseParenToken, CloseSquareToken, ColonToken, CommaToken, DelimToken, DimensionToken, HashToken, IdentToken, NumberToken, PercentageToken, SemicolonToken, StringToken, TokenizerDiagnostic, UnicodeRangeToken, UrlToken, WhitespaceToken } from "./tokens.js";
|
|
2
|
+
import type { ResourceUsage, SourceSpan } from "./types.js";
|
|
3
|
+
interface SyntaxNodeBase {
|
|
4
|
+
readonly id: number;
|
|
5
|
+
readonly span: SourceSpan;
|
|
6
|
+
}
|
|
7
|
+
export type PreservedToken = IdentToken | AtKeywordToken | HashToken | StringToken | BadStringToken | UrlToken | BadUrlToken | DelimToken | NumberToken | PercentageToken | DimensionToken | UnicodeRangeToken | WhitespaceToken | CdoToken | CdcToken | ColonToken | SemicolonToken | CommaToken | CloseSquareToken | CloseParenToken | CloseCurlyToken;
|
|
8
|
+
export interface CssFunction extends SyntaxNodeBase {
|
|
9
|
+
readonly kind: "function-block";
|
|
10
|
+
readonly name: string;
|
|
11
|
+
readonly value: readonly ComponentValue[];
|
|
12
|
+
}
|
|
13
|
+
export interface CssSimpleBlock extends SyntaxNodeBase {
|
|
14
|
+
readonly kind: "simple-block";
|
|
15
|
+
readonly associatedToken: "open-square" | "open-paren" | "open-curly";
|
|
16
|
+
readonly value: readonly ComponentValue[];
|
|
17
|
+
}
|
|
18
|
+
export type ComponentValue = PreservedToken | CssFunction | CssSimpleBlock;
|
|
19
|
+
export interface CssDeclaration extends SyntaxNodeBase {
|
|
20
|
+
readonly kind: "declaration";
|
|
21
|
+
readonly name: string;
|
|
22
|
+
readonly value: readonly ComponentValue[];
|
|
23
|
+
readonly important: boolean;
|
|
24
|
+
readonly originalText?: string;
|
|
25
|
+
}
|
|
26
|
+
export interface CssAtRule extends SyntaxNodeBase {
|
|
27
|
+
readonly kind: "at-rule";
|
|
28
|
+
readonly name: string;
|
|
29
|
+
readonly prelude: readonly ComponentValue[];
|
|
30
|
+
readonly block: CssBlock | null;
|
|
31
|
+
}
|
|
32
|
+
export interface CssQualifiedRule extends SyntaxNodeBase {
|
|
33
|
+
readonly kind: "qualified-rule";
|
|
34
|
+
readonly prelude: readonly ComponentValue[];
|
|
35
|
+
readonly block: CssBlock;
|
|
36
|
+
}
|
|
37
|
+
export type CssRule = CssAtRule | CssQualifiedRule;
|
|
38
|
+
export type CssBlockItem = CssDeclaration | CssRule;
|
|
39
|
+
export interface CssBlock extends SyntaxNodeBase {
|
|
40
|
+
readonly kind: "block";
|
|
41
|
+
readonly items: readonly CssBlockItem[];
|
|
42
|
+
}
|
|
43
|
+
export interface CssStylesheet extends SyntaxNodeBase {
|
|
44
|
+
readonly kind: "stylesheet";
|
|
45
|
+
readonly rules: readonly CssRule[];
|
|
46
|
+
}
|
|
47
|
+
export type ParserDiagnosticCode = "empty-input" | "trailing-input" | "invalid-rule" | "invalid-declaration" | "unexpected-closing-token" | "bad-string-token" | "bad-url-token";
|
|
48
|
+
export interface ParserDiagnostic {
|
|
49
|
+
readonly kind: "parser";
|
|
50
|
+
readonly code: ParserDiagnosticCode;
|
|
51
|
+
readonly message: string;
|
|
52
|
+
readonly span: SourceSpan;
|
|
53
|
+
readonly specRef: string;
|
|
54
|
+
}
|
|
55
|
+
export type SyntaxDiagnostic = TokenizerDiagnostic | ParserDiagnostic;
|
|
56
|
+
interface SyntaxResultBase {
|
|
57
|
+
readonly errors: readonly SyntaxDiagnostic[];
|
|
58
|
+
readonly usage: ResourceUsage;
|
|
59
|
+
}
|
|
60
|
+
export interface SyntaxSuccess<T> extends SyntaxResultBase {
|
|
61
|
+
readonly ok: true;
|
|
62
|
+
readonly value: T;
|
|
63
|
+
}
|
|
64
|
+
export interface SyntaxFailure extends SyntaxResultBase {
|
|
65
|
+
readonly ok: false;
|
|
66
|
+
}
|
|
67
|
+
export type SyntaxResult<T> = SyntaxSuccess<T> | SyntaxFailure;
|
|
68
|
+
export type ComponentValuesResult = SyntaxResult<readonly ComponentValue[]>;
|
|
69
|
+
export type CommaSeparatedComponentValuesResult = SyntaxResult<readonly (readonly ComponentValue[])[]>;
|
|
70
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export declare function isWhitespace(value: number | null): boolean;
|
|
2
|
+
export declare function isDigit(value: number | null): boolean;
|
|
3
|
+
export declare function isHexDigit(value: number | null): boolean;
|
|
4
|
+
export declare function isAsciiLetter(value: number | null): boolean;
|
|
5
|
+
export declare function isNonAsciiIdent(value: number | null): boolean;
|
|
6
|
+
export declare function isIdentStart(value: number | null): boolean;
|
|
7
|
+
export declare function isIdent(value: number | null): boolean;
|
|
8
|
+
export declare function isNonPrintable(value: number | null): boolean;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
export function isWhitespace(value) {
|
|
2
|
+
return value === 0x09 || value === 0x0a || value === 0x20;
|
|
3
|
+
}
|
|
4
|
+
export function isDigit(value) {
|
|
5
|
+
return value !== null && value >= 0x30 && value <= 0x39;
|
|
6
|
+
}
|
|
7
|
+
export function isHexDigit(value) {
|
|
8
|
+
return (isDigit(value) ||
|
|
9
|
+
(value !== null && value >= 0x41 && value <= 0x46) ||
|
|
10
|
+
(value !== null && value >= 0x61 && value <= 0x66));
|
|
11
|
+
}
|
|
12
|
+
export function isAsciiLetter(value) {
|
|
13
|
+
return ((value !== null && value >= 0x41 && value <= 0x5a) ||
|
|
14
|
+
(value !== null && value >= 0x61 && value <= 0x7a));
|
|
15
|
+
}
|
|
16
|
+
export function isNonAsciiIdent(value) {
|
|
17
|
+
return (value === 0x00b7 ||
|
|
18
|
+
(value !== null && value >= 0x00c0 && value <= 0x00d6) ||
|
|
19
|
+
(value !== null && value >= 0x00d8 && value <= 0x00f6) ||
|
|
20
|
+
(value !== null && value >= 0x00f8 && value <= 0x037d) ||
|
|
21
|
+
(value !== null && value >= 0x037f && value <= 0x1fff) ||
|
|
22
|
+
value === 0x200c ||
|
|
23
|
+
value === 0x200d ||
|
|
24
|
+
value === 0x203f ||
|
|
25
|
+
value === 0x2040 ||
|
|
26
|
+
(value !== null && value >= 0x2070 && value <= 0x218f) ||
|
|
27
|
+
(value !== null && value >= 0x2c00 && value <= 0x2fef) ||
|
|
28
|
+
(value !== null && value >= 0x3001 && value <= 0xd7ff) ||
|
|
29
|
+
(value !== null && value >= 0xf900 && value <= 0xfdcf) ||
|
|
30
|
+
(value !== null && value >= 0xfdf0 && value <= 0xfffd) ||
|
|
31
|
+
(value !== null && value >= 0x10000));
|
|
32
|
+
}
|
|
33
|
+
export function isIdentStart(value) {
|
|
34
|
+
return value === 0x5f || isAsciiLetter(value) || isNonAsciiIdent(value);
|
|
35
|
+
}
|
|
36
|
+
export function isIdent(value) {
|
|
37
|
+
return isIdentStart(value) || isDigit(value) || value === 0x2d;
|
|
38
|
+
}
|
|
39
|
+
export function isNonPrintable(value) {
|
|
40
|
+
return (value !== null &&
|
|
41
|
+
((value >= 0x00 && value <= 0x08) ||
|
|
42
|
+
value === 0x0b ||
|
|
43
|
+
(value >= 0x0e && value <= 0x1f) ||
|
|
44
|
+
value === 0x7f));
|
|
45
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { EncodingDecision, EncodingOptions } from "./types.js";
|
|
2
|
+
export declare function canonicalizeEncodingLabel(label: string): string | null;
|
|
3
|
+
export declare function sniffCssEncoding(bytes: Uint8Array, options?: EncodingOptions): EncodingDecision;
|
|
4
|
+
export declare function decodeCssBytes(bytes: Uint8Array, options?: EncodingOptions): {
|
|
5
|
+
readonly text: string;
|
|
6
|
+
readonly decision: EncodingDecision;
|
|
7
|
+
};
|
|
8
|
+
export declare class CssEncodingSniffer {
|
|
9
|
+
#private;
|
|
10
|
+
constructor(options?: EncodingOptions);
|
|
11
|
+
get bufferedBytes(): number;
|
|
12
|
+
get decision(): EncodingDecision | null;
|
|
13
|
+
write(chunk: Uint8Array): EncodingDecision | null;
|
|
14
|
+
finish(): EncodingDecision;
|
|
15
|
+
}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
const DEFAULT_MAX_CHARSET_BYTES = 1024;
|
|
2
|
+
const CHARSET_PREFIX = Uint8Array.from([
|
|
3
|
+
0x40, 0x63, 0x68, 0x61, 0x72, 0x73, 0x65, 0x74, 0x20, 0x22
|
|
4
|
+
]);
|
|
5
|
+
function detectBom(bytes, final) {
|
|
6
|
+
if (bytes.length >= 3 && bytes[0] === 0xef && bytes[1] === 0xbb && bytes[2] === 0xbf) {
|
|
7
|
+
return { encoding: "utf-8", bytes: 3 };
|
|
8
|
+
}
|
|
9
|
+
if (bytes.length >= 2 && bytes[0] === 0xfe && bytes[1] === 0xff) {
|
|
10
|
+
return { encoding: "utf-16be", bytes: 2 };
|
|
11
|
+
}
|
|
12
|
+
if (bytes.length >= 2 && bytes[0] === 0xff && bytes[1] === 0xfe) {
|
|
13
|
+
return { encoding: "utf-16le", bytes: 2 };
|
|
14
|
+
}
|
|
15
|
+
if (!final &&
|
|
16
|
+
(bytes.length === 0 ||
|
|
17
|
+
(bytes.length === 1 && (bytes[0] === 0xef || bytes[0] === 0xfe || bytes[0] === 0xff)) ||
|
|
18
|
+
(bytes.length === 2 && bytes[0] === 0xef && bytes[1] === 0xbb))) {
|
|
19
|
+
return undefined;
|
|
20
|
+
}
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
export function canonicalizeEncodingLabel(label) {
|
|
24
|
+
const normalized = label.trim().toLowerCase();
|
|
25
|
+
if (normalized.length === 0) {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
try {
|
|
29
|
+
return new TextDecoder(normalized).encoding.toLowerCase();
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
function normalizedCharsetEncoding(label) {
|
|
36
|
+
const encoding = canonicalizeEncodingLabel(label);
|
|
37
|
+
if (encoding === "utf-16be" || encoding === "utf-16le") {
|
|
38
|
+
return "utf-8";
|
|
39
|
+
}
|
|
40
|
+
return encoding;
|
|
41
|
+
}
|
|
42
|
+
function scanCharset(bytes, final, maximum) {
|
|
43
|
+
const length = Math.min(bytes.length, maximum);
|
|
44
|
+
for (let index = 0; index < Math.min(length, CHARSET_PREFIX.length); index += 1) {
|
|
45
|
+
if (bytes[index] !== CHARSET_PREFIX[index]) {
|
|
46
|
+
return { kind: "absent" };
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
if (length < CHARSET_PREFIX.length) {
|
|
50
|
+
return final || length >= maximum ? { kind: "absent" } : { kind: "pending" };
|
|
51
|
+
}
|
|
52
|
+
let label = "";
|
|
53
|
+
for (let index = CHARSET_PREFIX.length; index < length; index += 1) {
|
|
54
|
+
const byte = bytes[index];
|
|
55
|
+
if (byte === 0x22) {
|
|
56
|
+
if (index + 1 >= length) {
|
|
57
|
+
return final || length >= maximum ? { kind: "absent" } : { kind: "pending" };
|
|
58
|
+
}
|
|
59
|
+
if (bytes[index + 1] !== 0x3b) {
|
|
60
|
+
return { kind: "absent" };
|
|
61
|
+
}
|
|
62
|
+
const encoding = normalizedCharsetEncoding(label);
|
|
63
|
+
return encoding === null ? { kind: "absent" } : { kind: "encoding", encoding };
|
|
64
|
+
}
|
|
65
|
+
if (byte === undefined || byte > 0x7f) {
|
|
66
|
+
return { kind: "absent" };
|
|
67
|
+
}
|
|
68
|
+
label += String.fromCharCode(byte);
|
|
69
|
+
}
|
|
70
|
+
return final || length >= maximum ? { kind: "absent" } : { kind: "pending" };
|
|
71
|
+
}
|
|
72
|
+
function normalizedMaximum(options) {
|
|
73
|
+
const value = options.maxCharsetBytes ?? DEFAULT_MAX_CHARSET_BYTES;
|
|
74
|
+
if (!Number.isSafeInteger(value) || value < CHARSET_PREFIX.length + 2) {
|
|
75
|
+
throw new RangeError(`maxCharsetBytes must be a safe integer of at least ${String(CHARSET_PREFIX.length + 2)}`);
|
|
76
|
+
}
|
|
77
|
+
return value;
|
|
78
|
+
}
|
|
79
|
+
function fallbackDecision(options) {
|
|
80
|
+
const environment = options.environmentEncodingLabel === undefined
|
|
81
|
+
? null
|
|
82
|
+
: canonicalizeEncodingLabel(options.environmentEncodingLabel);
|
|
83
|
+
if (environment !== null) {
|
|
84
|
+
return { encoding: environment, source: "environment", bomBytes: 0 };
|
|
85
|
+
}
|
|
86
|
+
const fallback = options.defaultEncodingLabel === undefined
|
|
87
|
+
? "utf-8"
|
|
88
|
+
: canonicalizeEncodingLabel(options.defaultEncodingLabel);
|
|
89
|
+
return {
|
|
90
|
+
encoding: fallback ?? "utf-8",
|
|
91
|
+
source: "default",
|
|
92
|
+
bomBytes: 0
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
function decide(bytes, options, final) {
|
|
96
|
+
const bom = detectBom(bytes, final);
|
|
97
|
+
if (bom === undefined) {
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
if (bom !== null) {
|
|
101
|
+
return { encoding: bom.encoding, source: "bom", bomBytes: bom.bytes };
|
|
102
|
+
}
|
|
103
|
+
if (options.transportEncodingLabel !== undefined) {
|
|
104
|
+
const transport = canonicalizeEncodingLabel(options.transportEncodingLabel);
|
|
105
|
+
if (transport !== null) {
|
|
106
|
+
return { encoding: transport, source: "transport", bomBytes: 0 };
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
const maximum = normalizedMaximum(options);
|
|
110
|
+
const charset = scanCharset(bytes, final, maximum);
|
|
111
|
+
if (charset.kind === "pending") {
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
if (charset.kind === "encoding") {
|
|
115
|
+
return { encoding: charset.encoding, source: "charset", bomBytes: 0 };
|
|
116
|
+
}
|
|
117
|
+
return fallbackDecision(options);
|
|
118
|
+
}
|
|
119
|
+
export function sniffCssEncoding(bytes, options = {}) {
|
|
120
|
+
return decide(bytes, options, true) ?? fallbackDecision(options);
|
|
121
|
+
}
|
|
122
|
+
export function decodeCssBytes(bytes, options = {}) {
|
|
123
|
+
const decision = sniffCssEncoding(bytes, options);
|
|
124
|
+
const decoder = new TextDecoder(decision.encoding);
|
|
125
|
+
return Object.freeze({
|
|
126
|
+
text: decoder.decode(bytes),
|
|
127
|
+
decision
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
export class CssEncodingSniffer {
|
|
131
|
+
#options;
|
|
132
|
+
#buffer;
|
|
133
|
+
#length = 0;
|
|
134
|
+
#decision = null;
|
|
135
|
+
constructor(options = {}) {
|
|
136
|
+
const maximum = normalizedMaximum(options);
|
|
137
|
+
this.#options = Object.freeze({ ...options, maxCharsetBytes: maximum });
|
|
138
|
+
this.#buffer = new Uint8Array(maximum);
|
|
139
|
+
}
|
|
140
|
+
get bufferedBytes() {
|
|
141
|
+
return this.#length;
|
|
142
|
+
}
|
|
143
|
+
get decision() {
|
|
144
|
+
return this.#decision;
|
|
145
|
+
}
|
|
146
|
+
write(chunk) {
|
|
147
|
+
if (this.#decision !== null) {
|
|
148
|
+
return this.#decision;
|
|
149
|
+
}
|
|
150
|
+
const available = this.#buffer.length - this.#length;
|
|
151
|
+
const consumed = Math.min(available, chunk.byteLength);
|
|
152
|
+
this.#buffer.set(chunk.subarray(0, consumed), this.#length);
|
|
153
|
+
this.#length += consumed;
|
|
154
|
+
this.#decision = decide(this.#buffer.subarray(0, this.#length), this.#options, false);
|
|
155
|
+
return this.#decision;
|
|
156
|
+
}
|
|
157
|
+
finish() {
|
|
158
|
+
this.#decision ??= decide(this.#buffer.subarray(0, this.#length), this.#options, true) ?? fallbackDecision(this.#options);
|
|
159
|
+
return this.#decision;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { ResourceGuard } from "./resources.js";
|
|
2
|
+
import type { InputCodePoint, SourcePosition } from "./types.js";
|
|
3
|
+
export interface InputCursorMark {
|
|
4
|
+
readonly offset: number;
|
|
5
|
+
readonly line: number;
|
|
6
|
+
readonly column: number;
|
|
7
|
+
}
|
|
8
|
+
export declare function utf8ByteLength(input: string): number;
|
|
9
|
+
export declare class InputCursor {
|
|
10
|
+
#private;
|
|
11
|
+
constructor(input: string, guard?: ResourceGuard);
|
|
12
|
+
get sourceLength(): number;
|
|
13
|
+
get offset(): number;
|
|
14
|
+
get eof(): boolean;
|
|
15
|
+
position(): SourcePosition;
|
|
16
|
+
mark(): InputCursorMark;
|
|
17
|
+
restore(mark: InputCursorMark): void;
|
|
18
|
+
peek(distance?: number): number | null;
|
|
19
|
+
consume(): InputCodePoint | null;
|
|
20
|
+
reconsume(): void;
|
|
21
|
+
slice(start: number, end: number): string;
|
|
22
|
+
}
|
|
23
|
+
export declare function preprocessCssInput(input: string, guard?: ResourceGuard): string;
|