/* 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 DomUtils = require("domutils"), isTag = DomUtils.isTag, getText = DomUtils.getText, getParent = DomUtils.getParent, getChildren = DomUtils.getChildren, getSiblings = DomUtils.getSiblings, hasAttrib = DomUtils.hasAttrib, getName = DomUtils.getName, getAttribute= DomUtils.getAttributeValue, getNCheck = require("nth-check"), checkAttrib = require("./attributes.js").rules.equals, BaseFuncs = require("boolbase"), trueFunc = BaseFuncs.trueFunc, falseFunc = BaseFuncs.falseFunc; //helper methods function getFirstElement(elems){ for(var i = 0; elems && i < elems.length; i++){ if(isTag(elems[i])) return elems[i]; } } 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 !!getParent(elem) && next(elem); }; } var filters = { contains: function(next, text){ if( (text.charAt(0) === "\"" || text.charAt(0) === "'") && text.charAt(0) === text.substr(-1) ){ text = text.slice(1, -1); } return function contains(elem){ return next(elem) && getText(elem).indexOf(text) >= 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 = getSiblings(elem); for(var i = 0, pos = 0; i < siblings.length; i++){ if(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 = getSiblings(elem); for(var pos = 0, i = siblings.length - 1; i >= 0; i--){ if(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 = getSiblings(elem); for(var pos = 0, i = 0; i < siblings.length; i++){ if(isTag(siblings[i])){ if(siblings[i] === elem) break; if(getName(siblings[i]) === 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 = getSiblings(elem); for(var pos = 0, i = siblings.length - 1; i >= 0; i--){ if(isTag(siblings[i])){ if(siblings[i] === elem) break; if(getName(siblings[i]) === getName(elem)) pos++; } } return func(pos) && 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") }; //while filters are precompiled, pseudos get called when they are needed var pseudos = { root: function(elem){ return !getParent(elem); }, empty: function(elem){ return !getChildren(elem).some(function(elem){ return isTag(elem) || elem.type === "text"; }); }, "first-child": function(elem){ return getFirstElement(getSiblings(elem)) === elem; }, "last-child": function(elem){ var siblings = getSiblings(elem); for(var i = siblings.length - 1; i >= 0; i--){ if(siblings[i] === elem) return true; if(isTag(siblings[i])) break; } return false; }, "first-of-type": function(elem){ var siblings = getSiblings(elem); for(var i = 0; i < siblings.length; i++){ if(isTag(siblings[i])){ if(siblings[i] === elem) return true; if(getName(siblings[i]) === getName(elem)) break; } } return false; }, "last-of-type": function(elem){ var siblings = getSiblings(elem); for(var i = siblings.length-1; i >= 0; i--){ if(isTag(siblings[i])){ if(siblings[i] === elem) return true; if(getName(siblings[i]) === getName(elem)) break; } } return false; }, "only-of-type": function(elem){ var siblings = getSiblings(elem); for(var i = 0, j = siblings.length; i < j; i++){ if(isTag(siblings[i])){ if(siblings[i] === elem) continue; if(getName(siblings[i]) === getName(elem)) return false; } } return true; }, "only-child": function(elem){ var siblings = getSiblings(elem); for(var i = 0; i < siblings.length; i++){ if(isTag(siblings[i]) && siblings[i] !== elem) return false; } return true; }, //forms //to consider: :target, :enabled selected: function(elem){ if(hasAttrib(elem, "selected")) return true; else if(getName(elem) !== "option") return false; //the first