/*
 * Define known namespace URIs
 *
 * DEFAULT is a special case representing the default namespace
 * of the document, which is not necessarily a known namespace
 */
var namespaceURI = new Object();
namespaceURI["DEFAULT"] = document.documentElement.namespaceURI;
namespaceURI["XML"] = "http://www.w3.org/XML/1998/namespace";
namespaceURI["XHTML"] = "http://www.w3.org/1999/xhtml";
namespaceURI["MATHML"] = "http://www.w3.org/1998/Math/MathML";
namespaceURI["SVG"]    = "http://www.w3.org/2000/svg";


var ElementClassName = (function() {
	// Define class attributes from all known namespaces.
	// A class attribute's localName may not necessarily be "class" in some namespaces.
	function classAttribute(namespaceURI, localName) {
		this.namespaceURI = namespaceURI;
		this.localName = localName
	}
	var classAttributes = new Array();
	classAttributes["mathml"] = new classAttribute(namespaceURI["MATHML"], "class");
	classAttributes["xhtml"]  = new classAttribute(namespaceURI["XHTML"],  "class");
	classAttributes["svg"]    = new classAttribute(namespaceURI["SVG"],    "class");

	// Determine if the document is HTML, not XHTML or XML
	var isHTML = (function() {
		var htmlElement = document.getElementsByTagName("html").item(0);
		if (htmlElement) {
			return (htmlElement.tagName == "HTML");
		} else {
			return false;
		}
	})();

	return function() {
		this.hasClassName = function(className) {
			// Abort if no className provided or is not a valid class name
			className = className || "";
			if (!className.match(/^(\S+)$/)) return false;
	
			var re = new RegExp("(^|\\s)" + className + "(\\s|$)", "gm");
			var classNames = "";
			if (isHTML) {
				// HTML, not XHTML documents
				if (this.className) classNames = this.className;
			} else {
				// XML documents, including XHTML
				var prefix;
	
				// XHTML UAs should support HTMLDocument.className
				// If they don't, the XHTML class attribute will still be looked at later
				if (this.className) classNames = this.className + " ";
	
				// Check the namespace URI of the element
				// If the namespace URI is known, look for corresponding class attribute with no namespace
				// Don't bother searching XHTML elements for the class attribute if HTMLElement.className is supported
				if (this.namespaceURI && !(this.namespaceURI == classAttributes["xhtml"].namespaceURI && this.className != undefined)) {
					for (prefix in classAttributes) {
						if (this.namespaceURI == classAttributes[prefix].namespaceURI
						 && this.hasAttribute(classAttributes[prefix].localName)) {
							classNames += this.getAttribute(classAttributes[prefix].localName) + " ";
						}
					}
				}
	
				// Get class attributes in known namespaces from any element.
				if (this.getAttributeNS) {
					for (prefix in classAttributes) {
						if (this.hasAttributeNS(classAttributes[prefix].namespaceURI, classAttributes[prefix].localName)) {
							classNames += this.getAttributeNS(classAttributes[prefix].namespaceURI, classAttributes[prefix].localName) + " ";
						}
					}
				}
			}
	
			return re.test(classNames);
		}
	
		// Add ClassName to HTMLElement, or an XML element from a known namespace
		this.addClassName = function(className) {
			if (this.hasClassName(className)) return;
			if (isHTML) {
				// HTML, not XHTML documents
				this.className +=  " " + className;
			} else {
				// XML documents, including XHTML
				var prefix;
	
				if (this.className != undefined) {
					this.className += " " + className;
				} else if (this.namespaceURI) {
					// Check the namespace URI of the element
					// If the namespace URI is known, look for corresponding class attribute with no namespace
					// If present, append the class name; else, set the attribute.
					for (prefix in classAttributes) {
						if (this.namespaceURI == classAttributes[prefix].namespaceURI) {
							if (this.hasAttribute(classAttributes[prefix].localName)) {
								this.setAttribute(classAttributes[prefix].localName,
												  this.getAttribute(classAttributes[prefix].localName) + " " + className)
							} else {
								this.setAttribute(classAttributes[prefix].localName, className)
							}
							// ClassName added
							return true;
						}
					}
					// Element is not from a known namespace
					return false;
				} else {
					// No namespace support and HTMLElement.className is not supported, cannot add class name
					return false;
				}
			}
		}
	
		this.removeClassName = function(className) {
			var re = RegExp("(^|\\s)" + className + "(\\s|$)", "gm");
			if (isHTML) {
				// HTML, not XHTML documents
				if (this.className)	this.className = this.className.replace(re, "$1$2");
			} else {
				// XML documents, including XHTML
				var prefix, value;
				if (this.className)	this.className = this.className.replace(re, "$1$2");

				// Check the namespace URI of the element
				// If the namespace URI is known, look for corresponding class attribute with no namespace
				if (this.namespaceURI) {
					for (prefix in classAttributes) {
						if (this.namespaceURI == classAttributes[prefix].namespaceURI
						 && this.hasAttribute(classAttributes[prefix].localName)) {
							value = this.getAttribute(classAttributes[prefix].localName).replace(re, "$1$2");
							this.setAttribute(classAttributes[prefix].localName, value);
						}
					}
				}
	
				// Check class attributes in known namespaces from any element.
				if (this.getAttributeNS) {
					for (prefix in classAttributes) {
						if (this.hasAttributeNS(classAttributes[prefix].namespaceURI, classAttributes[prefix].localName)) {
							value = this.getAttributeNS(classAttributes[prefix].namespaceURI, classAttributes[prefix].localName).replace(re, "$1$2");
							this.setAttributeNS(classAttributes[prefix].namespaceURI, classAttributes[prefix].localName, value);
						}
					}
				}
			}
		}
	}
})();
ElementClassName.apply(Element.prototype);

