You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							219 lines
						
					
					
						
							6.0 KiB
						
					
					
				
			
		
		
	
	
							219 lines
						
					
					
						
							6.0 KiB
						
					
					
				/* | 
						|
	compiles a selector to an executable function | 
						|
*/ | 
						|
 | 
						|
module.exports = compile; | 
						|
 | 
						|
var parse = require("css-what").parse; | 
						|
var BaseFuncs = require("boolbase"); | 
						|
var sortRules = require("./sort.js"); | 
						|
var procedure = require("./procedure.json"); | 
						|
var Rules = require("./general.js"); | 
						|
var Pseudos = require("./pseudos.js"); | 
						|
var trueFunc = BaseFuncs.trueFunc; | 
						|
var falseFunc = BaseFuncs.falseFunc; | 
						|
 | 
						|
var filters = Pseudos.filters; | 
						|
 | 
						|
function compile(selector, options, context) { | 
						|
    var next = compileUnsafe(selector, options, context); | 
						|
    return wrap(next, options); | 
						|
} | 
						|
 | 
						|
function wrap(next, options) { | 
						|
    var adapter = options.adapter; | 
						|
 | 
						|
    return function base(elem) { | 
						|
        return adapter.isTag(elem) && next(elem); | 
						|
    }; | 
						|
} | 
						|
 | 
						|
function compileUnsafe(selector, options, context) { | 
						|
    var token = parse(selector, options); | 
						|
    return compileToken(token, options, context); | 
						|
} | 
						|
 | 
						|
function includesScopePseudo(t) { | 
						|
    return ( | 
						|
        t.type === "pseudo" && | 
						|
        (t.name === "scope" || | 
						|
            (Array.isArray(t.data) && | 
						|
                t.data.some(function(data) { | 
						|
                    return data.some(includesScopePseudo); | 
						|
                }))) | 
						|
    ); | 
						|
} | 
						|
 | 
						|
var DESCENDANT_TOKEN = { type: "descendant" }; | 
						|
var FLEXIBLE_DESCENDANT_TOKEN = { type: "_flexibleDescendant" }; | 
						|
var SCOPE_TOKEN = { type: "pseudo", name: "scope" }; | 
						|
var PLACEHOLDER_ELEMENT = {}; | 
						|
 | 
						|
//CSS 4 Spec (Draft): 3.3.1. Absolutizing a Scope-relative Selector | 
						|
//http://www.w3.org/TR/selectors4/#absolutizing | 
						|
function absolutize(token, options, context) { | 
						|
    var adapter = options.adapter; | 
						|
 | 
						|
    //TODO better check if context is document | 
						|
    var hasContext = | 
						|
        !!context && | 
						|
        !!context.length && | 
						|
        context.every(function(e) { | 
						|
            return e === PLACEHOLDER_ELEMENT || !!adapter.getParent(e); | 
						|
        }); | 
						|
 | 
						|
    token.forEach(function(t) { | 
						|
        if (t.length > 0 && isTraversal(t[0]) && t[0].type !== "descendant") { | 
						|
            //don't return in else branch | 
						|
        } else if (hasContext && !(Array.isArray(t) ? t.some(includesScopePseudo) : includesScopePseudo(t))) { | 
						|
            t.unshift(DESCENDANT_TOKEN); | 
						|
        } else { | 
						|
            return; | 
						|
        } | 
						|
 | 
						|
        t.unshift(SCOPE_TOKEN); | 
						|
    }); | 
						|
} | 
						|
 | 
						|
function compileToken(token, options, context) { | 
						|
    token = token.filter(function(t) { | 
						|
        return t.length > 0; | 
						|
    }); | 
						|
 | 
						|
    token.forEach(sortRules); | 
						|
 | 
						|
    var isArrayContext = Array.isArray(context); | 
						|
 | 
						|
    context = (options && options.context) || context; | 
						|
 | 
						|
    if (context && !isArrayContext) context = [context]; | 
						|
 | 
						|
    absolutize(token, options, context); | 
						|
 | 
						|
    var shouldTestNextSiblings = false; | 
						|
 | 
						|
    var query = token | 
						|
        .map(function(rules) { | 
						|
            if (rules[0] && rules[1] && rules[0].name === "scope") { | 
						|
                var ruleType = rules[1].type; | 
						|
                if (isArrayContext && ruleType === "descendant") { | 
						|
                    rules[1] = FLEXIBLE_DESCENDANT_TOKEN; | 
						|
                } else if (ruleType === "adjacent" || ruleType === "sibling") { | 
						|
                    shouldTestNextSiblings = true; | 
						|
                } | 
						|
            } | 
						|
            return compileRules(rules, options, context); | 
						|
        }) | 
						|
        .reduce(reduceRules, falseFunc); | 
						|
 | 
						|
    query.shouldTestNextSiblings = shouldTestNextSiblings; | 
						|
 | 
						|
    return query; | 
						|
} | 
						|
 | 
						|
