// Freja - a javascript Model-View-Controller Framework geared toward Zero-Latency Web Applications
// v1.01.11 Beta - Jan. 2006
// Copyright (c) 2006 Cédric Savarese <pro@4213miles.com>
// This software is licensed under the CC-GNU LGPL <http://creativecommons.org/licenses/LGPL/2.1/>
// Documentation : http://www.csscripting.com/freja

var _MVC_Cache = {
	// holds reference to the Models and Views
	models      : new Array(),
	views       : new Array(),
	handlers    : new Array(),
	undoHistory : new Array()
}

function Model (url) {
	// Check cache first
	for(var i=0;i<_MVC_Cache.models.length;i++) {
		if(_MVC_Cache.models[i].url == url) 
			break;		
	}
	_MVC_Cache.models[i]={url:url};
	this.idx  = i;
	this.type = 'model';

	this.get  = function(property) {
		return _MVC_Cache.models[this.idx][property];
	}
	this.set  = function(property, value) {
		return _MVC_Cache.models[this.idx][property] = value;
	}
	this._getElementById = function(id) {
		var doc = _MVC_Cache.models[this.idx]['document'];
		if(doc) {
			// getElementById don't work on XML document without xml:id
			var allElements = doc.getElementsByTagName('*');
			for(var i= 0; i< allElements.length; i++) {
				if(allElements[i].getAttribute('id') == id) 
					return allElements[i];
			}
		}
		return null;
	}
}

function View (url) {
	var options          = arguments[1] || {};
	var placeholder      = options.placeholder || null;
	var onRenderComplete = options.onRenderComplete || null;
		
	_MVC_Cache.views.push({url: url, placeholder: placeholder, onRenderComplete: onRenderComplete});
	this.idx  = _MVC_Cache.views.length-1; 
	this.type = 'view';

	this.get  = function(property) {
		return _MVC_Cache.views[this.idx][property];
	}
	this.set  = function(property, value) {
		return _MVC_Cache.views[this.idx][property] = value;
	}	
}

