我正在尝试从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
我在这里做错了什么吗?
这是因为响应中使用了默认命名空间,正如您所预期的那样。
但是,它的处理方式需要与您在答案中提到的不同。
这适用于以下两种情况
- 默认命名空间
- 带前缀的命名空间
定义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>
如果您在上面注意到:
- 公司、产品、人员位于同一命名空间中,并且还使用默认命名空间。
- Person 的元素使用不同的命名空间,也带有前缀
per
- 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="[^"]*"', "");