aboutsummaryrefslogtreecommitdiff
path: root/includes/js/dojo/_base/query.js
diff options
context:
space:
mode:
Diffstat (limited to 'includes/js/dojo/_base/query.js')
-rw-r--r--includes/js/dojo/_base/query.js1191
1 files changed, 1191 insertions, 0 deletions
diff --git a/includes/js/dojo/_base/query.js b/includes/js/dojo/_base/query.js
new file mode 100644
index 0000000..8743df8
--- /dev/null
+++ b/includes/js/dojo/_base/query.js
@@ -0,0 +1,1191 @@
+if(!dojo._hasResource["dojo._base.query"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
+dojo._hasResource["dojo._base.query"] = true;
+dojo.provide("dojo._base.query");
+dojo.require("dojo._base.NodeList");
+
+/*
+ dojo.query() architectural overview:
+
+ dojo.query is a relatively full-featured CSS3 query library. It is
+ designed to take any valid CSS3 selector and return the nodes matching
+ the selector. To do this quickly, it processes queries in several
+ steps, applying caching where profitable.
+
+ The steps (roughly in reverse order of the way they appear in the code):
+ 1.) check to see if we already have a "query dispatcher"
+ - if so, use that with the given parameterization. Skip to step 4.
+ 2.) attempt to determine which branch to dispatch the query to:
+ - JS (optimized DOM iteration)
+ - xpath (for browsers that support it and where it's fast)
+ - native (not available in any browser yet)
+ 3.) tokenize and convert to executable "query dispatcher"
+ - this is where the lion's share of the complexity in the
+ system lies. In the DOM version, the query dispatcher is
+ assembled as a chain of "yes/no" test functions pertaining to
+ a section of a simple query statement (".blah:nth-child(odd)"
+ but not "div div", which is 2 simple statements). Individual
+ statement dispatchers are cached (to prevent re-definition)
+ as are entire dispatch chains (to make re-execution of the
+ same query fast)
+ - in the xpath path, tokenization yeilds a concatenation of
+ parameterized xpath selectors. As with the DOM version, both
+ simple selector blocks and overall evaluators are cached to
+ prevent re-defintion
+ 4.) the resulting query dispatcher is called in the passed scope (by default the top-level document)
+ - for DOM queries, this results in a recursive, top-down
+ evaluation of nodes based on each simple query section
+ - xpath queries can, thankfully, be executed in one shot
+ 5.) matched nodes are pruned to ensure they are unique
+*/
+
+;(function(){
+ // define everything in a closure for compressability reasons. "d" is an
+ // alias to "dojo" since it's so frequently used. This seems a
+ // transformation that the build system could perform on a per-file basis.
+
+ ////////////////////////////////////////////////////////////////////////
+ // Utility code
+ ////////////////////////////////////////////////////////////////////////
+
+ var d = dojo;
+ var childNodesName = dojo.isIE ? "children" : "childNodes";
+ var caseSensitive = false;
+
+ var getQueryParts = function(query){
+ // summary: state machine for query tokenization
+ if(">~+".indexOf(query.charAt(query.length-1)) >= 0){
+ query += " *"
+ }
+ query += " "; // ensure that we terminate the state machine
+
+ var ts = function(s, e){
+ return d.trim(query.slice(s, e));
+ }
+
+ // the overall data graph of the full query, as represented by queryPart objects
+ var qparts = [];
+ // state keeping vars
+ var inBrackets = -1;
+ var inParens = -1;
+ var inMatchFor = -1;
+ var inPseudo = -1;
+ var inClass = -1;
+ var inId = -1;
+ var inTag = -1;
+ var lc = ""; // the last character
+ var cc = ""; // the current character
+ var pStart;
+ // iteration vars
+ var x = 0; // index in the query
+ var ql = query.length;
+ var currentPart = null; // data structure representing the entire clause
+ var _cp = null; // the current pseudo or attr matcher
+
+ var endTag = function(){
+ if(inTag >= 0){
+ var tv = (inTag == x) ? null : ts(inTag, x).toLowerCase();
+ currentPart[ (">~+".indexOf(tv) < 0) ? "tag" : "oper" ] = tv;
+ inTag = -1;
+ }
+ }
+
+ var endId = function(){
+ if(inId >= 0){
+ currentPart.id = ts(inId, x).replace(/\\/g, "");
+ inId = -1;
+ }
+ }
+
+ var endClass = function(){
+ if(inClass >= 0){
+ currentPart.classes.push(ts(inClass+1, x).replace(/\\/g, ""));
+ inClass = -1;
+ }
+ }
+
+ var endAll = function(){
+ endId(); endTag(); endClass();
+ }
+
+ for(; lc=cc, cc=query.charAt(x),x<ql; x++){
+ if(lc == "\\"){ continue; }
+ if(!currentPart){
+ // NOTE: I hate all this alloc, but it's shorter than writing tons of if's
+ pStart = x;
+ currentPart = {
+ query: null,
+ pseudos: [],
+ attrs: [],
+ classes: [],
+ tag: null,
+ oper: null,
+ id: null
+ };
+ inTag = x;
+ }
+
+ if(inBrackets >= 0){
+ // look for a the close first
+ if(cc == "]"){
+ if(!_cp.attr){
+ _cp.attr = ts(inBrackets+1, x);
+ }else{
+ _cp.matchFor = ts((inMatchFor||inBrackets+1), x);
+ }
+ var cmf = _cp.matchFor;
+ if(cmf){
+ if( (cmf.charAt(0) == '"') || (cmf.charAt(0) == "'") ){
+ _cp.matchFor = cmf.substring(1, cmf.length-1);
+ }
+ }
+ currentPart.attrs.push(_cp);
+ _cp = null; // necessaray?
+ inBrackets = inMatchFor = -1;
+ }else if(cc == "="){
+ var addToCc = ("|~^$*".indexOf(lc) >=0 ) ? lc : "";
+ _cp.type = addToCc+cc;
+ _cp.attr = ts(inBrackets+1, x-addToCc.length);
+ inMatchFor = x+1;
+ }
+ // now look for other clause parts
+ }else if(inParens >= 0){
+ if(cc == ")"){
+ if(inPseudo >= 0){
+ _cp.value = ts(inParens+1, x);
+ }
+ inPseudo = inParens = -1;
+ }
+ }else if(cc == "#"){
+ endAll();
+ inId = x+1;
+ }else if(cc == "."){
+ endAll();
+ inClass = x;
+ }else if(cc == ":"){
+ endAll();
+ inPseudo = x;
+ }else if(cc == "["){
+ endAll();
+ inBrackets = x;
+ _cp = {
+ /*=====
+ attr: null, type: null, matchFor: null
+ =====*/
+ };
+ }else if(cc == "("){
+ if(inPseudo >= 0){
+ _cp = {
+ name: ts(inPseudo+1, x),
+ value: null
+ }
+ currentPart.pseudos.push(_cp);
+ }
+ inParens = x;
+ }else if(cc == " " && lc != cc){
+ // note that we expect the string to be " " terminated
+ endAll();
+ if(inPseudo >= 0){
+ currentPart.pseudos.push({ name: ts(inPseudo+1, x) });
+ }
+ currentPart.hasLoops = (
+ currentPart.pseudos.length ||
+ currentPart.attrs.length ||
+ currentPart.classes.length );
+ currentPart.query = ts(pStart, x);
+ currentPart.tag = (currentPart["oper"]) ? null : (currentPart.tag || "*");
+ qparts.push(currentPart);
+ currentPart = null;
+ }
+ }
+ return qparts;
+ };
+
+
+ ////////////////////////////////////////////////////////////////////////
+ // XPath query code
+ ////////////////////////////////////////////////////////////////////////
+
+ // this array is a lookup used to generate an attribute matching function.
+ // There is a similar lookup/generator list for the DOM branch with similar
+ // calling semantics.
+ var xPathAttrs = {
+ "*=": function(attr, value){
+ return "[contains(@"+attr+", '"+ value +"')]";
+ },
+ "^=": function(attr, value){
+ return "[starts-with(@"+attr+", '"+ value +"')]";
+ },
+ "$=": function(attr, value){
+ return "[substring(@"+attr+", string-length(@"+attr+")-"+(value.length-1)+")='"+value+"']";
+ },
+ "~=": function(attr, value){
+ return "[contains(concat(' ',@"+attr+",' '), ' "+ value +" ')]";
+ },
+ "|=": function(attr, value){
+ return "[contains(concat(' ',@"+attr+",' '), ' "+ value +"-')]";
+ },
+ "=": function(attr, value){
+ return "[@"+attr+"='"+ value +"']";
+ }
+ };
+
+ // takes a list of attribute searches, the overall query, a function to
+ // generate a default matcher, and a closure-bound method for providing a
+ // matching function that generates whatever type of yes/no distinguisher
+ // the query method needs. The method is a bit tortured and hard to read
+ // because it needs to be used in both the XPath and DOM branches.
+ var handleAttrs = function( attrList,
+ query,
+ getDefault,
+ handleMatch){
+ d.forEach(query.attrs, function(attr){
+ var matcher;
+ // type, attr, matchFor
+ if(attr.type && attrList[attr.type]){
+ matcher = attrList[attr.type](attr.attr, attr.matchFor);
+ }else if(attr.attr.length){
+ matcher = getDefault(attr.attr);
+ }
+ if(matcher){ handleMatch(matcher); }
+ });
+ }
+
+ var buildPath = function(query){
+ var xpath = ".";
+ var qparts = getQueryParts(d.trim(query));
+ while(qparts.length){
+ var tqp = qparts.shift();
+ var prefix;
+ var postfix = "";
+ if(tqp.oper == ">"){
+ prefix = "/";
+ // prefix = "/child::*";
+ tqp = qparts.shift();
+ }else if(tqp.oper == "~"){
+ prefix = "/following-sibling::"; // get element following siblings
+ tqp = qparts.shift();
+ }else if(tqp.oper == "+"){
+ // FIXME:
+ // fails when selecting subsequent siblings by node type
+ // because the position() checks the position in the list
+ // of matching elements and not the localized siblings
+ prefix = "/following-sibling::";
+ postfix = "[position()=1]";
+ tqp = qparts.shift();
+ }else{
+ prefix = "//";
+ // prefix = "/descendant::*"
+ }
+
+ // get the tag name (if any)
+
+ xpath += prefix + tqp.tag + postfix;
+
+ // check to see if it's got an id. Needs to come first in xpath.
+ if(tqp.id){
+ xpath += "[@id='"+tqp.id+"'][1]";
+ }
+
+ d.forEach(tqp.classes, function(cn){
+ var cnl = cn.length;
+ var padding = " ";
+ if(cn.charAt(cnl-1) == "*"){
+ padding = ""; cn = cn.substr(0, cnl-1);
+ }
+ xpath +=
+ "[contains(concat(' ',@class,' '), ' "+
+ cn + padding + "')]";
+ });
+
+ handleAttrs(xPathAttrs, tqp,
+ function(condition){
+ return "[@"+condition+"]";
+ },
+ function(matcher){
+ xpath += matcher;
+ }
+ );
+
+ // FIXME: need to implement pseudo-class checks!!
+ };
+ return xpath;
+ };
+
+ var _xpathFuncCache = {};
+ var getXPathFunc = function(path){
+ if(_xpathFuncCache[path]){
+ return _xpathFuncCache[path];
+ }
+
+ var doc = d.doc;
+ // don't need to memoize. The closure scope handles it for us.
+ var xpath = buildPath(path);
+
+ var tf = function(parent){
+ // XPath query strings are memoized.
+ var ret = [];
+ var xpathResult;
+ try{
+ xpathResult = doc.evaluate(xpath, parent, null,
+ // XPathResult.UNORDERED_NODE_ITERATOR_TYPE, null);
+ XPathResult.ANY_TYPE, null);
+ }catch(e){
+ console.debug("failure in exprssion:", xpath, "under:", parent);
+ console.debug(e);
+ }
+ var result = xpathResult.iterateNext();
+ while(result){
+ ret.push(result);
+ result = xpathResult.iterateNext();
+ }
+ return ret;
+ }
+ return _xpathFuncCache[path] = tf;
+ };
+
+ /*
+ d.xPathMatch = function(query){
+ // XPath based DOM query system. Handles a small subset of CSS
+ // selectors, subset is identical to the non-XPath version of this
+ // function.
+
+ return getXPathFunc(query)();
+ }
+ */
+
+ ////////////////////////////////////////////////////////////////////////
+ // DOM query code
+ ////////////////////////////////////////////////////////////////////////
+
+ var _filtersCache = {};
+ var _simpleFiltersCache = {};
+
+ // the basic building block of the yes/no chaining system. agree(f1, f2)
+ // generates a new function which returns the boolean results of both of
+ // the passed functions to a single logical-anded result.
+ var agree = function(first, second){
+ if(!first){ return second; }
+ if(!second){ return first; }
+
+ return function(){
+ return first.apply(window, arguments) && second.apply(window, arguments);
+ }
+ }
+
+ var _childElements = function(root){
+ var ret = [];
+ var te, x=0, tret = root[childNodesName];
+ while(te=tret[x++]){
+ if(te.nodeType == 1){ ret.push(te); }
+ }
+ return ret;
+ }
+
+ var _nextSiblings = function(root, single){
+ var ret = [];
+ var te = root;
+ while(te = te.nextSibling){
+ if(te.nodeType == 1){
+ ret.push(te);
+ if(single){ break; }
+ }
+ }
+ return ret;
+ }
+
+ var _filterDown = function(element, queryParts, matchArr, idx){
+ // NOTE:
+ // in the fast path! this function is called recursively and for
+ // every run of a query.
+ var nidx = idx+1;
+ var isFinal = (queryParts.length == nidx);
+ var tqp = queryParts[idx];
+
+ // see if we can constrain our next level to direct children
+ if(tqp.oper){
+ var ecn = (tqp.oper == ">") ?
+ _childElements(element) :
+ _nextSiblings(element, (tqp.oper == "+"));
+
+ if(!ecn || !ecn.length){
+ return;
+ }
+ nidx++;
+ isFinal = (queryParts.length == nidx);
+ // kinda janky, too much array alloc
+ var tf = getFilterFunc(queryParts[idx+1]);
+ // for(var x=ecn.length-1, te; x>=0, te=ecn[x]; x--){
+ for(var x=0, ecnl=ecn.length, te; x<ecnl, te=ecn[x]; x++){
+ if(tf(te)){
+ if(isFinal){
+ matchArr.push(te);
+ }else{
+ _filterDown(te, queryParts, matchArr, nidx);
+ }
+ }
+ /*
+ if(x==0){
+ break;
+ }
+ */
+ }
+ }
+
+ // otherwise, keep going down, unless we'er at the end
+ var candidates = getElementsFunc(tqp)(element);
+ if(isFinal){
+ while(candidates.length){
+ matchArr.push(candidates.shift());
+ }
+ /*
+ candidates.unshift(0, matchArr.length-1);
+ matchArr.splice.apply(matchArr, candidates);
+ */
+ }else{
+ // if we're not yet at the bottom, keep going!
+ while(candidates.length){
+ _filterDown(candidates.shift(), queryParts, matchArr, nidx);
+ }
+ }
+ }
+
+ var filterDown = function(elements, queryParts){
+ var ret = [];
+
+ // for every root, get the elements that match the descendant selector
+ // for(var x=elements.length-1, te; x>=0, te=elements[x]; x--){
+ var x = elements.length - 1, te;
+ while(te = elements[x--]){
+ _filterDown(te, queryParts, ret, 0);
+ }
+ return ret;
+ }
+
+ var getFilterFunc = function(q){
+ // note: query can't have spaces!
+ if(_filtersCache[q.query]){
+ return _filtersCache[q.query];
+ }
+ var ff = null;
+
+ // does it have a tagName component?
+ if(q.tag){
+ if(q.tag == "*"){
+ ff = agree(ff,
+ function(elem){
+ return (elem.nodeType == 1);
+ }
+ );
+ }else{
+ // tag name match
+ ff = agree(ff,
+ function(elem){
+ return (
+ (elem.nodeType == 1) &&
+ (q.tag == elem.tagName.toLowerCase())
+ );
+ // return isTn;
+ }
+ );
+ }
+ }
+
+ // does the node have an ID?
+ if(q.id){
+ ff = agree(ff,
+ function(elem){
+ return (
+ (elem.nodeType == 1) &&
+ (elem.id == q.id)
+ );
+ }
+ );
+ }
+
+ if(q.hasLoops){
+ // if we have other query param parts, make sure we add them to the
+ // filter chain
+ ff = agree(ff, getSimpleFilterFunc(q));
+ }
+
+ return _filtersCache[q.query] = ff;
+ }
+
+ var getNodeIndex = function(node){
+ // NOTE:
+ // we could have a more accurate caching mechanism by invalidating
+ // caches after the query has finished, but I think that'd lead to
+ // significantly more cache churn than the cache would provide
+ // value for in the common case. Generally, we're more
+ // conservative (and therefore, more accurate) than jQuery and
+ // DomQuery WRT node node indexes, but there may be corner cases
+ // in which we fall down. How much we care about them is TBD.
+
+ var pn = node.parentNode;
+ var pnc = pn.childNodes;
+
+ // check to see if we can trust the cache. If not, re-key the whole
+ // thing and return our node match from that.
+
+ var nidx = -1;
+ var child = pn.firstChild;
+ if(!child){
+ return nidx;
+ }
+
+ var ci = node["__cachedIndex"];
+ var cl = pn["__cachedLength"];
+
+ // only handle cache building if we've gone out of sync
+ if(((typeof cl == "number")&&(cl != pnc.length))||(typeof ci != "number")){
+ // rip though the whole set, building cache indexes as we go
+ pn["__cachedLength"] = pnc.length;
+ var idx = 1;
+ do{
+ // we only assign indexes for nodes with nodeType == 1, as per:
+ // http://www.w3.org/TR/css3-selectors/#nth-child-pseudo
+ // only elements are counted in the search order, and they
+ // begin at 1 for the first child's index
+
+ if(child === node){
+ nidx = idx;
+ }
+ if(child.nodeType == 1){
+ child["__cachedIndex"] = idx;
+ idx++;
+ }
+ child = child.nextSibling;
+ }while(child);
+ }else{
+ // NOTE:
+ // could be incorrect in some cases (node swaps involving the
+ // passed node, etc.), but we ignore those due to the relative
+ // unlikelihood of that occuring
+ nidx = ci;
+ }
+ return nidx;
+ }
+
+ var firedCount = 0;
+
+ var blank = "";
+ var _getAttr = function(elem, attr){
+ if(attr == "class"){
+ return elem.className || blank;
+ }
+ if(attr == "for"){
+ return elem.htmlFor || blank;
+ }
+ return elem.getAttribute(attr, 2) || blank;
+ }
+
+ var attrs = {
+ "*=": function(attr, value){
+ return function(elem){
+ // E[foo*="bar"]
+ // an E element whose "foo" attribute value contains
+ // the substring "bar"
+ return (_getAttr(elem, attr).indexOf(value)>=0);
+ }
+ },
+ "^=": function(attr, value){
+ // E[foo^="bar"]
+ // an E element whose "foo" attribute value begins exactly
+ // with the string "bar"
+ return function(elem){
+ return (_getAttr(elem, attr).indexOf(value)==0);
+ }
+ },
+ "$=": function(attr, value){
+ // E[foo$="bar"]
+ // an E element whose "foo" attribute value ends exactly
+ // with the string "bar"
+ var tval = " "+value;
+ return function(elem){
+ var ea = " "+_getAttr(elem, attr);
+ return (ea.lastIndexOf(value)==(ea.length-value.length));
+ }
+ },
+ "~=": function(attr, value){
+ // E[foo~="bar"]
+ // an E element whose "foo" attribute value is a list of
+ // space-separated values, one of which is exactly equal
+ // to "bar"
+
+ // return "[contains(concat(' ',@"+attr+",' '), ' "+ value +" ')]";
+ var tval = " "+value+" ";
+ return function(elem){
+ var ea = " "+_getAttr(elem, attr)+" ";
+ return (ea.indexOf(tval)>=0);
+ }
+ },
+ "|=": function(attr, value){
+ // E[hreflang|="en"]
+ // an E element whose "hreflang" attribute has a
+ // hyphen-separated list of values beginning (from the
+ // left) with "en"
+ var valueDash = " "+value+"-";
+ return function(elem){
+ var ea = " "+(elem.getAttribute(attr, 2) || "");
+ return (
+ (ea == value) ||
+ (ea.indexOf(valueDash)==0)
+ );
+ }
+ },
+ "=": function(attr, value){
+ return function(elem){
+ return (_getAttr(elem, attr) == value);
+ }
+ }
+ };
+
+ var pseudos = {
+ "first-child": function(name, condition){
+ return function(elem){
+ if(elem.nodeType != 1){ return false; }
+ // check to see if any of the previous siblings are elements
+ var fc = elem.previousSibling;
+ while(fc && (fc.nodeType != 1)){
+ fc = fc.previousSibling;
+ }
+ return (!fc);
+ }
+ },
+ "last-child": function(name, condition){
+ return function(elem){
+ if(elem.nodeType != 1){ return false; }
+ // check to see if any of the next siblings are elements
+ var nc = elem.nextSibling;
+ while(nc && (nc.nodeType != 1)){
+ nc = nc.nextSibling;
+ }
+ return (!nc);
+ }
+ },
+ "empty": function(name, condition){
+ return function(elem){
+ // DomQuery and jQuery get this wrong, oddly enough.
+ // The CSS 3 selectors spec is pretty explicit about
+ // it, too.
+ var cn = elem.childNodes;
+ var cnl = elem.childNodes.length;
+ // if(!cnl){ return true; }
+ for(var x=cnl-1; x >= 0; x--){
+ var nt = cn[x].nodeType;
+ if((nt == 1)||(nt == 3)){ return false; }
+ }
+ return true;
+ }
+ },
+ "contains": function(name, condition){
+ return function(elem){
+ // FIXME: I dislike this version of "contains", as
+ // whimsical attribute could set it off. An inner-text
+ // based version might be more accurate, but since
+ // jQuery and DomQuery also potentially get this wrong,
+ // I'm leaving it for now.
+ return (elem.innerHTML.indexOf(condition) >= 0);
+ }
+ },
+ "not": function(name, condition){
+ var ntf = getFilterFunc(getQueryParts(condition)[0]);
+ return function(elem){
+ return (!ntf(elem));
+ }
+ },
+ "nth-child": function(name, condition){
+ var pi = parseInt;
+ if(condition == "odd"){
+ return function(elem){
+ return (
+ ((getNodeIndex(elem)) % 2) == 1
+ );
+ }
+ }else if((condition == "2n")||
+ (condition == "even")){
+ return function(elem){
+ return ((getNodeIndex(elem) % 2) == 0);
+ }
+ }else if(condition.indexOf("0n+") == 0){
+ var ncount = pi(condition.substr(3));
+ return function(elem){
+ return (elem.parentNode[childNodesName][ncount-1] === elem);
+ }
+ }else if( (condition.indexOf("n+") > 0) &&
+ (condition.length > 3) ){
+ var tparts = condition.split("n+", 2);
+ var pred = pi(tparts[0]);
+ var idx = pi(tparts[1]);
+ return function(elem){
+ return ((getNodeIndex(elem) % pred) == idx);
+ }
+ }else if(condition.indexOf("n") == -1){
+ var ncount = pi(condition);
+ return function(elem){
+ return (getNodeIndex(elem) == ncount);
+ }
+ }
+ }
+ };
+
+ var defaultGetter = (d.isIE) ? function(cond){
+ var clc = cond.toLowerCase();
+ return function(elem){
+ return elem[cond]||elem[clc];
+ }
+ } : function(cond){
+ return function(elem){
+ return (elem && elem.getAttribute && elem.hasAttribute(cond));
+ }
+ };
+
+ var getSimpleFilterFunc = function(query){
+
+ var fcHit = (_simpleFiltersCache[query.query]||_filtersCache[query.query]);
+ if(fcHit){ return fcHit; }
+
+ var ff = null;
+
+ // the only case where we'll need the tag name is if we came from an ID query
+ if(query.id){ // do we have an ID component?
+ if(query.tag != "*"){
+ ff = agree(ff, function(elem){
+ return (elem.tagName.toLowerCase() == query.tag);
+ });
+ }
+ }
+
+ // if there's a class in our query, generate a match function for it
+ d.forEach(query.classes, function(cname, idx, arr){
+ // get the class name
+ var isWildcard = cname.charAt(cname.length-1) == "*";
+ if(isWildcard){
+ cname = cname.substr(0, cname.length-1);
+ }
+ // I dislike the regex thing, even if memozied in a cache, but it's VERY short
+ var re = new RegExp("(?:^|\\s)" + cname + (isWildcard ? ".*" : "") + "(?:\\s|$)");
+ ff = agree(ff, function(elem){
+ return re.test(elem.className);
+ });
+ ff.count = idx;
+ });
+
+ d.forEach(query.pseudos, function(pseudo){
+ if(pseudos[pseudo.name]){
+ ff = agree(ff, pseudos[pseudo.name](pseudo.name, pseudo.value));
+ }
+ });
+
+ handleAttrs(attrs, query, defaultGetter,
+ function(tmatcher){ ff = agree(ff, tmatcher); }
+ );
+ if(!ff){
+ ff = function(){ return true; };
+ }
+ return _simpleFiltersCache[query.query] = ff;
+ }
+
+ var _getElementsFuncCache = { };
+
+ var getElementsFunc = function(query, root){
+ var fHit = _getElementsFuncCache[query.query];
+ if(fHit){ return fHit; }
+
+ // NOTE: this function is in the fast path! not memoized!!!
+
+ // the query doesn't contain any spaces, so there's only so many
+ // things it could be
+
+ if(query.id && !query.hasLoops && !query.tag){
+ // ID-only query. Easy.
+ return _getElementsFuncCache[query.query] = function(root){
+ // FIXME: if root != document, check for parenting!
+ return [ d.byId(query.id) ];
+ }
+ }
+
+ var filterFunc = getSimpleFilterFunc(query);
+
+ var retFunc;
+ if(query.tag && query.id && !query.hasLoops){
+ // we got a filtered ID search (e.g., "h4#thinger")
+ retFunc = function(root){
+ var te = d.byId(query.id);
+ if(filterFunc(te)){
+ return [ te ];
+ }
+ }
+ }else{
+ var tret;
+
+ if(!query.hasLoops){
+ // it's just a plain-ol elements-by-tag-name query from the root
+ retFunc = function(root){
+ var ret = [];
+ var te, x=0, tret = root.getElementsByTagName(query.tag);
+ while(te=tret[x++]){
+ ret.push(te);
+ }
+ return ret;
+ }
+ }else{
+ retFunc = function(root){
+ var ret = [];
+ var te, x=0, tret = root.getElementsByTagName(query.tag);
+ while(te=tret[x++]){
+ if(filterFunc(te)){
+ ret.push(te);
+ }
+ }
+ return ret;
+ }
+ }
+ }
+ return _getElementsFuncCache[query.query] = retFunc;
+ }
+
+ var _partsCache = {};
+
+ ////////////////////////////////////////////////////////////////////////
+ // the query runner
+ ////////////////////////////////////////////////////////////////////////
+
+ // this is the second level of spliting, from full-length queries (e.g.,
+ // "div.foo .bar") into simple query expressions (e.g., ["div.foo",
+ // ".bar"])
+ var _queryFuncCache = {
+ "*": d.isIE ?
+ function(root){
+ return root.all;
+ } :
+ function(root){
+ return root.getElementsByTagName("*");
+ },
+ "~": _nextSiblings,
+ "+": function(root){ return _nextSiblings(root, true); },
+ ">": _childElements
+ };
+
+ var getStepQueryFunc = function(query){
+ // if it's trivial, get a fast-path dispatcher
+ var qparts = getQueryParts(d.trim(query));
+ // if(query[query.length-1] == ">"){ query += " *"; }
+ if(qparts.length == 1){
+ var tt = getElementsFunc(qparts[0]);
+ tt.nozip = true;
+ return tt;
+ }
+
+ // otherwise, break it up and return a runner that iterates over the parts recursively
+ var sqf = function(root){
+ var localQueryParts = qparts.slice(0); // clone the src arr
+ var candidates;
+ if(localQueryParts[0].oper == ">"){ // FIXME: what if it's + or ~?
+ candidates = [ root ];
+ // root = document;
+ }else{
+ candidates = getElementsFunc(localQueryParts.shift())(root);
+ }
+ return filterDown(candidates, localQueryParts);
+ }
+ return sqf;
+ }
+
+ // a specialized method that implements our primoridal "query optimizer".
+ // This allows us to dispatch queries to the fastest subsystem we can get.
+ var _getQueryFunc = (
+ // NOTE:
+ // XPath on the Webkit nighlies is slower than it's DOM iteration
+ // for most test cases
+ // FIXME:
+ // we should try to capture some runtime speed data for each query
+ // function to determine on the fly if we should stick w/ the
+ // potentially optimized variant or if we should try something
+ // new.
+ (document["evaluate"] && !d.isSafari) ?
+ function(query){
+ // has xpath support that's faster than DOM
+ var qparts = query.split(" ");
+ // can we handle it?
+ if( (document["evaluate"])&&
+ (query.indexOf(":") == -1)&&
+ (query.indexOf("+") == -1) // skip direct sibling matches. See line ~344
+ ){
+ // dojo.debug(query);
+ // should we handle it?
+
+ // kind of a lame heuristic, but it works
+ if(
+ // a "div div div" style query
+ ((qparts.length > 2)&&(query.indexOf(">") == -1))||
+ // or something else with moderate complexity. kinda janky
+ (qparts.length > 3)||
+ (query.indexOf("[")>=0)||
+ // or if it's a ".thinger" query
+ ((1 == qparts.length)&&(0 <= query.indexOf(".")))
+
+ ){
+ // use get and cache a xpath runner for this selector
+ return getXPathFunc(query);
+ }
+ }
+
+ // fallthrough
+ return getStepQueryFunc(query);
+ } : getStepQueryFunc
+ );
+ // uncomment to disable XPath for testing and tuning the DOM path
+ // _getQueryFunc = getStepQueryFunc;
+
+ // FIXME: we've got problems w/ the NodeList query()/filter() functions if we go XPath for everything
+
+ // uncomment to disable DOM queries for testing and tuning XPath
+ // _getQueryFunc = getXPathFunc;
+
+ // this is the primary caching for full-query results. The query dispatcher
+ // functions are generated here and then pickled for hash lookup in the
+ // future
+ var getQueryFunc = function(query){
+ // return a cached version if one is available
+ var qcz = query.charAt(0);
+ if(d.doc["querySelectorAll"] &&
+ ( (!d.isSafari) || (d.isSafari > 3.1) ) && // see #5832
+ // as per CSS 3, we can't currently start w/ combinator:
+ // http://www.w3.org/TR/css3-selectors/#w3cselgrammar
+ (">+~".indexOf(qcz) == -1)
+ ){
+ return function(root){
+ var r = root.querySelectorAll(query);
+ r.nozip = true; // skip expensive duplication checks and just wrap in a NodeList
+ return r;
+ };
+ }
+ if(_queryFuncCache[query]){ return _queryFuncCache[query]; }
+ if(0 > query.indexOf(",")){
+ // if it's not a compound query (e.g., ".foo, .bar"), cache and return a dispatcher
+ return _queryFuncCache[query] = _getQueryFunc(query);
+ }else{
+ // if it's a complex query, break it up into it's constituent parts
+ // and return a dispatcher that will merge the parts when run
+
+ // var parts = query.split(", ");
+ var parts = query.split(/\s*,\s*/);
+ var tf = function(root){
+ var pindex = 0; // avoid array alloc for every invocation
+ var ret = [];
+ var tp;
+ while(tp = parts[pindex++]){
+ ret = ret.concat(_getQueryFunc(tp, tp.indexOf(" "))(root));
+ }
+ return ret;
+ }
+ // ...cache and return
+ return _queryFuncCache[query] = tf;
+ }
+ }
+
+ // FIXME:
+ // Dean's Base2 uses a system whereby queries themselves note if
+ // they'll need duplicate filtering. We need to get on that plan!!
+
+ // attempt to efficiently determine if an item in a list is a dupe,
+ // returning a list of "uniques", hopefully in doucment order
+ var _zipIdx = 0;
+ var _zip = function(arr){
+ if(arr && arr.nozip){ return d.NodeList._wrap(arr); }
+ var ret = new d.NodeList();
+ if(!arr){ return ret; }
+ if(arr[0]){
+ ret.push(arr[0]);
+ }
+ if(arr.length < 2){ return ret; }
+ _zipIdx++;
+ arr[0]["_zipIdx"] = _zipIdx;
+ for(var x=1, te; te = arr[x]; x++){
+ if(arr[x]["_zipIdx"] != _zipIdx){
+ ret.push(te);
+ }
+ te["_zipIdx"] = _zipIdx;
+ }
+ // FIXME: should we consider stripping these properties?
+ return ret;
+ }
+
+ // the main executor
+ d.query = function(/*String*/ query, /*String|DOMNode?*/ root){
+ // summary:
+ // Returns nodes which match the given CSS3 selector, searching the
+ // entire document by default but optionally taking a node to scope
+ // the search by. Returns an instance of dojo.NodeList.
+ // description:
+ // dojo.query() is the swiss army knife of DOM node manipulation in
+ // Dojo. Much like Prototype's "$$" (bling-bling) function or JQuery's
+ // "$" function, dojo.query provides robust, high-performance
+ // CSS-based node selector support with the option of scoping searches
+ // to a particular sub-tree of a document.
+ //
+ // Supported Selectors:
+ // --------------------
+ //
+ // dojo.query() supports a rich set of CSS3 selectors, including:
+ //
+ // * class selectors (e.g., `.foo`)
+ // * node type selectors like `span`
+ // * ` ` descendant selectors
+ // * `>` child element selectors
+ // * `#foo` style ID selectors
+ // * `*` universal selector
+ // * `~`, the immediately preceeded-by sibling selector
+ // * `+`, the preceeded-by sibling selector
+ // * attribute queries:
+ // | * `[foo]` attribute presence selector
+ // | * `[foo='bar']` attribute value exact match
+ // | * `[foo~='bar']` attribute value list item match
+ // | * `[foo^='bar']` attribute start match
+ // | * `[foo$='bar']` attribute end match
+ // | * `[foo*='bar']` attribute substring match
+ // * `:first-child`, `:last-child` positional selectors
+ // * `:empty` content emtpy selector
+ // * `:empty` content emtpy selector
+ // * `:nth-child(n)`, `:nth-child(2n+1)` style positional calculations
+ // * `:nth-child(even)`, `:nth-child(odd)` positional selectors
+ // * `:not(...)` negation pseudo selectors
+ //
+ // Any legal combination of these selectors will work with
+ // `dojo.query()`, including compound selectors ("," delimited).
+ // Very complex and useful searches can be constructed with this
+ // palette of selectors and when combined with functions for
+ // maniplation presented by dojo.NodeList, many types of DOM
+ // manipulation operations become very straightforward.
+ //
+ // Unsupported Selectors:
+ // ----------------------
+ //
+ // While dojo.query handles many CSS3 selectors, some fall outside of
+ // what's resaonable for a programmatic node querying engine to
+ // handle. Currently unsupported selectors include:
+ //
+ // * namespace-differentiated selectors of any form
+ // * all `::` pseduo-element selectors
+ // * certain pseduo-selectors which don't get a lot of day-to-day use:
+ // | * `:root`, `:lang()`, `:target`, `:focus`
+ // * all visual and state selectors:
+ // | * `:root`, `:active`, `:hover`, `:visisted`, `:link`,
+ // `:enabled`, `:disabled`, `:checked`
+ // * `:*-of-type` pseudo selectors
+ //
+ // dojo.query and XML Documents:
+ // -----------------------------
+ //
+ // `dojo.query` currently only supports searching XML documents
+ // whose tags and attributes are 100% lower-case. This is a known
+ // limitation and will [be addressed soon](http://trac.dojotoolkit.org/ticket/3866)
+ // Non-selector Queries:
+ // ---------------------
+ //
+ // If something other than a String is passed for the query,
+ // `dojo.query` will return a new `dojo.NodeList` constructed from
+ // that parameter alone and all further processing will stop. This
+ // means that if you have a reference to a node or NodeList, you
+ // can quickly construct a new NodeList from the original by
+ // calling `dojo.query(node)` or `dojo.query(list)`.
+ //
+ // query:
+ // The CSS3 expression to match against. For details on the syntax of
+ // CSS3 selectors, see <http://www.w3.org/TR/css3-selectors/#selectors>
+ // root:
+ // A DOMNode (or node id) to scope the search from. Optional.
+ // returns: dojo.NodeList
+ // An instance of `dojo.NodeList`. Many methods are available on
+ // NodeLists for searching, iterating, manipulating, and handling
+ // events on the matched nodes in the returned list.
+ // example:
+ // search the entire document for elements with the class "foo":
+ // | dojo.query(".foo");
+ // these elements will match:
+ // | <span class="foo"></span>
+ // | <span class="foo bar"></span>
+ // | <p class="thud foo"></p>
+ // example:
+ // search the entire document for elements with the classes "foo" *and* "bar":
+ // | dojo.query(".foo.bar");
+ // these elements will match:
+ // | <span class="foo bar"></span>
+ // while these will not:
+ // | <span class="foo"></span>
+ // | <p class="thud foo"></p>
+ // example:
+ // find `<span>` elements which are descendants of paragraphs and
+ // which have a "highlighted" class:
+ // | dojo.query("p span.highlighted");
+ // the innermost span in this fragment matches:
+ // | <p class="foo">
+ // | <span>...
+ // | <span class="highlighted foo bar">...</span>
+ // | </span>
+ // | </p>
+ // example:
+ // set an "odd" class on all odd table rows inside of the table
+ // `#tabular_data`, using the `>` (direct child) selector to avoid
+ // affecting any nested tables:
+ // | dojo.query("#tabular_data > tbody > tr:nth-child(odd)").addClass("odd");
+ // example:
+ // remove all elements with the class "error" from the document
+ // and store them in a list:
+ // | var errors = dojo.query(".error").orphan();
+ // example:
+ // add an onclick handler to every submit button in the document
+ // which causes the form to be sent via Ajax instead:
+ // | dojo.query("input[type='submit']").onclick(function(e){
+ // | dojo.stopEvent(e); // prevent sending the form
+ // | var btn = e.target;
+ // | dojo.xhrPost({
+ // | form: btn.form,
+ // | load: function(data){
+ // | // replace the form with the response
+ // | var div = dojo.doc.createElement("div");
+ // | dojo.place(div, btn.form, "after");
+ // | div.innerHTML = data;
+ // | dojo.style(btn.form, "display", "none");
+ // | }
+ // | });
+ // | });
+
+
+ // NOTE: elementsById is not currently supported
+ // NOTE: ignores xpath-ish queries for now
+
+ if(query.constructor == d.NodeList){
+ return query;
+ }
+ if(!d.isString(query)){
+ return new d.NodeList(query); // dojo.NodeList
+ }
+ if(d.isString(root)){
+ root = d.byId(root);
+ }
+
+ return _zip(getQueryFunc(query)(root||d.doc)); // dojo.NodeList
+ }
+
+ /*
+ // exposing this was a mistake
+ d.query.attrs = attrs;
+ */
+ // exposing this because new pseudo matches are only executed through the
+ // DOM query path (never through the xpath optimizing branch)
+ d.query.pseudos = pseudos;
+
+ // one-off function for filtering a NodeList based on a simple selector
+ d._filterQueryResult = function(nodeList, simpleFilter){
+ var tnl = new d.NodeList();
+ var ff = (simpleFilter) ? getFilterFunc(getQueryParts(simpleFilter)[0]) : function(){ return true; };
+ for(var x=0, te; te = nodeList[x]; x++){
+ if(ff(te)){ tnl.push(te); }
+ }
+ return tnl;
+ }
+})();
+
+}