SoapUI Groovy 脚本中的 XPath 无法按预期工作



我正在尝试从SoapUI中的XML响应中提取值,在时髦的脚本中使用XmlHolder。 但是 xpath 并没有按照我预期的方式工作。

这是我正在查询的响应:

<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>

这是我的 Groovy 脚本中的代码:

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

我在这里做错了什么吗?

这是因为响应中使用了默认命名空间,正如您所预期的那样。

但是,它的处理方式需要与您在答案中提到的不同。

这适用于以下两种情况

  1. 默认命名空间
  2. 带前缀的命名空间

定义XmlHolder的对象后,必须设置查询数据所需的所有命名空间。下面的语句做同样的事情,把完整的脚本放得很好。

如何定义/设置命名空间

在您的响应中,只使用了一个命名空间,即 http://www.opentravel.org/OTA/2003/05 .

设置命名空间映射,您可以在此处自由定义自己的前缀,无需担心它在原始响应中具有什么前缀(考虑到上面提到的第 2 种情况),并在使用 xpath 获取数据时使用此前缀('ns')。

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

时髦的脚本

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"))

如果有兴趣,可以在此处找到有关命名空间的更多信息。

在 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>

如果您在上面注意到:

  1. 公司、产品、人员位于同一命名空间中,并且还使用默认命名空间。
  2. Person 的元素使用不同的命名空间,也带有前缀 per
  3. Product 的元素正在使用另一个命名空间,pro修复。

下面的脚本示例中,将定义具有不同前缀或相同前缀的命名空间(只是为了说明用户在执行 xpath 时可以自由使用不同的前缀)

具有多个命名空间的时髦脚本

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"
它是

命名空间中的 xmlns 属性。

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

显然,如果它有任何值(除了空字符串),它会破坏XmlHolder的许多xpath功能。

从消息中删除 xmlns 属性会导致 xpath 正常工作。

这可以在Groovy中完成,如下所示:

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

最新更新