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
JavaScript
;
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;