parse.js 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. exports.default = parse;
  4. var reName = /^[^\\]?(?:\\(?:[\da-f]{1,6}\s?|.)|[\w\-\u00b0-\uFFFF])+/;
  5. var reEscape = /\\([\da-f]{1,6}\s?|(\s)|.)/gi;
  6. // Modified version of https://github.com/jquery/sizzle/blob/master/src/sizzle.js#L87
  7. var reAttr = /^\s*((?:\\.|[\w\u00b0-\uFFFF-])+)\s*(?:(\S?)=\s*(?:(['"])((?:[^\\]|\\[^])*?)\3|(#?(?:\\.|[\w\u00b0-\uFFFF-])*)|)|)\s*(i)?\]/;
  8. var actionTypes = {
  9. undefined: "exists",
  10. "": "equals",
  11. "~": "element",
  12. "^": "start",
  13. $: "end",
  14. "*": "any",
  15. "!": "not",
  16. "|": "hyphen",
  17. };
  18. var Traversals = {
  19. ">": "child",
  20. "<": "parent",
  21. "~": "sibling",
  22. "+": "adjacent",
  23. };
  24. var attribSelectors = {
  25. "#": ["id", "equals"],
  26. ".": ["class", "element"],
  27. };
  28. // Pseudos, whose data property is parsed as well.
  29. var unpackPseudos = new Set([
  30. "has",
  31. "not",
  32. "matches",
  33. "is",
  34. "host",
  35. "host-context",
  36. ]);
  37. var stripQuotesFromPseudos = new Set(["contains", "icontains"]);
  38. var quotes = new Set(['"', "'"]);
  39. // Unescape function taken from https://github.com/jquery/sizzle/blob/master/src/sizzle.js#L152
  40. function funescape(_, escaped, escapedWhitespace) {
  41. var high = parseInt(escaped, 16) - 0x10000;
  42. // NaN means non-codepoint
  43. return high !== high || escapedWhitespace
  44. ? escaped
  45. : high < 0
  46. ? // BMP codepoint
  47. String.fromCharCode(high + 0x10000)
  48. : // Supplemental Plane codepoint (surrogate pair)
  49. String.fromCharCode((high >> 10) | 0xd800, (high & 0x3ff) | 0xdc00);
  50. }
  51. function unescapeCSS(str) {
  52. return str.replace(reEscape, funescape);
  53. }
  54. function isWhitespace(c) {
  55. return c === " " || c === "\n" || c === "\t" || c === "\f" || c === "\r";
  56. }
  57. function parse(selector, options) {
  58. var subselects = [];
  59. selector = parseSelector(subselects, "" + selector, options);
  60. if (selector !== "") {
  61. throw new Error("Unmatched selector: " + selector);
  62. }
  63. return subselects;
  64. }
  65. function parseSelector(subselects, selector, options) {
  66. var _a, _b;
  67. if (options === void 0) { options = {}; }
  68. var tokens = [];
  69. var sawWS = false;
  70. function getName() {
  71. var match = selector.match(reName);
  72. if (!match) {
  73. throw new Error("Expected name, found " + selector);
  74. }
  75. var sub = match[0];
  76. selector = selector.substr(sub.length);
  77. return unescapeCSS(sub);
  78. }
  79. function stripWhitespace(start) {
  80. while (isWhitespace(selector.charAt(start)))
  81. start++;
  82. selector = selector.substr(start);
  83. }
  84. function isEscaped(pos) {
  85. var slashCount = 0;
  86. while (selector.charAt(--pos) === "\\")
  87. slashCount++;
  88. return (slashCount & 1) === 1;
  89. }
  90. stripWhitespace(0);
  91. while (selector !== "") {
  92. var firstChar = selector.charAt(0);
  93. if (isWhitespace(firstChar)) {
  94. sawWS = true;
  95. stripWhitespace(1);
  96. }
  97. else if (firstChar in Traversals) {
  98. tokens.push({ type: Traversals[firstChar] });
  99. sawWS = false;
  100. stripWhitespace(1);
  101. }
  102. else if (firstChar === ",") {
  103. if (tokens.length === 0) {
  104. throw new Error("Empty sub-selector");
  105. }
  106. subselects.push(tokens);
  107. tokens = [];
  108. sawWS = false;
  109. stripWhitespace(1);
  110. }
  111. else {
  112. if (sawWS) {
  113. if (tokens.length > 0) {
  114. tokens.push({ type: "descendant" });
  115. }
  116. sawWS = false;
  117. }
  118. if (firstChar === "*") {
  119. selector = selector.substr(1);
  120. tokens.push({ type: "universal" });
  121. }
  122. else if (firstChar in attribSelectors) {
  123. var _c = attribSelectors[firstChar], name_1 = _c[0], action = _c[1];
  124. selector = selector.substr(1);
  125. tokens.push({
  126. type: "attribute",
  127. name: name_1,
  128. action: action,
  129. value: getName(),
  130. ignoreCase: false,
  131. });
  132. }
  133. else if (firstChar === "[") {
  134. selector = selector.substr(1);
  135. var attributeMatch = selector.match(reAttr);
  136. if (!attributeMatch) {
  137. throw new Error("Malformed attribute selector: " + selector);
  138. }
  139. var completeSelector = attributeMatch[0], baseName = attributeMatch[1], actionType = attributeMatch[2], _d = attributeMatch[4], quotedValue = _d === void 0 ? "" : _d, _e = attributeMatch[5], value = _e === void 0 ? quotedValue : _e, ignoreCase = attributeMatch[6];
  140. selector = selector.substr(completeSelector.length);
  141. var name_2 = unescapeCSS(baseName);
  142. if ((_a = options.lowerCaseAttributeNames) !== null && _a !== void 0 ? _a : !options.xmlMode) {
  143. name_2 = name_2.toLowerCase();
  144. }
  145. tokens.push({
  146. type: "attribute",
  147. name: name_2,
  148. action: actionTypes[actionType],
  149. value: unescapeCSS(value),
  150. ignoreCase: !!ignoreCase,
  151. });
  152. }
  153. else if (firstChar === ":") {
  154. if (selector.charAt(1) === ":") {
  155. selector = selector.substr(2);
  156. tokens.push({
  157. type: "pseudo-element",
  158. name: getName().toLowerCase(),
  159. });
  160. continue;
  161. }
  162. selector = selector.substr(1);
  163. var name_3 = getName().toLowerCase();
  164. var data = null;
  165. if (selector.startsWith("(")) {
  166. if (unpackPseudos.has(name_3)) {
  167. var quot = selector.charAt(1);
  168. var quoted = quotes.has(quot);
  169. selector = selector.substr(quoted ? 2 : 1);
  170. data = [];
  171. selector = parseSelector(data, selector, options);
  172. if (quoted) {
  173. if (!selector.startsWith(quot)) {
  174. throw new Error("Unmatched quotes in :" + name_3);
  175. }
  176. else {
  177. selector = selector.substr(1);
  178. }
  179. }
  180. if (!selector.startsWith(")")) {
  181. throw new Error("Missing closing parenthesis in :" + name_3 + " (" + selector + ")");
  182. }
  183. selector = selector.substr(1);
  184. }
  185. else {
  186. var pos = 1;
  187. var counter = 1;
  188. for (; counter > 0 && pos < selector.length; pos++) {
  189. if (selector.charAt(pos) === "(" &&
  190. !isEscaped(pos)) {
  191. counter++;
  192. }
  193. else if (selector.charAt(pos) === ")" &&
  194. !isEscaped(pos)) {
  195. counter--;
  196. }
  197. }
  198. if (counter) {
  199. throw new Error("Parenthesis not matched");
  200. }
  201. data = selector.substr(1, pos - 2);
  202. selector = selector.substr(pos);
  203. if (stripQuotesFromPseudos.has(name_3)) {
  204. var quot = data.charAt(0);
  205. if (quot === data.slice(-1) && quotes.has(quot)) {
  206. data = data.slice(1, -1);
  207. }
  208. data = unescapeCSS(data);
  209. }
  210. }
  211. }
  212. tokens.push({ type: "pseudo", name: name_3, data: data });
  213. }
  214. else if (reName.test(selector)) {
  215. var name_4 = getName();
  216. if ((_b = options.lowerCaseTags) !== null && _b !== void 0 ? _b : !options.xmlMode) {
  217. name_4 = name_4.toLowerCase();
  218. }
  219. tokens.push({ type: "tag", name: name_4 });
  220. }
  221. else {
  222. if (tokens.length &&
  223. tokens[tokens.length - 1].type === "descendant") {
  224. tokens.pop();
  225. }
  226. addToken(subselects, tokens);
  227. return selector;
  228. }
  229. }
  230. }
  231. addToken(subselects, tokens);
  232. return selector;
  233. }
  234. function addToken(subselects, tokens) {
  235. if (subselects.length > 0 && tokens.length === 0) {
  236. throw new Error("Empty sub-selector");
  237. }
  238. subselects.push(tokens);
  239. }