basant307/AI_Governance_Project
048
1import { SelectorType, AttributeAction, } from "./types";2const reName = /^[^\\#]?(?:\\(?:[\da-f]{1,6}\s?|.)|[\w\-\u00b0-\uFFFF])+/;3const reEscape = /\\([\da-f]{1,6}\s?|(\s)|.)/gi;4const actionTypes = new Map([5 [126 /* Tilde */, AttributeAction.Element],6 [94 /* Circumflex */, AttributeAction.Start],7 [36 /* Dollar */, AttributeAction.End],8 [42 /* Asterisk */, AttributeAction.Any],9 [33 /* ExclamationMark */, AttributeAction.Not],10 [124 /* Pipe */, AttributeAction.Hyphen],11]);12// Pseudos, whose data property is parsed as well.13const unpackPseudos = new Set([14 "has",15 "not",16 "matches",17 "is",18 "where",19 "host",20 "host-context",21]);22/**23 * Checks whether a specific selector is a traversal.24 * This is useful eg. in swapping the order of elements that25 * are not traversals.26 *27 * @param selector Selector to check.28 */29export function isTraversal(selector) {30 switch (selector.type) {31 case SelectorType.Adjacent:32 case SelectorType.Child:33 case SelectorType.Descendant:34 case SelectorType.Parent:35 case SelectorType.Sibling:36 case SelectorType.ColumnCombinator:37 return true;38 default:39 return false;40 }41}42const stripQuotesFromPseudos = new Set(["contains", "icontains"]);43// Unescape function taken from https://github.com/jquery/sizzle/blob/master/src/sizzle.js#L15244function funescape(_, escaped, escapedWhitespace) {45 const high = parseInt(escaped, 16) - 0x10000;46 // NaN means non-codepoint47 return high !== high || escapedWhitespace48 ? escaped49 : high < 050 ? // BMP codepoint51 String.fromCharCode(high + 0x10000)52 : // Supplemental Plane codepoint (surrogate pair)53 String.fromCharCode((high >> 10) | 0xd800, (high & 0x3ff) | 0xdc00);54}55function unescapeCSS(str) {56 return str.replace(reEscape, funescape);57}58function isQuote(c) {59 return c === 39 /* SingleQuote */ || c === 34 /* DoubleQuote */;60}61function isWhitespace(c) {62 return (c === 32 /* Space */ ||63 c === 9 /* Tab */ ||64 c === 10 /* NewLine */ ||65 c === 12 /* FormFeed */ ||66 c === 13 /* CarriageReturn */);67}68/**69 * Parses `selector`, optionally with the passed `options`.70 *71 * @param selector Selector to parse.72 * @param options Options for parsing.73 * @returns Returns a two-dimensional array.74 * The first dimension represents selectors separated by commas (eg. `sub1, sub2`),75 * the second contains the relevant tokens for that selector.76 */77export function parse(selector) {78 const subselects = [];79 const endIndex = parseSelector(subselects, `${selector}`, 0);80 if (endIndex < selector.length) {81 throw new Error(`Unmatched selector: ${selector.slice(endIndex)}`);82 }83 return subselects;84}85function parseSelector(subselects, selector, selectorIndex) {86 let tokens = [];87 function getName(offset) {88 const match = selector.slice(selectorIndex + offset).match(reName);89 if (!match) {90 throw new Error(`Expected name, found ${selector.slice(selectorIndex)}`);91 }92 const [name] = match;93 selectorIndex += offset + name.length;94 return unescapeCSS(name);95 }96 function stripWhitespace(offset) {97 selectorIndex += offset;98 while (selectorIndex < selector.length &&99 isWhitespace(selector.charCodeAt(selectorIndex))) {100 selectorIndex++;101 }102 }103 function readValueWithParenthesis() {104 selectorIndex += 1;105 const start = selectorIndex;106 let counter = 1;107 for (; counter > 0 && selectorIndex < selector.length; selectorIndex++) {108 if (selector.charCodeAt(selectorIndex) ===109 40 /* LeftParenthesis */ &&110 !isEscaped(selectorIndex)) {111 counter++;112 }113 else if (selector.charCodeAt(selectorIndex) ===114 41 /* RightParenthesis */ &&115 !isEscaped(selectorIndex)) {116 counter--;117 }118 }119 if (counter) {120 throw new Error("Parenthesis not matched");121 }122 return unescapeCSS(selector.slice(start, selectorIndex - 1));123 }124 function isEscaped(pos) {125 let slashCount = 0;126 while (selector.charCodeAt(--pos) === 92 /* BackSlash */)127 slashCount++;128 return (slashCount & 1) === 1;129 }130 function ensureNotTraversal() {131 if (tokens.length > 0 && isTraversal(tokens[tokens.length - 1])) {132 throw new Error("Did not expect successive traversals.");133 }134 }135 function addTraversal(type) {136 if (tokens.length > 0 &&137 tokens[tokens.length - 1].type === SelectorType.Descendant) {138 tokens[tokens.length - 1].type = type;139 return;140 }141 ensureNotTraversal();142 tokens.push({ type });143 }144 function addSpecialAttribute(name, action) {145 tokens.push({146 type: SelectorType.Attribute,147 name,148 action,149 value: getName(1),150 namespace: null,151 ignoreCase: "quirks",152 });153 }154 /**155 * We have finished parsing the current part of the selector.156 *157 * Remove descendant tokens at the end if they exist,158 * and return the last index, so that parsing can be159 * picked up from here.160 */161 function finalizeSubselector() {162 if (tokens.length &&163 tokens[tokens.length - 1].type === SelectorType.Descendant) {164 tokens.pop();165 }166 if (tokens.length === 0) {167 throw new Error("Empty sub-selector");168 }169 subselects.push(tokens);170 }171 stripWhitespace(0);172 if (selector.length === selectorIndex) {173 return selectorIndex;174 }175 loop: while (selectorIndex < selector.length) {176 const firstChar = selector.charCodeAt(selectorIndex);177 switch (firstChar) {178 // Whitespace179 case 32 /* Space */:180 case 9 /* Tab */:181 case 10 /* NewLine */:182 case 12 /* FormFeed */:183 case 13 /* CarriageReturn */: {184 if (tokens.length === 0 ||185 tokens[0].type !== SelectorType.Descendant) {186 ensureNotTraversal();187 tokens.push({ type: SelectorType.Descendant });188 }189 stripWhitespace(1);190 break;191 }192 // Traversals193 case 62 /* GreaterThan */: {194 addTraversal(SelectorType.Child);195 stripWhitespace(1);196 break;197 }198 case 60 /* LessThan */: {199 addTraversal(SelectorType.Parent);200 stripWhitespace(1);201 break;202 }203 case 126 /* Tilde */: {204 addTraversal(SelectorType.Sibling);205 stripWhitespace(1);206 break;207 }208 case 43 /* Plus */: {209 addTraversal(SelectorType.Adjacent);210 stripWhitespace(1);211 break;212 }213 // Special attribute selectors: .class, #id214 case 46 /* Period */: {215 addSpecialAttribute("class", AttributeAction.Element);216 break;217 }218 case 35 /* Hash */: {219 addSpecialAttribute("id", AttributeAction.Equals);220 break;221 }222 case 91 /* LeftSquareBracket */: {223 stripWhitespace(1);224 // Determine attribute name and namespace225 let name;226 let namespace = null;227 if (selector.charCodeAt(selectorIndex) === 124 /* Pipe */) {228 // Equivalent to no namespace229 name = getName(1);230 }231 else if (selector.startsWith("*|", selectorIndex)) {232 namespace = "*";233 name = getName(2);234 }235 else {236 name = getName(0);237 if (selector.charCodeAt(selectorIndex) === 124 /* Pipe */ &&238 selector.charCodeAt(selectorIndex + 1) !==239 61 /* Equal */) {240 namespace = name;241 name = getName(1);242 }243 }244 stripWhitespace(0);245 // Determine comparison operation246 let action = AttributeAction.Exists;247 const possibleAction = actionTypes.get(selector.charCodeAt(selectorIndex));248 if (possibleAction) {249 action = possibleAction;250 if (selector.charCodeAt(selectorIndex + 1) !==251 61 /* Equal */) {252 throw new Error("Expected `=`");253 }254 stripWhitespace(2);255 }256 else if (selector.charCodeAt(selectorIndex) === 61 /* Equal */) {257 action = AttributeAction.Equals;258 stripWhitespace(1);259 }260 // Determine value261 let value = "";262 let ignoreCase = null;263 if (action !== "exists") {264 if (isQuote(selector.charCodeAt(selectorIndex))) {265 const quote = selector.charCodeAt(selectorIndex);266 let sectionEnd = selectorIndex + 1;267 while (sectionEnd < selector.length &&268 (selector.charCodeAt(sectionEnd) !== quote ||269 isEscaped(sectionEnd))) {270 sectionEnd += 1;271 }272 if (selector.charCodeAt(sectionEnd) !== quote) {273 throw new Error("Attribute value didn't end");274 }275 value = unescapeCSS(selector.slice(selectorIndex + 1, sectionEnd));276 selectorIndex = sectionEnd + 1;277 }278 else {279 const valueStart = selectorIndex;280 while (selectorIndex < selector.length &&281 ((!isWhitespace(selector.charCodeAt(selectorIndex)) &&282 selector.charCodeAt(selectorIndex) !==283 93 /* RightSquareBracket */) ||284 isEscaped(selectorIndex))) {285 selectorIndex += 1;286 }287 value = unescapeCSS(selector.slice(valueStart, selectorIndex));288 }289 stripWhitespace(0);290 // See if we have a force ignore flag291 const forceIgnore = selector.charCodeAt(selectorIndex) | 0x20;292 // If the forceIgnore flag is set (either `i` or `s`), use that value293 if (forceIgnore === 115 /* LowerS */) {294 ignoreCase = false;295 stripWhitespace(1);296 }297 else if (forceIgnore === 105 /* LowerI */) {298 ignoreCase = true;299 stripWhitespace(1);300 }301 }302 if (selector.charCodeAt(selectorIndex) !==303 93 /* RightSquareBracket */) {304 throw new Error("Attribute selector didn't terminate");305 }306 selectorIndex += 1;307 const attributeSelector = {308 type: SelectorType.Attribute,309 name,310 action,311 value,312 namespace,313 ignoreCase,314 };315 tokens.push(attributeSelector);316 break;317 }318 case 58 /* Colon */: {319 if (selector.charCodeAt(selectorIndex + 1) === 58 /* Colon */) {320 tokens.push({321 type: SelectorType.PseudoElement,322 name: getName(2).toLowerCase(),323 data: selector.charCodeAt(selectorIndex) ===324 40 /* LeftParenthesis */325 ? readValueWithParenthesis()326 : null,327 });328 continue;329 }330 const name = getName(1).toLowerCase();331 let data = null;332 if (selector.charCodeAt(selectorIndex) ===333 40 /* LeftParenthesis */) {334 if (unpackPseudos.has(name)) {335 if (isQuote(selector.charCodeAt(selectorIndex + 1))) {336 throw new Error(`Pseudo-selector ${name} cannot be quoted`);337 }338 data = [];339 selectorIndex = parseSelector(data, selector, selectorIndex + 1);340 if (selector.charCodeAt(selectorIndex) !==341 41 /* RightParenthesis */) {342 throw new Error(`Missing closing parenthesis in :${name} (${selector})`);343 }344 selectorIndex += 1;345 }346 else {347 data = readValueWithParenthesis();348 if (stripQuotesFromPseudos.has(name)) {349 const quot = data.charCodeAt(0);350 if (quot === data.charCodeAt(data.length - 1) &&351 isQuote(quot)) {352 data = data.slice(1, -1);353 }354 }355 data = unescapeCSS(data);356 }357 }358 tokens.push({ type: SelectorType.Pseudo, name, data });359 break;360 }361 case 44 /* Comma */: {362 finalizeSubselector();363 tokens = [];364 stripWhitespace(1);365 break;366 }367 default: {368 if (selector.startsWith("/*", selectorIndex)) {369 const endIndex = selector.indexOf("*/", selectorIndex + 2);370 if (endIndex < 0) {371 throw new Error("Comment was not terminated");372 }373 selectorIndex = endIndex + 2;374 // Remove leading whitespace375 if (tokens.length === 0) {376 stripWhitespace(0);377 }378 break;379 }380 let namespace = null;381 let name;382 if (firstChar === 42 /* Asterisk */) {383 selectorIndex += 1;384 name = "*";385 }386 else if (firstChar === 124 /* Pipe */) {387 name = "";388 if (selector.charCodeAt(selectorIndex + 1) === 124 /* Pipe */) {389 addTraversal(SelectorType.ColumnCombinator);390 stripWhitespace(2);391 break;392 }393 }394 else if (reName.test(selector.slice(selectorIndex))) {395 name = getName(0);396 }397 else {398 break loop;399 }400 if (selector.charCodeAt(selectorIndex) === 124 /* Pipe */ &&401 selector.charCodeAt(selectorIndex + 1) !== 124 /* Pipe */) {402 namespace = name;403 if (selector.charCodeAt(selectorIndex + 1) ===404 42 /* Asterisk */) {405 name = "*";406 selectorIndex += 2;407 }408 else {409 name = getName(1);410 }411 }412 tokens.push(name === "*"413 ? { type: SelectorType.Universal, namespace }414 : { type: SelectorType.Tag, name, namespace });415 }416 }417 }418 finalizeSubselector();419 return selectorIndex;420}421 