/*
compiles a selector to an executable function
*/
module.exports = compile; module.exports.compileUnsafe = compileUnsafe; module.exports.compileToken = compileToken;
var parse = require(“css-what”),
DomUtils = require("domutils"), isTag = DomUtils.isTag, Rules = require("./general.js"), sortRules = require("./sort.js"), BaseFuncs = require("boolbase"), trueFunc = BaseFuncs.trueFunc, falseFunc = BaseFuncs.falseFunc, procedure = require("./procedure.json");
function compile(selector, options, context){
var next = compileUnsafe(selector, options, context); return wrap(next);
}
function wrap(next){
return function base(elem){ return 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”},
SCOPE_TOKEN = {type: "pseudo", name: "scope"}, PLACEHOLDER_ELEMENT = {}, getParent = DomUtils.getParent;
//CSS 4 Spec (Draft): 3.3.1. Absolutizing a Scope-relative Selector //http://www.w3.org/TR/selectors4/#absolutizing function absolutize(token, context){
//TODO better check if context is document var hasContext = !!context && !!context.length && context.every(function(e){ return e === PLACEHOLDER_ELEMENT || !!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 && !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, context); return token .map(function(rules){ return compileRules(rules, options, context, isArrayContext); }) .reduce(reduceRules, falseFunc);
}
function isTraversal(t){
return procedure[t.type] < 0;
}
function compileRules(rules, options, context, isArrayContext){
var acceptSelf = (isArrayContext && rules[0].name === "scope" && rules[1].type === "descendant"); return rules.reduce(function(func, rule, index){ if(func === falseFunc) return func; return Rules[rule.type](func, rule, options, context, acceptSelf && index === 1); }, 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); };
}
//:not, :has and :matches have to compile selectors //doing this in lib/pseudos.js would lead to circular dependencies, //so we add them here
var Pseudos = require(“./pseudos.js”),
filters = Pseudos.filters, existsOne = DomUtils.existsOne, isTag = DomUtils.isTag, getChildren = DomUtils.getChildren;
function containsTraversal(t){
return t.some(isTraversal);
}
filters.not = function(next, token, options, context){
var opts = { xmlMode: !!(options && options.xmlMode), strict: !!(options && options.strict) }; if(opts.strict){ if(token.length > 1 || token.some(containsTraversal)){ throw new SyntaxError("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(elem){ return !func(elem) && next(elem); };
};
filters.has = function(next, token, options){
var opts = { xmlMode: !!(options && options.xmlMode), strict: !!(options && options.strict) }; //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(elem){ return getChildren(elem).some(isTag) && next(elem); }; func = wrap(func); if(context){ return function has(elem){ return next(elem) && ( (context[0] = elem), existsOne(func, getChildren(elem)) ); }; } return function has(elem){ return next(elem) && existsOne(func, getChildren(elem)); };
};
filters.matches = function(next, token, options, context){
var opts = { xmlMode: !!(options && options.xmlMode), strict: !!(options && options.strict), rootFunc: next }; return compileToken(token, opts, context);
};