I cannot select node value from XML with names

I'm having trouble converting an XML SOAP response to a text string. I am starting with XLST and I have read everything I can. Apparently what I need to do is simple, but all examples are simpler than my context.

First, I reach a web service (Bing Maps Reverse Geocoding) that returns this XML structure:

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

      

node b:GeocodeResult

repeats about 10 times. The rest of the c parts are (...)

irrelevant (no nodes connected). The only thing I need from this extensive answer is nodes b:Locality

and b:AdminDistrict

. I am afraid of the last couple of days to do this.

Here's one of many approaches:

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

      

I know this should only return the first nodes b:Locality

and b:AdminDistrict

and that's perfect. But when I try to do this, the result is all text in XML (no tags at all, just concatenated text). Some variations of this approach only return the "-" chunk between two tags xsl:value-of

.

What am I doing wrong? Could this be due to the infinity of namespaces?

+3


source to share


2 answers


What's going on in your stylesheet

What's going on in your source code is this: one template you wrote doesn't match anything in the input XML. This means that the code inside this template is never executed. Instead, standard templates are applied to all nodes in the input XML.

Inline templates traverse the tree and display nothing but all text content. This is why you end up:

But when I try to do that, the result is all the text in XML (no tags at all, just concatenated text).

To prevent this, write an empty template that matches the entire text:

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

      

Then you can immediately and more clearly see the difference between your template not being applied at all (no output) and it giving the wrong result (wrong output).

Why is this happening in your stylesheet?

The pattern doesn't match anything because your path expression is:

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

      

doesn't match node in input XML. For the path expression above, the XPath processor expects ReverseGeocodeResponse

and ReverseGeocodeResult

does not have a namespace. But for your input XML, this is not the case:



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

      

An element ReverseGeocodeResponse

has a default namespace, which in this case also refers to that element itself. Also, it makes the child ReverseGeocodeResult

accept this namespace.

The solution to this

Declare this namespace ( http://dev.virtualearth.net/webservices/v1/geocode/contracts

) in your XSLT stylesheet and prefix the two elements it has. I know you tried to map the default XML input namespace with:

<xsl:stylesheet version="1.0"
    xmlns="http://dev.virtualearth.net/webservices/v1/common">

      

but the effect is different. This defines the default namespace for elements in the XSLT stylesheet. But what you wanted to do was define a default namespace for XPath expressions. It is also possible with xpath-default-namespace

- which

  • only available in XSLT 2.0.
  • not useful because your input XML has more than one default namespace

Stylesheet

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

      

Text output

Campinas - SP

      

+3


source


jumble

The xml you see is dictated by the standard inline template processing routines . Typically, if you only want to process certain elements in a document, you will need to grab the root element and then use it apply-templates

selectively.

Also, the reason you don't see the expected values ​​is because ReverseGeocodeResponse

they ReverseGeocodeResult

are actually xmlns namespaces http://dev.virtualearth.net/webservices/v1/geocode/contracts

- you need to adjust the xslt accordingly (I added an alias 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>

      

+1


source







All Articles