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