UNPKG

css-zero-lexer

Version:

Friendly and forgiving CSS lexer/parser with lots of tests. Memory-efficient and Web Worker compatible.

285 lines (240 loc) 8.97 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }(); function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } } var NodeTypes = exports.NodeTypes = { OPEN_RULE: 1, CLOSE_RULE: 2, SELECTOR_NODE: 3, PROPERTY_NODE: 4, CLOSE_PROPERTY: 5, COMMENT_NODE: 6, SELECTOR_SEPARATOR: 7 // comma character between selectors }; var NodeTypeKeys = exports.NodeTypeKeys = Object.keys(NodeTypes).sort(function (a, b) { return NodeTypes[a] - NodeTypes[b]; }); var COMMENT = ["/*", "*/"]; var SASS_COMMENT = ["//", ["\n", "\r"]]; var WHITESPACE = [" ", "\r", "\n", "\t"]; var QUOTES = ['"', "'"]; var seekChar = function seekChar(css, i, searchChars) { while (searchChars.indexOf(css[i]) === -1) { i++; if (i > css.length) return i - 1; } return i; }; var NESTING_TOKENS = [["[", "]"], // attr selector ["(", ")"], // eg for :not() ['"', '"'], ["'", "'"]]; var NESTING_OPEN = NESTING_TOKENS.map(function (pair) { return pair[0]; }); var NESTING_CLOSE = NESTING_TOKENS.map(function (pair) { return pair[1]; }); var SELECTOR_OR_PROPERTY_TERMINATION = ["{", "}", ",", ";"]; var seekExpression = function seekExpression(css, i) { var nesting = []; var tokens = []; var token = void 0; var completeToken = function completeToken(tokens, i) { if (tokens.length && tokens[tokens.length - 1].length === 2) { // an incomplete token that we should finish before adding comment tokens[tokens.length - 1].push(i); return true; } return false; }; var exitAfter = Math.min(css.length, 1000000); // At least 10MB of tokens var char = void 0; while (i < css.length) { char = css[i]; if (char === COMMENT[0][0] && css[i + 1] === COMMENT[0][1]) { completeToken(tokens, i - 1); var endOfComment = seekString(css, i, COMMENT[1]); tokens.push([NodeTypes.COMMENT_NODE, i + 2, endOfComment + 2]); i = endOfComment + 2; } else { if (nesting.length === 0) { var startI = i; i = Math.min(seekChar(css, i, [].concat(_toConsumableArray(NESTING_OPEN), SELECTOR_OR_PROPERTY_TERMINATION)), seekString(css, i, COMMENT[0])); char = css[i]; if (NESTING_OPEN.indexOf(char) !== -1) { nesting.push(NESTING_OPEN.indexOf(char)); // incomplete token that we'll add to later... tokens.push([NodeTypes.PROPERTY_NODE, startI]); i++; } else { // time to leave if (!completeToken(tokens, i)) { tokens.push([NodeTypes.SELECTOR_NODE, startI, trimWhitespace(css, css.length >= i ? i - 1 : i)]); } return [i, tokens]; } } else { var closeChar = NESTING_CLOSE[nesting[nesting.length - 1]]; i = Math.min(seekChar(css, i, [closeChar].concat(_toConsumableArray(NESTING_OPEN))), seekString(css, i, COMMENT[0])); char = css[i]; if (char === COMMENT[0][0] && css[i + 1] === COMMENT[0][1]) { // loop around until end of comment } else if (char === closeChar) { nesting.pop(); i++; } else if (NESTING_OPEN.indexOf(char)) { nesting.push(char); i++; } } } exitAfter--; if (exitAfter === 0) throw Error("Exiting after too many loops"); } return [i, tokens]; }; var seekBackNotChar = function seekBackNotChar(css, i, searchChars) { var exitAfter = 100000; // At least 1MB of tokens while (i > 0 && searchChars.indexOf(css[i]) !== -1) { i--; exitAfter--; if (exitAfter === 0) throw Error("Exiting after too many loops"); } return i + 1; }; var trimWhitespace = function trimWhitespace(css, i) { return seekBackNotChar(css, i, WHITESPACE); }; var seekString = function seekString(css, i, searchString) { i = css.indexOf(searchString, i); if (i === -1) i = css.length; return i; }; var onComment = function onComment(css, i) { i++; var token = [NodeTypes.COMMENT_NODE, i]; i = seekString(css, i, "*/"); token.push(i); i += 2; return [i, token]; }; var onExpression = function onExpression(css, i) { var tokens = void 0; var _seekExpression = seekExpression(css, i); var _seekExpression2 = _slicedToArray(_seekExpression, 2); i = _seekExpression2[0]; tokens = _seekExpression2[1]; return [i, tokens]; }; var onClose = function onClose(css, i) { var token = [NodeTypes.CLOSE_RULE]; i++; return [i, token]; }; var defaultOptions = { i: 0 }; // his divine shadow var Lexx = function Lexx(css, options) { var useOptions = _extends({}, defaultOptions, options); var tokens = []; var i = useOptions.i || 0; // Number.MAX_SAFE_INTEGER is 9007199254740991 so that's 9007199 gigabytes of string and using integers makes sense var char = void 0; var token = void 0; var onExpressionTokens = void 0; var ambiguousTokens = []; var debugExitAfterLoops = 1073741824; // an arbitrary large number while (i < css.length) { char = css[i]; debugExitAfterLoops--; if (debugExitAfterLoops < 0) throw Error("Congratulations, you probably found a bug in css-zero-lexer! Please raise an issue on https://github.com/holloway/xml-zero.js/issues with your CSS, which was: " + css); if (char === "/" && css[i + 1] === "*") { i++; var _onComment = onComment(css, i); var _onComment2 = _slicedToArray(_onComment, 2); i = _onComment2[0]; token = _onComment2[1]; tokens.push(token); } else { switch (char) { case " ": case "\t": case "\r": case "\n": i++; break; case "{": i++; ambiguousTokens.forEach(function (token) { return token[0] = NodeTypes.SELECTOR_NODE; }); ambiguousTokens = []; token = [NodeTypes.OPEN_RULE]; tokens.push(token); break; case ",": token = [NodeTypes.SELECTOR_SEPARATOR]; tokens.push(token); i++; break; case ";": i++; ambiguousTokens.forEach(function (token) { return token[0] = NodeTypes.PROPERTY_NODE; }); ambiguousTokens = []; token = [NodeTypes.CLOSE_PROPERTY]; tokens.push(token); break; case "}": var _onClose = onClose(css, i); var _onClose2 = _slicedToArray(_onClose, 2); i = _onClose2[0]; token = _onClose2[1]; ambiguousTokens.forEach(function (token) { return token[0] = NodeTypes.PROPERTY_NODE; }); tokens.push(token); ambiguousTokens = []; break; default: var _onExpression = onExpression(css, i); // properties or selectors var _onExpression2 = _slicedToArray(_onExpression, 2); i = _onExpression2[0]; onExpressionTokens = _onExpression2[1]; onExpressionTokens // .filter(token => token[0] === NodeTypes.SELECTOR_NODE) .forEach(function (token) { if ([NodeTypes.SELECTOR_NODE, NodeTypes.PROPERTY_NODE].indexOf(token[0]) !== -1) { ambiguousTokens.push(token); } tokens.push(token); }); if (css[i] === "}") { var hasPropertyNode = false; ambiguousTokens.forEach(function (token) { if (token[0] === NodeTypes.SELECTOR_NODE) { token[0] === NodeTypes.PROPERTY_NODE; hasPropertyNode = true; } }); if (hasPropertyNode) { tokens.push([NodeTypes.CLOSE_PROPERTY]); } } break; } } } ambiguousTokens.forEach(function (token) { if (token[0] === NodeTypes.SELECTOR_NODE) { token[0] = NodeTypes.PROPERTY_NODE; } }); return tokens; }; exports.default = Lexx;