source: Dev/trunk/src/client/dojo/selector/acme.js @ 529

Last change on this file since 529 was 483, checked in by hendrikvanantwerpen, 11 years ago

Added Dojo 1.9.3 release.

File size: 48.4 KB
Line 
1define([
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){ return arr; }
1269
1270                if(!arr || !arr.length){ return []; }
1271                if(arr.length < 2){ return [arr[0]]; }
1272
1273                var ret = [];
1274
1275                _zipIdx++;
1276
1277                // we have to fork here for IE and XML docs because we can't set
1278                // expandos on their nodes (apparently). *sigh*
1279                var x, te;
1280                if(has("ie") && caseSensitive){
1281                        var szidx = _zipIdx+"";
1282                        for(x = 0; x < arr.length; x++){
1283                                if((te = arr[x]) && te.getAttribute(_zipIdxName) != szidx){
1284                                        ret.push(te);
1285                                        te.setAttribute(_zipIdxName, szidx);
1286                                }
1287                        }
1288                }else if(has("ie") && arr.commentStrip){
1289                        try{
1290                                for(x = 0; x < arr.length; x++){
1291                                        if((te = arr[x]) && _isElement(te)){
1292                                                ret.push(te);
1293                                        }
1294                                }
1295                        }catch(e){ /* squelch */ }
1296                }else{
1297                        for(x = 0; x < arr.length; x++){
1298                                if((te = arr[x]) && te[_zipIdxName] != _zipIdx){
1299                                        ret.push(te);
1300                                        te[_zipIdxName] = _zipIdx;
1301                                }
1302                        }
1303                }
1304                return ret;
1305        };
1306
1307        // the main executor
1308        var query = function(/*String*/ query, /*String|DOMNode?*/ root){
1309                // summary:
1310                //              Returns nodes which match the given CSS3 selector, searching the
1311                //              entire document by default but optionally taking a node to scope
1312                //              the search by. Returns an array.
1313                // description:
1314                //              dojo.query() is the swiss army knife of DOM node manipulation in
1315                //              Dojo. Much like Prototype's "$$" (bling-bling) function or JQuery's
1316                //              "$" function, dojo.query provides robust, high-performance
1317                //              CSS-based node selector support with the option of scoping searches
1318                //              to a particular sub-tree of a document.
1319                //
1320                //              Supported Selectors:
1321                //              --------------------
1322                //
1323                //              acme supports a rich set of CSS3 selectors, including:
1324                //
1325                //              - class selectors (e.g., `.foo`)
1326                //              - node type selectors like `span`
1327                //              - ` ` descendant selectors
1328                //              - `>` child element selectors
1329                //              - `#foo` style ID selectors
1330                //              - `*` universal selector
1331                //              - `~`, the preceded-by sibling selector
1332                //              - `+`, the immediately preceded-by sibling selector
1333                //              - attribute queries:
1334                //                      - `[foo]` attribute presence selector
1335                //                      - `[foo='bar']` attribute value exact match
1336                //                      - `[foo~='bar']` attribute value list item match
1337                //                      - `[foo^='bar']` attribute start match
1338                //                      - `[foo$='bar']` attribute end match
1339                //                      - `[foo*='bar']` attribute substring match
1340                //              - `:first-child`, `:last-child`, and `:only-child` positional selectors
1341                //              - `:empty` content emtpy selector
1342                //              - `:checked` pseudo selector
1343                //              - `:nth-child(n)`, `:nth-child(2n+1)` style positional calculations
1344                //              - `:nth-child(even)`, `:nth-child(odd)` positional selectors
1345                //              - `:not(...)` negation pseudo selectors
1346                //
1347                //              Any legal combination of these selectors will work with
1348                //              `dojo.query()`, including compound selectors ("," delimited).
1349                //              Very complex and useful searches can be constructed with this
1350                //              palette of selectors and when combined with functions for
1351                //              manipulation presented by dojo/NodeList, many types of DOM
1352                //              manipulation operations become very straightforward.
1353                //
1354                //              Unsupported Selectors:
1355                //              ----------------------
1356                //
1357                //              While dojo.query handles many CSS3 selectors, some fall outside of
1358                //              what's reasonable for a programmatic node querying engine to
1359                //              handle. Currently unsupported selectors include:
1360                //
1361                //              - namespace-differentiated selectors of any form
1362                //              - all `::` pseduo-element selectors
1363                //              - certain pseudo-selectors which don't get a lot of day-to-day use:
1364                //                      - `:root`, `:lang()`, `:target`, `:focus`
1365                //              - all visual and state selectors:
1366                //                      - `:root`, `:active`, `:hover`, `:visited`, `:link`,
1367                //                                `:enabled`, `:disabled`
1368                //                      - `:*-of-type` pseudo selectors
1369                //
1370                //              dojo.query and XML Documents:
1371                //              -----------------------------
1372                //
1373                //              `dojo.query` (as of dojo 1.2) supports searching XML documents
1374                //              in a case-sensitive manner. If an HTML document is served with
1375                //              a doctype that forces case-sensitivity (e.g., XHTML 1.1
1376                //              Strict), dojo.query() will detect this and "do the right
1377                //              thing". Case sensitivity is dependent upon the document being
1378                //              searched and not the query used. It is therefore possible to
1379                //              use case-sensitive queries on strict sub-documents (iframes,
1380                //              etc.) or XML documents while still assuming case-insensitivity
1381                //              for a host/root document.
1382                //
1383                //              Non-selector Queries:
1384                //              ---------------------
1385                //
1386                //              If something other than a String is passed for the query,
1387                //              `dojo.query` will return a new `dojo/NodeList` instance
1388                //              constructed from that parameter alone and all further
1389                //              processing will stop. This means that if you have a reference
1390                //              to a node or NodeList, you can quickly construct a new NodeList
1391                //              from the original by calling `dojo.query(node)` or
1392                //              `dojo.query(list)`.
1393                //
1394                // query:
1395                //              The CSS3 expression to match against. For details on the syntax of
1396                //              CSS3 selectors, see <http://www.w3.org/TR/css3-selectors/#selectors>
1397                // root:
1398                //              A DOMNode (or node id) to scope the search from. Optional.
1399                // returns: Array
1400                // example:
1401                //              search the entire document for elements with the class "foo":
1402                //      |       dojo.query(".foo");
1403                //              these elements will match:
1404                //      |       <span class="foo"></span>
1405                //      |       <span class="foo bar"></span>
1406                //      |       <p class="thud foo"></p>
1407                // example:
1408                //              search the entire document for elements with the classes "foo" *and* "bar":
1409                //      |       dojo.query(".foo.bar");
1410                //              these elements will match:
1411                //      |       <span class="foo bar"></span>
1412                //              while these will not:
1413                //      |       <span class="foo"></span>
1414                //      |       <p class="thud foo"></p>
1415                // example:
1416                //              find `<span>` elements which are descendants of paragraphs and
1417                //              which have a "highlighted" class:
1418                //      |       dojo.query("p span.highlighted");
1419                //              the innermost span in this fragment matches:
1420                //      |       <p class="foo">
1421                //      |               <span>...
1422                //      |                       <span class="highlighted foo bar">...</span>
1423                //      |               </span>
1424                //      |       </p>
1425                // example:
1426                //              set an "odd" class on all odd table rows inside of the table
1427                //              `#tabular_data`, using the `>` (direct child) selector to avoid
1428                //              affecting any nested tables:
1429                //      |       dojo.query("#tabular_data > tbody > tr:nth-child(odd)").addClass("odd");
1430                // example:
1431                //              remove all elements with the class "error" from the document
1432                //              and store them in a list:
1433                //      |       var errors = dojo.query(".error").orphan();
1434                // example:
1435                //              add an onclick handler to every submit button in the document
1436                //              which causes the form to be sent via Ajax instead:
1437                //      |       dojo.query("input[type='submit']").onclick(function(e){
1438                //      |               dojo.stopEvent(e); // prevent sending the form
1439                //      |               var btn = e.target;
1440                //      |               dojo.xhrPost({
1441                //      |                       form: btn.form,
1442                //      |                       load: function(data){
1443                //      |                               // replace the form with the response
1444                //      |                               var div = dojo.doc.createElement("div");
1445                //      |                               dojo.place(div, btn.form, "after");
1446                //      |                               div.innerHTML = data;
1447                //      |                               dojo.style(btn.form, "display", "none");
1448                //      |                       }
1449                //      |               });
1450                //      |       });
1451
1452                root = root || getDoc();
1453
1454                // throw the big case sensitivity switch
1455                var od = root.ownerDocument || root;    // root is either Document or a node inside the document
1456                caseSensitive = (od.createElement("div").tagName === "div");
1457
1458                // NOTE:
1459                //              adding "true" as the 2nd argument to getQueryFunc is useful for
1460                //              testing the DOM branch without worrying about the
1461                //              behavior/performance of the QSA branch.
1462                var r = getQueryFunc(query)(root);
1463
1464                // FIXME:
1465                //              need to investigate this branch WRT #8074 and #8075
1466                if(r && r.nozip){
1467                        return r;
1468                }
1469                return _zip(r); // dojo/NodeList
1470        };
1471        query.filter = function(/*Node[]*/ nodeList, /*String*/ filter, /*String|DOMNode?*/ root){
1472                // summary:
1473                //              function for filtering a NodeList based on a selector, optimized for simple selectors
1474                var tmpNodeList = [],
1475                        parts = getQueryParts(filter),
1476                        filterFunc =
1477                                (parts.length == 1 && !/[^\w#\.]/.test(filter)) ?
1478                                getSimpleFilterFunc(parts[0]) :
1479                                function(node){
1480                                        return array.indexOf(query(filter, dom.byId(root)), node) != -1;
1481                                };
1482                for(var x = 0, te; te = nodeList[x]; x++){
1483                        if(filterFunc(te)){ tmpNodeList.push(te); }
1484                }
1485                return tmpNodeList;
1486        };
1487        return query;
1488});
Note: See TracBrowser for help on using the repository browser.