我无法从具有命名空间的XML中选择节点值

3
我有困难将一个SOAP响应XML转换为纯文本字符串。我开始使用XLST并阅读了所有相关资料。显然,我需要完成的任务很简单,但是所有示例都比我的情境简单得多。
首先,我正在访问Bing Maps Reverse Geocoding的Web服务,该服务返回以下XML结构:
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
  <s:Body>
    <ReverseGeocodeResponse xmlns="http://dev.virtualearth.net/webservices/v1/geocode/contracts">
      <ReverseGeocodeResult xmlns:a="http://dev.virtualearth.net/webservices/v1/geocode" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
        <BrandLogoUri xmlns="http://dev.virtualearth.net/webservices/v1/common">
          http://dev.virtualearth.net/Branding/logo_powered_by.png
        </BrandLogoUri>
        <ResponseSummary xmlns="http://dev.virtualearth.net/webservices/v1/common">
          <AuthenticationResultCode>ValidCredentials</AuthenticationResultCode>
          <Copyright>(...)</Copyright>
          <FaultReason i:nil="true" />
          <StatusCode>Success</StatusCode>
          <TraceId>(...)</TraceId>
        </ResponseSummary>
        <a:Results xmlns:b="http://dev.virtualearth.net/webservices/v1/common">
          <b:GeocodeResult>
            <b:Address>
              <b:AddressLine>(...)</b:AddressLine>
              <b:AdminDistrict>SP</b:AdminDistrict>
              <b:CountryRegion>Brasil</b:CountryRegion>
              <b:District />
              <b:FormattedAddress>(...)</b:FormattedAddress>
              <b:Locality>Campinas</b:Locality>
              <b:PostalCode>13069-380</b:PostalCode>
              <b:PostalTown />
            </b:Address>
            <b:BestView>(...)</b:BestView>
            <b:Confidence>Medium</b:Confidence>
            <b:DisplayName>(...)</b:DisplayName>
            <b:EntityType>Address</b:EntityType>
            <b:Locations>(...)</b:Locations>
            <b:MatchCodes xmlns:c="http://schemas.microsoft.com/2003/10/Serialization/Arrays">
              <c:string>Good</c:string>
            </b:MatchCodes>
          </b:GeocodeResult>
          <b:GeocodeResult>
            (...)
          </b:GeocodeResult>
        </a:Results>
      </ReverseGeocodeResult>
    </ReverseGeocodeResponse>
  </s:Body>
</s:Envelope>

节点b:GeocodeResult重复了约10次。其他带有(...)的部分是无关的(没有相关的节点)。 我需要从这个详尽的响应中获得的是节点b:Localityb:AdminDistrict。 我已经努力了几天,但还是没有完成。 以下是其中的一种方法:
<xsl:stylesheet version="1.0"
        xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
        xmlns="http://dev.virtualearth.net/webservices/v1/common"
        xmlns:a="http://dev.virtualearth.net/webservices/v1/geocode"
        xmlns:b="http://dev.virtualearth.net/webservices/v1/common"
        xmlns:c="http://schemas.microsoft.com/2003/10/Serialization/Arrays"
        xmlns:i="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
    <xsl:template match="/s:Envelope/s:Body/ReverseGeocodeResponse/ReverseGeocodeResult/a:Results/b:GeocodeResult/b:Address">
        <xsl:value-of select="b:Locality"/> - <xsl:value-of select="b:AdminDistrict"/>
    </xsl:template>
</xsl:stylesheet>

我知道这应该只返回第一个 b:Localityb:AdminDistrict 节点,这很完美。但是当我尝试这样做时,结果是XML中的所有文本(没有标签,只有连接在一起的文本)。这种方法的一些变体仅返回两个 xsl:value-of 标签之间的 ' - ' 片段。

我做错了什么?这可能与无限的命名空间有关吗?

2个回答

3

你的样式表中发生了什么

在你的原始代码中发生的情况是:你编写的一个模板与输入XML中的任何内容都不匹配。这意味着此模板内的代码将永远不会被执行。相反,对于输入XML中的所有节点,将应用默认的内置模板

内置模板遍历整个树形结构,并且只输出所有文本内容,不会输出其他任何东西。这就是为什么你最终得到的结果是:

但是当我尝试这样做时,结果是XML中的所有文本(没有标签,只有连接的文本)。