var Controller = {
	
	// ---------------------------------------------------------------------------------
	// ASSET LOADING 
	// ---------------------------------------------------------------------------------

	pageLoadComplete   : false,
	assetsLoadComplete : false,
	
	onPageLoadComplete : function() { },
	onLoadComplete     : function() { },
	onLoadError        : function(url, status, responseText) { alert("Error "+status+" while loading " + url ); },

	loadAssets : function() {
		
		Controller.debug('loading assets',1);
		
		var forceReload = arguments[0] ? arguments[0] : false;
		var allInCache  = true;

		for(var i=0;i<_MVC_Cache.views.length;i++) {
			var inCache = Controller._loadAsset(_MVC_Cache.views[i], Controller._assetCallback, forceReload);
			allInCache = allInCache && inCache;
		}
		
		for(var i=0;i<_MVC_Cache.models.length;i++) {
			var inCache = Controller._loadAsset(_MVC_Cache.models[i], Controller._assetCallback, forceReload);
			allInCache = allInCache && inCache;
		}
		if(allInCache) {
			Controller.assetsLoadComplete = true;
			Controller._assetCallback();
		}
	},
	
	reloadAsset : function(asset) {
		var callback    = arguments[1] ? arguments[1] : Controller.onLoadComplete;
		var forceReload = arguments[2] ? arguments[2] : true;
		switch(asset.type) {
			case 'view':
				Controller._loadAsset(_MVC_Cache.views[asset.idx], callback, forceReload);
				break;
			case 'model':
				Controller._loadAsset(_MVC_Cache.models[asset.idx], callback, forceReload);
				break;
		}
	},
	
	_loadAsset : function(cacheObject, callback) {
		
		// Do not reload an asset unless otherwise specified.
		var forceReload = arguments[2] ? arguments[2] : false;
		if(cacheObject.document && !forceReload) 
			return true;

		// Load asset's document
		var url = cacheObject.url;		
		cacheObject.xhr = new XMLHttpRequest();
		var xhr = cacheObject.xhr;
		xhr.onreadystatechange = function() { 
			if(xhr.readyState == 4) {
				Controller.debug('retrieving url: '+ url);
				switch (xhr.status) {
					case undefined: // safari bug. 
						Controller.debug('status undefined url: '+ url);
						Controller.onLoadError(url,xhr.status, xhr.responseText);
						break;
					case 200: //fall through					
					case 201: //fall through
					case 304:
						if(xhr.responseXML) {
							cacheObject.document = xhr.responseXML;
							callback();
						} else {
							Controller.onLoadError(url,xhr.status,'Not a valid XML document or wrong MIME type. Data was: '+xhr.responseText);
						}
						break;
					case 404:
						Controller.onLoadError(url,xhr.status, xhr.responseText);
						break;
					default:
						Controller.onLoadError(url,xhr.status, xhr.responseText);
						break;
				}
			}
		}		
		xhr.open("GET", url, true);
		//xhr.setRequestHeader('If-Modified-Since', 'Wed, 15 Nov 1995 00:00:00 GMT');	
		xhr.send(null);
		return false;
	},	
	
	_assetCallback : function() {		
		for(var i=0;i<_MVC_Cache.models.length;i++) {
			if(!_MVC_Cache.models[i].xhr || _MVC_Cache.models[i].xhr.readyState != 4 || (_MVC_Cache.models[i].xhr.status != 200 && _MVC_Cache.models[i].xhr.status != 304)) {
				return false;
			}
		}
		for(var i=0;i<_MVC_Cache.views.length;i++) {
			if(!_MVC_Cache.views[i].xhr || _MVC_Cache.views[i].xhr.readyState != 4 ||  (_MVC_Cache.views[i].xhr.status != 200 && _MVC_Cache.views[i].xhr.status != 304)) {
				return false;
			}
		}	
		Controller.debug('All assets are loaded',1);
		Controller.assetsLoadComplete = true;
		
		if(!Controller.pageLoadComplete) {
			Controller.debug('.. but page is not fully loaded',1);
			Controller.onPageLoadComplete = Controller.onLoadComplete;
			return false;
		}
		// All loaded.
		Controller.debug('Load Complete',1);
		if(Controller.onLoadComplete)
			Controller.onLoadComplete();
	},
	
	
	// ---------------------------------------------------------------------------------
	// VIEW RENDERING
	// ---------------------------------------------------------------------------------

	throbberHtml   : "<p style='text-align: center'>Loading, please wait...</p>",
	xsltServiceUrl : "srvc-xslt.php",
	
	render : function(model, view) {
		
		Controller.debug('Render model: ' + model.get('url')+' view:' + view.get('url') ,1); 
		
		var xmlDoc        = model.get('document');
		var xslDoc        = view.get('document');
		var options       = arguments[2]             || {};
		var placeholder   = options.placeholder      || view.get('placeholder');
		var callback      = options.onRenderComplete || view.get('onRenderComplete') || null;
		var xslParams     = options.xslParams        || null;

		if(options.placeholder)
		   view.set('placeholder', options.placeholder);
		   
		if(typeof placeholder == 'string') 
			placeholder = document.getElementById(placeholder);
			
		if(!placeholder) {
			if(Controller.pageLoadComplete == false) {
				Controller.debug("Error - Page not fully loaded. Couldn't find the HTML element with the id "+ placeholder.id);
				// Try again when the page is fully loaded.
			   	Controller.onPageLoadComplete = function() { Controller.render(model, view, options); };
			} else {
				alert("Error: couldn't find the HTML element "+ placeholder );
			}
			return false;
		}
		if(!xmlDoc || !xslDoc) {
			if(!Controller.assetsLoadComplete) {
				// Try again when the assets are fully loaded.
				Controller.debug('oops, all assets are not fully loaded');
				
				var _onLoadComplete = Controller.onLoadComplete;
			   	Controller.onLoadComplete = function() { Controller.debug('Trying again to render view'); _onLoadComplete(); Controller.render(model, view, options); };
			} else {
				Controller.debug("Error: Model or View not properly loaded.\nModel was: "+ model.get('url') + ' doc='+xmlDoc+ ' \nView was: ' + view.get('url') + ' doc='+xslDoc );
			}
			return false;
		}

		// Transform client-side if possible
		if(typeof XSLTProcessor != 'undefined') {
			var processor = new XSLTProcessor();
			processor.importStylesheet(xslDoc);
			if(xslParams) {
				for(var i=0;i<xslParams.length;i++) {
					Controller.debug('Processor parameter:'+ xslParams[i][0] + '='+ xslParams[i][1],1);
					processor.setParameter(null, xslParams[i][0],xslParams[i][1]); 
				}
			}
			try {
				var result = processor.transformToDocument(xmlDoc);			
			} catch(e) {
				alert(e);
				placeholder.innerHTML = e;
			}
			if(result) {
				var serialized = Controller.serialize(result);
				if(!serialized) 
					serialized = "<p class='errMsg'>XSL Transformation error.</p>";
				else  // fix empty textareas
					serialized = serialized.replace(/<textarea([^\/>]*)\/>/gi,"<textarea $1></textarea>");
		
				placeholder.innerHTML = serialized;
				
				// onRenderComplete Event.
				if(callback) 
					callback();
			}
		} 
		// Transform server-side otherwise
		else {
			// show loading throbber
			var height            = placeholder.offsetHeight;
			var topmargin         = (height - 50)/2;
			height 		          = height - topmargin;
			var width             = placeholder.offsetWidth;
			placeholder.innerHTML = "<div style=\"margin-top: "+topmargin+"px; height: "+height+"px; width="+width+"px;\">" + Controller.throbberHtml + "</div>";
			// prepare posted data  (no need to send the XSL document, just its url)
			var xslUrl            = view.get('url'); 
			var postedData        = "xslFile="+encodeURIComponent(xslUrl)+"&xmlData="+encodeURIComponent(Controller.serialize(xmlDoc));
			if(xslParams) 
				postedData  = postedData + "&xslParam=" + encodeURIComponent(xslParams.toString());
			// send request to the server-side XSL transformation service
			var xhr = new XMLHttpRequest();
			xhr.onreadystatechange = function() { 
				if(xhr.readyState == 4) {
					if(xhr.status==200) {
						placeholder.innerHTML = xhr.responseText;
						// onRenderComplete Event.
						if(callback) 
							callback();
					} else {
						placeholder.innerHTML = xhr.responseText;
					}
				}
			} 			
			xhr.open("POST", Controller.xsltServiceUrl, true);
			xhr.setRequestHeader('Content-Type','application/x-www-form-urlencoded');
			xhr.send(postedData);		
			return false;
		}		
	},
	
	
	// ---------------------------------------------------------------------------------
	// MODEL UPDATING
	// ---------------------------------------------------------------------------------
	
	update : function(model, view) {
		var placeholder = view.get('placeholder');
		if(typeof placeholder == 'string')
			placeholder = document.getElementById(placeholder);
		var forms       = placeholder.getElementsByTagName('form');
		var modelDoc    = model.get('document');
		
		for (var i=0; i < forms.length; i++) {
			var contextNode = null;
			if(forms[i].id) 
				contextNode = model._getElementById(forms[i].id);
			if(!contextNode) 
				contextNode = arguments[2] ? arguments[2] : modelDoc;
			
			for (var j=0; j<forms[i].elements.length; j++) {
				var field = forms[i].elements[j];
				var value = null;
				Controller.debug("updating ... "+field.tagName.toLowerCase()+(field.type?':'+field.type : ''));
				switch(field.tagName.toLowerCase()+(field.type?':'+field.type : '')) {
					case 'select:select-one':
					case 'select':
						value = field.options[field.selectedIndex].value;
						break;		
					case 'textarea:textarea':
					case 'textarea':
						// Fall through
					case 'input:hidden':
						// Fall through
					case 'input:password':
						// Fall through
					case 'input:text':
						value = field.value;					
						break;
					case 'input:radio':
						// Fall through
					case 'input:checkbox':
						if(field.checked)
							value = field.value;		
						else
							value = "";
						break;
					default:
						// ignore others
						continue;
				}

				if(field.name) {
					Controller.debug('Update Model - field name: ' + field.name + ' context node: ' + contextNode.nodeName );
					// find the corresponding element in the model's xml document
					var xmlTagNames = field.name.split('.');
					var n           = contextNode;
					if(contextNode.nodeType==9) // Document node
						var k=0;
					else 
						var k=1;  // The convention requires that the first tagname matches the context node, so we go down from there
					for(; k<xmlTagNames.length; k++) {
						var nodes = n.getElementsByTagName(xmlTagNames[k].split(':')[0]);
						if(nodes) {
							n = nodes[0];
						} else  {
							n = null;
							break;
						}
					}
					// did we find a valid node to update ?
					if(n) {						
						// check if we're updating an attribute or an element
						var lastTag = xmlTagNames[xmlTagNames.length-1].split(':');
						
						if(lastTag[1]) {
							// Update attribute
							n.setAttribute(lastTag[1], value);
						} else {
							// Update element
							// remove existing text nodes
							
							for(var k=n.childNodes.length-1; k>=0; k--) {
								if(n.childNodes[k].nodeType==3 || n.childNodes[k].nodeType==4) {
									n.removeChild(n.childNodes[k]);
								}
							}
							// add value as a CDATA text node
							var txtNode = modelDoc.createCDATASection(value);
							n.appendChild(txtNode);
						}
					}
				}
			}
		}
	},
	
	save : function(model) {
		Controller.debug('saving model: '+ model.get('url'));
		var options        = arguments[1]           || {};
		var callback       = options.onSaveComplete || null;
		var errorHandler   = options.onError        || null;
		var url            = options.url            || model.get('url');
		var dataFieldName  = options.dataFieldName  || "data";
		var modelFieldName = options.modelFieldName || "model";
		var postedData     = modelFieldName+"="+encodeURIComponent(model.get('url'))+"&"+dataFieldName+"="+encodeURIComponent(Controller.serialize(model.get('document')));

		var xhr = new XMLHttpRequest();
		xhr.onreadystatechange = function() { 
			if(xhr.readyState == 4) {
				switch (xhr.status) {
					case 200: //fall through					
					case 201:
						Controller.debug('saving model: '+ model.get('url') + ' ok ');
						if(callback) callback(xhr.responseText, xhr.status);
						break;
					default:
						if(errorHandler) errorHandler(xhr.status, xhr.responseText);
						else alert('An error occured while saving: status='+xhr.status+' ' + xhr.responseText);
						break;
				}
			}
		} 			
		xhr.open("POST", url, true);
		xhr.setRequestHeader('Content-Type','application/x-www-form-urlencoded');
		xhr.send(postedData);		
	},
	
	/* UNDO HISTORY ( *NOT* Browser's History ) */
	history : {
		maxLength  : 5,
		_position  : 0, 
		_undoSteps : 0,
		
		add : function(model) {
			var historyIndex = Controller.history._position % Controller.history.maxLength;

			Controller.debug('add to undo_history #'+ historyIndex+ ' :' + model.get('url'),5);
			var modelDoc = model.get('document');
			_MVC_Cache.undoHistory[historyIndex]          = {};
			_MVC_Cache.undoHistory[historyIndex].model    = model;
			try {
				_MVC_Cache.undoHistory[historyIndex].document = modelDoc.cloneNode(true);
			} catch(e) {/* Can't clone a DocumentNode in Safari & Opera. Will try something else below */ }
					
			if(!_MVC_Cache.undoHistory[historyIndex].document) {
				if (document.implementation && document.implementation.createDocument) {
							
					_MVC_Cache.undoHistory[historyIndex].document  = document.implementation.createDocument("",modelDoc.documentElement.nodeName, null);					
					/* importNode is not safe in Safari ! the source document is altered. used cloneNode to fix the prblm */
					var data = _MVC_Cache.undoHistory[historyIndex].document.importNode(modelDoc.documentElement.cloneNode(true),true);					
					try {
						_MVC_Cache.undoHistory[historyIndex].document.appendChild(data);
					} catch(e) {	
						/* Opera has already created a documentElement and can't append another root node */
						var rootNode = _MVC_Cache.undoHistory[historyIndex].document.documentElement;
						for(var i=data.childNodes.length; i>=0; i--) {
							rootNode.insertBefore(data.childNodes[i],rootNode.firstChild);
						} 
						// need to copy root node attributes
						for(var i=0;i<modelDoc.documentElement.attributes.length;i++) {
							var name  = modelDoc.documentElement.attributes.item(i).name;
							var value = modelDoc.documentElement.attributes.item(i).value;
							_MVC_Cache.undoHistory[historyIndex].document.documentElement.setAttribute(name,value);
						}
					}
				}	
			}
			if(!_MVC_Cache.undoHistory[historyIndex].document) {
				Controller.debug("<strong>Error: couldn't add to history.</strong>");		
			} else {
				Controller.history._position++;
				// clear rest of the history if undo was used.
				var clearHistoryIndex = historyIndex;
				while(Controller.history._undoSteps > 0) {
					clearHistoryIndex = (clearHistoryIndex+1) % Controller.history.maxLength;
					_MVC_Cache.undoHistory[clearHistoryIndex] = {};
					Controller.history._undoSteps--;
				}
				return historyIndex;		
			}
		},
	
		removeLast: function() {
			Controller.history._position--;
			
			Controller.debug('remove from undo_history #'+ Controller.history._position,5);

			if( Controller.history._position < 0 ) 
				Controller.history._position = Controller.history.maxLength-1;
			_MVC_Cache.undoHistory[Controller.history._position] = {};	
			Controller.history._undoSteps = 0;
		},
		
		undo : function() {
			if(Controller.history._undoSteps < _MVC_Cache.undoHistory.length) {
				Controller.history._undoSteps++;
				Controller.history._position--;
				if( Controller.history._position < 0 ) 
					Controller.history._position = Controller.history.maxLength-1;
				
				Controller.debug('undo step: '+ Controller.history._undoSteps + ' pos#'+ Controller.history._position,5);

				var model = _MVC_Cache.undoHistory[Controller.history._position].model;
				if(_MVC_Cache.undoHistory[Controller.history._position].document) {
					model.set('document',_MVC_Cache.undoHistory[Controller.history._position].document);
					Controller.debug('<textarea>'+Controller.serialize(_MVC_Cache.undoHistory[Controller.history._position].document)+'</textarea>');
				}
				else 
					Controller.debug("the model's DOMDocument wasn't properly copied into the history");
			}
		},
		
		redo : function() {
			if(Controller.history._undoSteps > 0) {
				Controller.history._undoSteps--;
				Controller.history._position = (Controller.history._position+1) % Controller.history.maxLength;

				Controller.debug('redo step: '+ Controller.history._undoSteps + ' pos#'+ Controller.history._position,5);

				var model = _MVC_Cache.undoHistory[Controller.history._position].model;
				model.set('document',_MVC_Cache.undoHistory[Controller.history._position].document);
			}
		}
	}, // End History Object

	// ---------------------------------------------------------------------------------
	// AJAX UTILITIES
	// ---------------------------------------------------------------------------------

	handleSubmit : function(param) { 	

		var options     = arguments[1]        || {};
		var onSuccess   = options.onSuccess   || null;
		var onError     = options.onError     || null;
		var updateModel = options.updateModel || null;
		
		if(typeof param == 'string') {
			form = document.getElementById(param);
		} else if (typeof param == 'object') {
			form = param;
		}
		if(form && form.tagName && form.tagName.toLowerCase()=='form') {
			// check form.id.. set a random id if necessary
			Controller.debug('adding handler for form '+ form.nodeName);	
			_MVC_Cache.handlers[form.id] = { onSuccess : onSuccess, onError : onError, updateModel : updateModel };
			Controller.addEvent(form, 'submit', Controller._postHandler)
			//form.onsubmit = Controller._postHandler;
		} else {
			alert("Error: Can't handle submit event for form '"+form+"'");
			return false;
		}
		return true;
	},
	
	_postHandler : function(e) {
		var form    = this;
		Controller.debug('submitting form '+ form.nodeName);		
		var urlPost = form.action;
		var xhr     = new XMLHttpRequest();
		xhr.open(form.method ? form.method : "POST", urlPost, true);
		xhr.onreadystatechange= function() {
			if (xhr.readyState==4) {
				
				var handler = _MVC_Cache.handlers[form.id];
				switch (xhr.status) {
					case 200:
					case 201:
					case 304:
						if(handler) {
							if(handler.updateModel && xhr.responseXML)
								handler.updateModel.set('document', xhr.responseXML);
							if (handler.onSuccess) 
								handler.onSuccess();
						}
						break;
					case 401:
					case 402:
					case 403:					
					case 404:
					case 500:
						if(handler && handler.onError) {
							handler.onError(xhr.status, xhr.responseText, form.id);
						} else {
							alert(xhr.responseText);
						}
						break;
					default:
						alert(xhr.responseText);
						break;					
				}
			}
		}
		xhr.setRequestHeader('Content-Type', form.enctype ? form.enctype : 'application/x-www-form-urlencoded');
		var data = "";
		for(var i=0; i<form.elements.length; i++) {
			switch(form.elements[i].tagName.toLowerCase()+':'+form.elements[i].type) {
					case 'select:':
						var value = form.elements[i].options[form.elements[i].selectedIndex].value;
					case 'input:file':
						// ignore for now
						continue;
						break;
					default:
						var value = form.elements[i].value;
			}
			data += encodeURIComponent(form.elements[i].name) + '=' + encodeURIComponent(value) + '&';
		}
		xhr.send(data);
		return Controller.preventEvent(e);
	},

	// ---------------------------------------------------------------------------------
	// MVC HELPERS 
	// ---------------------------------------------------------------------------------
	
	serialize : function(obj)  {
		if(typeof XMLSerializer != "undefined") {
			 var xmlSerializer = new XMLSerializer();
			 return xmlSerializer.serializeToString(obj);
		}
		if(document.implementation && document.implementation.createLSSerialize) {
			var xmlSerializer = document.implementation.createLSSerializer();
			return xmlSerializer.writeToString(obj);
		}
		if(obj.xml) 
			return obj.xml;
		if(obj.responseText) 
			return obj.responseText;
		if(obj.innerHTML)	 
			return obj.innerHTML;
		if(obj.documentElement && obj.documentElement.innerHTML)
			return obj.documentElement.innerHTML;
	},
	
	// addEvent from http://ejohn.org/projects/flexible-javascript-events/
	addEvent : function(obj, type, fn) {
		if(!obj) return;
		if (obj.attachEvent) {
			obj['e'+type+fn] = fn;
			obj[type+fn] = function(){obj['e'+type+fn]( window.event );}
			obj.attachEvent( 'on'+type, obj[type+fn] );
		} else {			
			obj.addEventListener( type,fn, false );
		}
	},
	
	preventEvent : function(e) {
		if (!e) e = window.event;
		if (e.preventDefault) e.preventDefault();
		else e.returnValue = false;
		return false;
	},
	
	
	// ---------------------------------------------------------------------------------
	// DEBUG FUNCTIONS
	// ---------------------------------------------------------------------------------
	debugLevel  : 0,					// 1 = least importance, X = most important
	debugOutput : null,
	
	debug: function (msg) {
		msgLevel = arguments[1] || 1; 	// 1 = least importance, X = most important
		
		if(Controller.debugLevel > 0 && msgLevel >= Controller.debugLevel) {
			if(!Controller.debugOutput)
				Controller.initDebug();
			if(Controller.debugOutput)
				Controller.debugOutput.innerHTML += "<br />" + msg;
		}
	},
	
	initDebug : function() {
		var output = document.getElementById('debugOutput');
		if(!output) {
			output = document.createElement('div');
			output.id = 'debugOutput';
			output.style.position = 'absolute';
			output.style.right    = '10px';
			output.style.fontSize = 'x-small';
			output.style.fontFamily = 'courier';
			if(document.body) // if page fully loaded
				Controller.debugOutput = document.body.appendChild(output);
		}
		if(Controller.debugOutput)
			Controller.debugOutput.ondblclick = function() { this.innerHTML = '' };
	}
};

