﻿var XML = {};

/**
 * Create a new Document object. If no arguments are specified,
 * the document will be empty. If a root tag is specified, the document
 * will contain that single root tag. If the root tag has a namespace
 * prefix, the second argument must specify the URL that identifies the
 * namespace.
 */
XML.newDocument = function(rootTagName, namespaceURL) {
  if (!rootTagName) rootTagName = "";
  if (!namespaceURL) namespaceURL = "";
  if (document.implementation && document.implementation.createDocument) {
    // This is the W3C standard way to do it
    return document.implementation.createDocument(namespaceURL, rootTagName, null);
  }
  else { // This is the IE way to do it
    // Create an empty document as an ActiveX object
    // If there is no root element, this is all we have to do
    var doc = new ActiveXObject("MSXML2.DOMDocument");
    // If there is a root tag, initialize the document
    if (rootTagName) {
      // Look for a namespace prefix
      var prefix = "";
      var tagname = rootTagName;
      var p = rootTagName.indexOf(':');
      if (p != -1) {
        prefix = rootTagName.substring(0, p);
        tagname = rootTagName.substring(p+1);
      }
      // If we have a namespace, we must have a namespace prefix
      // If we don't have a namespace, we discard any prefix
      if (namespaceURL) {
        if (!prefix) prefix = "a0"; // What Firefox uses
      }
      else prefix = "";
      // Create the root element (with optional namespace) as a
      // string of text
      var text = "<" + (prefix?(prefix+":"):"") +  tagname +
          (namespaceURL
           ?(" xmlns:" + prefix + '="' + namespaceURL +'"')
           :"") +
          "/>";
      // And parse that text into the empty document
      doc.loadXML(text);
    }
    return doc;
  }
};

/**
 * Synchronously load the XML document at the specified URL and
 * return it as a Document object
 */
XML.load = function(url) {
    // Create a new document with the previously defined function
    var xmldoc = XML.newDocument();
    xmldoc.async = false;  // We want to load synchronously
    xmldoc.load(url);      // Load and parse
    return xmldoc;         // Return the document
};

/**
 * Asynchronously load and parse an XML document from the specified URL.
 * When the document is ready, pass it to the specified callback function.
 * This function returns immediately with no return value.
 */
XML.loadAsync = function(url, callback) {
    var xmldoc = XML.newDocument();
    // If we created the XML document using createDocument, use
    // onload to determine when it is loaded
    if (document.implementation && document.implementation.createDocument) {
        xmldoc.onload = function() { callback(xmldoc); };
    }
    // Otherwise, use onreadystatechange as with XMLHttpRequest
    else {
        xmldoc.onreadystatechange = function() {
            if (xmldoc.readyState == 4) callback(xmldoc);
        };
    }
    // Now go start the download and parsing
    xmldoc.load(url);
};

/**
 * Parse the XML document contained in the string argument and return
 * a Document object that represents it.
 */
XML.parse = function(text) {
    if (typeof DOMParser != "undefined") {
        // Mozilla, Firefox, and related browsers
        return (new DOMParser()).parseFromString(text, "text/xml");
    }
    else if (typeof ActiveXObject != "undefined") {
        // Internet Explorer.
        var doc = XML.newDocument();  // Create an empty document
        doc.loadXML(text);            // Parse text into it
        return doc;                   // Return it
    }
    else {
        // As a last resort, try loading the document from a data: URL
        // This is supposed to work in Safari. Thanks to Manos Batsis and
        // his Sarissa library (sarissa.sourceforge.net) for this technique.
        var url = "data:text/xml;charset=utf-8," + encodeURIComponent(text);
        var request = new XMLHttpRequest();
        request.open("GET", url, false);
        request.send(null);
        return request.responseXML;
    }
};

/**
 * Return a Document object that holds the contents of the <xml> tag
 * with the specified id. If the <xml> tag has a src attribute, an XML
 * document is loaded from that URL and returned instead.
 *
 * Since data islands are often looked up more than once, this function caches
 * the documents it returns.
 */
