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