Controller.addEvent(window, 'load', function() { Controller.pageLoadComplete = true;  Controller.onPageLoadComplete(); });




// ---------------------------------------------------------------------------------
/* Mozilla's implementation of XMLHTTPRequest and XSLTProcessor for IE 
 * adapted from Sarissa (Manos Batsis) 
 */
// ---------------------------------------------------------------------------------

if(typeof ActiveXObject != "undefined") {
	pickRecentProgID = function (idList){
		for(var i=0; i < idList.length; i++) {
			try {
				var oDoc = new ActiveXObject(idList[i]);
				return idList[i];
			} catch (objException) {
				// trap; try next progID
			}
		}
		throw "Could not retreive a valid progID of Class: " + idList[idList.length-1]+". (original exception: "+e+")";
	}
	// pick best available MSXML progIDs
	_IE_DOM_PROGID         = pickRecentProgID(["Msxml2.DOMDocument.5.0", "Msxml2.DOMDocument.4.0", "Msxml2.DOMDocument.3.0", "MSXML2.DOMDocument", "MSXML.DOMDocument", "Microsoft.XMLDOM"]);
	_IE_XMLHTTP_PROGID     = pickRecentProgID(["Msxml2.XMLHTTP.5.0", "Msxml2.XMLHTTP.4.0", "MSXML2.XMLHTTP.3.0", "MSXML2.XMLHTTP", "Microsoft.XMLHTTP"]);
	_IE_THREADEDDOM_PROGID = pickRecentProgID(["Msxml2.FreeThreadedDOMDocument.5.0", "MSXML2.FreeThreadedDOMDocument.4.0", "MSXML2.FreeThreadedDOMDocument.3.0"]);
	_IE_XSLTEMPLATE_PROGID = pickRecentProgID(["Msxml2.XSLTemplate.5.0", "Msxml2.XSLTemplate.4.0", "MSXML2.XSLTemplate.3.0"]);
	pickRecentProgID       = null; // we dont need this anymore
}

