///////////////////////////////////////////////////////////////
////////// JSXML XML Tools               //////////////////////
////////// Ver 1.2 Jun 18 2001           //////////////////////
////////// Copyright 2000 Peter Tracey   //////////////////////
////////// http://jsxml.homestead.com/   //////////////////////
////
////	Objects:
////
////	REXML
////    Regular Expression-based XML parser
////
////	JSXMLIterator
////    Iterates through the tree structure without recursion
////
////	JSXMLBuilder
////    Loads xml into a linear structure and provides 
////	interface for adding and removing elements 
////	and setting attributes, generates XML
////
////	Utility functions:
////
////	ParseAttribute
////    Takes string of attibutes and attribute name
////    Returns attribute value
////
////	Array_Remove
////    Removes element in array
////
////	Array_Add
////    Adds element to array
////
////	RepeatChar
////    Repeats string specified number of times
////
///////////////////////////////////////////////////////////////


function REXML(XML) {
	this.XML = XML;

	this.rootElement = null;

	this.parse = REXML_parse;
	if (this.XML && this.XML != "") this.parse();
}

	function REXML_parse() {
		var reTag = new RegExp("<([^>/ ]*)([^>]*)>","g"); // matches that tag name $1 and attribute string $2
		var reTagText = new RegExp("<([^>/ ]*)([^>]*)>([^<]*)","g"); // matches tag name $1, attribute string $2, and text $3
		var strType = "";
		var strTag = "";
		var strText = "";
		var strAttributes = "";
		var strOpen = "";
		var strClose = "";
		var iElements = 0;
		var xmleLastElement = null;
		if (this.XML.length == 0) return;
		var arrElementsUnparsed = this.XML.match(reTag);
		var arrElementsUnparsedText = this.XML.match(reTagText);
		var i=0;
		if (arrElementsUnparsed[0].replace(reTag, "$1") == "?xml") i++;

		for (; i<arrElementsUnparsed.length; i++) {
			strTag = arrElementsUnparsed[i].replace(reTag,"$1");
			strAttributes = arrElementsUnparsed[i].replace(reTag,"$2");
			strText = arrElementsUnparsedText[i].replace(reTagText,"$3").replace(/[\r\n\t ]+/g, " "); // remove white space
			strClose = "";
			if (strTag.indexOf("![CDATA[") == 0) {
				strOpen = "<![CDATA[";
				strClose = "]]>";
				strType = "cdata";
			} else if (strTag.indexOf("!--") == 0) {
				strOpen = "<!--";
				strClose = "-->";
				strType = "comment";
			} else if (strTag.indexOf("?") == 0) {
				strOpen = "<?";
				strClose = "?>";
				strType = "pi";
			} else strType = "element";
			if (strClose != "") {
				strText = "";
				if (arrElementsUnparsedText[i].indexOf(strClose) > -1) strText = arrElementsUnparsedText[i];
				else {
					for (; i<arrElementsUnparsed.length && arrElementsUnparsedText[i].indexOf(strClose) == -1; i++) {
						strText += arrElementsUnparsedText[i];
					}
					strText += arrElementsUnparsedText[i];
				}
				if (strText.substring(strOpen.length, strText.indexOf(strClose)) != "")	{
					xmleLastElement.childElements[xmleLastElement.childElements.length] = new REXML_XMLElement(strType, "","",xmleLastElement,strText.substring(strOpen.length, strText.indexOf(strClose)));
					if (strType == "cdata") xmleLastElement.text += strText.substring(strOpen.length, strText.indexOf(strClose));
				}
				if (strText.indexOf(strClose)+ strClose.length < strText.length) {
					xmleLastElement.childElements[xmleLastElement.childElements.length] = new REXML_XMLElement("text", "","",xmleLastElement,strText.substring(strText.indexOf(strClose)+ strClose.length, strText.length));
					if (strType == "cdata") xmleLastElement.text += strText.substring(strText.indexOf(strClose)+ strClose.length, strText.length);
				}
				continue;
			}
			if (strText.replace(/ */, "") == "") strText = "";
			if (arrElementsUnparsed[i].substring(1,2) != "/") {
				if (iElements == 0) {
					xmleLastElement = this.rootElement = new REXML_XMLElement(strType, strTag,strAttributes,null,strText);
					iElements++;
					if (strText != "") xmleLastElement.childElements[xmleLastElement.childElements.length] = new REXML_XMLElement("text", "","",xmleLastElement,strText);
				} else if (arrElementsUnparsed[i].substring(arrElementsUnparsed[i].length-2,arrElementsUnparsed[i].length-1) != "/") {
					xmleLastElement = xmleLastElement.childElements[xmleLastElement.childElements.length] = new REXML_XMLElement(strType, strTag,strAttributes,xmleLastElement,"");
					iElements++;
					if (strText != "") {
						xmleLastElement.text += strText;
						xmleLastElement.childElements[xmleLastElement.childElements.length] = new REXML_XMLElement("text", "","",xmleLastElement,strText);
					}
				} else {
					xmleLastElement.childElements[xmleLastElement.childElements.length] = new REXML_XMLElement(strType, strTag,strAttributes,xmleLastElement,strText);
					if (strText != "") xmleLastElement.childElements[xmleLastElement.childElements.length] = new REXML_XMLElement("text", "","",xmleLastElement,strText);
				}
			} else {
				xmleLastElement = xmleLastElement.parentElement;
				iElements--;
				if (xmleLastElement && strText != "") {
					xmleLastElement.text += strText;
					xmleLastElement.childElements[xmleLastElement.childElements.length] = new REXML_XMLElement("text", "","",xmleLastElement,strText);
				}
			}
		}
	}

	function REXML_XMLElement(strType, strName, strAttributes, xmlParent, strText) {
		this.type = strType;
		this.name = strName;
		this.attributeString = strAttributes;
		this.attributes = null;
		this.childElements = new Array();
		this.parentElement = xmlParent;
		this.text = strText; // text of element

		this.getText = REXML_XMLElement_getText; // text of element and child elements
		this.childElement = REXML_XMLElement_childElement;
		this.attribute = REXML_XMLElement_attribute;
	}

		function REXML_XMLElement_getText() {
			if (this.type == "text" || this.type == "cdata") {
				return this.text;
			} else if (this.childElements.length) {
				var L = "";
				for (var i=0; i<this.childElements.length; i++) {
					L += this.childElements[i].getText();
				}
				return L;
			} else return "";
		}
		
		function REXML_XMLElement_childElement(strElementName) {
			for (var i=0; i<this.childElements.length; i++) if (this.childElements[i].name == strElementName) return this.childElements[i];
			return null;
		}

		function REXML_XMLElement_attribute(strAttributeName) {
			if (!this.attributes) {
				var reAttributes = new RegExp(" ([^= ]*)=","g"); // matches attributes
				if (this.attributeString.match(reAttributes) && this.attributeString.match(reAttributes).length) {
					var arrAttributes = this.attributeString.match(reAttributes);
					if (!arrAttributes.length) arrAttributes = null;
					else for (var j=0; j<arrAttributes.length; j++) {
						arrAttributes[j] = new Array(
							(arrAttributes[j]+"").replace(/[= ]/g,""),
							ParseAttribute(this.attributeString, (arrAttributes[j]+"").replace(/[= ]/g,""))
										);
					}
					this.attributes = arrAttributes;
				}
			}
			if (this.attributes) for (var i=0; i<this.attributes.length; i++) if (this.attributes[i][0] == strAttributeName) return this.attributes[i][1];
			return "";
		}


