UNPKG

4.5 kBJavaScriptView Raw
1"use strict";
2
3module.exports = parse;
4
5var re_ws = /^\s/,
6 re_name = /^(?:\\.|[\w\-\u00c0-\uFFFF])+/,
7 re_escape = /\\([\da-f]{1,6}\s?|(\s)|.)/ig,
8 //modified version of https://github.com/jquery/sizzle/blob/master/src/sizzle.js#L87
9 re_attr = /^\s*((?:\\.|[\w\u00c0-\uFFFF\-])+)\s*(?:(\S?)=\s*(?:(['"])(.*?)\3|(#?(?:\\.|[\w\u00c0-\uFFFF\-])*)|)|)\s*(i)?\]/;
10
11var actionTypes = {
12 __proto__: null,
13 "undefined": "exists",
14 "": "equals",
15 "~": "element",
16 "^": "start",
17 "$": "end",
18 "*": "any",
19 "!": "not",
20 "|": "hyphen"
21};
22
23var simpleSelectors = {
24 __proto__: null,
25 ">": "child",
26 "<": "parent",
27 "~": "sibling",
28 "+": "adjacent"
29};
30
31var attribSelectors = {
32 __proto__: null,
33 "#": ["id", "equals"],
34 ".": ["class", "element"]
35};
36
37//unescape function taken from https://github.com/jquery/sizzle/blob/master/src/sizzle.js#L139
38function funescape( _, escaped, escapedWhitespace ) {
39 var high = "0x" + escaped - 0x10000;
40 // NaN means non-codepoint
41 // Support: Firefox
42 // Workaround erroneous numeric interpretation of +"0x"
43 return high !== high || escapedWhitespace ?
44 escaped :
45 // BMP codepoint
46 high < 0 ?
47 String.fromCharCode( high + 0x10000 ) :
48 // Supplemental Plane codepoint (surrogate pair)
49 String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 );
50}
51
52function unescapeCSS(str){
53 return str.replace(re_escape, funescape);
54}
55
56function getClosingPos(selector){
57 var pos = 1, counter = 1, len = selector.length;
58
59 for(; counter > 0 && pos < len; pos++){
60 if(selector.charAt(pos) === "(") counter++;
61 else if(selector.charAt(pos) === ")") counter--;
62 }
63
64 return pos;
65}
66
67function parse(selector, options){
68 selector = (selector + "").trimLeft();
69
70 var subselects = [],
71 tokens = [],
72 sawWS = false,
73 data, firstChar, name;
74
75 function getName(){
76 var sub = selector.match(re_name)[0];
77 selector = selector.substr(sub.length);
78 return unescapeCSS(sub);
79 }
80
81 while(selector !== ""){
82 if(re_name.test(selector)){
83 if(sawWS){
84 tokens.push({type: "descendant"});
85 sawWS = false;
86 }
87
88 name = getName();
89
90 if(!options || ("lowerCaseTags" in options ? options.lowerCaseTags : !options.xmlMode)){
91 name = name.toLowerCase();
92 }
93
94 tokens.push({type: "tag", name: name});
95 } else if(re_ws.test(selector)){
96 sawWS = true;
97 selector = selector.trimLeft();
98 } else {
99 firstChar = selector.charAt(0);
100 selector = selector.substr(1);
101
102 if(firstChar in simpleSelectors){
103 tokens.push({type: simpleSelectors[firstChar]});
104 selector = selector.trimLeft();
105 sawWS = false;
106 continue;
107 } else if(firstChar === ","){
108 if(tokens.length === 0){
109 throw new SyntaxError("empty sub-selector");
110 }
111 subselects.push(tokens);
112 tokens = [];
113
114 selector = selector.trimLeft();
115 sawWS = false;
116 continue;
117 } else if(sawWS){
118 tokens.push({type: "descendant"});
119 sawWS = false;
120 }
121
122 if(firstChar === "*"){
123 tokens.push({type: "universal"});
124 } else if(firstChar in attribSelectors){
125 tokens.push({
126 type: "attribute",
127 name: attribSelectors[firstChar][0],
128 action: attribSelectors[firstChar][1],
129 value: getName(),
130 ignoreCase: false
131 });
132 } else if(firstChar === "["){
133 data = selector.match(re_attr);
134 if(!data){
135 throw new SyntaxError("Malformed attribute selector: " + selector);
136 }
137 selector = selector.substr(data[0].length);
138 name = unescapeCSS(data[1]);
139
140 if(
141 !options || (
142 "lowerCaseAttributeNames" in options ?
143 options.lowerCaseAttributeNames :
144 !options.xmlMode
145 )
146 ){
147 name = name.toLowerCase();
148 }
149
150 tokens.push({
151 type: "attribute",
152 name: name,
153 action: actionTypes[data[2]],
154 value: unescapeCSS(data[4] || data[5] || ""),
155 ignoreCase: !!data[6]
156 });
157
158 } else if(firstChar === ":"){
159 //if(selector.charAt(0) === ":"){} //TODO pseudo-element
160 name = getName().toLowerCase();
161 data = null;
162
163 if(selector.charAt(0) === "("){
164 var pos = getClosingPos(selector);
165 data = selector.substr(1, pos - 2);
166 selector = selector.substr(pos);
167 }
168
169 tokens.push({type: "pseudo", name: name, data: data});
170 } else {
171 //otherwise, the parser needs to throw or it would enter an infinite loop
172 throw new SyntaxError("Unmatched selector: " + firstChar + selector);
173 }
174 }
175 }
176
177 if(subselects.length > 0 && tokens.length === 0){
178 throw new SyntaxError("empty sub-selector");
179 }
180 subselects.push(tokens);
181 return subselects;
182}
\No newline at end of file