February 5, 2004

XML Tips and Tricks. Conditional XPath expressions

I'm introducing another category in my blog - XML Tips and Tricks, where I'm going to post some XML, XPath, XSLT, XML Schema, XQuery etc tips and tricks. I know, many of my readers being real XML gurus know all this stuff (I encourage to correct me when I'm wrong ...

If you are like me and addicted to write

return a>b? a : b;
instead of
if (a>b)
    return a;
else
    return b;
then you should be used to grumble programming in XSLT, because XPath 1.0 doesn't support conditional expressions (XPath 2.0 does though). The most notorious sample is when outputting a value into HTML table cell - you should assure it's not empty otherwise the cell will collapse into nothing in a browser. So one usually ends up with the following verbose pattern:
<xsl:choose>
    <xsl:when test="price != ''">
        <xsl:value-of select="price"/>
    </xsl:when>
    <xsl:otherwise>&#xA0;</xsl:otherwise>
</xsl:choose>
Or when you need to output some value or "n/a" string if the value is empty. Quite common requirements. Things get even worse when you need to set up a variable conditionally - the only way then is to nest xsl:choose switch within xsl:variable, thus getting result tree fragment instead of nodeset.

But in fact there are tricks to address this XPath 1.0 restriction. Here they are.

For conditional nodesets the trick formula is
$nodeset1[$condition] | $nodeset2[not($condition)]

It's an union of both nodesets, filtered by mutually exclusive conditional expressions. Easy to see than depending on boolean value of the $condition one nodeset will be selected and second one filtered out. E.g.

<xsl:variable name="var" select="//foo[$param] | //bar[not($param)]"/>
binds $var to //foo if $param is true and to //bar otherwise.

For conditional strings or numbers the trick formula is more complicted:
concat( substring($s1, number(not($condition))*string-length($s1)+1),
     substring($s2, number($condition)*string-length($s2)+1) )

While it looks quite convolute, the idea (Becker's method after Oliver Becker) is simple - in XPath number(true()) is 1, while number(false()) is 0 and when second argument of substring() function is greater than actual length of the string, empty string is returned. Hence substring($s1, number(not($condition))*string-length($s1)+1) returns $s1 if $condition is true and empty string otherwise. Concatenating two such expressions in mutually exclusive way gives us conditional strings expression.
There is also another variant:
concat( substring($s1, 1, number($condition)*string-length($s1)),
     substring($s2, 1, number(not($condition))*string-length($s2)) )

In practice such expressions can be great deal simplified though. For instance to output price if it's not empty or "n/a" otherwise one can use just

<xsl:value-of select="concat(price, 
     substring('n/a', (price!='')*string-length('n/a')+1))"/>

Another interesting trick is to leverage the ability of msxsl:node-set() (or exslt:node-set) extension function to convert a string into a text node, thus enabling using aforementioned conditional nodeset trick for strings too. Here is the same sample written using this method:

<xsl:value-of select="concat(price, 
     msxsl:node-set('n/a')[current()/price=''])"/>
Well, probably enough. Hope you had fun looking at this clumsy XPath tricks. Remember than it's the very first version of the XPath language after all and XPath 2.0 will make these tricks obsolete bringing in support for conditional expressions, such as
<xsl:value-of select="if ($part/@discounted) 
  then $part/wholesale 
  else $part/retail"/>
Till then it's good to know these tricks.


Quote of the day:

Is it possible to transform a XML document to another XML document using XSLT? How?
:)

February 4, 2004

XML Bestiary: SerializableXPathNavigator - InnerXml/OuterXml for XPathNavigator

Dare has been talking recently about the disconnects developers may feel once they make the shift from tree based (XmlDocument) to cursor based (XPathNavigator) model. My personal XML learning curve has started with DOM (I remember those long convolute ugly DOM navigational programs I wrote back in Y2K), then I ...

February 3, 2004

I love XmlResolvers

Did you know XslTransform class allows custom XmlResolver to return not only Stream (it's only what default XmlResolver implementation - XmlUrlResolver class supports), but also XPathNavigator! Sounds like undeservedly undocumented feature. What it gives us? Really efficient advanced XML resolving scenarios such as just mentioned recently on asp.net XML forum ...

On transforming WordML to HTML again

One of consequences of the revolutionary XML support in Microsoft Office 2003 is a possibility to unlock information in the Microsoft Office System using XML. Most likely that was deliberate decision to open Office doors for XML technology and I'm sure that's winning strategy. Talking about transforming WordprocessingML (WordML) to ...

February 1, 2004

MovableType 3.0 Alpha soon

Six Apart has announced MovableType 3.0 Alpha testing is about to begin. Testers such as plugin developers, web standards advocates or just Movable Type users with an active commenting community are invited. Here is a list of upcoming MT 3.0 features. I keep getting 5-10 spam comments a day, so ...

EXSLT.NET rocks

Have you noted this thread in microsoft.public.dotnet.xml newsgroup? A guy was trying to get list of unique values from XML document of 46000 records. Using Muenchian grouping method. For MSXML4 it took 20 seconds, while in .NET 1.0 and 1.1 it effectively hung.Well, as all we know Muenchian method works ...