function JSXMLBuilder() {
	this.XML = "";
	this.elements = new Array();
	Array.prototype.remove = Array_Remove;
	Array.prototype.add = Array_Add;

	this.load = JSXMLBuilder_load;
	this.element = JSXMLBuilder_element;
	this.addElementAt = JSXMLBuilder_addElementAt;
	this.insertElementAt = JSXMLBuilder_insertElementAt;
	this.removeElement = JSXMLBuilder_removeElement;
	this.generateXML = JSXMLBuilder_generateXML;
	this.moveElement = JSXMLBuilder_moveElement;
}

	function JSXMLBuilder_load(strXML, xmleElem) {
		this.XML = strXML;

		if (!xmleElem) {
			if (strXML.length) xmleElem = (new REXML(strXML)).rootElement;
			else return false;
		}

		var xmlBuilder = new JSXMLIterator(xmleElem);

		while (true) {
			if (xmlBuilder.xmleElem.type == "element") {
				if (xmlBuilder.xmleElem.attributes) {
					this.addElementAt(xmlBuilder.xmleElem.name,xmlBuilder.xmleElem.attributes, xmlBuilder.xmleElem.text, this.elements.length, xmlBuilder.iElemLevel);
				} else {	
					this.addElementAt(xmlBuilder.xmleElem.name,xmlBuilder.xmleElem.attributeString, xmlBuilder.xmleElem.text, this.elements.length, xmlBuilder.iElemLevel);
				}
			}
			if (!xmlBuilder.getNextNode(false)) break;
		}
		for (var i=0; i<this.elements.length; i++) this.elements[i].index = i;
	}

	function JSXMLBuilder_element(iIndex) {
		return this.elements[iIndex];
	}

	function JSXMLBuilder_addElementAt(strElement,Attributes,strText,iElemIndex,iElemLevel) {
		iElemIndex = parseInt(iElemIndex);
		iElemLevel = parseInt(iElemLevel);
		if (iElemIndex < 0 || typeof(iElemIndex) != "number" || isNaN(iElemIndex)) iElemIndex = (this.elements.length>0) ? this.elements.length-1 : 0;
		if (iElemLevel < 0 || typeof(iElemLevel) != "number" || isNaN(iElemLevel)) iElemLevel = this.elements[iElemIndex-1].level;
		if (!Attributes) Attributes = "";
		var Elem = new Array();
		var iAddIndex = iElemIndex;
		if (iElemIndex > 0) {
			for (var i=iElemIndex; i<this.elements.length; i++) if (this.elements[i].level > iElemLevel) iAddIndex++;
			else if (this.elements[i].level <= this.elements[iElemIndex].level) break;
			Elem = new JSXMLBuilder_XMLElement(strElement,Attributes,strText,iElemLevel+1,this);
		} else {
			Elem = new JSXMLBuilder_XMLElement(strElement,Attributes,strText,1,this);
		}
		this.elements = this.elements.add(iAddIndex,Elem);
		for (var i=iAddIndex; i<this.elements.length; i++) this.elements[i].index = i;
	}

	function JSXMLBuilder_insertElementAt(strElement,Attributes,strText,iElemIndex,iElemLevel) {
		iElemIndex = parseInt(iElemIndex);
		iElemLevel = parseInt(iElemLevel);
		if (iElemIndex < 0 || typeof(iElemIndex) != "number" || isNaN(iElemIndex)) iElemIndex = (this.elements.length>0) ? this.elements.length-1 : 0;
		if (iElemLevel < 0 || typeof(iElemLevel) != "number" || isNaN(iElemLevel)) iElemLevel = this.elements[iElemIndex-1].level;
		if (!Attributes) Attributes = "";
		var Elem = null;
		var iAddIndex = iElemIndex;
		if (iElemIndex > 0 && iElemLevel > 0) {
			Elem = new JSXMLBuilder_XMLElement(strElement,Attributes,strText,iElemLevel+1,this);
		} else {
			Elem = new JSXMLBuilder_XMLElement(strElement,Attributes,strText,1,this);
		}
		this.elements = this.elements.add(iAddIndex,Elem);
		for (var i=iAddIndex; i<this.elements.length; i++) this.elements[i].index = i;
	}


	function JSXMLBuilder_removeElement(iElemIndex) {
		iElemIndex = parseInt(iElemIndex);
		for (var iAfterElem=iElemIndex+1; iAfterElem<this.elements.length; iAfterElem++) if (this.elements[iAfterElem].level < this.elements[iElemIndex].level+1) break;

		this.elements = this.elements.slice(0,iElemIndex).concat(this.elements.slice(iAfterElem,this.elements.length));
		for (var i=iElemIndex; i<this.elements.length; i++) this.elements[i].index = i;
	}

	function JSXMLBuilder_moveElement(iElem1Index,iElem2Index) {
		var arrElem1Elements = new Array(this.elements[iElem1Index]);
		var arrElem2Elements = new Array(this.elements[iElem2Index]);
		for (var i=iElem1Index; i<this.elements.length; i++) if (this.elements[i].level > this.elements[iElem1Index].level) arrElem1Elements[arrElem1Elements.length] = this.elements[i]; else if (i>iElem1Index) break;
		for (var i=iElem2Index; i<this.elements.length; i++) if (this.elements[i].level > this.elements[iElem2Index].level) arrElem2Elements[arrElem2Elements.length] = this.elements[i]; else if (i>iElem2Index) break;
		var arrMovedElements = new Array();
		if (iElem1Index < iElem2Index) {
			for (i=0; i<iElem1Index; i++) arrMovedElements[arrMovedElements.length] = this.elements[i]; // start to the 1st element
			for (i=iElem1Index+arrElem1Elements.length; i<iElem2Index+arrElem2Elements.length; i++) arrMovedElements[arrMovedElements.length] = this.elements[i]; // end of 1st element to end of 2nd element
			for (i=0; i<arrElem1Elements.length; i++) arrMovedElements[arrMovedElements.length] = arrElem1Elements[i]; // 1st element and all child elements
			for (i=iElem2Index+arrElem2Elements.length; i<this.elements.length; i++) arrMovedElements[arrMovedElements.length] = this.elements[i]; // end of 2nd element to end
			this.elements = arrMovedElements;
		} else {
			for (i=0; i<iElem2Index; i++) arrMovedElements[arrMovedElements.length] = this.elements[i]; // start to the 2nd element
			for (i=0; i<arrElem1Elements.length; i++) arrMovedElements[arrMovedElements.length] = arrElem1Elements[i]; // 1st element and all child elements
			for (i=iElem2Index; i<iElem1Index; i++) arrMovedElements[arrMovedElements.length] = this.elements[i]; // 2nd element to 1st element
			for (i=iElem1Index+arrElem1Elements.length; i<this.elements.length; i++) arrMovedElements[arrMovedElements.length] = this.elements[i]; // end of 1st element to end
			this.elements = arrMovedElements;
		}
		for (var i=0; i<this.elements.length; i++) this.elements[i].index = i;
	}


	function JSXMLBuilder_generateXML(bXMLTag) {
		var strXML = "";
		var arrXML = new Array();
		if (bXMLTag) strXML += '<?xml version="1.0"?>\n\n'
		for (var i=0; i<this.elements.length; i++) {
			strXML += RepeatChar("\t",this.elements[i].level-1);
			strXML += "<" + this.element(i).name // open tag
			if (this.element(i).attributes) {
				for (var j=0; j<this.element(i).attributes.length; j++) { // set attributes
					if (this.element(i).attributes[j]) {
						strXML += ' ' + this.element(i).attributes[j][0] + '="' + this.element(i).attributes[j][1] + '"';
					}
				}
			} else strXML += this.element(i).attributeString.replace(/[\/>]$/gi, "");
			if (((this.elements[i+1] && this.elements[i+1].level <= this.elements[i].level) || // next element is a lower or equal to
				(!this.elements[i+1] && this.elements[i-1])) // no next element, previous element
				&& this.element(i).text == "") {
				strXML += "/";
			}
			strXML += ">";
			if (this.element(i).text != "") strXML += this.element(i).text;
			else strXML += "\n";
			if (((this.elements[i+1] && this.elements[i+1].level <= this.elements[i].level) || // next element is a lower or equal to
				(!this.elements[i+1] && this.elements[i-1])) // no next element, previous element
				&& this.element(i).text != "") strXML += "</" + this.element(i).name + ">\n";
			if (!this.elements[i+1]) {
				lastelem = i;
				for (var j=i; j>-1; j--) {
					if (this.elements[j].level >= this.elements[i].level) continue;
					else {
						if (this.elements[j].level < this.elements[lastelem].level) {
							strXML += RepeatChar("\t",this.elements[j].level-1) + "</" + this.element(j).name + ">\n";
							lastelem = j;
						}
					}
				}
			} else {
				if (this.elements[i+1].level < this.elements[i].level) {
					lastelem = i;
					for (var j=i; this.elements[j].level>=this.elements[i+1].level; j--) {
						if (this.elements[i] && this.elements[j] && this.elements[j].level < this.elements[i].level && this.elements[j].level < this.elements[lastelem].level) {
							strXML += RepeatChar("\t",this.elements[j].level-1) + "</" + this.element(j).name + ">\n";
							lastelem = j;
						}
					}
				}
			}
			if (strXML.length > 1000) {
				arrXML[arrXML.length] = strXML;
				strXML = "";
			}
		}
		arrXML[arrXML.length] = strXML;
		return arrXML.join("");
	}

	function JSXMLBuilder_XMLElement(strName,Attributes,strText,iLevel,xmlBuilder) {
		this.type = "element";
		this.name = strName;
		this.attributes = (typeof(Attributes) != "string") ? Attributes : null;
		this.attributeString = (typeof(Attributes) == "string") ? Attributes : "";
		this.text = strText;
		this.level = iLevel;
		this.index = -1;
		this.xmlBuilder = xmlBuilder;

		this.parseAttributes = JSXMLBuilder_XMLElement_parseAttributes;
		this.attribute = JSXMLBuilder_XMLElement_attribute;
		this.setAttribute = JSXMLBuilder_XMLElement_setAttribute;
		this.removeAttribute = JSXMLBuilder_XMLElement_removeAttribute;
		this.parentElement = JSXMLBuilder_XMLElement_parentElement;
		this.childElement = JSXMLBuilder_XMLElement_childElement;
	}

		function JSXMLBuilder_XMLElement_parseAttributes() {
			if (!this.attributes) {
				var reAttributes = new RegExp(" ([^= ]*)=","g"); // matches attributes
				if (this.attributeString.match(reAttributes) && this.attributeString.match(reAttributes).length) {
					var arrAttributes = this.attributeString.match(reAttributes);
					if (!arrAttributes.length) arrAttributes = null;
					else for (var j=0; j<arrAttributes.length; j++) {
						arrAttributes[j] = new Array(
							(arrAttributes[j]+"").replace(/[= ]/g,""),
							ParseAttribute(this.attributeString, (arrAttributes[j]+"").replace(/[= ]/g,""))
										);
					}
					this.attributes = arrAttributes;
				}
			}
		}
	
		function JSXMLBuilder_XMLElement_attribute(AttributeName) {
			if (!this.attributes) this.parseAttributes();
			if (this.attributes) for (var i=0; i<this.attributes.length; i++) if (this.attributes[i][0] == AttributeName) return this.attributes[i][1];
			return "";
		}

		function JSXMLBuilder_XMLElement_setAttribute(AttributeName,Value) {
			if (!this.attributes) this.parseAttributes();
			if (this.attributes) for (var i=0; i<this.attributes.length; i++) if (this.attributes[i][0] == AttributeName) {
				this.attributes[i][1] = Value;
				return;
			}
			this.attributes[this.attributes.length] = new Array(AttributeName,Value);
		}

		function JSXMLBuilder_XMLElement_removeAttribute(AttributeName,Value) {
			if (!this.attributes) this.parseAttributes();
			if (this.attributes) for (var i=0; i<this.attributes.length; i++) if (this.attributes[i][0] == AttributeName) {
				this.attributes = this.attributes.remove(i);
				return;
			}
		}

		function JSXMLBuilder_XMLElement_parentElement() {
			for (var i=this.index; this.xmlBuilder.element(i) && this.xmlBuilder.element(i).level != this.level-1; i--);
			return this.xmlBuilder.element(i);
		}

		function JSXMLBuilder_XMLElement_childElement(Child) {
			var iFind = -1;
			for (var i=this.index+1; i<this.xmlBuilder.elements.length; i++) {
				if (this.xmlBuilder.elements[i].level == this.level+1) {
					iFind++;
					if (iFind == Child || this.xmlBuilder.elements[i].name == Child) return this.xmlBuilder.elements[i];
				} else if (this.xmlBuilder.elements[i].level <= this.level) break;
			}
			return null;
		}


