"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.parse = exports.isTraversal = void 0; var types_1 = require("./types"); var reName = /^[^\\#]?(?:\\(?:[\da-f]{1,6}\s?|.)|[\w\-\u00b0-\uFFFF])+/; var reEscape = /\\([\da-f]{1,6}\s?|(\s)|.)/gi; var actionTypes = new Map([ [126 /* Tilde */, types_1.AttributeAction.Element], [94 /* Circumflex */, types_1.AttributeAction.Start], [36 /* Dollar */, types_1.AttributeAction.End], [42 /* Asterisk */, types_1.AttributeAction.Any], [33 /* ExclamationMark */, types_1.AttributeAction.Not], [124 /* Pipe */, types_1.AttributeAction.Hyphen], ]); // Pseudos, whose data property is parsed as well. var unpackPseudos = new Set([ "has", "not", "matches", "is", "where", "host", "host-context", ]); /** * Checks whether a specific selector is a traversal. * This is useful eg. in swapping the order of elements that * are not traversals. * * @param selector Selector to check. */ function isTraversal(selector) { switch (selector.type) { case types_1.SelectorType.Adjacent: case types_1.SelectorType.Child: case types_1.SelectorType.Descendant: case types_1.SelectorType.Parent: case types_1.SelectorType.Sibling: case types_1.SelectorType.ColumnCombinator: return true; default: return false; } } exports.isTraversal = isTraversal; var stripQuotesFromPseudos = new Set(["contains", "icontains"]); // Unescape function taken from https://github.com/jquery/sizzle/blob/master/src/sizzle.js#L152 function funescape(_, escaped, escapedWhitespace) { var high = parseInt(escaped, 16) - 0x10000; // NaN means non-codepoint return high !== high || escapedWhitespace ? escaped : high < 0 ? // BMP codepoint String.fromCharCode(high + 0x10000) : // Supplemental Plane codepoint (surrogate pair) String.fromCharCode((high >> 10) | 0xd800, (high & 0x3ff) | 0xdc00); } function unescapeCSS(str) { return str.replace(reEscape, funescape); } function isQuote(c) { return c === 39 /* SingleQuote */ || c === 34 /* DoubleQuote */; } function isWhitespace(c) { return (c === 32 /* Space */ || c === 9 /* Tab */ || c === 10 /* NewLine */ || c === 12 /* FormFeed */ || c === 13 /* CarriageReturn */); } /** * Parses `selector`, optionally with the passed `options`. * * @param selector Selector to parse. * @param options Options for parsing. * @returns Returns a two-dimensional array. * The first dimension represents selectors separated by commas (eg. `sub1, sub2`), * the second contains the relevant tokens for that selector. */ function parse(selector) { var subselects = []; var endIndex = parseSelector(subselects, "".concat(selector), 0); if (endIndex < selector.length) { throw new Error("Unmatched selector: ".concat(selector.slice(endIndex))); } return subselects; } exports.parse = parse; function parseSelector(subselects, selector, selectorIndex) { var tokens = []; function getName(offset) { var match = selector.slice(selectorIndex + offset).match(reName); if (!match) { throw new Error("Expected name, found ".concat(selector.slice(selectorIndex))); } var name = match[0]; selectorIndex += offset + name.length; return unescapeCSS(name); } function stripWhitespace(offset) { selectorIndex += offset; while (selectorIndex < selector.length && isWhitespace(selector.charCodeAt(selectorIndex))) { selectorIndex++; } } function readValueWithParenthesis() { selectorIndex += 1; var start = selectorIndex; var counter = 1; for (; counter > 0 && selectorIndex < selector.length; selectorIndex++) { if (selector.charCodeAt(selectorIndex) === 40 /* LeftParenthesis */ && !isEscaped(selectorIndex)) { counter++; } else if (selector.charCodeAt(selectorIndex) === 41 /* RightParenthesis */ && !isEscaped(selectorIndex)) { counter--; } } if (counter) { throw new Error("Parenthesis not matched"); } return unescapeCSS(selector.slice(start, selectorIndex - 1)); } function isEscaped(pos) { var slashCount = 0; while (selector.charCodeAt(--pos) === 92 /* BackSlash */) slashCount++; return (slashCount & 1) === 1; } function ensureNotTraversal() { if (tokens.length > 0 && isTraversal(tokens[tokens.length - 1])) { throw new Error("Did not expect successive traversals."); } } function addTraversal(type) { if (tokens.length > 0 && tokens[tokens.length - 1].type === types_1.SelectorType.Descendant) { tokens[tokens.length - 1].type = type; return; } ensureNotTraversal(); tokens.push({ type: type }); } function addSpecialAttribute(name, action) { tokens.push({ type: types_1.SelectorType.Attribute, name: name, action: action, value: getName(1), namespace: null, ignoreCase: "quirks", }); } /** * We have finished parsing the current part of the selector. * * Remove descendant tokens at the end if they exist, * and return the last index, so that parsing can be * picked up from here. */ function finalizeSubselector() { if (tokens.length && tokens[tokens.length - 1].type === types_1.SelectorType.Descendant) { tokens.pop(); } if (tokens.length === 0) { throw new Error("Empty sub-selector"); } subselects.push(tokens); } stripWhitespace(0); if (selector.length === selectorIndex) { return selectorIndex; } loop: while (selectorIndex < selector.length) { var firstChar = selector.charCodeAt(selectorIndex); switch (firstChar) { // Whitespace case 32 /* Space */: case 9 /* Tab */: case 10 /* NewLine */: case 12 /* FormFeed */: case 13 /* CarriageReturn */: { if (tokens.length === 0 || tokens[0].type !== types_1.SelectorType.Descendant) { ensureNotTraversal(); tokens.push({ type: types_1.SelectorType.Descendant }); } stripWhitespace(1); break; } // Traversals case 62 /* GreaterThan */: { addTraversal(types_1.SelectorType.Child); stripWhitespace(1); break; } case 60 /* LessThan */: { addTraversal(types_1.SelectorType.Parent); stripWhitespace(1); break; } case 126 /* Tilde */: { addTraversal(types_1.SelectorType.Sibling); stripWhitespace(1); break; } case 43 /* Plus */: { addTraversal(types_1.SelectorType.Adjacent); stripWhitespace(1); break; } // Special attribute selectors: .class, #id case 46 /* Period */: { addSpecialAttribute("class", types_1.AttributeAction.Element); break; } case 35 /* Hash */: { addSpecialAttribute("id", types_1.AttributeAction.Equals); break; } case 91 /* LeftSquareBracket */: { stripWhitespace(1); // Determine attribute name and namespace var name_1 = void 0; var namespace = null; if (selector.charCodeAt(selectorIndex) === 124 /* Pipe */) { // Equivalent to no namespace name_1 = getName(1); } else if (selector.startsWith("*|", selectorIndex)) { namespace = "*"; name_1 = getName(2); } else { name_1 = getName(0); if (selector.charCodeAt(selectorIndex) === 124 /* Pipe */ && selector.charCodeAt(selectorIndex + 1) !== 61 /* Equal */) { namespace = name_1; name_1 = getName(1); } } stripWhitespace(0); // Determine comparison operation var action = types_1.AttributeAction.Exists; var possibleAction = actionTypes.get(selector.charCodeAt(selectorIndex)); if (possibleAction) { action = possibleAction; if (selector.charCodeAt(selectorIndex + 1) !== 61 /* Equal */) { throw new Error("Expected `=`"); } stripWhitespace(2); } else if (selector.charCodeAt(selectorIndex) === 61 /* Equal */) { action = types_1.AttributeAction.Equals; stripWhitespace(1); } // Determine value var value = ""; var ignoreCase = null; if (action !== "exists") { if (isQuote(selector.charCodeAt(selectorIndex))) { var quote = selector.charCodeAt(selectorIndex); var sectionEnd = selectorIndex + 1; while (sectionEnd < selector.length && (selector.charCodeAt(sectionEnd) !== quote || isEscaped(sectionEnd))) { sectionEnd += 1; } if (selector.charCodeAt(sectionEnd) !== quote) { throw new Error("Attribute value didn't end"); } value = unescapeCSS(selector.slice(selectorIndex + 1, sectionEnd)); selectorIndex = sectionEnd + 1; } else { var valueStart = selectorIndex; while (selectorIndex < selector.length && ((!isWhitespace(selector.charCodeAt(selectorIndex)) && selector.charCodeAt(selectorIndex) !== 93 /* RightSquareBracket */) || isEscaped(selectorIndex))) { selectorIndex += 1; } value = unescapeCSS(selector.slice(valueStart, selectorIndex)); } stripWhitespace(0); // See if we have a force ignore flag var forceIgnore = selector.charCodeAt(selectorIndex) | 0x20; // If the forceIgnore flag is set (either `i` or `s`), use that value if (forceIgnore === 115 /* LowerS */) { ignoreCase = false; stripWhitespace(1); } else if (forceIgnore === 105 /* LowerI */) { ignoreCase = true; stripWhitespace(1); } } if (selector.charCodeAt(selectorIndex) !== 93 /* RightSquareBracket */) { throw new Error("Attribute selector didn't terminate"); } selectorIndex += 1; var attributeSelector = { type: types_1.SelectorType.Attribute, name: name_1, action: action, value: value, namespace: namespace, ignoreCase: ignoreCase, }; tokens.push(attributeSelector); break; } case 58 /* Colon */: { if (selector.charCodeAt(selectorIndex + 1) === 58 /* Colon */) { tokens.push({ type: types_1.SelectorType.PseudoElement, name: getName(2).toLowerCase(), data: selector.charCodeAt(selectorIndex) === 40 /* LeftParenthesis */ ? readValueWithParenthesis() : null, }); continue; } var name_2 = getName(1).toLowerCase(); var data = null; if (selector.charCodeAt(selectorIndex) === 40 /* LeftParenthesis */) { if (unpackPseudos.has(name_2)) { if (isQuote(selector.charCodeAt(selectorIndex + 1))) { throw new Error("Pseudo-selector ".concat(name_2, " cannot be quoted")); } data = []; selectorIndex = parseSelector(data, selector, selectorIndex + 1); if (selector.charCodeAt(selectorIndex) !== 41 /* RightParenthesis */) { throw new Error("Missing closing parenthesis in :".concat(name_2, " (").concat(selector, ")")); } selectorIndex += 1; } else { data = readValueWithParenthesis(); if (stripQuotesFromPseudos.has(name_2)) { var quot = data.charCodeAt(0); if (quot === data.charCodeAt(data.length - 1) && isQuote(quot)) { data = data.slice(1, -1); } } data = unescapeCSS(data); } } tokens.push({ type: types_1.SelectorType.Pseudo, name: name_2, data: data }); break; } case 44 /* Comma */: { finalizeSubselector(); tokens = []; stripWhitespace(1); break; } default: { if (selector.startsWith("/*", selectorIndex)) { var endIndex = selector.indexOf("*/", selectorIndex + 2); if (endIndex < 0) { throw new Error("Comment was not terminated"); } selectorIndex = endIndex + 2; // Remove leading whitespace if (tokens.length === 0) { stripWhitespace(0); } break; } var namespace = null; var name_3 = void 0; if (firstChar === 42 /* Asterisk */) { selectorIndex += 1; name_3 = "*"; } else if (firstChar === 124 /* Pipe */) { name_3 = ""; if (selector.charCodeAt(selectorIndex + 1) === 124 /* Pipe */) { addTraversal(types_1.SelectorType.ColumnCombinator); stripWhitespace(2); break; } } else if (reName.test(selector.slice(selectorIndex))) { name_3 = getName(0); } else { break loop; } if (selector.charCodeAt(selectorIndex) === 124 /* Pipe */ && selector.charCodeAt(selectorIndex + 1) !== 124 /* Pipe */) { namespace = name_3; if (selector.charCodeAt(selectorIndex + 1) === 42 /* Asterisk */) { name_3 = "*"; selectorIndex += 2; } else { name_3 = getName(1); } } tokens.push(name_3 === "*" ? { type: types_1.SelectorType.Universal, namespace: namespace } : { type: types_1.SelectorType.Tag, name: name_3, namespace: namespace }); } } } finalizeSubselector(); return selectorIndex; }