DataPower

DataPower

Join this online group to communicate across IBM product users and experts by sharing advice and best practices with peers and staying up to date regarding product enhancements.

 View Only
Expand all | Collapse all

Gatewayscript variable in XPath

  • 1.  Gatewayscript variable in XPath

    Posted Thu December 08, 2022 05:19 AM
      |   view attached
    Hi All,

    We are trying to read a value from xml file using XPath through Gatewayscript, when the attribute type value checking with Gatewayscript variable value.
    XML file:
    <source>
    <route type="xml">
    <Validation> Required </Validation>
    </route>
    <route type="json">
    <Validation> Not Required </Validation>
    </route>
    </source>
    In XSLT we used this XPath: /source/route[@type=$gatewayscriptvariable]/Validation/text()
    If we hardcoded the value of type attribute it works as expected: /source/route[@type='xml']/Validation/text()

    Please check attached Gatewayscript code and share your valuable thoughts here.



    ------------------------------
    Anusha Pudari
    ------------------------------

    Attachment(s)

    js
    XPathHandling.js   308 B 1 version


  • 2.  RE: Gatewayscript variable in XPath

    Posted Thu December 08, 2022 03:15 PM
    I'm assuming you took out way too much from the attachment.  Otherwise, you have a long way to go.   For example, you're not including the transform module.  You're not using the 'XMLFileName' variable.   You have to load the contents of the XMLFileName into a variable using something like XML.parse(...).

    For your variable 'a' where the XPath is defined, you can concatenate strings together to replace the hard-coded "originalcontent" with the value of the 'originalContent' variable.  But GatewayScript isn't like XSLT, where you can often use an XSLT variable within an expression.

    And, for everyone's sake, you might want to remove the complexity of "closures" from the script.  You can write it like this:

    transform.xpath(a, xml, transformResponse);
    
    // =============================================
    // The function receiving the transform response
    // =============================================
    function transformResponse(error, response) 
    {
        if (error) 
        {
             session.output.write("error at xpath");
        }
        else 
        {
             session.output.write(response);
        }
    }
    ​

    Why is that better?  Because, consider transforming 3, 4 or 5 documents, you can use the same function, rather than all the anonymous functions, and make your code about a million times easier to write, understand, and, most importantly, maintain:

    transform.xpath(a, xml1, transformResponse);
    transform.xpath(b, xml2, transformResponse);
    transform.xpath(c, xml3, transformResponse);
    transform.xpath(d, xml4, transformResponse);
    
    // =============================================
    // The function receiving the transform response
    // =============================================
    function transformResponse(error, response) 
    {
        if (error) 
        {
             session.output.write("error at xpath");
        }
        else 
        {
             session.output.write(response);
        }
    }
    ​


    ------------------------------
    Joseph Morgan
    ------------------------------



  • 3.  RE: Gatewayscript variable in XPath
    Best Answer

    Posted Thu December 08, 2022 04:55 PM
    Edited by Steve Linn Mon December 12, 2022 03:25 PM

    Hi Anusha,

    In your attached source

    var XMLFileName = "local:///xmlrequest.xml";
    var originalcontent= 'xml';
    var a="/source/route[@type=originalcontent]/Validation/text()";

    your XPath is variable a is a string that has @type=originalcontent. I believe you want to have the variable value of originalcontent, ie, xml.  You can do this in JavaScript with either concatenation or the use of template literals.  For example

    var a="/source/route[@type='" + originalcontent +"']/Validation/text()"; // string concatenation
    var a=`/source/route[@type='${originalcontent}']/Validation/text()`; // template literal

    both will accomplish the same thing, setting your variable a to the desired XPath /source/route[@type='xml']/Validation/text()

    Note as well that in the predicate, the value being checked is a single quoted string .

    Best Regards,
    Steve



    ------------------------------
    Steve Linn
    Senior Consulting I/T Specialist
    IBM
    ------------------------------



  • 4.  RE: Gatewayscript variable in XPath

    Posted Mon May 15, 2023 09:41 AM

    Hi Steve...

    I also have a similar requirement in the API Connect Gatewayscript where I have an account number lets say, '333333333' and the below XML response to check whether '333333333' is present. Please advise how xpath can be used to compare the XML array value against the static value (without looping through individual complex types in XML) and return a boolean true or false.

    <AccountListPermissionResponse>
        <AccountListPermission>
            <AccountListPermission_REC>
                <ACC_LIST_ARRAY>1234567890</ACC_LIST_ARRAY>
            </AccountListPermission_REC>
            <AccountListPermission_REC>
                <ACC_LIST_ARRAY>9999999999</ACC_LIST_ARRAY>
            </AccountListPermission_REC>
            <AccountListPermission_REC>
                <ACC_LIST_ARRAY>333333333</ACC_LIST_ARRAY>
            </AccountListPermission_REC>
            <AccountListPermission_REC>
                <ACC_LIST_ARRAY>444444444</ACC_LIST_ARRAY>
            </AccountListPermission_REC>
            <AccountListPermission_REC>
                <ACC_LIST_ARRAY>136890345</ACC_LIST_ARRAY>
            </AccountListPermission_REC>
            <AccountListPermission_REC>
                <ACC_LIST_ARRAY>657535890</ACC_LIST_ARRAY>
            </AccountListPermission_REC>
        </AccountListPermission>
    </AccountListPermissionResponse>



    ------------------------------
    Ashok Beshra
    ------------------------------



  • 5.  RE: Gatewayscript variable in XPath

    Posted Mon May 15, 2023 10:12 AM

    Isn't this as easy as "//ACC_LIST_ARRAY[text()='333333333']"



    ------------------------------
    Joseph Morgan
    ------------------------------



  • 6.  RE: Gatewayscript variable in XPath

    Posted Tue May 16, 2023 05:03 AM

    Hi Joseph...

    I tried the above and it is not working in API Connect Gateway script. This simply prints the above text and not retrieving the value.



    ------------------------------
    Ashok Beshra
    ------------------------------



  • 7.  RE: Gatewayscript variable in XPath

    Posted Tue May 16, 2023 07:31 AM

    I'm not sure the exact details of what is happening in your runtime or your Gateway script, but, what I gave you above is a sample XPATH for evaluating if a particular value exists within the XML you described.  At least, that's what I think you are asking for.

    With Gateway Script, if your XML is in a string, you'll need to use the XML.parse() function to get it into a proper document, and then use the Transform module "xpath()" function to evaluate the XPATH (or the proper variant of it). 

    So, using both my and Steve's examples above, I suspect you'll create an XPATH variable 'a' like in Steve's suggestion, then use the transform function as in my example to evaluate it (after converting any XML into a proper document using XML.parse(), of course).



    ------------------------------
    Joseph Morgan
    ------------------------------



  • 8.  RE: Gatewayscript variable in XPath

    Posted Wed May 17, 2023 09:44 AM
      |   view attached

    Hi Joseph....

    Thanks for your reply. I followed the above approach and able to do a proper xpath extracting in API Connect Gatewayscript(Code attached). In the runtime, our backend will return multiple accounts and we wanted to check whether the account number sent by API consumer is present in the list of accounts returned by Backend. We have put a for loop to iterate over the array and find a match. This is taking some time and we wanted to write optimized code to handle it better. Please suggest if there are any other alternative approaches to do this quickly. Thanks

    Loop portion of the code. I have attached the full code as well for your reference.
    transform.xpath(xpathOption, function(error, acctPrmsnNodeList) {
                if (error) {
                    console.error('error while executing  xpath - ' + error);
                    throw error;
                } else {
                    acctPrmsn = acctPrmsnNodeList; // authorNodeList is DOM NodeList structure
                    console.error('Account Premission List length ' + acctPrmsn.length);
                    var flag = 'False';
                    for (var c=0; c<acctList.length; c++) {
                        //result += acctList.item(c).textContent + ', by ' + acctPrmsn.item(c).textContent + '\n';
                        if (acctList.item(c).textContent == 'XXXXXXXXXXXXXXX' && acctPrmsn.item(c).textContent == 'I') {
                                flag = 'True';
                                console.error('Allowed Account ' + acctList.item(c).textContent + 'Permission ' + acctPrmsn.item(c).textContent);
                                
                        } 
                    }
                    if(flag == 'False'){
                                 console.error('Account Permission Error');
                                 context.reject("GWSError","Account doesnt have Permission");
                                 context.message.statusCode = "500";
                    }
                    
                }
            



    ------------------------------
    Ashok Beshra
    ------------------------------

    Attachment(s)



  • 9.  RE: Gatewayscript variable in XPath

    Posted Fri May 19, 2023 01:02 PM

    Consider changing this section:

    var rootOptions = {
    	expression: '/soapenv:Envelope/soapenv:Body/xmlns:CustomGetAccountListPermissionResponse/xmlns:CustomGetAccountListPermission/xmlns:CustomGetAccountListPermission_REC/xmlns:ACC_LIST_ARRAY/text()',
        xmldom: domTree,  // your XML here
        namespace: { 'soapenv': 'http://schemas.xmlsoap.org/soap/envelope/',
    				 'xmlns' :'http://www.infosys.com/response/CustomGetAccountListPermission',
    				 'xsi' : 'http://www.w3.org/2001/XMLSchema-instance',
    				 'hd' : 'http://www.infosys.com/response/header',
    				 'ft' : 'http://www.infosys.com/response/footer'
                   }
    };
    
    


    with:

    var rootOptions = {
    	expression: '//ACC_LIST_ARRAY[text()=\\'XXXXXXXXXXXXXXX\\']',
        xmldom: domTree,  // your XML here
        namespace: { 'soapenv': 'http://schemas.xmlsoap.org/soap/envelope/',
    				 'xmlns' :'http://www.infosys.com/response/CustomGetAccountListPermission',
    				 'xsi' : 'http://www.w3.org/2001/XMLSchema-instance',
    				 'hd' : 'http://www.infosys.com/response/header',
    				 'ft' : 'http://www.infosys.com/response/footer'
                   }
    };
    
    


    And then change the code from looping to just a simple verification of existence.  That is, the XPATH response will return a nodeset with something in it or nothing in it.



    ------------------------------
    Joseph Morgan
    ------------------------------



  • 10.  RE: Gatewayscript variable in XPath

    Posted Mon May 22, 2023 03:16 PM

    Hi Ashok and Joseph,

    A lot of conversation while I was on vacation :-) Just a few comments:

    //ACC_LIST_ARRAY[text()='333333333']

    Just a FYI, the // XPath operator will inspect every element in document to see if there is a child element named ACC_LISTG_ARRAY with the desired value, so for performance, it is a best practice to specify the entire XPath instead of using the // short hand.

    /AccountListPermissionResponse/AccountListPermission/AccountListPermission_REC/ACC_LIST_ARRAY[text()='333333333']

    With this particular XML it probably isn't a big deal, but with larger and more complex XML it could make a difference.

    Now seeing your full XML, I see that your soap root body element has a default namespace which is then inherited by all of its children elements.  However, I ran your attached .js and it works, still, you're doing two xpaths to separately get the three ACC_LIST_ARRAY elements and the sibling elements ACC_PRMSN_ARRAY.  Since you're looking to see if have the former with an XXXX value and the latter sibling element with a value of I, why not simply combine these into one xpath.  If you get a result, you approve, if not, you reject?

    expression: `/soapenv:Envelope/soapenv:Body/xmlns:CustomGetAccountListPermissionResponse/xmlns:CustomGetAccountListPermission/xmlns:CustomGetAccountListPermission_REC/[xmlns:ACC_LIST_ARRAY/text()= 'XXXXXXXXXXXXXXX' and xmlns:ACC_PRMSN_ARRAY/text() = 'I']`,

    Note I made the expression value a string template so I didn't have to escape the single quotes in the XPath, but this will return all CustomGetAccountListPermission_REC elements whose child elements are the desired value.  If you get an empty nodelist, you fail the test and indicate you have a permission error.  The XPath is doing the iteration for you and really simplifies your code.

    Regards,

    Steve



    ------------------------------
    Steve Linn
    Senior Consulting I/T Specialist
    IBM
    ------------------------------



  • 11.  RE: Gatewayscript variable in XPath

    Posted Mon May 22, 2023 05:38 PM

    And a final observation using the GatewayScript debugger on your code.  The reason the current code is rejecting the request is because the data has three less XXX that the if statement is looking for, so it will not match and will fail.  I fixed that and it passes, although a much simpler XPath could handle this in one transform.xpath.

    Regards,
    Steve



    ------------------------------
    Steve Linn
    Senior Consulting I/T Specialist
    IBM
    ------------------------------



  • 12.  RE: Gatewayscript variable in XPath

    Posted Thu December 08, 2022 05:18 PM

    Just one caveat to Joseph's  post.  The transform.xslt function is asynchronous, thus the 4 transform.xpath functions as shown will run in parallel, each one doing its own session.output.write which I believe will overwrite each other with the last one being the result of the script.  Typically, you would want to use the results of the first xPath get to a nodelist response that would then be the subject of another xPath, but that won't happen in this case.  Another use case would be to save all of the results from each XPath and aggregate the results.  The solution to making these asynchronous calls "synchronous" is one of three ways

    1. A lot of nesting within the async function, which is hard to read depending upon how deep you get.  I agree with  Joseph I don't like that approach, especially if the nesting is greater than 2 deep.
    2. The use of callback functions, which is a very common practice
    3. Use promises to make the code appear to be more synchronous as they will control the waiting for the asynchronous call to finish.

    I don't have any promises examples handy, so I'll provide a callback example that I cobbled together using Joseph's example where you might have something like the following:

    var responseArray = [];
    transform.xpath(a, xml1, transformResponse1);
    
    // =============================================
    // The function receiving the transform response
    // =============================================
    function transformResponse1(error, response) 
    {
        if (error) 
        {
             session.output.write("error at xpath1");
        }
        else 
        {
             // successful xpath, save the response and do the next one
             responseArray.push(response);
             transform.xpath(b, xml2, transformResponse2);
        }
    }
    function transformResponse2(error, response) 
    {
        if (error) 
        {
             session.output.write("error at xpath2");
        }
        else 
        {
             // successful xpath, save the response and do the next one
             responseArray.push(response);
             transform.xpath(c, xml3, transformResponse3);
        }
    }
    function transformResponse3(error, response) 
    {
        if (error) 
        {
             session.output.write("error at xpath3");
        }
        else 
        {
             // successful xpath, save the response and do the next one
             responseArray.push(response);
             transform.xpath(d, xml4, transformResponse4);
        }
    }
    function transformResponse4(error, response) 
    {
        if (error) 
        {
             session.output.write("error at xpath4");
        }
        else 
        {
             // successful xpath, save the response
             responseArray.push(response);
             // all 4 XPath's successful, somehow aggregate the responses
             let aggregatedResult;
             ...
             session.output.write(aggregatedResult);
        }
    }

    Best Regards,

    Steve



    ------------------------------
    Steve Linn
    Senior Consulting I/T Specialist
    IBM
    ------------------------------



  • 13.  RE: Gatewayscript variable in XPath

    Posted Fri May 19, 2023 01:22 PM

    Not sure why one wouldn't just:

    var xmlArray = [];  // fill as needed
    var xpathArray = []; // fill as needed
    var responseArray = [];
    var messageIndex = 0;
    
    transform.xpath(xpathArray[messageIndex], xmlArray[messageIndex], transformResponse);
    
    // =============================================
    // The function receiving the transform response
    // =============================================
    function transformResponse(error, response) 
    {
        if (error) 
        {
             session.output.write("error at xpath1");
        }
        else 
        {
             // successful xpath, save the response and do the next one
             responseArray.push(response);
             
             messageIndex++;
             transform.xpath(xpathArray[messageIndex], xmlArray[messageIndex], transformResponse);
        }
    }
    
    


    ------------------------------
    Joseph Morgan
    ------------------------------



  • 14.  RE: Gatewayscript variable in XPath

    Posted Thu December 08, 2022 05:33 PM
    Yeah.  Sorry for the hacked example, I didn't check on synch/async.  Nested anonymous functions are a mess to write and maintain.

    Even in your example, I'd just create a couple more arrays and in index (var i), increment the index to them outside the if/else (unless you want to hard-fail on the first) single 'transformResponse' function's success path as you have expanded it, and simply called the transform again allow the transformResponse function to handle the response.

    So you'd have something more like:

    var responseArray = [];
    var xpathArray = [];
    var docArray = [];
    
    // Fill xpath and docArrays as needed
    
    var i = 0;
    .....
    
    function transformResponse(error, response) 
    {
        if (error) 
        {
             session.output.write("error at xpath1");
        }
        else 
        {
             // successful xpath, save the response and do the next one
             responseArray.push(response);
    
             i++;
    
             transform.xpath(xpathArray[i], docArray[i], transformResponse);
        }
    }​


    This removes the three extra identical functions.   And now we're waaay off topic...


    ------------------------------
    Joseph Morgan
    ------------------------------



  • 15.  RE: Gatewayscript variable in XPath

    Posted Fri December 09, 2022 01:32 AM
    Hi Steve and Joseph,

    Thank you for your valuable suggestions.

    Below XPath working as expected.
    var a="/source/route[@type='" + originalcontent +"']/Validation/text()"; // string concatenation


    ------------------------------
    Anusha Pudari
    ------------------------------