/** 
* Copyright 2006-2007 massimocorner.com
* License: http://www.massimocorner.com/license.htm
* @author      Massimo Foti (massimo@massimocorner.com)
* @version     0.3.6, 2007-16-04
* @require     tmt_net.js
*/

if(typeof(tmt) == "undefined"){
	var tmt = {};
}

tmt.xml = {};

/**
* Given a DOM node, evaluate an XPath expression against it
* Results are returned as an array of nodes. An empty array is returned in case there is no match
* Multiple namespaces must be separated by spaces like: 
* tmt.xml.evaluateXPath(xmlDOM, xpathPattern, 'xmlns:blue="http://www.bluenote.com/" xmlns:fantasy="http://concordmusicgroup.com/"');
*/
tmt.xml.evaluateXPath = function(domNode, pattern, namespaces){
	if(window.ActiveXObject){
		// Set namespaces, if any
		if(namespaces){
			domNode.setProperty("SelectionNamespaces", namespaces);
		}
		var theNodes = new Array;
		var selectedNodes = domNode.selectNodes(pattern);
		// Extract the nodes out of the nodeList returned by selectNodes and put them into an array
		// We could directly use the nodeList returned by selectNodes, but this would case inconsistencies across browsers
		for(var i=0; i<selectedNodes.length; i++){
			theNodes.push(selectedNodes[i]);
		}
		return theNodes;
	}
	else{
		var evaluator = new XPathEvaluator();
		var nsResolver = null;
		// Create a namespace resolver if required
		if(namespaces){
			// Store namespace info into an array of objects
			var nsStrings = namespaces.split(" ");
			var nsObjects = new Array();
			for(var i=0; i<nsStrings.length; i++){
				// Prefix is anything after xmlns: and before =
				var prefix = nsStrings[i].substring(6, nsStrings[i].indexOf("="));
				// For uri we have to start adding the legth of the prefix, plus xmlns: (6 chars) plus = and the leading quote (total 8 chars)
				// Substring the whole string minus one (the trailing quote)
				var uri = nsStrings[i].substring(8 + prefix.length, nsStrings[i].length -1);
				nsObjects.push({prefix: prefix, uri: uri});
			}
			// Closure acting as resolver
			nsResolver = function(nsPrefix){
				for(var j=0; j<nsObjects.length; j++){
					if(nsObjects[j].prefix == nsPrefix){
						return nsObjects[j].uri;
					}
				}
				return null;
			}
		}
		var result = evaluator.evaluate(pattern, domNode, nsResolver, XPathResult.ORDERED_NODE_ITERATOR_TYPE, null);
		var theNodes = new Array;
		if(result != null){
			var thElement;
			while(thElement = result.iterateNext()){
				theNodes.push(thElement);
			}
		}
		return theNodes;
	}
}

/**
* Serialize a DOM node into a string
*/
tmt.xml.nodeToString = function(domNode){
	if(window.ActiveXObject){
		return domNode.xml;
	}
	else{
		var serializer = new XMLSerializer();
		return serializer.serializeToString(domNode, tmt.xml.MIME_TYPE);
	}
}

/**
* Create a DOM document out of a string
*/
tmt.xml.stringToDOM = function(xmlStr){
	if(window.ActiveXObject){
		var xmlParser = new ActiveXObject(tmt.xml.DOM_ACTIVEX_NAME);
		xmlParser.loadXML(xmlStr);
		return xmlParser;
	}
	else{
		var xmlParser = new DOMParser();
		var domDoc = xmlParser.parseFromString(xmlStr, tmt.xml.MIME_TYPE);
		return domDoc;
	}
}

/**
* Given XSLT and XML (either strings or DOM documents), apply a transformation
* The third parameter is optional, set it to true if you want to return a string instead of a DOM document
*/
tmt.xml.transformDOM = function(xmlInput, xsltInput, params, asString){
	if(window.ActiveXObject){
		var xsltObj = new ActiveXObject(tmt.xml.THREADED_DOM_ACTIVEX);
		// If XSLT isn't a string, convert it
		if(typeof(xsltInput) != "string"){
			xsltInput = tmt.xml.nodeToString(xsltInput);
		}
		xsltObj.loadXML(xsltInput);
		var xsltTemplate = new ActiveXObject(tmt.xml.XSLT_ACTIVEX);
		xsltTemplate.stylesheet = xsltObj;
		var xsltProcessor = xsltTemplate.createProcessor();
		xsltProcessor.input = xmlInput;
		// Add parameters (if any)
		if(params){				
			for(var x in params){
				xsltProcessor.addParameter(x, params[x]);
			}
		}
		xsltProcessor.transform();
		var resultString = xsltProcessor.output;
		if(asString){
			return resultString;
		}
		else{
			return tmt.xml.stringToDOM(resultString);
		}
	}
	else{
		var xsltProcessor = new XSLTProcessor();
		xsltProcessor.importStylesheet(xsltInput);
		// Add parameters (if any)
		if(params){				
			for(var x in params){
				xsltProcessor.setParameter(null, x, params[x]);
			}
		}
		var resultDOM = xsltProcessor.transformToDocument(xmlInput);
		if(asString){
			return tmt.xml.nodeToString(resultDOM);
		}
		else{
			return resultDOM;
		}
	}
}

