﻿/* 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.
**/
var XMLProcessor = new Object();
XMLProcessor.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
**/
XMLProcessor.load = function(url) {
    // Create a new document with the previously defined function
    var xmldoc = XMLProcessor.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.
*/
XMLProcessor.loadAsync = function(url, callback) {
    var xmldoc = XMLProcessor.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. 
*/
XMLProcessor.parse = function(text) {
    if (typeof DOMParser != "undefined") {
        // Mozilla, Firefox, and related browsers
        return (new DOMParser()).parseFromString(text, "application/xml");
    }
    else if (typeof ActiveXObject != "undefined") {
        // Internet Explorer.
        var doc = XMLProcessor.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;
    }
};


/**
* This XMLProcessor.Transformer class encapsulates an XSL stylesheet.
* If the stylesheet parameter is a URL, we load it.
* Otherwise, we assume it is an appropriate DOM Document.
*/

XMLProcessor.Transformer = function(stylesheet) {
    // Load the stylesheet if necessary.
    if (typeof stylesheet == "string") stylesheet = XMLProcessor.load(stylesheet);
    this.stylesheet = stylesheet;
    // In Mozilla-based browsers, create an XSLTProcessor object and
    // tell it about the stylesheet.
    if (typeof XSLTProcessor != "undefined") {
        this.processor = new XSLTProcessor();
        this.processor.importStylesheet(this.stylesheet);
    }
};
/**
* This is the transform() method of the XMLProcessor.Transformer class.
* It transforms the specified xml node using the encapsulated stylesheet.
* The results of the transformation are assumed to be HTML and are used to
* replace the content of the specified element.
*/
XMLProcessor.Transformer.prototype.transform = function(node, element) {
    // If element is specified by id, look it up.
    if (typeof element == "string") element = document.getElementById(element);
    if (this.processor) {
        // If we've created an XSLTProcessor (i.e., we're in Mozilla) use it.
        // Transform the node into a DOM DocumentFragment.
        var fragment = this.processor.transformToFragment(node, document);
        // Erase the existing content of element.
        element.innerHTML = "";
        // And insert the transformed nodes.
        element.appendChild(fragment);
    }
    else if ("transformNode" in node) {
        // If the node has a transformNode() function (in IE), use that.
        // Note that transformNode() returns a string.
        element.innerHTML = node.transformNode(this.stylesheet);
    }
    else {
        // Otherwise, we're out of luck.
        throw "XSLT is not supported in this browser";
    }
};
/**
* This is an XSLT utility function that is useful when a stylesheet is
* used only once.
*/
XMLProcessor.transform = function(xmldoc, stylesheet, element) {
    var transformer = new XMLProcessor.Transformer(stylesheet);
    transformer.transform(xmldoc, element);
};

/**
* XMLProcessor.XPathExpression is a class that encapsulates an XPath query and its
* associated namespace prefix-to-URL mapping. Once an XMLProcessor.XPathExpression
* object has been created, it can be evaluated one or more times (in one
* or more contexts) using the getNode() or getNodes() methods.
*
* The first argument to this constructor is the text of the XPath expression.
*
* If the expression includes any XML namespaces, the second argument must
* be a JavaScript object that maps namespace prefixes to the URLs that define
* those namespaces. The properties of this object are the prefixes, and
* the values of those properties are the URLs.
*/
XMLProcessor.XPathExpression = function(xpathText, namespaces) {
    this.xpathText = xpathText;    // Save the text of the expression
    this.namespaces = namespaces;  // And the namespace mapping
    if (document.createExpression) {
        // If we're in a W3C-compliant browser, use the W3C API
        // to compile the text of the XPath query
        this.xpathExpr =
            document.createExpression(xpathText,
        // This function is passed a
        // namespace prefix and returns the URL.
                                      function(prefix) {
                                          return namespaces[prefix];
                                      });
    }
    else {
        // Otherwise, we assume for now that we're in IE and convert the
        // namespaces object into the textual form that IE requires.
        this.namespaceString = "";
        if (namespaces != null) {
            for (var prefix in namespaces) {
                // Add a space if there is already something there
                if (this.namespaceString) this.namespaceString += ' ';
                // And add the namespace
                this.namespaceString += 'xmlns:' + prefix + '="' +
                    namespaces[prefix] + '"';
            }
        }
    }
};
/**
* This is the getNodes() method of XMLProcessor.XPathExpression. It evaluates the
* XPath expression in the specified context. The context argument should
* be a Document or Element object. The return value is an array
* or array-like object containing the nodes that match the expression.
*/
XMLProcessor.XPathExpression.prototype.getNodes = function(context) {
    if (this.xpathExpr) {
        // If we are in a W3C-compliant browser, we compiled the
        // expression in the constructor. We now evaluate that compiled
        // expression in the specified context.
        var result =
            this.xpathExpr.evaluate(context,
        // This is the result type we want
                                    XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,
                                    null);
        // Copy the results we get into an array.
        var a = new Array(result.snapshotLength);
        for (var i = 0; i < result.snapshotLength; i++) {
            a[i] = result.snapshotItem(i);
        }
        return a;
    }
    else {
        // If we are not in a W3C-compliant browser, attempt to evaluate
        // the expression using the IE API.
        try {
            // We need the Document object to specify namespaces
            var doc = context.ownerDocument;
            // If the context doesn't have ownerDocument, it is the Document
            if (doc == null) doc = context;
            // This is IE-specific magic to specify prefix-to-URL mapping
            doc.setProperty("SelectionLanguage", "XPath");
            doc.setProperty("SelectionNamespaces", this.namespaceString);
            // In IE, the context must be an Element not a Document,
            // so if context is a document, use documentElement instead
            if (context == doc) context = doc.documentElement;
            // Now use the IE method selectNodes() to evaluate the expression
            return context.selectNodes(this.xpathText);
        }
        catch (e) {
            // If the IE API doesn't work, we just give up
            throw "XPath not supported by this browser.";
        }
    }
}
/**
* This is the getNode() method of XMLProcessor.XPathExpression. It evaluates the
* XPath expression in the specified context and returns a single matching
* node (or null if no node matches). If more than one node matches,
* this method returns the first one in the document.
* The implementation differs from getNodes() only in the return type.
*/
XMLProcessor.XPathExpression.prototype.getNode = function(context) {
    if (this.xpathExpr) {
        var result =
            this.xpathExpr.evaluate(context,
        // We just want the first match
                                    XPathResult.FIRST_ORDERED_NODE_TYPE,
                                    null);
        return result.singleNodeValue;
    }
    else {
        try {
            var doc = context.ownerDocument;
            if (doc == null) doc = context;
            doc.setProperty("SelectionLanguage", "XPath");
            doc.setProperty("SelectionNamespaces", this.namespaceString);
            if (context == doc) context = doc.documentElement;
            // In IE call selectSingleNode instead of selectNodes
            return context.selectSingleNode(this.xpathText);
        }
        catch (e) {
            throw "XPath not supported by this browser.";
        }
    }
};
// A utility to create an XMLProcessor.XPathExpression and call getNodes() on it
XMLProcessor.getNodes = function(context, xpathExpr, namespaces) {
    return (new XMLProcessor.XPathExpression(xpathExpr, namespaces)).getNodes(context);
};
// A utility to create an XMLProcessor.XPathExpression and call getNode() on it
XMLProcessor.getNode = function(context, xpathExpr, namespaces) {
    return (new XMLProcessor.XPathExpression(xpathExpr, namespaces)).getNode(context);
};

/**
* Serialize an XML Document or Element and return it as a string.
*/
XMLProcessor.serialize = function(node) {
    if (typeof XMLSerializer != "undefined")
        return (new XMLSerializer()).serializeToString(node);
    else if (node.xml) return node.xml;
    else throw "XMLProcessor.serialize is not supported or can't serialize " + node;
};

/**************************************
***************************************/
XMLProcessor.traverseXML = function(xml, nodeProcessor) {
    //var xml = parseXMLString(xmlStr);
    if (xml != null) {
        var nodes = xml.childNodes;
        for (var i = 0; i < nodes.length; i++) {
            var node = nodes[i];
            if (node.tagName) {
                nodeProcessor(node, 1);
            }
            if (node.hasChildNodes()) {
                XMLProcessor.processChildNodes(node.childNodes, nodeProcessor, 1);
            }
        }
    }
    return true;
}
XMLProcessor.processChildNodes = function(nodes, nodeProcessor, depth) {
    for (var i = 0; i < nodes.length; i++) {
        if (nodes[i].tagName) {
            nodeProcessor(nodes[i], depth + 1);
        }
        if (nodes[i].hasChildNodes()) {
            XMLProcessor.processChildNodes(nodes[i].childNodes, nodeProcessor, depth + 1);
        }
    }
}



