(I'm posting this self-answered question because the typically offered solution to this issue is needlessly verbose and I'd like to set the record straight. I couldn't find an existing SO question for this, but if there is one, please close this as a duplicate.)
I am looking for a way to perform an XPath selection to select the current node only if it matches a certain condition. This would be useful, for example, when I want to conditionally apply an XSLT template to the current node:
<xsl:template match="Device">
<div>
<h2><xsl:value-of select="Name" /></h2>
<xsl:apply-templates select="???[Featured = 'true']" mode="featured" />
<p><xsl:value-of select="Description" /></p>
</div>
</xsl:template>
<xsl:template match="Book">
<div>
<h2><xsl:value-of select="Title" /></h2>
<xsl:apply-templates select="???[FeaturedBook = 'true']" mode="featured" />
<h3><xsl:value-of select="Author" /></h3>
<p><xsl:value-of select="Summary" /></p>
</div>
</xsl:template>
<xsl:template match="node()" mode="featured">
<p class='featured-notice'>This is a featured item!
<a href="/AddToCart?productId={Id}">Buy now</a> to get a 15% discount.
</p>
</xsl:template>
I have tried using .[Featured = 'true']
, but I get a syntax error. How can I do this?
I'm not going to add an input and output here since they are tangential to the question and would make it exceedingly long, but if you want to see what I have in mind, I have placed them here: input, output.
The syntax .[predicate]
is not allowed in XPath 1.0 on account of the syntax rules (see the end of this post for the gritty details).
100% of the advice I have found says that the only option is to use self::node()
for this:
self::node()[Featured = 'true']
This XPath tester is even specifically designed to tell users to use self::node()[predicate]
if they try to use .[predicate]
, but this is not the only option.
A valid and more concise option is to just wrap the abbreviated step in parentheses:
(.)[Featured = 'true']
This is perfectly valid by XPath 1.0 syntax rules (and in my opinion, a lot clearer).
You can also use this approach with the ..
abbreviated step, even going up multiple levels:
Select grandfather node if it is featured
../..[Featured = 'true'] - Not valid
../../../*[Featured = 'true'] - Valid, but not accurate
../../self::node()[Featured = 'true'] - Valid, but verbose
(../..)[Featured = 'true'] - Valid
Addendum: Why it's not possible to use
.[predicate]
in XPath 1.0
The following is the definition of a "step" in XPath 1.0 (basically, the pieces of an XPath node selection expression separated by slashes are called "steps"):
[4] Step ::= AxisSpecifier NodeTest Predicate* | AbbreviatedStep
This means that one step consists of one of two possible options:
- An axis specifier (which can be an empty string), followed by a node test, followed by 0 or more predicates
- An abbreviated step:
.
or ..
There is no option to have an abbreviated step followed by predicates.
<xsl:template match="Device">
<div>
<h2><xsl:value-of select="Name" /></h2>
<xsl:apply-templates select="Featured[. = 'true']" />
<p><xsl:value-of select="Description" /></p>
</div>
</xsl:template>
<xsl:template match="Book">
<div>
<h2><xsl:value-of select="Title" /></h2>
<xsl:apply-templates select="FeaturedBook[. = 'true']" />
<h3><xsl:value-of select="Author" /></h3>
<p><xsl:value-of select="Summary" /></p>
</div>
</xsl:template>
<xsl:template match="FeaturedBook|Featured">
<p class='featured-notice'>This is a featured item!
<a href="/AddToCart?productId={Id}">Buy now</a> to get a 15% discount.
</p>
</xsl:template>