Reputation: 4332
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
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
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:
The first transformation generates the top element cars
, then simply selects the wanted car
element and copies it as the body of cars
.
The second transformation is based on one of the most fundamental and powerful XSLT design patterns -- using and overriding the identity rule.
The identity template copies every matched node (for which it is selected to process) "as-is".
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