// xmldom.js - an XML DOM parser in JavaScript.
// version 3.1
// Copyright (C) 2000 - 2002, 2003 Michael Houghton (mike@idle.org), Raymond Irving and David Joham (djoham@yahoo.com)
var whitespace = "\n\r\t ";
var quotes = "\"'";
function convertEscapes(str) {
    var gt;
    gt = -1;
    while (str.indexOf("&lt;", gt + 1) > -1) {
        var gt = str.indexOf("&lt;", gt + 1);
        var newStr = str.substr(0, gt);
        newStr += "<";
        newStr = newStr + str.substr(gt + 4, str.length);
        str = newStr;
    }
    gt = -1;
    while (str.indexOf("&gt;", gt + 1) > -1) {
        var gt = str.indexOf("&gt;", gt + 1);
        var newStr = str.substr(0, gt);
        newStr += ">";
        newStr = newStr + str.substr(gt + 4, str.length);
        str = newStr;
    }
    gt = -1;
    while (str.indexOf("&amp;", gt + 1) > -1) {
        var gt = str.indexOf("&amp;", gt + 1);
        var newStr = str.substr(0, gt);
        newStr += "&";
        newStr = newStr + str.substr(gt + 5, str.length);
        str = newStr;
    }

    return str;
}
function convertToEscapes(str) {
    var gt = -1;
    while (str.indexOf("&", gt + 1) > -1) {
        gt = str.indexOf("&", gt + 1);
        var newStr = str.substr(0, gt);
        newStr += "&amp;";
        newStr = newStr + str.substr(gt + 1, str.length);
        str = newStr;
    }
    gt = -1;
    while (str.indexOf("<", gt + 1) > -1) {
        var gt = str.indexOf("<", gt + 1);
        var newStr = str.substr(0, gt);
        newStr += "&lt;";
        newStr = newStr + str.substr(gt + 1, str.length);
        str = newStr;
    }
    gt = -1;
    while (str.indexOf(">", gt + 1) > -1) {
        var gt = str.indexOf(">", gt + 1);
        var newStr = str.substr(0, gt);
        newStr += "&gt;";
        newStr = newStr + str.substr(gt + 1, str.length);
        str = newStr;
    }


    return str;
} 

function _displayElement(domElement, strRet) {
    if(domElement==null) {
        return;
    }
    if(!(domElement.nodeType=='ELEMENT')) {
        return;
    }
    var tagName = domElement.tagName;
    var tagInfo = "";
    tagInfo = "<" + tagName;
    var attributeList = domElement.getAttributeNames();
    for(var intLoop = 0; intLoop < attributeList.length; intLoop++) {
        var attribute = attributeList[intLoop];
        tagInfo = tagInfo + " " + attribute + "=";
        tagInfo = tagInfo + "\"" + domElement.getAttribute(attribute) + "\"";
    }
    tagInfo = tagInfo + ">";
    strRet=strRet+tagInfo;
    if(domElement.children!=null) {
        var domElements = domElement.children;
        for(var intLoop = 0; intLoop < domElements.length; intLoop++) {
            var childNode = domElements[intLoop];
            if(childNode.nodeType=='COMMENT') {
                strRet = strRet + "<!--" + childNode.content + "-->";
            }

            else if(childNode.nodeType=='TEXT') {
                var cont = trim(childNode.content,true,true);
                strRet = strRet + childNode.content;
            }

            else if (childNode.nodeType=='CDATA') {
                var cont = trim(childNode.content,true,true);
                strRet = strRet + "<![CDATA[" + cont + "]]>";
            }

            else {
                strRet = _displayElement(childNode, strRet);
            }
        }
    }
    strRet = strRet + "</" + tagName + ">";
    return strRet;
}
function firstWhiteChar(str,pos) {
    if (isEmpty(str)) {
        return -1;
    }
    while(pos < str.length) {
        if (whitespace.indexOf(str.charAt(pos))!=-1) {
            return pos;
        }
        else {
            pos++;
        }
    }
    return str.length;
}
function isEmpty(str) {
    return (str==null) || (str.length==0);

}

