// Global NS variables
var AIURI='http://www.asoft.ru/xsl/ai';
var AINS={'ai': AIURI};
var STURI='http://www.asoft.ru/xml/st';
var STNS={'st': STURI};
/**
  * Cross-browser XML DOM parser
  * Requires: ajax-request.js
  **/

var AXML = Class.create({
/**
  * Creates XML document (empty by default)
  * @param sRoot if present, root element with such name will be created automatically
  **/
initialize: function(sRoot, sNS) {
	this.bUseDOM_ = (document.implementation && document.implementation.createDocument);
	this.bUseActiveX_ = (typeof ActiveXObject != "undefined");
	if (!sRoot) sRoot = "";
	if (!sNS) sNS = "";
	var sText = "";
	if (sRoot) {
		// Look for a namespace prefix
		var sPrefix = "";
		var sTagName = sRoot;
		var p = sRoot.indexOf(':');
		if (p != -1) {
			sPrefix = sRoot.substring(0, p);
			sTagName = sRoot.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 (sNS) {
			if (!sPrefix) sPrefix = "a0"; // What Firefox uses
		}
		else sPrefix = "";
		// Create the root element (with optional namespace) as a
		// string of text
		sText = '<?xml version="1.0" encoding="utf-8"?><' + (sPrefix?(sPrefix+':'):'') + sTagName +
			(sNS ? (' xmlns:'+sPrefix+'="'+sNS +'"'):'')+'/>';
		// And parse that text into the empty document
	}
	if (this.bUseDOM_) {
		// Mozilla way to do things
		if (sText) this.Parse(sText);
		else this.oXML_ = document.implementation.createDocument(sNS, sRoot, null);
	} else if (this.bUseActiveX_) {
		// IE way
		if (!AXML.DOMVersion) this.detectDOMVersion();
		this.oXML_ = new ActiveXObject(AXML.prototype.DOMVersion);
		if (sText) this.Parse(sText);
	}
},
detectDOMVersion: function() {
	this.oXML_ = null;
	var aDOMVersions = ["MSXML2.DOMDocument.3.0", "MSXML2.DOMDocument.6.0"];
	for (var i = 0; i < aDOMVersions.length; i++) {
		try {
			var sDOMVersion = aDOMVersions[i];
			this.oXML_ = new ActiveXObject(sDOMVersion);
			if (this.oXML_) {
				// cache DOM object version
				AXML.prototype.DOMVersion = sDOMVersion;
				return;
			}
		} catch(e) { }
	}
	throw new Error("AXML::detectDOMVersion(): Cannot acquire DOM object! ");
	this.oXML_ = null;
},
/**
  * Loads XML document located by passed URL
  * @param sURL URL containing XML content
  * @param fCallback callback function called when loading is done
  * @param oThis object that will be bound as this to fCallback
  **/
Load: function(sURL, fCallback, oThis) {
	// TODO: if possible, use Mozilla implementation of .prototype.load
	if (!oThis) oThis = null;
	var bAsync = true;
	if (!fCallback) bAsync = false;
	else this.fLoadingCallback_ = fCallback.bind(oThis);
	new AjaxRequest("get", sURL, "", { onSuccess: this.loadingCallback.bind(this) }, bAsync);

},
/**
  * Internal callback that is called when XML is loaded
  * @param oAjaxRequest Ajax request handler carrying data
  **/
loadingCallback: function(oAjaxRequest) {
	// Callback is called with AXML object in single argument
	this.oXML_ = oAjaxRequest.XML(); // get response XML
	if (oAjaxRequest.IsAsync()) this.fLoadingCallback_(this);
},
/**
  * Parses text content
  * @param sContent string containing XML text
  * @return false if parsing failed, true otherwise
  **/
Parse: function(sContent) {
	if (!sContent) sContent = "";
	if (window.DOMParser) {
		// This browser appears to support DOMParser
		var oParser = new DOMParser();
		this.oXML_ = oParser.parseFromString(sContent, "text/xml");
	} else if (this.oXML_ && typeof(this.oXML_.loadXML) != "undefined") {
		this.oXML_.loadXML(sContent);
	} else {
		throw new Error("XML document not initialized or parsing not supported! ");
	}
},
/**
  * Sets XML document to given object
  * @param oXMLDom DOM document (please note that no copying is performed!)
  **/
SetXML: function(oXMLDom) {
	this.oXML_ = oXMLDom;
},
/**
  * Serializes XML document to string
  * @return serialized XML content (string)
  **/
Serialize: function() {
	return this.oXML_.xml;
},
/**
  * Returns reference to internal XML document
  * @return XML document (formally, browser-dependent, but differences are not necessary)
  **/
XML: function() {
	return this.oXML_;
},
/**
  * Returns XML transformed by XSLT template (as string)
  * @return transformed content (text)
  **/
Transform: function(oXSLTDoc) {
	return AXML.TransformDOM(this.oXML_, oXSLTDoc.XML());
}

});

AXML.typeDocs = {};
AXML.typeSets = {};
AXML.getTypeXsl = function() {
	if (AXML.typeXsl) return AXML.typeXsl;

	var t = new AXSLTemplate(document.location.pathname.replace(/[^\/]+$/, "") + "/xslt/types.xsl");
	t.Compile();
	return AXML.typeXsl = t;
};
AXML.getTypeDoc = function(nm) {
	if (AXML.typeDocs[nm]) return AXML.typeDocs[nm];

	var doc1 = new AXML();
	doc1.Load(document.location.pathname.replace(/[^\/]+$/, "")+"/types/"+nm+".xml");
	return AXML.typeDocs[nm] = doc1;
};
AXML.getTypeSet = function(typeList) {
	typeList = typeList.sort();
	var hashKey = typeList.join(":");

	if (AXML.typeSets[hashKey]) return AXML.typeSets[hashKey];
	var tset = new AXML("st:typeset", STURI);
	var r = tset.oXML_.documentElement;

	for (var i = 0; i < typeList.length; ++i) {
		var tdoc = AXML.getTypeDoc(typeList[i]);
		if (!tdoc || !tdoc.oXML_ || !tdoc.oXML_.documentElement) {
			LogE("Cannot load types from " + typeList[i]);
			continue;
		}
		var types = AXML.SelectNodes(tdoc.oXML_.documentElement, "//*[local-name(.) = 'types']");

		for (var t = 0; t < types.length; ++t) {
			var c = ImportNode(tset, types[t], true);
			c.setAttribute("from", typeList[i]);
			r.appendChild(c);
		}
	}	
	var xsl = AXML.getTypeXsl();
	var xml = AXML.TransformDOM(tset.oXML_, xsl);
	var ret = new AXML();
	ret.Parse(xml)

	return AXML.typeSets[hashKey] = ret;
};

AXML.EmbedPDLTypes = function(oXMLDom) {
	var rootElement = (oXMLDom.documentElement || oXMLDom);
	oXMLDom = (rootElement.ownerDocument || oXMLDom);
	if (rootElement.nodeName == "st:typeset") return;
	var types = [];
	for (var i = 0; i < rootElement.childNodes.length; ++i) {
		var n = rootElement.childNodes[i];
		if ( (n.baseName || n.localName) != "types" || n.namespaceURI != AIURI) continue;
		var nm = n.getAttribute('from');
		types.push(nm);
		rootElement.removeChild(n);
		i--;	
	}
	if (!types.length) return;

	var tset = AXML.getTypeSet(types);
	if (!tset || !tset.oXML_.documentElement) return;

	var tnodes = AXML.SelectNodes(tset.oXML_.documentElement, "/*/*[local-name(.) = 'types']");
	for (var i = 0; i < tnodes.length; ++i) {
		rootElement.appendChild( ImportNode(oXMLDom, tnodes[i], true) );
	}
}

// TODO: Recognition of AXML objects in arguments (instead of DOM trees)?
AXML.TransformDOM = function(oXMLDom, xslTemplate) {
	AXML.EmbedPDLTypes(oXMLDom);
	if (typeof XSLTProcessor != "undefined") {
		try {

			var oResultDOM = xslTemplate.processor.transformToDocument(oXMLDom);
			Element.wrap(oResultDOM.documentElement, 'div');
			var ret = oResultDOM.documentElement.innerHTML;
			return ret;
		} catch (exc) {
			ThrowError("AXML::TransformDOM: XSLT transformation failed: " + exc);
		}
	} else if (typeof ActiveXObject != "undefined") {
		var p = xslTemplate.processor.createProcessor();
		p.input = oXMLDom;
		p.transform();

		return p.output;
	} else {
		throw new Error("AXML::TransformDOM: No XSLT engine found. ");
	}
}

AXML.TransformDOMTree = function(oXMLDom, xslTemplate) {
	AXML.EmbedPDLTypes(oXMLDom);
	if (typeof XSLTProcessor != "undefined") {
		try {
			//LogE("Transform started");
			//LogL("XML="+oXMLDom.xml);LogL("----------------------------------------------");
			var oResultDOM = xslTemplate.processor.transformToDocument(oXMLDom);
			//LogL("Transform done");
			return oResultDOM;
		} catch (exc) {
			ThrowError("AXML::TransformDOM: XSLT transformation failed: " + exc);
		}
	} else if (typeof ActiveXObject != "undefined") {
		//LogE("Transform started");
		var p = xslTemplate.processor.createProcessor();
		p.input = oXMLDom;
		p.transform();

		var r = p.output;
		//LogL("Transform done");
		return r;
		// works only with <xsl:output method="xml"> (fails on some tables)
		//var oResultDOM = new ActiveXObject(AXML.prototype.DOMVersion);
		//oResultDOM.validateOnParse = false;
		//oXMLDom.transformNodeToObject(xslTemplate, oResultDOM);
		//if (oResultDOM.parseError && oResultDOM.parseError.errorCode) alert(oResultDOM.parseError.reason);
		//return oResultDOM;
	} else {
		throw new Error("AXML::TransformDOM: No XSLT engine found. ");
	}
}


// Raw code
if (document.implementation && document.implementation.createDocument) {
	Node.prototype.__defineGetter__("xml", function () {
		var oSerializer = new XMLSerializer();
		return oSerializer.serializeToString(this, "text/xml");
	});

	Node.prototype.__defineGetter__("text", function () {
		var sText = "";
		for (var i = 0; i < this.childNodes.length; i++) {
			if (this.childNodes[i].hasChildNodes()) {
				sText += this.childNodes[i].text;
			} else {
				sText += this.childNodes[i].nodeValue;
			}
		}
		return sText;
	});
}


/**
  * Selects the first node matching a given XPath expression.
  * @param oRefNode The node from which to evaluate the expression.
  * @param sXPath The XPath expression.
  * @param oNS An object containing the namespaces used in the expression.
  * @return An XML node matching the expression or null if no matches found.
  **/
AXML.SelectNodes = function(oRefNode, sXPath, oNS) {
	if (typeof XPathEvaluator != "undefined") {
		var oEvaluator = new XPathEvaluator();
		var nsResolver = oRefNode.ownerDocument.createNSResolver(oRefNode.ownerDocument.documentElement);
		var oResult = oEvaluator.evaluate(sXPath, oRefNode, nsResolver, XPathResult.ORDERED_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;
	} else if (typeof ActiveXObject != "undefined") {
		AXML.registerNS(oRefNode, oNS);
		oRefNode.ownerDocument.setProperty("SelectionLanguage", "XPath")
		return oRefNode.selectNodes(sXPath);
	} else {
		throw new Error("No XPath engine found. ");
	}
};

/**
  * Selects the first node matching a given XPath expression.
  * @param oRefNode The node from which to evaluate the expression.
  * @param sXPath The XPath expression.
  * @param oNS An object containing the namespaces used in the expression.
  * @return An XML node matching the expression or null if no matches found.
  **/
AXML.SelectNode = function (oRefNode, sXPath, oNS) {
	if (typeof XPathEvaluator != "undefined") {
		var oEvaluator = new XPathEvaluator();
		var nsResolver = oRefNode.ownerDocument.createNSResolver(oRefNode.ownerDocument.documentElement);
		var oResult = oEvaluator.evaluate(sXPath, oRefNode, nsResolver, XPathResult.FIRST_ORDERED_NODE_TYPE, null);
		if (oResult != null) {
			return oResult.singleNodeValue;
		} else {
			return null;
		}
	} else if (typeof ActiveXObject != "undefined") {
		AXML.registerNS(oRefNode, oNS);
		try{oRefNode.ownerDocument.setProperty("SelectionLanguage", "XPath")}catch(err){}
		return oRefNode.selectSingleNode(sXPath);
	} else {
		throw new Error("No XPath engine found.")
	}
};

AXML.registerNS = function(oRefNode, oNS) {
	if (oNS) {
		var sNS = "";
		for (var sProp in oNS) {
			sNS += "xmlns:" + sProp + "=\'" + oNS[sProp] + "\' ";
		}
		oRefNode.ownerDocument.setProperty("SelectionNamespaces", sNS);
	}
}

/**
  * Escapes XML entities in string
  * @param s input string
  * @return escaped string
  **/
AXML.EscapeEntities = function(s) {
	// TODO: rewrite to str_replace (or so on...)
	var r = "";
	for (var i = 0; i < s.length; i++) {
		var sym = s.charAt(i);
		if (sym == '&') r += "&amp;";
		else if (sym == '<') r += "&lt;";
		else if (sym == '>') r += "&gt;";
		else if (sym == "'") r += "&#39;"; // &apos; does not work in IE!
		else if (sym == "\"") r += "&quot;";
		else r = r + sym;
	}
	return r;
}

var AXSLTemplate = Class.create({
	MsVersions: [".3.0", ".6.0"],
	initialize: function(url) {
		if (window.XSLTProcessor) {
			var d = new AXML();
			d.Load(url);
			this.doc = d.oXML_;
			
			this.processor = new XSLTProcessor();
		}
		else {
			for (var i = 0; i < this.MsVersions.length; i++) try {
				this.msVersion = this.MsVersions[i];
				if (this.processor = new ActiveXObject("Msxml2.XslTemplate" + this.msVersion)) break;
			} catch(e) { } 

			if (!this.processor) {
				throw new Error("AXML::detectDOMVersion(): Cannot acquire XSL template object! ");
			}

			this.doc = new ActiveXObject('MSXML2.FreeThreadedDOMDocument' + this.msVersion);
			this.doc.async = false;
			this.doc.load(url);

			var txt = this.doc.documentElement.xml;
			txt = txt.replace(/xmlns:exsl="[^"]*"/, 'xmlns:exsl="urn:schemas-microsoft-com:xslt"');
			this.doc.loadXML(txt);
		}
	},
	FixXSLInclude: function(baseUrl, additionalXsl) {
		var includes = (additionalXsl.slice() || []);
		for (var i = 0; i < includes.length; ++i) {
			includes[i] = baseUrl + "../../" + includes[i];
		}
		var nodes = this.doc.documentElement.childNodes;
		for (var i = 0; i < nodes.length; ++i) {
			var n = nodes[i];
			if (n.nodeName != "xsl:include") continue;

			includes.push(baseUrl + n.getAttribute('href'));
			this.doc.documentElement.removeChild(n);
			i--;
		}

		for (var i = 0; i < includes.length; ++i) {
			var nm = includes[i];

			var re = /[^\/]+\/\.\.\//;
			while (nm.search(re) != -1) {
				nm = nm.replace(re, '');
			}

			var d;
			if (window.XSLTProcessor) {
				var doc = new AXML();
				doc.Load(nm);
				d = doc.oXML_.documentElement;
			}
			else {
				var doc = new ActiveXObject('MSXML2.FreeThreadedDOMDocument' + this.msVersion);
				doc.async = false;
				doc.load(nm);
				d = doc.documentElement;
			}
		
			for (var j = 0; j < d.childNodes.length; ++j) {
				c = d.childNodes[j];
				var cnode=ImportNode(this.doc, c, true);
				this.doc.documentElement.appendChild(cnode);
			}
		}
	},
	Compile: function() {
		this.compiled = true;
		if (window.XSLTProcessor) {
			this.processor.importStylesheet(this.doc);
		}
		else {
			this.processor.stylesheet = this.doc;
		}

	}

});

/**
  * Browser independent document.importNode implementation that supports import from another document.
  * Please note that usage of document.cloneNode is deprecated for that purpose and we must use
  * document.importNode instead.
  **/
/*
if (!document.ELEMENT_NODE) {
	document.ELEMENT_NODE = 1;
	document.ATTRIBUTE_NODE = 2;
	document.TEXT_NODE = 3;
	document.CDATA_SECTION_NODE = 4;
	document.ENTITY_REFERENCE_NODE = 5;
	document.ENTITY_NODE = 6;
	document.PROCESSING_INSTRUCTION_NODE = 7;
	document.COMMENT_NODE = 8;
	document.DOCUMENT_NODE = 9;
	document.DOCUMENT_TYPE_NODE = 10;
	document.DOCUMENT_FRAGMENT_NODE = 11;
	document.NOTATION_NODE = 12;
}

document.ImportNode = function(node, bRecursive) {
	// find the node type to import
	switch (node.nodeType) {
		case document.ELEMENT_NODE:
			// create a new element
			var newNode = document.createElement(node.nodeName);
			// does the node have any attributes to add?
			if (node.attributes && node.attributes.length > 0) {
				// add all of the attributes
				for (var i = 0, il = node.attributes.length; i < il;) {
					newNode.setAttribute(node.attributes[i].nodeName, node.getAttribute(node.attributes[i++].nodeName));
				}
			}
			// are we going after children too, and does the node have any?
			if (bRecursive && node.childNodes && node.childNodes.length > 0) {
				// recursively get all of the child nodes
				for (var i = 0, il = node.childNodes.length; i < il;) {
					newNode.appendChild(document.ImportNode(node.childNodes[i++], bRecursive));
				}
			}
			return newNode;
			break;
		case document.TEXT_NODE:
		case document.CDATA_SECTION_NODE:
		case document.COMMENT_NODE:
			return document.createTextNode(node.nodeValue);
		break;
	}
}
*/
function ImportNode(doc, node, bRecursive) {
	if (!doc.importNode) return node.cloneNode(true);
	return doc.importNode(node, bRecursive);
}

