// This is a very basic XML processing engine
// It can also handle some HTML shortfalls like no attribute quotes
function XmlAttribute(p_sName, p_sValue)
{
	this.type = "attribute";
	this.name = p_sName;
	this.value = p_sValue;
}
XmlAttribute.prototype.getAsString = XmlAttribute_GetAsString;

function XmlAttribute_GetAsString()
{
	return (this.name + '="' + this.value + '"');
}

function XmlContent(p_sContent)
{
	this.type = "content";
	this.content = p_sContent;
}
XmlContent.prototype.getAsString = XmlContent_GetAsString;

function XmlContent_GetAsString()
{
	return this.content;
}

function XmlDocument(p_sParse)
{
	this.type = "document";
	this.elements = new Array();
	
	this._iBlockSize = 10000;
	this._iBlockCount = 0;
	this._parse(p_sParse);
}
XmlDocument.prototype.getAsString = XmlDocument_GetAsString;
XmlDocument.prototype._parse = XmlDocument_Parse;

function XmlDocument_GetAsString()
{
	var sReturn = "";
	var iLength = this.elements.length;
	
	for (var i = 0; i < iLength; i++)
		sReturn += this.elements[i].getAsString();
	
	return sReturn;
}

function XmlDocument_Parse(p_sParse)
{
	var aElements = new Array(this._iBlockSize);
	var aElementChain = new Array();
	var aUnexpectedElementsChain = new Array();
	var iCount = 0;
	var sWorking = String(p_sParse);
	
	var aTempElements = sWorking.split("<");
	var aTempElementsLength = aTempElements.length;
	
	if (aTempElements[0].length > 0)
		aElements[iCount++] = new XmlContent(aTempElements[0]);
	for (var i = 1; i < aTempElementsLength; i++)
	{
		var aTempElements2 = aTempElements[i].split(">");
		// This should always produce 2 elements
		if (aTempElements2.length != 2)
		{
			alert("Error - unexpected data while processing");
			throw "xmlparser2.js - XmlDocument_Parse()";
		}
		
		var sTempElement = _Xml_StringTrim(aTempElements2[0]);
		
		// Possibilities:
		// Ends with "/" - complete element
		// Starts with "/" - end element
		// Does not contain "/" - start element
		if ((/\/$/gi).test(sTempElement) == true)
		{
			// Ends with "/" - complete element
			if (aElementChain.length > 0)
				aElementChain[aElementChain.length - 1].addElement(sTempElement.substring(0, sTempElement.length - 1));
			else
			{
				aElements[iCount++] = new XmlElement(sTempElement.substring(0, sTempElement.length - 1));
				if (iCount == aElements.length)
					aElements.length += this._iBlockSize;
			}
			
			// Add any extra content to the parent element
			if (aTempElements2[1].length > 0)
			{
				if (aElementChain.length > 0)
					aElementChain[aElementChain.length - 1].addContent(aTempElements2[1]);
				else
				{
					aElements[iCount++] = new XmlContent(aTempElements2[1]);
					if (iCount == aElements.length)
						aElements.length += this._iBlockSize;
				}
			}
		}
		else if ((/^\//gi).test(sTempElement) == true)
		{
			// Starts with "/" - end element
			
			// First check to see if we can match any unexpected end elements
			// Then continue processing normally
			var sParentElementName = aElementChain[aElementChain.length - 1].name;
			for (var j = aUnexpectedElementsChain.length - 1; j >= 0; j--)
			{
				if (sParentElementName == aUnexpectedElementsChain[j].toLowerCase())
				{
					// Matched an unexpected element - process, remove from
					// unexpected list and continue normal execution
					
					// Add this element to its parent and remove it from the chain
					if (aElementChain.length == 1)
					{
						aElements[iCount++] = aElementChain[aElementChain.length - 1];
						if (iCount == aElements.length)
							aElements.length += this._iBlockSize;
						aElementChain.length--;
						
						// Clear any unexpected elements since we are now at the topmost level
						for (var j = aUnexpectedElementsChain.length - 1; j >= 0; j--)
							aUnexpectedElementsChain[j] = null;
						aUnexpectedElementsChain.length = 0;
					}
					else
					{
						var aParentElement = aElementChain[aElementChain.length - 2];
						aParentElement.elements[aParentElement.elements.length] = aElementChain[aElementChain.length - 1];
						aElementChain.length--;
						
						// Remove the this one element from the unexpected element chain
						var kLength = aUnexpectedElementsChain.length - 1;
						for (var k = j; k < kLength; k++)
							aUnexpectedElementsChain[k] = aUnexpectedElementsChain[k + 1];
						
						aUnexpectedElementsChain[aUnexpectedElementsChain.length - 1] = null;
						aUnexpectedElementsChain.length--;
					}
					// Only need to match one unexpected element
					break;
				}
			}
			
			if (aElementChain.length  > 0)
			{
				sTempElement = sTempElement.substring(1);
				
				if (aElementChain[aElementChain.length - 1].name == sTempElement.toLowerCase())
				{
					// Add this element to its parent and remove it from the chain
					if (aElementChain.length == 1)
					{
						aElements[iCount++] = aElementChain[aElementChain.length - 1];
						if (iCount == aElements.length)
							aElements.length += this._iBlockSize;
						aElementChain.length--;
						
						// Clear any unexpected elements since we are now at the topmost level
						for (var j = aUnexpectedElementsChain.length - 1; j >= 0; j--)
							aUnexpectedElementsChain[j] = null;
						aUnexpectedElementsChain.length = 0;
					}
					else
					{
						var aParentElement = aElementChain[aElementChain.length - 2];
						aParentElement.elements[aParentElement.elements.length] = aElementChain[aElementChain.length - 1];
						aElementChain.length--;
					}
				}
				else
				{
					// Search the parent elements for occurances of this element and compare
					// with list of unexpected elements.  If this element will be expected in
					// the future, keep it, otherwise ignore it and move on
					var jLength = aElementChain.length;
					var jCount = 0;
					for (var j = 0; j < jLength; j++)
					{
						if (aElementChain[j].name == sTempElement.toLowerCase())
							jCount++;
					}
					jLength = aUnexpectedElementsChain.length;
					for (var j = 0; j < jLength; j++)
					{
						if (aUnexpectedElementsChain[j].toLowerCase() == sTempElement.toLowerCase())
							jCount--;
					}
					if (jLength > 0)
						aUnexpectedElementsChain[aUnexpectedElementsChain.length] = sTempElement;
				}
			}
			
			// Add any extra content to the parent element
			if (aTempElements2[1].length > 0)
			{
				if (aElementChain.length > 0)
					aElementChain[aElementChain.length - 1].addContent(aTempElements2[1]);
				else
				{
					aElements[iCount++] = new XmlContent(aTempElements2[1]);
					if (iCount == aElements.length)
						aElements.length += this._iBlockSize;
				}
			}
		}
		else
		{
			// Does not contain "/" - start element
			// Add element to chain of working elements
			aElementChain[aElementChain.length] = new XmlElement(sTempElement);
			
			// Add any extra content to the parent element
			if (aTempElements2[1].length > 0)
				aElementChain[aElementChain.length - 1].addContent(aTempElements2[1]);
		}
	}
	
	// Process any left-over items from the element chain
	while (aElementChain.length > 0)
	{
		// Add this element to its parent and remove it from the chain
		if (aElementChain.length == 1)
		{
			// Last element in chain so add element to main aElements list
			aElements[iCount++] = aElementChain[aElementChain.length - 1];
			
			// Increase the containing array if necessary
			if (iCount == aElements.length)
				aElements.length += this._iBlockSize;
			aElementChain.length--;
		}
		else
		{
			// Add the last element in chain to second last element in chain (its parent)
			var aParentElement = aElementChain[aElementChain.length - 2];
			aParentElement.elements[aParentElement.elements.length] = aElementChain[aElementChain.length - 1];
			aElementChain.length--;
		}
	}
	
	// Store the array in this object
	aElements.length = iCount;
	this.elements = aElements;
}

function XmlElement(p_sParse)
{
	this.type = "element";
	this.name = "";
	this.elements = new Array();
	this.attributes = new Array();
	
	var sParse = _Xml_StringTrim(p_sParse);
	var iNameEnd = sParse.search(/[\s]/i);
	if (iNameEnd == -1)
		this.name = sParse.toLowerCase();
	else
	{
		this.name = sParse.substring(0, iNameEnd).toLowerCase();
		sParse = sParse.substring(iNameEnd);
		this.attributes = this.getAttributes(sParse);
	}
}
// Static methods
XmlElement.prototype.addAttribute = XmlElement_AddAttribute;
XmlElement.prototype.addContent = XmlElement_AddContent;
XmlElement.prototype.addElement = XmlElement_AddElement;
XmlElement.prototype.getAsString = XmlElement_GetAsString;
XmlElement.prototype.getAttributes = XmlElement_GetAttributes;

function XmlElement_AddAttribute(p_sName, p_sValue)
{
	var oXmlAttribute = new XmlAttribute(p_sName, p_sValue);
	this.attributes[this.attributes.length] = oXmlAttribute;
	return oXmlAttribute;
}

function XmlElement_AddContent(p_sContent)
{
	var oXmlContent = new XmlContent(p_sContent);
	this.elements[this.elements.length] = oXmlContent;
	return oXmlContent;
}

function XmlElement_AddElement(p_sParse)
{
	var oXmlElement =  new XmlElement(p_sParse);
	this.elements[this.elements.length] = oXmlElement;
	return oXmlElement;
}

function XmlElement_GetAsString()
{
	var sReturn = "<" + this.name;
	
	var iLength = this.attributes.length;
	for (var i = 0; i < iLength; i++)
		sReturn += " " + this.attributes[i].getAsString();
	
	iLength = this.elements.length;
	if (iLength == 0)
		sReturn += "/>";
	else
	{
		sReturn += ">";
		for(var i = 0; i < iLength; i++)
			sReturn += this.elements[i].getAsString();
		sReturn += "</" + this.name + ">";
	}
	
	return sReturn;
}

function XmlElement_GetAttributes(p_sParse)
{
	var sParse = _Xml_StringTrim(p_sParse);
	var aPairs = sParse.split(/\s/gi);
	var aReturn = new Array();
	
	for(var i = 0; i < aPairs.length; i++)
	{
		var aPair = aPairs[i].split("=");
		var sName = aPair[0].replace(/\s/gi, '');
		
		var sValue = String(aPair[1]).replace(/^[\s]*/gi, '').replace(/[\s]*$/gi, '');
		if (((/^[a-zA-Z][\w]*$/gi).test(sName) == true) && ((/^["]?[\s\x21\x23-\x3B\x3D\x3F-\x7E]*["]?$/gi).test(sValue) == true))
		{
			sValue = sValue.replace(/"/gi, '');
			aReturn[aReturn.length] = new XmlAttribute(sName, sValue);
		}
		// else
			// Do nothing
	}
	return aReturn;
}

// Functions
function PrepareForXml(p_s)
{
	return String(p_s).replace(/\&/gi, "&amp;").replace(/\"/gi, "&quot;");
}

function _Xml_StringTrim(p_s)
{
	p_s = p_s.replace(/^[\s]*/gi, "");
	p_s = p_s.replace(/[\s]*$/gi, "");
	return p_s;
}