为了防止这种情况发生,可以编写一个空模板,以匹配所有文本:

<xsl:template match="text()"/>

然后,您可以立即更清楚地看到在模板未应用时(无输出)与给出错误结果(错误输出)之间的区别。

为什么会在您的样式表中出现这种情况?

模板不匹配任何内容,因为您的路径表达式:

/s:Envelope/s:Body/ReverseGeocodeResponse/ReverseGeocodeResult/a:Results/b:GeocodeResult/b:Address"

在输入的XML中没有匹配任何节点。对于上述路径表达式,XPath处理器期望ReverseGeocodeResponseReverseGeocodeResult不在任何命名空间中。但是对于您的输入XML来说,这并不是真实的情况:

<ReverseGeocodeResponse xmlns="http://dev.virtualearth.net/webservices/v1/geocode/contracts">
    <ReverseGeocodeResult xmlns:a="http://dev.virtualearth.net/webservices/v1/geocode">

ReverseGeocodeResponse元素上,有一个默认命名空间 - 在这种情况下也适用于此元素本身。而且,它使其子元素ReverseGeocodeResult采用此命名空间。 解决方案 在您的XSLT样式表中声明此命名空间(http://dev.virtualearth.net/webservices/v1/geocode/contracts),并为具有该命名空间的两个元素添加前缀。我知道您尝试模拟输入XML的默认命名空间:
<xsl:stylesheet version="1.0"
    xmlns="http://dev.virtualearth.net/webservices/v1/common">

但效果是不同的。这定义了XSLT样式表中元素的默认命名空间。但您想要做的是定义XPath表达式的默认命名空间。这也可以通过xpath-default-namespace实现。

  • 遗憾的是,它仅在XSLT 2.0中可用
  • 因为您的输入XML具有多个默认命名空间,所以并不实用

样式表

<xsl:stylesheet version="1.0"
        xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
        xmlns:a="http://dev.virtualearth.net/webservices/v1/geocode"
        xmlns:b="http://dev.virtualearth.net/webservices/v1/common"
        xmlns:c="http://schemas.microsoft.com/2003/10/Serialization/Arrays"
        xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"
        xmlns:con="http://dev.virtualearth.net/webservices/v1/geocode/contracts">

    <xsl:output method="text"/>

    <xsl:template match="/s:Envelope/s:Body/con:ReverseGeocodeResponse/con:ReverseGeocodeResult/a:Results/b:GeocodeResult/b:Address">
        <xsl:value-of select="b:Locality"/> - <xsl:value-of select="b:AdminDistrict"/>
    </xsl:template>

    <xsl:template match="text()"/>

</xsl:stylesheet>

文本输出

Campinas - SP

太好了!已经测试过了,效果非常棒!非常感谢,我会仔细阅读你的答案并尝试理解细节,但是你提供的样式表完全符合需求。 - Paulo Avelar
1
@PauloAvelar 不用谢。如果您能阅读细节,我会很感激的——它们花费了很多时间 :-)。 - Mathias Müller

1
你看到的XML混乱是由于内置模板的默认处理规则。通常,如果你只想处理文档中的特定元素,你需要捕获根元素,然后有选择地使用apply-templates
此外,你没有看到预期值的原因是因为ReverseGeocodeResponseReverseGeocodeResult实际上是xmlns命名空间http://dev.virtualearth.net/webservices/v1/geocode/contracts - 你需要相应地调整你的XSLT(我添加了别名zz):
<xsl:stylesheet version="1.0"
        xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
        xmlns="http://dev.virtualearth.net/webservices/v1/common"
        xmlns:a="http://dev.virtualearth.net/webservices/v1/geocode"
        xmlns:b="http://dev.virtualearth.net/webservices/v1/common"
        xmlns:c="http://schemas.microsoft.com/2003/10/Serialization/Arrays"
        xmlns:i="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"
        xmlns:zz="http://dev.virtualearth.net/webservices/v1/geocode/contracts">

  <xsl:template match="/">
    <xsl:apply-templates select="/s:Envelope/s:Body/zz:ReverseGeocodeResponse/zz:ReverseGeocodeResult/a:Results/b:GeocodeResult/b:Address"/>
  </xsl:template>

  <xsl:template match="b:Address">
    <xsl:value-of select="b:Locality"/> - <xsl:value-of select="b:AdminDistrict"/>
  </xsl:template>

</xsl:stylesheet>

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接