James Dunn
James Dunn

Reputation: 8274

XPath in SoapUI Groovy Script not working as expected

I am attempting to extract values out of an XML response in SoapUI, using XmlHolder in a groovy script. But the xpath is not working the way I expect it to.

This is the response that I'm querying:

<OTA_HotelRatePlanNotifRQ MessageContentCode="8" TimeStamp="2016-03-02T21:08:10.912Z" Version="6" CorrelationID="aut0mat3-th15-g00d-n1c3-ch01c37udsvb" xmlns="http://www.opentravel.org/OTA/2003/05">
<POS>
    <Source>
        <RequestorID Type="10" ID="ACME"/>
        <BookingChannel Type="4">
            <CompanyName Code="ACME"/>
        </BookingChannel>
    </Source>
</POS>
<UniqueID Type="10" ID_Context="HI123" ID="HI123"/>
<RatePlans ChainCode="EC" HotelCode="HI123">
    <RatePlan RatePlanNotifType="Delta" RatePlanNotifScopeType="RateOnly" CurrencyCode="USD" RatePlanCode="COOL">
        <Rates>
            <Rate InvCode="QQQ" InvTypeCode="QQQ" Mon="false" Tue="false" Weds="false" Thur="false" Fri="true" Sat="true" Sun="true" Start="2016-03-03" End="2016-04-02">
                <BaseByGuestAmts>
                    <BaseByGuestAmt NumberOfGuests="1" AgeQualifyingCode="10" AmountBeforeTax="1099.0"/>
                    <BaseByGuestAmt NumberOfGuests="2" AgeQualifyingCode="10" AmountBeforeTax="1199.0"/>
                </BaseByGuestAmts>
            </Rate>
            <Rate InvCode="QQQ" InvTypeCode="QQQ" Mon="true" Tue="true" Weds="true" Thur="true" Fri="false" Sat="false" Sun="false" Start="2016-03-03" End="2016-04-02">
                <BaseByGuestAmts>
                    <BaseByGuestAmt NumberOfGuests="1" AgeQualifyingCode="10" AmountBeforeTax="899.0"/>
                    <BaseByGuestAmt NumberOfGuests="2" AgeQualifyingCode="10" AmountBeforeTax="999.0"/>
                </BaseByGuestAmts>
            </Rate>
        </Rates>
    </RatePlan>
</RatePlans>

This is the code in my Groovy Script:

import com.eviware.soapui.support.XmlHolder;

XmlHolder holder = new XmlHolder(context.getProperty("response"))

// These work as expected
log.info(holder.getNodeValue("//@MessageContentCode"));  // 8
log.info(holder.getNodeValue("//@Version"));  // 6
log.info(holder.getNodeValue("//@Type"));  // 10
log.info(holder.getNodeValue("//@ID"));  // ACME
log.info(holder.getNodeValue("//@Code"));  // ACME

// These do NOT work as expected.  Instead they log null.
log.info(holder.getNodeValue("/OTA_HotelRatePlanNotifRQ/@Version")); // null
log.info(holder.getNodeValue("/OTA_HotelRatePlanNotifRQ/POS/Source/RequestorID/@Type"));  // null
//etc

Am I doing something wrong here?

Upvotes: 1

Views: 2995

Answers (2)

Rao
Rao

Reputation: 21379

That is because of the default namespace being used in the response, like you anticipated.

However, it needs to be handled differently than you mentioned in the answer.

And this applies to the both below conditions

  1. default namespace
  2. namespace with prefix

After defining the object for the XmlHolder, have to set all the namespaces that are required to query the data. And below statement does the same and putting the complete script has well.

How to define / set the namespaces

In your response, there is only one namespace used i.e., http://www.opentravel.org/OTA/2003/05.

Set namespaces map, you are free to define your own prefix here, need to not to worry about what prefix does it have in original response (considering above mentioned 2 nd case) and use this prefix('ns') while getting data using xpath.

holder.declareNamespace('ns',"http://www.opentravel.org/OTA/2003/05")

Groovy Script

import com.eviware.soapui.support.XmlHolder

