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