﻿/*
Copyright (c)2004 Creatop

XMLRPC Class - XMLRPC for Mozilla/NS/Gecko and IE 5.5 or greater 

By Scott Horn
scott@creatop.com.au

Process:
1. Build XML by converting javascript objects
2. Send XML
3. Receive XML Response and convert to js objects
	
This code is free of charge and without warranty. Use at your own risk.
NOTE: If you make significant modifications or improvements to this component, send the resulting code to the author.
AUTHOR NOTE: This comment section must remain with the component in any distribution. Feel free to snip it out on your production box.
*/

//
// XMLRPC create object 
//
// Call with a url and optional callback function
// eg. var XMLRPCObj = XMLRPC( "test.xml", function() { alert "function complete"; } );
//
function XMLRPC(url, callback, bdebug, synchronous) {
	this.url = url;
	this.callback = callback;
	this.bdebug = bdebug;
	this.debugWindow = null;
	this.async = true;
	//patch by jay. allow users to send in sync/async. default to async.
	if (synchronous) {
		this.async = false;
	}
}

//
// XMLRPC.call( method, arg1, arg2...argN );
//
XMLRPC.prototype.call = function(method){
	
	// Build a Method Call Object
	if (document.all)
		this.oRequestXML = new ActiveXObject("Microsoft.XMLDOM");
	else 
		this.oRequestXML = document.implementation.createDocument("","",null);
	
	var oMethodCall = this.createElement("methodCall");
	this.oRequestXML.appendChild(oMethodCall);
	oMethodCall.appendChild( this.createElement("methodName", method) );

	// Append Each argument as a serialized parameter	
	var oParams = this.createElement("params");
	for (i = 1; i<arguments.length; i++) {
		oParams.appendChild( this.AddParam(arguments[i]) );		
	}
	oMethodCall.appendChild(oParams);
	if(this.bdebug) this.dumpwin(this.oRequestXML);
	// Hand the work HTTP Post and Callback work off to sendAndLoad
	this.sendAndLoad(this.url);
}

//
// Extended CreateElement to reduce coding
//
XMLRPC.prototype.createElement = function(tType, tContent) {
	var oElement = this.oRequestXML.createElement(tType);
	if ( typeof(tContent) != 'undefined' ) oElement.appendChild(this.oRequestXML.createTextNode(tContent));
	return oElement;
}

//
// Javascript blocks so we need to use callbacks when a event is complete
//
XMLRPC.prototype.sendAndLoad = function() {
	//document.getElementById('debugitem').innerHTML = this.dump(this.oRequestXML);
	if (document.all) {
		this.xmlHTTPPost = new ActiveXObject("MSXML2.XMLHttp");
		this.xmlHTTPPost.onreadystatechange = this._onSendAndLoad(this, this.oResponseXML);
	} else {
		this.xmlHTTPPost = new XMLHttpRequest();
		this.xmlHTTPPost.onload = this._onSendAndLoad(this, this.oResponseXML);
	}
	this.xmlHTTPPost.open("POST", this.url, this.async); //Patch by Jay. exposing sync flag to the library user.
	this.xmlHTTPPost.send(this.oRequestXML);
}

//
// Closure for Send Event (to allow the storage of the this object)
//
XMLRPC.prototype._onSendAndLoad = function(oThis) {
	return function() {
		if(!document.all || (document.all && oThis.xmlHTTPPost.readyState == 4)) {
			
			// DEBUGGING
			if(oThis.bdebug && ( oThis.xmlHTTPPost.responseXML || oThis.xmlHTTPPost.responseText) ) {
				try {
					oThis.debugWindow = oThis.debugWindow || window.open('','','location=no, directories=no, status=no, menubar=no, scrollbars=yes');
					oDiv = oThis.debugWindow.document.createElement('div');
					oThis.debugWindow.document.body.appendChild(oDiv);
					if(oThis.xmlHTTPPost.responseXML) oDiv.innerHTML = 'Response Dump<BR>'+oThis.dump(oThis.xmlHTTPPost.responseXML);
					if(oThis.xmlHTTPPost.responseText) oDiv.innerHTML += '<br><br><br><b>Text</b><BR>' + oThis.xmlHTTPPost.responseText;
				} catch(e) {};
			}
			var oResponseObj = new Object;
			var oResponseXML = oThis.xmlHTTPPost.responseXML;
			var oFault = 1;
			if(oResponseXML) {
				var oFault = oResponseXML.getElementsByTagName('fault').item(0);
				if( oFault ) {
					oResponseObj = oThis.XMLtoObject( oFault.firstChild.firstChild );
				} else {
					//lil modification by jay so that we ignore any 
					// whitespace/textnodes which could break the firstChild.firstChild in original code below
					var firstParam = oResponseXML.getElementsByTagName('param').item(0);
					if (firstParam){
						var firstValue = firstParam.getElementsByTagName('value').item(0);
						oResponseObj = oThis.XMLtoObject( firstValue.firstChild); //Jay.
					}
					//old code:
					//oResponseObj = oThis.XMLtoObject( oResponseXML.getElementsByTagName('param').item(0).firstChild.firstChild );
				}
			} else {
				oResponseObj.faultString = oThis.xmlHTTPPost.responseText;
			}
			
			if(oThis.callback) {
				oThis.callback(oResponseObj, oFault);
			}
		}
	}
}


