/*****************************************************************************\

  Javascript "SOAP Client" library

  @version: 1.4 - 2005.12.10
  @author: Matteo Casati, Ihar Voitka - http://www.guru4.net/
  @description: (1) SOAPClientParameters.add() method returns 'this' pointer.
  (2) "_getElementsByTagName" method added for xpath queries.
  (3) "_getXmlHttpPrefix" refactored to "_getXmlHttpProgID" (full 
  ActiveX ProgID).

  @version: 1.3 - 2005.12.06
  @author: Matteo Casati - http://www.guru4.net/
  @description: callback function now receives (as second - optional - parameter) 
  the SOAP response too. Thanks to Ihar Voitka.

  @version: 1.2 - 2005.12.02
  @author: Matteo Casati - http://www.guru4.net/
  @description: (1) fixed update in v. 1.1 for no string params.
  (2) the "_loadWsdl" method has been updated to fix a bug when 
  the wsdl is cached and the call is sync. Thanks to Linh Hoang.

  @version: 1.1 - 2005.11.11
  @author: Matteo Casati - http://www.guru4.net/
  @description: the SOAPClientParameters.toXML method has been updated to allow
  special characters ("<", ">" and "&"). Thanks to Linh Hoang.

  @version: 1.0 - 2005.09.08
  @author: Matteo Casati - http://www.guru4.net/
  @notes: first release.

  \*****************************************************************************/

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)
    {
      if(typeof(_pl[p]) != "function")
        xml += "<" + p + ">" + _pl[p].toString().replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;") + "</" + p + ">";
    }
    return xml;	
  }
}

function SOAPClient() {}
SOAPClient._debugging = function(){
  //return true;
  return false;
}

SOAPClient.invoke = function(url, method, parameters, async, callback, wsdlUrl)
{
  if(!wsdlUrl)
    wsdlUrl = url + "?wsdl";
  if(async)
    SOAPClient._loadWsdl(url, method, parameters, async, callback, wsdlUrl);
  else
    return SOAPClient._loadWsdl(url, method, parameters, async, callback, wsdlUrl);
}

// private: wsdl cache
SOAPClient_cacheWsdl = new Array();

// private: invoke async
SOAPClient._loadWsdl = function(url, method, parameters, async, callback, wsdlUrl)
{
  // load from cache?
  var wsdl = SOAPClient_cacheWsdl[wsdlUrl];
  if(wsdl + "" != "" && wsdl + "" != "undefined")
    return SOAPClient._sendSoapRequest(url, method, parameters, async, callback, wsdl);
  // get wsdl
  var xmlHttp = SOAPClient._getXmlHttp();
  xmlHttp.open("GET", wsdlUrl, 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-8\"?>" +
    "<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() +
    "</" + 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 attributes = req.responseXML.documentElement.attributes;
  var nodeName = false;
  try{
outer_loop: for(var i = 0; i < attributes.length; i++){
              if(attributes[i].value == "http://schemas.xmlsoap.org/soap/envelope/"){
                var bodyTag = attributes[i].name.substr(6) + ":Body";
                var soapbody = SOAPClient._getElementsByTagName(req.responseXML, bodyTag);
                for(var j = 0; j < soapbody[0].childNodes.length; j++){
                  if(soapbody[0].childNodes[j].nodeName.indexOf(method + "Response") != -1){
                    nodeName = soapbody[0].childNodes[j].nodeName;
                    break outer_loop;
                  }
                }
              }
            }
  } 
  catch(ex){
    //this is for firefox.  congratulations.
  }
  try{
    if(!nodeName){
      nodeName = method + "Response";
    }
    var nd = SOAPClient._getElementsByTagName(req.responseXML, nodeName);
    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, req.responseXML);
      if(callback) {
        callback(o, req.responseXML);
      }
    }
    if(!async) {
      return o;		
    }
  }catch (ex){ if(SOAPClient._debugging()) { alert("Callback function failed: " + ex.message);}  return null; }
}

// 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, document)
{
  return SOAPClient._node2object(node, wsdl, document);
}
SOAPClient._getElementsByTagNameAndId = function(document, tagName, id){
  var elements = SOAPClient._getElementsByTagName(document, tagName);
  for(var i = 0; i < elements.length; i++){
    e = elements[i];
    if(e.getAttribute('id') && e.getAttribute('id') == id){
      return e;
    }
  }
}
SOAPClient._node2object = function(node, wsdl, document)
{
  // null node
  if(node == null)
    return null;
  // child node
  // text node
  if(node.nodeType == 3 || node.nodeType == 4)
    return SOAPClient._extractValue(node, wsdl);
  // leaf node
  if (node.childNodes.length == 1 && (node.childNodes[0].nodeType == 3 || node.childNodes[0].nodeType == 4))
    return SOAPClient._node2object(node.childNodes[0], wsdl, document);
  var isarray = SOAPClient._getTypeFromWsdl(node, wsdl).toLowerCase().indexOf("arrayof") != -1;
  // href node
  if(node.getAttribute('href')) {
    var href = node.getAttribute('href');
    if(href.substr(0,1) == '#'){
      href = href.substr(1);
    }
    var multiref = SOAPClient._getElementsByTagNameAndId(document, "multiRef", href);
    var obj = new Object();
    return SOAPClient._node2object(multiref, wsdl, document);
  }
  // object node
  if(!isarray)
  {
    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], wsdl,document);
      obj[node.childNodes[i].nodeName] = p;
    }
    return obj;
  }
  // list node
  else
  {
    // create node ref
    var l = new Array();
    for(var i = 0; i < node.childNodes.length; i++)
      l[l.length] = SOAPClient._node2object(node.childNodes[i], wsdl, document);
    return l;
  }
  return null;
}
SOAPClient._extractValue = function(node, wsdl)
{
  var value = node.nodeValue;
  switch(SOAPClient._getTypeFromWsdl(node.parentNode, 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;				
        }
  }
}
SOAPClient._getTypeFromWsdl = function(element, wsdl)
{
  var elementname = element.nodeName;
  var ell = wsdl.getElementsByTagName("s:element");	// IE
  if(ell.length == 0)
    ell = wsdl.getElementsByTagName("element");	// MOZ
  for(var i = 0; i < ell.length; i++)
  {
    if(ell[i].attributes["name"] + "" == "undefined")	// IE
    {
      if(ell[i].attributes.getNamedItem("name") != null && ell[i].attributes.getNamedItem("name").nodeValue == elementname && ell[i].attributes.getNamedItem("type") != null) 
        return ell[i].attributes.getNamedItem("type").nodeValue;
    }	
    else // MOZ
    {
      if(ell[i].attributes["name"] != null && ell[i].attributes["name"].value == elementname && ell[i].attributes["type"] != null)
        return ell[i].attributes["type"].value;
    }
  }
  for(var i = 0; i < element.attributes.length; i++){
    var attrib = element.attributes[i];
    if(attrib.name.match(/(:|^)type$/)){
      if(attrib.value.indexOf(":Array") != -1){
        return "arrayof";
      }
    }
  }
  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");
}
