1

I have a problem with the below javascript file.
It's supplied by a third party, and worked only in IE QuirksMode.
Because I don't control the server backend, I can't change the API to JSON instead of SOAP, and avoid the completely braindead script altogether.

So I fixed the script so it also works in IE standards mode in IE 10/11 in a non-intranet environment.
While I was at it, I tried to fix it for Chrome, too.

I have only one remaining problem that I can't solve:

In function GetSOAPResponseEx i get the following error:

Uncaught NamespaceError: Failed to execute 'evaluate' on 'Document': The string '//ns1:RunLocatorResponse' contains unresolvable namespaces. SOAPAPI.js:64
XMLDocument.selectSingleNode SOAPAPI.js:64
GetSOAPResponseEx SOAPAPI.js:371
loadOrgRoleUserNames AWEMain.aspx:156

but I don't know why or how to fix it.

My hunch is that

objXmlDoc.setProperty("SelectionNamespaces", 'xmlns:ns1="http://tempuri.org/AWEService/API/WASAPI"');

does not exactly the same thing as

  objXmlDoc = objXmlDomParser.parseFromString(szResponse, "text/xml");
            objXmlDoc.documentElement.setAttribute("xmlns:ns1", "http://tempuri.org/AWEService/API/WASAPI");

But I don't know if the problem is setAttribute or something in selectSingleNode.

This are the XML files the selectSingleNode xpath query runs on:

-- Chrome

<?xml version="1.0" encoding="UTF-8"?>
<soapenv:Envelope 
xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" 
xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
xmlns:ns1="http://tempuri.org/AWEService/API/WASAPI">

<soapenv:Body>

<ns1:RunLocatorResponse xmlns="urn:RunLocator" 
xmlns:ns1="http://tempuri.org/AWEService/API/WASAPI" 
xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">

<ns1:RunLocatorResult>
<ns1:Target>Administrator</ns1:Target>
<ns1:FirstName>Administrator</ns1:FirstName>
<ns1:LastName>Administrator</ns1:LastName>
</ns1:RunLocatorResult>
<ns1:RunLocatorResult>

<ns1:Target>MyFirstName.MyLastName</ns1:Target>
<ns1:FirstName>MyFirstName</ns1:FirstName>
<ns1:LastName>MyLastName</ns1:LastName>
</ns1:RunLocatorResult>
</ns1:RunLocatorResponse>

</soapenv:Body>
</soapenv:Envelope> 

And this XML is from IE

 -- IE 

<?xml version="1.0"?>
<soapenv:Envelope 
xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" 
xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">

    <soapenv:Body>

        <ns1:RunLocatorResponse xmlns="urn:RunLocator" 
        xmlns:ns1="http://tempuri.org/AWEService/API/WASAPI" 
        xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">

            <ns1:RunLocatorResult>
                <ns1:Target>Administrator</ns1:Target>
                <ns1:FirstName>Administrator</ns1:FirstName>
                <ns1:LastName>Administrator</ns1:LastName>
            </ns1:RunLocatorResult>
            <ns1:RunLocatorResult>
                <ns1:Target>MyFirstName.MyLastName</ns1:Target>
                <ns1:FirstName>MyFirstName</ns1:FirstName>
                <ns1:LastName>MyLastName</ns1:LastName>
            </ns1:RunLocatorResult>
        </ns1:RunLocatorResponse>
    </soapenv:Body>
</soapenv:Envelope>

And this is the file in question:

/*  File:   SOAPAPI.js
Functions that encapsulate a portal SOAP functions
*/
//-------------------------------------------------------------------------
// Global variables specifically for device placement
//-------------------------------------------------------------------------

// Variables used to communicate with the server via SOAP
var objXmlDoc;
var objXmlDomParser;
var objHttp;
var bDocument = false;
var bHttp = false;
var ghttpAsync = true;

/*
// http://richardpianka.com/2011/01/cross-browser-xpath/
if (document.implementation.hasFeature("XPath", "3.0")) {
if (typeof XMLDocument == "undefined") { XMLDocument = Document; }

XMLDocument.prototype.selectNodes = function(cXPathString, xNode) {
if (!xNode) { xNode = this; }
var oNSResolver = this.createNSResolver(this.documentElement)
var aItems = this.evaluate(cXPathString, xNode, oNSResolver, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null)
var aResult = [];
for (var i = 0; i < aItems.snapshotLength; i++) { aResult[i] = aItems.snapshotItem(i); }
return aResult;
}

XMLDocument.prototype.selectSingleNode = function(cXPathString, xNode) {
if (!xNode) { xNode = this; }
var xItems = this.selectNodes(cXPathString, xNode);
if (xItems.length > 0) { return xItems[0]; }
else { return null; }
}

Element.prototype.selectNodes = function(cXPathString) {
if (this.ownerDocument.selectNodes) { return this.ownerDocument.selectNodes(cXPathString, this); }
else { throw "For XML Elements Only"; }
}

Element.prototype.selectSingleNode = function(cXPathString) {
if (this.ownerDocument.selectSingleNode) { return this.ownerDocument.selectSingleNode(cXPathString, this); }
else { throw "For XML Elements Only"; }
}


}

*/


