13

I have the following simple XML document:

<?xml version="1.0" encoding="UTF-8"?>
<cars>
    <car>
        <data attrib="Make">
            <text>Volvo</text>
        </data>
        <data attrib="Model">
            <text>855</text>
        </data>
    </car>
    <car>
        <data attrib="Make">
            <text>Volvo</text>
        </data>
        <data attrib="Model">
            <text>745</text>
        </data>
    </car>
    <car>
        <data attrib="Make">
            <text>Volvo</text>
        </data>
        <data attrib="Model">
            <text>V70R</text>
        </data>
    </car>
</cars>

And the following XPath:

/cars/car/data[(@attrib='Model') and (text='855')]

This returns the following result:

<data attrib="Model"><text>855</text></data>

I want the XPath to return the whole <car> block for the match.

So return data would be like this:

<cars>
    <car>
        <data attrib="Make">
            <text>Volvo</text>
        </data>
        <data attrib="Model">
            <text>855</text>
        </data>
    </car>
</cars>

How would I modify the XPath expression above to achieve this?

Ramon
  • 1,169
  • 11
  • 25
general exception
  • 4,202
  • 9
  • 54
  • 82

2 Answers2

20

XPath returns whatever node you go up to - in your case you're going to data, so that's what you're getting back. If you want car instead, place your predicate after car.

/cars/car[data/@attrib='Model' and data/text='855']

Or, slightly shorter

/cars/car[data[@attrib='Model' and text='855']]

XQuery to produce the desired output:

<cars>
  {/cars/car[data[@attrib='Model' and text='855']]}
</cars>
Mitya
  • 33,629
  • 9
  • 60
  • 107
  • 2
    Just to add to this response: this will return the selected "car" element. It won't wrap this in a "cars" element as requested. Because the input doesn't contain an existing "cars" element with only one child, you can only achieve this output by constructing a new "cars" element, and for that you will need XQuery rather than XPath. – Michael Kay Jul 05 '12 at 09:55
  • To add to @MichaelKay 's comment, it is even easier and more natural to do such processing with XSLT rather than with XQuery. – Dimitre Novatchev Jul 05 '12 at 13:24
  • @DimitreNovatchev could you give an XSLT example please ? – general exception Jul 06 '12 at 07:39
3

Here is a complete and likely one of the shortest possible XSLT solutions:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output omit-xml-declaration="yes" />

 <xsl:template match="/*">
     <cars>
       <xsl:copy-of select="car[data[@attrib='Model' and text='855']]"/>
   </cars>
 </xsl:template>
</xsl:stylesheet>

However, the following transformation, using the wellknown identity rule is both easier to write and provides maximum flexibility, extensibility and maintainability:

   <xsl:stylesheet version="1.0"
     xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
     <xsl:output omit-xml-declaration="yes" indent="yes"/>
     <xsl:strip-space elements="*"/>

     <xsl:template match="node()|@*">
         <xsl:copy>
           <xsl:apply-templates select="node()|@*"/>
         </xsl:copy>
     </xsl:template>

     <xsl:template match="car[not(data[@attrib='Model' and text='855'])]"/>
    </xsl:stylesheet>

When either of these two transformations is applied on the provided XML document:

<cars>
    <car>
        <data attrib="Make">
            <text>Volvo</text>
        </data>
        <data attrib="Model">
            <text>855</text>
        </data>
    </car>
    <car>
        <data attrib="Make">
            <text>Volvo</text>
        </data>
        <data attrib="Model">
            <text>745</text>
        </data>
    </car>
    <car>
        <data attrib="Make">
            <text>Volvo</text>
        </data>
        <data attrib="Model">
            <text>V70R</text>
        </data>
    </car>
</cars>

the wanted, correct result is produced:

<cars>
   <car>
      <data attrib="Make">
         <text>Volvo</text>
      </data>
      <data attrib="Model">
         <text>855</text>
      </data>
   </car>
</cars>

Explanation:

  1. The first transformation generates the top element cars, then simply selects the wanted car element and copies it as the body of cars.

  2. The second transformation is based on one of the most fundamental and powerful XSLT design patterns -- using and overriding the identity rule.

  3. The identity template copies every matched node (for which it is selected to process) "as-is".

  4. There is one template overriding the identity rule. This template matches any car for which it is not true that data[@attrib='Model' and text='855']. The body of the template is empty and this results in nothing from the matched car element being copied to the output -- in other words we can say that amy matching car element is "deleted".

Dimitre Novatchev
  • 240,661
  • 26
  • 293
  • 431