"use strict"; var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) { if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) { if (ar || !(i in from)) { if (!ar) ar = Array.prototype.slice.call(from, 0, i); ar[i] = from[i]; } } return to.concat(ar || Array.prototype.slice.call(from)); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.isTraversal = void 0; var reName = /^[^\\#]?(?:\\(?:[\da-f]{1,6}\s?|.)|[\w\-\u00b0-\uFFFF])+/; var reEscape = /\\([\da-f]{1,6}\s?|(\s)|.)/gi; var actionTypes = new Map([ ["~", "element"], ["^", "start"], ["$", "end"], ["*", "any"], ["!", "not"], ["|", "hyphen"], ]); var Traversals = { ">": "child", "<": "parent", "~": "sibling", "+": "adjacent", }; var attribSelectors = { "#": ["id", "equals"], ".": ["class", "element"], }; // Pseudos, whose data property is parsed as well. var unpackPseudos = new Set([ "has", "not", "matches", "is", "where", "host", "host-context", ]); var traversalNames = new Set(__spreadArray([ "descendant" ], Object.keys(Traversals).map(function (k) { return Traversals[k]; }), true)); /** * Attributes that are case-insensitive in HTML. * * @private * @see https://html.spec.whatwg.org/multipage/semantics-other.html#case-sensitivity-of-selectors */ var caseInsensitiveAttributes = new Set([ "accept", "accept-charset", "align", "alink", "axis", "bgcolor", "charset", "checked", "clear", "codetype", "color", "compact", "declare", "defer", "dir", "direction", "disabled", "enctype", "face", "frame", "hreflang", "http-equiv", "lang", "language", "link", "media", "method", "multiple", "nohref", "noresize", "noshade", "nowrap", "readonly", "rel", "rev", "rules", "scope", "scrolling", "selected", "shape", "target", "text", "type", "valign", "valuetype", "vlink", ]); /** * 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) { return traversalNames.has(selector.type); } exports.isTraversal = isTraversal; var stripQuotesFromPseudos = new Set(["contains", "icontains"]); var quotes = new Set(['"', "'"]); // 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 isWhitespace(c) { return c === " " || c === "\n" || c === "\t" || c === "\f" || c === "\r"; } /** * 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, options) { var subselects = []; var endIndex = parseSelector(subselects, "" + selector, options, 0); if (endIndex < selector.length) { throw new Error("Unmatched selector: " + selector.slice(endIndex)); } return subselects; } exports.default = parse; function parseSelector(subselects, selector, options, selectorIndex) { var _a, _b; if (options === void 0) { options = {}; } var tokens = []; var sawWS = false; function getName(offset) { var match = selector.slice(selectorIndex + offset).match(reName); if (!match) { throw new Error("Expected name, found " + selector.slice(selectorIndex)); } var name = match[0]; selectorIndex += offset + name.length; return unescapeCSS(name); } function stripWhitespace(offset) { while (isWhitespace(selector.charAt(selectorIndex + offset))) offset++; selectorIndex += offset; } function isEscaped(pos) { var slashCount = 0; while (selector.charAt(--pos) === "\\") slashCount++; return (slashCount & 1) === 1; } function ensureNotTraversal() { if (tokens.length > 0 && isTraversal(tokens[tokens.length - 1])) { throw new Error("Did not expect successive traversals."); } } stripWhitespace(0); while (selector !== "") { var firstChar = selector.charAt(selectorIndex); if (isWhitespace(firstChar)) { sawWS = true; stripWhitespace(1); } else if (firstChar in Traversals) { ensureNotTraversal(); tokens.push({ type: Traversals[firstChar] }); sawWS = false; stripWhitespace(1); } else if (firstChar === ",") { if (tokens.length === 0) { throw new Error("Empty sub-selector"); } subselects.push(tokens); tokens = []; sawWS = false; stripWhitespace(1); } else if (selector.startsWith("/*", selectorIndex)) { var endIndex = selector.indexOf("*/", selectorIndex + 2); if (endIndex < 0) { throw new Error("Comment was not terminated"); } selectorIndex = endIndex + 2; } else { if (sawWS) { ensureNotTraversal(); tokens.push({ type: "descendant" }); sawWS = false; } if (firstChar in attribSelectors) { var _c = attribSelectors[firstChar], name_1 = _c[0], action = _c[1]; tokens.push({ type: "attribute", name: name_1, action: action, value: getName(1), namespace: null, // TODO: Add quirksMode option, which makes `ignoreCase` `true` for HTML. ignoreCase: options.xmlMode ? null : false, }); } else if (firstChar === "[") { stripWhitespace(1); // Determine attribute name and namespace var namespace = null; if (selector.charAt(selectorIndex) === "|") { namespace = ""; selectorIndex += 1; } if (selector.startsWith("*|", selectorIndex)) { namespace = "*"; selectorIndex += 2; } var name_2 = getName(0); if (namespace === null && selector.charAt(selectorIndex) === "|" && selector.charAt(selectorIndex + 1) !== "=") { namespace = name_2; name_2 = getName(1); } if ((_a = options.lowerCaseAttributeNames) !== null && _a !== void 0 ? _a : !options.xmlMode) { name_2 = name_2.toLowerCase(); } stripWhitespace(0); // Determine comparison operation var action = "exists"; var possibleAction = actionTypes.get(selector.charAt(selectorIndex)); if (possibleAction) { action = possibleAction; if (selector.charAt(selectorIndex + 1) !== "=") { throw new Error("Expected `=`"); } stripWhitespace(2); } else if (selector.charAt(selectorIndex) === "=") { action = "equals"; stripWhitespace(1); } // Determine value var value = ""; var ignoreCase = null; if (action !== "exists") { if (quotes.has(selector.charAt(selectorIndex))) { var quote = selector.charAt(selectorIndex); var sectionEnd = selectorIndex + 1; while (sectionEnd < selector.length && (selector.charAt(sectionEnd) !== quote || isEscaped(sectionEnd))) { sectionEnd += 1; } if (selector.charAt(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.charAt(selectorIndex)) && selector.charAt(selectorIndex) !== "]") || isEscaped(selectorIndex))) { selectorIndex += 1; } value = unescapeCSS(selector.slice(valueStart, selectorIndex)); } stripWhitespace(0); // See if we have a force ignore flag var forceIgnore = selector.charAt(selectorIndex); // If the forceIgnore flag is set (either `i` or `s`), use that value if (forceIgnore === "s" || forceIgnore === "S") { ignoreCase = false; stripWhitespace(1); } else if (forceIgnore === "i" || forceIgnore === "I") { ignoreCase = true; stripWhitespace(1); } } // If `xmlMode` is set, there are no rules; otherwise, use the `caseInsensitiveAttributes` list. if (!options.xmlMode) { // TODO: Skip this for `exists`, as there is no value to compare to. ignoreCase !== null && ignoreCase !== void 0 ? ignoreCase : (ignoreCase = caseInsensitiveAttributes.has(name_2)); } if (selector.charAt(selectorIndex) !== "]") { throw new Error("Attribute selector didn't terminate"); } selectorIndex += 1; var attributeSelector = { type: "attribute", name: name_2, action: action, value: value, namespace: namespace, ignoreCase: ignoreCase, }; tokens.push(attributeSelector); } else if (firstChar === ":") { if (selector.charAt(selectorIndex + 1) === ":") { tokens.push({ type: "pseudo-element", name: getName(2).toLowerCase(), }); continue; } var name_3 = getName(1).toLowerCase(); var data = null; if (selector.charAt(selectorIndex) === "(") { if (unpackPseudos.has(name_3)) { if (quotes.has(selector.charAt(selectorIndex + 1))) { throw new Error("Pseudo-selector " + name_3 + " cannot be quoted"); } data = []; selectorIndex = parseSelector(data, selector, options, selectorIndex + 1); if (selector.charAt(selectorIndex) !== ")") { throw new Error("Missing closing parenthesis in :" + name_3 + " (" + selector + ")"); } selectorIndex += 1; } else { selectorIndex += 1; var start = selectorIndex; var counter = 1; for (; counter > 0 && selectorIndex < selector.length; selectorIndex++) { if (selector.charAt(selectorIndex) === "(" && !isEscaped(selectorIndex)) { counter++; } else if (selector.charAt(selectorIndex) === ")" && !isEscaped(selectorIndex)) { counter--; } } if (counter) { throw new Error("Parenthesis not matched"); } data = selector.slice(start, selectorIndex - 1); if (stripQuotesFromPseudos.has(name_3)) { var quot = data.charAt(0); if (quot === data.slice(-1) && quotes.has(quot)) { data = data.slice(1, -1); } data = unescapeCSS(data); } } } tokens.push({ type: "pseudo", name: name_3, data: data }); } else { var namespace = null; var name_4 = void 0; if (firstChar === "*") { selectorIndex += 1; name_4 = "*"; } else if (reName.test(selector.slice(selectorIndex))) { if (selector.charAt(selectorIndex) === "|") { namespace = ""; selectorIndex += 1; } name_4 = getName(0); } else { /* * We have finished parsing 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. */ if (tokens.length && tokens[tokens.length - 1].type === "descendant") { tokens.pop(); } addToken(subselects, tokens); return selectorIndex; } if (selector.charAt(selectorIndex) === "|") { namespace = name_4; if (selector.charAt(selectorIndex + 1) === "*") { name_4 = "*"; selectorIndex += 2; } else { name_4 = getName(1); } } if (name_4 === "*") { tokens.push({ type: "universal", namespace: namespace }); } else { if ((_b = options.lowerCaseTags) !== null && _b !== void 0 ? _b : !options.xmlMode) { name_4 = name_4.toLowerCase(); } tokens.push({ type: "tag", name: name_4, namespace: namespace }); } } } } addToken(subselects, tokens); return selectorIndex; } function addToken(subselects, tokens) { if (subselects.length > 0 && tokens.length === 0) { throw new Error("Empty sub-selector"); } subselects.push(tokens); }