//
// Deserialization Routines - ie XML to JS
//
XMLRPC.prototype.XMLtoObject = function(oParam) {
	return this.ExtractObject(oParam);	
}
XMLRPC.prototype.ExtractObject = function(oXML) {
	try {
		
		switch ( oXML.tagName.toLowerCase() ){
			case "string" : return (oXML.firstChild ? oXML.firstChild.nodeValue:'');	break;
			case "boolean" : return (parseInt(oXML.firstChild.nodeValue) ? true : false );	break;
			case "i4" :  return parseInt(oXML.firstChild.nodeValue); 						break;
			case "int" :  return parseInt(oXML.firstChild.nodeValue); 						break;
			case "double": return parseFloat(oXML.firstChild.nodeValue); 					break;
			case "datetime.iso8601":   return this.DateObject(oXML.firstChild.nodeValue); 	break;
			case "array":  return this.ArrayObject( oXML );  								break;
			case "struct": return this.StructObject( oXML ); 								break;
		}
	} catch(e) {
		//document.title = oXML.tagName.toLowerCase();
		return null;
	}
}
XMLRPC.prototype.ArrayObject = function(oXML) {
	// passed array need to step through data
	var oData = oXML.firstChild;
	var aArray = new Array(oData.childNodes.length);
	for( var i = 0; i < oData.childNodes.length; i++) {
		// jump past value tag
		aArray[i] = this.ExtractObject(oData.childNodes.item(i).firstChild);
	}
	return aArray;
}
XMLRPC.prototype.StructObject = function(oXML) {
	var oStruct = new Object;
	for( var i = 0; i < oXML.childNodes.length; i++) {
		var oMember = oXML.childNodes.item(i);
		var name = oMember.getElementsByTagName("name").item(0).firstChild.nodeValue;
		var value = this.ExtractObject( oMember.getElementsByTagName("value").item(0).firstChild );
		oStruct[name] = value;
	}
	return oStruct;
}
XMLRPC.prototype.DateObject = function(tDate) {
	var yr_num = tDate.substring(0,4);
	
	var mo_num = parseInt(tDate.substring(4,6), 10) - 1;
	var day_num = tDate.substring(6,8);
	var hr_num = tDate.substring(9,11);
	var min_num = tDate.substring(12,14);
	var sec_num = tDate.substring(15,17);
	var oDate = new Date(yr_num, mo_num, day_num, hr_num, min_num, sec_num);
	
	return oDate;
}



//
// Serialisation Routines ie JS to XML
//
XMLRPC.prototype.AddParam = function(oObject) {
	var oParam = this.createElement("param");
	var oValue = this.createElement("value");
	oValue.appendChild(this.AddObject( oObject ));	
	oParam.appendChild(oValue);
	return oParam;
}

XMLRPC.prototype.AddObject = function (oObject) {
	var tType = this.dataType(oObject);
	switch ( tType ){
		case "array":
			return this.ArrayXML(oObject);
		case "struct":
			return this.StructXML(oObject);
		case "date":
			return this.createElement( "dateTime.iso8601", this.dateToISO8601(oObject) );
		case "boolean":
			return this.createElement( tType, oObject == true ? '1' : '0' );
		// All other types
		default:
			return this.createElement( tType, oObject );
  	}
	
}

XMLRPC.prototype.dataType = function (oObject){
  var tType = typeof(oObject);
  tType = tType.toLowerCase();
  switch(tType){
    case "number":
      Math.round(oObject) == oObject ? tType = "i4" : tType = "double";
      break;
    case "object":
		switch(oObject.constructor) {
			case Date: 	tType = "date"; 	break;
			case Array: tType = "array"; 	break;
			default: 	tType = "struct";
		}
		break;
  }
  return tType;
}


