| "use strict"; |
| |
| module.exports = parse; |
| |
| var re_name = /^(?:\\.|[\w\-\u00b0-\uFFFF])+/, |
| re_escape = /\\([\da-f]{1,6}\s?|(\s)|.)/ig, |
| //modified version of https://github.com/jquery/sizzle/blob/master/src/sizzle.js#L87 |
| re_attr = /^\s*((?:\\.|[\w\u00b0-\uFFFF\-])+)\s*(?:(\S?)=\s*(?:(['"])([^]*?)\3|(#?(?:\\.|[\w\u00b0-\uFFFF\-])*)|)|)\s*(i)?\]/; |
| |
| var actionTypes = { |
| __proto__: null, |
| "undefined": "exists", |
| "": "equals", |
| "~": "element", |
| "^": "start", |
| "$": "end", |
| "*": "any", |
| "!": "not", |
| "|": "hyphen" |
| }; |
| |
| var simpleSelectors = { |
| __proto__: null, |
| ">": "child", |
| "<": "parent", |
| "~": "sibling", |
| "+": "adjacent" |
| }; |
| |
| var attribSelectors = { |
| __proto__: null, |
| "#": ["id", "equals"], |
| ".": ["class", "element"] |
| }; |
| |
| //pseudos, whose data-property is parsed as well |
| var unpackPseudos = { |
| __proto__: null, |
| "has": true, |
| "not": true, |
| "matches": true |
| }; |
| |
| var stripQuotesFromPseudos = { |
| __proto__: null, |
| "contains": true, |
| "icontains": true |
| }; |
| |
| var quotes = { |
| __proto__: null, |
| "\"": true, |
| "'": true |
| }; |
| |
| //unescape function taken from https://github.com/jquery/sizzle/blob/master/src/sizzle.js#L139 |
| function funescape( _, escaped, escapedWhitespace ) { |
| var high = "0x" + escaped - 0x10000; |
| // NaN means non-codepoint |
| // Support: Firefox |
| // Workaround erroneous numeric interpretation of +"0x" |
| return high !== high || escapedWhitespace ? |
| escaped : |
| // BMP codepoint |
| high < 0 ? |
| String.fromCharCode( high + 0x10000 ) : |
| // Supplemental Plane codepoint (surrogate pair) |
| String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 ); |
| } |
| |
| function unescapeCSS(str){ |
| return str.replace(re_escape, funescape); |
| } |
| |
| function isWhitespace(c){ |
| return c === " " || c === "\n" || c === "\t" || c === "\f" || c === "\r"; |
| } |
| |
| function parse(selector, options){ |
| var subselects = []; |
| |
| selector = parseSelector(subselects, selector + "", options); |
| |
| if(selector !== ""){ |
| throw new SyntaxError("Unmatched selector: " + selector); |
| } |
| |
| return subselects; |
| } |
| |
| function parseSelector(subselects, selector, options){ |
| var tokens = [], |
| sawWS = false, |
| data, firstChar, name, quot; |
| |
| function getName(){ |
| var sub = selector.match(re_name)[0]; |
| selector = selector.substr(sub.length); |
| return unescapeCSS(sub); |
| } |
| |
| function stripWhitespace(start){ |
| while(isWhitespace(selector.charAt(start))) start++; |
| selector = selector.substr(start); |
| } |
| |
| function isEscaped(pos) { |
| var slashCount = 0; |
| |
| while (selector.charAt(--pos) === "\\") slashCount++; |
| return (slashCount & 1) === 1; |
| } |
| |
| stripWhitespace(0); |
| |
| while(selector !== ""){ |
| firstChar = selector.charAt(0); |
| |
| if(isWhitespace(firstChar)){ |
| sawWS = true; |
| stripWhitespace(1); |
| } else if(firstChar in simpleSelectors){ |
| tokens.push({type: simpleSelectors[firstChar]}); |
| sawWS = false; |
| |
| stripWhitespace(1); |
| } else if(firstChar === ","){ |
| if(tokens.length === 0){ |
| throw new SyntaxError("empty sub-selector"); |
| } |
| subselects.push(tokens); |
| tokens = []; |
| sawWS = false; |
| stripWhitespace(1); |
| } else { |
| if(sawWS){ |
| if(tokens.length > 0){ |
| tokens.push({type: "descendant"}); |
| } |
| sawWS = false; |
| } |
| |
| if(firstChar === "*"){ |
| selector = selector.substr(1); |
| tokens.push({type: "universal"}); |
| } else if(firstChar in attribSelectors){ |
| selector = selector.substr(1); |
| tokens.push({ |
| type: "attribute", |
| name: attribSelectors[firstChar][0], |
| action: attribSelectors[firstChar][1], |
| value: getName(), |
| ignoreCase: false |
| }); |
| } else if(firstChar === "["){ |
| selector = selector.substr(1); |
| data = selector.match(re_attr); |
| if(!data){ |
| throw new SyntaxError("Malformed attribute selector: " + selector); |
| } |
| selector = selector.substr(data[0].length); |
| name = unescapeCSS(data[1]); |
| |
| if( |
| !options || ( |
| "lowerCaseAttributeNames" in options ? |
| options.lowerCaseAttributeNames : |
| !options.xmlMode |
| ) |
| ){ |
| name = name.toLowerCase(); |
| } |
| |
| tokens.push({ |
| type: "attribute", |
| name: name, |
| action: actionTypes[data[2]], |
| value: unescapeCSS(data[4] || data[5] || ""), |
| ignoreCase: !!data[6] |
| }); |
| |
| } else if(firstChar === ":"){ |
| if(selector.charAt(1) === ":"){ |
| selector = selector.substr(2); |
| tokens.push({type: "pseudo-element", name: getName().toLowerCase()}); |
| continue; |
| } |
| |
| selector = selector.substr(1); |
| |
| name = getName().toLowerCase(); |
| data = null; |
| |
| if(selector.charAt(0) === "("){ |
| if(name in unpackPseudos){ |
| quot = selector.charAt(1); |
| var quoted = quot in quotes; |
| |
| selector = selector.substr(quoted + 1); |
| |
| data = []; |
| selector = parseSelector(data, selector, options); |
| |
| if(quoted){ |
| if(selector.charAt(0) !== quot){ |
| throw new SyntaxError("unmatched quotes in :" + name); |
| } else { |
| selector = selector.substr(1); |
| } |
| } |
| |
| if(selector.charAt(0) !== ")"){ |
| throw new SyntaxError("missing closing parenthesis in :" + name + " " + selector); |
| } |
| |
| selector = selector.substr(1); |
| } else { |
| var pos = 1, counter = 1; |
| |
| for(; counter > 0 && pos < selector.length; pos++){ |
| if(selector.charAt(pos) === "(" && !isEscaped(pos)) counter++; |
| else if(selector.charAt(pos) === ")" && !isEscaped(pos)) counter--; |
| } |
| |
| if(counter){ |
| throw new SyntaxError("parenthesis not matched"); |
| } |
| |
| data = selector.substr(1, pos - 2); |
| selector = selector.substr(pos); |
| |
| if(name in stripQuotesFromPseudos){ |
| quot = data.charAt(0); |
| |
| if(quot === data.slice(-1) && quot in quotes){ |
| data = data.slice(1, -1); |
| } |
| |
| data = unescapeCSS(data); |
| } |
| } |
| } |
| |
| tokens.push({type: "pseudo", name: name, data: data}); |
| } else if(re_name.test(selector)){ |
| name = getName(); |
| |
| if(!options || ("lowerCaseTags" in options ? options.lowerCaseTags : !options.xmlMode)){ |
| name = name.toLowerCase(); |
| } |
| |
| tokens.push({type: "tag", name: name}); |
| } else { |
| if(tokens.length && tokens[tokens.length - 1].type === "descendant"){ |
| tokens.pop(); |
| } |
| addToken(subselects, tokens); |
| return selector; |
| } |
| } |
| } |
| |
| addToken(subselects, tokens); |
| |
| return selector; |
| } |
| |
| function addToken(subselects, tokens){ |
| if(subselects.length > 0 && tokens.length === 0){ |
| throw new SyntaxError("empty sub-selector"); |
| } |
| |
| subselects.push(tokens); |
| } |