Using XSLT Apply-Templates to conditionally select nodes
Solution 1
The condition should be on xsl:apply-templates instead of xsl:template:
<Plays>
<xsl:apply-templates select="play[a='Will Smith']">"/>
</Plays>
In your solution, you are transforming ALL <play> nodes. For play nodes that match the condition, your template is applied. But for those that don't match the condition, a default template ("identity transform") is applied instead.
Alternatively, you could keep the condition on xsl:template match, but add another template for <play> that do not match the condition, to transform those <play> into nothing:
<xsl:template match="play[a='Will Smith']">
<play title="{data(t)[1]}">
<xsl:apply-templates select="a"/>
</play>
</xsl:template>
<xsl:template match="play">
</xsl:template>
Solution 2
I. Probably the most efficient XSLT 1.0 solution:
<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:key name="kWSPlayByTitle" match="play[a='Will Smith']"
use="t"/>
<xsl:key name="kActorByTitle" match="a"
use="../t"/>
<xsl:template match="/">
<Plays>
<xsl:apply-templates select=
"*/play[generate-id()
=
generate-id(key('kWSPlayByTitle',t)[1])
]"/>
</Plays>
</xsl:template>
<xsl:template match="play">
<Play title="{t}">
<xsl:apply-templates select="key('kActorByTitle',t)"/>
</Play>
</xsl:template>
<xsl:template match="a">
<star><xsl:value-of select="."/></star>
</xsl:template>
</xsl:stylesheet>
when this transformation is applied on the provided XML document:
<director>
<play>
<t>Nutcracker</t>
<a>Tom Cruise</a>
</play>
<play>
<t>Nutcracker</t>
<a>Robin Williams</a>
</play>
<play>
<t>Grinch Stole Christmas</t>
<a>Will Smith</a>
</play>
<play>
<t>Grinch Stole Christmas</t>
<a>Mel Gibson</a>
</play>
</director>
the wanted result is produced:
<Plays>
<Play title="Grinch Stole Christmas">
<star>Will Smith</star>
<star>Mel Gibson</star>
</Play>
</Plays>
Do note:
Efficiency is achieved by using keys both for all plays in which Mell Gibson took part and for all actors that took part in a given (titled) play.
Even if a play title with Mel Gibson were listed more than once (due to accidental error, perhaps...) it will be listed only once in the result.
II. A simple and efficient XSLT 2.0 solution:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="/*">
<Plays>
<xsl:for-each-group select="play[a='Mel Gibson']"
group-by="t">
<xsl:apply-templates select="."/>
</xsl:for-each-group>
</Plays>
</xsl:template>
<xsl:template match="play">
<Play title="{t}">
<xsl:for-each-group select="../play[t = current()/t]/a"
group-by=".">
<xsl:apply-templates select="."/>
</xsl:for-each-group>
</Play>
</xsl:template>
<xsl:template match="a">
<star>
<xsl:value-of select="."/>
</star>
</xsl:template>
</xsl:stylesheet>
Msencenb
Updated on August 11, 2022Comments
-
Msencenb almost 2 years
Let's say I have an xml document like this:
<director> <play> <t>Nutcracker</t> <a>Tom Cruise</a> </play> <play> <t>Nutcracker</t> <a>Robin Williams</a> </play> <play> <t>Grinch Stole Christmas</t> <a>Will Smith</a> </play> <play> <t>Grinch Stole Christmas</t> <a>Mel Gibson</a> </play> </director>
Now I want to be able to select all the plays with Will Smith as an actor and reformat it into something like this:
<Plays> <Play title="Grinch Stole Christmas"> <star>Will Smith</star> <star>Mel Gibson</star> </Play> </Plays>
I only want to use apply-templates.. No xsl:if or for each loops (I have contrived this example as a simpler version of what I'm doing so you can help me understand how to use xpath within a match statement)
Here is what I have so far:
<?xml version="1.0"?> <xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <xsl:output method="xml" indent="yes" /> <xsl:template match="/director"> <Plays> <xsl:apply-templates select="play"/> </Plays> </xsl:template> <xsl:template match="play[a='Will Smith']"> <play title="{data(t)[1]}"> <xsl:apply-templates select="a"/> </play> </xsl:template> <xsl:template match="a"> <star> <xsl:value-of select="."/> </star> </xsl:template> </xsl:stylesheet>
Basically I am just unsure of how to filter out nodes using XPath in the match attribute of the template. Any help would be great!