general exception
general exception

Reputation: 4332

xquery filter on attribute and element

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?

Upvotes: 13

Views: 26498

Answers (2)

Mitya
Mitya

Reputation: 34556

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>

Upvotes: 20

Dimitre Novatchev
Dimitre Novatchev

Reputation: 243479

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

Upvotes: 3

Related Questions