function trim(trimString, leftTrim, rightTrim) {
    if (isEmpty(trimString)) {
        return "";
    }
    if (leftTrim == null) {
        leftTrim = true;
    }
    if (rightTrim == null) {
        rightTrim = true;
    }
    var left=0;
    var right=0;
    var i=0;
    var k=0;
    if (leftTrim == true) {
        while ((i<trimString.length) && (whitespace.indexOf(trimString.charAt(i++))!=-1)) {
            left++;
        }
    }
    if (rightTrim == true) {
        k=trimString.length-1;
        while((k>=left) && (whitespace.indexOf(trimString.charAt(k--))!=-1)) {
            right++;
        }
    }
    return trimString.substring(left, trimString.length - right);
}
function XMLDoc(source, errFn) {
    this.topNode=null;
    this.errFn = errFn; 
    this.createXMLNode = _XMLDoc_createXMLNode;
    this.error = _XMLDoc_error;
    this.getUnderlyingXMLText = _XMLDoc_getUnderlyingXMLText;
    this.handleNode = _XMLDoc_handleNode;
    this.hasErrors = false;
    this.insertNodeAfter =  _XMLDoc_insertNodeAfter;
    this.insertNodeInto = _XMLDoc_insertNodeInto;
    this.loadXML = _XMLDoc_loadXML;
	this.parse = _XMLDoc_parse;
    this.parseAttribute = _XMLDoc_parseAttribute;
    this.parseDTD = _XMLDoc_parseDTD;
    this.parsePI = _XMLDoc_parsePI;
    this.parseTag = _XMLDoc_parseTag;
    this.removeNodeFromTree = _XMLDoc_removeNodeFromTree;
    this.replaceNodeContents = _XMLDoc_replaceNodeContents;
    this.selectNode = _XMLDoc_selectNode;
	this.selectNodeText = _XMLDoc_selectNodeText;
	this.source = source;
    if (this.parse()) {
        if (this.topNode!=null) {
            return this.error("expected close " + this.topNode.tagName);
        }
        else {
            return true;
        }
    }
}
function _XMLDoc_createXMLNode(strXML) {
    return new XMLDoc(strXML, this.errFn).docNode;
} 

function _XMLDoc_error(str) {  
	this.hasErrors=true;
    if(this.errFn){
        this.errFn("ERROR: " + str);
    }else if(this.onerror){
        this.onerror("ERROR: " + str); 
	}
    return 0;
	
}
function _XMLDoc_getTagNameParams(tag,obj){
	var elm=-1,e,s=tag.indexOf('[');
	var attr=[];
	if(s>=0){
		e=tag.indexOf(']');
		if(e>=0)elm=tag.substr(s+1,(e-s)-1);
		else obj.error('expected ] near '+tag);
		tag=tag.substr(0,s);
		if(isNaN(elm) && elm!='*'){
			attr=elm.substr(1,elm.length-1);
			attr=attr.split('=');
			if(attr[1]) { //remove "
				s=attr[1].indexOf('"');
				attr[1]=attr[1].substr(s+1,attr[1].length-1);
				e=attr[1].indexOf('"');
				if(e>=0) attr[1]=attr[1].substr(0,e);
				else obj.error('expected " near '+tag)
			};elm=-1;
		}else if(elm=='*') elm=-1;
	}
	return [tag,elm,attr[0],attr[1]]
}
function _XMLDoc_getUnderlyingXMLText() {
    var strRet = "";
    strRet = strRet + "<?xml version=\"1.0\"?>";
    if (this.docNode==null) {
        return;
    }
    strRet = _displayElement(this.docNode, strRet);
    return strRet;
} 
function _XMLDoc_handleNode(current) {
    if ((current.nodeType=='COMMENT') && (this.topNode!=null)) {
        return this.topNode.addElement(current);
    }
    else if ((current.nodeType=='TEXT') ||  (current.nodeType=='CDATA')) {
        if(this.topNode==null) {
            if (trim(current.content,true,false)=="") {
                return true;
            }
            else {
                return this.error("expected document node, found: " + current);
            }
        }
        else {
            return this.topNode.addElement(current);
        }
    }
    else if ((current.nodeType=='OPEN') || (current.nodeType=='SINGLE')) {
        var success = false;
        if(this.topNode==null) {
            this.docNode = current;
            current.parent = null;
            success = true;
        }
        else {
            success = this.topNode.addElement(current);
        }
        if (success && (current.nodeType!='SINGLE')) {
            this.topNode = current;
        }
        current.nodeType = "ELEMENT";
        return success;
    }
    else if (current.nodeType=='CLOSE') {
        if (this.topNode==null) {
            return this.error("close tag without open: " +  current.toString());
        }
        else {
            if (current.tagName!=this.topNode.tagName) {
                return this.error("expected closing " + this.topNode.tagName + ", found closing " + current.tagName);
            }
            else {
                this.topNode = this.topNode.getParent();
            }
        }
    }
    return true;
}

