//Copyright Steve Holt 2005  - contact via http://www.clockbeat.com
/*
If you wish to use this code in your own web site you must:
	Contact Steve (see above) and let him know where you are using it and how
	Provide a link on your site back to http://www.clockbeat.com
	Leave all these comments in place
	Not modify the code (contact Steve if you want to do that)
*/

function CSSSelect(selector) {
	this.tagName = null;
	this.ids = new Array();
	this.pseudos = new Array();
	this.combinator = "";
	this.attributes = new Array();
	var start = 0;
	if (typeof selector == "string") {
		selector = selector.replace(/^\s+|\s+$/g,''); //Trim
		selector = CSSSelect.lexsplit([/^(\s*\+\s*|\s*>\s*|\s+)/, /^\.[\w_-]+/, /^#[\w_-]+/,
			/^\w+|\*/, /^\[([\w_-]*)([^"']*)'?"?([^'"]*)'?"?]/, /^:[\w_-]+/ ], selector); //"
	} else {
		start = arguments[1];
	}
	for (var i = start; i < selector.length; i++)
	{
		var token = selector[i];
		var string = token.match[0];
		switch (token.index) {
			case 0: //0 Combinator
				this.child = new CSSSelect(selector, i+1);
				this.combinator = string.replace(/^\s+|\s+$/g,''); //Trim
				i = selector.length;
				break;
			case 1: //1 Class
				this.attributes[this.attributes.length] = new Array("class", ".", string.substr(1), new RegExp("\\b" + string.substr(1).replace(/([\/\^$*+?.()|{}[\]])/g, "\\$1") + "\\b"));
				break;
			case 2: //2 Id
				this.ids[this.ids.length] = string.substr(1); break;
			case 3: //3 Tag
				this.tagName = (string == "*") ? null : string.toUpperCase(); break;
			case 4: //4 Attribute
				var mp = token.match;
				var rx = mp[3].replace(/([\/\^$*+?.()|{}[\]])/g, "\\$1"); //Escape
				switch (mp[2]) {
					case "=":  rx = "^" + rx + "$"; break;
					case "~=": rx = "(\\s|^)" + rx + "(\\s|$)"; break;
					case "|=": rx = "(-|^)" + rx + "(-|$)"; break;
					default: rx = ".*";
				}
				this.attributes[this.attributes.length] = new Array(mp[1], mp[2], mp[3], new RegExp(rx));
				break;
			case 5: //5 Pseudo
				this.pseudos[this.pseudos.length] = string.substr(1); break;
			}
	}
}

CSSSelect.lexsplit = function(tests, string) {
	var result = new Array();
	var index = 0;
	while (index < string.length)
	{
		var bestlen = -1;
		var bestmatch = -1;
		var hit = new Array(string.substr(index,1));
		for (var i = 0; i < tests.length; i++) {
			var m = string.substr(index).match(tests[i]);
			if (m && m[0].length > bestlen) {
				bestlen = m[0].length;
				bestmatch = i;
				hit = m;
			}
		}
		result[result.length] = {match:hit, index:bestmatch, position:index, toString:function(){return this.match[0];}};
		index += (bestlen < 1) ? 1 : bestlen;
	}
	return result;
}

CSSSelect.prototype.toString = function () {
	var s = "";
	s += (this.tagName) ? this.tagName : "";
	s += (this.ids.length == 0) ? "" : "#" + this.ids.join("#");
	s += (this.pseudos.length == 0) ? "" : ":" + this.pseudos.join(":");
	for (var i = 0; i < this.attributes.length; i++) {
		var at = this.attributes[i];
		s += (at[1] == ".") ? "." + at[2] : "[" + at[0] + at[1] + "\"" + at[2] + "\"]";
	}
	s += (this.child) ? " " + this.combinator + " " + this.child : "";
	return s;
}

CSSSelect.addUnique = function (toar, fromar) {
	var i;
	var j;
	for (i = 0; i < fromar.length; i++) {
		for (j = 0; j < toar.length; j++)
			if (toar[j] === fromar[i])
				break;
		toar[j] = fromar[i];
	}
}

CSSSelect.getNextSiblingElement = function(el) {
	var sib = el.nextSibling;
	while (sib && sib.nodeType != 1)
		sib = sib.nextSibling;
	return (sib) ? new Array(sib) : new Array();
}

CSSSelect.getChildElements = function(el) {
	var ch = el.childNodes;
	var sel = new Array();
	for (var i in ch)
		 if (ch[i].nodeType == 1)
			 sel[sel.length] = ch[i];
	return sel;
}

CSSSelect.getElementsByTagName = function(el, tag, ordered) {
	if (ordered) {
		var sel = new Array();
		var ch = CSSSelect.getChildElements(el);
		for (var i = 0; i < ch.length; i++) {
			if (!tag || tag == "*" || ch[i].tagName == tag)
				sel[sel.length] = ch[i];
			var sub = CSSSelect.getElementsByTagName(ch[i], tag, ordered);
			sel = sel.concat(sub);
		}
		return sel;
	} else {
		var sel;
		if (tag == null || tag == "*")
			sel = (el.all) ? el.all : el.getElementsByTagName("*");
		else
			sel = el.getElementsByTagName(tag);
		return sel;
	}
}

CSSSelect.prototype.getSelectedElements = function (element, ordered) {
	// Consider element and all of its decendents for a match
	if (element == null)
		element = document.getElementsByTagName("BODY")[0];
	var elArray = new Array(element);
	CSSSelect.addUnique(elArray, CSSSelect.getElementsByTagName(element, "*", ordered));
	return this.getSelectedElementsFrom(elArray, ordered);
}

CSSSelect.prototype.getSelectedElementsFrom = function (elementArray, ordered) {
	// Consider only elements in the array
	var matched = new Array();
	var nextLayer = new Array();
	for (var i = 0; i < elementArray.length; i++) {
		var el = elementArray[i];
		if (this.oneSelect(el)) {
			matched[matched.length] = el;
			if (this.child) {
				var sel = new Array();
				switch (this.combinator) {
					case "":  sel = CSSSelect.getElementsByTagName(el, this.child.tagName, ordered);
						break;
					case ">": sel = CSSSelect.getChildElements(el); break;
					case "+": sel = CSSSelect.getNextSiblingElement(el); break;
				}
				CSSSelect.addUnique(nextLayer, sel);
			}
		}
	}
	return (this.child) ? this.child.getSelectedElementsFrom(nextLayer, ordered) : matched;
}

CSSSelect.prototype.oneSelect = function (element) {
	if (element.nodeType != 1)
		return false;
	if (this.tagName && this.tagName != element.tagName)
		return false;
	for (var i = 0; i < this.ids.length; i++) {
		if (this.ids[i] != element.id)
			return false;
	}
	for (var i = 0; i < this.attributes.length; i++) {
		var att = this.attributes[i];
		var attVal = element.getAttribute(att[0]);
		if (!attVal && att[0] == "class")
			attVal = element.className;
		if (attVal == null || !attVal.match(att[3])) {
			return false;
		}
	}
	for (var i = 0; i < this.pseudos.length; i++)
	{
		if (this.pseudos[i] == "first-child") //Only one we can do
		{
			var sib = element.previousSibling;
			while (sib && sib.nodeType != 1)
				sib = sib.previousSibling;
			if (sib)
				return false;
		}
	}
	return true;
}