var isIE = /**@preserve/*@cc_on!@*/false;

if (!isIE) {

    // http://doogalbellend.blogspot.ch/2012/04/cross-browser-selectsinglenode-for-xml.html
    //function SelectSingleNode(xmlDoc,
    XMLDocument.prototype.selectSingleNode = function(elementPath) 
    {
        var xmlDoc = this;
        if (xmlDoc.evaluate) 
        {
            var nodes = xmlDoc.evaluate(elementPath, xmlDoc, null, XPathResult.ANY_TYPE, null);
            var results = nodes.iterateNext();
            return results;
        }
        else
            return xmlDoc.selectSingleNode(elementPath);
    }

} // End if (!isIE) {




//-------------------------------------------------------------------------
// Create a generally used DOM document object used to process the result sets
//-------------------------------------------------------------------------
function CreateDOMDocument() {
    if (bDocument != false)
        return bDocument;

    if (window.DOMParser) {
        objXmlDomParser = new DOMParser();
        bDocument = true;
        // objXmlDoc = objXmlDoc.parseFromString(txt, "text/xml");
    }
    else // Internet Explorer
    {
        objXmlDoc = new ActiveXObject("Microsoft.XMLDOM");
        objXmlDoc.async = false;
        bDocument = true;
    }

    return bDocument;
}



function CreateDOMDocument_old() {
    if (bDocument == false) {
        // Create the XML Document object

        var aszDocumentProgIDs = ["MSXML2.DOMDocument.4.0",
                               "MSXML2.DOMDocument.3.0",
                               "MSXML2.DOMDocument",
                               "MSXML.DOMDocument",
                               "Microsoft.XmlDom"];

        for (var i = 0; !bDocument && i < aszDocumentProgIDs.length; i++) {
            try {
                objXmlDoc = new ActiveXObject(aszDocumentProgIDs[i]);
                bDocument = true;
            } catch (objException) {
                // error handling elided for clarity
            }
        }
    }
    return bDocument;
}








function CreateHTTPSocket() {
    // http://stackoverflow.com/questions/2557247/easiest-way-to-retrieve-cross-browser-xmlhttprequest
    if (bHttp != false)
        return bHttp;

    var XMLHttpFactories = [
        function() { return new XMLHttpRequest() },
        function() { return new ActiveXObject("Msxml3.XMLHTTP") },
        function() { return new ActiveXObject("Msxml2.XMLHTTP.4.0") },
        function() { return new ActiveXObject("Msxml2.XMLHTTP.3.0") },
        function() { return new ActiveXObject("Msxml2.XMLHTTP") },
        function() { return new ActiveXObject("Microsoft.XMLHTTP") }
    ];


    for (var i = 0; i < XMLHttpFactories.length; i++) {
        try {
            objHttp = XMLHttpFactories[i]();
            bHttp = true;
        }
        catch (e) {
            continue;
        }
        break;
    }

    return bHttp;
}



//-------------------------------------------------------------------------
// Create a generally used HTTP socket object used to communicate with WAS
//-------------------------------------------------------------------------
function CreateHTTPSocket_old() {
    if (bHttp == false) {
        // Create the XML HTTP object

        var aszHttpProgIDs = ["MSXML2.XMLHTTP.4.0",
                           "MSXML2.XMLHTTP.3.0",
                           "MSXML2.XMLHTTP",
                           "Microsoft.XMLHTTP"];

        var aszHttpAsyncFlags = [true,
                  true,
                  false,
                  false];

        for (var i = 2; !bHttp && i < aszHttpProgIDs.length; i++) {
            try {
                objHttp = new ActiveXObject(aszHttpProgIDs[i]);
                bHttp = true;
                ghttpAsync = aszHttpAsyncFlags[i];
            } catch (objException) {
                // error handling elided for clarity
            }
        }
    }

    return bHttp;
}

