Spaces:
Running
Running
; | |
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; | |
} | |