function JSXMLIterator(xmleElem) {
	this.xmleElem = xmleElem;
	
	this.iElemIndex = 0;
	this.arrElemIndex = new Array(0);
	this.iElemLevel = 0;
	this.iElem = 0;
	this.arrElemIndex[this.iElemLevel] = -1;

	this.getNextNode = JSXMLIterator_getNextNode;
}

	function JSXMLIterator_getNextNode() {
		if (!this.xmleElem || this.iElemLevel<0)	return false;
		if (this.xmleElem.childElements.length) {  // move up
			this.arrElemIndex[this.iElemLevel]++;
			this.iElemIndex++;
			this.iElemLevel++;
			this.arrElemIndex[this.iElemLevel] = 0;
			this.xmleElem = this.xmleElem.childElements[0];
		} else { // move next
			this.iElemIndex++;
			this.arrElemIndex[this.iElemLevel]++;
			if (this.xmleElem.parentElement && this.xmleElem.parentElement.childElements.length && this.arrElemIndex[this.iElemLevel] < this.xmleElem.parentElement.childElements.length) this.xmleElem = this.xmleElem.parentElement.childElements[this.arrElemIndex[this.iElemLevel]];
			else {
				if (this.iElemLevel>0) { // move down
					for (; this.iElemLevel > 0; this.iElemLevel--) {
						if (this.xmleElem.parentElement && this.xmleElem.parentElement.childElements[this.arrElemIndex[this.iElemLevel]]) {
							this.xmleElem = this.xmleElem.parentElement.childElements[this.arrElemIndex[this.iElemLevel]];
							this.iElemLevel++;
							this.arrElemIndex = this.arrElemIndex.slice(0,this.iElemLevel+1);
							break;
						} else {
							this.xmleElem = this.xmleElem.parentElement;
						}
					}
					this.iElemLevel--;
				} else {
					return false;
				}
			}
		}
		return (typeof(this.xmleElem) == "object" && this.iElemLevel > -1);
	}

function ParseAttribute(str,Attribute) {
	var str = str +  ">";
	if (str.indexOf(Attribute + "='")>-1) var Attr = new RegExp(".*" + Attribute + "='([^']*)'.*>");
	else if (str.indexOf(Attribute + '="')>-1) var Attr = new RegExp(".*" + Attribute + '="([^"]*)".*>');
	return str.replace(Attr, "$1");
}

function Array_Remove(c) {
	var tmparr = new Array();
	for (var i=0; i<this.length; i++) if (i!=c) tmparr[tmparr.length] = this[i];
	return tmparr;
}

function Array_Add(c, cont) {
	var tmparr = new Array();
	for (var i=0; i<this.length; i++) {
		if (i==c) tmparr[tmparr.length] = cont;
		tmparr[tmparr.length] = this[i];
	}
	if (!tmparr[c]) tmparr[c] = cont;
	return tmparr;
}

function RepeatChar(sChar,iNum) {
	var L = "";
	for (var i=0; i<iNum; i++) L += sChar;
	return L;
}
