/* ENTREE XML DOCUMENT CLASS */

/* CROSS BROWSER SUPPORT
 * SUPPORT FOR IE6+, FIREFOX 2.0+, SAFARI 3.1+, OPERA 9.2+
 */

/* Create a new entree xml document
 * SXML - the initial xml string or an xml node as root to load the document, OPTIONAL
 */
XmlDocument = function(sXml) {

    /* Load an xml string STR or an xml node as root into this document */
    this.loadXml = function(str) {
        if (typeof str != 'string' && str.xml) {
            str = str.xml;
        }
        else if (typeof str != 'string') {
            try {
                str = new XMLSerializer().serializeToString(str);
            }
            catch(e) {
                alert('enXmlDoc - loadXml ' + e.message);
            }
        }
        try
        {
            xmldoc.loadXML(str);
        }
        catch(e)
        {
            try
            {
                /* Remove all children */
                var parser = new DOMParser();
                xmldoc = parser.parseFromString(str, "text/xml");
                parser = null;
            }
            catch(e)
            {
                alert('enXmlDoc - loadXml ' + e.message);
            }
        }
    };
    
    /* Load an xml document with path PATH */
    this.loadFromFile = function(path) {
        try {
            xmldoc.load(path);
        }
        catch(e)
        {
			try {
				XMLDocument.prototype.load = function(filePath) {
					var xmlhttp = new XMLHttpRequest();
					xmlhttp.open("GET", filePath, false);
					xmlhttp.send(null);
					var returnDoc = xmlhttp.responseXML;
					if (returnDoc) {
						var newElement = this.importNode(returnDoc.documentElement, true);
						this.appendChild(newElement);
						return true;
					}
				}
				xmldoc.load(path);
			}
			catch(e) {
				alert('enXmlDoc - loadFromFile ' + e.message);
			}
        }
    };
    
    /* Get the nth occurance of node in the xml document with tag name TAGNAME and optionally specify attrbute name ATTR and value VALUE
     * Return the first occurance of n is not defined
     * !!HINT - tagName can be expressed as XPATH (eg. Data/Row/Elem), this will get the nth occurance of Elem, within Row, within Data
     * TAGNAME - tag name, REQUIRED
     * ATTR - attribute name, OPTIONAL
     * VALUE - attribute value, OPTIONAL
     * N - the nth occurance to get
    */    
    this.selectSingleNodeByName = function(tagName, attr, value, n) {
        var nodes = this.getNodesByName(tagName, attr, value);
        n = n || 0;
        if (nodes && nodes.length > n)
            return nodes[n];
        else
            return null;
    };
	
	/* Get all nodes in the xml document with tag name TAGNAME and optionally specify attrbute name ATTR and value VALUE
	 * !!HINT - tagName can be expressed as XPATH (eg. Data/Row/Elem), this will get all occurances of Elem, within Row, within Data
     * TAGNAME - tag name, REQUIRED
     * ATTR - attribute name, OPTIONAL
     * VALUE - attribute value, OPTIONAL
    */    
    this.selectNodesByName = function(tagName, attr, value) {
        try {
			if (attr != null && value != null) {
				return xmldoc.selectNodes('//' + tagName + '[@' + attr + '="' + value + '"]');
			} 
			else if (attr != null) {
				return xmldoc.selectNodes('//' + tagName + '[@' + attr + ']');
			}
			else {
			    return xmldoc.selectNodes('//' + tagName);
			}
		}
		catch(e) {
		    try {
		        var oE = new XPathEvaluator();
			var nsResolver = oE.createNSResolver( xmldoc.ownerDocument == null ? xmldoc.documentElement : xmldoc.ownerDocument.documentElement);
		        var oR = null;
		        if (attr != null && value != null) {
			        oR = oE.evaluate('//' + tagName + '[@' + attr + '="' + value + '"]',xmldoc,nsResolver,XPathResult.ORDERED_NODE_ITERATOR_TYPE,null);								
		        }
		        else if (attr != null) {
			        oR = oE.evaluate('//' + tagName + '[@' + attr + ']',xmldoc,nsResolver,XPathResult.ORDERED_NODE_ITERATOR_TYPE,null);	
		        }
		        else {
			        oR = oE.evaluate('//' + tagName,xmldoc,nsResolver,XPathResult.ORDERED_NODE_ITERATOR_TYPE,null);
		        }			
		        if (oR != null) {
		            var nRet = new Array();
		            while (n = oR.iterateNext()) {
		                nRet.push(n);
		            }
			        return nRet;
		        }
		    }
		    catch(e) {
		        alert('enXmlDoc.getNodesByName - ' + e.message);
		    }
		}	
	};
    
    /* Get nth occurance of node in the xml document with parent tag name PARENTTAGNAME and child with tag name CHILDTAGNAME
     * Return the first occurance if n is not defined
     * !!HINT - tagName can be expressed as XPATH (eg. Data/Row/Elem), this will get the nth occurance of Elem, within Row, within Data, within ParentTagName
     * PARENTTAGNAME - parent tag name, REQUIRED
     * CHILDTAGNAME - child tag name, REQUIRED
     * N - nth occurance of nodes to return, OPTIONAL
    */    
    this.selectSingleChildByParent = function(parentTagName, childTagName, n) {
        var nodes = this.getChildrenByParent(parentTagName, childTagName);
        n = n || 0;
        if (nodes && nodes.length > n)
            return nodes[n];
        else
            return null;
	};
	
	/* Get all nodes in the xml document with parent tag name PARENTTAGNAME and child with tag name CHILDTAGNAME
	 * !!HINT - tagName can be expressed as XPATH (eg. Data/Row/Elem), this will get all occurances of Elem, within Row, within Data, within ParentTagName
     * PARENTTAGNAME - parent tag name, REQUIRED
     * CHILDTAGNAME - child tag name, REQUIRED
    */    
    this.selectChildrenByParent = function(parentTagName, childTagName) {
		try {
		    return xmldoc.selectNodes('//' + parentTagName + '/' + childTagName);
		}
		catch(e) {
		    try {
		        var oE = new XPathEvaluator();
			var nsResolver = oE.createNSResolver( xmldoc.ownerDocument == null ? xmldoc.documentElement : xmldoc.ownerDocument.documentElement);
		        var oR = oE.evaluate('//' + parentTagName + '/' + childTagName,xmldoc,nsResolver,XPathResult.ORDERED_NODE_ITERATOR_TYPE,null);								
		        if (oR != null) {
			        var nRet = new Array();
		            while (n = oR.iterateNext()) {
		                nRet.push(n);
		            }
			        return nRet;
		        }
		    }
		    catch(e) {
		        alert('enXmlDoc.getChildrenByParent - ' + e.message);
		    }
		}			
	};
     
    /* Add an existing xml node from another xml document as a child of PARENT
     * PARENT - The node to append the new child to, REQUIRED
     * CHILD - The existing node to add, REQUIRED
     */
    this.appendChild = function(parent, child) {
        try {
            parent.appendChild(child);
        }
        catch(e) {
            try {
                var c = parent.ownerDocument.importNode(child, true);
                parent.appendChild(c);
            }
            catch(e) {
                alert('enXmlDoc.appendChild - ' + e.message);
            }
        }
    }; 
      
    /* Add a node with node name NODENAME and attributes ATTRS and inner text TEXT as a child to node PARENT
     * NODENAME - new node name, REQUIRED
     * PARENT - parent node to append the child to, REQUIRED
     * ATTRS - new node's attrbutes, { name : value, name : value, ... }, OPTIONAL
     * TEXT - inner text, OPTIONAL
     * @return Return the newly added node
    */
    this.addChild = function(nodeName, parent, attrs, text) {
        var newNode = parent.ownerDocument.createElement(nodeName);
        for (var a in attrs) {
            newNode.setAttribute(a, attrs[a]);
        }
        parent.appendChild(newNode);
        if (text != null)
            newNode.appendChild(parent.ownerDocument.createTextNode(text));
        return newNode;
    };
    
    /* Return innerText of NODE
     * Only get first level of innerText
     * Assume NODE only contains text
     */
    this.getInnerText = function(node) {
        var s = '';
        for (var i=0; i<node.childNodes.length; i++) {
            s += node.childNodes[i].nodeValue;
        }
        return s;
    };
    
    /* Get the whole document xml string */
    this.getXmlString = function() {
        if (xmldoc.xml)
            return xmldoc.xml;
        else {
            try {
                var serializer = new XMLSerializer();
                return serializer.serializeToString(xmldoc);
            }
            catch(e) {
                alert('enXmlDoc.xml - ' + e.message);
            }
        }            
    };
    
    /* Get the whole document xml string well formatted for display 
     * INDENT - character(s) to indent the nodes, OPTIONAL, default to two empty spaces
    */
    this.getFormattedXmlString = function(indent) {
        indent = indent || '  ';
        var str = this.getXmlString();
        var retStr = '';
        var curIndent = '';
        var curMode = 0; // 0 is open, 1 is close
        for(var s=0; s<str.length; s++) {
            if (str.charAt(s) == '<' && str.charAt(s+1) != '/') {
                curMode = 0;
                if (s != 0)
                    retStr = retStr + '\n' + curIndent;
                curIndent = curIndent + indent;
            }
            if (str.charAt(s) == '<' && str.charAt(s+1) == '/' && str.charAt(s-1) == '>') {
                curMode = 1;
                retStr = retStr + '\n' + curIndent.substring(0, curIndent.length - indent.length);
            }
            if ((str.charAt(s) == '/' && str.charAt(s-1) != '<') || (str.charAt(s) == '/' && str.charAt(s+1) != '>')) {
                curMode = 1;
            }
            if (str.charAt(s) == '>' && curMode == 1) {
                curIndent = curIndent.substring(0, curIndent.length - indent.length);
            }
            retStr += str.charAt(s);
        }
        return retStr;
    };
    
    /* Return the underlying xmldoc */
    this.getXmlDoc = function() {
        return xmldoc;
    };

	/*
	 * Transform the given xml document according to this document xslt definition
	 * Return the result string and insert the result into dom element node if supplied
	*/
	this.xsltTransform = function(xmldom, node) {
		var html;
		try {
			html = xmldom.getXmlDoc().transformNode(xmldoc);
		}
		catch(e) {
			var oProcessor = new XSLTProcessor();
			oProcessor.importStylesheet(xmldoc);
			oResultDom = oProcessor.transformToFragment(xmldom.getXmlDoc(), document);
			var serializer = new XMLSerializer();
			html = serializer.serializeToString(oResultDom);
		}
		if (node) {
			if (node.innerHTML != null) {
				node.innerHTML = html;
			}
			else {
				var range = node.ownerDocument.createRange();
				range.selectNodeContents(node);
				range.deleteContents();
				var fragment = range.createContextualFragment(html);
				node.appendChild(fragment);
			}
		}
		return html;
	}
	
    /* START PRIVATE FUNCTIONS */
    
    /* END PRIVATE FUNCTIONS */
    
    /* START CONSTRUCTOR */
    var xmldoc = null;
    
    try {
        xmldoc = new ActiveXObject('Microsoft.XMLDOM');
    }
    catch(e) {
        try {
            xmldoc = document.implementation.createDocument('text/xml', '', null);
        }
        catch(e) {
			alert('Your browser is currently not supported!');
        }
    }
    xmldoc.async = false;
    if (sXml && typeof sXml == 'string')
        this.loadXml(sXml);
    else if (sXml) {
        try {
            this.loadXml(sXml.xml || sXml);
        }
        catch(e) {
            alert('enXmlDoc - constructor');
        }
    }
    /* END CONSTRUCTOR */
}
