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