XML.getDataIsland = function(id) {
    var doc;
    // Check the cache first
    doc = XML.getDataIsland.cache[id];
    if (doc) return doc;
    // Look up the specified element
    doc = document.getElementById(id);
    // If there is a "src" attribute, fetch the Document from that URL
    var url = doc.getAttribute('src');
    if (url) {
        doc = XML.load(url);
    }
    // Otherwise, if there was no src attribute, the content of the <xml>
    // tag is the document we want to return. In Internet Explorer, doc is
    // already the document object we want. In other browsers, doc refers to
    // an HTML element, and we've got to copy the content of that element
    // into a new document object
    else if (!doc.documentElement) {// If this is not already a document...
        // First, find the document element within the <xml> tag. This is
        // the first child of the <xml> tag that is an element, rather
        // than text, comment, or processing instruction
        var docelt = doc.firstChild;
        while(docelt != null) {
            if (docelt.nodeType == 1 /*Node.ELEMENT_NODE*/) break;
            docelt = docelt.nextSibling;
        }
        // Create an empty document
        doc = XML.newDocument();
        // If the <xml> node had some content, import it into the new document
        if (docelt) doc.appendChild(doc.importNode(docelt, true));
    }
    // Now cache and return the document
    XML.getDataIsland.cache[id] = doc;
    return doc;
};
XML.getDataIsland.cache = {}; // Initialize the cache

XML.addTextElementToNode = function(xmlDoc, ElementTag, ElementText)
{
    var nNode = xmlDoc.createElement(ElementTag);
    var nText = xmlDoc.createTextNode(ElementText);
    nNode.appendChild(nText);
    xmlDoc.firstChild.appendChild(nNode);
};

XML.XMLNodeArraytoJSONArray = function(xmlNodes)
{
    var jsonArray = new Array();
    for (var i=0; i<xmlNodes.length; i++)
    {
        jsonArray.push(XML.XMLNodetoJSON(xmlNodes[i]));
    }
    return jsonArray;
};

XML.XMLNodetoJXML = function(xmlNode)
{
    var res = {};
    res.nodeType = xmlNode.nodeType;
    
    switch (xmlNode.nodeType)
    {
        case 1: // NODE_ELEMENT
            res.nodeName = xmlNode.nodeName;
            
        case 9: // NODE_DOCUMENT
            
            var myChildNodes = xmlNode.childNodes;
            var childNodes = new Array();
            res.childNodes = childNodes;
            
            for (var intNode=0; intNode < myChildNodes.length; intNode++)
            {
                var evalNode = myChildNodes[intNode];
                childNodes.push(XML.XMLNodetoJXML(evalNode));
            }
            
            break;
            
        case 2: // NODE_ATTRIBUTE
        case 3: // NODE_TEXT
            res.nodeName = xmlNode.nodeName;
            res.nodeValue = xmlNode.nodeValue;
            break;

        case 4: // NODE_CDATA_SECTION
            res.nodeName = xmlNode.nodeName;
            res.nodeValue = eval("(" + xmlNode.data + ")");
            break;

        case 5: // NODE_ENTITY_REFERENCE
        case 6: // NODE_ENTITY
        case 7: // NODE_PROCESSING_INSTRUCTION
        case 8: // NODE_COMMENT
        case 10: // NODE_DOCUMENT_TYPE
        case 11: // NODE_DOCUMENT_FRAGMENT
        case 12: // NODE_NOTATION
        default:
            res.remark = "not implemented(" + xmlNode.nodeType + ".";
            break;            
            
    }
    
    return res;
    
};