XMLRPC.prototype.ArrayXML = function(oObject){
  var oArray = this.createElement("array");
  var oData = this.createElement("data");  
  for (var i = 0; i < oObject.length; i++){
  	var oValue = this.createElement("value");
	oValue.appendChild( this.AddObject( oObject[i] ) );
    oData.appendChild( oValue );
  }
  oArray.appendChild(oData);
  return oArray;
}

XMLRPC.prototype.StructXML = function(oObject){
  var oStruct =  this.createElement("struct");
  for (var i in oObject){
  	var oMember = this.createElement("member");
	var oName = this.createElement("name", i );
	var oValue = this.createElement("value");
	oValue.appendChild( this.AddObject( oObject[i] ) );
	oMember.appendChild( oName );
	oMember.appendChild( oValue );
	oStruct.appendChild( oMember );
  }
  return oStruct;
}

XMLRPC.prototype.dateToISO8601 = function (date) {
  var tYear = 1900 + date.getYear();
  tYear = tYear.toString();
  var tMonth = 	 date.getMonth() + 1;
  tMonth = 		this.TwoDigit( tMonth.toString() );
  var tDay = 	this.TwoDigit( date.getDate().toString() );
  var tHour = 	this.TwoDigit( date.getHours().toString() );
  var tMin = 	this.TwoDigit( date.getMinutes().toString() ); 
  var tSec = 	this.TwoDigit( date.getSeconds().toString() );
  return tYear+tMonth+tDay+"T"+tHour+":"+tMin+":"+tSec;
} 

// Pads 2 digit numbers with a zero if < 10  
XMLRPC.prototype.TwoDigit = function(tString){
  return (tString.length == 1 ? "0" + tString : tString );
}


//
// Quick Form to Struct Conversion
//
XMLRPC.prototype.FormExtract = function(oForm) {
	var elements = oForm.elements;
	var oForm = new Object;
	for(var i=0; i < elements.length; i++) {
		var oElement = elements[i];
		if (!oElement.name || oElement.name.indexOf('__CURRENTROW__') != -1 ) continue;
		
		var tType = oElement.type.toLowerCase();
		if( tType.search(/(checkbox)|(radio)/i) != -1 ) {
			if(oElement.checked) oForm[oElement.name] =  oForm[oElement.name] ?  oForm[oElement.name] + "," + oElement.value : oElement.value;
		} else if(tType.search(/(text)|(textarea)|(hidden)|(password)/i) != -1) {
			oForm[oElement.name] = oElement.value || '';
		} else if(tType.search(/select/i) != -1) {
			if (tType == "select-one") {
				oForm[oElement.name] = oElement.options[oElement.options.selectedIndex].value;
			} else {
				var bFirstTime = 1;
				for (j=0; j < oElement.options.length; j++) {
					if (oElement.options[j].selected == true) {
						if (bFirstTime) {
							oForm[oElement.name] = oElement.options[j].value;
							bFirstTime = 0;
						} else {
							oForm[oElement.name] += "," + oElement.options[j].value;
						}
					}
				}
			}
		}
	}
	return oForm;
}

//
// Facility to render our the XML Object for Debugging 
//
XMLRPC.prototype.dump = function(oObj) {
	var tText = '';
	tText += '<blockquote style="margin-top:0px;margin-bottom:0px;margin-left:15px">&lt;' + (oObj.nodeName||'Noname');
	var aAttributes = oObj.attributes|| new Array();
	for(var i = 0; i <  aAttributes.length; i++) {	
		tText += ' '+ aAttributes[i].name+'="'+aAttributes[i].value+'"';
	}
	tText += '&gt;<br>'+ (oObj.nodeValue ? oObj.nodeValue :'');
	var aChildren = oObj.childNodes|| new Array();
	for(var i = 0; i <aChildren.length; i++) {		
		tText += this.dump(aChildren[i]);
	}
	tText += '&lt;/'+ (oObj.nodeName||'Noname')+'&gt;</blockquote>';
	return tText;
}

XMLRPC.prototype.dumpwin = function(oObj) {
	try{
		var tHTML = this.dump(oObj);
		this.debugWindow = this.debugWindow || window.open('','','location=no, directories=no, status=no, menubar=no, scrollbars=yes');
		oDiv = this.debugWindow.document.createElement('div');
		this.debugWindow.document.body.appendChild(oDiv);
		oDiv.innerHTML = 'XML Dump<BR>' + tHTML;
	} catch(e){}
}