//-------------------------------------------------------------------------
// Insert the specified search parameters into a SOAP parameter block.  The
// block types currently supported are for locate, create, change, or delete  
// functions
//-------------------------------------------------------------------------
function SOAPEnvelopeEx(action, formIDParms, formFieldsParms, allowNulls) {
    var strEnvelope;
    var actionVerb;
    var parms;
    var allowNullsParm = '';

    if ((typeof allowNulls == 'boolean') && (allowNulls == true)) allowNullsParm = ' AllowNulls="yes"';

    switch (action) {
        case 'Locate':
            actionVerb = 'RunLocator';
            parms = "<locatorSearchFieldsIdentification>" +
        formIDParms +
           "</locatorSearchFieldsIdentification>";
            break;
        case 'CreateNewInstance':
            actionVerb = 'CreateNewInstance';
            parms = "<formIdenfication>" +
        formIDParms +
            "</formIdenfication>" +
        "<formFieldsIdentification>" +
        formFieldsParms +
        "</formFieldsIdentification>";
            break;
        case 'ChangeInstance':
            actionVerb = 'ChangeInstance';
            parms = "<formFieldsIdentification>" +
        formIDParms +
        "</formFieldsIdentification>";
            break;
        case 'DeleteInstance':
            actionVerb = 'DeleteInstance';
            parms = "<formFieldsIdentification>" +
        formIDParms +
        "</formFieldsIdentification>";
            break;
        case 'SetInstanceProperty':
            actionVerb = 'SetInstanceProperty';
            parms = "<formIDIdentification>" +
          formIDParms +
        "</formIDIdentification>" +
        "<instancePropertyIdentification>" +
          formFieldsParms +
        "</instancePropertyIdentification>";
            break;
        default:
            alert("Unrecognized parameter in formSoapEnvelope");
    }

    strEnvelope = "<soap:Envelope xmlns:soap='http://schemas.xmlsoap.org/soap/envelope/' " +
        "xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' " +
        "xmlns:xsd='http://www.w3.org/2001/XMLSchema'>" +
        "<soap:Body><" + actionVerb + " xmlns='http://tempuri.org/AWEService/API/WASAPI'" + allowNullsParm + ">" +
        "<userIdentification><WAS_userName>" + g_SOAPUSERID + "</WAS_userName>" +
                       "<WAS_session>" + g_SOAPTransID + "</WAS_session>" +
        "</userIdentification>" +
        parms +
        "</" + actionVerb + "></soap:Body> </soap:Envelope>";

    return strEnvelope;
}


//-------------------------------------------------------------------------
// Use an HTTP socket to pass the SOAP information to WAS.  Identify the 
// routine that will be driven when the result arrives.  Supports locate,
// create, change, or delete functions.
//-------------------------------------------------------------------------
function CallSOAPEx(action, handler, formName, strEnvelope) {
    // Must be using IE for this to work
    // if (!window.ActiveXObject) return;
    if (!CreateDOMDocument()) return;
    if (!CreateHTTPSocket()) return;

    var functionURL = RemoveSSFromURLPrefix(g_SOAPURL) + "/API/AWEService.asmx?w_v=1&w_s=" + g_SUITE_ID;

    var actionVerb;
    var parms;
    switch (action) {
        case 'Locate':
            actionVerb = 'RunLocator';
            parms = functionURL + "&w_o=" + formName;
            break;
        case 'CreateNewInstance':
            actionVerb = 'CreateNewInstance';
            parms = functionURL + "&w_ifn=" + formName;
            break;
        case 'ChangeInstance':
            actionVerb = 'ChangeInstance';
            parms = functionURL + "&w_o=" + formName;
            break;
        case 'DeleteInstance':
            actionVerb = 'DeleteInstance';
            parms = functionURL + "&w_o=" + formName;
            break;
        case 'SetInstanceProperty':
            actionVerb = 'SetInstanceProperty';
            parms = functionURL + "&w_o=" + formName;
            break;
        default:
            alert("Unrecognized parameter in formCallSoap");
    }

    // Set up the post
    objHttp.onreadystatechange = handler;
    objHttp.open("post", parms, ghttpAsync);

    // http://blogs.msdn.com/b/ie/archive/2012/07/19/xmlhttprequest-responsexml-in-ie10-release-preview.aspx
    // !! Important !! 
    try { objHttp.responseType = 'msxml-document'; } catch (e) { }

    objHttp.setRequestHeader("Content-Type", "text/xml; charset=utf-8");
    objHttp.setRequestHeader("SOAPAction", "http://tempuri.org/AWEService/API/WASAPI/" + actionVerb);
    objHttp.send(strEnvelope);
    if (!ghttpAsync)
        handler();
}

function RemoveSSFromURLPrefix(urlPrefix) {
    if (urlPrefix != null) {
        var i = urlPrefix.toUpperCase().lastIndexOf("/SS");
        if (i > 1) {
            return urlPrefix.substring(0, i);
        }
    }
    return urlPrefix;
}