XML.XMLNodetoJSON = function(xmlNode, track)
{
    // Check if it is a text node
    switch (xmlNode.nodeType)
    {
        case 1: // NODE_ELEMENT
        
            var myChildNodes = xmlNode.childNodes;
            
            // Check if it has one and only text node child
            if (xmlNode.hasChildNodes && myChildNodes.length==1)
            {
                if (myChildNodes[0].nodeType==3)
                    return myChildNodes[0].nodeValue;
            }
            else if (myChildNodes.length==0)
            {
                // There is no childnodes, which means an empty value tag
                return "";
            }
            else
            {
                // Check if all its children are text node
                var bIsAllChild = true;
                var ListOfChildSize = new Array();
                var ListOfChildText = new Array();
                for (var intChildCheck=0; intChildCheck < myChildNodes.length; intChildCheck++)
                {
                    if (myChildNodes[intChildCheck].nodeType!=3)
                    {
                        bIsAllChild = false;
                        ListOfChildText = null;
                        break;
                    }
                    else
                    {
                        ListOfChildText.push(myChildNodes[intChildCheck].nodeValue);
                    }
                    
                    if (myChildNodes[intChildCheck].nodeValue.length!=4096)
                    {
                        ListOfChildSize.push("1");
                    }
                }
                if (bIsAllChild)
                {
                    if (ListOfChildSize.length==1)
                    {
                        // This is highly possibly a concatenated text node
                        // or a unparsable element node turned into multiple text node.
                        //var n = XML.XMLNodetoJSON(XML.parse(ListOfChildText.join("")));
                        //cs.debugalert(cs.Debug.inspect(n, {depth:5}).join(""));
                        return ListOfChildText.join("");
                    }
                }
            }
            
            var result = {};
            for (var intNode=0; intNode < myChildNodes.length; intNode++)
            {
                var evalNode = myChildNodes[intNode];

                switch (evalNode.nodeType)
                {
                    case 1: // NODE_ELEMENT
                    case 3: // NODE_TEXT
                        // TODO: Big problem!!! firefox will break the NODE_ELEMENT into 2 NODE_TEXT
                        //if (track!=undefined) cs.debugalert("evalNode.nodeValue:"+evalNode.nodeValue);
                        // filtering the #text node, not sure if this is 100% working
                        if (evalNode.nodeName[0]!="#")
                        {
                            // Need to create an array if the element had already exist
                            if (result[evalNode.nodeName]!=undefined)
                            {
                                if (result[evalNode.nodeName].constructor==Array)
                                {
                                    result[evalNode.nodeName].push(XML.XMLNodetoJSON(evalNode));
                                }
                                else
                                {
                                    var old = result[evalNode.nodeName];
                                    result[evalNode.nodeName] = new Array();
                                    result[evalNode.nodeName].push(old);
                                    result[evalNode.nodeName].push(XML.XMLNodetoJSON(evalNode));
                                }
                            }
                            else
                            {
                                //if (track!=undefined) cs.debugalert("first entry nodeName");
                                result[evalNode.nodeName]=XML.XMLNodetoJSON(evalNode);
                            }
                        }
                        break;
                        
                    case 4: // NODE_CDATA_SECTION
                        result = XML.XMLNodetoJSON(evalNode);                        
                        break;
                        
                    case 2: // NODE_ATTRIBUTE
                        if (result.attribute==null) result.attribute={};
                        result.attribute[evalNode.nodeName]=XML.XMLNodetoJSON(evalNode);
                        break;

                    default:
                        result[evalNode.nodeName]="#not implemented:" + evalNode.nodeType + "#";
                }

            }
            
            return result;
            
            break;
            
        case 2: // NODE_ATTRIBUTE
        case 3: // NODE_TEXT
            
            return xmlNode.nodeValue;
            break;

        case 4: // NODE_CDATA_SECTION
            
            return eval("(" + xmlNode.data + ")");
            break;
            
        case 9: // NODE_DOCUMENT

            var result = {};
            var docEle = xmlNode.documentElement;
            
            var docChildNodes = docEle.childNodes;
            //cs.debugalert("Doc Child:" + docChildNodes.length);
            for (var intChild = 0; intChild < docChildNodes.length; intChild++)
            {
                var childNode = docChildNodes.item(intChild);
                // Simple workaround to eliminate the empty innerText as suspected by CS
                //cs.debugalert(childNode.tagName);
                if (childNode.tagName=="ResponseBody")
                {
                    //cs.debugalert("ResponseBody NodeType:" + childNode.nodeType);
                    //cs.debugalert("ResponseBody:" + childNode.childNodes.length);
                }
                if (childNode.tagName!=undefined)
                    result[childNode.tagName] = XML.XMLNodetoJSON(childNode);
            }
            
            return result;        
            break;

        case 5: // NODE_ENTITY_REFERENCE
        case 6: // NODE_ENTITY
        case 7: // NODE_PROCESSING_INSTRUCTION
        case 8: // NODE_COMMENT
        case 10: // NODE_DOCUMENT_TYPE
        case 11: // NODE_DOCUMENT_FRAGMENT
        case 12: // NODE_NOTATION
        default:
            return "#not implemented:" + xmlNode.nodeType + "#";
            break;
            
    }
    return null;
};