function isTraversal(t) { | 
						|
    return procedure[t.type] < 0; | 
						|
} | 
						|
 | 
						|
function compileRules(rules, options, context) { | 
						|
    return rules.reduce(function(func, rule) { | 
						|
        if (func === falseFunc) return func; | 
						|
 | 
						|
        if (!(rule.type in Rules)) { | 
						|
            throw new Error("Rule type " + rule.type + " is not supported by css-select"); | 
						|
        } | 
						|
 | 
						|
        return Rules[rule.type](func, rule, options, context); | 
						|
    }, (options && options.rootFunc) || trueFunc); | 
						|
} | 
						|
 | 
						|
function reduceRules(a, b) { | 
						|
    if (b === falseFunc || a === trueFunc) { | 
						|
        return a; | 
						|
    } | 
						|
    if (a === falseFunc || b === trueFunc) { | 
						|
        return b; | 
						|
    } | 
						|
 | 
						|
    return function combine(elem) { | 
						|
        return a(elem) || b(elem); | 
						|
    }; | 
						|
} | 
						|
 | 
						|
function containsTraversal(t) { | 
						|
    return t.some(isTraversal); | 
						|
} | 
						|
 | 
						|
//:not, :has and :matches have to compile selectors | 
						|
//doing this in lib/pseudos.js would lead to circular dependencies, | 
						|
//so we add them here | 
						|
filters.not = function(next, token, options, context) { | 
						|
    var opts = { | 
						|
        xmlMode: !!(options && options.xmlMode), | 
						|
        strict: !!(options && options.strict), | 
						|
        adapter: options.adapter | 
						|
    }; | 
						|
 | 
						|
    if (opts.strict) { | 
						|
        if (token.length > 1 || token.some(containsTraversal)) { | 
						|
            throw new Error("complex selectors in :not aren't allowed in strict mode"); | 
						|
        } | 
						|
    } | 
						|
 | 
						|
    var func = compileToken(token, opts, context); | 
						|
 | 
						|
    if (func === falseFunc) return next; | 
						|
    if (func === trueFunc) return falseFunc; | 
						|
 | 
						|
    return function not(elem) { | 
						|
        return !func(elem) && next(elem); | 
						|
    }; | 
						|
}; | 
						|
 | 
						|
filters.has = function(next, token, options) { | 
						|
    var adapter = options.adapter; | 
						|
    var opts = { | 
						|
        xmlMode: !!(options && options.xmlMode), | 
						|
        strict: !!(options && options.strict), | 
						|
        adapter: adapter | 
						|
    }; | 
						|
 | 
						|
    //FIXME: Uses an array as a pointer to the current element (side effects) | 
						|
    var context = token.some(containsTraversal) ? [PLACEHOLDER_ELEMENT] : null; | 
						|
 | 
						|
    var func = compileToken(token, opts, context); | 
						|
 | 
						|
    if (func === falseFunc) return falseFunc; | 
						|
    if (func === trueFunc) { | 
						|
        return function hasChild(elem) { | 
						|
            return adapter.getChildren(elem).some(adapter.isTag) && next(elem); | 
						|
        }; | 
						|
    } | 
						|
 | 
						|
    func = wrap(func, options); | 
						|
 | 
						|
    if (context) { | 
						|
        return function has(elem) { | 
						|
            return next(elem) && ((context[0] = elem), adapter.existsOne(func, adapter.getChildren(elem))); | 
						|
        }; | 
						|
    } | 
						|
 | 
						|
    return function has(elem) { | 
						|
        return next(elem) && adapter.existsOne(func, adapter.getChildren(elem)); | 
						|
    }; | 
						|
}; | 
						|
 | 
						|
filters.matches = function(next, token, options, context) { | 
						|
    var opts = { | 
						|
        xmlMode: !!(options && options.xmlMode), | 
						|
        strict: !!(options && options.strict), | 
						|
        rootFunc: next, | 
						|
        adapter: options.adapter | 
						|
    }; | 
						|
 | 
						|
    return compileToken(token, opts, context); | 
						|
}; | 
						|
 | 
						|
compile.compileToken = compileToken; | 
						|
compile.compileUnsafe = compileUnsafe; | 
						|
compile.Pseudos = Pseudos;
 | 
						|
 |