Upgrade to ExtJS 4.0.2 - Released 06/09/2011
[extjs.git] / src / core / src / dom / DomQuery.js
1 /*
2
3 This file is part of Ext JS 4
4
5 Copyright (c) 2011 Sencha Inc
6
7 Contact:  http://www.sencha.com/contact
8
9 GNU General Public License Usage
10 This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file.  Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
11
12 If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
13
14 */
15 /*
16  * This is code is also distributed under MIT license for use
17  * with jQuery and prototype JavaScript libraries.
18  */
19 /**
20  * @class Ext.DomQuery
21 Provides high performance selector/xpath processing by compiling queries into reusable functions. New pseudo classes and matchers can be plugged. It works on HTML and XML documents (if a content node is passed in).
22 <p>
23 DomQuery supports most of the <a href="http://www.w3.org/TR/2005/WD-css3-selectors-20051215/#selectors">CSS3 selectors spec</a>, along with some custom selectors and basic XPath.</p>
24
25 <p>
26 All selectors, attribute filters and pseudos below can be combined infinitely in any order. For example "div.foo:nth-child(odd)[@foo=bar].bar:first" would be a perfectly valid selector. Node filters are processed in the order in which they appear, which allows you to optimize your queries for your document structure.
27 </p>
28 <h4>Element Selectors:</h4>
29 <ul class="list">
30     <li> <b>*</b> any element</li>
31     <li> <b>E</b> an element with the tag E</li>
32     <li> <b>E F</b> All descendent elements of E that have the tag F</li>
33     <li> <b>E > F</b> or <b>E/F</b> all direct children elements of E that have the tag F</li>
34     <li> <b>E + F</b> all elements with the tag F that are immediately preceded by an element with the tag E</li>
35     <li> <b>E ~ F</b> all elements with the tag F that are preceded by a sibling element with the tag E</li>
36 </ul>
37 <h4>Attribute Selectors:</h4>
38 <p>The use of &#64; and quotes are optional. For example, div[&#64;foo='bar'] is also a valid attribute selector.</p>
39 <ul class="list">
40     <li> <b>E[foo]</b> has an attribute "foo"</li>
41     <li> <b>E[foo=bar]</b> has an attribute "foo" that equals "bar"</li>
42     <li> <b>E[foo^=bar]</b> has an attribute "foo" that starts with "bar"</li>
43     <li> <b>E[foo$=bar]</b> has an attribute "foo" that ends with "bar"</li>
44     <li> <b>E[foo*=bar]</b> has an attribute "foo" that contains the substring "bar"</li>
45     <li> <b>E[foo%=2]</b> has an attribute "foo" that is evenly divisible by 2</li>
46     <li> <b>E[foo!=bar]</b> attribute "foo" does not equal "bar"</li>
47 </ul>
48 <h4>Pseudo Classes:</h4>
49 <ul class="list">
50     <li> <b>E:first-child</b> E is the first child of its parent</li>
51     <li> <b>E:last-child</b> E is the last child of its parent</li>
52     <li> <b>E:nth-child(<i>n</i>)</b> E is the <i>n</i>th child of its parent (1 based as per the spec)</li>
53     <li> <b>E:nth-child(odd)</b> E is an odd child of its parent</li>
54     <li> <b>E:nth-child(even)</b> E is an even child of its parent</li>
55     <li> <b>E:only-child</b> E is the only child of its parent</li>
56     <li> <b>E:checked</b> E is an element that is has a checked attribute that is true (e.g. a radio or checkbox) </li>
57     <li> <b>E:first</b> the first E in the resultset</li>
58     <li> <b>E:last</b> the last E in the resultset</li>
59     <li> <b>E:nth(<i>n</i>)</b> the <i>n</i>th E in the resultset (1 based)</li>
60     <li> <b>E:odd</b> shortcut for :nth-child(odd)</li>
61     <li> <b>E:even</b> shortcut for :nth-child(even)</li>
62     <li> <b>E:contains(foo)</b> E's innerHTML contains the substring "foo"</li>
63     <li> <b>E:nodeValue(foo)</b> E contains a textNode with a nodeValue that equals "foo"</li>
64     <li> <b>E:not(S)</b> an E element that does not match simple selector S</li>
65     <li> <b>E:has(S)</b> an E element that has a descendent that matches simple selector S</li>
66     <li> <b>E:next(S)</b> an E element whose next sibling matches simple selector S</li>
67     <li> <b>E:prev(S)</b> an E element whose previous sibling matches simple selector S</li>
68     <li> <b>E:any(S1|S2|S2)</b> an E element which matches any of the simple selectors S1, S2 or S3//\\</li>
69 </ul>
70 <h4>CSS Value Selectors:</h4>
71 <ul class="list">
72     <li> <b>E{display=none}</b> css value "display" that equals "none"</li>
73     <li> <b>E{display^=none}</b> css value "display" that starts with "none"</li>
74     <li> <b>E{display$=none}</b> css value "display" that ends with "none"</li>
75     <li> <b>E{display*=none}</b> css value "display" that contains the substring "none"</li>
76     <li> <b>E{display%=2}</b> css value "display" that is evenly divisible by 2</li>
77     <li> <b>E{display!=none}</b> css value "display" that does not equal "none"</li>
78 </ul>
79  * @singleton
80  */
81 Ext.ns('Ext.core');
82
83 Ext.core.DomQuery = Ext.DomQuery = function(){
84     var cache = {},
85         simpleCache = {},
86         valueCache = {},
87         nonSpace = /\S/,
88         trimRe = /^\s+|\s+$/g,
89         tplRe = /\{(\d+)\}/g,
90         modeRe = /^(\s?[\/>+~]\s?|\s|$)/,
91         tagTokenRe = /^(#)?([\w-\*]+)/,
92         nthRe = /(\d*)n\+?(\d*)/,
93         nthRe2 = /\D/,
94         // This is for IE MSXML which does not support expandos.
95     // IE runs the same speed using setAttribute, however FF slows way down
96     // and Safari completely fails so they need to continue to use expandos.
97     isIE = window.ActiveXObject ? true : false,
98     key = 30803;
99
100     // this eval is stop the compressor from
101     // renaming the variable to something shorter
102     eval("var batch = 30803;");
103
104     // Retrieve the child node from a particular
105     // parent at the specified index.
106     function child(parent, index){
107         var i = 0,
108             n = parent.firstChild;
109         while(n){
110             if(n.nodeType == 1){
111                if(++i == index){
112                    return n;
113                }
114             }
115             n = n.nextSibling;
116         }
117         return null;
118     }
119
120     // retrieve the next element node
121     function next(n){
122         while((n = n.nextSibling) && n.nodeType != 1);
123         return n;
124     }
125
126     // retrieve the previous element node
127     function prev(n){
128         while((n = n.previousSibling) && n.nodeType != 1);
129         return n;
130     }
131
132     // Mark each child node with a nodeIndex skipping and
133     // removing empty text nodes.
134     function children(parent){
135         var n = parent.firstChild,
136         nodeIndex = -1,
137         nextNode;
138         while(n){
139             nextNode = n.nextSibling;
140             // clean worthless empty nodes.
141             if(n.nodeType == 3 && !nonSpace.test(n.nodeValue)){
142             parent.removeChild(n);
143             }else{
144             // add an expando nodeIndex
145             n.nodeIndex = ++nodeIndex;
146             }
147             n = nextNode;
148         }
149         return this;
150     }
151
152
153     // nodeSet - array of nodes
154     // cls - CSS Class
155     function byClassName(nodeSet, cls){
156         if(!cls){
157             return nodeSet;
158         }
159         var result = [], ri = -1;
160         for(var i = 0, ci; ci = nodeSet[i]; i++){
161             if((' '+ci.className+' ').indexOf(cls) != -1){
162                 result[++ri] = ci;
163             }
164         }
165         return result;
166     };
167
168     function attrValue(n, attr){
169         // if its an array, use the first node.
170         if(!n.tagName && typeof n.length != "undefined"){
171             n = n[0];
172         }
173         if(!n){
174             return null;
175         }
176
177         if(attr == "for"){
178             return n.htmlFor;
179         }
180         if(attr == "class" || attr == "className"){
181             return n.className;
182         }
183         return n.getAttribute(attr) || n[attr];
184
185     };
186
187
188     // ns - nodes
189     // mode - false, /, >, +, ~
190     // tagName - defaults to "*"
191     function getNodes(ns, mode, tagName){
192         var result = [], ri = -1, cs;
193         if(!ns){
194             return result;
195         }
196         tagName = tagName || "*";
197         // convert to array
198         if(typeof ns.getElementsByTagName != "undefined"){
199             ns = [ns];
200         }
201
202         // no mode specified, grab all elements by tagName
203         // at any depth
204         if(!mode){
205             for(var i = 0, ni; ni = ns[i]; i++){
206                 cs = ni.getElementsByTagName(tagName);
207                 for(var j = 0, ci; ci = cs[j]; j++){
208                     result[++ri] = ci;
209                 }
210             }
211         // Direct Child mode (/ or >)
212         // E > F or E/F all direct children elements of E that have the tag
213         } else if(mode == "/" || mode == ">"){
214             var utag = tagName.toUpperCase();
215             for(var i = 0, ni, cn; ni = ns[i]; i++){
216                 cn = ni.childNodes;
217                 for(var j = 0, cj; cj = cn[j]; j++){
218                     if(cj.nodeName == utag || cj.nodeName == tagName  || tagName == '*'){
219                         result[++ri] = cj;
220                     }
221                 }
222             }
223         // Immediately Preceding mode (+)
224         // E + F all elements with the tag F that are immediately preceded by an element with the tag E
225         }else if(mode == "+"){
226             var utag = tagName.toUpperCase();
227             for(var i = 0, n; n = ns[i]; i++){
228                 while((n = n.nextSibling) && n.nodeType != 1);
229                 if(n && (n.nodeName == utag || n.nodeName == tagName || tagName == '*')){
230                     result[++ri] = n;
231                 }
232             }
233         // Sibling mode (~)
234         // E ~ F all elements with the tag F that are preceded by a sibling element with the tag E
235         }else if(mode == "~"){
236             var utag = tagName.toUpperCase();
237             for(var i = 0, n; n = ns[i]; i++){
238                 while((n = n.nextSibling)){
239                     if (n.nodeName == utag || n.nodeName == tagName || tagName == '*'){
240                         result[++ri] = n;
241                     }
242                 }
243             }
244         }
245         return result;
246     }
247
248     function concat(a, b){
249         if(b.slice){
250             return a.concat(b);
251         }
252         for(var i = 0, l = b.length; i < l; i++){
253             a[a.length] = b[i];
254         }
255         return a;
256     }
257
258     function byTag(cs, tagName){
259         if(cs.tagName || cs == document){
260             cs = [cs];
261         }
262         if(!tagName){
263             return cs;
264         }
265         var result = [], ri = -1;
266         tagName = tagName.toLowerCase();
267         for(var i = 0, ci; ci = cs[i]; i++){
268             if(ci.nodeType == 1 && ci.tagName.toLowerCase() == tagName){
269                 result[++ri] = ci;
270             }
271         }
272         return result;
273     }
274
275     function byId(cs, id){
276         if(cs.tagName || cs == document){
277             cs = [cs];
278         }
279         if(!id){
280             return cs;
281         }
282         var result = [], ri = -1;
283         for(var i = 0, ci; ci = cs[i]; i++){
284             if(ci && ci.id == id){
285                 result[++ri] = ci;
286                 return result;
287             }
288         }
289         return result;
290     }
291
292     // operators are =, !=, ^=, $=, *=, %=, |= and ~=
293     // custom can be "{"
294     function byAttribute(cs, attr, value, op, custom){
295         var result = [],
296             ri = -1,
297             useGetStyle = custom == "{",
298             fn = Ext.DomQuery.operators[op],
299             a,
300             xml,
301             hasXml;
302
303         for(var i = 0, ci; ci = cs[i]; i++){
304             // skip non-element nodes.
305             if(ci.nodeType != 1){
306                 continue;
307             }
308             // only need to do this for the first node
309             if(!hasXml){
310                 xml = Ext.DomQuery.isXml(ci);
311                 hasXml = true;
312             }
313
314             // we only need to change the property names if we're dealing with html nodes, not XML
315             if(!xml){
316                 if(useGetStyle){
317                     a = Ext.DomQuery.getStyle(ci, attr);
318                 } else if (attr == "class" || attr == "className"){
319                     a = ci.className;
320                 } else if (attr == "for"){
321                     a = ci.htmlFor;
322                 } else if (attr == "href"){
323                     // getAttribute href bug
324                     // http://www.glennjones.net/Post/809/getAttributehrefbug.htm
325                     a = ci.getAttribute("href", 2);
326                 } else{
327                     a = ci.getAttribute(attr);
328                 }
329             }else{
330                 a = ci.getAttribute(attr);
331             }
332             if((fn && fn(a, value)) || (!fn && a)){
333                 result[++ri] = ci;
334             }
335         }
336         return result;
337     }
338
339     function byPseudo(cs, name, value){
340         return Ext.DomQuery.pseudos[name](cs, value);
341     }
342
343     function nodupIEXml(cs){
344         var d = ++key,
345             r;
346         cs[0].setAttribute("_nodup", d);
347         r = [cs[0]];
348         for(var i = 1, len = cs.length; i < len; i++){
349             var c = cs[i];
350             if(!c.getAttribute("_nodup") != d){
351                 c.setAttribute("_nodup", d);
352                 r[r.length] = c;
353             }
354         }
355         for(var i = 0, len = cs.length; i < len; i++){
356             cs[i].removeAttribute("_nodup");
357         }
358         return r;
359     }
360
361     function nodup(cs){
362         if(!cs){
363             return [];
364         }
365         var len = cs.length, c, i, r = cs, cj, ri = -1;
366         if(!len || typeof cs.nodeType != "undefined" || len == 1){
367             return cs;
368         }
369         if(isIE && typeof cs[0].selectSingleNode != "undefined"){
370             return nodupIEXml(cs);
371         }
372         var d = ++key;
373         cs[0]._nodup = d;
374         for(i = 1; c = cs[i]; i++){
375             if(c._nodup != d){
376                 c._nodup = d;
377             }else{
378                 r = [];
379                 for(var j = 0; j < i; j++){
380                     r[++ri] = cs[j];
381                 }
382                 for(j = i+1; cj = cs[j]; j++){
383                     if(cj._nodup != d){
384                         cj._nodup = d;
385                         r[++ri] = cj;
386                     }
387                 }
388                 return r;
389             }
390         }
391         return r;
392     }
393
394     function quickDiffIEXml(c1, c2){
395         var d = ++key,
396             r = [];
397         for(var i = 0, len = c1.length; i < len; i++){
398             c1[i].setAttribute("_qdiff", d);
399         }
400         for(var i = 0, len = c2.length; i < len; i++){
401             if(c2[i].getAttribute("_qdiff") != d){
402                 r[r.length] = c2[i];
403             }
404         }
405         for(var i = 0, len = c1.length; i < len; i++){
406            c1[i].removeAttribute("_qdiff");
407         }
408         return r;
409     }
410
411     function quickDiff(c1, c2){
412         var len1 = c1.length,
413             d = ++key,
414             r = [];
415         if(!len1){
416             return c2;
417         }
418         if(isIE && typeof c1[0].selectSingleNode != "undefined"){
419             return quickDiffIEXml(c1, c2);
420         }
421         for(var i = 0; i < len1; i++){
422             c1[i]._qdiff = d;
423         }
424         for(var i = 0, len = c2.length; i < len; i++){
425             if(c2[i]._qdiff != d){
426                 r[r.length] = c2[i];
427             }
428         }
429         return r;
430     }
431
432     function quickId(ns, mode, root, id){
433         if(ns == root){
434            var d = root.ownerDocument || root;
435            return d.getElementById(id);
436         }
437         ns = getNodes(ns, mode, "*");
438         return byId(ns, id);
439     }
440
441     return {
442         getStyle : function(el, name){
443             return Ext.fly(el).getStyle(name);
444         },
445         /**
446          * Compiles a selector/xpath query into a reusable function. The returned function
447          * takes one parameter "root" (optional), which is the context node from where the query should start.
448          * @param {String} selector The selector/xpath query
449          * @param {String} type (optional) Either "select" (the default) or "simple" for a simple selector match
450          * @return {Function}
451          */
452         compile : function(path, type){
453             type = type || "select";
454
455             // setup fn preamble
456             var fn = ["var f = function(root){\n var mode; ++batch; var n = root || document;\n"],
457                 mode,
458                 lastPath,
459                 matchers = Ext.DomQuery.matchers,
460                 matchersLn = matchers.length,
461                 modeMatch,
462                 // accept leading mode switch
463                 lmode = path.match(modeRe);
464
465             if(lmode && lmode[1]){
466                 fn[fn.length] = 'mode="'+lmode[1].replace(trimRe, "")+'";';
467                 path = path.replace(lmode[1], "");
468             }
469
470             // strip leading slashes
471             while(path.substr(0, 1)=="/"){
472                 path = path.substr(1);
473             }
474
475             while(path && lastPath != path){
476                 lastPath = path;
477                 var tokenMatch = path.match(tagTokenRe);
478                 if(type == "select"){
479                     if(tokenMatch){
480                         // ID Selector
481                         if(tokenMatch[1] == "#"){
482                             fn[fn.length] = 'n = quickId(n, mode, root, "'+tokenMatch[2]+'");';
483                         }else{
484                             fn[fn.length] = 'n = getNodes(n, mode, "'+tokenMatch[2]+'");';
485                         }
486                         path = path.replace(tokenMatch[0], "");
487                     }else if(path.substr(0, 1) != '@'){
488                         fn[fn.length] = 'n = getNodes(n, mode, "*");';
489                     }
490                 // type of "simple"
491                 }else{
492                     if(tokenMatch){
493                         if(tokenMatch[1] == "#"){
494                             fn[fn.length] = 'n = byId(n, "'+tokenMatch[2]+'");';
495                         }else{
496                             fn[fn.length] = 'n = byTag(n, "'+tokenMatch[2]+'");';
497                         }
498                         path = path.replace(tokenMatch[0], "");
499                     }
500                 }
501                 while(!(modeMatch = path.match(modeRe))){
502                     var matched = false;
503                     for(var j = 0; j < matchersLn; j++){
504                         var t = matchers[j];
505                         var m = path.match(t.re);
506                         if(m){
507                             fn[fn.length] = t.select.replace(tplRe, function(x, i){
508                                 return m[i];
509                             });
510                             path = path.replace(m[0], "");
511                             matched = true;
512                             break;
513                         }
514                     }
515                     // prevent infinite loop on bad selector
516                     if(!matched){
517                         //<debug>
518                         Ext.Error.raise({
519                             sourceClass: 'Ext.DomQuery',
520                             sourceMethod: 'compile',
521                             msg: 'Error parsing selector. Parsing failed at "' + path + '"'
522                         });
523                         //</debug>
524                     }
525                 }
526                 if(modeMatch[1]){
527                     fn[fn.length] = 'mode="'+modeMatch[1].replace(trimRe, "")+'";';
528                     path = path.replace(modeMatch[1], "");
529                 }
530             }
531             // close fn out
532             fn[fn.length] = "return nodup(n);\n}";
533
534             // eval fn and return it
535             eval(fn.join(""));
536             return f;
537         },
538
539         /**
540          * Selects an array of DOM nodes using JavaScript-only implementation.
541          *
542          * Use {@link #select} to take advantage of browsers built-in support for CSS selectors.
543          *
544          * @param {String} selector The selector/xpath query (can be a comma separated list of selectors)
545          * @param {Node/String} root (optional) The start of the query (defaults to document).
546          * @return {Array} An Array of DOM elements which match the selector. If there are
547          * no matches, and empty Array is returned.
548          */
549         jsSelect: function(path, root, type){
550             // set root to doc if not specified.
551             root = root || document;
552
553             if(typeof root == "string"){
554                 root = document.getElementById(root);
555             }
556             var paths = path.split(","),
557                 results = [];
558
559             // loop over each selector
560             for(var i = 0, len = paths.length; i < len; i++){
561                 var subPath = paths[i].replace(trimRe, "");
562                 // compile and place in cache
563                 if(!cache[subPath]){
564                     cache[subPath] = Ext.DomQuery.compile(subPath);
565                     if(!cache[subPath]){
566                         //<debug>
567                         Ext.Error.raise({
568                             sourceClass: 'Ext.DomQuery',
569                             sourceMethod: 'jsSelect',
570                             msg: subPath + ' is not a valid selector'
571                         });
572                         //</debug>
573                     }
574                 }
575                 var result = cache[subPath](root);
576                 if(result && result != document){
577                     results = results.concat(result);
578                 }
579             }
580
581             // if there were multiple selectors, make sure dups
582             // are eliminated
583             if(paths.length > 1){
584                 return nodup(results);
585             }
586             return results;
587         },
588
589         isXml: function(el) {
590             var docEl = (el ? el.ownerDocument || el : 0).documentElement;
591             return docEl ? docEl.nodeName !== "HTML" : false;
592         },
593
594         /**
595          * Selects an array of DOM nodes by CSS/XPath selector.
596          *
597          * Uses [document.querySelectorAll][0] if browser supports that, otherwise falls back to
598          * {@link #jsSelect} to do the work.
599          * 
600          * Aliased as {@link Ext#query}.
601          * 
602          * [0]: https://developer.mozilla.org/en/DOM/document.querySelectorAll
603          *
604          * @param {String} path The selector/xpath query
605          * @param {Node} root (optional) The start of the query (defaults to document).
606          * @return {Array} An array of DOM elements (not a NodeList as returned by `querySelectorAll`).
607          * Empty array when no matches.
608          * @method
609          */
610         select : document.querySelectorAll ? function(path, root, type) {
611             root = root || document;
612             if (!Ext.DomQuery.isXml(root)) {
613             try {
614                 var cs = root.querySelectorAll(path);
615                 return Ext.Array.toArray(cs);
616             }
617             catch (ex) {}
618             }
619             return Ext.DomQuery.jsSelect.call(this, path, root, type);
620         } : function(path, root, type) {
621             return Ext.DomQuery.jsSelect.call(this, path, root, type);
622         },
623
624         /**
625          * Selects a single element.
626          * @param {String} selector The selector/xpath query
627          * @param {Node} root (optional) The start of the query (defaults to document).
628          * @return {Element} The DOM element which matched the selector.
629          */
630         selectNode : function(path, root){
631             return Ext.DomQuery.select(path, root)[0];
632         },
633
634         /**
635          * Selects the value of a node, optionally replacing null with the defaultValue.
636          * @param {String} selector The selector/xpath query
637          * @param {Node} root (optional) The start of the query (defaults to document).
638          * @param {String} defaultValue
639          * @return {String}
640          */
641         selectValue : function(path, root, defaultValue){
642             path = path.replace(trimRe, "");
643             if(!valueCache[path]){
644                 valueCache[path] = Ext.DomQuery.compile(path, "select");
645             }
646             var n = valueCache[path](root), v;
647             n = n[0] ? n[0] : n;
648
649             // overcome a limitation of maximum textnode size
650             // Rumored to potentially crash IE6 but has not been confirmed.
651             // http://reference.sitepoint.com/javascript/Node/normalize
652             // https://developer.mozilla.org/En/DOM/Node.normalize
653             if (typeof n.normalize == 'function') n.normalize();
654
655             v = (n && n.firstChild ? n.firstChild.nodeValue : null);
656             return ((v === null||v === undefined||v==='') ? defaultValue : v);
657         },
658
659         /**
660          * Selects the value of a node, parsing integers and floats. Returns the defaultValue, or 0 if none is specified.
661          * @param {String} selector The selector/xpath query
662          * @param {Node} root (optional) The start of the query (defaults to document).
663          * @param {Number} defaultValue
664          * @return {Number}
665          */
666         selectNumber : function(path, root, defaultValue){
667             var v = Ext.DomQuery.selectValue(path, root, defaultValue || 0);
668             return parseFloat(v);
669         },
670
671         /**
672          * Returns true if the passed element(s) match the passed simple selector (e.g. div.some-class or span:first-child)
673          * @param {String/HTMLElement/Array} el An element id, element or array of elements
674          * @param {String} selector The simple selector to test
675          * @return {Boolean}
676          */
677         is : function(el, ss){
678             if(typeof el == "string"){
679                 el = document.getElementById(el);
680             }
681             var isArray = Ext.isArray(el),
682                 result = Ext.DomQuery.filter(isArray ? el : [el], ss);
683             return isArray ? (result.length == el.length) : (result.length > 0);
684         },
685
686         /**
687          * Filters an array of elements to only include matches of a simple selector (e.g. div.some-class or span:first-child)
688          * @param {Array} el An array of elements to filter
689          * @param {String} selector The simple selector to test
690          * @param {Boolean} nonMatches If true, it returns the elements that DON'T match
691          * the selector instead of the ones that match
692          * @return {Array} An Array of DOM elements which match the selector. If there are
693          * no matches, and empty Array is returned.
694          */
695         filter : function(els, ss, nonMatches){
696             ss = ss.replace(trimRe, "");
697             if(!simpleCache[ss]){
698                 simpleCache[ss] = Ext.DomQuery.compile(ss, "simple");
699             }
700             var result = simpleCache[ss](els);
701             return nonMatches ? quickDiff(result, els) : result;
702         },
703
704         /**
705          * Collection of matching regular expressions and code snippets.
706          * Each capture group within () will be replace the {} in the select
707          * statement as specified by their index.
708          */
709         matchers : [{
710                 re: /^\.([\w-]+)/,
711                 select: 'n = byClassName(n, " {1} ");'
712             }, {
713                 re: /^\:([\w-]+)(?:\(((?:[^\s>\/]*|.*?))\))?/,
714                 select: 'n = byPseudo(n, "{1}", "{2}");'
715             },{
716                 re: /^(?:([\[\{])(?:@)?([\w-]+)\s?(?:(=|.=)\s?['"]?(.*?)["']?)?[\]\}])/,
717                 select: 'n = byAttribute(n, "{2}", "{4}", "{3}", "{1}");'
718             }, {
719                 re: /^#([\w-]+)/,
720                 select: 'n = byId(n, "{1}");'
721             },{
722                 re: /^@([\w-]+)/,
723                 select: 'return {firstChild:{nodeValue:attrValue(n, "{1}")}};'
724             }
725         ],
726
727         /**
728          * Collection of operator comparison functions. The default operators are =, !=, ^=, $=, *=, %=, |= and ~=.
729          * New operators can be added as long as the match the format <i>c</i>= where <i>c</i> is any character other than space, &gt; &lt;.
730          */
731         operators : {
732             "=" : function(a, v){
733                 return a == v;
734             },
735             "!=" : function(a, v){
736                 return a != v;
737             },
738             "^=" : function(a, v){
739                 return a && a.substr(0, v.length) == v;
740             },
741             "$=" : function(a, v){
742                 return a && a.substr(a.length-v.length) == v;
743             },
744             "*=" : function(a, v){
745                 return a && a.indexOf(v) !== -1;
746             },
747             "%=" : function(a, v){
748                 return (a % v) == 0;
749             },
750             "|=" : function(a, v){
751                 return a && (a == v || a.substr(0, v.length+1) == v+'-');
752             },
753             "~=" : function(a, v){
754                 return a && (' '+a+' ').indexOf(' '+v+' ') != -1;
755             }
756         },
757
758         /**
759 Object hash of "pseudo class" filter functions which are used when filtering selections. 
760 Each function is passed two parameters:
761
762 - **c** : Array
763     An Array of DOM elements to filter.
764     
765 - **v** : String
766     The argument (if any) supplied in the selector.
767
768 A filter function returns an Array of DOM elements which conform to the pseudo class.
769 In addition to the provided pseudo classes listed above such as `first-child` and `nth-child`,
770 developers may add additional, custom psuedo class filters to select elements according to application-specific requirements.
771
772 For example, to filter `a` elements to only return links to __external__ resources:
773
774     Ext.DomQuery.pseudos.external = function(c, v){
775         var r = [], ri = -1;
776         for(var i = 0, ci; ci = c[i]; i++){
777             // Include in result set only if it's a link to an external resource
778             if(ci.hostname != location.hostname){
779                 r[++ri] = ci;
780             }
781         }
782         return r;
783     };
784
785 Then external links could be gathered with the following statement:
786
787     var externalLinks = Ext.select("a:external");
788
789         * @markdown
790         */
791         pseudos : {
792             "first-child" : function(c){
793                 var r = [], ri = -1, n;
794                 for(var i = 0, ci; ci = n = c[i]; i++){
795                     while((n = n.previousSibling) && n.nodeType != 1);
796                     if(!n){
797                         r[++ri] = ci;
798                     }
799                 }
800                 return r;
801             },
802
803             "last-child" : function(c){
804                 var r = [], ri = -1, n;
805                 for(var i = 0, ci; ci = n = c[i]; i++){
806                     while((n = n.nextSibling) && n.nodeType != 1);
807                     if(!n){
808                         r[++ri] = ci;
809                     }
810                 }
811                 return r;
812             },
813
814             "nth-child" : function(c, a) {
815                 var r = [], ri = -1,
816                     m = nthRe.exec(a == "even" && "2n" || a == "odd" && "2n+1" || !nthRe2.test(a) && "n+" + a || a),
817                     f = (m[1] || 1) - 0, l = m[2] - 0;
818                 for(var i = 0, n; n = c[i]; i++){
819                     var pn = n.parentNode;
820                     if (batch != pn._batch) {
821                         var j = 0;
822                         for(var cn = pn.firstChild; cn; cn = cn.nextSibling){
823                             if(cn.nodeType == 1){
824                                cn.nodeIndex = ++j;
825                             }
826                         }
827                         pn._batch = batch;
828                     }
829                     if (f == 1) {
830                         if (l == 0 || n.nodeIndex == l){
831                             r[++ri] = n;
832                         }
833                     } else if ((n.nodeIndex + l) % f == 0){
834                         r[++ri] = n;
835                     }
836                 }
837
838                 return r;
839             },
840
841             "only-child" : function(c){
842                 var r = [], ri = -1;;
843                 for(var i = 0, ci; ci = c[i]; i++){
844                     if(!prev(ci) && !next(ci)){
845                         r[++ri] = ci;
846                     }
847                 }
848                 return r;
849             },
850
851             "empty" : function(c){
852                 var r = [], ri = -1;
853                 for(var i = 0, ci; ci = c[i]; i++){
854                     var cns = ci.childNodes, j = 0, cn, empty = true;
855                     while(cn = cns[j]){
856                         ++j;
857                         if(cn.nodeType == 1 || cn.nodeType == 3){
858                             empty = false;
859                             break;
860                         }
861                     }
862                     if(empty){
863                         r[++ri] = ci;
864                     }
865                 }
866                 return r;
867             },
868
869             "contains" : function(c, v){
870                 var r = [], ri = -1;
871                 for(var i = 0, ci; ci = c[i]; i++){
872                     if((ci.textContent||ci.innerText||'').indexOf(v) != -1){
873                         r[++ri] = ci;
874                     }
875                 }
876                 return r;
877             },
878
879             "nodeValue" : function(c, v){
880                 var r = [], ri = -1;
881                 for(var i = 0, ci; ci = c[i]; i++){
882                     if(ci.firstChild && ci.firstChild.nodeValue == v){
883                         r[++ri] = ci;
884                     }
885                 }
886                 return r;
887             },
888
889             "checked" : function(c){
890                 var r = [], ri = -1;
891                 for(var i = 0, ci; ci = c[i]; i++){
892                     if(ci.checked == true){
893                         r[++ri] = ci;
894                     }
895                 }
896                 return r;
897             },
898
899             "not" : function(c, ss){
900                 return Ext.DomQuery.filter(c, ss, true);
901             },
902
903             "any" : function(c, selectors){
904                 var ss = selectors.split('|'),
905                     r = [], ri = -1, s;
906                 for(var i = 0, ci; ci = c[i]; i++){
907                     for(var j = 0; s = ss[j]; j++){
908                         if(Ext.DomQuery.is(ci, s)){
909                             r[++ri] = ci;
910                             break;
911                         }
912                     }
913                 }
914                 return r;
915             },
916
917             "odd" : function(c){
918                 return this["nth-child"](c, "odd");
919             },
920
921             "even" : function(c){
922                 return this["nth-child"](c, "even");
923             },
924
925             "nth" : function(c, a){
926                 return c[a-1] || [];
927             },
928
929             "first" : function(c){
930                 return c[0] || [];
931             },
932
933             "last" : function(c){
934                 return c[c.length-1] || [];
935             },
936
937             "has" : function(c, ss){
938                 var s = Ext.DomQuery.select,
939                     r = [], ri = -1;
940                 for(var i = 0, ci; ci = c[i]; i++){
941                     if(s(ss, ci).length > 0){
942                         r[++ri] = ci;
943                     }
944                 }
945                 return r;
946             },
947
948             "next" : function(c, ss){
949                 var is = Ext.DomQuery.is,
950                     r = [], ri = -1;
951                 for(var i = 0, ci; ci = c[i]; i++){
952                     var n = next(ci);
953                     if(n && is(n, ss)){
954                         r[++ri] = ci;
955                     }
956                 }
957                 return r;
958             },
959
960             "prev" : function(c, ss){
961                 var is = Ext.DomQuery.is,
962                     r = [], ri = -1;
963                 for(var i = 0, ci; ci = c[i]; i++){
964                     var n = prev(ci);
965                     if(n && is(n, ss)){
966                         r[++ri] = ci;
967                     }
968                 }
969                 return r;
970             }
971         }
972     };
973 }();
974
975 /**
976  * Selects an array of DOM nodes by CSS/XPath selector. Shorthand of {@link Ext.DomQuery#select}
977  * @param {String} path The selector/xpath query
978  * @param {Node} root (optional) The start of the query (defaults to document).
979  * @return {Array}
980  * @member Ext
981  * @method query
982  */
983 Ext.query = Ext.DomQuery.select;
984