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