123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267 |
- "use strict";
- module.exports = parse;
- var re_name = /^(?:\\.|[\w\-\u00c0-\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\u00c0-\uFFFF\-])+)\s*(?:(\S?)=\s*(?:(['"])(.*?)\3|(#?(?:\\.|[\w\u00c0-\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);
- }
- 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) === "(") counter++;
- else if(selector.charAt(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);
- }
|