/**
* Given two strings, perform a trasformation
*/
tmt.xml.transformStrings = function(xmlStr, xsltStr, params){
	if(window.ActiveXObject){
		return tmt.xml.transformDOM(tmt.xml.stringToDOM(xmlStr), xsltStr, params, true);
	}
	else{
		return tmt.xml.transformDOM(tmt.xml.stringToDOM(xmlStr), tmt.xml.stringToDOM(xsltStr), params, true);
	}
}

/**
* Transform the content of two url. Doesn't work in IE if protocol is file:///
* Requires tmt_net.js
*/
tmt.xml.transformUrl = function(xmlUrl, xsltUrl, callback, params, errback){
	// Abort if tmt_net.js is missing
	if(typeof(tmt.net) == "undefined"){
		alert("Error: tmt.net JavaScript library missing");
		return false;
	}
	var xmlDoc = null;
	var xsltDoc = null;
	
	// Inner callback/closure
	var tempCallback = function(){
		if(this.response.contextData.id == "xml"){
			xmlDoc = this.response.responseXML;
		}
		if(this.response.contextData.id == "xslt"){
			xsltDoc = this.response.responseXML;
		}
		// Both docs are downloaded, invoke the callback
		if(xmlDoc && xsltDoc){
			var responseXML = tmt.xml.transformDOM(xmlDoc, xsltDoc, params);
			var responseText = tmt.xml.nodeToString(responseXML);
			var container = new Object();
			container.response = {responseXML: responseXML, responseText: responseText};
			callback.call(container);
		}
	}
	
	tmt.net.httpRequest({url: xmlUrl, loadCallback: tempCallback, id: "xml", errback: errback, contextData: {id: "xml"}});
	tmt.net.httpRequest({url: xsltUrl, loadCallback: tempCallback, id: "xslt", errback: errback, contextData: {id: "xslt"}});
}

/**
* Returns true if the current browser is supprted by the library, false otherwise
*/
tmt.xml.isSupported = function(){
	if(window.XSLTProcessor || window.ActiveXObject){
		return true;
	}
	return false;
}

// Store names for XMLDOM Active-X, from the newer to the older
// removed 5.0 and 4.0 with ref : http://blogs.msdn.com/xmlteam/archive/2006/10/23/using-the-right-version-of-msxml-in-internet-explorer.aspx
tmt.xml.DOM_ACTIVEX_VERSIONS = new Array(
	"MSXML2.DOMDocument.6.0", 
	"MSXML2.DOMDocument.3.0",
	"Microsoft.XmlDom"
);

tmt.xml.DOM_ACTIVEX_NAME = null;
// Find the most recent XMLDOM Active-X available
if(window.ActiveXObject){
	for(var i=0; tmt.xml.DOM_ACTIVEX_VERSIONS.length; i++){
		// IE keeps running the loop if it's unable to find any Active-X. Really weird indeed...
		if(i > tmt.xml.DOM_ACTIVEX_VERSIONS.length){
			throw new Error("Unable to instantiate any suitable XMLDOM component");
		}
		try{
			new ActiveXObject(tmt.xml.DOM_ACTIVEX_VERSIONS[i]);
			tmt.xml.DOM_ACTIVEX_NAME = tmt.xml.DOM_ACTIVEX_VERSIONS[i];
			break;
		}
		catch(err){
		}
	}
}

// Constants
tmt.xml.MIME_TYPE = "text/xml";
tmt.xml.THREADED_DOM_ACTIVEX = "Msxml2.FreeThreadedDOMDocument.3.0";
tmt.xml.XSLT_ACTIVEX = "Msxml2.XSLTemplate.3.0";
