Recipe 1.4. Shrinking Conditional Code with If Expressions
Problem
Your complex XSLT code is
too verbose due to the high
overhead of XML when expressing simple if-then-else conditions.
Solution
XPath 1.0
There are a few tricks you can play in XPath 1.0 to avoid using
XSLT's verbose xsl:choose in
simple situations. These tricks rely on the fact that false converts
to 0 and true to 1 when used in a mathematical context.
So, for example, min, max, and absolute value can be calculated
directly in XPath 1.0. In these examples, assume $x and $y contain
integers.
(: min :)
($x <= $y) * $x + ($y < $x) * $y
(: max :)
($x >= $y) * $x + ($y > $x) * $y
(: abs :)
(1 - 2 * ($x < 0)) * $x
XPath 2.0
For the simple cases in the
XPath 1.0 section
(min, max, abs), there are now built-in XPath functions. For other
simple conditionals, use the new conditional if
expression.
(: Default the value of a missing attribute to 10. :)
if (@x) then @x else 10
(: Default the value of a missing element to 'unauthorized'. :)
if (password) then password else 'unauthorized''unauthorized'
(: Guard against division by zero. :)
if ($d ne 0) then $x div $d else 0
(: A para elements text if it contains at least one non-whitespace character; otherwise, a single space. :)
if (normalize-space(para)) then string(para) else ' '
Discussion
If you are a veteran XSLT 1.0 programmer, you probably cringe every
time you need to add some conditional code to a template. I know I
do, and often go through pains to exploit XSLT's
pattern-matching constructs to minimize conditional code. This is not
because such code is more complicated or inefficient in XSLT but
rather because it is so darn verbose. A simple
xsl:if is not that bad, but if you need to express
if-then-else logic, you are now forced to use the bulkier
xsl:choose. Yech!
In XSLT 2.0, there is an alternative but it is delivered in XPath 2.0
rather than XSLT 2.0 proper. On first exposure, one may get the
impression that XPath was somehow bastardized via the introduction of
what procedural programmers call flow of control statements. However,
once you begin to use XPath 2.0 in its full glory, you should quickly
conclude that both XPath and XSLT is bettered by these enhancements.
Further, the XPath 2.0 conditional expression does not deprecate the
xsl:if element but rather reduces the need to use
it in just those cases where it is most awkward. As an illustration,
compare the following snippets:
<!-- XSLT 1.0 -->
<xsl:variable name="size">
<xsl:choose>
<xsl:when test="$x > 3">big</xsl:when>
<xsl:otherwise>small</xsl:when>
</xsl:choose>
</xsl:variable>
<!-- XSLT 2.0 -->
<xsl:variable name="size" select="if ($x gt 3) then 'big' else 'small' "/>
I think most readers will prefer the later XPath 2.0 solution over
the former XSLT 1.0 one.
One important fact about the XPath conditional expression is that the
else is not optional. C programmers can appreciate this by comparing
it to the a ? b : c expression in that language.
Often one will use the empty sequence ( ) when
there is no other sensible value for the else part
of the expression.
Conditional expressions are useful for defaulting in the absence of a
schema that provides defaults.
(: Defaulting the value of an optional attribute :)
if (@optional) then @optional else 'some-default´
(: Defaulting the value of an optional element :)
if (optional) then optional else 'some-default´
Handling undefined or undesirable results in expressions is also a
good application. In this example we have an application specific
reason to prefer 0 rather than
number(`Infinity') as the result.
if ($divisor ne 0) then $dividend div $divisor else 0
You can also create conditions that are more complex. The following
code that decodes an enumerated list
if (size eq 'XXL') then 50
else if (size eq 'XL') then 45
else if (size eq 'L') then 40
else if (size eq 'M') then 34
else if (size eq 'S') then 32
else if (size eq 'XS') then 29
else -1
However, in this case, you might find a solution using sequences to
be cleaner especially if you replace the literal sequences with
variables that might be initialized from an external XML file.
(50,45,40,34,32,29,-1)[(index-of((('XXL', 'XL', 'L', 'M', 'S', 'XS')), size), 7)[1]]
Here we are assuming the context has only a single
size child element otherwise the expression is
illegal (but you can then write size[1] instead). We are also relying
on the fact that index-of returns an empty
sequence when the search item is not found which we concatenate with
7 to handle the else case.
 |