function XMLDoc ()
{
	// Determine whether standard DOM Level 2 is supported.
	if (document.implementation && document.implementation.createDocument)
	{
		this.$api = 'dom';
	}
	// If not, use Microsoft's proprietary API.
	else if (window.ActiveXObject)
	{
		this.$api = 'microsoft';
	}
	// If neither is supported, throw an error and quit.
	else
	{
		throw new Error("XMLDoc cannot be instantiated: neither the standard DOM Level 2 " + 
						"nor the proprietary Microsoft API is supported");
	}

	this.$xmldoc = null;

	if (this.$api == 'dom')
	{
		try
		{
			this.$xmldoc = document.implementation.createDocument("", "", null);
		}
		catch(_error)
		{
			throw new Error("XMLDoc cannot be instantiated: unknown DOM implementation error");
		}
	}
	else
	{
		try
		{
			this.$xmldoc = new ActiveXObject("Microsoft.XMLDOM");
		}
		catch(_error)
		{
			throw new Error("XMLDoc cannot be instantiated: unknown Microsoft API error");
		}
	}

	if (!this.$xmldoc)
	{
		throw new Error("XMLDoc cannot be instantiated: document creation failed");
	}
}

XMLDoc.prototype.appendChild = function(_newChildNode) {
	if (!_newChildNode || typeof _newChildNode != "object" || !_newChildNode.nodeName || !_newChildNode.nodeType)
	{
		throw new Error("XMLDoc.appendChild() failed: missing or invalid new child node");
	}
	return this.$xmldoc.appendChild(_newChildNode);
}

XMLDoc.prototype.createComment = function(_data) {
	if (!_data || typeof _data != "string")
	{
		throw new Error("XMLDoc.createComment() failed: missing data");
	}
	return this.$xmldoc.createComment(_data);
}

XMLDoc.prototype.createElement = function(_elemName) {
	if (!_elemName || typeof _elemName != "string")
	{
		throw new Error("XMLDoc.createElement() failed: missing element name");
	}
	return this.$xmldoc.createElement(_elemName);
}

XMLDoc.prototype.createProcessingInstruction = function(_target, _data) {
	if (!_target || typeof _target != "string")
	{
		throw new Error("XMLDoc.createProcessingInstruction() failed: missing or invalid target");
	}
	if (!_data || typeof _data != "string")
	{
		throw new Error("XMLDoc.createProcessingInstruction() failed: missing or invalid data");
	}
	return this.$xmldoc.createProcessingInstruction(_target, _data);
}

XMLDoc.prototype.createTextNode = function(_data) {
	return this.$xmldoc.createTextNode(_data);
}

XMLDoc.prototype.importNode = function(_sourceNode, _deep) {
	if (!_sourceNode || typeof _sourceNode != "object" || !_sourceNode.nodeName || !_sourceNode.nodeType)
	{
		throw new Error("XMLDoc.importNode() failed: missing or invalid source node");
	}
	if (!_deep)
	{
		var _deep = false;
	}

	// If importNode is available as a native method of the standard DOM Level 2 XML object, use it.
	if (this.$xmldoc.importNode)
	{
		return this.$xmldoc.importNode(_sourceNode, _deep);
	}
	// Otherwise, recreate the functionality.
	else
	{
		var _output = false;

		if (_sourceNode.nodeType == 1)		// Element node
		{
			try
			{
				_output = this.$xmldoc.createElement(_sourceNode.nodeName);
			}
			catch(_error)
			{
				// Do nothing.
			}

			if (_sourceNode.attributes)
			{
				for (var _i = 0; _i < _sourceNode.attributes.length; _i++)
				{
					if (_sourceNode.attributes[_i].specified !== undefined && !_sourceNode.attributes[_i].specified)
					{
						continue;
					}

					var _currentAttr = _sourceNode.attributes[_i];

					try
					{
						_output.setAttribute(_currentAttr.nodeName, _currentAttr.nodeValue);
					}
					catch(_error)
					{
						// Do nothing.
					}
				}
			}

			if (_deep && _sourceNode.childNodes.length > 0)
			{
				for (var _i = 0; _i < _sourceNode.childNodes.length; _i++)
				{
					var _currentChild = _sourceNode.childNodes[_i];
					var _newChild = this.importNode(_currentChild, true);
					if (_newChild)
					{
						_output.appendChild(_newChild);
					}
				}
			}
		}
		else if (_sourceNode.nodeType == 3)	// Text node
		{
			_output = this.$xmldoc.createTextNode(_sourceNode.nodeValue);
		}
		else if (_sourceNode.nodeType == 4)	// CDATA section node
		{
			_output = this.$xmldoc.createCDATASection(_sourceNode.nodeValue);
		}
		else if (_sourceNode.nodeType == 7)	// Processing instruction node
		{
			_output = this.$xmldoc.createProcessingInstruction(_sourceNode.nodeName, _sourceNode.nodeValue);
		}
		else if (_sourceNode.nodeType == 8)	// Comment node
		{
			_output = this.$xmldoc.createComment(_sourceNode.nodeValue);
		}

		return _output;
	}
}