function _XMLDoc_insertNodeAfter (referenceNode, newNode) {
    var parentXMLText = this.getUnderlyingXMLText();
    var selectedNodeXMLText = referenceNode.getUnderlyingXMLText();
    var originalNodePos = parentXMLText.indexOf(selectedNodeXMLText) + selectedNodeXMLText.length;
    var newXML = parentXMLText.substr(0,originalNodePos);
    newXML += newNode.getUnderlyingXMLText();
    newXML += parentXMLText.substr(originalNodePos);
    var newDoc = new XMLDoc(newXML, this.errFn);
    return newDoc;

}

function _XMLDoc_insertNodeInto (referenceNode, insertNode) {
    var parentXMLText = this.getUnderlyingXMLText();
    var selectedNodeXMLText = referenceNode.getUnderlyingXMLText();
    var endFirstTag = selectedNodeXMLText.indexOf(">") + 1;
    var originalNodePos = parentXMLText.indexOf(selectedNodeXMLText) + endFirstTag;
    var newXML = parentXMLText.substr(0,originalNodePos);
    newXML += insertNode.getUnderlyingXMLText();
    newXML += parentXMLText.substr(originalNodePos);
    var newDoc = new XMLDoc(newXML, this.errFn);
    return newDoc;
}

function _XMLDoc_loadXML(source){
	this.topNode=null;
	this.hasErrors = false;
	this.source=source;
    // parse the document
	return this.parse();

}

function _XMLDoc_parse() {
    var pos = 0;
    err = false;
    while(!err) {
        var closing_tag_prefix = '';
        var chpos = this.source.indexOf('<',pos);
        var open_length = 1;
        var open;
        var close;
        if (chpos ==-1) {
            break;
        }
        open = chpos;
        var str = this.source.substring(pos, open);
        if (str.length!=0) {
            err = !this.handleNode(new XMLNode('TEXT',this, str));
        }
        if (chpos == this.source.indexOf("<?",pos)) {
            pos = this.parsePI(this.source, pos + 2);
            if (pos==0) {
                err=true;
            }
            continue;
        }
        if (chpos == this.source.indexOf("<!DOCTYPE",pos)) {
            pos = this.parseDTD(this.source, chpos+ 9);
            if (pos==0) {
                err=true;
            }
            continue;
        }
        if(chpos == this.source.indexOf('<!--',pos)) {
            open_length = 4;
            closing_tag_prefix = '--';
        }
        if (chpos == this.source.indexOf('<![CDATA[',pos)) {
            open_length = 9;
            closing_tag_prefix = ']]';
        }
        chpos = this.source.indexOf(closing_tag_prefix + '>',chpos);
        if (chpos ==-1) {
            return this.error("expected closing tag sequence: " + closing_tag_prefix + '>');
        }
        close = chpos + closing_tag_prefix.length;
        str = this.source.substring(open+1, close);
        var n = this.parseTag(str);
        if (n) {
            err = !this.handleNode(n);
        }
        pos = close +1;
    }
    return !err;
}

function _XMLDoc_parseAttribute(src,pos,node) {
    while ((pos<src.length) && (whitespace.indexOf(src.charAt(pos))!=-1)) {
        pos++;
    }
    if (pos >= src.length) {
        return pos;
    }
    var p1 = pos;
    while ((pos < src.length) && (src.charAt(pos)!='=')) {
        pos++;
    }
    var msg = "attributes must have values";
    if(pos >= src.length) {
        return this.error(msg);
    }
    var paramname = trim(src.substring(p1,pos++),false,true);
    while ((pos < src.length) && (whitespace.indexOf(src.charAt(pos))!=-1)) {
        pos++;
    }
    if (pos >= src.length) {
        return this.error(msg);
    }
    msg = "attribute values must be in quotes";
    var quote = src.charAt(pos++);
    if (quotes.indexOf(quote)==-1) {
        return this.error(msg);
    }
    p1 = pos;
    while ((pos < src.length) && (src.charAt(pos)!=quote)) {
        pos++;
    }
    if (pos >= src.length) {
        return this.error(msg);
    }
    if (!node.addAttribute(paramname,trim(src.substring(p1,pos++),false,true))) {
        return 0;
    }
    return pos;

}

