SoapUI中的XPath Groovy脚本没有按预期工作

时间:2016-03-02 20:25:33

标签: xpath groovy soapui xml-namespaces

我试图在一个groovy脚本中使用XmlHolder从SoapUI中的XML响应中提取值。但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

我在这里做错了吗?

2 个答案:

答案 0 :(得分:2)

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

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

这适用于以下两个条件

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

    在为XmlHolder定义对象后,必须设置查询数据所需的所有命名空间。而下面的语句也是如此,完整的脚本也很好。

    如何定义/设置命名空间

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

    设置命名空间映射,你可以在这里自由定义你自己的前缀,不用担心它在原始响应中有什么前缀(考虑上面提到的第二种情况)并使用这个前缀(&#39; ns&# 39;)使用xpath获取数据时。

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

    Groovy脚本

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

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

    Xml以下,包含多个namcespaces,包括默认值。
    从上面的链接中取这个样本。添加以下详细信息,因为它可以清除并有助于更好地了解命名空间。

    <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修复。
    4. 在下面的脚本示例中,将定义具有不同前缀或相同前缀的命名空间(仅用于说明用户在执行xpath时可以自由使用不同的前缀)

      具有多个命名空间的Groovy脚本

      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"
      

答案 1 :(得分:0)

它是命名空间中的xmlns属性。

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

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

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

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

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