parse.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428
  1. "use strict";
  2. var __spreadArray = (this && this.__spreadArray) || function (to, from) {
  3. for (var i = 0, il = from.length, j = to.length; i < il; i++, j++)
  4. to[j] = from[i];
  5. return to;
  6. };
  7. Object.defineProperty(exports, "__esModule", { value: true });
  8. exports.isTraversal = void 0;
  9. var reName = /^[^\\#]?(?:\\(?:[\da-f]{1,6}\s?|.)|[\w\-\u00b0-\uFFFF])+/;
  10. var reEscape = /\\([\da-f]{1,6}\s?|(\s)|.)/gi;
  11. var actionTypes = new Map([
  12. ["~", "element"],
  13. ["^", "start"],
  14. ["$", "end"],
  15. ["*", "any"],
  16. ["!", "not"],
  17. ["|", "hyphen"],
  18. ]);
  19. var Traversals = {
  20. ">": "child",
  21. "<": "parent",
  22. "~": "sibling",
  23. "+": "adjacent",
  24. };
  25. var attribSelectors = {
  26. "#": ["id", "equals"],
  27. ".": ["class", "element"],
  28. };
  29. // Pseudos, whose data property is parsed as well.
  30. var unpackPseudos = new Set([
  31. "has",
  32. "not",
  33. "matches",
  34. "is",
  35. "host",
  36. "host-context",
  37. ]);
  38. var traversalNames = new Set(__spreadArray([
  39. "descendant"
  40. ], Object.keys(Traversals).map(function (k) { return Traversals[k]; })));
  41. /**
  42. * Attributes that are case-insensitive in HTML.
  43. *
  44. * @private
  45. * @see https://html.spec.whatwg.org/multipage/semantics-other.html#case-sensitivity-of-selectors
  46. */
  47. var caseInsensitiveAttributes = new Set([
  48. "accept",
  49. "accept-charset",
  50. "align",
  51. "alink",
  52. "axis",
  53. "bgcolor",
  54. "charset",
  55. "checked",
  56. "clear",
  57. "codetype",
  58. "color",
  59. "compact",
  60. "declare",
  61. "defer",
  62. "dir",
  63. "direction",
  64. "disabled",
  65. "enctype",
  66. "face",
  67. "frame",
  68. "hreflang",
  69. "http-equiv",
  70. "lang",
  71. "language",
  72. "link",
  73. "media",
  74. "method",
  75. "multiple",
  76. "nohref",
  77. "noresize",
  78. "noshade",
  79. "nowrap",
  80. "readonly",
  81. "rel",
  82. "rev",
  83. "rules",
  84. "scope",
  85. "scrolling",
  86. "selected",
  87. "shape",
  88. "target",
  89. "text",
  90. "type",
  91. "valign",
  92. "valuetype",
  93. "vlink",
  94. ]);
  95. /**
  96. * Checks whether a specific selector is a traversal.
  97. * This is useful eg. in swapping the order of elements that
  98. * are not traversals.
  99. *
  100. * @param selector Selector to check.
  101. */
  102. function isTraversal(selector) {
  103. return traversalNames.has(selector.type);
  104. }
  105. exports.isTraversal = isTraversal;
  106. var stripQuotesFromPseudos = new Set(["contains", "icontains"]);
  107. var quotes = new Set(['"', "'"]);
  108. // Unescape function taken from https://github.com/jquery/sizzle/blob/master/src/sizzle.js#L152
  109. function funescape(_, escaped, escapedWhitespace) {
  110. var high = parseInt(escaped, 16) - 0x10000;
  111. // NaN means non-codepoint
  112. return high !== high || escapedWhitespace
  113. ? escaped
  114. : high < 0
  115. ? // BMP codepoint
  116. String.fromCharCode(high + 0x10000)
  117. : // Supplemental Plane codepoint (surrogate pair)
  118. String.fromCharCode((high >> 10) | 0xd800, (high & 0x3ff) | 0xdc00);
  119. }
  120. function unescapeCSS(str) {
  121. return str.replace(reEscape, funescape);
  122. }
  123. function isWhitespace(c) {
  124. return c === " " || c === "\n" || c === "\t" || c === "\f" || c === "\r";
  125. }
  126. /**
  127. * Parses `selector`, optionally with the passed `options`.
  128. *
  129. * @param selector Selector to parse.
  130. * @param options Options for parsing.
  131. * @returns Returns a two-dimensional array.
  132. * The first dimension represents selectors separated by commas (eg. `sub1, sub2`),
  133. * the second contains the relevant tokens for that selector.
  134. */
  135. function parse(selector, options) {
  136. var subselects = [];
  137. var endIndex = parseSelector(subselects, "" + selector, options, 0);
  138. if (endIndex < selector.length) {
  139. throw new Error("Unmatched selector: " + selector.slice(endIndex));
  140. }
  141. return subselects;
  142. }
  143. exports.default = parse;
  144. function parseSelector(subselects, selector, options, selectorIndex) {
  145. var _a, _b;
  146. if (options === void 0) { options = {}; }
  147. var tokens = [];
  148. var sawWS = false;
  149. function getName(offset) {
  150. var match = selector.slice(selectorIndex + offset).match(reName);
  151. if (!match) {
  152. throw new Error("Expected name, found " + selector.slice(selectorIndex));
  153. }
  154. var name = match[0];
  155. selectorIndex += offset + name.length;
  156. return unescapeCSS(name);
  157. }
  158. function stripWhitespace(offset) {
  159. while (isWhitespace(selector.charAt(selectorIndex + offset)))
  160. offset++;
  161. selectorIndex += offset;
  162. }
  163. function isEscaped(pos) {
  164. var slashCount = 0;
  165. while (selector.charAt(--pos) === "\\")
  166. slashCount++;
  167. return (slashCount & 1) === 1;
  168. }
  169. function ensureNotTraversal() {
  170. if (tokens.length > 0 && isTraversal(tokens[tokens.length - 1])) {
  171. throw new Error("Did not expect successive traversals.");
  172. }
  173. }
  174. stripWhitespace(0);
  175. while (selector !== "") {
  176. var firstChar = selector.charAt(selectorIndex);
  177. if (isWhitespace(firstChar)) {
  178. sawWS = true;
  179. stripWhitespace(1);
  180. }
  181. else if (firstChar in Traversals) {
  182. ensureNotTraversal();
  183. tokens.push({ type: Traversals[firstChar] });
  184. sawWS = false;
  185. stripWhitespace(1);
  186. }
  187. else if (firstChar === ",") {
  188. if (tokens.length === 0) {
  189. throw new Error("Empty sub-selector");
  190. }
  191. subselects.push(tokens);
  192. tokens = [];
  193. sawWS = false;
  194. stripWhitespace(1);
  195. }
  196. else if (selector.startsWith("/*", selectorIndex)) {
  197. var endIndex = selector.indexOf("*/", selectorIndex + 2);
  198. if (endIndex < 0) {
  199. throw new Error("Comment was not terminated");
  200. }
  201. selectorIndex = endIndex + 2;
  202. }
  203. else {
  204. if (sawWS) {
  205. ensureNotTraversal();
  206. tokens.push({ type: "descendant" });
  207. sawWS = false;
  208. }
  209. if (firstChar in attribSelectors) {
  210. var _c = attribSelectors[firstChar], name_1 = _c[0], action = _c[1];
  211. tokens.push({
  212. type: "attribute",
  213. name: name_1,
  214. action: action,
  215. value: getName(1),
  216. namespace: null,
  217. // TODO: Add quirksMode option, which makes `ignoreCase` `true` for HTML.
  218. ignoreCase: options.xmlMode ? null : false,
  219. });
  220. }
  221. else if (firstChar === "[") {
  222. stripWhitespace(1);
  223. // Determine attribute name and namespace
  224. var name_2 = void 0;
  225. var namespace = null;
  226. if (selector.charAt(selectorIndex) === "|") {
  227. namespace = "";
  228. selectorIndex += 1;
  229. }
  230. if (selector.startsWith("*|", selectorIndex)) {
  231. namespace = "*";
  232. selectorIndex += 2;
  233. }
  234. name_2 = getName(0);
  235. if (namespace === null &&
  236. selector.charAt(selectorIndex) === "|" &&
  237. selector.charAt(selectorIndex + 1) !== "=") {
  238. namespace = name_2;
  239. name_2 = getName(1);
  240. }
  241. if ((_a = options.lowerCaseAttributeNames) !== null && _a !== void 0 ? _a : !options.xmlMode) {
  242. name_2 = name_2.toLowerCase();
  243. }
  244. stripWhitespace(0);
  245. // Determine comparison operation
  246. var action = "exists";
  247. var possibleAction = actionTypes.get(selector.charAt(selectorIndex));
  248. if (possibleAction) {
  249. action = possibleAction;
  250. if (selector.charAt(selectorIndex + 1) !== "=") {
  251. throw new Error("Expected `=`");
  252. }
  253. stripWhitespace(2);
  254. }
  255. else if (selector.charAt(selectorIndex) === "=") {
  256. action = "equals";
  257. stripWhitespace(1);
  258. }
  259. // Determine value
  260. var value = "";
  261. var ignoreCase = null;
  262. if (action !== "exists") {
  263. if (quotes.has(selector.charAt(selectorIndex))) {
  264. var quote = selector.charAt(selectorIndex);
  265. var sectionEnd = selectorIndex + 1;
  266. while (sectionEnd < selector.length &&
  267. (selector.charAt(sectionEnd) !== quote ||
  268. isEscaped(sectionEnd))) {
  269. sectionEnd += 1;
  270. }
  271. if (selector.charAt(sectionEnd) !== quote) {
  272. throw new Error("Attribute value didn't end");
  273. }
  274. value = unescapeCSS(selector.slice(selectorIndex + 1, sectionEnd));
  275. selectorIndex = sectionEnd + 1;
  276. }
  277. else {
  278. var valueStart = selectorIndex;
  279. while (selectorIndex < selector.length &&
  280. ((!isWhitespace(selector.charAt(selectorIndex)) &&
  281. selector.charAt(selectorIndex) !== "]") ||
  282. isEscaped(selectorIndex))) {
  283. selectorIndex += 1;
  284. }
  285. value = unescapeCSS(selector.slice(valueStart, selectorIndex));
  286. }
  287. stripWhitespace(0);
  288. // See if we have a force ignore flag
  289. var forceIgnore = selector.charAt(selectorIndex);
  290. // If the forceIgnore flag is set (either `i` or `s`), use that value
  291. if (forceIgnore === "s" || forceIgnore === "S") {
  292. ignoreCase = false;
  293. stripWhitespace(1);
  294. }
  295. else if (forceIgnore === "i" || forceIgnore === "I") {
  296. ignoreCase = true;
  297. stripWhitespace(1);
  298. }
  299. }
  300. // If `xmlMode` is set, there are no rules; otherwise, use the `caseInsensitiveAttributes` list.
  301. if (!options.xmlMode) {
  302. // TODO: Skip this for `exists`, as there is no value to compare to.
  303. ignoreCase !== null && ignoreCase !== void 0 ? ignoreCase : (ignoreCase = caseInsensitiveAttributes.has(name_2));
  304. }
  305. if (selector.charAt(selectorIndex) !== "]") {
  306. throw new Error("Attribute selector didn't terminate");
  307. }
  308. selectorIndex += 1;
  309. var attributeSelector = {
  310. type: "attribute",
  311. name: name_2,
  312. action: action,
  313. value: value,
  314. namespace: namespace,
  315. ignoreCase: ignoreCase,
  316. };
  317. tokens.push(attributeSelector);
  318. }
  319. else if (firstChar === ":") {
  320. if (selector.charAt(selectorIndex + 1) === ":") {
  321. tokens.push({
  322. type: "pseudo-element",
  323. name: getName(2).toLowerCase(),
  324. });
  325. continue;
  326. }
  327. var name_3 = getName(1).toLowerCase();
  328. var data = null;
  329. if (selector.charAt(selectorIndex) === "(") {
  330. if (unpackPseudos.has(name_3)) {
  331. if (quotes.has(selector.charAt(selectorIndex + 1))) {
  332. throw new Error("Pseudo-selector " + name_3 + " cannot be quoted");
  333. }
  334. data = [];
  335. selectorIndex = parseSelector(data, selector, options, selectorIndex + 1);
  336. if (selector.charAt(selectorIndex) !== ")") {
  337. throw new Error("Missing closing parenthesis in :" + name_3 + " (" + selector + ")");
  338. }
  339. selectorIndex += 1;
  340. }
  341. else {
  342. selectorIndex += 1;
  343. var start = selectorIndex;
  344. var counter = 1;
  345. for (; counter > 0 && selectorIndex < selector.length; selectorIndex++) {
  346. if (selector.charAt(selectorIndex) === "(" &&
  347. !isEscaped(selectorIndex)) {
  348. counter++;
  349. }
  350. else if (selector.charAt(selectorIndex) === ")" &&
  351. !isEscaped(selectorIndex)) {
  352. counter--;
  353. }
  354. }
  355. if (counter) {
  356. throw new Error("Parenthesis not matched");
  357. }
  358. data = selector.slice(start, selectorIndex - 1);
  359. if (stripQuotesFromPseudos.has(name_3)) {
  360. var quot = data.charAt(0);
  361. if (quot === data.slice(-1) && quotes.has(quot)) {
  362. data = data.slice(1, -1);
  363. }
  364. data = unescapeCSS(data);
  365. }
  366. }
  367. }
  368. tokens.push({ type: "pseudo", name: name_3, data: data });
  369. }
  370. else {
  371. var namespace = null;
  372. var name_4 = void 0;
  373. if (firstChar === "*") {
  374. selectorIndex += 1;
  375. name_4 = "*";
  376. }
  377. else if (reName.test(selector.slice(selectorIndex))) {
  378. if (selector.charAt(selectorIndex) === "|") {
  379. namespace = "";
  380. selectorIndex += 1;
  381. }
  382. name_4 = getName(0);
  383. }
  384. else {
  385. /*
  386. * We have finished parsing the selector.
  387. * Remove descendant tokens at the end if they exist,
  388. * and return the last index, so that parsing can be
  389. * picked up from here.
  390. */
  391. if (tokens.length &&
  392. tokens[tokens.length - 1].type === "descendant") {
  393. tokens.pop();
  394. }
  395. addToken(subselects, tokens);
  396. return selectorIndex;
  397. }
  398. if (selector.charAt(selectorIndex) === "|") {
  399. namespace = name_4;
  400. if (selector.charAt(selectorIndex + 1) === "*") {
  401. name_4 = "*";
  402. selectorIndex += 2;
  403. }
  404. else {
  405. name_4 = getName(1);
  406. }
  407. }
  408. if (name_4 === "*") {
  409. tokens.push({ type: "universal", namespace: namespace });
  410. }
  411. else {
  412. if ((_b = options.lowerCaseTags) !== null && _b !== void 0 ? _b : !options.xmlMode) {
  413. name_4 = name_4.toLowerCase();
  414. }
  415. tokens.push({ type: "tag", name: name_4, namespace: namespace });
  416. }
  417. }
  418. }
  419. }
  420. addToken(subselects, tokens);
  421. return selectorIndex;
  422. }
  423. function addToken(subselects, tokens) {
  424. if (subselects.length > 0 && tokens.length === 0) {
  425. throw new Error("Empty sub-selector");
  426. }
  427. subselects.push(tokens);
  428. }