/* pseudo selectors --- they are available in two forms: * filters called when the selector is compiled and return a function that needs to return next() * pseudos get called on execution they need to return a boolean */ var getNCheck = require("nth-check"), BaseFuncs = require("boolbase"), attributesFactory = require("./attributes.js"), trueFunc = BaseFuncs.trueFunc, falseFunc = BaseFuncs.falseFunc; function filtersFactory(adapter){ var attributes = attributesFactory(adapter), checkAttrib = attributes.rules.equals; //helper methods function equals(a, b){ if(typeof adapter.equals === "function") return adapter.equals(a, b); return a === b; } function getAttribFunc(name, value){ var data = {name: name, value: value}; return function attribFunc(next){ return checkAttrib(next, data); }; } function getChildFunc(next){ return function(elem){ return !!adapter.getParent(elem) && next(elem); }; } var filters = { contains: function(next, text){ return function contains(elem){ return next(elem) && adapter.getText(elem).indexOf(text) >= 0; }; }, icontains: function(next, text){ var itext = text.toLowerCase(); return function icontains(elem){ return next(elem) && adapter.getText(elem).toLowerCase().indexOf(itext) >= 0; }; }, //location specific methods "nth-child": function(next, rule){ var func = getNCheck(rule); if(func === falseFunc) return func; if(func === trueFunc) return getChildFunc(next); return function nthChild(elem){ var siblings = adapter.getSiblings(elem); for(var i = 0, pos = 0; i < siblings.length; i++){ if(adapter.isTag(siblings[i])){ if(siblings[i] === elem) break; else pos++; } } return func(pos) && next(elem); }; }, "nth-last-child": function(next, rule){ var func = getNCheck(rule); if(func === falseFunc) return func; if(func === trueFunc) return getChildFunc(next); return function nthLastChild(elem){ var siblings = adapter.getSiblings(elem); for(var pos = 0, i = siblings.length - 1; i >= 0; i--){ if(adapter.isTag(siblings[i])){ if(siblings[i] === elem) break; else pos++; } } return func(pos) && next(elem); }; }, "nth-of-type": function(next, rule){ var func = getNCheck(rule); if(func === falseFunc) return func; if(func === trueFunc) return getChildFunc(next); return function nthOfType(elem){ var siblings = adapter.getSiblings(elem); for(var pos = 0, i = 0; i < siblings.length; i++){ if(adapter.isTag(siblings[i])){ if(siblings[i] === elem) break; if(adapter.getName(siblings[i]) === adapter.getName(elem)) pos++; } } return func(pos) && next(elem); }; }, "nth-last-of-type": function(next, rule){ var func = getNCheck(rule); if(func === falseFunc) return func; if(func === trueFunc) return getChildFunc(next); return function nthLastOfType(elem){ var siblings = adapter.getSiblings(elem); for(var pos = 0, i = siblings.length - 1; i >= 0; i--){ if(adapter.isTag(siblings[i])){ if(siblings[i] === elem) break; if(adapter.getName(siblings[i]) === adapter.getName(elem)) pos++; } } return func(pos) && next(elem); }; }, //TODO determine the actual root element root: function(next){ return function(elem){ return !adapter.getParent(elem) && next(elem); }; }, scope: function(next, rule, options, context){ if(!context || context.length === 0){ //equivalent to :root return filters.root(next); } if(context.length === 1){ //NOTE: can't be unpacked, as :has uses this for side-effects return function(elem){ return equals(context[0], elem) && next(elem); }; } return function(elem){ return context.indexOf(elem) >= 0 && next(elem); }; }, //jQuery extensions (others follow as pseudos) checkbox: getAttribFunc("type", "checkbox"), file: getAttribFunc("type", "file"), password: getAttribFunc("type", "password"), radio: getAttribFunc("type", "radio"), reset: getAttribFunc("type", "reset"), image: getAttribFunc("type", "image"), submit: getAttribFunc("type", "submit") }; return filters; } function pseudosFactory(adapter){ //helper methods function getFirstElement(elems){ for(var i = 0; elems && i < elems.length; i++){ if(adapter.isTag(elems[i])) return elems[i]; } } //while filters are precompiled, pseudos get called when they are needed var pseudos = { empty: function(elem){ return !adapter.getChildren(elem).some(function(elem){ return adapter.isTag(elem) || elem.type === "text"; }); }, "first-child": function(elem){ return getFirstElement(adapter.getSiblings(elem)) === elem; }, "last-child": function(elem){ var siblings = adapter.getSiblings(elem); for(var i = siblings.length - 1; i >= 0; i--){ if(siblings[i] === elem) return true; if(adapter.isTag(siblings[i])) break; } return false; }, "first-of-type": function(elem){ var siblings = adapter.getSiblings(elem); for(var i = 0; i < siblings.length; i++){ if(adapter.isTag(siblings[i])){ if(siblings[i] === elem) return true; if(adapter.getName(siblings[i]) === adapter.getName(elem)) break; } } return false; }, "last-of-type": function(elem){ var siblings = adapter.getSiblings(elem); for(var i = siblings.length - 1; i >= 0; i--){ if(adapter.isTag(siblings[i])){ if(siblings[i] === elem) return true; if(adapter.getName(siblings[i]) === adapter.getName(elem)) break; } } return false; }, "only-of-type": function(elem){ var siblings = adapter.getSiblings(elem); for(var i = 0, j = siblings.length; i < j; i++){ if(adapter.isTag(siblings[i])){ if(siblings[i] === elem) continue; if(adapter.getName(siblings[i]) === adapter.getName(elem)) return false; } } return true; }, "only-child": function(elem){ var siblings = adapter.getSiblings(elem); for(var i = 0; i < siblings.length; i++){ if(adapter.isTag(siblings[i]) && siblings[i] !== elem) return false; } return true; }, //:matches(a, area, link)[href] link: function(elem){ return adapter.hasAttrib(elem, "href"); }, visited: falseFunc, //seems to be a valid implementation //TODO: :any-link once the name is finalized (as an alias of :link) //forms //to consider: :target //:matches([selected], select:not([multiple]):not(> option[selected]) > option:first-of-type) selected: function(elem){ if(adapter.hasAttrib(elem, "selected")) return true; else if(adapter.getName(elem) !== "option") return false; //the first