Fixes #2715 - Adding Microsoft Edge UA string support to Browser.
[mootools.git] / Source / Slick / Slick.Finder.js
bloba257e3591e9470194e382b36b2315bb38ec9233a
1 /*
2 ---
3 name: Slick.Finder
4 description: The new, superfast css selector engine.
5 provides: Slick.Finder
6 requires: Slick.Parser
7 ...
8 */
10 ;(function(){
12 var local = {},
13         featuresCache = {},
14         toString = Object.prototype.toString;
16 // Feature / Bug detection
18 local.isNativeCode = function(fn){
19         return (/\{\s*\[native code\]\s*\}/).test('' + fn);
22 local.isXML = function(document){
23         return (!!document.xmlVersion) || (!!document.xml) || (toString.call(document) == '[object XMLDocument]') ||
24         (document.nodeType == 9 && document.documentElement.nodeName != 'HTML');
27 local.setDocument = function(document){
29         // convert elements / window arguments to document. if document cannot be extrapolated, the function returns.
30         var nodeType = document.nodeType;
31         if (nodeType == 9); // document
32         else if (nodeType) document = document.ownerDocument; // node
33         else if (document.navigator) document = document.document; // window
34         else return;
36         // check if it's the old document
38         if (this.document === document) return;
39         this.document = document;
41         // check if we have done feature detection on this document before
43         var root = document.documentElement,
44                 rootUid = this.getUIDXML(root),
45                 features = featuresCache[rootUid],
46                 feature;
48         if (features){
49                 for (feature in features){
50                         this[feature] = features[feature];
51                 }
52                 return;
53         }
55         features = featuresCache[rootUid] = {};
57         features.root = root;
58         features.isXMLDocument = this.isXML(document);
60         features.brokenStarGEBTN
61         = features.starSelectsClosedQSA
62         = features.idGetsName
63         = features.brokenMixedCaseQSA
64         = features.brokenGEBCN
65         = features.brokenCheckedQSA
66         = features.brokenEmptyAttributeQSA
67         = features.isHTMLDocument
68         = features.nativeMatchesSelector
69         = false;
71         var starSelectsClosed, starSelectsComments,
72                 brokenSecondClassNameGEBCN, cachedGetElementsByClassName,
73                 brokenFormAttributeGetter;
75         var selected, id = 'slick_uniqueid';
76         var testNode = document.createElement('div');
78         var testRoot = document.body || document.getElementsByTagName('body')[0] || root;
79         testRoot.appendChild(testNode);
81         // on non-HTML documents innerHTML and getElementsById doesnt work properly
82         try {
83                 testNode.innerHTML = '<a id="'+id+'"></a>';
84                 features.isHTMLDocument = !!document.getElementById(id);
85         } catch(e){}
87         if (features.isHTMLDocument){
89                 testNode.style.display = 'none';
91                 // IE returns comment nodes for getElementsByTagName('*') for some documents
92                 testNode.appendChild(document.createComment(''));
93                 starSelectsComments = (testNode.getElementsByTagName('*').length > 1);
95                 // IE returns closed nodes (EG:"</foo>") for getElementsByTagName('*') for some documents
96                 try {
97                         testNode.innerHTML = 'foo</foo>';
98                         selected = testNode.getElementsByTagName('*');
99                         starSelectsClosed = (selected && !!selected.length && selected[0].nodeName.charAt(0) == '/');
100                 } catch(e){};
102                 features.brokenStarGEBTN = starSelectsComments || starSelectsClosed;
104                 // IE returns elements with the name instead of just id for getElementsById for some documents
105                 try {
106                         testNode.innerHTML = '<a name="'+ id +'"></a><b id="'+ id +'"></b>';
107                         features.idGetsName = document.getElementById(id) === testNode.firstChild;
108                 } catch(e){}
110                 if (testNode.getElementsByClassName){
112                         // Safari 3.2 getElementsByClassName caches results
113                         try {
114                                 testNode.innerHTML = '<a class="f"></a><a class="b"></a>';
115                                 testNode.getElementsByClassName('b').length;
116                                 testNode.firstChild.className = 'b';
117                                 cachedGetElementsByClassName = (testNode.getElementsByClassName('b').length != 2);
118                         } catch(e){};
120                         // Opera 9.6 getElementsByClassName doesnt detects the class if its not the first one
121                         try {
122                                 testNode.innerHTML = '<a class="a"></a><a class="f b a"></a>';
123                                 brokenSecondClassNameGEBCN = (testNode.getElementsByClassName('a').length != 2);
124                         } catch(e){}
126                         features.brokenGEBCN = cachedGetElementsByClassName || brokenSecondClassNameGEBCN;
127                 }
129                 if (testNode.querySelectorAll){
130                         // IE 8 returns closed nodes (EG:"</foo>") for querySelectorAll('*') for some documents
131                         try {
132                                 testNode.innerHTML = 'foo</foo>';
133                                 selected = testNode.querySelectorAll('*');
134                                 features.starSelectsClosedQSA = (selected && !!selected.length && selected[0].nodeName.charAt(0) == '/');
135                         } catch(e){}
137                         // Safari 3.2 querySelectorAll doesnt work with mixedcase on quirksmode
138                         try {
139                                 testNode.innerHTML = '<a class="MiX"></a>';
140                                 features.brokenMixedCaseQSA = !testNode.querySelectorAll('.MiX').length;
141                         } catch(e){}
143                         // Webkit and Opera dont return selected options on querySelectorAll
144                         try {
145                                 testNode.innerHTML = '<select><option selected="selected">a</option></select>';
146                                 features.brokenCheckedQSA = (testNode.querySelectorAll(':checked').length == 0);
147                         } catch(e){};
149                         // IE returns incorrect results for attr[*^$]="" selectors on querySelectorAll
150                         try {
151                                 testNode.innerHTML = '<a class=""></a>';
152                                 features.brokenEmptyAttributeQSA = (testNode.querySelectorAll('[class*=""]').length != 0);
153                         } catch(e){}
155                 }
157                 // IE6-7, if a form has an input of id x, form.getAttribute(x) returns a reference to the input
158                 try {
159                         testNode.innerHTML = '<form action="s"><input id="action"/></form>';
160                         brokenFormAttributeGetter = (testNode.firstChild.getAttribute('action') != 's');
161                 } catch(e){}
163                 // native matchesSelector function
165                 features.nativeMatchesSelector = root.matches || /*root.msMatchesSelector ||*/ root.mozMatchesSelector || root.webkitMatchesSelector;
166                 if (features.nativeMatchesSelector) try {
167                         // if matchesSelector trows errors on incorrect sintaxes we can use it
168                         features.nativeMatchesSelector.call(root, ':slick');
169                         features.nativeMatchesSelector = null;
170                 } catch(e){}
172         }
174         try {
175                 root.slick_expando = 1;
176                 delete root.slick_expando;
177                 features.getUID = this.getUIDHTML;
178         } catch(e){
179                 features.getUID = this.getUIDXML;
180         }
182         testRoot.removeChild(testNode);
183         testNode = selected = testRoot = null;
185         // getAttribute
187         features.getAttribute = (features.isHTMLDocument && brokenFormAttributeGetter) ? function(node, name){
188                 var method = this.attributeGetters[name];
189                 if (method) return method.call(node);
190                 var attributeNode = node.getAttributeNode(name);
191                 return (attributeNode) ? attributeNode.nodeValue : null;
192         } : function(node, name){
193                 var method = this.attributeGetters[name];
194                 return (method) ? method.call(node) : node.getAttribute(name);
195         };
197         // hasAttribute
199         features.hasAttribute = (root && this.isNativeCode(root.hasAttribute)) ? function(node, attribute){
200                 return node.hasAttribute(attribute);
201         } : function(node, attribute){
202                 node = node.getAttributeNode(attribute);
203                 return !!(node && (node.specified || node.nodeValue));
204         };
206         // contains
207         // FIXME: Add specs: local.contains should be different for xml and html documents?
208         var nativeRootContains = root && this.isNativeCode(root.contains),
209                 nativeDocumentContains = document && this.isNativeCode(document.contains);
211         features.contains = (nativeRootContains && nativeDocumentContains) ? function(context, node){
212                 return context.contains(node);
213         } : (nativeRootContains && !nativeDocumentContains) ? function(context, node){
214                 // IE8 does not have .contains on document.
215                 return context === node || ((context === document) ? document.documentElement : context).contains(node);
216         } : (root && root.compareDocumentPosition) ? function(context, node){
217                 return context === node || !!(context.compareDocumentPosition(node) & 16);
218         } : function(context, node){
219                 if (node) do {
220                         if (node === context) return true;
221                 } while ((node = node.parentNode));
222                 return false;
223         };
225         // document order sorting
226         // credits to Sizzle (http://sizzlejs.com/)
228         features.documentSorter = (root.compareDocumentPosition) ? function(a, b){
229                 if (!a.compareDocumentPosition || !b.compareDocumentPosition) return 0;
230                 return a.compareDocumentPosition(b) & 4 ? -1 : a === b ? 0 : 1;
231         } : ('sourceIndex' in root) ? function(a, b){
232                 if (!a.sourceIndex || !b.sourceIndex) return 0;
233                 return a.sourceIndex - b.sourceIndex;
234         } : (document.createRange) ? function(a, b){
235                 if (!a.ownerDocument || !b.ownerDocument) return 0;
236                 var aRange = a.ownerDocument.createRange(), bRange = b.ownerDocument.createRange();
237                 aRange.setStart(a, 0);
238                 aRange.setEnd(a, 0);
239                 bRange.setStart(b, 0);
240                 bRange.setEnd(b, 0);
241                 return aRange.compareBoundaryPoints(Range.START_TO_END, bRange);
242         } : null ;
244         root = null;
246         for (feature in features){
247                 this[feature] = features[feature];
248         }
251 // Main Method
253 var reSimpleSelector = /^([#.]?)((?:[\w-]+|\*))$/,
254         reEmptyAttribute = /\[.+[*$^]=(?:""|'')?\]/,
255         qsaFailExpCache = {};
257 local.search = function(context, expression, append, first){
259         var found = this.found = (first) ? null : (append || []);
261         if (!context) return found;
262         else if (context.navigator) context = context.document; // Convert the node from a window to a document
263         else if (!context.nodeType) return found;
265         // setup
267         var parsed, i,
268                 uniques = this.uniques = {},
269                 hasOthers = !!(append && append.length),
270                 contextIsDocument = (context.nodeType == 9);
272         if (this.document !== (contextIsDocument ? context : context.ownerDocument)) this.setDocument(context);
274         // avoid duplicating items already in the append array
275         if (hasOthers) for (i = found.length; i--;) uniques[this.getUID(found[i])] = true;
277         // expression checks
279         if (typeof expression == 'string'){ // expression is a string
281                 /*<simple-selectors-override>*/
282                 var simpleSelector = expression.match(reSimpleSelector);
283                 simpleSelectors: if (simpleSelector){
285                         var symbol = simpleSelector[1],
286                                 name = simpleSelector[2],
287                                 node, nodes;
289                         if (!symbol){
291                                 if (name == '*' && this.brokenStarGEBTN) break simpleSelectors;
292                                 nodes = context.getElementsByTagName(name);
293                                 if (first) return nodes[0] || null;
294                                 for (i = 0; node = nodes[i++];){
295                                         if (!(hasOthers && uniques[this.getUID(node)])) found.push(node);
296                                 }
298                         } else if (symbol == '#'){
300                                 if (!this.isHTMLDocument || !contextIsDocument) break simpleSelectors;
301                                 node = context.getElementById(name);
302                                 if (!node) return found;
303                                 if (this.idGetsName && node.getAttributeNode('id').nodeValue != name) break simpleSelectors;
304                                 if (first) return node || null;
305                                 if (!(hasOthers && uniques[this.getUID(node)])) found.push(node);
307                         } else if (symbol == '.'){
309                                 if (!this.isHTMLDocument || ((!context.getElementsByClassName || this.brokenGEBCN) && context.querySelectorAll)) break simpleSelectors;
310                                 if (context.getElementsByClassName && !this.brokenGEBCN){
311                                         nodes = context.getElementsByClassName(name);
312                                         if (first) return nodes[0] || null;
313                                         for (i = 0; node = nodes[i++];){
314                                                 if (!(hasOthers && uniques[this.getUID(node)])) found.push(node);
315                                         }
316                                 } else {
317                                         var matchClass = new RegExp('(^|\\s)'+ Slick.escapeRegExp(name) +'(\\s|$)');
318                                         nodes = context.getElementsByTagName('*');
319                                         for (i = 0; node = nodes[i++];){
320                                                 className = node.className;
321                                                 if (!(className && matchClass.test(className))) continue;
322                                                 if (first) return node;
323                                                 if (!(hasOthers && uniques[this.getUID(node)])) found.push(node);
324                                         }
325                                 }
327                         }
329                         if (hasOthers) this.sort(found);
330                         return (first) ? null : found;
332                 }
333                 /*</simple-selectors-override>*/
335                 /*<query-selector-override>*/
336                 querySelector: if (context.querySelectorAll){
338                         if (!this.isHTMLDocument
339                                 || qsaFailExpCache[expression]
340                                 //TODO: only skip when expression is actually mixed case
341                                 || this.brokenMixedCaseQSA
342                                 || (this.brokenCheckedQSA && expression.indexOf(':checked') > -1)
343                                 || (this.brokenEmptyAttributeQSA && reEmptyAttribute.test(expression))
344                                 || (!contextIsDocument //Abort when !contextIsDocument and...
345                                         //  there are multiple expressions in the selector
346                                         //  since we currently only fix non-document rooted QSA for single expression selectors
347                                         && expression.indexOf(',') > -1
348                                 )
349                                 || Slick.disableQSA
350                         ) break querySelector;
352                         var _expression = expression, _context = context;
353                         if (!contextIsDocument){
354                                 // non-document rooted QSA
355                                 // credits to Andrew Dupont
356                                 var currentId = _context.getAttribute('id'), slickid = 'slickid__';
357                                 _context.setAttribute('id', slickid);
358                                 _expression = '#' + slickid + ' ' + _expression;
359                                 context = _context.parentNode;
360                         }
362                         try {
363                                 if (first) return context.querySelector(_expression) || null;
364                                 else nodes = context.querySelectorAll(_expression);
365                         } catch(e){
366                                 qsaFailExpCache[expression] = 1;
367                                 break querySelector;
368                         } finally {
369                                 if (!contextIsDocument){
370                                         if (currentId) _context.setAttribute('id', currentId);
371                                         else _context.removeAttribute('id');
372                                         context = _context;
373                                 }
374                         }
376                         if (this.starSelectsClosedQSA) for (i = 0; node = nodes[i++];){
377                                 if (node.nodeName > '@' && !(hasOthers && uniques[this.getUID(node)])) found.push(node);
378                         } else for (i = 0; node = nodes[i++];){
379                                 if (!(hasOthers && uniques[this.getUID(node)])) found.push(node);
380                         }
382                         if (hasOthers) this.sort(found);
383                         return found;
385                 }
386                 /*</query-selector-override>*/
388                 parsed = this.Slick.parse(expression);
389                 if (!parsed.length) return found;
390         } else if (expression == null){ // there is no expression
391                 return found;
392         } else if (expression.Slick){ // expression is a parsed Slick object
393                 parsed = expression;
394         } else if (this.contains(context.documentElement || context, expression)){ // expression is a node
395                 (found) ? found.push(expression) : found = expression;
396                 return found;
397         } else { // other junk
398                 return found;
399         }
401         /*<pseudo-selectors>*//*<nth-pseudo-selectors>*/
403         // cache elements for the nth selectors
405         this.posNTH = {};
406         this.posNTHLast = {};
407         this.posNTHType = {};
408         this.posNTHTypeLast = {};
410         /*</nth-pseudo-selectors>*//*</pseudo-selectors>*/
412         // if append is null and there is only a single selector with one expression use pushArray, else use pushUID
413         this.push = (!hasOthers && (first || (parsed.length == 1 && parsed.expressions[0].length == 1))) ? this.pushArray : this.pushUID;
415         if (found == null) found = [];
417         // default engine
419         var j, m, n;
420         var combinator, tag, id, classList, classes, attributes, pseudos;
421         var currentItems, currentExpression, currentBit, lastBit, expressions = parsed.expressions;
423         search: for (i = 0; (currentExpression = expressions[i]); i++) for (j = 0; (currentBit = currentExpression[j]); j++){
425                 combinator = 'combinator:' + currentBit.combinator;
426                 if (!this[combinator]) continue search;
428                 tag        = (this.isXMLDocument) ? currentBit.tag : currentBit.tag.toUpperCase();
429                 id         = currentBit.id;
430                 classList  = currentBit.classList;
431                 classes    = currentBit.classes;
432                 attributes = currentBit.attributes;
433                 pseudos    = currentBit.pseudos;
434                 lastBit    = (j === (currentExpression.length - 1));
436                 this.bitUniques = {};
438                 if (lastBit){
439                         this.uniques = uniques;
440                         this.found = found;
441                 } else {
442                         this.uniques = {};
443                         this.found = [];
444                 }
446                 if (j === 0){
447                         this[combinator](context, tag, id, classes, attributes, pseudos, classList);
448                         if (first && lastBit && found.length) break search;
449                 } else {
450                         if (first && lastBit) for (m = 0, n = currentItems.length; m < n; m++){
451                                 this[combinator](currentItems[m], tag, id, classes, attributes, pseudos, classList);
452                                 if (found.length) break search;
453                         } else for (m = 0, n = currentItems.length; m < n; m++) this[combinator](currentItems[m], tag, id, classes, attributes, pseudos, classList);
454                 }
456                 currentItems = this.found;
457         }
459         // should sort if there are nodes in append and if you pass multiple expressions.
460         if (hasOthers || (parsed.expressions.length > 1)) this.sort(found);
462         return (first) ? (found[0] || null) : found;
465 // Utils
467 local.uidx = 1;
468 local.uidk = 'slick-uniqueid';
470 local.getUIDXML = function(node){
471         var uid = node.getAttribute(this.uidk);
472         if (!uid){
473                 uid = this.uidx++;
474                 node.setAttribute(this.uidk, uid);
475         }
476         return uid;
479 local.getUIDHTML = function(node){
480         return node.uniqueNumber || (node.uniqueNumber = this.uidx++);
483 // sort based on the setDocument documentSorter method.
485 local.sort = function(results){
486         if (!this.documentSorter) return results;
487         results.sort(this.documentSorter);
488         return results;
491 /*<pseudo-selectors>*//*<nth-pseudo-selectors>*/
493 local.cacheNTH = {};
495 local.matchNTH = /^([+-]?\d*)?([a-z]+)?([+-]\d+)?$/;
497 local.parseNTHArgument = function(argument){
498         var parsed = argument.match(this.matchNTH);
499         if (!parsed) return false;
500         var special = parsed[2] || false;
501         var a = parsed[1] || 1;
502         if (a == '-') a = -1;
503         var b = +parsed[3] || 0;
504         parsed =
505                 (special == 'n')        ? {a: a, b: b} :
506                 (special == 'odd')      ? {a: 2, b: 1} :
507                 (special == 'even')     ? {a: 2, b: 0} : {a: 0, b: a};
509         return (this.cacheNTH[argument] = parsed);
512 local.createNTHPseudo = function(child, sibling, positions, ofType){
513         return function(node, argument){
514                 var uid = this.getUID(node);
515                 if (!this[positions][uid]){
516                         var parent = node.parentNode;
517                         if (!parent) return false;
518                         var el = parent[child], count = 1;
519                         if (ofType){
520                                 var nodeName = node.nodeName;
521                                 do {
522                                         if (el.nodeName != nodeName) continue;
523                                         this[positions][this.getUID(el)] = count++;
524                                 } while ((el = el[sibling]));
525                         } else {
526                                 do {
527                                         if (el.nodeType != 1) continue;
528                                         this[positions][this.getUID(el)] = count++;
529                                 } while ((el = el[sibling]));
530                         }
531                 }
532                 argument = argument || 'n';
533                 var parsed = this.cacheNTH[argument] || this.parseNTHArgument(argument);
534                 if (!parsed) return false;
535                 var a = parsed.a, b = parsed.b, pos = this[positions][uid];
536                 if (a == 0) return b == pos;
537                 if (a > 0){
538                         if (pos < b) return false;
539                 } else {
540                         if (b < pos) return false;
541                 }
542                 return ((pos - b) % a) == 0;
543         };
546 /*</nth-pseudo-selectors>*//*</pseudo-selectors>*/
548 local.pushArray = function(node, tag, id, classes, attributes, pseudos){
549         if (this.matchSelector(node, tag, id, classes, attributes, pseudos)) this.found.push(node);
552 local.pushUID = function(node, tag, id, classes, attributes, pseudos){
553         var uid = this.getUID(node);
554         if (!this.uniques[uid] && this.matchSelector(node, tag, id, classes, attributes, pseudos)){
555                 this.uniques[uid] = true;
556                 this.found.push(node);
557         }
560 local.matchNode = function(node, selector){
561         if (this.isHTMLDocument && this.nativeMatchesSelector){
562                 try {
563                         return this.nativeMatchesSelector.call(node, selector.replace(/\[([^=]+)=\s*([^'"\]]+?)\s*\]/g, '[$1="$2"]'));
564                 } catch(matchError){}
565         }
567         var parsed = this.Slick.parse(selector);
568         if (!parsed) return true;
570         // simple (single) selectors
571         var expressions = parsed.expressions, simpleExpCounter = 0, i, currentExpression;
572         for (i = 0; (currentExpression = expressions[i]); i++){
573                 if (currentExpression.length == 1){
574                         var exp = currentExpression[0];
575                         if (this.matchSelector(node, (this.isXMLDocument) ? exp.tag : exp.tag.toUpperCase(), exp.id, exp.classes, exp.attributes, exp.pseudos)) return true;
576                         simpleExpCounter++;
577                 }
578         }
580         if (simpleExpCounter == parsed.length) return false;
582         var nodes = this.search(this.document, parsed), item;
583         for (i = 0; item = nodes[i++];){
584                 if (item === node) return true;
585         }
586         return false;
589 local.matchPseudo = function(node, name, argument){
590         var pseudoName = 'pseudo:' + name;
591         if (this[pseudoName]) return this[pseudoName](node, argument);
592         var attribute = this.getAttribute(node, name);
593         return (argument) ? argument == attribute : !!attribute;
596 local.matchSelector = function(node, tag, id, classes, attributes, pseudos){
597         if (tag){
598                 var nodeName = (this.isXMLDocument) ? node.nodeName : node.nodeName.toUpperCase();
599                 if (tag == '*'){
600                         if (nodeName < '@') return false; // Fix for comment nodes and closed nodes
601                 } else {
602                         if (nodeName != tag) return false;
603                 }
604         }
606         if (id && node.getAttribute('id') != id) return false;
608         var i, part, cls;
609         if (classes) for (i = classes.length; i--;){
610                 cls = this.getAttribute(node, 'class');
611                 if (!(cls && classes[i].regexp.test(cls))) return false;
612         }
613         if (attributes) for (i = attributes.length; i--;){
614                 part = attributes[i];
615                 if (part.operator ? !part.test(this.getAttribute(node, part.key)) : !this.hasAttribute(node, part.key)) return false;
616         }
617         if (pseudos) for (i = pseudos.length; i--;){
618                 part = pseudos[i];
619                 if (!this.matchPseudo(node, part.key, part.value)) return false;
620         }
621         return true;
624 var combinators = {
626         ' ': function(node, tag, id, classes, attributes, pseudos, classList){ // all child nodes, any level
628                 var i, item, children;
630                 if (this.isHTMLDocument){
631                         getById: if (id){
632                                 item = this.document.getElementById(id);
633                                 if ((!item && node.all) || (this.idGetsName && item && item.getAttributeNode('id').nodeValue != id)){
634                                         // all[id] returns all the elements with that name or id inside node
635                                         // if theres just one it will return the element, else it will be a collection
636                                         children = node.all[id];
637                                         if (!children) return;
638                                         if (!children[0]) children = [children];
639                                         for (i = 0; item = children[i++];){
640                                                 var idNode = item.getAttributeNode('id');
641                                                 if (idNode && idNode.nodeValue == id){
642                                                         this.push(item, tag, null, classes, attributes, pseudos);
643                                                         break;
644                                                 }
645                                         }
646                                         return;
647                                 }
648                                 if (!item){
649                                         // if the context is in the dom we return, else we will try GEBTN, breaking the getById label
650                                         if (this.contains(this.root, node)) return;
651                                         else break getById;
652                                 } else if (this.document !== node && !this.contains(node, item)) return;
653                                 this.push(item, tag, null, classes, attributes, pseudos);
654                                 return;
655                         }
656                         getByClass: if (classes && node.getElementsByClassName && !this.brokenGEBCN){
657                                 children = node.getElementsByClassName(classList.join(' '));
658                                 if (!(children && children.length)) break getByClass;
659                                 for (i = 0; item = children[i++];) this.push(item, tag, id, null, attributes, pseudos);
660                                 return;
661                         }
662                 }
663                 getByTag: {
664                         children = node.getElementsByTagName(tag);
665                         if (!(children && children.length)) break getByTag;
666                         if (!this.brokenStarGEBTN) tag = null;
667                         for (i = 0; item = children[i++];) this.push(item, tag, id, classes, attributes, pseudos);
668                 }
669         },
671         '>': function(node, tag, id, classes, attributes, pseudos){ // direct children
672                 if ((node = node.firstChild)) do {
673                         if (node.nodeType == 1) this.push(node, tag, id, classes, attributes, pseudos);
674                 } while ((node = node.nextSibling));
675         },
677         '+': function(node, tag, id, classes, attributes, pseudos){ // next sibling
678                 while ((node = node.nextSibling)) if (node.nodeType == 1){
679                         this.push(node, tag, id, classes, attributes, pseudos);
680                         break;
681                 }
682         },
684         '^': function(node, tag, id, classes, attributes, pseudos){ // first child
685                 node = node.firstChild;
686                 if (node){
687                         if (node.nodeType == 1) this.push(node, tag, id, classes, attributes, pseudos);
688                         else this['combinator:+'](node, tag, id, classes, attributes, pseudos);
689                 }
690         },
692         '~': function(node, tag, id, classes, attributes, pseudos){ // next siblings
693                 while ((node = node.nextSibling)){
694                         if (node.nodeType != 1) continue;
695                         var uid = this.getUID(node);
696                         if (this.bitUniques[uid]) break;
697                         this.bitUniques[uid] = true;
698                         this.push(node, tag, id, classes, attributes, pseudos);
699                 }
700         },
702         '++': function(node, tag, id, classes, attributes, pseudos){ // next sibling and previous sibling
703                 this['combinator:+'](node, tag, id, classes, attributes, pseudos);
704                 this['combinator:!+'](node, tag, id, classes, attributes, pseudos);
705         },
707         '~~': function(node, tag, id, classes, attributes, pseudos){ // next siblings and previous siblings
708                 this['combinator:~'](node, tag, id, classes, attributes, pseudos);
709                 this['combinator:!~'](node, tag, id, classes, attributes, pseudos);
710         },
712         '!': function(node, tag, id, classes, attributes, pseudos){ // all parent nodes up to document
713                 while ((node = node.parentNode)) if (node !== this.document) this.push(node, tag, id, classes, attributes, pseudos);
714         },
716         '!>': function(node, tag, id, classes, attributes, pseudos){ // direct parent (one level)
717                 node = node.parentNode;
718                 if (node !== this.document) this.push(node, tag, id, classes, attributes, pseudos);
719         },
721         '!+': function(node, tag, id, classes, attributes, pseudos){ // previous sibling
722                 while ((node = node.previousSibling)) if (node.nodeType == 1){
723                         this.push(node, tag, id, classes, attributes, pseudos);
724                         break;
725                 }
726         },
728         '!^': function(node, tag, id, classes, attributes, pseudos){ // last child
729                 node = node.lastChild;
730                 if (node){
731                         if (node.nodeType == 1) this.push(node, tag, id, classes, attributes, pseudos);
732                         else this['combinator:!+'](node, tag, id, classes, attributes, pseudos);
733                 }
734         },
736         '!~': function(node, tag, id, classes, attributes, pseudos){ // previous siblings
737                 while ((node = node.previousSibling)){
738                         if (node.nodeType != 1) continue;
739                         var uid = this.getUID(node);
740                         if (this.bitUniques[uid]) break;
741                         this.bitUniques[uid] = true;
742                         this.push(node, tag, id, classes, attributes, pseudos);
743                 }
744         }
748 for (var c in combinators) local['combinator:' + c] = combinators[c];
750 var pseudos = {
752         /*<pseudo-selectors>*/
754         'empty': function(node){
755                 var child = node.firstChild;
756                 return !(child && child.nodeType == 1) && !(node.innerText || node.textContent || '').length;
757         },
759         'not': function(node, expression){
760                 return !this.matchNode(node, expression);
761         },
763         'contains': function(node, text){
764                 return (node.innerText || node.textContent || '').indexOf(text) > -1;
765         },
767         'first-child': function(node){
768                 while ((node = node.previousSibling)) if (node.nodeType == 1) return false;
769                 return true;
770         },
772         'last-child': function(node){
773                 while ((node = node.nextSibling)) if (node.nodeType == 1) return false;
774                 return true;
775         },
777         'only-child': function(node){
778                 var prev = node;
779                 while ((prev = prev.previousSibling)) if (prev.nodeType == 1) return false;
780                 var next = node;
781                 while ((next = next.nextSibling)) if (next.nodeType == 1) return false;
782                 return true;
783         },
785         /*<nth-pseudo-selectors>*/
787         'nth-child': local.createNTHPseudo('firstChild', 'nextSibling', 'posNTH'),
789         'nth-last-child': local.createNTHPseudo('lastChild', 'previousSibling', 'posNTHLast'),
791         'nth-of-type': local.createNTHPseudo('firstChild', 'nextSibling', 'posNTHType', true),
793         'nth-last-of-type': local.createNTHPseudo('lastChild', 'previousSibling', 'posNTHTypeLast', true),
795         'index': function(node, index){
796                 return this['pseudo:nth-child'](node, '' + (index + 1));
797         },
799         'even': function(node){
800                 return this['pseudo:nth-child'](node, '2n');
801         },
803         'odd': function(node){
804                 return this['pseudo:nth-child'](node, '2n+1');
805         },
807         /*</nth-pseudo-selectors>*/
809         /*<of-type-pseudo-selectors>*/
811         'first-of-type': function(node){
812                 var nodeName = node.nodeName;
813                 while ((node = node.previousSibling)) if (node.nodeName == nodeName) return false;
814                 return true;
815         },
817         'last-of-type': function(node){
818                 var nodeName = node.nodeName;
819                 while ((node = node.nextSibling)) if (node.nodeName == nodeName) return false;
820                 return true;
821         },
823         'only-of-type': function(node){
824                 var prev = node, nodeName = node.nodeName;
825                 while ((prev = prev.previousSibling)) if (prev.nodeName == nodeName) return false;
826                 var next = node;
827                 while ((next = next.nextSibling)) if (next.nodeName == nodeName) return false;
828                 return true;
829         },
831         /*</of-type-pseudo-selectors>*/
833         // custom pseudos
835         'enabled': function(node){
836                 return !node.disabled;
837         },
839         'disabled': function(node){
840                 return node.disabled;
841         },
843         'checked': function(node){
844                 return node.checked || node.selected;
845         },
847         'focus': function(node){
848                 return this.isHTMLDocument && this.document.activeElement === node && (node.href || node.type || this.hasAttribute(node, 'tabindex'));
849         },
851         'root': function(node){
852                 return (node === this.root);
853         },
855         'selected': function(node){
856                 return node.selected;
857         }
859         /*</pseudo-selectors>*/
862 for (var p in pseudos) local['pseudo:' + p] = pseudos[p];
864 // attributes methods
866 var attributeGetters = local.attributeGetters = {
868         'for': function(){
869                 return ('htmlFor' in this) ? this.htmlFor : this.getAttribute('for');
870         },
872         'href': function(){
873                 return ('href' in this) ? this.getAttribute('href', 2) : this.getAttribute('href');
874         },
876         'style': function(){
877                 return (this.style) ? this.style.cssText : this.getAttribute('style');
878         },
880         'tabindex': function(){
881                 var attributeNode = this.getAttributeNode('tabindex');
882                 return (attributeNode && attributeNode.specified) ? attributeNode.nodeValue : null;
883         },
885         'type': function(){
886                 return this.getAttribute('type');
887         },
889         'maxlength': function(){
890                 var attributeNode = this.getAttributeNode('maxLength');
891                 return (attributeNode && attributeNode.specified) ? attributeNode.nodeValue : null;
892         }
896 attributeGetters.MAXLENGTH = attributeGetters.maxLength = attributeGetters.maxlength;
898 // Slick
900 var Slick = local.Slick = (this.Slick || {});
902 Slick.version = '1.1.7';
904 // Slick finder
906 Slick.search = function(context, expression, append){
907         return local.search(context, expression, append);
910 Slick.find = function(context, expression){
911         return local.search(context, expression, null, true);
914 // Slick containment checker
916 Slick.contains = function(container, node){
917         local.setDocument(container);
918         return local.contains(container, node);
921 // Slick attribute getter
923 Slick.getAttribute = function(node, name){
924         local.setDocument(node);
925         return local.getAttribute(node, name);
928 Slick.hasAttribute = function(node, name){
929         local.setDocument(node);
930         return local.hasAttribute(node, name);
933 // Slick matcher
935 Slick.match = function(node, selector){
936         if (!(node && selector)) return false;
937         if (!selector || selector === node) return true;
938         local.setDocument(node);
939         return local.matchNode(node, selector);
942 // Slick attribute accessor
944 Slick.defineAttributeGetter = function(name, fn){
945         local.attributeGetters[name] = fn;
946         return this;
949 Slick.lookupAttributeGetter = function(name){
950         return local.attributeGetters[name];
953 // Slick pseudo accessor
955 Slick.definePseudo = function(name, fn){
956         local['pseudo:' + name] = function(node, argument){
957                 return fn.call(node, argument);
958         };
959         return this;
962 Slick.lookupPseudo = function(name){
963         var pseudo = local['pseudo:' + name];
964         if (pseudo) return function(argument){
965                 return pseudo.call(this, argument);
966         };
967         return null;
970 // Slick overrides accessor
972 Slick.override = function(regexp, fn){
973         local.override(regexp, fn);
974         return this;
977 Slick.isXML = local.isXML;
979 Slick.uidOf = function(node){
980         return local.getUIDHTML(node);
983 if (!this.Slick) this.Slick = Slick;
985 }).apply(/*<CommonJS>*/(typeof exports != 'undefined') ? exports : /*</CommonJS>*/this);