//-------------------------------------------------------------------------
// Find the multi-instance form Response within the response XML
//-------------------------------------------------------------------------
// also see: https://www9.cor-asp.ch/Wincasa_Portal/menu_com.js
function GetSOAPResponseEx(action) {
    if (objHttp.readyState != 4) return null;

    // Get the return envelope
    var szResponse = objHttp.responseText;
    // console.log(szResponse);

    if (window.DOMParser) {
        // console.log(objXmlDoc);
        objXmlDoc = objHttp.responseXML; // Because responseType = 'msxml-document'

        try {
            objXmlDoc.setProperty("SelectionNamespaces", 'xmlns:ns1="http://tempuri.org/AWEService/API/WASAPI"');
            // console.log(objXmlDoc.xml);
        }
        catch (e) {
            // console.log("DomParserException");
            // console.log(typeof (objXmlDoc));
            // console.log(objXmlDoc.selectSingleNode);
            objXmlDoc = objXmlDomParser.parseFromString(szResponse, "text/xml");
            objXmlDoc.documentElement.setAttribute("xmlns:ns1", "http://tempuri.org/AWEService/API/WASAPI");

            // console.log((new XMLSerializer()).serializeToString(objXmlDoc));
            // $(xmlDOM.documentElement).attr("xmlns:xsd", "http://www.w3.org/2001/XMLSchema");
        }

    }
    else 
    {
        objXmlDoc.loadXML(szResponse);
        objXmlDoc.setProperty("SelectionNamespaces", 'xmlns:ns1="http://tempuri.org/AWEService/API/WASAPI"');
    }

    // At this point, the 2 xml's have been logged

    /*
    console.log("foo");

    // Here I get: 
    // Uncaught NamespaceError: Failed to execute 'evaluate' on 'Document': 
    // The string '//ns1:RunLocatorResponse' contains unresolvable namespaces.         SOAPAPI.js:64

XMLDocument.selectSingleNode

    objXmlDoc.selectSingleNode('//ns1:RunLocatorResponse');
    console.log("bar");
    */

    switch (action) {
        case 'Locate':
            return objXmlDoc.selectSingleNode('//ns1:RunLocatorResponse');
        case 'CreateNewInstance':
            return objXmlDoc.selectSingleNode('//ns1:CreateNewInstanceResponse');
        case 'ChangeInstance':
            return objXmlDoc.selectSingleNode('//ns1:ChangeInstanceResponse');
        case 'DeleteInstance':
            return objXmlDoc.selectSingleNode('//ns1:DeleteInstanceResponse');
        case 'SetInstanceProperty':
            return objXmlDoc.selectSingleNode('//ns1:SetInstancePropertyResponse');
        default:
            alert("Unrecognized parameter in GetSOAPResponseEx");
    }

}
Stefan Steiger
  • 78,642
  • 66
  • 377
  • 442

1 Answers1

2

As the error message Failed to execute 'evaluate' on 'Document' indicates, the problem occurs with the evaluate method of the DOM document object you or your library uses to select nodes using XPath. To use a path expression with a prefix like //ns1:RunLocatorResponse you need to make sure the evaluate methods knows how to resolve that prefix by doing e.g.

xmldoc.evaluate(
    '//ns1:RunLocatorResponse',
    xmldoc,
    function (prefix) {
        if (prefix === 'ns1')
            return 'http://tempuri.org/AWEService/API/WASAPI';
        else
            return null;
    },
    XPathResult.ANY_TYPE,
    null
);
Paul S.
  • 64,864
  • 9
  • 122
  • 138
Martin Honnen
  • 160,499
  • 6
  • 90
  • 110
  • Don't forget about [`document.createNSResolver(node)`](https://developer.mozilla.org/en-US/docs/Web/API/document.createNSResolver) which might be helpful to generate the resolver without hardcoding – Paul S. Apr 16 '14 at 13:17
  • @Paul S: Shomehow, createNSResolver(node) doesn't work. – Stefan Steiger Apr 23 '14 at 09:28
  • @Martin Honnen: Note: The resolve function should actually be a object with property lookupNamespaceURI. { lookupNamespaceURI: function(prefix)... } – Stefan Steiger Aug 14 '18 at 08:16
  • @StefanSteiger, when I learned that stuff https://www.w3.org/TR/2004/NOTE-DOM-Level-3-XPath-20040226/DOM3-XPath.html#ecma-script-binding was relevant and it clearly says "The parameter resolver of the method XPathEvaluator.evaluate is specified as an object that implements the XPathNSResolver interface. ECMAScript users can also pass to this method a function which returns a String and takes a String parameter instead of the resolver parameter". – Martin Honnen Aug 14 '18 at 08:56
  • I have not checked whether that no longer holds but given that all the attempts around HTML5 and DOM4 are usually aiming to make interfaces easier to use from Javascript I kind of doubt that the simplification to allow a function object has been withdrawn. – Martin Honnen Aug 14 '18 at 08:57
  • @Martin Honnen: In that case, it's a bug in the typescript type specification.https://github.com/Microsoft/TypeScript/issues/26437 – Stefan Steiger Aug 14 '18 at 10:39