def xml = '''<OTA_HotelRatePlanNotifRQ MessageContentCode="8" TimeStamp="2016-03-02T21:08:10.912Z" Version="6" CorrelationID="aut0mat3-th15-g00d-n1c3-ch01c37udsvb" xmlns="http://www.opentravel.org/OTA/2003/05">
<POS>
    <Source>
        <RequestorID Type="10" ID="ACME"/>
        <BookingChannel Type="4">
            <CompanyName Code="ACME"/>
        </BookingChannel>
    </Source>
</POS>
<UniqueID Type="10" ID_Context="HI123" ID="HI123"/>
<RatePlans ChainCode="EC" HotelCode="HI123">
    <RatePlan RatePlanNotifType="Delta" RatePlanNotifScopeType="RateOnly" CurrencyCode="USD" RatePlanCode="COOL">
        <Rates>
            <Rate InvCode="QQQ" InvTypeCode="QQQ" Mon="false" Tue="false" Weds="false" Thur="false" Fri="true" Sat="true" Sun="true" Start="2016-03-03" End="2016-04-02">
                <BaseByGuestAmts>
                    <BaseByGuestAmt NumberOfGuests="1" AgeQualifyingCode="10" AmountBeforeTax="1099.0"/>
                    <BaseByGuestAmt NumberOfGuests="2" AgeQualifyingCode="10" AmountBeforeTax="1199.0"/>
                </BaseByGuestAmts>
            </Rate>
            <Rate InvCode="QQQ" InvTypeCode="QQQ" Mon="true" Tue="true" Weds="true" Thur="true" Fri="false" Sat="false" Sun="false" Start="2016-03-03" End="2016-04-02">
                <BaseByGuestAmts>
                    <BaseByGuestAmt NumberOfGuests="1" AgeQualifyingCode="10" AmountBeforeTax="899.0"/>
                    <BaseByGuestAmt NumberOfGuests="2" AgeQualifyingCode="10" AmountBeforeTax="999.0"/>
                </BaseByGuestAmts>
            </Rate>
        </Rates>
    </RatePlan>
</RatePlans>
</OTA_HotelRatePlanNotifRQ>'''

def holder = new XmlHolder(xml)
//set the namespace, add more where there are more namespaces
holder.declareNamespace('ns',"http://www.opentravel.org/OTA/2003/05")

//and use the above defined prefix while doing xpath to get the data
log.info holder.getNodeValue('//ns:OTA_HotelRatePlanNotifRQ/@Version')
log.info(holder.getNodeValue("//ns:OTA_HotelRatePlanNotifRQ/ns:POS/ns:Source/ns:RequestorID/@Type"))

May be you can find more information on namespaces here if interested.

Below Xml with Multiple namcespaces including default.
Taken this sample from above link. Adding the below details because if it can clear and helps understanding a bit more on the namespace aware.

<Company xmlns="http://www.company.org" xmlns:pro="http://www.product.org" xmlns:per="http://www.person.org" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.company.org Company.xsd">
        <Person>
                <per:Name>John Doe</per:Name>
                <per:SSN>123-45-6789</per:SSN>
        </Person>
        <Product>
                <pro:Type>Widget</pro:Type>
        </Product>
</Company>

If you notice above:

  1. Company, Product, Person are in the same namespace and also using default namespace.
  2. The elements of Person are using different namespace, also with prefix per
  3. The elements of Product are using another namespace, with pro fix.

In the below script example, going to define namespaces with different prefix or same prefix(just to illustrate that user can freely use different prefix while doing xpath)

Groovy Script With Multiple Namespaces

import com.eviware.soapui.support.XmlHolder
def xml = '''
<Company xmlns="http://www.company.org" xmlns:pro="http://www.product.org" xmlns:per="http://www.person.org" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.company.org Company.xsd">
        <Person>
                <per:Name>John Doe</per:Name>
                <per:SSN>123-45-6789</per:SSN>
        </Person>
        <Product>
                <pro:Type>Widget</pro:Type>
        </Product>
</Company>'''

def holder = new XmlHolder(xml)
//declaring multiple namespaces on the holder object
holder.declareNamespace('com',"http://www.company.org")
holder.declareNamespace('prod',"http://www.product.org")
holder.declareNamespace('per',"http://www.person.org")

//below snippets shows using different namespaces prefixes while doing xpath
//which may or may not be the same as defined prefixes in actualxml
//per - prefix is same as in xml and rest are different prefixes
assert 'John Doe'== holder.getNodeValue('//com:Person/per:Name'), "Person Name is not matching"
assert '123-45-6789' == holder.getNodeValue('//com:Person/per:SSN'), "Person SSN is not matching"
assert 'Widget' == holder.getNodeValue('//com:Product/prod:Type'), "Product Type is not matching"

Upvotes: 2

James Dunn
James Dunn

Reputation: 8274

It's the xmlns attribute in the namespace.

<OTAHotelRatePlanNotifRQ ... @xmlns="http://www.opentravel.org/OTA/2003/05" ... >

Apparently, if it has any value (other than an empty string), it breaks a lot of XmlHolder's xpath functionality.

Removing the xmlns attribute from the message causes the xpaths to work as normal.

This can be done in Groovy like so:

response = response.replaceAll('xmlns="[^"]*"', "");

Upvotes: 0

Related Questions