| | |
| | | "use strict"; |
| | | |
| | | /** |
| | | * Defines the css token callbacks type used by this module. |
| | | * @typedef {object} CssTokenCallbacks |
| | | * @property {((input: string, start: number, end: number, innerStart: number, innerEnd: number) => number)=} url |
| | | * @property {((input: string, start: number, end: number) => number)=} comment |
| | | * @property {((input: string, start: number, end: number) => number)=} whitespace |
| | | * @property {((input: string, start: number, end: number) => number)=} string |
| | | * @property {((input: string, start: number, end: number) => number)=} leftCurlyBracket |
| | | * @property {((input: string, start: number, end: number) => number)=} rightCurlyBracket |
| | | * @property {((input: string, start: number, end: number) => number)=} leftParenthesis |
| | | * @property {((input: string, start: number, end: number) => number)=} rightParenthesis |
| | | * @property {((input: string, start: number, end: number) => number)=} leftSquareBracket |
| | | * @property {((input: string, start: number, end: number) => number)=} rightSquareBracket |
| | | * @property {((input: string, start: number, end: number) => number)=} function |
| | | * @property {((input: string, start: number, end: number, innerStart: number, innerEnd: number) => number)=} url |
| | | * @property {((input: string, start: number, end: number) => number)=} colon |
| | | * @property {((input: string, start: number, end: number) => number)=} atKeyword |
| | | * @property {((input: string, start: number, end: number) => number)=} delim |
| | | * @property {((input: string, start: number, end: number) => number)=} identifier |
| | | * @property {((input: string, start: number, end: number) => number)=} percentage |
| | | * @property {((input: string, start: number, end: number) => number)=} number |
| | | * @property {((input: string, start: number, end: number) => number)=} dimension |
| | | * @property {((input: string, start: number, end: number, isId: boolean) => number)=} hash |
| | | * @property {((input: string, start: number, end: number) => number)=} leftCurlyBracket |
| | | * @property {((input: string, start: number, end: number) => number)=} rightCurlyBracket |
| | | * @property {((input: string, start: number, end: number) => number)=} semicolon |
| | | * @property {((input: string, start: number, end: number) => number)=} comma |
| | | * @property {((input: string, start: number, end: number) => number)=} cdo |
| | | * @property {((input: string, start: number, end: number) => number)=} cdc |
| | | * @property {((input: string, start: number, end: number) => number)=} badStringToken |
| | | * @property {((input: string, start: number, end: number) => number)=} badUrlToken |
| | | * @property {(() => boolean)=} needTerminate |
| | | */ |
| | | |
| | |
| | | const CC_GREATER_THAN_SIGN = ">".charCodeAt(0); |
| | | |
| | | /** @type {CharHandler} */ |
| | | const consumeSpace = (input, pos, _callbacks) => { |
| | | const consumeSpace = (input, pos, callbacks) => { |
| | | const start = pos - 1; |
| | | |
| | | // Consume as much whitespace as possible. |
| | | while (_isWhiteSpace(input.charCodeAt(pos))) { |
| | | pos++; |
| | | } |
| | | |
| | | // Return a <whitespace-token>. |
| | | if (callbacks.whitespace !== undefined) { |
| | | return callbacks.whitespace(input, start, pos); |
| | | } |
| | | |
| | | return pos; |
| | | }; |
| | | |
| | |
| | | // Replace any U+000D CARRIAGE RETURN (CR) code points, U+000C FORM FEED (FF) code points, or pairs of U+000D CARRIAGE RETURN (CR) followed by U+000A LINE FEED (LF) in input by a single U+000A LINE FEED (LF) code point. |
| | | |
| | | /** |
| | | * Checks whether newline true, if cc is a newline. |
| | | * @param {number} cc char code |
| | | * @returns {boolean} true, if cc is a newline |
| | | */ |
| | |
| | | cc === CC_LINE_FEED || cc === CC_CARRIAGE_RETURN || cc === CC_FORM_FEED; |
| | | |
| | | /** |
| | | * Consume extra newline. |
| | | * @param {number} cc char code |
| | | * @param {string} input input |
| | | * @param {number} pos position |
| | |
| | | }; |
| | | |
| | | /** |
| | | * Checks whether space true, if cc is a space (U+0009 CHARACTER TABULATION or U+0020 SPACE). |
| | | * @param {number} cc char code |
| | | * @returns {boolean} true, if cc is a space (U+0009 CHARACTER TABULATION or U+0020 SPACE) |
| | | */ |
| | | const _isSpace = (cc) => cc === CC_TAB || cc === CC_SPACE; |
| | | |
| | | /** |
| | | * Checks whether white space true, if cc is a whitespace. |
| | | * @param {number} cc char code |
| | | * @returns {boolean} true, if cc is a whitespace |
| | | */ |
| | |
| | | cc >= 0x80; |
| | | |
| | | /** @type {CharHandler} */ |
| | | const consumeDelimToken = (input, pos, _callbacks) => |
| | | const consumeDelimToken = (input, pos, callbacks) => { |
| | | // Return a <delim-token> with its value set to the current input code point. |
| | | pos; |
| | | if (callbacks.delim) { |
| | | pos = callbacks.delim(input, pos - 1, pos); |
| | | } |
| | | |
| | | return pos; |
| | | }; |
| | | |
| | | /** @type {CharHandler} */ |
| | | const consumeComments = (input, pos, callbacks) => { |
| | |
| | | }; |
| | | |
| | | /** |
| | | * Checks whether hex digit true, if cc is a hex digit. |
| | | * @param {number} cc char code |
| | | * @returns {boolean} true, if cc is a hex digit |
| | | */ |
| | |
| | | (cc >= CC_LOWER_A && cc <= CC_LOWER_F); |
| | | |
| | | /** |
| | | * Consume an escaped code point. |
| | | * @param {string} input input |
| | | * @param {number} pos position |
| | | * @returns {number} position |
| | |
| | | // Reconsume the current input code point, create a <bad-string-token>, and return it. |
| | | else if (_isNewline(cc)) { |
| | | pos--; |
| | | |
| | | if (callbacks.badStringToken !== undefined) { |
| | | return callbacks.badStringToken(input, start, pos); |
| | | } |
| | | |
| | | // bad string |
| | | return pos; |
| | | } |
| | |
| | | }; |
| | | |
| | | /** |
| | | * Checks whether this object is non ascii code point. |
| | | * @param {number} cc char code |
| | | * @param {number} q char code |
| | | * @returns {boolean} is non-ASCII code point |
| | |
| | | cc > 0x80; |
| | | |
| | | /** |
| | | * Checks whether this object is letter. |
| | | * @param {number} cc char code |
| | | * @returns {boolean} is letter |
| | | */ |
| | |
| | | (cc >= CC_UPPER_A && cc <= CC_UPPER_Z); |
| | | |
| | | /** |
| | | * Is ident start code point. |
| | | * @param {number} cc char code |
| | | * @param {number} q char code |
| | | * @returns {boolean} is identifier start code |
| | |
| | | isLetter(cc) || isNonASCIICodePoint(cc, q) || cc === CC_LOW_LINE; |
| | | |
| | | /** |
| | | * Is ident code point. |
| | | * @param {number} cc char code |
| | | * @param {number} q char code |
| | | * @returns {boolean} is identifier code |
| | |
| | | const _isIdentCodePoint = (cc, q) => |
| | | _isIdentStartCodePoint(cc, q) || _isDigit(cc) || cc === CC_HYPHEN_MINUS; |
| | | /** |
| | | * Checks whether digit is digit. |
| | | * @param {number} cc char code |
| | | * @returns {boolean} is digit |
| | | */ |
| | | const _isDigit = (cc) => cc >= CC_0 && cc <= CC_9; |
| | | |
| | | /** |
| | | * If two code points are valid escape. |
| | | * @param {string} input input |
| | | * @param {number} pos position |
| | | * @param {number=} f first code point |
| | |
| | | }; |
| | | |
| | | /** |
| | | * If three code points would start an ident sequence. |
| | | * @param {string} input input |
| | | * @param {number} pos position |
| | | * @param {number=} f first |
| | |
| | | }; |
| | | |
| | | /** |
| | | * If three code points would start a number. |
| | | * @param {string} input input |
| | | * @param {number} pos position |
| | | * @param {number=} f first |
| | |
| | | |
| | | const first = f || input.charCodeAt(pos - 1); |
| | | const second = s || input.charCodeAt(pos); |
| | | const third = t || input.charCodeAt(pos); |
| | | const third = t || input.charCodeAt(pos + 1); |
| | | |
| | | // Look at the first code point: |
| | | |
| | |
| | | return pos; |
| | | } |
| | | |
| | | if (callbacks.delim !== undefined) { |
| | | return callbacks.delim(input, start, pos); |
| | | } |
| | | |
| | | // Otherwise, return a <delim-token> with its value set to the current input code point. |
| | | return pos; |
| | | }; |
| | |
| | | input.charCodeAt(pos) === CC_HYPHEN_MINUS && |
| | | input.charCodeAt(pos + 1) === CC_GREATER_THAN_SIGN |
| | | ) { |
| | | if (callbacks.cdc !== undefined) { |
| | | return callbacks.cdc(input, pos - 1, pos + 2); |
| | | } |
| | | |
| | | return pos + 2; |
| | | } |
| | | // Otherwise, if the input stream starts with an ident sequence, reconsume the current input code point, consume an ident-like token, and return it. |
| | | else if (_ifThreeCodePointsWouldStartAnIdentSequence(input, pos)) { |
| | | pos--; |
| | | return consumeAnIdentLikeToken(input, pos, callbacks); |
| | | } |
| | | |
| | | if (callbacks.delim !== undefined) { |
| | | return callbacks.delim(input, pos - 1, pos); |
| | | } |
| | | |
| | | // Otherwise, return a <delim-token> with its value set to the current input code point. |
| | |
| | | |
| | | /** @type {CharHandler} */ |
| | | const consumePlusSign = (input, pos, callbacks) => { |
| | | const start = pos - 1; |
| | | |
| | | // If the input stream starts with a number, reconsume the current input code point, consume a numeric token, and return it. |
| | | if (_ifThreeCodePointsWouldStartANumber(input, pos)) { |
| | | pos--; |
| | |
| | | } |
| | | |
| | | // Otherwise, return a <delim-token> with its value set to the current input code point. |
| | | if (callbacks.delim !== undefined) { |
| | | return callbacks.delim(input, start, pos); |
| | | } |
| | | |
| | | return pos; |
| | | }; |
| | | |
| | |
| | | // This section describes how to consume a numeric token from a stream of code points. |
| | | // It returns either a <number-token>, <percentage-token>, or <dimension-token>. |
| | | |
| | | const start = pos; |
| | | |
| | | // Consume a number and let number be the result. |
| | | pos = _consumeANumber(input, pos, callbacks); |
| | | |
| | |
| | | third |
| | | ) |
| | | ) { |
| | | return _consumeAnIdentSequence(input, pos, callbacks); |
| | | pos = _consumeAnIdentSequence(input, pos, callbacks); |
| | | |
| | | if (callbacks.dimension !== undefined) { |
| | | return callbacks.dimension(input, start, pos); |
| | | } |
| | | |
| | | return pos; |
| | | } |
| | | // Otherwise, if the next input code point is U+0025 PERCENTAGE SIGN (%), consume it. |
| | | // Create a <percentage-token> with the same value as number, and return it. |
| | | else if (first === CC_PERCENTAGE) { |
| | | if (callbacks.percentage !== undefined) { |
| | | return callbacks.percentage(input, start, pos + 1); |
| | | } |
| | | |
| | | return pos + 1; |
| | | } |
| | | |
| | | // Otherwise, create a <number-token> with the same value and type flag as number, and return it. |
| | | if (callbacks.number !== undefined) { |
| | | return callbacks.number(input, start, pos); |
| | | } |
| | | |
| | | return pos; |
| | | }; |
| | | |
| | |
| | | if (callbacks.colon !== undefined) { |
| | | return callbacks.colon(input, pos - 1, pos); |
| | | } |
| | | |
| | | return pos; |
| | | }; |
| | | |
| | |
| | | if (callbacks.leftParenthesis !== undefined) { |
| | | return callbacks.leftParenthesis(input, pos - 1, pos); |
| | | } |
| | | |
| | | return pos; |
| | | }; |
| | | |
| | |
| | | if (callbacks.rightParenthesis !== undefined) { |
| | | return callbacks.rightParenthesis(input, pos - 1, pos); |
| | | } |
| | | |
| | | return pos; |
| | | }; |
| | | |
| | | /** @type {CharHandler} */ |
| | | const consumeLeftSquareBracket = (input, pos, _callbacks) => |
| | | const consumeLeftSquareBracket = (input, pos, callbacks) => { |
| | | // Return a <]-token>. |
| | | pos; |
| | | if (callbacks.leftSquareBracket !== undefined) { |
| | | return callbacks.leftSquareBracket(input, pos - 1, pos); |
| | | } |
| | | |
| | | return pos; |
| | | }; |
| | | |
| | | /** @type {CharHandler} */ |
| | | const consumeRightSquareBracket = (input, pos, _callbacks) => |
| | | const consumeRightSquareBracket = (input, pos, callbacks) => { |
| | | // Return a <]-token>. |
| | | pos; |
| | | if (callbacks.rightSquareBracket !== undefined) { |
| | | return callbacks.rightSquareBracket(input, pos - 1, pos); |
| | | } |
| | | |
| | | return pos; |
| | | }; |
| | | |
| | | /** @type {CharHandler} */ |
| | | const consumeLeftCurlyBracket = (input, pos, callbacks) => { |
| | |
| | | if (callbacks.leftCurlyBracket !== undefined) { |
| | | return callbacks.leftCurlyBracket(input, pos - 1, pos); |
| | | } |
| | | |
| | | return pos; |
| | | }; |
| | | |
| | |
| | | if (callbacks.rightCurlyBracket !== undefined) { |
| | | return callbacks.rightCurlyBracket(input, pos - 1, pos); |
| | | } |
| | | |
| | | return pos; |
| | | }; |
| | | |
| | |
| | | if (callbacks.semicolon !== undefined) { |
| | | return callbacks.semicolon(input, pos - 1, pos); |
| | | } |
| | | |
| | | return pos; |
| | | }; |
| | | |
| | |
| | | if (callbacks.comma !== undefined) { |
| | | return callbacks.comma(input, pos - 1, pos); |
| | | } |
| | | |
| | | return pos; |
| | | }; |
| | | |
| | |
| | | }; |
| | | |
| | | /** |
| | | * Is non printable code point. |
| | | * @param {number} cc char code |
| | | * @returns {boolean} true, when cc is the non-printable code point, otherwise false |
| | | */ |
| | |
| | | cc === 0x7f; |
| | | |
| | | /** |
| | | * Consume the remnants of a bad url. |
| | | * @param {string} input input |
| | | * @param {number} pos position |
| | | * @returns {number} position |
| | |
| | | }; |
| | | |
| | | /** |
| | | * Consume a url token. |
| | | * @param {string} input input |
| | | * @param {number} pos position |
| | | * @param {number} fnStart start |
| | |
| | | } |
| | | |
| | | // Don't handle bad urls |
| | | return consumeTheRemnantsOfABadUrl(input, pos); |
| | | pos = consumeTheRemnantsOfABadUrl(input, pos); |
| | | |
| | | if (callbacks.badUrlToken !== undefined) { |
| | | return callbacks.badUrlToken(input, fnStart, pos); |
| | | } |
| | | |
| | | return pos; |
| | | } |
| | | // U+0022 QUOTATION MARK (") |
| | | // U+0027 APOSTROPHE (') |
| | |
| | | _isNonPrintableCodePoint(cc) |
| | | ) { |
| | | // Don't handle bad urls |
| | | return consumeTheRemnantsOfABadUrl(input, pos); |
| | | pos = consumeTheRemnantsOfABadUrl(input, pos); |
| | | |
| | | if (callbacks.badUrlToken !== undefined) { |
| | | return callbacks.badUrlToken(input, fnStart, pos); |
| | | } |
| | | |
| | | return pos; |
| | | } |
| | | // // U+005C REVERSE SOLIDUS (\) |
| | | // // If the stream starts with a valid escape, consume an escaped code point and append the returned code point to the <url-token>’s value. |
| | |
| | | pos = _consumeAnEscapedCodePoint(input, pos); |
| | | } else { |
| | | // Don't handle bad urls |
| | | return consumeTheRemnantsOfABadUrl(input, pos); |
| | | pos = consumeTheRemnantsOfABadUrl(input, pos); |
| | | |
| | | if (callbacks.badUrlToken !== undefined) { |
| | | return callbacks.badUrlToken(input, fnStart, pos); |
| | | } |
| | | |
| | | return pos; |
| | | } |
| | | } |
| | | // anything else |
| | |
| | | }; |
| | | |
| | | /** @type {CharHandler} */ |
| | | const consumeLessThan = (input, pos, _callbacks) => { |
| | | const consumeLessThan = (input, pos, callbacks) => { |
| | | // If the next 3 input code points are U+0021 EXCLAMATION MARK U+002D HYPHEN-MINUS U+002D HYPHEN-MINUS (!--), consume them and return a <CDO-token>. |
| | | if (input.slice(pos, pos + 3) === "!--") { |
| | | if (callbacks.cdo !== undefined) { |
| | | return callbacks.cdo(input, pos - 1, pos + 3); |
| | | } |
| | | |
| | | return pos + 3; |
| | | } |
| | | |
| | | if (callbacks.delim !== undefined) { |
| | | return callbacks.delim(input, pos - 1, pos); |
| | | } |
| | | |
| | | // Otherwise, return a <delim-token> with its value set to the current input code point. |
| | |
| | | } |
| | | |
| | | // Otherwise, return a <delim-token> with its value set to the current input code point. |
| | | if (callbacks.delim !== undefined) { |
| | | return callbacks.delim(input, start, pos); |
| | | } |
| | | |
| | | return pos; |
| | | }; |
| | | |
| | |
| | | } |
| | | |
| | | // Otherwise, this is a parse error. Return a <delim-token> with its value set to the current input code point. |
| | | if (callbacks.delim !== undefined) { |
| | | return callbacks.delim(input, pos - 1, pos); |
| | | } |
| | | |
| | | return pos; |
| | | }; |
| | | |
| | |
| | | }; |
| | | |
| | | /** |
| | | * Returns pos. |
| | | * @param {string} input input css |
| | | * @param {number=} pos pos |
| | | * @param {CssTokenCallbacks=} callbacks callbacks |
| | |
| | | }; |
| | | |
| | | /** |
| | | * Returns pos. |
| | | * @param {string} input input css |
| | | * @param {number} pos pos |
| | | * @param {CssTokenCallbacks} callbacks callbacks |
| | | * @param {CssTokenCallbacks} additional additional callbacks |
| | | * @param {CssTokenCallbacks=} additional additional callbacks |
| | | * @param {{ onlyTopLevel?: boolean, declarationValue?: boolean, atRulePrelude?: boolean, functionValue?: boolean }=} options options |
| | | * @returns {number} pos |
| | | */ |
| | |
| | | needHandle = false; |
| | | } |
| | | |
| | | if (additional.function !== undefined) { |
| | | if (additional && additional.function !== undefined) { |
| | | return additional.function(input, start, end); |
| | | } |
| | | |
| | |
| | | needTerminate = true; |
| | | return end; |
| | | }; |
| | | servicedCallbacks.semicolon = (_input, _start, end) => { |
| | | needTerminate = true; |
| | | return end; |
| | | }; |
| | | } |
| | | |
| | | const mergedCallbacks = { ...servicedCallbacks, ...callbacks }; |
| | | |
| | | while (pos < input.length) { |
| | | // Consume comments. |
| | | pos = consumeComments( |
| | | input, |
| | | pos, |
| | | needHandle ? { ...servicedCallbacks, ...callbacks } : servicedCallbacks |
| | | needHandle ? mergedCallbacks : servicedCallbacks |
| | | ); |
| | | |
| | | const start = pos; |
| | |
| | | pos = consumeAToken( |
| | | input, |
| | | pos, |
| | | needHandle ? { ...servicedCallbacks, ...callbacks } : servicedCallbacks |
| | | needHandle ? mergedCallbacks : servicedCallbacks |
| | | ); |
| | | |
| | | if (needTerminate) { |
| | |
| | | }; |
| | | |
| | | /** |
| | | * Returns position after comments. |
| | | * @param {string} input input |
| | | * @param {number} pos position |
| | | * @returns {number} position after comments |
| | |
| | | }; |
| | | |
| | | /** |
| | | * Returns position after whitespace. |
| | | * @param {string} input input |
| | | * @param {number} pos position |
| | | * @returns {number} position after whitespace |
| | |
| | | }; |
| | | |
| | | /** |
| | | * Eat whitespace and comments. |
| | | * @param {string} input input |
| | | * @param {number} pos position |
| | | * @returns {number} position after whitespace and comments |
| | | * @returns {[number, boolean]} position after whitespace and comments |
| | | */ |
| | | const eatWhitespaceAndComments = (input, pos) => { |
| | | let foundWhitespace = false; |
| | | |
| | | for (;;) { |
| | | const originalPos = pos; |
| | | pos = consumeComments(input, pos, {}); |
| | | while (_isWhiteSpace(input.charCodeAt(pos))) { |
| | | if (!foundWhitespace) { |
| | | foundWhitespace = true; |
| | | } |
| | | pos++; |
| | | } |
| | | if (originalPos === pos) { |
| | |
| | | } |
| | | } |
| | | |
| | | return pos; |
| | | return [pos, foundWhitespace]; |
| | | }; |
| | | |
| | | /** |
| | | * Returns position after whitespace. |
| | | * @param {string} input input |
| | | * @param {number} pos position |
| | | * @returns {number} position after whitespace |
| | |
| | | }; |
| | | |
| | | /** |
| | | * Skip comments and eat ident sequence. |
| | | * @param {string} input input |
| | | * @param {number} pos position |
| | | * @returns {[number, number] | undefined} positions of ident sequence |
| | |
| | | }; |
| | | |
| | | /** |
| | | * Returns positions of ident sequence. |
| | | * @param {string} input input |
| | | * @param {number} pos position |
| | | * @returns {[number, number] | undefined} positions of ident sequence |
| | | */ |
| | | const eatString = (input, pos) => { |
| | | pos = eatWhitespaceAndComments(input, pos); |
| | | pos = eatWhitespaceAndComments(input, pos)[0]; |
| | | |
| | | const start = pos; |
| | | |
| | |
| | | }; |
| | | |
| | | /** |
| | | * Eat image set strings. |
| | | * @param {string} input input |
| | | * @param {number} pos position |
| | | * @param {CssTokenCallbacks} cbs callbacks |
| | |
| | | }; |
| | | |
| | | /** |
| | | * Returns positions of top level tokens. |
| | | * @param {string} input input |
| | | * @param {number} pos position |
| | | * @param {CssTokenCallbacks} cbs callbacks |
| | |
| | | }; |
| | | |
| | | /** |
| | | * Eat ident sequence. |
| | | * @param {string} input input |
| | | * @param {number} pos position |
| | | * @returns {[number, number] | undefined} positions of ident sequence |
| | | */ |
| | | const eatIdentSequence = (input, pos) => { |
| | | pos = eatWhitespaceAndComments(input, pos); |
| | | pos = eatWhitespaceAndComments(input, pos)[0]; |
| | | |
| | | const start = pos; |
| | | |
| | |
| | | }; |
| | | |
| | | /** |
| | | * Eat ident sequence or string. |
| | | * @param {string} input input |
| | | * @param {number} pos position |
| | | * @returns {[number, number, boolean] | undefined} positions of ident sequence or string |
| | | */ |
| | | const eatIdentSequenceOrString = (input, pos) => { |
| | | pos = eatWhitespaceAndComments(input, pos); |
| | | pos = eatWhitespaceAndComments(input, pos)[0]; |
| | | |
| | | const start = pos; |
| | | |
| | |
| | | }; |
| | | |
| | | /** |
| | | * Returns function to eat characters. |
| | | * @param {string} chars characters |
| | | * @returns {(input: string, pos: number) => number} function to eat characters |
| | | */ |
| | |
| | | module.exports.eatWhitespace = eatWhitespace; |
| | | module.exports.eatWhitespaceAndComments = eatWhitespaceAndComments; |
| | | module.exports.isIdentStartCodePoint = isIdentStartCodePoint; |
| | | module.exports.isWhiteSpace = _isWhiteSpace; |
| | | module.exports.skipCommentsAndEatIdentSequence = |
| | | skipCommentsAndEatIdentSequence; |