182 lines
4.0 KiB
JavaScript
182 lines
4.0 KiB
JavaScript
/*
|
|
compiles a selector to an executable function
|
|
*/
|
|
|
|
module.exports = compile;
|
|
module.exports.compileUnsafe = compileUnsafe;
|
|
|
|
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){
|
|
var next = compileUnsafe(selector, options);
|
|
return wrap(next);
|
|
}
|
|
|
|
function wrap(next){
|
|
return function base(elem){
|
|
return isTag(elem) && next(elem);
|
|
};
|
|
}
|
|
|
|
function compileUnsafe(selector, options){
|
|
var token = parse(selector, options);
|
|
return compileToken(token, options);
|
|
}
|
|
|
|
function compileToken(token, options){
|
|
token.forEach(sortRules);
|
|
|
|
if(options && options.context){
|
|
var ctx = options.context;
|
|
|
|
token.forEach(function(t){
|
|
if(!isTraversal(t[0])){
|
|
t.unshift({type: "descendant"});
|
|
}
|
|
});
|
|
|
|
var context = Array.isArray(ctx) ?
|
|
function(elem){
|
|
return ctx.indexOf(elem) >= 0;
|
|
} : function(elem){
|
|
return ctx === elem;
|
|
};
|
|
|
|
if(options.rootFunc){
|
|
var root = options.rootFunc;
|
|
|
|
options.rootFunc = function(elem){
|
|
return context(elem) && root(elem);
|
|
};
|
|
} else {
|
|
options.rootFunc = context;
|
|
}
|
|
}
|
|
|
|
return token
|
|
.map(compileRules, options)
|
|
.reduce(reduceRules, falseFunc);
|
|
}
|
|
|
|
function isTraversal(t){
|
|
return procedure[t.type] < 0;
|
|
}
|
|
|
|
function compileRules(rules){
|
|
if(rules.length === 0) return falseFunc;
|
|
|
|
var options = this;
|
|
|
|
return rules.reduce(function(func, rule){
|
|
if(func === falseFunc) return func;
|
|
return Rules[rule.type](func, rule, options);
|
|
}, 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);
|
|
}
|
|
|
|
function stripQuotes(str){
|
|
var firstChar = str.charAt(0);
|
|
|
|
if(firstChar === str.slice(-1) && (firstChar === "'" || firstChar === "\"")){
|
|
str = str.slice(1, -1);
|
|
}
|
|
|
|
return str;
|
|
}
|
|
|
|
filters.not = function(next, select, options){
|
|
var func,
|
|
opts = {
|
|
xmlMode: !!(options && options.xmlMode),
|
|
strict: !!(options && options.strict)
|
|
};
|
|
|
|
select = stripQuotes(select);
|
|
|
|
if(opts.strict){
|
|
var tokens = parse(select);
|
|
if(tokens.length > 1 || tokens.some(containsTraversal)){
|
|
throw new SyntaxError("complex selectors in :not aren't allowed in strict mode");
|
|
}
|
|
|
|
func = compileToken(tokens, opts);
|
|
} else {
|
|
func = compileUnsafe(select, opts);
|
|
}
|
|
|
|
if(func === falseFunc) return next;
|
|
if(func === trueFunc) return falseFunc;
|
|
|
|
return function(elem){
|
|
return !func(elem) && next(elem);
|
|
};
|
|
};
|
|
|
|
filters.has = function(next, selector, options){
|
|
//TODO add a dynamic context in front of every selector with a traversal
|
|
//:has will never be reached with options.strict == true
|
|
var opts = {
|
|
xmlMode: !!(options && options.xmlMode),
|
|
strict: !!(options && options.strict)
|
|
};
|
|
var func = compileUnsafe(selector, opts);
|
|
|
|
if(func === falseFunc) return falseFunc;
|
|
if(func === trueFunc) return function(elem){
|
|
return getChildren(elem).some(isTag) && next(elem);
|
|
};
|
|
|
|
func = wrap(func);
|
|
|
|
return function has(elem){
|
|
return next(elem) && existsOne(func, getChildren(elem));
|
|
};
|
|
};
|
|
|
|
filters.matches = function(next, selector, options){
|
|
var opts = {
|
|
xmlMode: !!(options && options.xmlMode),
|
|
strict: !!(options && options.strict),
|
|
rootFunc: next
|
|
};
|
|
|
|
selector = stripQuotes(selector);
|
|
|
|
return compileUnsafe(selector, opts);
|
|
};
|