source: Dev/branches/rest-dojo-ui/client/dojo/selector/acme.js @ 263

Last change on this file since 263 was 256, checked in by hendrikvanantwerpen, 13 years ago

Reworked project structure based on REST interaction and Dojo library. As
soon as this is stable, the old jQueryUI branch can be removed (it's
kept for reference).

File size: 48.1 KB
Line 
1define(["../_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
Note: See TracBrowser for help on using the repository browser.