XMLDoc.prototype.importXML = function(_sourceDocument) {
	if (!_sourceDocument || !_sourceDocument.documentElement)
	{
		throw new Error("XMLDoc.importXML() failed: missing or invalid source document");
	}

	this.$xmldoc = _sourceDocument;
}

XMLDoc.prototype.insertBefore = function(_newChildNode, _refChildNode) {
	if (!_newChildNode || typeof _newChildNode != "object" || !_newChildNode.nodeName || !_newChildNode.nodeType)
	{
		throw new Error("XMLDoc.insertBefore() failed: missing or invalid new child node");
	}
	if (!_refChildNode || typeof _refChildNode != "object" || !_refChildNode.nodeName || !_refChildNode.nodeType)
	{
		throw new Error("XMLDoc.insertBefore() failed: missing or invalid reference child node");
	}
	return this.$xmldoc.insertBefore(_newChildNode, _refChildNode);
}

XMLDoc.prototype.load = function (_url, _handlerFunction, _handlerFunctionArgs) {
	if (!_url || typeof _url != "string")
	{
		throw new Error("Missing or invalid first argument passed to XMLDoc.load(): not a string");
	}
	if (!_handlerFunction || typeof _handlerFunction != "function")
	{
		throw new Error("Missing or invalid second argument passed to XMLDoc.load(): not a valid function");
	}
	if (_handlerFunctionArgs === undefined)
	{
		var _handlerFunctionArgs = new Array();
	}
	else if (!_handlerFunctionArgs.constructor || _handlerFunctionArgs.constructor != Array)
	{
		_handlerFunctionArgs = new Array(_handlerFunctionArgs);
	}

	// Make reference to $xmldoc property, since using the keyword "this" inside a function call will 
	// cause namespace confusion.
	var _docRef = this;

	// Use the standard DOM Level 2 technique, if it is supported.
	if (this.$api == 'dom')
	{
		// Specify what should happen when document finishes loading.
		this.$xmldoc.onload = function() { _handlerFunction.apply(_docRef, _handlerFunctionArgs); }
    }
	// Otherwise, use Microsoft's proprietary API.
	else if (window.ActiveXObject)
	{
		// Specify what should happen when document finishes loading.
		this.$xmldoc.onreadystatechange = function() {
			if (_docRef.$xmldoc.readyState == 4) {
				_handlerFunction.apply(_docRef, _handlerFunctionArgs);
			}
		}
	}
	// And finally load the desired URL.
	this.$xmldoc.load(_url);
}

