parse.js 8.5 KB

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