function SOAPClientParameters() {
	var _pl = new Array();
	this.add = function(name, value)  {
		_pl[name] = value; 
		return this; 
	}
	this.toXml = function() {
		var xml = "";
		for (var p in _pl) {
			var xmlItem = "";
			try {
				xmlItem = "<" + p + ">" + SOAPClientParameters._serialize(_pl[p]) + "</" + p + ">";
			} catch (e) {}
			xml += xmlItem;
		}
		return xml;	
	}
}
SOAPClientParameters._serialize = function(o) {
    var s = "";
    switch (typeof(o)) {
        case "string":
            s += o.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;"); break;
        case "number":
        case "boolean":
            s += 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;
                s += 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;
                        }
                        s += "<" + type + ">" + SOAPClientParameters._serialize(o[p]) + "</" + type + ">"
                    }
                    else    // associative array
                        s += "<" + p + ">" + SOAPClientParameters._serialize(o[p]) + "</" + p + ">"
                }
            }
            // Object or custom function
            else
                for(var p in o)
                    s += "<" + p + ">" + SOAPClientParameters._serialize(o[p]) + "</" + p + ">";
            break;
        default:
            throw new Error(500, "SOAPClientParameters: type '" + typeof(o) + "' is not supported");
    }
    return s;
}

function SOAPClient() {}

SOAPClient.invoke = function(url, method, parameters, async, callback, callbackparams) {
	try {
		if(async) {
			SOAPClient._loadWsdl(url, method, parameters, async, callback, callbackparams);
		} else {
			return SOAPClient._loadWsdl(url, method, parameters, async, callback, callbackparams);
		}
	} catch ( ex ) {
		alert( ex);
	} 
}

// private: wsdl cache
SOAPClient_cacheWsdl = new Array();

// private: invoke async
SOAPClient._loadWsdl = function(url, method, parameters, async, callback, callbackparams) {
	// load from cache?
	var wsdl = SOAPClient_cacheWsdl[url];
	if(wsdl + "" != "" && wsdl + "" != "undefined")
		return SOAPClient._sendSoapRequest(url, method, parameters, async, callback, callbackparams, 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, callbackparams, xmlHttp);
		}
	}
	xmlHttp.send(null);
	if (!async)
		return SOAPClient._onLoadWsdl(url, method, parameters, async, callback, callbackparams, xmlHttp);
}
SOAPClient._onLoadWsdl = function(url, method, parameters, async, callback, callbackparams, req) {
	var wsdl = req.responseXML;
	SOAPClient_cacheWsdl[url] = wsdl;	// save a copy in cache
	return SOAPClient._sendSoapRequest(url, method, parameters, async, callback, callbackparams, wsdl);
}

SOAPClient._sendSoapRequest = function(url, method, parameters, async, callback, callbackparams, 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-8\"?>" +
				"<soapenv:Envelope " +
				"xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" " +
				"xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" " +
				"xmlns:q0=\"" + ns + "\" " +
				"xmlns:soapenv=\"http://schemas.xmlsoap.org/soap/envelope/\">" +
				"<soapenv:Body>" +
				"<q0:" + method + ">" +
				parameters.toXml() +
				"</q0:" + method + "></soapenv:Body></soapenv: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, callbackparams, wsdl, xmlHttp);
		}
	}
	xmlHttp.send(sr);
	if (!async) {
		return SOAPClient._onSendSoapRequest(method, async, callback, callbackparams, wsdl, xmlHttp);
	}
}

SOAPClient._onSendSoapRequest = function(method, async, callback, callbackparams, wsdl, req) {
	var o = null;
	var nd = null;
	try {
		nd = req.responseXML.documentElement.childNodes[0].childNodes[0];
		var str1 = nd.tagName.toLowerCase();
		var str2 = (method + "Response").toLowerCase();
		var str1compstr2 = str1.indexOf(str2);
		if (str1compstr2 == -1) {
			nd = null;
		}
	} catch (ex) {}

	if (nd == null) {
		if(req.responseXML.getElementsByTagName("faultcode").length > 0) {
			if(async || callback) {
		        	o = new Error(500, req.responseXML.getElementsByTagName("faultstring")[0].childNodes[0].nodeValue);
			} else {
			    throw new Error(500, req.responseXML.getElementsByTagName("faultstring")[0].childNodes[0].nodeValue);
			}
		}
	} else {
		o = SOAPClient._soapresult2object(nd, wsdl);
	}
	if (callback) {
		callback(o, req.responseXML, callbackparams);
	}
	if (!async) {
		return o;
	}
}
SOAPClient._soapresult2object = function(node, wsdl) {
    return SOAPClient._node2object(node);
}
SOAPClient._node2object = function(node) {
	// null node
	if(node == null) {
		return null;
	}
	// text node
	if (node.nodeType == 3 || node.nodeType == 4) {
		return SOAPClient._extractValue(node);
	}
	// leaf node
	if (node.childNodes.length == 1 && (node.childNodes[0].nodeType == 3 || node.childNodes[0].nodeType == 4)) {
		return SOAPClient._node2object(node.childNodes[0]);
	}

	isarray = false;
	var nodeName = "";
	if (node.childNodes.length > 1) {
		isarray = true;
		for (var i = 0; i < node.childNodes.length && isarray; i++) {
			if (nodeName ==  "") {
				nodeName = node.childNodes[i].nodeName;
			} else {
				if (nodeName != node.childNodes[i].nodeName) {
					isarray = false;
				}
			}
		}
	}
	if (!isarray) {
		// object node
		var obj = null;
		if (node.hasChildNodes()) {
			obj = new Object();
		}
		for (var i = 0; i < node.childNodes.length; i++) {
			var p = SOAPClient._node2object(node.childNodes[i]);
			obj[node.childNodes[i].nodeName] = p;
		}
		for (var i = 0; i < node.attributes.length; i++) {
			var p = SOAPClient._node2object(node.attributes[i]);
			obj[node.attributes[i].nodeName] = p;
		}
		return obj;
	} else {
		// list node
		// create node ref
		var l = new Array();
		for (var i = 0; i < node.childNodes.length; i++) {
			l[l.length] = SOAPClient._node2object(node.childNodes[i]);
		}
		return l;
	}
	return null;
}
SOAPClient._extractValue = function(node) {
	var value = node.nodeValue;
	if (node.parentNode == null) {
		return (value != null) ? value + "" : "";
	}
	return (value != null) ? value + "" : "";
}
// 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);
}
// 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");
}

SOAPClient.getResultAsArray = function(result, objectName) {
	if (result != null && typeof result == 'object' && result.constructor != Array) {
		var resultArray = new Array();
		
		if (objectName) {
		  resultArray[0] = result[objectName];
		} else {
		  resultArray[0] = result;
		}
		return resultArray;
	}
	return result;
}
