Tip: Keep context straight in XSLT

Use the current() function as an anchor for XPath expressions

Developers frequently forget where context changes in XPath. XSLT provides an anchor for the initial context used for XPath expressions -- the current() function. In this tip, Uche Ogbuji warns about common bugs associated with changing context, and explains how to use current(). To follow this tip, you should be familiar with the basics of XPath and XSLT.

Uche Ogbuji, Principal Consultant, Fourthought, Inc.

Photo of Uche OgbujiUche 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.



01 February 2003

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

Conclusion

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.

Resources

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into XML on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=XML
ArticleID=12346
ArticleTitle=Tip: Keep context straight in XSLT
publish-date=02012003