123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659 |
- /*
- MIT License http://www.opensource.org/licenses/mit-license.php
- Author Tobias Koppers @sokra
- */
- "use strict";
- /**
- * @typedef {Object} CssTokenCallbacks
- * @property {function(string, number): boolean} isSelector
- * @property {function(string, number, number, number, number): number=} url
- * @property {function(string, number, number): number=} string
- * @property {function(string, number, number): number=} leftParenthesis
- * @property {function(string, number, number): number=} rightParenthesis
- * @property {function(string, number, number): number=} pseudoFunction
- * @property {function(string, number, number): number=} function
- * @property {function(string, number, number): number=} pseudoClass
- * @property {function(string, number, number): number=} atKeyword
- * @property {function(string, number, number): number=} class
- * @property {function(string, number, number): number=} identifier
- * @property {function(string, number, number): number=} id
- * @property {function(string, number, number): number=} leftCurlyBracket
- * @property {function(string, number, number): number=} rightCurlyBracket
- * @property {function(string, number, number): number=} semicolon
- * @property {function(string, number, number): number=} comma
- */
- /** @typedef {function(string, number, CssTokenCallbacks): number} CharHandler */
- // spec: https://drafts.csswg.org/css-syntax/
- const CC_LINE_FEED = "\n".charCodeAt(0);
- const CC_CARRIAGE_RETURN = "\r".charCodeAt(0);
- const CC_FORM_FEED = "\f".charCodeAt(0);
- const CC_TAB = "\t".charCodeAt(0);
- const CC_SPACE = " ".charCodeAt(0);
- const CC_SLASH = "/".charCodeAt(0);
- const CC_BACK_SLASH = "\\".charCodeAt(0);
- const CC_ASTERISK = "*".charCodeAt(0);
- const CC_LEFT_PARENTHESIS = "(".charCodeAt(0);
- const CC_RIGHT_PARENTHESIS = ")".charCodeAt(0);
- const CC_LEFT_CURLY = "{".charCodeAt(0);
- const CC_RIGHT_CURLY = "}".charCodeAt(0);
- const CC_QUOTATION_MARK = '"'.charCodeAt(0);
- const CC_APOSTROPHE = "'".charCodeAt(0);
- const CC_FULL_STOP = ".".charCodeAt(0);
- const CC_COLON = ":".charCodeAt(0);
- const CC_SEMICOLON = ";".charCodeAt(0);
- const CC_COMMA = ",".charCodeAt(0);
- const CC_PERCENTAGE = "%".charCodeAt(0);
- const CC_AT_SIGN = "@".charCodeAt(0);
- const CC_LOW_LINE = "_".charCodeAt(0);
- const CC_LOWER_A = "a".charCodeAt(0);
- const CC_LOWER_U = "u".charCodeAt(0);
- const CC_LOWER_E = "e".charCodeAt(0);
- const CC_LOWER_Z = "z".charCodeAt(0);
- const CC_UPPER_A = "A".charCodeAt(0);
- const CC_UPPER_E = "E".charCodeAt(0);
- const CC_UPPER_Z = "Z".charCodeAt(0);
- const CC_0 = "0".charCodeAt(0);
- const CC_9 = "9".charCodeAt(0);
- const CC_NUMBER_SIGN = "#".charCodeAt(0);
- const CC_PLUS_SIGN = "+".charCodeAt(0);
- const CC_HYPHEN_MINUS = "-".charCodeAt(0);
- const CC_LESS_THAN_SIGN = "<".charCodeAt(0);
- const CC_GREATER_THAN_SIGN = ">".charCodeAt(0);
- const _isNewLine = cc => {
- return (
- cc === CC_LINE_FEED || cc === CC_CARRIAGE_RETURN || cc === CC_FORM_FEED
- );
- };
- /** @type {CharHandler} */
- const consumeSpace = (input, pos, callbacks) => {
- let cc;
- do {
- pos++;
- cc = input.charCodeAt(pos);
- } while (_isWhiteSpace(cc));
- return pos;
- };
- const _isWhiteSpace = cc => {
- return (
- cc === CC_LINE_FEED ||
- cc === CC_CARRIAGE_RETURN ||
- cc === CC_FORM_FEED ||
- cc === CC_TAB ||
- cc === CC_SPACE
- );
- };
- /** @type {CharHandler} */
- const consumeSingleCharToken = (input, pos, callbacks) => {
- return pos + 1;
- };
- /** @type {CharHandler} */
- const consumePotentialComment = (input, pos, callbacks) => {
- pos++;
- if (pos === input.length) return pos;
- let cc = input.charCodeAt(pos);
- if (cc !== CC_ASTERISK) return pos;
- for (;;) {
- pos++;
- if (pos === input.length) return pos;
- cc = input.charCodeAt(pos);
- while (cc === CC_ASTERISK) {
- pos++;
- if (pos === input.length) return pos;
- cc = input.charCodeAt(pos);
- if (cc === CC_SLASH) return pos + 1;
- }
- }
- };
- /** @type {function(number): CharHandler} */
- const consumeString = end => (input, pos, callbacks) => {
- const start = pos;
- pos = _consumeString(input, pos, end);
- if (callbacks.string !== undefined) {
- pos = callbacks.string(input, start, pos);
- }
- return pos;
- };
- const _consumeString = (input, pos, end) => {
- pos++;
- for (;;) {
- if (pos === input.length) return pos;
- const cc = input.charCodeAt(pos);
- if (cc === end) return pos + 1;
- if (_isNewLine(cc)) {
- // bad string
- return pos;
- }
- if (cc === CC_BACK_SLASH) {
- // we don't need to fully parse the escaped code point
- // just skip over a potential new line
- pos++;
- if (pos === input.length) return pos;
- pos++;
- } else {
- pos++;
- }
- }
- };
- const _isIdentifierStartCode = cc => {
- return (
- cc === CC_LOW_LINE ||
- (cc >= CC_LOWER_A && cc <= CC_LOWER_Z) ||
- (cc >= CC_UPPER_A && cc <= CC_UPPER_Z) ||
- cc > 0x80
- );
- };
- const _isDigit = cc => {
- return cc >= CC_0 && cc <= CC_9;
- };
- const _startsIdentifier = (input, pos) => {
- const cc = input.charCodeAt(pos);
- if (cc === CC_HYPHEN_MINUS) {
- if (pos === input.length) return false;
- const cc = input.charCodeAt(pos + 1);
- if (cc === CC_HYPHEN_MINUS) return true;
- if (cc === CC_BACK_SLASH) {
- const cc = input.charCodeAt(pos + 2);
- return !_isNewLine(cc);
- }
- return _isIdentifierStartCode(cc);
- }
- if (cc === CC_BACK_SLASH) {
- const cc = input.charCodeAt(pos + 1);
- return !_isNewLine(cc);
- }
- return _isIdentifierStartCode(cc);
- };
- /** @type {CharHandler} */
- const consumeNumberSign = (input, pos, callbacks) => {
- const start = pos;
- pos++;
- if (pos === input.length) return pos;
- if (callbacks.isSelector(input, pos) && _startsIdentifier(input, pos)) {
- pos = _consumeIdentifier(input, pos);
- if (callbacks.id !== undefined) {
- return callbacks.id(input, start, pos);
- }
- }
- return pos;
- };
- /** @type {CharHandler} */
- const consumeMinus = (input, pos, callbacks) => {
- const start = pos;
- pos++;
- if (pos === input.length) return pos;
- const cc = input.charCodeAt(pos);
- if (cc === CC_FULL_STOP || _isDigit(cc)) {
- return consumeNumericToken(input, pos, callbacks);
- } else if (cc === CC_HYPHEN_MINUS) {
- pos++;
- if (pos === input.length) return pos;
- const cc = input.charCodeAt(pos);
- if (cc === CC_GREATER_THAN_SIGN) {
- return pos + 1;
- } else {
- pos = _consumeIdentifier(input, pos);
- if (callbacks.identifier !== undefined) {
- return callbacks.identifier(input, start, pos);
- }
- }
- } else if (cc === CC_BACK_SLASH) {
- if (pos + 1 === input.length) return pos;
- const cc = input.charCodeAt(pos + 1);
- if (_isNewLine(cc)) return pos;
- pos = _consumeIdentifier(input, pos);
- if (callbacks.identifier !== undefined) {
- return callbacks.identifier(input, start, pos);
- }
- } else if (_isIdentifierStartCode(cc)) {
- pos++;
- pos = _consumeIdentifier(input, pos);
- if (callbacks.identifier !== undefined) {
- return callbacks.identifier(input, start, pos);
- }
- }
- return pos;
- };
- /** @type {CharHandler} */
- const consumeDot = (input, pos, callbacks) => {
- const start = pos;
- pos++;
- if (pos === input.length) return pos;
- const cc = input.charCodeAt(pos);
- if (_isDigit(cc)) return consumeNumericToken(input, pos - 2, callbacks);
- if (!callbacks.isSelector(input, pos) || !_startsIdentifier(input, pos))
- return pos;
- pos = _consumeIdentifier(input, pos);
- if (callbacks.class !== undefined) return callbacks.class(input, start, pos);
- return pos;
- };
- /** @type {CharHandler} */
- const consumeNumericToken = (input, pos, callbacks) => {
- pos = _consumeNumber(input, pos);
- if (pos === input.length) return pos;
- if (_startsIdentifier(input, pos)) return _consumeIdentifier(input, pos);
- const cc = input.charCodeAt(pos);
- if (cc === CC_PERCENTAGE) return pos + 1;
- return pos;
- };
- /** @type {CharHandler} */
- const consumeOtherIdentifier = (input, pos, callbacks) => {
- const start = pos;
- pos = _consumeIdentifier(input, pos);
- if (
- pos !== input.length &&
- !callbacks.isSelector(input, pos) &&
- input.charCodeAt(pos) === CC_LEFT_PARENTHESIS
- ) {
- pos++;
- if (callbacks.function !== undefined) {
- return callbacks.function(input, start, pos);
- }
- } else {
- if (callbacks.identifier !== undefined) {
- return callbacks.identifier(input, start, pos);
- }
- }
- return pos;
- };
- /** @type {CharHandler} */
- const consumePotentialUrl = (input, pos, callbacks) => {
- const start = pos;
- pos = _consumeIdentifier(input, pos);
- if (pos === start + 3 && input.slice(start, pos + 1) === "url(") {
- pos++;
- let cc = input.charCodeAt(pos);
- while (_isWhiteSpace(cc)) {
- pos++;
- if (pos === input.length) return pos;
- cc = input.charCodeAt(pos);
- }
- if (cc === CC_QUOTATION_MARK || cc === CC_APOSTROPHE) {
- pos++;
- const contentStart = pos;
- pos = _consumeString(input, pos, cc);
- const contentEnd = pos - 1;
- cc = input.charCodeAt(pos);
- while (_isWhiteSpace(cc)) {
- pos++;
- if (pos === input.length) return pos;
- cc = input.charCodeAt(pos);
- }
- if (cc !== CC_RIGHT_PARENTHESIS) return pos;
- pos++;
- if (callbacks.url !== undefined)
- return callbacks.url(input, start, pos, contentStart, contentEnd);
- return pos;
- } else {
- const contentStart = pos;
- let contentEnd;
- for (;;) {
- if (cc === CC_BACK_SLASH) {
- pos++;
- if (pos === input.length) return pos;
- pos++;
- } else if (_isWhiteSpace(cc)) {
- contentEnd = pos;
- do {
- pos++;
- if (pos === input.length) return pos;
- cc = input.charCodeAt(pos);
- } while (_isWhiteSpace(cc));
- if (cc !== CC_RIGHT_PARENTHESIS) return pos;
- pos++;
- if (callbacks.url !== undefined) {
- return callbacks.url(input, start, pos, contentStart, contentEnd);
- }
- return pos;
- } else if (cc === CC_RIGHT_PARENTHESIS) {
- contentEnd = pos;
- pos++;
- if (callbacks.url !== undefined) {
- return callbacks.url(input, start, pos, contentStart, contentEnd);
- }
- return pos;
- } else if (cc === CC_LEFT_PARENTHESIS) {
- return pos;
- } else {
- pos++;
- }
- if (pos === input.length) return pos;
- cc = input.charCodeAt(pos);
- }
- }
- } else {
- if (callbacks.identifier !== undefined) {
- return callbacks.identifier(input, start, pos);
- }
- return pos;
- }
- };
- /** @type {CharHandler} */
- const consumePotentialPseudo = (input, pos, callbacks) => {
- const start = pos;
- pos++;
- if (!callbacks.isSelector(input, pos) || !_startsIdentifier(input, pos))
- return pos;
- pos = _consumeIdentifier(input, pos);
- let cc = input.charCodeAt(pos);
- if (cc === CC_LEFT_PARENTHESIS) {
- pos++;
- if (callbacks.pseudoFunction !== undefined) {
- return callbacks.pseudoFunction(input, start, pos);
- }
- return pos;
- }
- if (callbacks.pseudoClass !== undefined) {
- return callbacks.pseudoClass(input, start, pos);
- }
- return pos;
- };
- /** @type {CharHandler} */
- const consumeLeftParenthesis = (input, pos, callbacks) => {
- pos++;
- if (callbacks.leftParenthesis !== undefined) {
- return callbacks.leftParenthesis(input, pos - 1, pos);
- }
- return pos;
- };
- /** @type {CharHandler} */
- const consumeRightParenthesis = (input, pos, callbacks) => {
- pos++;
- if (callbacks.rightParenthesis !== undefined) {
- return callbacks.rightParenthesis(input, pos - 1, pos);
- }
- return pos;
- };
- /** @type {CharHandler} */
- const consumeLeftCurlyBracket = (input, pos, callbacks) => {
- pos++;
- if (callbacks.leftCurlyBracket !== undefined) {
- return callbacks.leftCurlyBracket(input, pos - 1, pos);
- }
- return pos;
- };
- /** @type {CharHandler} */
- const consumeRightCurlyBracket = (input, pos, callbacks) => {
- pos++;
- if (callbacks.rightCurlyBracket !== undefined) {
- return callbacks.rightCurlyBracket(input, pos - 1, pos);
- }
- return pos;
- };
- /** @type {CharHandler} */
- const consumeSemicolon = (input, pos, callbacks) => {
- pos++;
- if (callbacks.semicolon !== undefined) {
- return callbacks.semicolon(input, pos - 1, pos);
- }
- return pos;
- };
- /** @type {CharHandler} */
- const consumeComma = (input, pos, callbacks) => {
- pos++;
- if (callbacks.comma !== undefined) {
- return callbacks.comma(input, pos - 1, pos);
- }
- return pos;
- };
- const _consumeIdentifier = (input, pos) => {
- for (;;) {
- const cc = input.charCodeAt(pos);
- if (cc === CC_BACK_SLASH) {
- pos++;
- if (pos === input.length) return pos;
- pos++;
- } else if (
- _isIdentifierStartCode(cc) ||
- _isDigit(cc) ||
- cc === CC_HYPHEN_MINUS
- ) {
- pos++;
- } else {
- return pos;
- }
- }
- };
- const _consumeNumber = (input, pos) => {
- pos++;
- if (pos === input.length) return pos;
- let cc = input.charCodeAt(pos);
- while (_isDigit(cc)) {
- pos++;
- if (pos === input.length) return pos;
- cc = input.charCodeAt(pos);
- }
- if (cc === CC_FULL_STOP && pos + 1 !== input.length) {
- const next = input.charCodeAt(pos + 1);
- if (_isDigit(next)) {
- pos += 2;
- cc = input.charCodeAt(pos);
- while (_isDigit(cc)) {
- pos++;
- if (pos === input.length) return pos;
- cc = input.charCodeAt(pos);
- }
- }
- }
- if (cc === CC_LOWER_E || cc === CC_UPPER_E) {
- if (pos + 1 !== input.length) {
- const next = input.charCodeAt(pos + 2);
- if (_isDigit(next)) {
- pos += 2;
- } else if (
- (next === CC_HYPHEN_MINUS || next === CC_PLUS_SIGN) &&
- pos + 2 !== input.length
- ) {
- const next = input.charCodeAt(pos + 2);
- if (_isDigit(next)) {
- pos += 3;
- } else {
- return pos;
- }
- } else {
- return pos;
- }
- }
- } else {
- return pos;
- }
- cc = input.charCodeAt(pos);
- while (_isDigit(cc)) {
- pos++;
- if (pos === input.length) return pos;
- cc = input.charCodeAt(pos);
- }
- return pos;
- };
- /** @type {CharHandler} */
- const consumeLessThan = (input, pos, callbacks) => {
- if (input.slice(pos + 1, pos + 4) === "!--") return pos + 4;
- return pos + 1;
- };
- /** @type {CharHandler} */
- const consumeAt = (input, pos, callbacks) => {
- const start = pos;
- pos++;
- if (pos === input.length) return pos;
- if (_startsIdentifier(input, pos)) {
- pos = _consumeIdentifier(input, pos);
- if (callbacks.atKeyword !== undefined) {
- pos = callbacks.atKeyword(input, start, pos);
- }
- }
- return pos;
- };
- const CHAR_MAP = Array.from({ length: 0x80 }, (_, cc) => {
- // https://drafts.csswg.org/css-syntax/#consume-token
- switch (cc) {
- case CC_LINE_FEED:
- case CC_CARRIAGE_RETURN:
- case CC_FORM_FEED:
- case CC_TAB:
- case CC_SPACE:
- return consumeSpace;
- case CC_QUOTATION_MARK:
- case CC_APOSTROPHE:
- return consumeString(cc);
- case CC_NUMBER_SIGN:
- return consumeNumberSign;
- case CC_SLASH:
- return consumePotentialComment;
- // case CC_LEFT_SQUARE:
- // case CC_RIGHT_SQUARE:
- // case CC_COMMA:
- // case CC_COLON:
- // return consumeSingleCharToken;
- case CC_COMMA:
- return consumeComma;
- case CC_SEMICOLON:
- return consumeSemicolon;
- case CC_LEFT_PARENTHESIS:
- return consumeLeftParenthesis;
- case CC_RIGHT_PARENTHESIS:
- return consumeRightParenthesis;
- case CC_LEFT_CURLY:
- return consumeLeftCurlyBracket;
- case CC_RIGHT_CURLY:
- return consumeRightCurlyBracket;
- case CC_COLON:
- return consumePotentialPseudo;
- case CC_PLUS_SIGN:
- return consumeNumericToken;
- case CC_FULL_STOP:
- return consumeDot;
- case CC_HYPHEN_MINUS:
- return consumeMinus;
- case CC_LESS_THAN_SIGN:
- return consumeLessThan;
- case CC_AT_SIGN:
- return consumeAt;
- case CC_LOWER_U:
- return consumePotentialUrl;
- case CC_LOW_LINE:
- return consumeOtherIdentifier;
- default:
- if (_isDigit(cc)) return consumeNumericToken;
- if (
- (cc >= CC_LOWER_A && cc <= CC_LOWER_Z) ||
- (cc >= CC_UPPER_A && cc <= CC_UPPER_Z)
- ) {
- return consumeOtherIdentifier;
- }
- return consumeSingleCharToken;
- }
- });
- /**
- * @param {string} input input css
- * @param {CssTokenCallbacks} callbacks callbacks
- * @returns {void}
- */
- module.exports = (input, callbacks) => {
- let pos = 0;
- while (pos < input.length) {
- const cc = input.charCodeAt(pos);
- if (cc < 0x80) {
- pos = CHAR_MAP[cc](input, pos, callbacks);
- } else {
- pos++;
- }
- }
- };
- module.exports.eatComments = (input, pos) => {
- loop: for (;;) {
- const cc = input.charCodeAt(pos);
- if (cc === CC_SLASH) {
- if (pos === input.length) return pos;
- let cc = input.charCodeAt(pos + 1);
- if (cc !== CC_ASTERISK) return pos;
- pos++;
- for (;;) {
- pos++;
- if (pos === input.length) return pos;
- cc = input.charCodeAt(pos);
- while (cc === CC_ASTERISK) {
- pos++;
- if (pos === input.length) return pos;
- cc = input.charCodeAt(pos);
- if (cc === CC_SLASH) {
- pos++;
- continue loop;
- }
- }
- }
- }
- return pos;
- }
- };
- module.exports.eatWhitespaceAndComments = (input, pos) => {
- loop: for (;;) {
- const cc = input.charCodeAt(pos);
- if (cc === CC_SLASH) {
- if (pos === input.length) return pos;
- let cc = input.charCodeAt(pos + 1);
- if (cc !== CC_ASTERISK) return pos;
- pos++;
- for (;;) {
- pos++;
- if (pos === input.length) return pos;
- cc = input.charCodeAt(pos);
- while (cc === CC_ASTERISK) {
- pos++;
- if (pos === input.length) return pos;
- cc = input.charCodeAt(pos);
- if (cc === CC_SLASH) {
- pos++;
- continue loop;
- }
- }
- }
- } else if (_isWhiteSpace(cc)) {
- pos++;
- continue;
- }
- return pos;
- }
- };
|