Skip to content

Commit fe4c590

Browse files
committed
improve parsing of selectors inside pseudos
:has([attr=")"]) is now also supported
1 parent f46f4aa commit fe4c590

File tree

1 file changed

+79
-30
lines changed

1 file changed

+79
-30
lines changed

index.js

+79-30
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,16 @@ var unpackPseudos = {
4343
};
4444

4545
var stripQuotesFromPseudos = {
46-
__proto__: unpackPseudos,
46+
__proto__: null,
4747
"contains": true
4848
};
4949

50+
var quotes = {
51+
__proto__: null,
52+
"\"": true,
53+
"'": true
54+
};
55+
5056
//unescape function taken from https://github.com/jquery/sizzle/blob/master/src/sizzle.js#L139
5157
function funescape( _, escaped, escapedWhitespace ) {
5258
var high = "0x" + escaped - 0x10000;
@@ -66,24 +72,24 @@ function unescapeCSS(str){
6672
return str.replace(re_escape, funescape);
6773
}
6874

69-
function getClosingPos(selector){
70-
var pos = 1, counter = 1, len = selector.length;
75+
function parse(selector, options){
76+
var subselects = [];
7177

72-
for(; counter > 0 && pos < len; pos++){
73-
if(selector.charAt(pos) === "(") counter++;
74-
else if(selector.charAt(pos) === ")") counter--;
75-
}
78+
selector = parseSelector(subselects, selector + "", options);
79+
80+
if(selector !== ""){
81+
throw new SyntaxError("Unmatched selector: " + selector);
82+
}
7683

77-
return pos;
84+
return subselects;
7885
}
7986

80-
function parse(selector, options){
81-
selector = (selector + "").trimLeft();
87+
function parseSelector(subselects, selector, options){
88+
var tokens = [],
89+
sawWS = false,
90+
data, firstChar, name, quot;
8291

83-
var subselects = [],
84-
tokens = [],
85-
sawWS = false,
86-
data, firstChar, name;
92+
selector = selector.trimLeft();
8793

8894
function getName(){
8995
var sub = selector.match(re_name)[0];
@@ -174,34 +180,77 @@ function parse(selector, options){
174180
data = null;
175181

176182
if(selector.charAt(0) === "("){
177-
var pos = getClosingPos(selector);
178-
data = selector.substr(1, pos - 2);
179-
selector = selector.substr(pos);
183+
if(name in unpackPseudos){
184+
quot = selector.charAt(1);
185+
var quoted = quot in quotes;
186+
187+
selector = selector.substr(quoted + 1);
188+
189+
data = [];
190+
selector = parseSelector(data, selector, options);
180191

181-
if(name in stripQuotesFromPseudos){
182-
var quot = data.charAt(0);
192+
if(quoted){
193+
if(selector.charAt(0) !== quot){
194+
throw new SyntaxError("unmatched quotes in :" + name);
195+
} else {
196+
selector = selector.substr(1);
197+
}
198+
}
199+
200+
if(selector.charAt(0) !== ")"){
201+
throw new SyntaxError("missing closing parenthesis in :" + name + " " + selector);
202+
}
183203

184-
if(quot === data.slice(-1) && (quot === "'" || quot === "\"")){
185-
data = data.slice(1, -1);
186-
}
204+
selector = selector.substr(1);
205+
} else {
206+
var pos = 1, counter = 1;
187207

188-
if(name in unpackPseudos){
189-
data = parse(data, options);
208+
for(; counter > 0 && pos < selector.length; pos++){
209+
if(selector.charAt(pos) === "(") counter++;
210+
else if(selector.charAt(pos) === ")") counter--;
211+
}
212+
213+
if(counter){
214+
throw new SyntaxError("parenthesis not matched");
215+
}
216+
217+
data = selector.substr(1, pos - 2);
218+
selector = selector.substr(pos);
219+
220+
if(name in stripQuotesFromPseudos){
221+
quot = data.charAt(0);
222+
223+
if(quot === data.slice(-1) && quot in quotes){
224+
data = data.slice(1, -1);
225+
}
226+
227+
if(name in unpackPseudos){
228+
data = parse(data, options);
229+
}
190230
}
191231
}
192232
}
193233

194234
tokens.push({type: "pseudo", name: name, data: data});
195235
} else {
196-
//otherwise, the parser needs to throw or it would enter an infinite loop
197-
throw new SyntaxError("Unmatched selector: " + firstChar + selector);
236+
if(tokens.length && tokens[tokens.length - 1].type === "descendant"){
237+
tokens.pop();
238+
}
239+
addToken(subselects, tokens);
240+
return firstChar + selector;
198241
}
199242
}
200243
}
201244

202-
if(subselects.length > 0 && tokens.length === 0){
203-
throw new SyntaxError("empty sub-selector");
204-
}
245+
addToken(subselects, tokens);
246+
247+
return selector;
248+
}
249+
250+
function addToken(subselects, tokens){
251+
if(subselects.length > 0 && tokens.length === 0){
252+
throw new SyntaxError("empty sub-selector");
253+
}
254+
205255
subselects.push(tokens);
206-
return subselects;
207256
}

0 commit comments

Comments
 (0)