function _XMLDoc_parseDTD(str,pos) {
    var firstClose = str.indexOf('>',pos);
    if (firstClose==-1) {
        return this.error("error in DTD: expected '>'");
    }
    var closing_tag_prefix = '';
    var firstOpenSquare = str.indexOf('[',pos);
    if ((firstOpenSquare!=-1) && (firstOpenSquare < firstClose)) {
        closing_tag_prefix = ']';
    }
    while(true) {
        var closepos = str.indexOf(closing_tag_prefix + '>',pos);
        if (closepos ==-1) {
            return this.error("expected closing tag sequence: " + closing_tag_prefix + '>');
        }
        pos = closepos + closing_tag_prefix.length +1;
        if (str.substring(closepos-1,closepos+2) != ']]>') {
            break;
        }
    }
    return pos;
}

function _XMLDoc_parsePI(str,pos) {
    var closepos = str.indexOf('?>',pos);
    return closepos + 2;
}

function _XMLDoc_parseTag(src) {
    if (src.indexOf('!--')==0) {
        return new XMLNode('COMMENT', this, src.substring(3,src.length-2));
    }
    if (src.indexOf('![CDATA[')==0) {
        return new XMLNode('CDATA', this, src.substring(8,src.length-2));
    }
    var n = new XMLNode();
    n.doc = this;
    if (src.charAt(0)=='/') {
        n.nodeType = 'CLOSE';
        src = src.substring(1);
    }
    else {
        n.nodeType = 'OPEN';
    }
    if (src.charAt(src.length-1)=='/') {
        if (n.nodeType=='CLOSE') {
            return this.error("singleton close tag");
        }
        else {
            n.nodeType = 'SINGLE';
        }
        src = src.substring(0,src.length-1);
    }
    if (n.nodeType!='CLOSE') {
        n.attributes = new Array();
    }

    if (n.nodeType=='OPEN') {
        n.children = new Array();
    }
    src = trim(src,true,true);
    if (src.length==0) {
        return this.error("empty tag");
    }
    var endOfName = firstWhiteChar(src,0);
    if (endOfName==-1) {
        n.tagName = src;
        return n;
    }
    n.tagName = src.substring(0,endOfName);
    var pos = endOfName;
    while(pos< src.length) {
        pos = this.parseAttribute(src, pos, n);
        if (this.pos==0) {
            return null;
        }
    }
    return n;
}

function _XMLDoc_removeNodeFromTree(node) {
    var parentXMLText = this.getUnderlyingXMLText();
    var selectedNodeXMLText = node.getUnderlyingXMLText();
    var originalNodePos = parentXMLText.indexOf(selectedNodeXMLText);
    var newXML = parentXMLText.substr(0,originalNodePos);
    newXML += parentXMLText.substr(originalNodePos + selectedNodeXMLText.length);
    var newDoc = new XMLDoc(newXML, this.errFn);
    return newDoc;
}

function _XMLDoc_replaceNodeContents(referenceNode, newContents) {
    var newNode = this.createXMLNode("<X>" + newContents + "</X>");
    referenceNode.children = newNode.children;
    return this;
}

function _XMLDoc_selectNode(tagpath){
	tagpath = trim(tagpath, true, true);
	var srcnode,node,tag,params,elm,rg;
	var tags,attrName,attrValue,ok;
	srcnode=node=((this.source)?this.docNode:this);
	if (!tagpath) return node;
	if(tagpath.indexOf('/')==0)tagpath=tagpath.substr(1);
	tagpath=tagpath.replace(tag,'');
	tags=tagpath.split('/');
	tag=tags[0];
	if(tag){
		if(tagpath.indexOf('/')==0)tagpath=tagpath.substr(1);
		tagpath=tagpath.replace(tag,'');
		params=_XMLDoc_getTagNameParams(tag,this);
		tag=params[0];elm=params[1];
		attrName=params[2];attrValue=params[3];
		node=(tag=='*')? node.getElements():node.getElements(tag);
		if (node.length) {
			if(elm<0){
				srcnode=node;var i=0;
				while(i<srcnode.length){
					if(attrName){
						if (srcnode[i].getAttribute(attrName)!=attrValue) ok=false;
						else ok=true;
					}else ok=true;
					if(ok){
						node=srcnode[i].selectNode(tagpath);
						if(node) return node;
					}
					i++;
				}
			}else if (elm<node.length){
				node=node[elm].selectNode(tagpath);
				if(node) return node;
			}
		}
	}
}