XMLDoc.prototype.loadFormData = function (_formElem) {
	if (!_formElem)
	{
		throw new Error("Missing argument to XMLDoc.loadFormData()");
	}
	else if (typeof _formElem == "string" && !(_formElem = document.getElementById(_formElem)))
	{
		throw new Error("Invalid argument ('" + _formElem + "') passed to XMLDoc.loadFormData(): not a valid element ID");
	}
	if (!_formElem.nodeType || _formElem.nodeType != 1 || _formElem.nodeName.toLowerCase() != "form")
	{
		throw new Error("Invalid argument passed to XMLDoc.loadFormData(): object is not a FORM");
	}

	if (!this.$xmldoc.documentElement)
	{
		var _docRoot = this.$xmldoc.createElement("ROOT");
		if ( !( _docRoot = this.$xmldoc.appendChild(_docRoot) ) )
		{
			throw new Error("XMLDoc.loadFormData() failed: unable to create document root");
		}
	}

	var _formChildren = _formElem.getElementsByTagName("*");
	for (var _i = 0; _i < _formChildren.length; _i++)
	{
		var _formField = _formChildren[_i];
		if (_formField.name === undefined || !_formField.name.match(/\S+/) || _formField.value === undefined)
		{
			continue;
		}
		var _formFieldValue = "";

		if (_formField.options && _formField.multiple)
		{
			for (var _j = 0; _j < _formField.options.length; _j++)
			{
				if (_formField.options[_j].selected)
				{
					var _docNode = this.$xmldoc.createElement("item");
					try
					{
						_docNode.setAttribute("name", _formField.name);
					}
					catch(_error)
					{
						_docNode.setAttribute("name", "");
					}

					var _optionVal = _formField.options[_j].value;
					_formFieldValue = (_optionVal === null || _optionVal === false) ? "" : _optionVal;

					try
					{
						var _docNodeValue = this.$xmldoc.createTextNode(_formFieldValue);
						_docNode.appendChild(_docNodeValue);
					}
					catch(_error)
					{
						// Do nothing.
					}
					_docRoot.appendChild(_docNode);
				}
			}
		}
		else
		{
			var _docNode = this.$xmldoc.createElement("item");
			try
			{
				_docNode.setAttribute("name", _formField.name);
			}
			catch(_error)
			{
				_docNode.setAttribute("name", "");
			}

			if (_formField.options)
			{
				var _selectValue = _formField.selectedIndex > -1 ? _formField.options[_formField.selectedIndex].value : false;
				_formFieldValue = _selectValue;
			}
			else if (!_formField.type || !(_formField.type == "radio" || _formField.type == "checkbox") || 
					 ( (_formField.type == "radio" || _formField.type == "checkbox") && _formField.checked ) )
			{
				_formFieldValue = _formField.value;
			}

			if (_formFieldValue)
			{
				try
				{
					var _docNodeValue = this.$xmldoc.createTextNode(_formFieldValue);
					_docNode.appendChild(_docNodeValue);
				}
				catch(_error)
				{
					// Do nothing.
				}
				_docRoot.appendChild(_docNode);
			}
		}
	}
}

XMLDoc.prototype.post = function (_url, _responseHandler, _responseHandlerArgs) {
	if (!_url || typeof _url != "string")
	{
		throw new Error("Missing or invalid first argument passed to XMLDoc.post(): not a valid URL");
	}
	if (_responseHandler && typeof _responseHandler != "function")
	{
		throw new Error("Invalid second argument passed to XMLDoc.post(): not a valid function");
	}
	if (_responseHandlerArgs === undefined)
	{
		var _responseHandlerArgs = new Array();
	}
	else if (!_responseHandlerArgs.constructor || _responseHandlerArgs.constructor != Array)
	{
		_responseHandlerArgs = new Array(_responseHandlerArgs);
	}

	var _xmlHttp = null;

	if (window.XMLHttpRequest)
	{
		_xmlHttp = new XMLHttpRequest();
	}
	else if (window.ActiveXObject)
	{
		try
		{
			_xmlHttp = new ActiveXObject("Msxml2.XMLHTTP");
		}
		catch(_error)
		{
			try
			{
				_xmlHttp = new ActiveXObject("Microsoft.XMLHTTP");
			}
			catch(_error)
			{
				throw new Error("XMLDoc.post() failed: unknown Microsoft API error");
			}
		}
	}
	else
	{
		throw new Error("XMLDoc.post() failed: neither the XMLHttpRequest class " + 
						"nor the proprietary Microsoft API is supported");
	}

	if (_xmlHttp)
	{
		if (_responseHandler)
		{
			_xmlHttp.onreadystatechange = function() {
				if (_xmlHttp.readyState == 4 && _xmlHttp.status == 200)
				{
					var _response = _xmlHttp.responseXML && _xmlHttp.responseXML.childNodes.length > 0 ? 
									_xmlHttp.responseXML : (_xmlHttp.responseText ? _xmlHttp.responseText : null);
					_responseHandler.apply(_response, _responseHandlerArgs);
				}
			}
		}

		var _docRef = this.$xmldoc;	// Create reference to XML object so it can be used as a function argument.
		var _asynchronous = _responseHandler ? true : false;
		_xmlHttp.open("POST", _url, _asynchronous);
		_xmlHttp.send(_docRef);
	}
}

