]> CyberLeo.Net >> Repos - Github/sugarcrm.git/blob - include/javascript/yui/build/selector/selector.js
Release 6.5.0
[Github/sugarcrm.git] / include / javascript / yui / build / selector / selector.js
1 /*
2 Copyright (c) 2011, Yahoo! Inc. All rights reserved.
3 Code licensed under the BSD License:
4 http://developer.yahoo.com/yui/license.html
5 version: 2.9.0
6 */
7 var Y = YAHOO,
8     Y_DOM = YAHOO.util.Dom, 
9     EMPTY_ARRAY = [],
10     Y_UA = Y.env.ua,
11     Y_Lang = Y.lang,
12     Y_DOC = document,
13     Y_DOCUMENT_ELEMENT = Y_DOC.documentElement,
14
15     Y_DOM_inDoc = Y_DOM.inDocument, 
16     Y_mix = Y_Lang.augmentObject,
17     Y_guid = Y_DOM.generateId,
18
19     Y_getDoc = function(element) {
20         var doc = Y_DOC;
21         if (element) {
22             doc = (element.nodeType === 9) ? element : // element === document
23                 element.ownerDocument || // element === DOM node
24                 element.document || // element === window
25                 Y_DOC; // default
26         }
27
28         return doc;
29     },
30     
31     Y_Array = function(o, startIdx) {
32         var l, a, start = startIdx || 0;
33
34         // IE errors when trying to slice HTMLElement collections
35         try {
36             return Array.prototype.slice.call(o, start);
37         } catch (e) {
38             a = [];
39             l = o.length;
40             for (; start < l; start++) {
41                 a.push(o[start]);
42             }
43             return a;
44         }
45     },
46
47     Y_DOM_allById = function(id, root) {
48         root = root || Y_DOC;
49         var nodes = [],
50             ret = [],
51             i,
52             node;
53
54         if (root.querySelectorAll) {
55             ret = root.querySelectorAll('[id="' + id + '"]');
56         } else if (root.all) {
57             nodes = root.all(id);
58
59             if (nodes) {
60                 // root.all may return HTMLElement or HTMLCollection.
61                 // some elements are also HTMLCollection (FORM, SELECT).
62                 if (nodes.nodeName) {
63                     if (nodes.id === id) { // avoid false positive on name
64                         ret.push(nodes);
65                         nodes = EMPTY_ARRAY; // done, no need to filter
66                     } else { //  prep for filtering
67                         nodes = [nodes];
68                     }
69                 }
70
71                 if (nodes.length) {
72                     // filter out matches on node.name
73                     // and element.id as reference to element with id === 'id'
74                     for (i = 0; node = nodes[i++];) {
75                         if (node.id === id  || 
76                                 (node.attributes && node.attributes.id &&
77                                 node.attributes.id.value === id)) { 
78                             ret.push(node);
79                         }
80                     }
81                 }
82             }
83         } else {
84             ret = [Y_getDoc(root).getElementById(id)];
85         }
86
87         return ret;
88     };
89
90 /**
91  * The selector-native module provides support for native querySelector
92  * @module dom
93  * @submodule selector-native
94  * @for Selector
95  */
96
97 /**
98  * Provides support for using CSS selectors to query the DOM 
99  * @class Selector 
100  * @static
101  * @for Selector
102  */
103
104 var COMPARE_DOCUMENT_POSITION = 'compareDocumentPosition',
105     OWNER_DOCUMENT = 'ownerDocument',
106
107 Selector = {
108     _foundCache: [],
109
110     useNative: true,
111
112     _compare: ('sourceIndex' in Y_DOCUMENT_ELEMENT) ?
113         function(nodeA, nodeB) {
114             var a = nodeA.sourceIndex,
115                 b = nodeB.sourceIndex;
116
117             if (a === b) {
118                 return 0;
119             } else if (a > b) {
120                 return 1;
121             }
122
123             return -1;
124
125         } : (Y_DOCUMENT_ELEMENT[COMPARE_DOCUMENT_POSITION] ?
126         function(nodeA, nodeB) {
127             if (nodeA[COMPARE_DOCUMENT_POSITION](nodeB) & 4) {
128                 return -1;
129             } else {
130                 return 1;
131             }
132         } :
133         function(nodeA, nodeB) {
134             var rangeA, rangeB, compare;
135             if (nodeA && nodeB) {
136                 rangeA = nodeA[OWNER_DOCUMENT].createRange();
137                 rangeA.setStart(nodeA, 0);
138                 rangeB = nodeB[OWNER_DOCUMENT].createRange();
139                 rangeB.setStart(nodeB, 0);
140                 compare = rangeA.compareBoundaryPoints(1, rangeB); // 1 === Range.START_TO_END
141             }
142
143             return compare;
144         
145     }),
146
147     _sort: function(nodes) {
148         if (nodes) {
149             nodes = Y_Array(nodes, 0, true);
150             if (nodes.sort) {
151                 nodes.sort(Selector._compare);
152             }
153         }
154
155         return nodes;
156     },
157
158     _deDupe: function(nodes) {
159         var ret = [],
160             i, node;
161
162         for (i = 0; (node = nodes[i++]);) {
163             if (!node._found) {
164                 ret[ret.length] = node;
165                 node._found = true;
166             }
167         }
168
169         for (i = 0; (node = ret[i++]);) {
170             node._found = null;
171             node.removeAttribute('_found');
172         }
173
174         return ret;
175     },
176
177     /**
178      * Retrieves a set of nodes based on a given CSS selector. 
179      * @method query
180      *
181      * @param {string} selector The CSS Selector to test the node against.
182      * @param {HTMLElement} root optional An HTMLElement to start the query from. Defaults to Y.config.doc
183      * @param {Boolean} firstOnly optional Whether or not to return only the first match.
184      * @return {Array} An array of nodes that match the given selector.
185      * @static
186      */
187     query: function(selector, root, firstOnly, skipNative) {
188         if (root && typeof root == 'string') {
189             root = Y_DOM.get(root);
190             if (!root) {
191                 return (firstOnly) ? null : [];
192             }
193         } else {
194             root = root || Y_DOC;
195         }
196
197         var ret = [],
198             useNative = (Selector.useNative && Y_DOC.querySelector && !skipNative),
199             queries = [[selector, root]],
200             query,
201             result,
202             i,
203             fn = (useNative) ? Selector._nativeQuery : Selector._bruteQuery;
204
205         if (selector && fn) {
206             // split group into seperate queries
207             if (!skipNative && // already done if skipping
208                     (!useNative || root.tagName)) { // split native when element scoping is needed
209                 queries = Selector._splitQueries(selector, root);
210             }
211
212             for (i = 0; (query = queries[i++]);) {
213                 result = fn(query[0], query[1], firstOnly);
214                 if (!firstOnly) { // coerce DOM Collection to Array
215                     result = Y_Array(result, 0, true);
216                 }
217                 if (result) {
218                     ret = ret.concat(result);
219                 }
220             }
221
222             if (queries.length > 1) { // remove dupes and sort by doc order 
223                 ret = Selector._sort(Selector._deDupe(ret));
224             }
225         }
226
227         return (firstOnly) ? (ret[0] || null) : ret;
228
229     },
230
231     // allows element scoped queries to begin with combinator
232     // e.g. query('> p', document.body) === query('body > p')
233     _splitQueries: function(selector, node) {
234         var groups = selector.split(','),
235             queries = [],
236             prefix = '',
237             i, len;
238
239         if (node) {
240             // enforce for element scoping
241             if (node.tagName) {
242                 node.id = node.id || Y_guid();
243                 prefix = '[id="' + node.id + '"] ';
244             }
245
246             for (i = 0, len = groups.length; i < len; ++i) {
247                 selector =  prefix + groups[i];
248                 queries.push([selector, node]);
249             }
250         }
251
252         return queries;
253     },
254
255     _nativeQuery: function(selector, root, one) {
256         if (Y_UA.webkit && selector.indexOf(':checked') > -1 &&
257                 (Selector.pseudos && Selector.pseudos.checked)) { // webkit (chrome, safari) fails to find "selected"
258             return Selector.query(selector, root, one, true); // redo with skipNative true to try brute query
259         }
260         try {
261             return root['querySelector' + (one ? '' : 'All')](selector);
262         } catch(e) { // fallback to brute if available
263             return Selector.query(selector, root, one, true); // redo with skipNative true
264         }
265     },
266
267     filter: function(nodes, selector) {
268         var ret = [],
269             i, node;
270
271         if (nodes && selector) {
272             for (i = 0; (node = nodes[i++]);) {
273                 if (Selector.test(node, selector)) {
274                     ret[ret.length] = node;
275                 }
276             }
277         } else {
278         }
279
280         return ret;
281     },
282
283     test: function(node, selector, root) {
284         var ret = false,
285             groups = selector.split(','),
286             useFrag = false,
287             parent,
288             item,
289             items,
290             frag,
291             i, j, group;
292
293         if (node && node.tagName) { // only test HTMLElements
294
295             // we need a root if off-doc
296             if (!root && !Y_DOM_inDoc(node)) {
297                 parent = node.parentNode;
298                 if (parent) { 
299                     root = parent;
300                 } else { // only use frag when no parent to query
301                     frag = node[OWNER_DOCUMENT].createDocumentFragment();
302                     frag.appendChild(node);
303                     root = frag;
304                     useFrag = true;
305                 }
306             }
307             root = root || node[OWNER_DOCUMENT];
308
309             if (!node.id) {
310                 node.id = Y_guid();
311             }
312             for (i = 0; (group = groups[i++]);) { // TODO: off-dom test
313                 group += '[id="' + node.id + '"]';
314                 items = Selector.query(group, root);
315
316                 for (j = 0; item = items[j++];) {
317                     if (item === node) {
318                         ret = true;
319                         break;
320                     }
321                 }
322                 if (ret) {
323                     break;
324                 }
325             }
326
327             if (useFrag) { // cleanup
328                 frag.removeChild(node);
329             }
330         }
331
332         return ret;
333     }
334
335 };
336
337 YAHOO.util.Selector = Selector;
338 /**
339  * The selector module provides helper methods allowing CSS2 Selectors to be used with DOM elements.
340  * @module dom
341  * @submodule selector-css2
342  * @for Selector
343  */
344
345 /**
346  * Provides helper methods for collecting and filtering DOM elements.
347  */
348
349 var PARENT_NODE = 'parentNode',
350     TAG_NAME = 'tagName',
351     ATTRIBUTES = 'attributes',
352     COMBINATOR = 'combinator',
353     PSEUDOS = 'pseudos',
354
355     SelectorCSS2 = {
356         _reRegExpTokens: /([\^\$\?\[\]\*\+\-\.\(\)\|\\])/, // TODO: move?
357         SORT_RESULTS: true,
358         _children: function(node, tag) {
359             var ret = node.children,
360                 i,
361                 children = [],
362                 childNodes,
363                 child;
364
365             if (node.children && tag && node.children.tags) {
366                 children = node.children.tags(tag);
367             } else if ((!ret && node[TAG_NAME]) || (ret && tag)) { // only HTMLElements have children
368                 childNodes = ret || node.childNodes;
369                 ret = [];
370                 for (i = 0; (child = childNodes[i++]);) {
371                     if (child.tagName) {
372                         if (!tag || tag === child.tagName) {
373                             ret.push(child);
374                         }
375                     }
376                 }
377             }
378
379             return ret || [];
380         },
381
382         _re: {
383             //attr: /(\[.*\])/g,
384             attr: /(\[[^\]]*\])/g,
385             //esc: /\\[:\[][\w\d\]]*/gi,
386             esc: /\\[:\[\]\(\)#\.\'\>+~"]/gi,
387             //pseudos: /:([\-\w]+(?:\(?:['"]?(.+)['"]?\))*)/i
388             pseudos: /(\([^\)]*\))/g
389         },
390
391         /**
392          * Mapping of shorthand tokens to corresponding attribute selector 
393          * @property shorthand
394          * @type object
395          */
396         shorthand: {
397             //'\\#([^\\s\\\\(\\[:]*)': '[id=$1]',
398             '\\#(-?[_a-z]+[-\\w\\uE000]*)': '[id=$1]',
399             //'\\#([^\\s\\\.:\\[\\]]*)': '[id=$1]',
400             //'\\.([^\\s\\\\(\\[:]*)': '[className=$1]'
401             '\\.(-?[_a-z]+[-\\w\\uE000]*)': '[className~=$1]'
402         },
403
404         /**
405          * List of operators and corresponding boolean functions. 
406          * These functions are passed the attribute and the current node's value of the attribute.
407          * @property operators
408          * @type object
409          */
410         operators: {
411             '': function(node, attr) { return !!node.getAttribute(attr); }, // Just test for existence of attribute
412             //'': '.+',
413             //'=': '^{val}$', // equality
414             '~=': '(?:^|\\s+){val}(?:\\s+|$)', // space-delimited
415             '|=': '^{val}(?:-|$)' // optional hyphen-delimited
416         },
417
418         pseudos: {
419            'first-child': function(node) { 
420                 return Selector._children(node[PARENT_NODE])[0] === node; 
421             } 
422         },
423
424         _bruteQuery: function(selector, root, firstOnly) {
425             var ret = [],
426                 nodes = [],
427                 tokens = Selector._tokenize(selector),
428                 token = tokens[tokens.length - 1],
429                 rootDoc = Y_getDoc(root),
430                 child,
431                 id,
432                 className,
433                 tagName;
434
435
436             // if we have an initial ID, set to root when in document
437             /*
438             if (tokens[0] && rootDoc === root &&  
439                     (id = tokens[0].id) &&
440                     rootDoc.getElementById(id)) {
441                 root = rootDoc.getElementById(id);
442             }
443             */
444
445             if (token) {
446                 // prefilter nodes
447                 id = token.id;
448                 className = token.className;
449                 tagName = token.tagName || '*';
450
451                 if (root.getElementsByTagName) { // non-IE lacks DOM api on doc frags
452                     // try ID first, unless no root.all && root not in document
453                     // (root.all works off document, but not getElementById)
454                     // TODO: move to allById?
455                     if (id && (root.all || (root.nodeType === 9 || Y_DOM_inDoc(root)))) {
456                         nodes = Y_DOM_allById(id, root);
457                     // try className
458                     } else if (className) {
459                         nodes = root.getElementsByClassName(className);
460                     } else { // default to tagName
461                         nodes = root.getElementsByTagName(tagName);
462                     }
463
464                 } else { // brute getElementsByTagName('*')
465                     child = root.firstChild;
466                     while (child) {
467                         if (child.tagName) { // only collect HTMLElements
468                             nodes.push(child);
469                         }
470                         child = child.nextSilbing || child.firstChild;
471                     }
472                 }
473                 if (nodes.length) {
474                     ret = Selector._filterNodes(nodes, tokens, firstOnly);
475                 }
476             }
477
478             return ret;
479         },
480         
481         _filterNodes: function(nodes, tokens, firstOnly) {
482             var i = 0,
483                 j,
484                 len = tokens.length,
485                 n = len - 1,
486                 result = [],
487                 node = nodes[0],
488                 tmpNode = node,
489                 getters = Selector.getters,
490                 operator,
491                 combinator,
492                 token,
493                 path,
494                 pass,
495                 //FUNCTION = 'function',
496                 value,
497                 tests,
498                 test;
499
500             //do {
501             for (i = 0; (tmpNode = node = nodes[i++]);) {
502                 n = len - 1;
503                 path = null;
504                 
505                 testLoop:
506                 while (tmpNode && tmpNode.tagName) {
507                     token = tokens[n];
508                     tests = token.tests;
509                     j = tests.length;
510                     if (j && !pass) {
511                         while ((test = tests[--j])) {
512                             operator = test[1];
513                             if (getters[test[0]]) {
514                                 value = getters[test[0]](tmpNode, test[0]);
515                             } else {
516                                 value = tmpNode[test[0]];
517                                 // use getAttribute for non-standard attributes
518                                 if (value === undefined && tmpNode.getAttribute) {
519                                     value = tmpNode.getAttribute(test[0]);
520                                 }
521                             }
522
523                             if ((operator === '=' && value !== test[2]) ||  // fast path for equality
524                                 (typeof operator !== 'string' && // protect against String.test monkey-patch (Moo)
525                                 operator.test && !operator.test(value)) ||  // regex test
526                                 (!operator.test && // protect against RegExp as function (webkit)
527                                         typeof operator === 'function' && !operator(tmpNode, test[0], test[2]))) { // function test
528
529                                 // skip non element nodes or non-matching tags
530                                 if ((tmpNode = tmpNode[path])) {
531                                     while (tmpNode &&
532                                         (!tmpNode.tagName ||
533                                             (token.tagName && token.tagName !== tmpNode.tagName))
534                                     ) {
535                                         tmpNode = tmpNode[path]; 
536                                     }
537                                 }
538                                 continue testLoop;
539                             }
540                         }
541                     }
542
543                     n--; // move to next token
544                     // now that we've passed the test, move up the tree by combinator
545                     if (!pass && (combinator = token.combinator)) {
546                         path = combinator.axis;
547                         tmpNode = tmpNode[path];
548
549                         // skip non element nodes
550                         while (tmpNode && !tmpNode.tagName) {
551                             tmpNode = tmpNode[path]; 
552                         }
553
554                         if (combinator.direct) { // one pass only
555                             path = null; 
556                         }
557
558                     } else { // success if we made it this far
559                         result.push(node);
560                         if (firstOnly) {
561                             return result;
562                         }
563                         break;
564                     }
565                 }
566             }// while (tmpNode = node = nodes[++i]);
567             node = tmpNode = null;
568             return result;
569         },
570
571         combinators: {
572             ' ': {
573                 axis: 'parentNode'
574             },
575
576             '>': {
577                 axis: 'parentNode',
578                 direct: true
579             },
580
581
582             '+': {
583                 axis: 'previousSibling',
584                 direct: true
585             }
586         },
587
588         _parsers: [
589             {
590                 name: ATTRIBUTES,
591                 //re: /^\[(-?[a-z]+[\w\-]*)+([~\|\^\$\*!=]=?)?['"]?([^\]]*?)['"]?\]/i,
592                 re: /^\uE003(-?[a-z]+[\w\-]*)+([~\|\^\$\*!=]=?)?['"]?([^\uE004'"]*)['"]?\uE004/i,
593                 fn: function(match, token) {
594                     var operator = match[2] || '',
595                         operators = Selector.operators,
596                         escVal = (match[3]) ? match[3].replace(/\\/g, '') : '',
597                         test;
598
599                     // add prefiltering for ID and CLASS
600                     if ((match[1] === 'id' && operator === '=') ||
601                             (match[1] === 'className' &&
602                             Y_DOCUMENT_ELEMENT.getElementsByClassName &&
603                             (operator === '~=' || operator === '='))) {
604                         token.prefilter = match[1];
605
606
607                         match[3] = escVal; 
608
609                         // escape all but ID for prefilter, which may run through QSA (via Dom.allById)
610                         token[match[1]] = (match[1] === 'id') ? match[3] : escVal;
611
612                     }
613
614                     // add tests
615                     if (operator in operators) {
616                         test = operators[operator];
617                         if (typeof test === 'string') {
618                             match[3] = escVal.replace(Selector._reRegExpTokens, '\\$1');
619                             test = new RegExp(test.replace('{val}', match[3]));
620                         }
621                         match[2] = test;
622                     }
623                     if (!token.last || token.prefilter !== match[1]) {
624                         return match.slice(1);
625                     }
626                 }
627
628             },
629             {
630                 name: TAG_NAME,
631                 re: /^((?:-?[_a-z]+[\w-]*)|\*)/i,
632                 fn: function(match, token) {
633                     var tag = match[1].toUpperCase();
634                     token.tagName = tag;
635
636                     if (tag !== '*' && (!token.last || token.prefilter)) {
637                         return [TAG_NAME, '=', tag];
638                     }
639                     if (!token.prefilter) {
640                         token.prefilter = 'tagName';
641                     }
642                 }
643             },
644             {
645                 name: COMBINATOR,
646                 re: /^\s*([>+~]|\s)\s*/,
647                 fn: function(match, token) {
648                 }
649             },
650             {
651                 name: PSEUDOS,
652                 re: /^:([\-\w]+)(?:\uE005['"]?([^\uE005]*)['"]?\uE006)*/i,
653                 fn: function(match, token) {
654                     var test = Selector[PSEUDOS][match[1]];
655                     if (test) { // reorder match array and unescape special chars for tests
656                         if (match[2]) {
657                             match[2] = match[2].replace(/\\/g, '');
658                         }
659                         return [match[2], test]; 
660                     } else { // selector token not supported (possibly missing CSS3 module)
661                         return false;
662                     }
663                 }
664             }
665             ],
666
667         _getToken: function(token) {
668             return {
669                 tagName: null,
670                 id: null,
671                 className: null,
672                 attributes: {},
673                 combinator: null,
674                 tests: []
675             };
676         },
677
678         /**
679             Break selector into token units per simple selector.
680             Combinator is attached to the previous token.
681          */
682         _tokenize: function(selector) {
683             selector = selector || '';
684             selector = Selector._replaceShorthand(Y_Lang.trim(selector)); 
685             var token = Selector._getToken(),     // one token per simple selector (left selector holds combinator)
686                 query = selector, // original query for debug report
687                 tokens = [],    // array of tokens
688                 found = false,  // whether or not any matches were found this pass
689                 match,         // the regex match
690                 test,
691                 i, parser;
692
693             /*
694                 Search for selector patterns, store, and strip them from the selector string
695                 until no patterns match (invalid selector) or we run out of chars.
696
697                 Multiple attributes and pseudos are allowed, in any order.
698                 for example:
699                     'form:first-child[type=button]:not(button)[lang|=en]'
700             */
701
702             outer:
703             do {
704                 found = false; // reset after full pass
705
706                 for (i = 0; (parser = Selector._parsers[i++]);) {
707                     if ( (match = parser.re.exec(selector)) ) { // note assignment
708                         if (parser.name !== COMBINATOR ) {
709                             token.selector = selector;
710                         }
711                         selector = selector.replace(match[0], ''); // strip current match from selector
712                         if (!selector.length) {
713                             token.last = true;
714                         }
715
716                         if (Selector._attrFilters[match[1]]) { // convert class to className, etc.
717                             match[1] = Selector._attrFilters[match[1]];
718                         }
719
720                         test = parser.fn(match, token);
721                         if (test === false) { // selector not supported
722                             found = false;
723                             break outer;
724                         } else if (test) {
725                             token.tests.push(test);
726                         }
727
728                         if (!selector.length || parser.name === COMBINATOR) {
729                             tokens.push(token);
730                             token = Selector._getToken(token);
731                             if (parser.name === COMBINATOR) {
732                                 token.combinator = Selector.combinators[match[1]];
733                             }
734                         }
735                         found = true;
736
737
738                     }
739                 }
740             } while (found && selector.length);
741
742             if (!found || selector.length) { // not fully parsed
743                 tokens = [];
744             }
745             return tokens;
746         },
747
748         _replaceShorthand: function(selector) {
749             var shorthand = Selector.shorthand,
750                 esc = selector.match(Selector._re.esc), // pull escaped colon, brackets, etc. 
751                 attrs,
752                 pseudos,
753                 re, i, len;
754
755             if (esc) {
756                 selector = selector.replace(Selector._re.esc, '\uE000');
757             }
758
759             attrs = selector.match(Selector._re.attr);
760             pseudos = selector.match(Selector._re.pseudos);
761
762             if (attrs) {
763                 selector = selector.replace(Selector._re.attr, '\uE001');
764             }
765
766             if (pseudos) {
767                 selector = selector.replace(Selector._re.pseudos, '\uE002');
768             }
769
770
771             for (re in shorthand) {
772                 if (shorthand.hasOwnProperty(re)) {
773                     selector = selector.replace(new RegExp(re, 'gi'), shorthand[re]);
774                 }
775             }
776
777             if (attrs) {
778                 for (i = 0, len = attrs.length; i < len; ++i) {
779                     selector = selector.replace(/\uE001/, attrs[i]);
780                 }
781             }
782
783             if (pseudos) {
784                 for (i = 0, len = pseudos.length; i < len; ++i) {
785                     selector = selector.replace(/\uE002/, pseudos[i]);
786                 }
787             }
788
789             selector = selector.replace(/\[/g, '\uE003');
790             selector = selector.replace(/\]/g, '\uE004');
791
792             selector = selector.replace(/\(/g, '\uE005');
793             selector = selector.replace(/\)/g, '\uE006');
794
795             if (esc) {
796                 for (i = 0, len = esc.length; i < len; ++i) {
797                     selector = selector.replace('\uE000', esc[i]);
798                 }
799             }
800
801             return selector;
802         },
803
804         _attrFilters: {
805             'class': 'className',
806             'for': 'htmlFor'
807         },
808
809         getters: {
810             href: function(node, attr) {
811                 return Y_DOM.getAttribute(node, attr);
812             }
813         }
814     };
815
816 Y_mix(Selector, SelectorCSS2, true);
817 Selector.getters.src = Selector.getters.rel = Selector.getters.href;
818
819 // IE wants class with native queries
820 if (Selector.useNative && Y_DOC.querySelector) {
821     Selector.shorthand['\\.([^\\s\\\\(\\[:]*)'] = '[class~=$1]';
822 }
823
824 /**
825  * The selector css3 module provides support for css3 selectors.
826  * @module dom
827  * @submodule selector-css3
828  * @for Selector
829  */
830
831 /*
832     an+b = get every _a_th node starting at the _b_th
833     0n+b = no repeat ("0" and "n" may both be omitted (together) , e.g. "0n+1" or "1", not "0+1"), return only the _b_th element
834     1n+b =  get every element starting from b ("1" may may be omitted, e.g. "1n+0" or "n+0" or "n")
835     an+0 = get every _a_th element, "0" may be omitted 
836 */
837
838 Selector._reNth = /^(?:([\-]?\d*)(n){1}|(odd|even)$)*([\-+]?\d*)$/;
839
840 Selector._getNth = function(node, expr, tag, reverse) {
841     Selector._reNth.test(expr);
842     var a = parseInt(RegExp.$1, 10), // include every _a_ elements (zero means no repeat, just first _a_)
843         n = RegExp.$2, // "n"
844         oddeven = RegExp.$3, // "odd" or "even"
845         b = parseInt(RegExp.$4, 10) || 0, // start scan from element _b_
846         result = [],
847         siblings = Selector._children(node.parentNode, tag),
848         op;
849
850     if (oddeven) {
851         a = 2; // always every other
852         op = '+';
853         n = 'n';
854         b = (oddeven === 'odd') ? 1 : 0;
855     } else if ( isNaN(a) ) {
856         a = (n) ? 1 : 0; // start from the first or no repeat
857     }
858
859     if (a === 0) { // just the first
860         if (reverse) {
861             b = siblings.length - b + 1; 
862         }
863
864         if (siblings[b - 1] === node) {
865             return true;
866         } else {
867             return false;
868         }
869
870     } else if (a < 0) {
871         reverse = !!reverse;
872         a = Math.abs(a);
873     }
874
875     if (!reverse) {
876         for (var i = b - 1, len = siblings.length; i < len; i += a) {
877             if ( i >= 0 && siblings[i] === node ) {
878                 return true;
879             }
880         }
881     } else {
882         for (var i = siblings.length - b, len = siblings.length; i >= 0; i -= a) {
883             if ( i < len && siblings[i] === node ) {
884                 return true;
885             }
886         }
887     }
888     return false;
889 };
890
891 Y_mix(Selector.pseudos, {
892     'root': function(node) {
893         return node === node.ownerDocument.documentElement;
894     },
895
896     'nth-child': function(node, expr) {
897         return Selector._getNth(node, expr);
898     },
899
900     'nth-last-child': function(node, expr) {
901         return Selector._getNth(node, expr, null, true);
902     },
903
904     'nth-of-type': function(node, expr) {
905         return Selector._getNth(node, expr, node.tagName);
906     },
907      
908     'nth-last-of-type': function(node, expr) {
909         return Selector._getNth(node, expr, node.tagName, true);
910     },
911      
912     'last-child': function(node) {
913         var children = Selector._children(node.parentNode);
914         return children[children.length - 1] === node;
915     },
916
917     'first-of-type': function(node) {
918         return Selector._children(node.parentNode, node.tagName)[0] === node;
919     },
920      
921     'last-of-type': function(node) {
922         var children = Selector._children(node.parentNode, node.tagName);
923         return children[children.length - 1] === node;
924     },
925      
926     'only-child': function(node) {
927         var children = Selector._children(node.parentNode);
928         return children.length === 1 && children[0] === node;
929     },
930
931     'only-of-type': function(node) {
932         var children = Selector._children(node.parentNode, node.tagName);
933         return children.length === 1 && children[0] === node;
934     },
935
936     'empty': function(node) {
937         return node.childNodes.length === 0;
938     },
939
940     'not': function(node, expr) {
941         return !Selector.test(node, expr);
942     },
943
944     'contains': function(node, expr) {
945         var text = node.innerText || node.textContent || '';
946         return text.indexOf(expr) > -1;
947     },
948
949     'checked': function(node) {
950         return (node.checked === true || node.selected === true);
951     },
952
953     enabled: function(node) {
954         return (node.disabled !== undefined && !node.disabled);
955     },
956
957     disabled: function(node) {
958         return (node.disabled);
959     }
960 });
961
962 Y_mix(Selector.operators, {
963     '^=': '^{val}', // Match starts with value
964     '!=': function(node, attr, val) { return node[attr] !== val; }, // Match starts with value
965     '$=': '{val}$', // Match ends with value
966     '*=': '{val}' // Match contains value as substring 
967 });
968
969 Selector.combinators['~'] = {
970     axis: 'previousSibling'
971 };
972 YAHOO.register("selector", YAHOO.util.Selector, {version: "2.9.0", build: "2800"});