 
/*****************************************************************************\

 Javascript "SOAP Client" library

 @version: 1.5 - 2006.05.23
 @author: Alan Machado
 @description: Major modifications and buffering to increase performance. Also allow for wsdl attributes
               
 @author: Matteo Casati - http://www.guru4.net/

\*****************************************************************************/

function StringBuffer() { 
   this.buffer = []; 
 } 

 StringBuffer.prototype.append = function append(str) { 
 
   this.buffer[this.buffer.length] = str; 
   return this; 
 }; 

 StringBuffer.prototype.toString = function toString() { 
   return this.buffer.join(""); 
 }; 
 
  StringBuffer.prototype.clear = function clear() { 
   this.buffer.length = 0; 
   return this.buffer
 }; 

function SOAPClientParameters()
{
	var _pl = new Array();
	var _buf = new StringBuffer();
	this.add = function(name, value) 
	{
		_pl[name] = value; 
		return this; 
	}
	
	this.toXml = function(wsdl)
	{
		_buf.clear();
		for(var p in _pl)
			if (p.substring(0,1) != "_")
				_buf.append(SOAPClientParameters._serialize(p,_pl[p],wsdl));
		return _buf.toString();	
	}
}

SOAPClientParameters._serialize = function(parentName, o,wsdl)
{
    var _buf = new StringBuffer();
    var isAttributes = false;
    
    switch(typeof(o))
    {
		  case "undefined":
				o = "";
        case "string":
            _buf.append(o.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;")); break;
        case "number":
        case "boolean":
            _buf.append(o.toString()); break;
        case "object":
            // Date
            if(o.constructor.toString().indexOf("function Date()") > -1)
            {
                var year = o.getFullYear().toString();
                var month = (o.getMonth() + 1).toString(); month = (month.length == 1) ? "0" + month : month;
                var date = o.getDate().toString(); date = (date.length == 1) ? "0" + date : date;
                var hours = o.getHours().toString(); hours = (hours.length == 1) ? "0" + hours : hours;
                var minutes = o.getMinutes().toString(); minutes = (minutes.length == 1) ? "0" + minutes : minutes;
                var seconds = o.getSeconds().toString(); seconds = (seconds.length == 1) ? "0" + seconds : seconds;
                var milliseconds = o.getMilliseconds().toString();
                var tzminutes = Math.abs(o.getTimezoneOffset());
                var tzhours = 0;
                while(tzminutes >= 60)
                {
                    tzhours++;
                    tzminutes -= 60;
                }
                tzminutes = (tzminutes.toString().length == 1) ? "0" + tzminutes.toString() : tzminutes.toString();
                tzhours = (tzhours.toString().length == 1) ? "0" + tzhours.toString() : tzhours.toString();
                var timezone = ((o.getTimezoneOffset() < 0) ? "+" : "-") + tzhours + ":" + tzminutes;
                _buf.append(year + "-" + month + "-" + date + "T" + hours + ":" + minutes + ":" + seconds + "." + milliseconds + timezone);
            }
            // Array
            else if(o.constructor.toString().indexOf("function Array()") > -1)
            {
                for(var p in o)
                {
                    if(!isNaN(p))   // linear array
                    {
                        (/function\s+(\w*)\s*\(/ig).exec(o[p].constructor.toString());
                        var type = RegExp.$1;
                        switch(type)
                        {
                            case "":
                                type = typeof(o[p]);
                            case "String":
                                type = "string"; break;
                            case "Number":
                                type = "int"; break;
                            case "Boolean":
                                type = "bool"; break;
                            case "Date":
                                type = "DateTime"; break;
                        }
                        _buf.append(SOAPClientParameters._serialize(type,o[p],wsdl));
                    }
                    else    // associative array
                        _buf.append(SOAPClientParameters._serialize(p,o[p],wsdl));
                }
            }
            // Object or custom function
            else
            {
				 	 str = ""
                for(var p in o)
                {
						if (p.substring(0,1) != "_")
						{
							if (SOAPClient._isAttributeTypeWsdl(p,wsdl))
							{
								isAttributes = true;
								_buf.append(p + "=\"" + SOAPClientParameters._serialize("",o[p],wsdl) + "\" ")
							}
							else
							{
								_buf.append(SOAPClientParameters._serialize(p,o[p],wsdl))
							}                
						}
					 }
				}	
            break;
        default:
				return "";
            //throw new Error(500, "SOAPClientParameters: type '" + typeof(o) + "' is not supported");
    }

	if (parentName == "") return _buf.toString();
	
	return addNodeStr(parentName,_buf.toString(),isAttributes);
}


function addNodeStr(elname,str,attributes)
{
	if (attributes == true)
		return "<" + elname + " " + str +  " />"
	else
		return "<" + elname + ">" + str + "</" + elname + ">"
}


function SOAPClient() {}

SOAPClient.invoke = function(url, method, parameters, async, callback)
{
	if(async)
		SOAPClient._loadWsdl(url, method, parameters, async, callback);
	else
		return SOAPClient._loadWsdl(url, method, parameters, async, callback);
}

// private: wsdl cache
SOAPClient_cacheWsdl = new Array();

// private: invoke async
SOAPClient._loadWsdl = function(url, method, parameters, async, callback)
{
	// load from cache?
	var wsdl = SOAPClient_cacheWsdl[url];
	if(wsdl + "" != "" && wsdl + "" != "undefined")
		return SOAPClient._sendSoapRequest(url, method, parameters, async, callback, wsdl);
	// get wsdl
	var xmlHttp = SOAPClient._getXmlHttp();
	xmlHttp.open("GET", url + "?wsdl", async);
	if(async) 
	{
		xmlHttp.onreadystatechange = function() 
		{
			if(xmlHttp.readyState == 4)
				SOAPClient._onLoadWsdl(url, method, parameters, async, callback, xmlHttp);
		}
	}
	xmlHttp.send(null);
	if (!async)
		return SOAPClient._onLoadWsdl(url, method, parameters, async, callback, xmlHttp);
}
SOAPClient._onLoadWsdl = function(url, method, parameters, async, callback, req)
{
	var wsdl = req.responseXML;
	SOAPClient_cacheWsdl[url] = wsdl;	// save a copy in cache
	return SOAPClient._sendSoapRequest(url, method, parameters, async, callback, wsdl);
}
SOAPClient._sendSoapRequest = function(url, method, parameters, async, callback, wsdl)
{
	// get namespace
	var ns = (wsdl.documentElement.attributes["targetNamespace"] + "" == "undefined") ? wsdl.documentElement.attributes.getNamedItem("targetNamespace").nodeValue : wsdl.documentElement.attributes["targetNamespace"].value;
	// build SOAP request

	var sr = 
				"<?xml version=\"1.0\" encoding=\"utf-16\"?>" +
				"<soap:Envelope " +
				"xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" " +
				"xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" " +
				"xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">" +
				"<soap:Body>" +
				"<" + method + " xmlns=\"" + ns + "\">" +
				parameters.toXml(wsdl) +
				"</" + method + "></soap:Body></soap:Envelope>";
	// send request

	var xmlHttp = SOAPClient._getXmlHttp();
	xmlHttp.open("POST", url, async);
	var soapaction = ((ns.lastIndexOf("/") != ns.length - 1) ? ns + "/" : ns) + method;
	xmlHttp.setRequestHeader("SOAPAction", soapaction);
	xmlHttp.setRequestHeader("Content-Type", "text/xml; charset=utf-8");
	if(async) 
	{
		xmlHttp.onreadystatechange = function() 
		{
			if(xmlHttp.readyState == 4)
				SOAPClient._onSendSoapRequest(method, async, callback, wsdl, xmlHttp);
		}
	}
	xmlHttp.send(sr);
	if (!async)
		return SOAPClient._onSendSoapRequest(method, async, callback, wsdl, xmlHttp);
}
SOAPClient._onSendSoapRequest = function(method, async, callback, wsdl, req)
{
	var o = null;
	var nd = SOAPClient._getElementsByTagName(req.responseXML, method + "Result");
	if(nd.length == 0)
	{
		if(req.responseXML.getElementsByTagName("faultcode").length > 0)
			throw new Error(500, req.responseXML.getElementsByTagName("faultstring")[0].childNodes[0].nodeValue);
	}
	else
		o = SOAPClient._soapresult2object(nd[0], wsdl);
	if(callback)
		callback(o, req.responseXML);
	if(!async)
		return o;		
}

// private: utils
SOAPClient._getElementsByTagName = function(document, tagName)
{
	try
	{
		// trying to get node omitting any namespaces (latest versions of MSXML.XMLDocument)
		return document.selectNodes(".//*[local-name()=\""+ tagName +"\"]");
	}
	catch (ex) {}
	// old XML parser support
	return document.getElementsByTagName(tagName);
}

SOAPClient._soapresult2object = function(node, wsdl)
{
	return SOAPClient._node2object(node, wsdl);
}
SOAPClient._node2object = function(node, wsdl)
{
 // var obj = new Object()

/* Node Types
	ELEMENT_NODE = 1 
	ATTRIBUTE_NODE = 2 
	TEXT_NODE = 3 
	CDATA_SECTION_NODE = 4 
	ENTITY_REFERENCE_NODE = 5 
	ENTITY_NODE = 6 
	PROCESSING_INSTRUCTION_NODE = 7 
	COMMENT_NODE = 8 
	DOCUMENT_NODE = 9 
	DOCUMENT_TYPE_NODE = 10 
	DOCUMENT_FRAGMENT_NODE = 11 
	NOTATION_NODE = 12 
*/
	// null node
	if(node == null)
		return null;
	if(node.nodeType == 2) 
		return SOAPClient._extractValue(node.nodeName,node.nodeValue, wsdl);
	if(node.nodeType == 3 || node.nodeType == 4)
	{
		return SOAPClient._extractValue(node.parentNode.nodeName,node.nodeValue, wsdl);
	}
	
	var obj = eval("new function "+node.nodeName+"(){}");
		
	// leaf node
	if (node.childNodes.length == 1 && (node.childNodes[0].nodeType == 3 || node.childNodes[0].nodeType == 4))
		obj = SOAPClient._node2object(node.childNodes[0], wsdl);
		
	var isarray = SOAPClient._getDataTypeFromWsdl(node.nodeName, wsdl).toLowerCase().indexOf("arrayof") != -1;
	// object node
	if(!isarray)
	{
		for(var i = 0; i < node.childNodes.length; i++)
		{
			var p = SOAPClient._node2object(node.childNodes[i], wsdl);
			obj[node.childNodes[i].nodeName] = p;
		}

	}
	// list node
	else
	{
		// create node ref
		var arr = new Array();
		for(var i = 0; i < node.childNodes.length; i++)
			arr[arr.length] = SOAPClient._node2object(node.childNodes[i], wsdl);
		return arr;
	}
			// attributes and no text
	if(node.attributes != null)
	{
		for(var i = 0; i < node.attributes.length; i++)
		{
			var p = SOAPClient._node2object(node.attributes[i], wsdl);
			obj[node.attributes[i].nodeName] = p;
		}
	}
	return obj;
}


SOAPClient._extractValue = function(nodeName,nodeValue, wsdl)
{
	var value = nodeValue;
	switch(SOAPClient._getDataTypeFromWsdl(nodeName, wsdl).toLowerCase())
	{
		default:
		case "s:string":			
			return (value != null) ? value + "" : "";
		case "s:boolean":
			return value+"" == "true";
		case "s:int":
		case "s:long":
			return (value != null) ? parseInt(value + "", 10) : 0;
		case "s:double":
			return (value != null) ? parseFloat(value + "") : 0;
		case "s:datetime":
			if(value == null)
				return null;
			else
			{
				value = value + "";
				value = value.substring(0, value.lastIndexOf("."));
				value = value.replace(/T/gi," ");
				value = value.replace(/-/gi,"/");
				var d = new Date();
				d.setTime(Date.parse(value));										
				return d;				
			}
	}
}

var wsdlNodeBuff = []
SOAPClient._getDataTypeFromWsdl = function(elementname, wsdl)
{
   if (wsdlNodeBuff[elementname] == null)
   {
		wsdlNodeBuff[elementname] = getWsdlNode(elementname,wsdl)
	}
	
	node = wsdlNodeBuff[elementname]

	if (node == null)
	{
		return "";
	}

	type = node.attributes.getNamedItem("type").nodeValue; // IE
	if (type == "")
		type = node.attributes["type"].value; // MOZ
	return type
	
}

SOAPClient._isAttributeTypeWsdl = function(elementname, wsdl)
{
   if (wsdlNodeBuff[elementname] == null)
   {
		wsdlNodeBuff[elementname] = getWsdlNode(elementname,wsdl)
	}
	
	node = wsdlNodeBuff[elementname]

	if (node == null)
	{
		return false;
	}

	return (node.nodeName.indexOf("attribute") >= 0); 
	
}

function getWsdlNode(elementname, wsdl)
{
		
	var el = wsdl.getElementsByTagName("s:element");	// IE
	if(el.length == 0)
	{
		el = wsdl.getElementsByTagName("element");	// MOZ
	}
	
	var result;
	for(var i = 0; i < el.length; i++)
	{
		if(isRequiredWsdl(el[i],elementname))
			return el[i];
	}

	var el = wsdl.getElementsByTagName("s:attribute");	// IE
	if(el.length == 0)
		el = wsdl.getElementsByTagName("attribute");	// MOZ
	
	for(var i = 0; i < el.length; i++)
	{
		if(isRequiredWsdl(el[i],elementname))
			return el[i];
	}
	
	return null;	
	
}

function isRequiredWsdl(el,elname)
{
	if(el.attributes["name"] + "" == "undefined")	// IE
	{
		if(el.attributes.getNamedItem("name") != null && el.attributes.getNamedItem("name").nodeValue == elname && el.attributes.getNamedItem("type") != null) 
		{
			return  true;
		}
	}
	else // MOZ
	{
		if(el.attributes["name"] != null && el.attributes["name"].value == elname && el.attributes["type"] != null)
		{
			return true;
		}
	}
	return false;
}

function IsRequiredElement(el,elname)
{
	if(el.attributes["name"] + "" == "undefined")	// IE
	{
		if(el.attributes.getNamedItem("name") != null && el.attributes.getNamedItem("name").nodeValue == elname && el.attributes.getNamedItem("type") != null) 
		{
			return  el.attributes.getNamedItem("type").nodeValue;
		}
	}
	else // moz
	{
		if(el.attributes["name"] != null && el.attributes["name"].value == elname && el.attributes["type"] != null)
		{
			return el.attributes["type"].value;
		}
	}
	
	return "";
}

// private: xmlhttp factory
SOAPClient._getXmlHttp = function() 
{
	try
	{
		if(window.XMLHttpRequest) 
		{
			var req = new XMLHttpRequest();
			// some versions of Moz do not support the readyState property and the onreadystate event so we patch it!
			if(req.readyState == null) 
			{
				req.readyState = 1;
				req.addEventListener("load", 
									function() 
									{
										req.readyState = 4;
										if(typeof req.onreadystatechange == "function")
											req.onreadystatechange();
									},
									false);
			}
			return req;
		}
		if(window.ActiveXObject) 
			return new ActiveXObject(SOAPClient._getXmlHttpProgID());
	}
	catch (ex) {}
	throw new Error("Your browser does not support XmlHttp objects");
}
SOAPClient._getXmlHttpProgID = function()
{
	if(SOAPClient._getXmlHttpProgID.progid)
		return SOAPClient._getXmlHttpProgID.progid;
	var progids = ["Msxml2.XMLHTTP.5.0", "Msxml2.XMLHTTP.4.0", "MSXML2.XMLHTTP.3.0", "MSXML2.XMLHTTP", "Microsoft.XMLHTTP"];
	var o;
	for(var i = 0; i < progids.length; i++)
	{
		try
		{
			o = new ActiveXObject(progids[i]);
			return SOAPClient._getXmlHttpProgID.progid = progids[i];
		}
		catch (ex) {};
	}
	throw new Error("Could not find an installed XML parser");
}