XMLDoc.prototype.removeChild = function(_oldChildNode) {
	if (!_oldChildNode || typeof _oldChildNode != "object" || !_oldChildNode.nodeName || !_oldChildNode.nodeType)
	{
		throw new Error("XMLDoc.removeChild() failed: missing or invalid old child node");
	}
	return this.$xmldoc.removeChild(_oldChildNode);
}

XMLDoc.prototype.replaceChild = function(_newChildNode, _oldChildNode) {
	if (!_newChildNode || typeof _newChildNode != "object" || !_newChildNode.nodeName || !_newChildNode.nodeType)
	{
		throw new Error("XMLDoc.replaceChild() failed: missing or invalid new child node");
	}
	if (!_oldChildNode || typeof _oldChildNode != "object" || !_oldChildNode.nodeName || !_oldChildNode.nodeType)
	{
		throw new Error("XMLDoc.replaceChild() failed: missing or invalid old child node");
	}
	return this.$xmldoc.replaceChild(_newChildNode, _oldChildNode);
}

XMLDoc.prototype.save = function() {
	return this.$xmldoc;
}

XMLDoc.prototype.saveXML = function(_baseNode) {
	if (!_baseNode)
	{
		var _baseNode = this.$xmldoc;
	}
	if (typeof _baseNode != "object" || !_baseNode.nodeName || !_baseNode.nodeType)
	{
		throw new Error("XMLDoc.saveXML() failed: invalid base node");
	}

	var _xmlString = "";

	for (var _i = 0; _i < _baseNode.childNodes.length; _i++)
	{
		var _currentNode = _baseNode.childNodes[_i];
		if (_currentNode.nodeType == 1)		// Element node
		{
			_xmlString += "\u003C" + _currentNode.nodeName;
			if (_currentNode.attributes)
			{
				for (var _j = 0; _j < _currentNode.attributes.length; _j++)
				{
					if (!_currentNode.attributes[_j].specified)
					{
						continue;
					}
					var _currentAttr = _currentNode.attributes[_j];
					_xmlString += " " + _currentAttr.nodeName + '="' + _currentAttr.nodeValue.replace(/\"/g, '\\"') + '"';
				}
			}
			_xmlString += "\u003E";
			if (_currentNode.childNodes.length > 0)
			{
				_xmlString += this.saveXML(_currentNode);
			}
			_xmlString += "\u003C/" + _currentNode.nodeName + "\u003E";
		}
		else if (_currentNode.nodeType == 3)	// Text node
		{
			_xmlString += _currentNode.nodeValue;
		}
		else if (_currentNode.nodeType == 4)	// CDATA section node
		{
			_xmlString += _currentNode.nodeValue;
		}
		else if (_currentNode.nodeType == 7)	// Processing instruction node
		{
			_xmlString += "\u003C?" + _currentNode.nodeName + " " + _currentNode.nodeValue + " ?\u003E";
		}
		else if (_currentNode.nodeType == 8)	// Comment node
		{
			_xmlString += "\u003C!--" + _currentNode.nodeValue + "--\u003E";
		}
	}

	return _xmlString;
}

/****
XMLDoc.toArray(): an example

XML source:
<root>
	<elem attrNameA="attrValA" attrNameB="attrValB">
		this is a test
		<subelem arrayIndex="x">another test</subelem>
		<subelem arrayIndex="y">and one more test</subelem>
	</elem>
	<elem>
		yet another test
		<subelemA>hello</subelemA>
		<subelemB>world</subelemB>
	</elem>
	<elem>last test</elem>
</root>

XMLDoc.toArray("arrayIndex") returns following array:

ARRAY["root"][0]["elem"][0]["attrNameA"] = "attrValA"
						[0]["attrNameB"] = "attrValB"
						[0]["#text"] = "this is a test"
						[0]["x"] = "another test"
						[0]["y"] = "and one more test"
						[1]["#text"] = "yet another test"
						[1]["subelemA"][0] = "hello"
						[1]["subelemB"][0] = "world"
						[2] = "last test"
****/
XMLDoc.prototype.toArray = function(_arrayIndexAttr, _baseNode) {
	if (!_arrayIndexAttr || typeof _arrayIndexAttr != "string")
	{
		var _arrayIndexAttr = false;
	}
	if (!_baseNode)
	{
		var _baseNode = this.$xmldoc;
	}
	if (typeof _baseNode != "object" || !_baseNode.nodeName || !_baseNode.nodeType)
	{
		throw new Error("XMLDoc.toArray() failed: invalid base node");
	}

	// If base node is a Text or CData section node and its text value in non-empty, return the text; 
	// otherwise, return nothing.
	if (_baseNode.nodeType == 3 || _baseNode.nodeType == 4)
	{
		return !_baseNode.nodeValue.match(/^\s*$/) ? _baseNode.nodeValue : null;
	}

	// If base node is not the document element or an Element node, return nothing.
	if (_baseNode.nodeType != 9 && _baseNode.nodeType != 1)
	{
		return null;
	}

	// If base node has no attributes and no child nodes, return nothing.
	if (_baseNode.nodeType == 1 && (!_baseNode.attributes || _baseNode.attributes.length == 0) && 
		_baseNode.childNodes.length == 0)
	{
		return null;
	}

	// Check whether any of base node's descendants are Element nodes.
	var _descElements = _baseNode.getElementsByTagName("*").length > 0 ? true : false;

	// If base node has no attributes and has a single child Text or CData section node with nothing but 
	// whitespace, return nothing. Note: In Firefox 1.5, nodes of type 9 that do not have attributes have 
	// their "attributes" property set to null instead of an object with a "length" property equal to zero.
	if ( !_descElements && (!_baseNode.attributes || _baseNode.attributes.length == 0) )
	{
		var _quitNow = true;
		for (var _i = 0; _i < _baseNode.childNodes.length; _i++)
		{
			if ( (_baseNode.childNodes[_i].nodeType == 3 || _baseNode.childNodes[_i].nodeType == 4) && 
				 !_baseNode.childNodes[_i].nodeValue.match(/^\s*$/) )
			{
				_quitNow = false;
			}
		}
		if (_quitNow)
		{
			return null;
		}
	}

	var _returnVal = new Array();

	// Add all attributes as properties of the current array element.
	if (_baseNode.attributes)
	{
		for (var _i = 0; _i < _baseNode.attributes.length; _i++)
		{
			if (_arrayIndexAttr && _baseNode.attributes[_i].nodeName == _arrayIndexAttr)
			{
				continue;
			}
			_returnVal[_baseNode.attributes[_i].nodeName] = _baseNode.attributes[_i].nodeValue;
		}
	}

	for (var _i = 0; _i < _baseNode.childNodes.length; _i++)
	{
		var _currentChild = _baseNode.childNodes[_i];
		if (_currentChild.nodeType != 1 && _currentChild.nodeType != 3 && _currentChild.nodeType != 4)
		{
			continue;
		}
		if (_currentChild.nodeType == 1)
		{
			// If the current element has an attribute named "arrayIndex", use it as the array index. The element's 
			// value does not need to be an array because arrayIndex's value is supposed to be unique (so the resulting 
			// array index will also be unique).
			var _arrayIndex = _currentChild.getAttribute(_arrayIndexAttr);
			if (_arrayIndex)
			{
				_returnVal[_arrayIndex] = this.toArray(_arrayIndexAttr, _currentChild);
			}
			// Otherwise, use the element's name as the array index and make its value an array as well.
			else
			{
				if (!_returnVal[_currentChild.nodeName])
				{
					_returnVal[_currentChild.nodeName] = new Array();
				}
				_returnVal[_currentChild.nodeName].push( this.toArray(_arrayIndexAttr, _currentChild) );
			}
		}
		// Join all text nodes that are children of the same node in order and add it to the current array element as 
		// a property named "#text".
		else if ( !_currentChild.nodeValue.match(/^\s*$/) )
		{
			// If current child node has sibling element nodes, add text nodes as array elements having "#text" as index.
			if (_descElements)
			{
				if (!_returnVal["#text"])
				{
					_returnVal["#text"] = "";
				}
				_returnVal["#text"] += _currentChild.nodeValue;
			}
			// Otherwise, make value a simple string.
			else
			{
				if (!_returnVal)
				{
					_returnVal = "";
				}
				_returnVal += _currentChild.nodeValue;
			}
		}
	}

	return _returnVal;
}