uri: Move exports to the end of the file.
7 * - gl.selector.match(node, selector)
8 * - gl.selector.query(node, selector)
9 * - gl.selector.queryAll(node, selector)
10 * - gl.selector.queryAncestorOrSelf(node, selector)
12 * See: http://www.grauw.nl/projects/selectors/
15 * @author Laurens Holst (http://www.grauw.nl/)
17 * Copyright 2010 Laurens Holst
19 * Licensed under the Apache License, Version 2.0 (the "License");
20 * you may not use this file except in compliance with the License.
21 * You may obtain a copy of the License at
23 * http://www.apache.org/licenses/LICENSE-2.0
25 * Unless required by applicable law or agreed to in writing, software
26 * distributed under the License is distributed on an "AS IS" BASIS,
27 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
28 * See the License for the specific language governing permissions and
29 * limitations under the License.
37 * The main CSS Selectors object.
38 * Acts as sort of a namespace; all other selector objects are closures of the instance of this object.
40 gl.selector = new function() {
42 var browser_ie = !!window.ActiveXObject;
44 function extend(cBase, fConstructor) {
45 var cPrototype = new Function();
46 cPrototype.prototype = cBase.prototype;
47 fConstructor.prototype = new cPrototype();
48 fConstructor.prototype.constructor = fConstructor;
53 * Helper function for cross-browser hasAttribute that ignores default values
54 * See: http://www.w3.org/TR/css3-selectors/#def-values
55 * Note: IE’s hasAttribute has issues with ‘class’ vs. ‘className’, ‘for’ vs. ‘htmlFor’, and ‘enctype’ vs. ‘encType’.
56 * Note: other browsers ignore attribute default values, even though spec says they shouldn’t
58 function hasAttribute(oNode, sName) {
60 var oAttr = oNode.getAttributeNode(sName);
61 return !!oAttr && oAttr.specified;
63 return oNode.hasAttribute(sName);
67 * Helper function for cross-browser getAttribute that ignores default values
68 * See: http://www.w3.org/TR/css3-selectors/#def-values
69 * Note: IE’s getAttribute has issues with ‘class’ vs. ‘className’, ‘for’ vs. ‘htmlFor’, and ‘enctype’ vs. ‘encType’.
70 * Note: other browsers ignore attribute default values, even though spec says they shouldn’t
72 function getAttribute(oNode, sName) {
74 var oAttr = oNode.getAttributeNode(sName);
75 return oAttr && oAttr.specified ? oAttr.nodeValue : '';
77 return oNode.getAttribute(sName) || '';
81 * Helper function for cross-browser getElementsByTagName('*').
82 * In particular, ignores HTMLCommentElement nodes thrown at you by Internet Explorer.
84 function getAllDescendants(oNode) {
85 var aDescendants = oNode.getElementsByTagName('*');
89 for (var i = 0, len = aDescendants.length; i < len; i++) {
90 if (aDescendants[i].nodeType == 1)
91 aResult.push(aDescendants[i]);
98 * Matches a node against a CSS selector or selector group.
99 * Supports all CSS3 Selectors, except for pseudo-elements and the following
100 * pseuro-classes: :link, :visited, :hover, :active, :focus.
101 * @param node The node
102 * @param selector The selector
103 * @return true if the selector matches.
105 function match(oNode, sSelector) {
106 var oSelectorGroup = oParser.parse(sSelector);
107 return oSelectorGroup ? oSelectorGroup.match(oNode) : false;
111 * Queries a CSS selector or selector group against a node.
112 * @param node The node
113 * @param selector The selector
114 * @return An array containing the selected nodes.
116 function queryAll(oNode, sSelector) {
117 if (oNode.nodeType == 9)
118 oNode = oNode.documentElement;
119 var oSelectorGroup = oParser.parse(sSelector);
120 return oSelectorGroup ? oSelectorGroup.query(oNode, false) : [];
124 * Queries a CSS selector or selector group against a node and returns one node.
125 * @param node The node
126 * @param selector The selector
127 * @return The selected node.
129 function query(oNode, sSelector) {
130 if (oNode.nodeType == 9)
131 oNode = oNode.documentElement;
132 var oSelectorGroup = oParser.parse(sSelector);
133 var aResult = oSelectorGroup ? oSelectorGroup.query(oNode, true) : [];
134 return aResult.length ? aResult[0] : null;
138 * Use a CSS selector to return the first selection found on a node or its ancestors.
139 * @param node The node
140 * @param selector The selector
141 * @param stop Stop querying after arriving at this node (optional)
142 * @return The selected node.
144 function queryAncestorOrSelf(oNode, sSelector, oStop) {
145 var oSelectorGroup = oParser.parse(sSelector);
149 while (oNode && oNode.nodeType == 1) {
150 if (oSelectorGroup.match(oNode))
152 oNode = oNode.parentNode;
160 * Queries a CSS selector or selector group against the document and returns one node.
161 * @param selector The selector
162 * @return The selected node.
164 function queryDocument(sSelector) {
165 return query(document.documentElement, sSelector);
169 * Queries a CSS selector or selector group against the document.
170 * @param selector The selector
171 * @return Array containing the selected nodes.
173 function queryDocumentAll(sSelector) {
174 return queryAll(document.documentElement, sSelector);
182 * SelectorGroup class
183 * See: http://www.w3.org/TR/css3-selectors/#selector-syntax
185 var SelectorGroup = function() {
190 * Add a selector to the selector group.
191 * @param selector The selector
193 SelectorGroup.prototype.add = function(oSelector) {
194 if (!oSelector instanceof Selector)
195 throw new Error('Argument must be a Selector.');
196 this.selectors[this.selectors.length] = oSelector;
200 * Check whether the selector is in a valid final state.
201 * @return true if valid final state
203 SelectorGroup.prototype.validate = function() {
204 var iLen = this.selectors.length,
206 for (var i = 0; i < iLen && bValid; i++)
207 bValid = this.selectors[i].validate();
212 * Matches a node against this selector group.
213 * @param node The node
214 * @return true if the selector matches.
216 SelectorGroup.prototype.match = function(oNode) {
219 for (var i = 0, iLen = this.selectors.length; i < iLen && !bMatch; i++) {
220 oSelector = this.selectors[i];
221 bMatch = oSelector.match(oNode);
227 * Queries a CSS selector group against a node.
228 * @param node The node
229 * @param singleResult If true, returns immediately when first match is found.
230 * @return The result array
232 SelectorGroup.prototype.query = function(oNode, bSingleResult) {
235 for (var i = 0, iLen = this.selectors.length; i < iLen && !(bSingleResult && bMatch); i++)
236 bMatch = this.selectors[i].query(oNode, aResult, bSingleResult) || bMatch;
237 // reset __queryResult variable
238 for (var i = 0, iLen = aResult.length; i < iLen; i++)
239 aResult[i].__queryResult = false;
248 * See: http://www.w3.org/TR/css3-selectors/#selectors
250 var Selector = function() {
251 this.combinators = [];
255 * Add a combinator to the selector.
256 * @param combinator The combinator
258 Selector.prototype.add = function(oCombinator) {
259 if (!oCombinator instanceof Combinator)
260 throw new Error('Argument must be a Combinator.');
261 var iLen = this.combinators.length;
263 this.combinators[iLen - 1].nextCombinator = oCombinator;
264 oCombinator.previousCombinator = this.combinators[iLen - 1];
266 this.combinators[iLen] = oCombinator;
270 * Check whether the selector is in a valid final state.
271 * @return true if valid final state
273 Selector.prototype.validate = function() {
274 var iLen = this.combinators.length,
277 for (var i = 0; i < iLen && bValid; i++) {
278 oCombinator = this.combinators[i];
279 bValid = ((i == 0 && oCombinator instanceof RootCombinator) ||
280 (i > 0 && !(oCombinator instanceof RootCombinator))) &&
281 oCombinator.validate();
287 * Matches a node against a selector.
288 * @param node The node
289 * @return true if the selector matches.
291 Selector.prototype.match = function(oNode) {
292 return this.combinators[this.combinators.length - 1].match(oNode);
296 * Queries a CSS selector group against a node.
297 * @param node The node
298 * @param result The result array
299 * @param singleResult If true, returns immediately when first match is found.
300 * @return true if at least one match
302 Selector.prototype.query = function(oNode, aResult, bSingleResult) {
303 return this.combinators[0].query(oNode, aResult, bSingleResult);
311 * Abstract base class for combinator implementations
312 * See: http://www.w3.org/TR/css3-selectors/#combinators
314 var Combinator = function() {
315 this.simpleSelectors = [];
316 this.nextCombinator = null;
317 this.previousCombinator = null;
321 * Add a simpleSelector to the combinator.
322 * @param simpleSelector The simple selector
324 Combinator.prototype.add = function(oSimpleSelector) {
325 if (!oSimpleSelector instanceof SimpleSelector)
326 throw new Error('Argument must be a SimpleSelector.');
327 this.simpleSelectors[this.simpleSelectors.length] = oSimpleSelector;
331 * Check whether the combinator is in a valid final state.
332 * @return true if valid final state
334 Combinator.prototype.validate = function() {
335 var iLen = this.simpleSelectors.length,
338 for (var i = 0; i < iLen && bValid; i++) {
339 oSimpleSelector = this.simpleSelectors[i];
340 bValid = (i == 0 || !(oSimpleSelector instanceof UniversalSelector)) &&
341 (i == 0 || !(oSimpleSelector instanceof TypeSelector)) &&
342 oSimpleSelector.validate();
348 * Matches a node against a combinator.
349 * @param node The node
350 * @param start The first simple selector to match
351 * @return true if the selector matches.
353 Combinator.prototype.matchSimpleSelectors = function(oNode, iStart) {
355 for (var i = iStart || 0, iLen = this.simpleSelectors.length; i < iLen && bMatch; i++)
356 bMatch = this.simpleSelectors[i].match(oNode);
361 * Matches a node against a combinator, and adds it to the query results when it matches.
362 * @param node The node
363 * @param result The result array
364 * @param singleResult If true, returns immediately when first match is found.
365 * @param start The first simpleSelector to match (used to skip selectors that have already been satisfied)
366 * @return true if the selector matches.
368 Combinator.prototype.querySimpleSelectors = function(oNode, aResult, bSingleResult, iStart) {
369 if (!this.nextCombinator) {
370 // if last combinator
371 if (oNode.__queryResult !== true && this.matchSimpleSelectors(oNode, iStart)) {
372 // __queryResult makes sure it never returns an element more than once
373 aResult[aResult.length] = oNode;
374 return oNode.__queryResult = true;
376 // if already matched before, pretend it doesn't match and skip
379 if (this.matchSimpleSelectors(oNode, iStart))
380 return this.nextCombinator.query(oNode, aResult, bSingleResult);
385 * Matches a node against a combinator.
386 * @param node The node
387 * @return true if the selector matches.
389 Combinator.prototype.match = function(oNode) {
390 var bMatch = this.matchSimpleSelectors(oNode);
394 return this.matchNext(oNode);
398 * Matches on the next combinator.
399 * @param node The node
400 * Note: this method is replaced with a matcher function.
402 Combinator.prototype.matchNext = function(oNode) {
403 // throw new Error('Must implement...');
407 * Queries a CSS selector group against a selector.
408 * Note: this method is replaced with a matcher function.
409 * @param node The node
410 * @param result The result array
411 * @param singleResult If true, returns immediately when first match is found.
413 Combinator.prototype.query = function(oNode, aResult, bSingleResult) {
414 // throw new Error('Must implement...');
418 * Descendant combinator (white space)
420 var DescendantCombinator = function() {
421 Combinator.call(this);
423 extend(Combinator, DescendantCombinator);
425 DescendantCombinator.prototype.matchNext = function(oNode) {
427 while (!bMatch && (oNode = oNode.parentNode) && oNode.nodeType == 1)
428 bMatch = this.previousCombinator.match(oNode);
432 DescendantCombinator.prototype.query = function(oNode, aResult, bSingleResult) {
434 oSimpleSelector = this.simpleSelectors[0],
436 // Note: possible Firefox optimisation: use .getElementsByClassName
437 if (oSimpleSelector instanceof TypeSelector) {
438 aNodes = oNode.getElementsByTagName(oSimpleSelector.arg1);
441 if (oSimpleSelector instanceof IdSelector) {
442 var oNode2 = oNode.ownerDocument.getElementById(oSimpleSelector.arg1);
443 if (oNode2 && (oNode.contains ? oNode2 != oNode && oNode.contains(oNode2) : !!(oNode.compareDocumentPosition(oNode2) & 16)))
444 return this.querySimpleSelectors(oNode2, aResult, bSingleResult, 1);
446 aNodes = getAllDescendants(oNode);
449 for (var i = 0, iLen = aNodes.length; i < iLen && !(bSingleResult && bMatch); i++) {
450 bMatch = this.querySimpleSelectors(aNodes[i], aResult, bSingleResult, iStart) || bMatch;
456 * Child combinator (>)
458 var ChildCombinator = function() {
459 Combinator.call(this);
461 extend(Combinator, ChildCombinator);
463 ChildCombinator.prototype.matchNext = function(oNode) {
464 return !!(oNode = oNode.parentNode) && oNode.nodeType == 1 && this.previousCombinator.match(oNode);
467 ChildCombinator.prototype.query = function(oNode, aResult, bSingleResult) {
469 oSimpleSelector = this.simpleSelectors[0],
471 if (browser_ie && oSimpleSelector instanceof TypeSelector) {
472 aNodes = oNode.children.tags(oSimpleSelector.arg1);
475 if (oSimpleSelector instanceof IdSelector) {
476 var oNode2 = oNode.ownerDocument.getElementById(oSimpleSelector.arg1);
477 if (oNode2.parentNode == oNode)
478 return this.querySimpleSelectors(oNode2, aResult, bSingleResult, 1);
480 aNodes = oNode.children || oNode.childNodes;
483 for (var i = 0, iLen = aNodes.length; i < iLen && !(bSingleResult && bMatch); i++) {
484 if (aNodes[i].nodeType == 1)
485 bMatch = this.querySimpleSelectors(aNodes[i], aResult, bSingleResult, iStart) || bMatch;
491 * Sibling combinator (+)
493 var AdjacentCombinator = function() {
494 Combinator.call(this);
496 extend(Combinator, AdjacentCombinator);
498 AdjacentCombinator.prototype.matchNext = function(oNode) {
499 while ((oNode = oNode.previousSibling) && oNode.nodeType != 1)
501 return !!oNode && this.previousCombinator.match(oNode);
504 AdjacentCombinator.prototype.query = function(oNode, aResult, bSingleResult) {
505 while (oNode = oNode.nextSibling) {
506 if (oNode.nodeType == 1) {
507 return this.querySimpleSelectors(oNode, aResult, bSingleResult, 0);
514 * Sibling combinator (~)
516 var SiblingCombinator = function() {
517 Combinator.call(this);
519 extend(Combinator, SiblingCombinator);
521 SiblingCombinator.prototype.matchNext = function(oNode) {
523 while (!bMatch && (oNode = oNode.previousSibling)) {
524 if (oNode.nodeType == 1)
525 bMatch = this.previousCombinator.match(oNode);
530 SiblingCombinator.prototype.query = function(oNode, aResult, bSingleResult) {
532 while ((oNode = oNode.nextSibling) && !(bSingleResult && bMatch)) {
533 if (oNode.nodeType == 1)
534 bMatch = this.querySimpleSelectors(oNode, aResult, bSingleResult, 0) || bMatch;
541 * This combinator does not actually exist, it just contains the first set of simple selectors.
543 var RootCombinator = function() {
544 Combinator.call(this);
546 extend(Combinator, RootCombinator);
548 RootCombinator.prototype.matchNext = function(oNode) {
552 RootCombinator.prototype.query = function(oNode, aResult, bSingleResult) {
553 var bMatch = this.querySimpleSelectors(oNode, aResult, bSingleResult, 0);
554 return DescendantCombinator.prototype.query.call(this, oNode, aResult, bSingleResult) || bMatch;
562 * Simple selector class
563 * Abstract base class for selector implementations
564 * See: http://www.w3.org/TR/css3-selectors/#simple-selectors
566 var SimpleSelector = function() {
570 * Check whether the simpleSelector is in a valid final state.
571 * @return true if valid final state
573 SimpleSelector.prototype.validate = function() {
578 * Matches a node against a simple selector.
579 * Note: this method is replaced with a matcher function.
580 * @param node The node
581 * @return true if the selector matches.
583 SimpleSelector.prototype.match = function(oNode) {
584 // throw new Error('Must implement...');
588 * Universal selector (*)
590 var UniversalSelector = function() {
591 // SimpleSelector.call(this);
593 extend(SimpleSelector, UniversalSelector);
595 UniversalSelector.prototype.match = function(oNode) {
600 * Type selector (elementName)
602 var TypeSelector = function(sArg1) {
603 // SimpleSelector.call(this);
606 extend(SimpleSelector, TypeSelector);
608 TypeSelector.prototype.match = function(oNode) {
609 var bXHTML = oNode.namespaceURI && oNode.namespaceURI != 'http://www.w3.org/1999/xhtml';
610 var sNodeName = bXHTML ? oNode.nodeName : oNode.nodeName.toLowerCase();
611 var sSelectorName = bXHTML ? this.arg1 : this.arg1.toLowerCase();
612 return sNodeName == sSelectorName;
616 * Class selector (.className)
618 var ClassSelector = function(sArg1) {
619 // SimpleSelector.call(this);
622 this.classRegexp = new RegExp('(^|\\s)' + Parser.escapeRegexp(sArg1) + '($|\\s)');
624 extend(SimpleSelector, ClassSelector);
626 ClassSelector.prototype.match = function(oNode) {
627 return this.classRegexp.test(oNode.className);
633 var IdSelector = function(sArg1) {
634 // SimpleSelector.call(this);
637 extend(SimpleSelector, IdSelector);
639 IdSelector.prototype.match = function(oNode) {
640 return oNode.getAttribute('id') == this.arg1;
644 * Attribute exist selector ([name])
646 var AttributeExistSelector = function(sArg1) {
647 // SimpleSelector.call(this);
650 extend(SimpleSelector, AttributeExistSelector);
652 AttributeExistSelector.prototype.match = function(oNode) {
653 return hasAttribute(oNode, this.arg1);
657 * Attribute equals selector ([name=value])
659 var AttributeEqualsSelector = function(sArg1, sArg2) {
660 // SimpleSelector.call(this);
664 extend(SimpleSelector, AttributeEqualsSelector);
666 AttributeEqualsSelector.prototype.match = function(oNode) {
667 if (!hasAttribute(oNode, this.arg1))
669 return getAttribute(oNode, this.arg1) == this.arg2;
673 * Attribute list selector ([name~=value])
675 var AttributeListSelector = function(sArg1, sArg2) {
676 // SimpleSelector.call(this);
680 this.attrListRegexp = regexp_whiteSpace.test(sArg2) ?
681 /$x^/ : // regexp that doesn’t match anything
682 new RegExp('(^|\\s)' + Parser.escapeRegexp(sArg2) + '($|\\s)');
684 extend(SimpleSelector, AttributeListSelector);
686 AttributeListSelector.prototype.match = function(oNode) {
687 return !!this.arg2 && this.attrListRegexp.test(getAttribute(oNode, this.arg1));
691 * Attribute language selector ([name!=en-US])
693 var AttributeLangSelector = function(sArg1, sArg2) {
694 // SimpleSelector.call(this);
698 extend(SimpleSelector, AttributeLangSelector);
700 AttributeLangSelector.prototype.match = function(oNode) {
701 if (!hasAttribute(oNode, this.arg1))
703 var sAttrValue = getAttribute(oNode, this.arg1);
704 return sAttrValue.toLowerCase().indexOf(this.arg2.toLowerCase()) == 0 &&
705 (this.arg2.length == sAttrValue.length || sAttrValue.charAt(this.arg2.length) == '-');
709 * Attribute start selector ([name^=val])
711 var AttributeStartSelector = function(sArg1, sArg2) {
712 // SimpleSelector.call(this);
716 extend(SimpleSelector, AttributeStartSelector);
718 AttributeStartSelector.prototype.match = function(oNode) {
719 return !!this.arg2 && getAttribute(oNode, this.arg1).indexOf(this.arg2) == 0;
723 * Attribute end selector ([name$=lue])
725 var AttributeEndSelector = function(sArg1, sArg2) {
726 // SimpleSelector.call(this);
730 extend(SimpleSelector, AttributeEndSelector);
732 AttributeEndSelector.prototype.match = function(oNode) {
733 var sAttrValue = getAttribute(oNode, this.arg1);
734 return !!this.arg2 && sAttrValue.indexOf(this.arg2) == (sAttrValue.length - this.arg2.length);
738 * Attribute contains selector ([name*=alu])
740 var AttributeContainsSelector = function(sArg1, sArg2) {
741 // SimpleSelector.call(this);
745 extend(SimpleSelector, AttributeContainsSelector);
747 AttributeContainsSelector.prototype.match = function(oNode) {
748 return !!this.arg2 && getAttribute(oNode, this.arg1).indexOf(this.arg2) != -1;
752 * Root pseudo-class selector (:root)
754 var RootSelector = function() {
755 // SimpleSelector.call(this);
757 extend(SimpleSelector, RootSelector);
759 RootSelector.prototype.match = function(oNode) {
760 return oNode.ownerDocument.documentElement == oNode;
764 * Abstract base class for nth-* selectors
766 var NthSelector = function(sArg) {
767 // SimpleSelector.call(this);
772 } else if (sArg == 'even') {
775 n = sArg.indexOf('n');
776 a = sArg.substring(0, n);
777 b = sArg.substring(n + 1);
778 a = n == -1 ? 0 : a == '' ? 1 : a == '-' ? -1 : Number(a);
779 b = b == '' ? 0 : Number(b);
784 extend(SimpleSelector, NthSelector);
787 * Nth-child pseudo-class selector (:nth-child(3n+1))
789 var NthChildSelector = function(sArg) {
790 NthSelector.call(this, sArg);
792 extend(NthSelector, NthChildSelector);
794 NthChildSelector.prototype.match = function(oNode) {
795 var iCount = 1, a = this.a, b = this.b;
796 if (a == 1 && b == 0)
798 while ((oNode = oNode.previousSibling) && (a > 0 || iCount <= b))
799 if (oNode.nodeType == 1)
801 return a > 0 ? (iCount - b) % a == 0 : a < 0 ? iCount <= b && iCount % a == 0 : iCount - b == 0;
805 * Nth-last-child pseudo-class selector (:nth-last-child(3n+1))
807 var NthLastChildSelector = function(sArg) {
808 NthSelector.call(this, sArg);
810 extend(NthSelector, NthLastChildSelector);
812 NthLastChildSelector.prototype.match = function(oNode) {
813 var iCount = 1, a = this.a, b = this.b;
814 if (a == 1 && b == 0)
816 while ((oNode = oNode.nextSibling) && (a > 0 || iCount <= b))
817 if (oNode.nodeType == 1)
819 return a > 0 ? (iCount - b) % a == 0 : a < 0 ? iCount <= b && iCount % a == 0 : iCount - b == 0;
823 * Nth of type pseudo-class selector (:nth-of-type(3n+1))
825 var NthOfTypeSelector = function(sArg) {
826 NthSelector.call(this, sArg);
828 extend(NthSelector, NthOfTypeSelector);
830 NthOfTypeSelector.prototype.match = function(oNode) {
831 var iCount = 1, a = this.a, b = this.b, sNodeName = oNode.nodeName;
832 if (a == 1 && b == 0)
834 while ((oNode = oNode.previousSibling) && (a > 0 || iCount <= b))
835 if (oNode.nodeType == 1 && oNode.nodeName == sNodeName)
837 return a > 0 ? (iCount - b) % a == 0 : a < 0 ? iCount <= b && iCount % a == 0 : iCount - b == 0;
841 * Nth last of type pseudo-class selector (:nth-last-of-type(3n+1))
843 var NthLastOfTypeSelector = function(sArg) {
844 NthSelector.call(this, sArg);
846 extend(NthSelector, NthLastOfTypeSelector);
848 NthLastOfTypeSelector.prototype.match = function(oNode) {
849 var iCount = 1, a = this.a, b = this.b, sNodeName = oNode.nodeName;
850 if (a == 1 && b == 0)
852 while ((oNode = oNode.nextSibling) && (a > 0 || iCount <= b))
853 if (oNode.nodeType == 1 && oNode.nodeName == sNodeName)
855 return a > 0 ? (iCount - b) % a == 0 : a < 0 ? iCount <= b && iCount % a == 0 : iCount - b == 0;
859 * First child pseudo-class selector (:first-child)
861 var FirstChildSelector = function() {
862 // SimpleSelector.call(this);
864 extend(SimpleSelector, FirstChildSelector);
866 FirstChildSelector.prototype.match = function firstChildMatch(oNode) {
868 oNode = oNode.previousSibling;
869 while (oNode && oNode.nodeType != 1);
874 * Last child pseudo-class selector (:last-child)
876 var LastChildSelector = function() {
877 // SimpleSelector.call(this);
879 extend(SimpleSelector, LastChildSelector);
881 LastChildSelector.prototype.match = function lastChildMatch(oNode) {
883 oNode = oNode.nextSibling;
884 while (oNode && oNode.nodeType != 1);
889 * First of type pseudo-class selector (:first-of-type)
891 var FirstOfTypeSelector = function() {
892 // SimpleSelector.call(this);
894 extend(SimpleSelector, FirstOfTypeSelector);
896 FirstOfTypeSelector.prototype.match = function firstOfTypeMatch(oNode) {
897 var sNodeName = oNode.nodeName;
899 oNode = oNode.previousSibling;
900 while (oNode && (oNode.nodeType != 1 || oNode.nodeName != sNodeName));
905 * Last of type pseudo-class selector (:last-of-type)
907 var LastOfTypeSelector = function() {
908 // SimpleSelector.call(this);
910 extend(SimpleSelector, LastOfTypeSelector);
912 LastOfTypeSelector.prototype.match = function lastOfTypeMatch(oNode) {
913 var sNodeName = oNode.nodeName;
915 oNode = oNode.nextSibling;
916 while (oNode && (oNode.nodeType != 1 || oNode.nodeName != sNodeName));
921 * Only child pseudo-class selector (:only-child)
923 var OnlyChildSelector = function() {
924 // SimpleSelector.call(this);
926 extend(SimpleSelector, OnlyChildSelector);
928 OnlyChildSelector.prototype.match = function(oNode) {
929 return FirstChildSelector.prototype.match.call(this, oNode) &&
930 LastChildSelector.prototype.match.call(this, oNode);
934 * Only of type pseudo-class selector (:only-of-type)
936 var OnlyOfTypeSelector = function() {
937 // SimpleSelector.call(this);
939 extend(SimpleSelector, OnlyOfTypeSelector);
941 OnlyOfTypeSelector.prototype.match = function(oNode) {
942 return FirstOfTypeSelector.prototype.match.call(this, oNode) &&
943 LastOfTypeSelector.prototype.match.call(this, oNode);
947 * Empty pseudo-class selector (:empty)
949 var EmptySelector = function() {
950 // SimpleSelector.call(this);
952 extend(SimpleSelector, EmptySelector);
954 EmptySelector.prototype.match = function(oNode) {
955 oNode = oNode.firstChild;
956 while (oNode && oNode.nodeType != 1 && (oNode.nodeType != 3 || oNode.length == 0))
957 oNode = oNode.nextSibling;
962 * Language pseudo-class selector (:lang(nl))
964 var LangSelector = function(sArg1) {
965 // SimpleSelector.call(this);
968 extend(SimpleSelector, LangSelector);
970 LangSelector.prototype.match = function(oNode) {
973 sLang = oNode.lang || '';
974 oNode = oNode.parentNode;
975 } while (!sLang && oNode && oNode.nodeType == 1);
976 // TODO: add support for language from META element (Content-Language)
977 return sLang.toLowerCase().indexOf(this.arg1.toLowerCase()) == 0 &&
978 (this.arg1.length == sLang.length || sLang.charAt(this.arg1.length) == '-');
982 * Enabled pseudo-class selector (:enabled)
984 var EnabledSelector = function() {
985 // SimpleSelector.call(this);
987 extend(SimpleSelector, EnabledSelector);
989 EnabledSelector.prototype.match = function(oNode) {
990 var sTagName = oNode.tagName.toUpperCase();
991 return sTagName != 'LINK' && sTagName != 'STYLE' && 'disabled' in oNode && !oNode.disabled;
995 * Disabled pseudo-class selector (:disabled)
997 var DisabledSelector = function() {
998 // SimpleSelector.call(this);
1000 extend(SimpleSelector, DisabledSelector);
1002 DisabledSelector.prototype.match = function(oNode) {
1003 var sTagName = oNode.tagName.toUpperCase();
1004 return sTagName != 'LINK' && sTagName != 'STYLE' && 'disabled' in oNode && oNode.disabled;
1008 * Checked pseudo-class selector (:checked)
1010 var CheckedSelector = function() {
1011 // SimpleSelector.call(this);
1013 extend(SimpleSelector, CheckedSelector);
1015 CheckedSelector.prototype.match = function(oNode) {
1016 var sTagName = oNode.tagName.toUpperCase();
1017 return sTagName == 'INPUT' && oNode.checked || sTagName == 'OPTION' && oNode.selected;
1021 * Read-only pseudo-class selector (:read-only)
1023 var ReadOnlySelector = function() {
1024 // SimpleSelector.call(this);
1026 extend(SimpleSelector, ReadOnlySelector);
1028 ReadOnlySelector.prototype.match = function(oNode) {
1029 return !('readOnly' in oNode) || oNode.readOnly;
1033 * Read-write pseudo-class selector (:read-write)
1035 var ReadWriteSelector = function() {
1036 // SimpleSelector.call(this);
1038 extend(SimpleSelector, ReadWriteSelector);
1040 ReadWriteSelector.prototype.match = function(oNode) {
1041 return 'readOnly' in oNode && !oNode.readOnly;
1045 * Target pseudo-class selector (:target)
1047 var TargetSelector = function() {
1048 // SimpleSelector.call(this);
1050 extend(SimpleSelector, TargetSelector);
1052 TargetSelector.prototype.match = function(oNode) {
1053 return '#' + oNode.getAttribute('id') == oNode.ownerDocument.location.hash;
1057 * Negation pseudo-class selector (:not)
1059 var NotSelector = function(oSimpleSelector) {
1060 // SimpleSelector.call(this);
1061 if (!oSimpleSelector instanceof SimpleSelector)
1062 throw new Error('Argument must be a SimpleSelector.');
1063 this.simpleSelector = oSimpleSelector;
1065 extend(SimpleSelector, NotSelector);
1067 NotSelector.prototype.validate = function() {
1068 return !(this.simpleSelector instanceof NotSelector);
1071 NotSelector.prototype.match = function(oNode) {
1072 return !this.simpleSelector.match(oNode);
1078 * Parser object constructor
1079 * @class Parser class
1082 var Parser = function() {
1086 * Parser regexp macros and patterns for the tokenizer
1087 * See: http://www.w3.org/TR/CSS21/syndata.html#tokenization
1090 var pattern_w = '[ \t\r\n\f]*',
1091 pattern_S = '[ \t\r\n\f]+',
1092 pattern_escape = '\\\\[0-9a-fA-F]{1,6}(?:\\r\\n|[ \\n\\r\\t\\f])?|\\\\[^\\n\\r\\f0-9a-fA-F]',
1093 pattern_nlescape = '\\\\(?:\\r\\n|[\\n\\r\\f])',
1094 pattern_ident = '(?:[-]?(?:[_a-zA-Z]|[^\\0-\\177]|' + pattern_escape + ')(?:[_a-zA-Z0-9-]|[^\\0-\\177]|' + pattern_escape + ')*)',
1095 // pattern_ns_ident = '(?:(?:' + pattern_ident + '|\\*)?\\|)?' + pattern_ident,
1096 pattern_string1 = '\\"(?:[^\\n\\r\\f\\\\"]|' + pattern_nlescape + '|' + pattern_escape + ')*\\"',
1097 pattern_string2 = "\\'(?:[^\\n\\r\\f\\\\']|" + pattern_nlescape + "|" + pattern_escape + ")*\\'",
1098 pattern_string = '(?:' + pattern_string1 + '|' + pattern_string2 + ')',
1099 // pattern_nth = '(?:-?[0-9]*n)?(?:[+\\-][0-9]+)?|odd|even',
1101 pattern_class = '\\.' + pattern_ident,
1102 pattern_id = '#' + pattern_ident,
1103 pattern_attribute = '\\[(' + pattern_ident + ')(?:([~|\\^\\$*]?=)(' + pattern_ident + '|' + pattern_string + '))?\\]',
1104 pattern_pseudo = ':' + pattern_ident + '(?:\\([^)]+\\))?',
1105 pattern_universal = '\\*', // '(?:' + pattern_ident + '\\|)\\*'
1106 pattern_element = pattern_ident, // pattern_ns_ident
1108 // Note: combinators must be placed inbetween two \s* patterns
1109 pattern_child = pattern_w + '>' + pattern_w,
1110 pattern_adjacent = pattern_w + '\\+' + pattern_w,
1111 pattern_sibling = pattern_w + '~' + pattern_w,
1112 pattern_comma = pattern_w + ',' + pattern_w,
1113 pattern_descendant = pattern_S,
1114 pattern_negation = ':not\\((?:' + [pattern_class, pattern_id, pattern_attribute,
1115 pattern_pseudo, pattern_universal,
1116 pattern_element].join('|') + ')\\)';
1120 * Regular expression objects for patterns
1122 var regexp_selector = new RegExp([
1123 pattern_class, // first all selectors
1130 pattern_child, // then the combinators
1134 pattern_descendant // descendant combinator must be last
1136 regexp_attribute = new RegExp(pattern_attribute),
1137 regexp_nlescape = new RegExp(pattern_nlescape, 'g'),
1138 regexp_pseudo = new RegExp(':([^(]+)(?:\\((.+)\\))?'),
1139 regexp_trim = /^\s+|\s+$/gm,
1140 regexp_escapeCSS = /\\([^\n\r\f0-9a-fA-F])|\\([0-9a-fA-F]{1,6})(?:\r\n|[ \n\r\t\f])?/g,
1141 regexp_escapeRegexp = /([\[\\\^\$\.\|\?\*\+\(\)\{\}])/g,
1142 regexp_whiteSpace = /\s/;
1144 var hAttrTypeMap = {
1145 '=' : AttributeEqualsSelector,
1146 '~=': AttributeListSelector,
1147 '|=': AttributeLangSelector,
1148 '^=': AttributeStartSelector,
1149 '$=': AttributeEndSelector,
1150 '*=': AttributeContainsSelector
1153 var hPseudoTypeMap = {
1154 'root' : RootSelector,
1155 'nth-child' : NthChildSelector,
1156 'nth-last-child' : NthLastChildSelector,
1157 'nth-of-type' : NthOfTypeSelector,
1158 'nth-last-of-type': NthLastOfTypeSelector,
1159 'first-child' : FirstChildSelector,
1160 'last-child' : LastChildSelector,
1161 'first-of-type' : FirstOfTypeSelector,
1162 'last-of-type' : LastOfTypeSelector,
1163 'only-child' : OnlyChildSelector,
1164 'only-of-type' : OnlyOfTypeSelector,
1165 'empty' : EmptySelector,
1166 'lang' : LangSelector,
1167 'enabled' : EnabledSelector,
1168 'disabled' : DisabledSelector,
1169 'checked' : CheckedSelector,
1170 'read-only' : ReadOnlySelector,
1171 'read-write' : ReadWriteSelector,
1172 'target' : TargetSelector
1173 // 'not' : NotSelector
1177 * Parses a CSS selector or selector group.
1178 * Uses caching to speed up subsequent calls.
1179 * @param selector The selector.
1180 * @return SelectorGroup object, null if unable to parse the selector (error message in errorMessage property).
1182 Parser.prototype.parse = function(sSelector) {
1184 var aSelectorParts = sSelector.match(regexp_selector);
1185 // check whether selector was parsed completely...
1186 if (sSelector != aSelectorParts.join(''))
1187 throw new ParsingException(sSelector);
1189 // instantiate initial object model
1190 var oSelectorGroup = new SelectorGroup(),
1191 oSelector = new Selector(),
1192 oCombinator = new RootCombinator();
1193 oSelectorGroup.add(oSelector);
1194 oSelector.add(oCombinator);
1196 // post-process segments
1197 for (var i = 0, iLen = aSelectorParts.length; i < iLen; i++) {
1198 // trim combinators with excessive space, note that descendant combinator becomes ''
1199 var sSelectorPart = aSelectorParts[i].replace(regexp_trim, '');
1200 switch (sSelectorPart.charAt(0)) {
1203 oSelector = new Selector();
1204 oCombinator = new RootCombinator();
1205 oSelectorGroup.add(oSelector);
1206 oSelector.add(oCombinator);
1210 oCombinator.add(new UniversalSelector());
1213 oCombinator.add(new ClassSelector(Parser.decodeIdent(sSelectorPart.substr(1))));
1216 oCombinator.add(new IdSelector(Parser.decodeIdent(sSelectorPart.substr(1))));
1219 oCombinator.add(this.parseAttributeSelector(sSelectorPart));
1223 oCombinator.add(this.parsePseudoSelector(sSelectorPart));
1225 if (e instanceof ParsingException)
1226 throw new ParsingException(sSelector);
1232 oCombinator = new DescendantCombinator();
1233 oSelector.add(oCombinator);
1236 oCombinator = new ChildCombinator();
1237 oSelector.add(oCombinator);
1240 oCombinator = new AdjacentCombinator();
1241 oSelector.add(oCombinator);
1244 oCombinator = new SiblingCombinator();
1245 oSelector.add(oCombinator);
1247 // element selector (no single prefix)
1249 oCombinator.add(new TypeSelector(Parser.decodeIdent(sSelectorPart)));
1253 if (!oSelectorGroup.validate())
1254 throw new ParsingException(sSelector);
1256 return oSelectorGroup;
1259 Parser.prototype.parseAttributeSelector = function(sSelectorPart) {
1260 var aParsedValues = regexp_attribute.exec(sSelectorPart);
1261 var sName = Parser.decodeIdent(aParsedValues[1]);
1262 if (!aParsedValues[2]) {
1263 return new AttributeExistSelector(sName);
1265 var sValue = aParsedValues[3].charAt(0) == '"' || aParsedValues[3].charAt(0) == "'" ?
1266 Parser.decodeString(aParsedValues[3]) :
1267 Parser.decodeIdent(aParsedValues[3]);
1268 return this.createAttributeSelector(aParsedValues[2], sName, sValue);
1273 * Returns a new Attribute*Selector instance that matches on name and value.
1274 * Note that the grammar parsing has already prevented any non-existant identifier
1275 * from occurring here, so no existence check needs to be done.
1276 * @param sIdentifier The bit between the name and value in the selector (e.g. ^=)
1277 * @param sName Name to match on
1278 * @param sValue Value to match on
1279 * @return A new Attribute*Selector instance.
1281 Parser.prototype.createAttributeSelector = function(sIdentifier, sName, sValue) {
1282 return new hAttrTypeMap[sIdentifier](sName, sValue);
1285 Parser.prototype.parsePseudoSelector = function(sSelectorPart) {
1286 var aParsedPseudo = regexp_pseudo.exec(sSelectorPart),
1287 sIdentifier = Parser.decodeIdent(aParsedPseudo[1]);
1288 if (sIdentifier == 'not') {
1289 var oTempGroup = this.parse(aParsedPseudo[2]);
1290 var oTempCombinator = oTempGroup.selectors[0].combinators[0];
1291 if (oTempCombinator.simpleSelectors.length != 1)
1292 throw new ParsingException(sSelectorPart);
1293 return new NotSelector(oTempCombinator.simpleSelectors[0]);
1295 return this.createPseudoSelector(sIdentifier, aParsedPseudo[2]);
1300 * Returns a SimpleSelector instance matching the passed pseudo-class name
1301 * @param sIdentifier Pseudo-class name
1302 * @param sArgument Argument to pass to the pseudo-class
1303 * @param sSelector to use by the
1304 * @return A new SimpleSelector instance
1306 Parser.prototype.createPseudoSelector = function(sIdentifier, sArgument) {
1307 if (!hPseudoTypeMap[sIdentifier])
1308 throw new ParsingException(sIdentifier);
1309 return new hPseudoTypeMap[sIdentifier](sArgument);
1313 * Decode a CSS identifier
1314 * @param identifier The identifier
1315 * @return Decoded string
1317 Parser.decodeIdent = function(sIdentifier) {
1318 sIdentifier = sIdentifier.replace(regexp_escapeCSS,
1319 function(sEsc, sCharacter, sUnicode) {
1323 return String.fromCharCode(parseInt(sUnicode, 16));
1330 * Decode a CSS string
1331 * @param string The string (including quotes)
1332 * @return Decoded string
1334 Parser.decodeString = function(sString) {
1335 /* Atm this check is not necessary, because
1336 * the parser patterns do not allow this...
1337 var sFirstChar = sString.charAt(0),
1338 sLastChar = sString.charAt(sString.length - 1);
1339 if (sString.length < 2 ||
1340 !((sFirstChar == "'" && sLastChar == "'") ||
1341 (sFirstChar == '"' && sLastChar == '"')))
1342 throw new ParsingException(sString);
1344 sString = sString.substring(1, sString.length - 1);
1345 sString = sString.replace(regexp_nlescape, '');
1346 return Parser.decodeIdent(sString);
1350 * Escapes a string for use in a regular expression.
1351 * @param string The string.
1352 * @return The escaped string.
1354 Parser.escapeRegexp = function(sString) {
1355 return sString.replace(regexp_escapeRegexp, '\\$1');
1361 * A subclass of Parser that provides caching of parsed selectors.
1363 var CachingParser = function() {
1367 extend(Parser, CachingParser);
1369 CachingParser.prototype.parse = function(sSelector) {
1370 if (!(sSelector in this.cache))
1371 this.cache[sSelector] = Parser.prototype.parse.call(this, sSelector);
1372 return this.cache[sSelector];
1378 * An exception object that is thrown when there is a parse error.
1380 var ParsingException = function(sSelector) {
1381 this.selector = sSelector;
1384 ParsingException.prototype.toString = function() {
1385 return 'CSS Selector parsing error: ' + this.selector;
1390 // instantiate caching parser
1391 var oParser = new CachingParser();
1398 this.queryAll = queryAll;
1400 this.queryAncestorOrSelf = queryAncestorOrSelf;
1401 this.queryDocument = queryDocument;
1402 this.queryDocumentAll = queryDocumentAll;
1403 this.SelectorGroup = SelectorGroup;
1404 this.Selector = Selector;
1405 this.Combinator = Combinator;
1406 this.DescendantCombinator = DescendantCombinator;
1407 this.ChildCombinator = ChildCombinator;
1408 this.AdjacentCombinator = AdjacentCombinator;
1409 this.SiblingCombinator = SiblingCombinator;
1410 this.RootCombinator = RootCombinator;
1411 this.SimpleSelector = SimpleSelector;
1412 this.UniversalSelector = UniversalSelector;
1413 this.TypeSelector = TypeSelector;
1414 this.ClassSelector = ClassSelector;
1415 this.IdSelector = IdSelector;
1416 this.AttributeExistSelector = AttributeExistSelector;
1417 this.AttributeEqualsSelector = AttributeEqualsSelector;
1418 this.AttributeListSelector = AttributeListSelector;
1419 this.AttributeLangSelector = AttributeLangSelector;
1420 this.AttributeStartSelector = AttributeStartSelector;
1421 this.AttributeEndSelector = AttributeEndSelector;
1422 this.AttributeContainsSelector = AttributeContainsSelector;
1423 this.RootSelector = RootSelector;
1424 this.NthSelector = NthSelector;
1425 this.NthChildSelector = NthChildSelector;
1426 this.NthLastChildSelector = NthLastChildSelector;
1427 this.NthOfTypeSelector = NthOfTypeSelector;
1428 this.NthLastOfTypeSelector = NthLastOfTypeSelector;
1429 this.FirstChildSelector = FirstChildSelector;
1430 this.LastChildSelector = LastChildSelector;
1431 this.FirstOfTypeSelector = FirstOfTypeSelector;
1432 this.LastOfTypeSelector = LastOfTypeSelector;
1433 this.OnlyChildSelector = OnlyChildSelector;
1434 this.OnlyOfTypeSelector = OnlyOfTypeSelector;
1435 this.EmptySelector = EmptySelector;
1436 this.LangSelector = LangSelector;
1437 this.EnabledSelector = EnabledSelector;
1438 this.DisabledSelector = DisabledSelector;
1439 this.CheckedSelector = CheckedSelector;
1440 this.ReadOnlySelector = ReadOnlySelector;
1441 this.ReadWriteSelector = ReadWriteSelector;
1442 this.TargetSelector = TargetSelector;
1443 this.NotSelector = NotSelector;
1444 this.Parser = Parser;
1445 this.CachingParser = CachingParser;
1446 this.ParsingException = ParsingException;
1447 this.parser = oParser;