A very common error in XPath and XSLT is to miss where context changes take place. Listing 1 (assignments.xml) is an example of a source document that records assignments of people to various tasks, with back-ups in case a primarily assigned person becomes unavailable.
Listing 1. XML document recording task assignments (assignments.xml)
<?xml version="1.0" encoding="utf-8"?>
<assignments>
<task id="t1">
<assigned-to>
<person primary-to="t1">Uche</person>
</assigned-to>
<assigned-to>
<person>Jideobi</person>
</assigned-to>
</task>
<task id="t2">
<assigned-to>
<person primary-to="t1">Uche</person>
</assigned-to>
<assigned-to>
<person primary-to="t2">Lori</person>
</assigned-to>
</task>
<task id="t3">
<assigned-to>
<person primary-to="t3">Osita</person>
</assigned-to>
<assigned-to>
<person>Jideobi</person>
</assigned-to>
<assigned-to>
<person primary-to="t2">Lori</person>
</assigned-to>
</task>
</assignments>
|
The design of this XML format is not ideal (it's repetitious, for one thing), but it does illustrate the point well enough. If I want to write some XSLT to display each task and its primarily assigned party, I might write a script like that in Listing 2 (primaries.xslt).
Listing 2. XSLT transform for displaying the primary assignments for each task (primaries.xslt)
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:output method="text"/>
<xsl:strip-space elements="*"/>
<xsl:template match="task">
<xsl:value-of select="@id"/>
<xsl:text>: </xsl:text>
<xsl:value-of select="assigned-to/person[@primary-to = @id]"/>
<!-- Print new line -->
<xsl:text>
</xsl:text>
</xsl:template>
</xsl:stylesheet>
|
But this does not display the desired result at all. There are no matches from each task to its primary person. The problem lies in the XPath assigned-to/person[@primary-to = @id]. Originally, the context node was a task element, as selected by the template. This is why I could extract the current task's ID with the simple XPath @id in the earlier XPath expression. However, the first step -- assigned-to -- selects all child elements with that name and shifts the context to iterate over this list.
In the next step, person[@primary-to = @id], a list of person elements is gathered, and then the context node is shifted to each in turn as the predicate [@primary-to = @id] is evaluated. Since the context for this evaluation is a person element, the @primary-to sub expression returns an attribute, as expected, from that element. The @id sub-expression, however, is a different matter. There is no such attribute on any person element, so this always returns an empty node set. What I really want is the id attribute on the task element that is an ancestor of the context node.
One way to do this is to use specific XPath operations within the predicate, such as [@primary-to = ../../@id] or [@primary-to = ancestor::*/@id]. But though these would work in the current example, they are not very general solutions because there is not always a consistent path back to the desired node. The most general solution is to have some sort of anchor back to the original context node that was passed to the overall XPath expression. XSLT provides precisely this with the current() function. Listing 3 (primaries.xslt) is a working version of my desired transform, using the current() function.
Listing 3. XSLT transform for displaying the primary assignments for each task, using current() (primaries.xslt)
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:output method="text"/>
<xsl:strip-space elements="*"/>
<xsl:template match="task">
<xsl:value-of select="@id"/>
<xsl:text>: </xsl:text>
<xsl:value-of
select="assigned-to/person[@primary-to=current()/@id]"/>
<!-- Print new line -->
<xsl:text>
</xsl:text>
</xsl:template>
</xsl:stylesheet>
|
The current() function returns a node set whose sole item is the original context node from XSLT at the point where XPath was evaluated -- in this case, the current task element. This provides exactly what I need to compare the person's primary assignment to the desired ID in the step person[@primary-to = current()/@id]. The result of this transform is as follows:
$ 4xslt assignments.xml primaries.xslt t1: Uche t2: Lori t3: Osita |
One technique that would work pretty much anywhere that current() works is to set a variable such as <xsl:variable name="current" select="."/> for the targeted XPath expression. The main advantage of current() over this is that it makes the expression stand alone better, making it easier to reuse the expression in other settings. Context is perhaps the most important concept in XSLT, and you should learn all the tools available for handling context, including the current() function.
- The W3C's XSL page has many useful links to XSLT-related resources, including the specifications themselves, tutorials, articles, and implementations. For a precise specification of the
current()function, see section 12.4 of the XSLT 1.0 specification. - For a good introduction to XSLT, read Investigating XSLT: The XML transformation language, by LindaMay Patterson (developerWorks, August 2001).
- See Uche Ogbuji's other XSLT-related tips on developerWorks:
- Counting with node sets (May 2002)
- Generating internal HTML links with XSLT (February 2001)
- XSLT lookup tables (February 2001)
- Multi-pass XSLT (September 2002)
- The stylesheet processor used in these examples is 4XSLT, part of 4Suite, which is co-developed by the author.
- Find more XML resources on the developerWorks XML zone. For a complete list of XML tips to date, check out the tips summary page.
-
IBM trial software: Build your next development project with trial software available for download directly from developerWorks.
- Want us to send you useful XML tips like this every week? Sign up for the developerWorks XML Tips newsletter.

Uche Ogbuji is a consultant and co-founder of Fourthought Inc., a software vendor and consultancy specializing in XML solutions for enterprise knowledge management. Fourthought develops 4Suite, an open source platform for XML, RDF, and knowledge-management applications. Mr. Ogbuji is a Computer Engineer and writer born in Nigeria, living and working in Boulder, Colorado, USA. You can contact Mr. Ogbuji at uche@ogbuji.net.