XML.JSONtoXMLDoc = function(jsonData, strDocName)
{
    var xmlDoc = XML.newDocument(strDocName);
    
    for (strName in jsonData)
    {
        if (strName[0]!="#")
        {
            var xnJSON = XML.JSONtoXMLNode(jsonData[strName], strName, xmlDoc);
            xmlDoc.documentElement.appendChild(xnJSON);
        }
    }
    
    return xmlDoc;
};

XML.JSONtoXMLNode = function(jsonData, strNodeName, xmlDoc)
{
    var xnNewNode = null;
    try
    {
        xnNewNode = xmlDoc.createElement(strNodeName);
    }
    catch(e)
    {
        cs.debugalert("Error in createElement:" + strNodeName);
    }
    
    if (jsonData != undefined)
    {
        // Check if the jsonData is object structure
        if (jsonData.constructor==String||jsonData.constructor==Number||jsonData.constructor==Boolean)
        {
            // Edited by CS 20080618
            // Added .toString() for jsonData, as IE cast boolean true=-1 and false=0
            var tnText = xmlDoc.createTextNode(jsonData.toString()); // .replace(/&/g,"&amp;")
            xnNewNode.appendChild(tnText);
        }
        else
        {
            if (jsonData.constructor==Array)
            {
                for (var i=0; i<jsonData.length; i++)
                {
                    var xnJSON = XML.JSONtoXMLNode(jsonData[i], strNodeName, xmlDoc);
                    xnNewNode.appendChild(xnJSON);
                }
            }
            else
            {
                for (strName in jsonData)
                {
                    if (strName[0]!="#")
                    {
                        var xnJSON = XML.JSONtoXMLNode(jsonData[strName], strName, xmlDoc);
                        xnNewNode.appendChild(xnJSON);
                    }
                }
            }
        }
    }
    
    return xnNewNode;
};

XML.selectSingleNode = function(xmlDoc, elementPath)
{
    if(window.ActiveXObject)
    {
        return xmlDoc.selectSingleNode(elementPath);
    }
    else
    {
       var xpe = new XPathEvaluator();
       var nsResolver = xpe.createNSResolver( xmlDoc.ownerDocument == null ? xmlDoc.documentElement : xmlDoc.ownerDocument.documentElement);
       var results = xpe.evaluate(elementPath,xmlDoc,nsResolver,XPathResult.FIRST_ORDERED_NODE_TYPE, null);
       return results.singleNodeValue; 
    }
}

XML.NSResolver = function(strNS){
    switch(strNS)
    {
        case "diffgr":
            return "urn:schemas-microsoft-com:xml-diffgram-v1";
            break;
    }
    return null;
}

XML.selectNodes = function (xmlDoc, sXPath){

    if (window.ActiveXObject)
    {
        return xmlDoc.selectNodes(sXPath);
    }
    else
    {
        var oEvaluator = new XPathEvaluator();
        var oResult = oEvaluator.evaluate(sXPath, xmlDoc, XML.NSResolver, XPathResult.UNORDERED_NODE_ITERATOR_TYPE, null);    

        var aNodes = new Array;

        if (oResult != null) {
            var oElement = oResult.iterateNext();
            while(oElement) {
                aNodes.push(oElement);
                oElement = oResult.iterateNext();
            }
        }

        return aNodes;
    }

}