blob: 786b13ee18592e6297fdc3799fef9192cb4d0534 [file] [log] [blame]
"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);
}