]> git.wh0rd.org Git - tt-rss.git/blob - lib/dojo/selector/acme.js.uncompressed.js
modify dojo rebuild script to remove uncompressed files
[tt-rss.git] / lib / dojo / selector / acme.js.uncompressed.js
1 define("dojo/selector/acme", [
2         "../dom", "../sniff", "../_base/array", "../_base/lang", "../_base/window"
3 ], function(dom, has, array, lang, win){
4
5         // module:
6         //              dojo/selector/acme
7
8 /*
9         acme architectural overview:
10
11                 acme is a relatively full-featured CSS3 query library. It is
12                 designed to take any valid CSS3 selector and return the nodes matching
13                 the selector. To do this quickly, it processes queries in several
14                 steps, applying caching where profitable.
15
16                 The steps (roughly in reverse order of the way they appear in the code):
17                         1.) check to see if we already have a "query dispatcher"
18                                 - if so, use that with the given parameterization. Skip to step 4.
19                         2.) attempt to determine which branch to dispatch the query to:
20                                 - JS (optimized DOM iteration)
21                                 - native (FF3.1+, Safari 3.1+, IE 8+)
22                         3.) tokenize and convert to executable "query dispatcher"
23                                 - this is where the lion's share of the complexity in the
24                                         system lies. In the DOM version, the query dispatcher is
25                                         assembled as a chain of "yes/no" test functions pertaining to
26                                         a section of a simple query statement (".blah:nth-child(odd)"
27                                         but not "div div", which is 2 simple statements). Individual
28                                         statement dispatchers are cached (to prevent re-definition)
29                                         as are entire dispatch chains (to make re-execution of the
30                                         same query fast)
31                         4.) the resulting query dispatcher is called in the passed scope
32                                         (by default the top-level document)
33                                 - for DOM queries, this results in a recursive, top-down
34                                         evaluation of nodes based on each simple query section
35                                 - for native implementations, this may mean working around spec
36                                         bugs. So be it.
37                         5.) matched nodes are pruned to ensure they are unique (if necessary)
38 */
39
40
41         ////////////////////////////////////////////////////////////////////////
42         // Toolkit aliases
43         ////////////////////////////////////////////////////////////////////////
44
45         // if you are extracting acme for use in your own system, you will
46         // need to provide these methods and properties. No other porting should be
47         // necessary, save for configuring the system to use a class other than
48         // dojo/NodeList as the return instance instantiator
49         var trim =                      lang.trim;
50         var each =                      array.forEach;
51
52         var getDoc = function(){ return win.doc; };
53         // NOTE(alex): the spec is idiotic. CSS queries should ALWAYS be case-sensitive, but nooooooo
54         var cssCaseBug = (getDoc().compatMode) == "BackCompat";
55
56         ////////////////////////////////////////////////////////////////////////
57         // Global utilities
58         ////////////////////////////////////////////////////////////////////////
59
60
61         var specials = ">~+";
62
63         // global thunk to determine whether we should treat the current query as
64         // case sensitive or not. This switch is flipped by the query evaluator
65         // based on the document passed as the context to search.
66         var caseSensitive = false;
67
68         // how high?
69         var yesman = function(){ return true; };
70
71         ////////////////////////////////////////////////////////////////////////
72         // Tokenizer
73         ////////////////////////////////////////////////////////////////////////
74
75         var getQueryParts = function(query){
76                 // summary:
77                 //              state machine for query tokenization
78                 // description:
79                 //              instead of using a brittle and slow regex-based CSS parser,
80                 //              acme implements an AST-style query representation. This
81                 //              representation is only generated once per query. For example,
82                 //              the same query run multiple times or under different root nodes
83                 //              does not re-parse the selector expression but instead uses the
84                 //              cached data structure. The state machine implemented here
85                 //              terminates on the last " " (space) character and returns an
86                 //              ordered array of query component structures (or "parts"). Each
87                 //              part represents an operator or a simple CSS filtering
88                 //              expression. The structure for parts is documented in the code
89                 //              below.
90
91
92                 // NOTE:
93                 //              this code is designed to run fast and compress well. Sacrifices
94                 //              to readability and maintainability have been made.  Your best
95                 //              bet when hacking the tokenizer is to put The Donnas on *really*
96                 //              loud (may we recommend their "Spend The Night" release?) and
97                 //              just assume you're gonna make mistakes. Keep the unit tests
98                 //              open and run them frequently. Knowing is half the battle ;-)
99                 if(specials.indexOf(query.slice(-1)) >= 0){
100                         // if we end with a ">", "+", or "~", that means we're implicitly
101                         // searching all children, so make it explicit
102                         query += " * ";
103                 }else{
104                         // if you have not provided a terminator, one will be provided for
105                         // you...
106                         query += " ";
107                 }
108
109                 var ts = function(/*Integer*/ s, /*Integer*/ e){
110                         // trim and slice.
111
112                         // take an index to start a string slice from and an end position
113                         // and return a trimmed copy of that sub-string
114                         return trim(query.slice(s, e));
115                 };
116
117                 // the overall data graph of the full query, as represented by queryPart objects
118                 var queryParts = [];
119
120
121                 // state keeping vars
122                 var inBrackets = -1, inParens = -1, inMatchFor = -1,
123                         inPseudo = -1, inClass = -1, inId = -1, inTag = -1, currentQuoteChar,
124                         lc = "", cc = "", pStart;
125
126                 // iteration vars
127                 var x = 0, // index in the query
128                         ql = query.length,
129                         currentPart = null, // data structure representing the entire clause
130                         _cp = null; // the current pseudo or attr matcher
131
132                 // several temporary variables are assigned to this structure during a
133                 // potential sub-expression match:
134                 //              attr:
135                 //                      a string representing the current full attribute match in a
136                 //                      bracket expression
137                 //              type:
138                 //                      if there's an operator in a bracket expression, this is
139                 //                      used to keep track of it
140                 //              value:
141                 //                      the internals of parenthetical expression for a pseudo. for
142                 //                      :nth-child(2n+1), value might be "2n+1"
143
144                 var endTag = function(){
145                         // called when the tokenizer hits the end of a particular tag name.
146                         // Re-sets state variables for tag matching and sets up the matcher
147                         // to handle the next type of token (tag or operator).
148                         if(inTag >= 0){
149                                 var tv = (inTag == x) ? null : ts(inTag, x); // .toLowerCase();
150                                 currentPart[ (specials.indexOf(tv) < 0) ? "tag" : "oper" ] = tv;
151                                 inTag = -1;
152                         }
153                 };
154
155                 var endId = function(){
156                         // called when the tokenizer might be at the end of an ID portion of a match
157                         if(inId >= 0){
158                                 currentPart.id = ts(inId, x).replace(/\\/g, "");
159                                 inId = -1;
160                         }
161                 };
162
163                 var endClass = function(){
164                         // called when the tokenizer might be at the end of a class name
165                         // match. CSS allows for multiple classes, so we augment the
166                         // current item with another class in its list
167                         if(inClass >= 0){
168                                 currentPart.classes.push(ts(inClass + 1, x).replace(/\\/g, ""));
169                                 inClass = -1;
170                         }
171                 };
172
173                 var endAll = function(){
174                         // at the end of a simple fragment, so wall off the matches
175                         endId();
176                         endTag();
177                         endClass();
178                 };
179
180                 var endPart = function(){
181                         endAll();
182                         if(inPseudo >= 0){
183                                 currentPart.pseudos.push({ name: ts(inPseudo + 1, x) });
184                         }
185                         // hint to the selector engine to tell it whether or not it
186                         // needs to do any iteration. Many simple selectors don't, and
187                         // we can avoid significant construction-time work by advising
188                         // the system to skip them
189                         currentPart.loops = (
190                                         currentPart.pseudos.length ||
191                                         currentPart.attrs.length ||
192                                         currentPart.classes.length      );
193
194                         currentPart.oquery = currentPart.query = ts(pStart, x); // save the full expression as a string
195
196
197                         // otag/tag are hints to suggest to the system whether or not
198                         // it's an operator or a tag. We save a copy of otag since the
199                         // tag name is cast to upper-case in regular HTML matches. The
200                         // system has a global switch to figure out if the current
201                         // expression needs to be case sensitive or not and it will use
202                         // otag or tag accordingly
203                         currentPart.otag = currentPart.tag = (currentPart["oper"]) ? null : (currentPart.tag || "*");
204
205                         if(currentPart.tag){
206                                 // if we're in a case-insensitive HTML doc, we likely want
207                                 // the toUpperCase when matching on element.tagName. If we
208                                 // do it here, we can skip the string op per node
209                                 // comparison
210                                 currentPart.tag = currentPart.tag.toUpperCase();
211                         }
212
213                         // add the part to the list
214                         if(queryParts.length && (queryParts[queryParts.length-1].oper)){
215                                 // operators are always infix, so we remove them from the
216                                 // list and attach them to the next match. The evaluator is
217                                 // responsible for sorting out how to handle them.
218                                 currentPart.infixOper = queryParts.pop();
219                                 currentPart.query = currentPart.infixOper.query + " " + currentPart.query;
220                                 /*
221                                 console.debug(  "swapping out the infix",
222                                                                 currentPart.infixOper,
223                                                                 "and attaching it to",
224                                                                 currentPart);
225                                 */
226                         }
227                         queryParts.push(currentPart);
228
229                         currentPart = null;
230                 };
231
232                 // iterate over the query, character by character, building up a
233                 // list of query part objects
234                 for(; lc=cc, cc=query.charAt(x), x < ql; x++){
235                         //              cc: the current character in the match
236                         //              lc: the last character (if any)
237
238                         // someone is trying to escape something, so don't try to match any
239                         // fragments. We assume we're inside a literal.
240                         if(lc == "\\"){ continue; }
241                         if(!currentPart){ // a part was just ended or none has yet been created
242                                 // NOTE: I hate all this alloc, but it's shorter than writing tons of if's
243                                 pStart = x;
244                                 //      rules describe full CSS sub-expressions, like:
245                                 //              #someId
246                                 //              .className:first-child
247                                 //      but not:
248                                 //              thinger > div.howdy[type=thinger]
249                                 //      the indidual components of the previous query would be
250                                 //      split into 3 parts that would be represented a structure like:
251                                 //              [
252                                 //                      {
253                                 //                              query: "thinger",
254                                 //                              tag: "thinger",
255                                 //                      },
256                                 //                      {
257                                 //                              query: "div.howdy[type=thinger]",
258                                 //                              classes: ["howdy"],
259                                 //                              infixOper: {
260                                 //                                      query: ">",
261                                 //                                      oper: ">",
262                                 //                              }
263                                 //                      },
264                                 //              ]
265                                 currentPart = {
266                                         query: null, // the full text of the part's rule
267                                         pseudos: [], // CSS supports multiple pseud-class matches in a single rule
268                                         attrs: [],      // CSS supports multi-attribute match, so we need an array
269                                         classes: [], // class matches may be additive, e.g.: .thinger.blah.howdy
270                                         tag: null,      // only one tag...
271                                         oper: null, // ...or operator per component. Note that these wind up being exclusive.
272                                         id: null,       // the id component of a rule
273                                         getTag: function(){
274                                                 return caseSensitive ? this.otag : this.tag;
275                                         }
276                                 };
277
278                                 // if we don't have a part, we assume we're going to start at
279                                 // the beginning of a match, which should be a tag name. This
280                                 // might fault a little later on, but we detect that and this
281                                 // iteration will still be fine.
282                                 inTag = x;
283                         }
284
285                         // Skip processing all quoted characters.
286                         // If we are inside quoted text then currentQuoteChar stores the character that began the quote,
287                         // thus that character that will end it.
288                         if(currentQuoteChar){
289                                 if(cc == currentQuoteChar){
290                                         currentQuoteChar = null;
291                                 }
292                                 continue;
293                         }else if (cc == "'" || cc == '"'){
294                                 currentQuoteChar = cc;
295                                 continue;
296                         }
297
298                         if(inBrackets >= 0){
299                                 // look for a the close first
300                                 if(cc == "]"){ // if we're in a [...] clause and we end, do assignment
301                                         if(!_cp.attr){
302                                                 // no attribute match was previously begun, so we
303                                                 // assume this is an attribute existence match in the
304                                                 // form of [someAttributeName]
305                                                 _cp.attr = ts(inBrackets+1, x);
306                                         }else{
307                                                 // we had an attribute already, so we know that we're
308                                                 // matching some sort of value, as in [attrName=howdy]
309                                                 _cp.matchFor = ts((inMatchFor||inBrackets+1), x);
310                                         }
311                                         var cmf = _cp.matchFor;
312                                         if(cmf){
313                                                 // try to strip quotes from the matchFor value. We want
314                                                 // [attrName=howdy] to match the same
315                                                 //      as [attrName = 'howdy' ]
316                                                 if(     (cmf.charAt(0) == '"') || (cmf.charAt(0) == "'") ){
317                                                         _cp.matchFor = cmf.slice(1, -1);
318                                                 }
319                                         }
320                                         // remove backslash escapes from an attribute match, since DOM
321                                         // querying will get attribute values without backslashes
322                                         if(_cp.matchFor){
323                                                 _cp.matchFor = _cp.matchFor.replace(/\\/g, "");
324                                         }
325
326                                         // end the attribute by adding it to the list of attributes.
327                                         currentPart.attrs.push(_cp);
328                                         _cp = null; // necessary?
329                                         inBrackets = inMatchFor = -1;
330                                 }else if(cc == "="){
331                                         // if the last char was an operator prefix, make sure we
332                                         // record it along with the "=" operator.
333                                         var addToCc = ("|~^$*".indexOf(lc) >=0 ) ? lc : "";
334                                         _cp.type = addToCc+cc;
335                                         _cp.attr = ts(inBrackets+1, x-addToCc.length);
336                                         inMatchFor = x+1;
337                                 }
338                                 // now look for other clause parts
339                         }else if(inParens >= 0){
340                                 // if we're in a parenthetical expression, we need to figure
341                                 // out if it's attached to a pseudo-selector rule like
342                                 // :nth-child(1)
343                                 if(cc == ")"){
344                                         if(inPseudo >= 0){
345                                                 _cp.value = ts(inParens+1, x);
346                                         }
347                                         inPseudo = inParens = -1;
348                                 }
349                         }else if(cc == "#"){
350                                 // start of an ID match
351                                 endAll();
352                                 inId = x+1;
353                         }else if(cc == "."){
354                                 // start of a class match
355                                 endAll();
356                                 inClass = x;
357                         }else if(cc == ":"){
358                                 // start of a pseudo-selector match
359                                 endAll();
360                                 inPseudo = x;
361                         }else if(cc == "["){
362                                 // start of an attribute match.
363                                 endAll();
364                                 inBrackets = x;
365                                 // provide a new structure for the attribute match to fill-in
366                                 _cp = {
367                                         /*=====
368                                         attr: null, type: null, matchFor: null
369                                         =====*/
370                                 };
371                         }else if(cc == "("){
372                                 // we really only care if we've entered a parenthetical
373                                 // expression if we're already inside a pseudo-selector match
374                                 if(inPseudo >= 0){
375                                         // provide a new structure for the pseudo match to fill-in
376                                         _cp = {
377                                                 name: ts(inPseudo+1, x),
378                                                 value: null
379                                         };
380                                         currentPart.pseudos.push(_cp);
381                                 }
382                                 inParens = x;
383                         }else if(
384                                 (cc == " ") &&
385                                 // if it's a space char and the last char is too, consume the
386                                 // current one without doing more work
387                                 (lc != cc)
388                         ){
389                                 endPart();
390                         }
391                 }
392                 return queryParts;
393         };
394
395
396         ////////////////////////////////////////////////////////////////////////
397         // DOM query infrastructure
398         ////////////////////////////////////////////////////////////////////////
399
400         var agree = function(first, second){
401                 // the basic building block of the yes/no chaining system. agree(f1,
402                 // f2) generates a new function which returns the boolean results of
403                 // both of the passed functions to a single logical-anded result. If
404                 // either are not passed, the other is used exclusively.
405                 if(!first){ return second; }
406                 if(!second){ return first; }
407
408                 return function(){
409                         return first.apply(window, arguments) && second.apply(window, arguments);
410                 };
411         };
412
413         var getArr = function(i, arr){
414                 // helps us avoid array alloc when we don't need it
415                 var r = arr||[]; // FIXME: should this be 'new d._NodeListCtor()' ?
416                 if(i){ r.push(i); }
417                 return r;
418         };
419
420         var _isElement = function(n){ return (1 == n.nodeType); };
421
422         // FIXME: need to coalesce _getAttr with defaultGetter
423         var blank = "";
424         var _getAttr = function(elem, attr){
425                 if(!elem){ return blank; }
426                 if(attr == "class"){
427                         return elem.className || blank;
428                 }
429                 if(attr == "for"){
430                         return elem.htmlFor || blank;
431                 }
432                 if(attr == "style"){
433                         return elem.style.cssText || blank;
434                 }
435                 return (caseSensitive ? elem.getAttribute(attr) : elem.getAttribute(attr, 2)) || blank;
436         };
437
438         var attrs = {
439                 "*=": function(attr, value){
440                         return function(elem){
441                                 // E[foo*="bar"]
442                                 //              an E element whose "foo" attribute value contains
443                                 //              the substring "bar"
444                                 return (_getAttr(elem, attr).indexOf(value)>=0);
445                         };
446                 },
447                 "^=": function(attr, value){
448                         // E[foo^="bar"]
449                         //              an E element whose "foo" attribute value begins exactly
450                         //              with the string "bar"
451                         return function(elem){
452                                 return (_getAttr(elem, attr).indexOf(value)==0);
453                         };
454                 },
455                 "$=": function(attr, value){
456                         // E[foo$="bar"]
457                         //              an E element whose "foo" attribute value ends exactly
458                         //              with the string "bar"
459                         return function(elem){
460                                 var ea = " "+_getAttr(elem, attr);
461                                 var lastIndex = ea.lastIndexOf(value);
462                                 return lastIndex > -1 && (lastIndex==(ea.length-value.length));
463                         };
464                 },
465                 "~=": function(attr, value){
466                         // E[foo~="bar"]
467                         //              an E element whose "foo" attribute value is a list of
468                         //              space-separated values, one of which is exactly equal
469                         //              to "bar"
470
471                         // return "[contains(concat(' ',@"+attr+",' '), ' "+ value +" ')]";
472                         var tval = " "+value+" ";
473                         return function(elem){
474                                 var ea = " "+_getAttr(elem, attr)+" ";
475                                 return (ea.indexOf(tval)>=0);
476                         };
477                 },
478                 "|=": function(attr, value){
479                         // E[hreflang|="en"]
480                         //              an E element whose "hreflang" attribute has a
481                         //              hyphen-separated list of values beginning (from the
482                         //              left) with "en"
483                         var valueDash = value+"-";
484                         return function(elem){
485                                 var ea = _getAttr(elem, attr);
486                                 return (
487                                         (ea == value) ||
488                                         (ea.indexOf(valueDash)==0)
489                                 );
490                         };
491                 },
492                 "=": function(attr, value){
493                         return function(elem){
494                                 return (_getAttr(elem, attr) == value);
495                         };
496                 }
497         };
498
499         // avoid testing for node type if we can. Defining this in the negative
500         // here to avoid negation in the fast path.
501         var _noNES = (typeof getDoc().firstChild.nextElementSibling == "undefined");
502         var _ns = !_noNES ? "nextElementSibling" : "nextSibling";
503         var _ps = !_noNES ? "previousElementSibling" : "previousSibling";
504         var _simpleNodeTest = (_noNES ? _isElement : yesman);
505
506         var _lookLeft = function(node){
507                 // look left
508                 while(node = node[_ps]){
509                         if(_simpleNodeTest(node)){ return false; }
510                 }
511                 return true;
512         };
513
514         var _lookRight = function(node){
515                 // look right
516                 while(node = node[_ns]){
517                         if(_simpleNodeTest(node)){ return false; }
518                 }
519                 return true;
520         };
521
522         var getNodeIndex = function(node){
523                 var root = node.parentNode;
524                 root = root.nodeType != 7 ? root : root.nextSibling; // PROCESSING_INSTRUCTION_NODE
525                 var i = 0,
526                         tret = root.children || root.childNodes,
527                         ci = (node["_i"]||node.getAttribute("_i")||-1),
528                         cl = (root["_l"]|| (typeof root.getAttribute !== "undefined" ? root.getAttribute("_l") : -1));
529
530                 if(!tret){ return -1; }
531                 var l = tret.length;
532
533                 // we calculate the parent length as a cheap way to invalidate the
534                 // cache. It's not 100% accurate, but it's much more honest than what
535                 // other libraries do
536                 if( cl == l && ci >= 0 && cl >= 0 ){
537                         // if it's legit, tag and release
538                         return ci;
539                 }
540
541                 // else re-key things
542                 if(has("ie") && typeof root.setAttribute !== "undefined"){
543                         root.setAttribute("_l", l);
544                 }else{
545                         root["_l"] = l;
546                 }
547                 ci = -1;
548                 for(var te = root["firstElementChild"]||root["firstChild"]; te; te = te[_ns]){
549                         if(_simpleNodeTest(te)){
550                                 if(has("ie")){
551                                         te.setAttribute("_i", ++i);
552                                 }else{
553                                         te["_i"] = ++i;
554                                 }
555                                 if(node === te){
556                                         // NOTE:
557                                         //      shortcutting the return at this step in indexing works
558                                         //      very well for benchmarking but we avoid it here since
559                                         //      it leads to potential O(n^2) behavior in sequential
560                                         //      getNodexIndex operations on a previously un-indexed
561                                         //      parent. We may revisit this at a later time, but for
562                                         //      now we just want to get the right answer more often
563                                         //      than not.
564                                         ci = i;
565                                 }
566                         }
567                 }
568                 return ci;
569         };
570
571         var isEven = function(elem){
572                 return !((getNodeIndex(elem)) % 2);
573         };
574
575         var isOdd = function(elem){
576                 return ((getNodeIndex(elem)) % 2);
577         };
578
579         var pseudos = {
580                 "checked": function(name, condition){
581                         return function(elem){
582                                 return !!("checked" in elem ? elem.checked : elem.selected);
583                         };
584                 },
585                 "disabled": function(name, condition){
586                         return function(elem){
587                                 return elem.disabled;
588                         };
589                 },
590                 "enabled": function(name, condition){
591                         return function(elem){
592                                 return !elem.disabled;
593                         };
594                 },
595                 "first-child": function(){ return _lookLeft; },
596                 "last-child": function(){ return _lookRight; },
597                 "only-child": function(name, condition){
598                         return function(node){
599                                 return _lookLeft(node) && _lookRight(node);
600                         };
601                 },
602                 "empty": function(name, condition){
603                         return function(elem){
604                                 // DomQuery and jQuery get this wrong, oddly enough.
605                                 // The CSS 3 selectors spec is pretty explicit about it, too.
606                                 var cn = elem.childNodes;
607                                 var cnl = elem.childNodes.length;
608                                 // if(!cnl){ return true; }
609                                 for(var x=cnl-1; x >= 0; x--){
610                                         var nt = cn[x].nodeType;
611                                         if((nt === 1)||(nt == 3)){ return false; }
612                                 }
613                                 return true;
614                         };
615                 },
616                 "contains": function(name, condition){
617                         var cz = condition.charAt(0);
618                         if( cz == '"' || cz == "'" ){ //remove quote
619                                 condition = condition.slice(1, -1);
620                         }
621                         return function(elem){
622                                 return (elem.innerHTML.indexOf(condition) >= 0);
623                         };
624                 },
625                 "not": function(name, condition){
626                         var p = getQueryParts(condition)[0];
627                         var ignores = { el: 1 };
628                         if(p.tag != "*"){
629                                 ignores.tag = 1;
630                         }
631                         if(!p.classes.length){
632                                 ignores.classes = 1;
633                         }
634                         var ntf = getSimpleFilterFunc(p, ignores);
635                         return function(elem){
636                                 return (!ntf(elem));
637                         };
638                 },
639                 "nth-child": function(name, condition){
640                         var pi = parseInt;
641                         // avoid re-defining function objects if we can
642                         if(condition == "odd"){
643                                 return isOdd;
644                         }else if(condition == "even"){
645                                 return isEven;
646                         }
647                         // FIXME: can we shorten this?
648                         if(condition.indexOf("n") != -1){
649                                 var tparts = condition.split("n", 2);
650                                 var pred = tparts[0] ? ((tparts[0] == '-') ? -1 : pi(tparts[0])) : 1;
651                                 var idx = tparts[1] ? pi(tparts[1]) : 0;
652                                 var lb = 0, ub = -1;
653                                 if(pred > 0){
654                                         if(idx < 0){
655                                                 idx = (idx % pred) && (pred + (idx % pred));
656                                         }else if(idx>0){
657                                                 if(idx >= pred){
658                                                         lb = idx - idx % pred;
659                                                 }
660                                                 idx = idx % pred;
661                                         }
662                                 }else if(pred<0){
663                                         pred *= -1;
664                                         // idx has to be greater than 0 when pred is negative;
665                                         // shall we throw an error here?
666                                         if(idx > 0){
667                                                 ub = idx;
668                                                 idx = idx % pred;
669                                         }
670                                 }
671                                 if(pred > 0){
672                                         return function(elem){
673                                                 var i = getNodeIndex(elem);
674                                                 return (i>=lb) && (ub<0 || i<=ub) && ((i % pred) == idx);
675                                         };
676                                 }else{
677                                         condition = idx;
678                                 }
679                         }
680                         var ncount = pi(condition);
681                         return function(elem){
682                                 return (getNodeIndex(elem) == ncount);
683                         };
684                 }
685         };
686
687         var defaultGetter = (has("ie") < 9 || has("ie") == 9 && has("quirks")) ? function(cond){
688                 var clc = cond.toLowerCase();
689                 if(clc == "class"){ cond = "className"; }
690                 return function(elem){
691                         return (caseSensitive ? elem.getAttribute(cond) : elem[cond]||elem[clc]);
692                 };
693         } : function(cond){
694                 return function(elem){
695                         return (elem && elem.getAttribute && elem.hasAttribute(cond));
696                 };
697         };
698
699         var getSimpleFilterFunc = function(query, ignores){
700                 // generates a node tester function based on the passed query part. The
701                 // query part is one of the structures generated by the query parser
702                 // when it creates the query AST. The "ignores" object specifies which
703                 // (if any) tests to skip, allowing the system to avoid duplicating
704                 // work where it may have already been taken into account by other
705                 // factors such as how the nodes to test were fetched in the first
706                 // place
707                 if(!query){ return yesman; }
708                 ignores = ignores||{};
709
710                 var ff = null;
711
712                 if(!("el" in ignores)){
713                         ff = agree(ff, _isElement);
714                 }
715
716                 if(!("tag" in ignores)){
717                         if(query.tag != "*"){
718                                 ff = agree(ff, function(elem){
719                                         return (elem && ((caseSensitive ? elem.tagName : elem.tagName.toUpperCase()) == query.getTag()));
720                                 });
721                         }
722                 }
723
724                 if(!("classes" in ignores)){
725                         each(query.classes, function(cname, idx, arr){
726                                 // get the class name
727                                 /*
728                                 var isWildcard = cname.charAt(cname.length-1) == "*";
729                                 if(isWildcard){
730                                         cname = cname.substr(0, cname.length-1);
731                                 }
732                                 // I dislike the regex thing, even if memoized in a cache, but it's VERY short
733                                 var re = new RegExp("(?:^|\\s)" + cname + (isWildcard ? ".*" : "") + "(?:\\s|$)");
734                                 */
735                                 var re = new RegExp("(?:^|\\s)" + cname + "(?:\\s|$)");
736                                 ff = agree(ff, function(elem){
737                                         return re.test(elem.className);
738                                 });
739                                 ff.count = idx;
740                         });
741                 }
742
743                 if(!("pseudos" in ignores)){
744                         each(query.pseudos, function(pseudo){
745                                 var pn = pseudo.name;
746                                 if(pseudos[pn]){
747                                         ff = agree(ff, pseudos[pn](pn, pseudo.value));
748                                 }
749                         });
750                 }
751
752                 if(!("attrs" in ignores)){
753                         each(query.attrs, function(attr){
754                                 var matcher;
755                                 var a = attr.attr;
756                                 // type, attr, matchFor
757                                 if(attr.type && attrs[attr.type]){
758                                         matcher = attrs[attr.type](a, attr.matchFor);
759                                 }else if(a.length){
760                                         matcher = defaultGetter(a);
761                                 }
762                                 if(matcher){
763                                         ff = agree(ff, matcher);
764                                 }
765                         });
766                 }
767
768                 if(!("id" in ignores)){
769                         if(query.id){
770                                 ff = agree(ff, function(elem){
771                                         return (!!elem && (elem.id == query.id));
772                                 });
773                         }
774                 }
775
776                 if(!ff){
777                         if(!("default" in ignores)){
778                                 ff = yesman;
779                         }
780                 }
781                 return ff;
782         };
783
784         var _nextSibling = function(filterFunc){
785                 return function(node, ret, bag){
786                         while(node = node[_ns]){
787                                 if(_noNES && (!_isElement(node))){ continue; }
788                                 if(
789                                         (!bag || _isUnique(node, bag)) &&
790                                         filterFunc(node)
791                                 ){
792                                         ret.push(node);
793                                 }
794                                 break;
795                         }
796                         return ret;
797                 };
798         };
799
800         var _nextSiblings = function(filterFunc){
801                 return function(root, ret, bag){
802                         var te = root[_ns];
803                         while(te){
804                                 if(_simpleNodeTest(te)){
805                                         if(bag && !_isUnique(te, bag)){
806                                                 break;
807                                         }
808                                         if(filterFunc(te)){
809                                                 ret.push(te);
810                                         }
811                                 }
812                                 te = te[_ns];
813                         }
814                         return ret;
815                 };
816         };
817
818         // get an array of child *elements*, skipping text and comment nodes
819         var _childElements = function(filterFunc){
820                 filterFunc = filterFunc||yesman;
821                 return function(root, ret, bag){
822                         // get an array of child elements, skipping text and comment nodes
823                         var te, x = 0, tret = root.children || root.childNodes;
824                         while(te = tret[x++]){
825                                 if(
826                                         _simpleNodeTest(te) &&
827                                         (!bag || _isUnique(te, bag)) &&
828                                         (filterFunc(te, x))
829                                 ){
830                                         ret.push(te);
831                                 }
832                         }
833                         return ret;
834                 };
835         };
836
837         // test to see if node is below root
838         var _isDescendant = function(node, root){
839                 var pn = node.parentNode;
840                 while(pn){
841                         if(pn == root){
842                                 break;
843                         }
844                         pn = pn.parentNode;
845                 }
846                 return !!pn;
847         };
848
849         var _getElementsFuncCache = {};
850
851         var getElementsFunc = function(query){
852                 var retFunc = _getElementsFuncCache[query.query];
853                 // if we've got a cached dispatcher, just use that
854                 if(retFunc){ return retFunc; }
855                 // else, generate a new on
856
857                 // NOTE:
858                 //              this function returns a function that searches for nodes and
859                 //              filters them.  The search may be specialized by infix operators
860                 //              (">", "~", or "+") else it will default to searching all
861                 //              descendants (the " " selector). Once a group of children is
862                 //              found, a test function is applied to weed out the ones we
863                 //              don't want. Many common cases can be fast-pathed. We spend a
864                 //              lot of cycles to create a dispatcher that doesn't do more work
865                 //              than necessary at any point since, unlike this function, the
866                 //              dispatchers will be called every time. The logic of generating
867                 //              efficient dispatchers looks like this in pseudo code:
868                 //
869                 //              # if it's a purely descendant query (no ">", "+", or "~" modifiers)
870                 //              if infixOperator == " ":
871                 //                      if only(id):
872                 //                              return def(root):
873                 //                                      return d.byId(id, root);
874                 //
875                 //                      elif id:
876                 //                              return def(root):
877                 //                                      return filter(d.byId(id, root));
878                 //
879                 //                      elif cssClass && getElementsByClassName:
880                 //                              return def(root):
881                 //                                      return filter(root.getElementsByClassName(cssClass));
882                 //
883                 //                      elif only(tag):
884                 //                              return def(root):
885                 //                                      return root.getElementsByTagName(tagName);
886                 //
887                 //                      else:
888                 //                              # search by tag name, then filter
889                 //                              return def(root):
890                 //                                      return filter(root.getElementsByTagName(tagName||"*"));
891                 //
892                 //              elif infixOperator == ">":
893                 //                      # search direct children
894                 //                      return def(root):
895                 //                              return filter(root.children);
896                 //
897                 //              elif infixOperator == "+":
898                 //                      # search next sibling
899                 //                      return def(root):
900                 //                              return filter(root.nextElementSibling);
901                 //
902                 //              elif infixOperator == "~":
903                 //                      # search rightward siblings
904                 //                      return def(root):
905                 //                              return filter(nextSiblings(root));
906
907                 var io = query.infixOper;
908                 var oper = (io ? io.oper : "");
909                 // the default filter func which tests for all conditions in the query
910                 // part. This is potentially inefficient, so some optimized paths may
911                 // re-define it to test fewer things.
912                 var filterFunc = getSimpleFilterFunc(query, { el: 1 });
913                 var qt = query.tag;
914                 var wildcardTag = ("*" == qt);
915                 var ecs = getDoc()["getElementsByClassName"];
916
917                 if(!oper){
918                         // if there's no infix operator, then it's a descendant query. ID
919                         // and "elements by class name" variants can be accelerated so we
920                         // call them out explicitly:
921                         if(query.id){
922                                 // testing shows that the overhead of yesman() is acceptable
923                                 // and can save us some bytes vs. re-defining the function
924                                 // everywhere.
925                                 filterFunc = (!query.loops && wildcardTag) ?
926                                         yesman :
927                                         getSimpleFilterFunc(query, { el: 1, id: 1 });
928
929                                 retFunc = function(root, arr){
930                                         var te = dom.byId(query.id, (root.ownerDocument||root));
931                                         if(!te || !filterFunc(te)){ return; }
932                                         if(9 == root.nodeType){ // if root's a doc, we just return directly
933                                                 return getArr(te, arr);
934                                         }else{ // otherwise check ancestry
935                                                 if(_isDescendant(te, root)){
936                                                         return getArr(te, arr);
937                                                 }
938                                         }
939                                 };
940                         }else if(
941                                 ecs &&
942                                 // isAlien check. Workaround for Prototype.js being totally evil/dumb.
943                                 /\{\s*\[native code\]\s*\}/.test(String(ecs)) &&
944                                 query.classes.length &&
945                                 !cssCaseBug
946                         ){
947                                 // it's a class-based query and we've got a fast way to run it.
948
949                                 // ignore class and ID filters since we will have handled both
950                                 filterFunc = getSimpleFilterFunc(query, { el: 1, classes: 1, id: 1 });
951                                 var classesString = query.classes.join(" ");
952                                 retFunc = function(root, arr, bag){
953                                         var ret = getArr(0, arr), te, x=0;
954                                         var tret = root.getElementsByClassName(classesString);
955                                         while((te = tret[x++])){
956                                                 if(filterFunc(te, root) && _isUnique(te, bag)){
957                                                         ret.push(te);
958                                                 }
959                                         }
960                                         return ret;
961                                 };
962
963                         }else if(!wildcardTag && !query.loops){
964                                 // it's tag only. Fast-path it.
965                                 retFunc = function(root, arr, bag){
966                                         var ret = getArr(0, arr), te, x=0;
967                                         var tag = query.getTag(),
968                                                 tret = tag ? root.getElementsByTagName(tag) : [];
969                                         while((te = tret[x++])){
970                                                 if(_isUnique(te, bag)){
971                                                         ret.push(te);
972                                                 }
973                                         }
974                                         return ret;
975                                 };
976                         }else{
977                                 // the common case:
978                                 //              a descendant selector without a fast path. By now it's got
979                                 //              to have a tag selector, even if it's just "*" so we query
980                                 //              by that and filter
981                                 filterFunc = getSimpleFilterFunc(query, { el: 1, tag: 1, id: 1 });
982                                 retFunc = function(root, arr, bag){
983                                         var ret = getArr(0, arr), te, x=0;
984                                         // we use getTag() to avoid case sensitivity issues
985                                         var tag = query.getTag(),
986                                                 tret = tag ? root.getElementsByTagName(tag) : [];
987                                         while((te = tret[x++])){
988                                                 if(filterFunc(te, root) && _isUnique(te, bag)){
989                                                         ret.push(te);
990                                                 }
991                                         }
992                                         return ret;
993                                 };
994                         }
995                 }else{
996                         // the query is scoped in some way. Instead of querying by tag we
997                         // use some other collection to find candidate nodes
998                         var skipFilters = { el: 1 };
999                         if(wildcardTag){
1000                                 skipFilters.tag = 1;
1001                         }
1002                         filterFunc = getSimpleFilterFunc(query, skipFilters);
1003                         if("+" == oper){
1004                                 retFunc = _nextSibling(filterFunc);
1005                         }else if("~" == oper){
1006                                 retFunc = _nextSiblings(filterFunc);
1007                         }else if(">" == oper){
1008                                 retFunc = _childElements(filterFunc);
1009                         }
1010                 }
1011                 // cache it and return
1012                 return _getElementsFuncCache[query.query] = retFunc;
1013         };
1014
1015         var filterDown = function(root, queryParts){
1016                 // NOTE:
1017                 //              this is the guts of the DOM query system. It takes a list of
1018                 //              parsed query parts and a root and finds children which match
1019                 //              the selector represented by the parts
1020                 var candidates = getArr(root), qp, x, te, qpl = queryParts.length, bag, ret;
1021
1022                 for(var i = 0; i < qpl; i++){
1023                         ret = [];
1024                         qp = queryParts[i];
1025                         x = candidates.length - 1;
1026                         if(x > 0){
1027                                 // if we have more than one root at this level, provide a new
1028                                 // hash to use for checking group membership but tell the
1029                                 // system not to post-filter us since we will already have been
1030                                 // guaranteed to be unique
1031                                 bag = {};
1032                                 ret.nozip = true;
1033                         }
1034                         var gef = getElementsFunc(qp);
1035                         for(var j = 0; (te = candidates[j]); j++){
1036                                 // for every root, get the elements that match the descendant
1037                                 // selector, adding them to the "ret" array and filtering them
1038                                 // via membership in this level's bag. If there are more query
1039                                 // parts, then this level's return will be used as the next
1040                                 // level's candidates
1041                                 gef(te, ret, bag);
1042                         }
1043                         if(!ret.length){ break; }
1044                         candidates = ret;
1045                 }
1046                 return ret;
1047         };
1048
1049         ////////////////////////////////////////////////////////////////////////
1050         // the query runner
1051         ////////////////////////////////////////////////////////////////////////
1052
1053         // these are the primary caches for full-query results. The query
1054         // dispatcher functions are generated then stored here for hash lookup in
1055         // the future
1056         var _queryFuncCacheDOM = {},
1057                 _queryFuncCacheQSA = {};
1058
1059         // this is the second level of splitting, from full-length queries (e.g.,
1060         // "div.foo .bar") into simple query expressions (e.g., ["div.foo",
1061         // ".bar"])
1062         var getStepQueryFunc = function(query){
1063                 var qparts = getQueryParts(trim(query));
1064
1065                 // if it's trivial, avoid iteration and zipping costs
1066                 if(qparts.length == 1){
1067                         // we optimize this case here to prevent dispatch further down the
1068                         // chain, potentially slowing things down. We could more elegantly
1069                         // handle this in filterDown(), but it's slower for simple things
1070                         // that need to be fast (e.g., "#someId").
1071                         var tef = getElementsFunc(qparts[0]);
1072                         return function(root){
1073                                 var r = tef(root, []);
1074                                 if(r){ r.nozip = true; }
1075                                 return r;
1076                         };
1077                 }
1078
1079                 // otherwise, break it up and return a runner that iterates over the parts recursively
1080                 return function(root){
1081                         return filterDown(root, qparts);
1082                 };
1083         };
1084
1085         // NOTES:
1086         //      * we can't trust QSA for anything but document-rooted queries, so
1087         //        caching is split into DOM query evaluators and QSA query evaluators
1088         //      * caching query results is dirty and leak-prone (or, at a minimum,
1089         //        prone to unbounded growth). Other toolkits may go this route, but
1090         //        they totally destroy their own ability to manage their memory
1091         //        footprint. If we implement it, it should only ever be with a fixed
1092         //        total element reference # limit and an LRU-style algorithm since JS
1093         //        has no weakref support. Caching compiled query evaluators is also
1094         //        potentially problematic, but even on large documents the size of the
1095         //        query evaluators is often < 100 function objects per evaluator (and
1096         //        LRU can be applied if it's ever shown to be an issue).
1097         //      * since IE's QSA support is currently only for HTML documents and even
1098         //        then only in IE 8's "standards mode", we have to detect our dispatch
1099         //        route at query time and keep 2 separate caches. Ugg.
1100
1101         // we need to determine if we think we can run a given query via
1102         // querySelectorAll or if we'll need to fall back on DOM queries to get
1103         // there. We need a lot of information about the environment and the query
1104         // to make the determination (e.g. does it support QSA, does the query in
1105         // question work in the native QSA impl, etc.).
1106
1107         // IE QSA queries may incorrectly include comment nodes, so we throw the
1108         // zipping function into "remove" comments mode instead of the normal "skip
1109         // it" which every other QSA-clued browser enjoys
1110         var noZip = has("ie") ? "commentStrip" : "nozip";
1111
1112         var qsa = "querySelectorAll";
1113         var qsaAvail = !!getDoc()[qsa];
1114
1115         //Don't bother with n+3 type of matches, IE complains if we modify those.
1116         var infixSpaceRe = /\\[>~+]|n\+\d|([^ \\])?([>~+])([^ =])?/g;
1117         var infixSpaceFunc = function(match, pre, ch, post){
1118                 return ch ? (pre ? pre + " " : "") + ch + (post ? " " + post : "") : /*n+3*/ match;
1119         };
1120         
1121         //Don't apply the infixSpaceRe to attribute value selectors
1122         var attRe = /([^[]*)([^\]]*])?/g;
1123         var attFunc = function(match, nonAtt, att){
1124                 return nonAtt.replace(infixSpaceRe, infixSpaceFunc) + (att||"");
1125         };
1126         var getQueryFunc = function(query, forceDOM){
1127                 //Normalize query. The CSS3 selectors spec allows for omitting spaces around
1128                 //infix operators, >, ~ and +
1129                 //Do the work here since detection for spaces is used as a simple "not use QSA"
1130                 //test below.
1131                 query = query.replace(attRe, attFunc);
1132
1133                 if(qsaAvail){
1134                         // if we've got a cached variant and we think we can do it, run it!
1135                         var qsaCached = _queryFuncCacheQSA[query];
1136                         if(qsaCached && !forceDOM){ return qsaCached; }
1137                 }
1138
1139                 // else if we've got a DOM cached variant, assume that we already know
1140                 // all we need to and use it
1141                 var domCached = _queryFuncCacheDOM[query];
1142                 if(domCached){ return domCached; }
1143
1144                 // TODO:
1145                 //              today we're caching DOM and QSA branches separately so we
1146                 //              recalc useQSA every time. If we had a way to tag root+query
1147                 //              efficiently, we'd be in good shape to do a global cache.
1148
1149                 var qcz = query.charAt(0);
1150                 var nospace = (-1 == query.indexOf(" "));
1151
1152                 // byId searches are wicked fast compared to QSA, even when filtering
1153                 // is required
1154                 if( (query.indexOf("#") >= 0) && (nospace) ){
1155                         forceDOM = true;
1156                 }
1157
1158                 var useQSA = (
1159                         qsaAvail && (!forceDOM) &&
1160                         // as per CSS 3, we can't currently start w/ combinator:
1161                         //              http://www.w3.org/TR/css3-selectors/#w3cselgrammar
1162                         (specials.indexOf(qcz) == -1) &&
1163                         // IE's QSA impl sucks on pseudos
1164                         (!has("ie") || (query.indexOf(":") == -1)) &&
1165
1166                         (!(cssCaseBug && (query.indexOf(".") >= 0))) &&
1167
1168                         // FIXME:
1169                         //              need to tighten up browser rules on ":contains" and "|=" to
1170                         //              figure out which aren't good
1171                         //              Latest webkit (around 531.21.8) does not seem to do well with :checked on option
1172                         //              elements, even though according to spec, selected options should
1173                         //              match :checked. So go nonQSA for it:
1174                         //              http://bugs.dojotoolkit.org/ticket/5179
1175                         (query.indexOf(":contains") == -1) && (query.indexOf(":checked") == -1) &&
1176                         (query.indexOf("|=") == -1) // some browsers don't grok it
1177                 );
1178
1179                 // TODO:
1180                 //              if we've got a descendant query (e.g., "> .thinger" instead of
1181                 //              just ".thinger") in a QSA-able doc, but are passed a child as a
1182                 //              root, it should be possible to give the item a synthetic ID and
1183                 //              trivially rewrite the query to the form "#synid > .thinger" to
1184                 //              use the QSA branch
1185
1186
1187                 if(useQSA){
1188                         var tq = (specials.indexOf(query.charAt(query.length-1)) >= 0) ?
1189                                                 (query + " *") : query;
1190                         return _queryFuncCacheQSA[query] = function(root){
1191                                 try{
1192                                         // the QSA system contains an egregious spec bug which
1193                                         // limits us, effectively, to only running QSA queries over
1194                                         // entire documents.  See:
1195                                         //              http://ejohn.org/blog/thoughts-on-queryselectorall/
1196                                         //      despite this, we can also handle QSA runs on simple
1197                                         //      selectors, but we don't want detection to be expensive
1198                                         //      so we're just checking for the presence of a space char
1199                                         //      right now. Not elegant, but it's cheaper than running
1200                                         //      the query parser when we might not need to
1201                                         if(!((9 == root.nodeType) || nospace)){ throw ""; }
1202                                         var r = root[qsa](tq);
1203                                         // skip expensive duplication checks and just wrap in a NodeList
1204                                         r[noZip] = true;
1205                                         return r;
1206                                 }catch(e){
1207                                         // else run the DOM branch on this query, ensuring that we
1208                                         // default that way in the future
1209                                         return getQueryFunc(query, true)(root);
1210                                 }
1211                         };
1212                 }else{
1213                         // DOM branch
1214                         var parts = query.match(/([^\s,](?:"(?:\\.|[^"])+"|'(?:\\.|[^'])+'|[^,])*)/g);
1215                         return _queryFuncCacheDOM[query] = ((parts.length < 2) ?
1216                                 // if not a compound query (e.g., ".foo, .bar"), cache and return a dispatcher
1217                                 getStepQueryFunc(query) :
1218                                 // if it *is* a complex query, break it up into its
1219                                 // constituent parts and return a dispatcher that will
1220                                 // merge the parts when run
1221                                 function(root){
1222                                         var pindex = 0, // avoid array alloc for every invocation
1223                                                 ret = [],
1224                                                 tp;
1225                                         while((tp = parts[pindex++])){
1226                                                 ret = ret.concat(getStepQueryFunc(tp)(root));
1227                                         }
1228                                         return ret;
1229                                 }
1230                         );
1231                 }
1232         };
1233
1234         var _zipIdx = 0;
1235
1236         // NOTE:
1237         //              this function is Moo inspired, but our own impl to deal correctly
1238         //              with XML in IE
1239         var _nodeUID = has("ie") ? function(node){
1240                 if(caseSensitive){
1241                         // XML docs don't have uniqueID on their nodes
1242                         return (node.getAttribute("_uid") || node.setAttribute("_uid", ++_zipIdx) || _zipIdx);
1243
1244                 }else{
1245                         return node.uniqueID;
1246                 }
1247         } :
1248         function(node){
1249                 return (node._uid || (node._uid = ++_zipIdx));
1250         };
1251
1252         // determine if a node in is unique in a "bag". In this case we don't want
1253         // to flatten a list of unique items, but rather just tell if the item in
1254         // question is already in the bag. Normally we'd just use hash lookup to do
1255         // this for us but IE's DOM is busted so we can't really count on that. On
1256         // the upside, it gives us a built in unique ID function.
1257         var _isUnique = function(node, bag){
1258                 if(!bag){ return 1; }
1259                 var id = _nodeUID(node);
1260                 if(!bag[id]){ return bag[id] = 1; }
1261                 return 0;
1262         };
1263
1264         // attempt to efficiently determine if an item in a list is a dupe,
1265         // returning a list of "uniques", hopefully in document order
1266         var _zipIdxName = "_zipIdx";
1267         var _zip = function(arr){
1268                 if(arr && arr.nozip){
1269                         return arr;
1270                 }
1271                 var ret = [];
1272                 if(!arr || !arr.length){ return ret; }
1273                 if(arr[0]){
1274                         ret.push(arr[0]);
1275                 }
1276                 if(arr.length < 2){ return ret; }
1277
1278                 _zipIdx++;
1279
1280                 // we have to fork here for IE and XML docs because we can't set
1281                 // expandos on their nodes (apparently). *sigh*
1282                 var x, te;
1283                 if(has("ie") && caseSensitive){
1284                         var szidx = _zipIdx+"";
1285                         arr[0].setAttribute(_zipIdxName, szidx);
1286                         for(x = 1; te = arr[x]; x++){
1287                                 if(arr[x].getAttribute(_zipIdxName) != szidx){
1288                                         ret.push(te);
1289                                 }
1290                                 te.setAttribute(_zipIdxName, szidx);
1291                         }
1292                 }else if(has("ie") && arr.commentStrip){
1293                         try{
1294                                 for(x = 1; te = arr[x]; x++){
1295                                         if(_isElement(te)){
1296                                                 ret.push(te);
1297                                         }
1298                                 }
1299                         }catch(e){ /* squelch */ }
1300                 }else{
1301                         if(arr[0]){ arr[0][_zipIdxName] = _zipIdx; }
1302                         for(x = 1; te = arr[x]; x++){
1303                                 if(arr[x][_zipIdxName] != _zipIdx){
1304                                         ret.push(te);
1305                                 }
1306                                 te[_zipIdxName] = _zipIdx;
1307                         }
1308                 }
1309                 return ret;
1310         };
1311
1312         // the main executor
1313         var query = function(/*String*/ query, /*String|DOMNode?*/ root){
1314                 // summary:
1315                 //              Returns nodes which match the given CSS3 selector, searching the
1316                 //              entire document by default but optionally taking a node to scope
1317                 //              the search by. Returns an array.
1318                 // description:
1319                 //              dojo.query() is the swiss army knife of DOM node manipulation in
1320                 //              Dojo. Much like Prototype's "$$" (bling-bling) function or JQuery's
1321                 //              "$" function, dojo.query provides robust, high-performance
1322                 //              CSS-based node selector support with the option of scoping searches
1323                 //              to a particular sub-tree of a document.
1324                 //
1325                 //              Supported Selectors:
1326                 //              --------------------
1327                 //
1328                 //              acme supports a rich set of CSS3 selectors, including:
1329                 //
1330                 //              - class selectors (e.g., `.foo`)
1331                 //              - node type selectors like `span`
1332                 //              - ` ` descendant selectors
1333                 //              - `>` child element selectors
1334                 //              - `#foo` style ID selectors
1335                 //              - `*` universal selector
1336                 //              - `~`, the preceded-by sibling selector
1337                 //              - `+`, the immediately preceded-by sibling selector
1338                 //              - attribute queries:
1339                 //                      - `[foo]` attribute presence selector
1340                 //                      - `[foo='bar']` attribute value exact match
1341                 //                      - `[foo~='bar']` attribute value list item match
1342                 //                      - `[foo^='bar']` attribute start match
1343                 //                      - `[foo$='bar']` attribute end match
1344                 //                      - `[foo*='bar']` attribute substring match
1345                 //              - `:first-child`, `:last-child`, and `:only-child` positional selectors
1346                 //              - `:empty` content emtpy selector
1347                 //              - `:checked` pseudo selector
1348                 //              - `:nth-child(n)`, `:nth-child(2n+1)` style positional calculations
1349                 //              - `:nth-child(even)`, `:nth-child(odd)` positional selectors
1350                 //              - `:not(...)` negation pseudo selectors
1351                 //
1352                 //              Any legal combination of these selectors will work with
1353                 //              `dojo.query()`, including compound selectors ("," delimited).
1354                 //              Very complex and useful searches can be constructed with this
1355                 //              palette of selectors and when combined with functions for
1356                 //              manipulation presented by dojo/NodeList, many types of DOM
1357                 //              manipulation operations become very straightforward.
1358                 //
1359                 //              Unsupported Selectors:
1360                 //              ----------------------
1361                 //
1362                 //              While dojo.query handles many CSS3 selectors, some fall outside of
1363                 //              what's reasonable for a programmatic node querying engine to
1364                 //              handle. Currently unsupported selectors include:
1365                 //
1366                 //              - namespace-differentiated selectors of any form
1367                 //              - all `::` pseduo-element selectors
1368                 //              - certain pseudo-selectors which don't get a lot of day-to-day use:
1369                 //                      - `:root`, `:lang()`, `:target`, `:focus`
1370                 //              - all visual and state selectors:
1371                 //                      - `:root`, `:active`, `:hover`, `:visited`, `:link`,
1372                 //                                `:enabled`, `:disabled`
1373                 //                      - `:*-of-type` pseudo selectors
1374                 //
1375                 //              dojo.query and XML Documents:
1376                 //              -----------------------------
1377                 //
1378                 //              `dojo.query` (as of dojo 1.2) supports searching XML documents
1379                 //              in a case-sensitive manner. If an HTML document is served with
1380                 //              a doctype that forces case-sensitivity (e.g., XHTML 1.1
1381                 //              Strict), dojo.query() will detect this and "do the right
1382                 //              thing". Case sensitivity is dependent upon the document being
1383                 //              searched and not the query used. It is therefore possible to
1384                 //              use case-sensitive queries on strict sub-documents (iframes,
1385                 //              etc.) or XML documents while still assuming case-insensitivity
1386                 //              for a host/root document.
1387                 //
1388                 //              Non-selector Queries:
1389                 //              ---------------------
1390                 //
1391                 //              If something other than a String is passed for the query,
1392                 //              `dojo.query` will return a new `dojo/NodeList` instance
1393                 //              constructed from that parameter alone and all further
1394                 //              processing will stop. This means that if you have a reference
1395                 //              to a node or NodeList, you can quickly construct a new NodeList
1396                 //              from the original by calling `dojo.query(node)` or
1397                 //              `dojo.query(list)`.
1398                 //
1399                 // query:
1400                 //              The CSS3 expression to match against. For details on the syntax of
1401                 //              CSS3 selectors, see <http://www.w3.org/TR/css3-selectors/#selectors>
1402                 // root:
1403                 //              A DOMNode (or node id) to scope the search from. Optional.
1404                 // returns: Array
1405                 // example:
1406                 //              search the entire document for elements with the class "foo":
1407                 //      |       dojo.query(".foo");
1408                 //              these elements will match:
1409                 //      |       <span class="foo"></span>
1410                 //      |       <span class="foo bar"></span>
1411                 //      |       <p class="thud foo"></p>
1412                 // example:
1413                 //              search the entire document for elements with the classes "foo" *and* "bar":
1414                 //      |       dojo.query(".foo.bar");
1415                 //              these elements will match:
1416                 //      |       <span class="foo bar"></span>
1417                 //              while these will not:
1418                 //      |       <span class="foo"></span>
1419                 //      |       <p class="thud foo"></p>
1420                 // example:
1421                 //              find `<span>` elements which are descendants of paragraphs and
1422                 //              which have a "highlighted" class:
1423                 //      |       dojo.query("p span.highlighted");
1424                 //              the innermost span in this fragment matches:
1425                 //      |       <p class="foo">
1426                 //      |               <span>...
1427                 //      |                       <span class="highlighted foo bar">...</span>
1428                 //      |               </span>
1429                 //      |       </p>
1430                 // example:
1431                 //              set an "odd" class on all odd table rows inside of the table
1432                 //              `#tabular_data`, using the `>` (direct child) selector to avoid
1433                 //              affecting any nested tables:
1434                 //      |       dojo.query("#tabular_data > tbody > tr:nth-child(odd)").addClass("odd");
1435                 // example:
1436                 //              remove all elements with the class "error" from the document
1437                 //              and store them in a list:
1438                 //      |       var errors = dojo.query(".error").orphan();
1439                 // example:
1440                 //              add an onclick handler to every submit button in the document
1441                 //              which causes the form to be sent via Ajax instead:
1442                 //      |       dojo.query("input[type='submit']").onclick(function(e){
1443                 //      |               dojo.stopEvent(e); // prevent sending the form
1444                 //      |               var btn = e.target;
1445                 //      |               dojo.xhrPost({
1446                 //      |                       form: btn.form,
1447                 //      |                       load: function(data){
1448                 //      |                               // replace the form with the response
1449                 //      |                               var div = dojo.doc.createElement("div");
1450                 //      |                               dojo.place(div, btn.form, "after");
1451                 //      |                               div.innerHTML = data;
1452                 //      |                               dojo.style(btn.form, "display", "none");
1453                 //      |                       }
1454                 //      |               });
1455                 //      |       });
1456
1457                 root = root || getDoc();
1458
1459                 // throw the big case sensitivity switch
1460                 var od = root.ownerDocument || root;    // root is either Document or a node inside the document
1461                 caseSensitive = (od.createElement("div").tagName === "div");
1462
1463                 // NOTE:
1464                 //              adding "true" as the 2nd argument to getQueryFunc is useful for
1465                 //              testing the DOM branch without worrying about the
1466                 //              behavior/performance of the QSA branch.
1467                 var r = getQueryFunc(query)(root);
1468
1469                 // FIXME:
1470                 //              need to investigate this branch WRT #8074 and #8075
1471                 if(r && r.nozip){
1472                         return r;
1473                 }
1474                 return _zip(r); // dojo/NodeList
1475         };
1476         query.filter = function(/*Node[]*/ nodeList, /*String*/ filter, /*String|DOMNode?*/ root){
1477                 // summary:
1478                 //              function for filtering a NodeList based on a selector, optimized for simple selectors
1479                 var tmpNodeList = [],
1480                         parts = getQueryParts(filter),
1481                         filterFunc =
1482                                 (parts.length == 1 && !/[^\w#\.]/.test(filter)) ?
1483                                 getSimpleFilterFunc(parts[0]) :
1484                                 function(node){
1485                                         return array.indexOf(query(filter, dom.byId(root)), node) != -1;
1486                                 };
1487                 for(var x = 0, te; te = nodeList[x]; x++){
1488                         if(filterFunc(te)){ tmpNodeList.push(te); }
1489                 }
1490                 return tmpNodeList;
1491         };
1492         return query;
1493 });