if(typeof XMLHttpRequest == "undefined" && typeof ActiveXObject != "undefined") {
	XMLHttpRequest = function() {
		return new ActiveXObject(_IE_XMLHTTP_PROGID);
	}
}

if(typeof XSLTProcessor == "undefined" && typeof ActiveXObject != "undefined") {
	XSLTProcessor = function() {
		this.template  = new ActiveXObject(_IE_XSLTEMPLATE_PROGID);
		this.processor = null;
	}
	XSLTProcessor.prototype.importStylesheet = function(xslDoc) {
		// convert stylesheet to free threaded
		var converted = new ActiveXObject(_IE_THREADEDDOM_PROGID); 
		converted.loadXML(xslDoc.xml);
		this.template.stylesheet = converted;
		this.processor = this.template.createProcessor();
		// (re)set default param values
		this.paramsSet = new Array();
	}
	XSLTProcessor.prototype.transformToDocument = function(sourceDoc) {
		this.processor.input = sourceDoc;
		var outDoc = new ActiveXObject(_IE_DOM_PROGID);
		this.processor.output = outDoc; 
		this.processor.transform();
		return outDoc;
	}
	XSLTProcessor.prototype.setParameter = function(nsURI, name, value) {
		/* nsURI is optional but cannot be null */
		if(nsURI) {
			this.processor.addParameter(name, value, nsURI);
		} else {
			this.processor.addParameter(name, value);
		}
		/* update updated params for getParameter */
		if(!this.paramsSet[""+nsURI]) {
			this.paramsSet[""+nsURI] = new Array();
		}
		this.paramsSet[""+nsURI][name] = value;
	}
	XSLTProcessor.prototype.getParameter = function(nsURI, name) {
		nsURI = nsURI || "";
		if(nsURI in this.paramsSet && name in this.paramsSet[nsURI]) {
			return this.paramsSet[nsURI][name];
		}else {
			return null;
		}
	}
}