Thursday, 14 February 2008

When to use xsl:if and xsl:when

In the last few weeks I have been approached by work colleagues for advice on this subject a couple of times, so I guess there must be more people out there that will either be searching the web for answers or aren't entirely clear about when xsl:if or xsl:when should be used, and the constraints of using either.


I am sure most people understand that xsl:if represents a conditional statement, just like most languages do, in fact I can't thing of a language that doesn't. Even XPath 2 now offers conditional statements.

<xsl:if test="/pub[@name='The Elephants Head']/@status = 'closed'">dammit, what do we do now?</xsl:if>

If The Elephants Head is closed, we have ourselves a good question as the text node output of this conditional statement, "dammit, what do we do now?".

The difference in conditional statements between XSL and other languages is that elseif and else are represented in a different construct by xsl:when and xsl:otherwise, both children of xsl:choose.

<xsl:choose>
<xsl:when test="/pub[@name='Elefants Head']/status = 'opened'">top banana, I'll have a pint of Krony please</xsl:when>
<xsl:otherwise>pants, we'll drink at The Mixer instead</xsl:otherwise>
</xsl:choose>

There we go, in this case we have a far more structured conditional statement if The Elephants Head pub is opened "top banana, I'll have a pint of Krony please", although if it is closed closed, I know exactly what to do next, "pants, we'll drink at The Mixer instead".

So lets say I am an indecisive guy, and am not quite sure what pub to go to, but have some solid perquisites to help my decision, alongside a psychic bit of information in the form of an XML document:


<?xml version="1.0" encoding="UTF-8"?>
<pubs>
<pub name="The Elephants Head" matesInside="0" status="opened"/>
<pub name="The Devenshire Arms" matesInside="0" status="closed"/>
<pub name="The Worlds End" matesInside="2" status="opened"/>
<pub name="The Good Mixer" matesInside="3" status="opened"/>
</pubs>


<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:template match="/pubs">
<xsl:choose>
<xsl:when test="pub[@name='The Elephants Head'][@status = 'opened'][@matesInside &gt; 0]">top banana, I'll have a pint of Krony please</xsl:when>
<xsl:when test="pub[@name='The Devenshire Arms'][@status='opened'][@matesInside &gt; 0]">head off to the Devensire, and see dead people</xsl:when>
<xsl:when test="pub[@name='The Worlds End'][@status='opened'][@matesInside &gt; 0]">head off to the Worlds End, to check out the gigs</xsl:when>
<xsl:otherwise>pants, we'll drink at The Mixer instead, someone will turn up soon enough</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>


So here I go walking down Camden High Street, as I approach The Elephants Head, I look inside and see the pub is opened and check if there are any of my mates inside, if this condition is true, mate, I'm on my way to the bar to get that cold pint of Krony.

Now lets consider the XML document above, and as you can see Billy no mates me at the Elephants, there's no one in, dammit. In this case XSL has an hierarchical decision making engine built in, that helps prioritizing the options in ascending position order.

First I check if the Devenshire Arms is opened, and weather I'm facing yet another Billy no mates scenario here. In this case as defined by the XML document the Dev is closed, so no one's there at all, bloody goths, probably sitting in a corner at home feeling sorry for themselves.

Ok . . . whats' the next prospect here . . . we have The Worlds End, it's opened, and guess what, 3 of my mates are inside enjoying the beverage . . . hurrah, I guess "head off to the Worlds End, to check out the gigs" it's the option I'd go for in this instance.

Obviously as oblivious as I was (bless), there were already about 3 other mates comfortably on the booze for some time at the Mixer, and failing all of the previous options eventually my fate would indeed be "pants, we'll drink at The Mixer instead, someone will turn up soon enough".

So to recap xsl:if provides a mechanism to make a boolean evaluation of one or more xpath expressions, and xsl:choose, provides the same boolean evaluation mechanism hierarchically via a collection of xsl:when with the addition of a graceful fail over option.

I have also been asked frequently about combining xsl:if and xsl:choose/xsl:when. Is this bad practice?, I am asked. The answer is:It depends on the circumstances, it's frequently required to have both in the same algorithm, to assure you deliver an accurate output. Often the combination of xsl:if and xsl:choose might improve performance.
For example I knew the code above would not need to be executed unless I knew at least one of the pubs was opened, so I could wrap the xsl:choose in an xsl:if

<xsl:if test="pub[@status = 'opened']">
<xsl:choose>
<xsl:when test="....
</xsl:choose>
</xsl:if>

This case would probably be past 3:00am or something, and I wouldn't even be considering any of those options. Saves processing power and clearly documents that unless at least one of the pus is opened, there isn't any reason to even consider any of those options.

Sometimes is also useful to nest xsl:if inside an xsl:when, to further filter your options:

<xsl:choose>
<xsl:when test="...
<xsl:when test="pub[@name='The Worlds End'][@status='opened'][@matesInside &gt; 0]">
<xsl:if test="@timeToShut = 'last orders'">get to the bar as quickly as possible</xsl:if>
</xsl:when>
<xsl:otherwise...
</xsl:choose>

or even xsl:choose inside xsl:when

<xsl:choose>
<xsl:when test="...
<xsl:when test="pub[@name='The Worlds End'][@status='opened'][@matesInside &gt; 0]">
<xsl:choose>
<xsl:when test="pub/mate/pint[@status='nearely finished']">
<xsl:text>order </xsl:text>
<xsl:value-of select="count(pub/mate/pint[@status='nearely finished']) + 1"/>
<xsl:text>pints.</xsl:text>
</xsl:when>
<xsl:otherwise>it's only one pint for me, thank you.</xsl:otherwise>
</xsl:choose>
</xsl:when>
<xsl:otherwise...
</xsl:choose>

7 comments:

Anup said...

Good post. Sometimes I see inside an xsl:when a single xsl:if. This could be made easier to read if the xsl:if is combined into the xsl:when.

Eg:

Instead of

<xsl:when test="$condition1">
  <xsl:if test="$condition2">
  </xsl:if>
</xsl:when>

This is cleaner:

<xsl:when test="$condition1 and $condition2">
</xsl:when>

I guess this could be extended to when you only have multiple xsl:if's inside your when, you could create multiple when's -- I think performance differences are quite small, if any, and maybe it comes down to code preference in that case?

Miguel deMelo said...

Good point Anup, we shouldn't be afraid to concatenate XPath expressions in conditional XSL elements.

You've just reminded me of another great performance booster, by using variables to store XPath expressions that are repeatedly used throughout a conditional structure. You could also store the result Boolean value of an expression even before placing it in xsl:if/@test or xsl:when/@test by wrapping the xsl:variable/@select value expression in a boolean() function.

Anup said...

Yes, that is a good point. I wrote a few such tips on this page XSLT Tips for Cleaner Code and Better Performance on my blog.

Dave said...

nice article, sounds very familiar too!

AtifBhatti said...

quite informative stuff.

Miguel de Melo said...

AtifBhatti, unfortunately I no longer frequent those pubs, as most of them now host a completely new breed of people, for whom I would frown to offer my company. But other are still great hangouts.

Ho did you mean the XSLT ?

;)

Anonymous said...

Excellent example and explanation. Made it very easy for me to understand. Now I just need to figure out why the hell I'm doing this in XSL instead of PHP.

Loading...