function _XMLDoc_selectNodeText(tagpath){
    var node=this.selectNode(tagpath);
    if (node != null) {
		return node.getText();
	}
    else {
		return null;
	}
}

function XMLNode(nodeType,doc, str) {
     if (nodeType=='TEXT' || nodeType=='CDATA' || nodeType=='COMMENT' ) {
        this.content = str;
    }
    else {
        this.content = null;
    }
    this.attributes = null;
    this.children = null;
    this.doc = doc;
    this.nodeType = nodeType;
    this.parent = "";
    this.tagName = "";
    this.addAttribute = _XMLNode_addAttribute;
    this.addElement = _XMLNode_addElement;
    this.getAttribute = _XMLNode_getAttribute;
    this.getAttributeNames = _XMLNode_getAttributeNames;
    this.getElementById = _XMLNode_getElementById;
    this.getElements = _XMLNode_getElements;
    this.getText = _XMLNode_getText;
    this.getParent = _XMLNode_getParent;
    this.getUnderlyingXMLText = _XMLNode_getUnderlyingXMLText;
    this.removeAttribute = _XMLNode_removeAttribute;
    this.selectNode = _XMLDoc_selectNode;
	this.selectNodeText = _XMLDoc_selectNodeText;
    this.toString = _XMLNode_toString;
}

function _XMLNode_addAttribute(attributeName,attributeValue) {
    this.attributes['_' + attributeName] = attributeValue;
    return true;
}

function _XMLNode_addElement(node) {
    node.parent = this;
    this.children[this.children.length] = node;
    return true;
}

function _XMLNode_getAttribute(name) {
    if (this.attributes == null) {
        return null;
    }
    return this.attributes['_' + name];
}

function _XMLNode_getAttributeNames() {
    if (this.attributes == null) {
        var ret = new Array();
        return ret;
    }

    var attlist = new Array();

    for (var a in this.attributes) {
        attlist[attlist.length] = a.substring(1);
    }
    return attlist;
}

function _XMLNode_getElementById(id) {
    var node = this;
    var ret;
   if (node.getAttribute("id") == id) {
        return node;
    }
    else{
        var elements = node.getElements();
        var intLoop = 0;
        while (intLoop < elements.length) {
            var element = elements[intLoop];
            ret = element.getElementById(id);
            if (ret != null) {
                break;
            }
            intLoop++;
        }
    }
    return ret;
}

function _XMLNode_getElements(byName) {
    if (this.children==null) {
        var ret = new Array();
        return ret;
    }

    var elements = new Array();
    for (var i=0; i<this.children.length; i++) {
        if ((this.children[i].nodeType=='ELEMENT') && ((byName==null) || (this.children[i].tagName == byName))) {
            elements[elements.length] = this.children[i];
        }
    }
    return elements;
}

function _XMLNode_getText() {
    if (this.nodeType=='ELEMENT') {
        if (this.children==null) {
            return null;
        }
        var str = "";
        for (var i=0; i < this.children.length; i++) {
            var t = this.children[i].getText();
            str +=  (t == null ? "" : t);
        }
        return str;
    }
    else if (this.nodeType=='TEXT') {
        return convertEscapes(this.content);
    }
    else {
        return this.content;
    }
}

function _XMLNode_getParent() {
    return this.parent;

}

function _XMLNode_getUnderlyingXMLText() {
    var strRet = "";
    strRet = _displayElement(this, strRet);
    return strRet;

}

function _XMLNode_removeAttribute(attributeName) {
    if(attributeName == null) {
        return this.doc.error("You must pass an attribute name into the removeAttribute function");
    }

    var attributes = this.getAttributeNames();
    var intCount = attributes.length;
    var tmpAttributeValues = new Array();
    for ( intLoop = 0; intLoop < intCount; intLoop++) {
        tmpAttributeValues[intLoop] = this.getAttribute(attributes[intLoop]);
    }
    this.attributes = new Array();
    for (intLoop = 0; intLoop < intCount; intLoop++) {
        if ( attributes[intLoop] != attributeName) {
            this.addAttribute(attributes[intLoop], tmpAttributeValues[intLoop]);
        }
    }
return true;
}

function _XMLNode_toString() {
    return "" + this.nodeType + ":" + (this.nodeType=='TEXT' || this.nodeType=='CDATA' || this.nodeType=='COMMENT' ? this.content : this.tagName);

}