Menu

Finding Relatives

October 4, 2000

Bob DuCharme

When your XSLT processor is processing one node of your source tree, and you want to get something from another node, you can use an XPath expression and an xsl:value-of instruction. I'm going to hurry through the general structure of an XPath expression to get to the really useful stuff: using XPath abbreviations to get at the different "relatives" a node can have on a source tree -- siblings, a parent, grandparents and so on. This brief review of the full XPath syntax will put the abbreviations in context.

An XPath expression consists of one or more location steps separated by slashes. Each location step consists of an axis specifier, a node test, and an optional predicate. You don't have to include an axis specifier, either; if you leave it out, a default of child is assumed. This makes a node test name like "product" a perfectly valid one-step XPath location step -- in fact, it's a perfectly valid XPath expression. When you include an axis specifier, it's separated from the node test with two colons, and a predicate goes inside of square brackets, so a location step of preceding-sibling::item means "of all the nodes in the preceding-sibling axis, I want the ones named 'item'." A location step that includes a predicate, like preceding-sibling::item[1], means "of all the nodes in the preceding-sibling axis, I want the first one named 'item'."

XPath offers abbreviations that let you use much of its power without spelling out full location path steps every time you want to use them. For example, @ means the same thing as attribute:: and .. means the same thing as parent::node(). Let's see how useful these abbreviations can be and how much more flexibility you have when you use axes that have no abbreviations.

Parent, Grandparent, Sibling and other Relative Elements: Getting their Content and Attributes

In the following, the list element is a child of the prices element, which is a child of the wine element. We're going to see how a template rule that processes that list element can access many other nodes of an XSLT source tree holding this document.


  <wine grape="Chardonnay">

    <winery>Lindeman's</winery>

    <product>Bin 65</product>

    <year>1998</year>

    <desc>Youthful, with a cascade of spicy fig.</desc>

    <prices>

      <list>6.99</list>

      <discounted>5.99</discounted>

      <case>71.50</case>

    </prices>

  </wine>

The following template rule tells the XSLT processor to add information about the list element and its various relatives to the result tree. Much of the template is text nodes: "~~~~ Start of list element's template ~~~~" to show where the template's output begins and "1. List price (current node): {" to label the results of the xsl:apply-templates element. The curly braces around the xsl:apply-templates and the xsl:value-of elements will make it easier to see where the results of these elements begin and end.


<xsl:template match="list">

~~~~ Start of list element's template  ~~~~

1. List price (current node): {<xsl:apply-templates/>}

2. Parent element (prices) contents: {<xsl:value-of select=".."/>}

3. Grandparent element contents: {<xsl:value-of select="../.."/>}

4. Attribute of grandparent: {<xsl:value-of select="../../@grape"/>}

5. Sibling node {<xsl:value-of select="../discounted"/>}

6. "Uncle" node {<xsl:value-of select="../../product"/>}

7.  Parent node's name: {<xsl:value-of select="name(..)"/>}

8.  Grandparent node's name: {<xsl:value-of select="name(../..)"/>}

~~~~ End of list element's template  ~~~~

</xsl:template>

Before we examine the template in detail, let's look at the result it creates with the wine element above as input:


~~~~ Start of list element's template  ~~~~

1. List price (current node): {6.99}

2. Parent element (prices) contents: {

      6.99

      5.99

      71.50

    }

3. Grandparent element contents: {

    Lindeman's

    Bin 65

    1998

    Youthful, with a cascade of spicy fig.

    

      6.99

      5.99

      71.50

    

  }

4. Attribute of grandparent: {Chardonnay}

5. Sibling node {5.99}

6. "Uncle" node {Bin 65}

7.  Parent node's name: {prices}

8.  Grandparent node's name: {wine}

~~~~ End of list element's template  ~~~~

Between the labels showing the beginning and end of the template's actions, it performs eight numbered steps.

  • The line labeled "1. List price" has an xsl:apply-templates element that tells the XSLT processor to apply any relevant templates to the node's children. The item node named by the xsl:template element's match attribute has only one child: a text element, and the default processing for a text element is to add its contents to the result tree. The text string "6.99" that makes up the list element's character data gets added to the result between the template line's curly braces.

  • The line labeled "2. Parent element" has ".." as the xsl:value-of element's select attribute value. This abbreviation tells the XSLT processor to output the contents of the list element node's parent. Its parent is prices, and with no template rule for prices or its other children in the stylesheet, the built-in rules output the character data content between that line's curly braces: the contents of the three children of the price element, complete with their carriage returns. As this shows, relying on built-in template rules to output the contents of an element that has element children leads to less control over the appearance of the children's contents.

  • The third line outputs the content of the grandparent wine element's contents using the .. abbreviation twice to say "the parent of the parent." The slash separates these two location path steps. As with line 2, the contents of this element are output using built-in template rules, resulting in a flat dump of the source tree text (including its carriage returns) to the result tree.

  • The fourth line, "4. Attribute of grandparent," uses the same XPath expression as the xsl:value-of element's select attribute in line 3, but with an addition. It has one more location path step to say "after going to the parent of the parent of the context node (../..), get the value of its grape attribute." The attribute value "Chardonnay" shows up between line 4's curly braces in the result.

  • Line 5 gets the value of a sibling by first looking to the list node's parent and then looking at its child named discounted. The discounted element's content of "5.99" shows up between line 5's curly braces in the result.

  • Line 6 looks at an uncle node (the sibling of a parent) by looking at a child of the grandparent much the same way that line 3 looked at an attribute of the grandparent: by adding a new location step (product, to look at the child element with that name) to the ../.. expression that looks at the parent of the context node's parent.

  • Line 7 uses the name() function to get the element type name of the node's parent. The template passes the .. expression for parent to the function.

  • Line 8 resembles 7 except that it passes the parent of parent XPath expression (../..) to the name() function. The element type name wine shows up between the curly braces in the result.

The pieces of these expressions mix and match well. For example, if the context node's desc uncle node has a color attribute, and you want its value when processing the context node, the xsl:value-of element's select attribute can use the expression ../../desc/@color.

Previous, Next, First, Third, Last Siblings

We've seen how to access nodes that are siblings of the context nodes, as well as nodes that are at different levels of the source tree from the context node, such as the parent and grandparent. Sometimes, when you want a sibling node, specifying its name isn't good enough, because other siblings may have the same name. Maybe you want a particular node because of its order among the siblings.

You can use the preceding-sibling and following-sibling axes to refer to sibling elements, or you can use a two-step location path to refer to the child of the parent node that has a particular name. The latter method sounds more cumbersome, but using the abbreviation of the parent axis (..) often makes it the more convenient form to use. The next template rule uses both methods to access the siblings of the third item element in the following document.


<list>

<item flavor="mint">First node.</item>

<item flavor="chocolate">Second node.</item>

<item flavor="vanilla">Third node.</item>

<item flavor="strawberry">Fourth node.</item>

</list>

If the template rule's match pattern only said "item" the template rule would apply to all item elements, but this one includes a predicate, [3]. So when the XSLT processor finds item children of a node that it's processing, it will only apply this template to the third item child. (Remember, when no axis is specified in a template rule's match pattern, a default of child:: is assumed, so the template is looking for item elements that are the children of another node currently being processed.)

The format of the output is similar to the output of the earlier example. The template includes text nodes ("~~~~ Start of item element's template ~~~~" and "1. This node: {") to show the beginning, end, and numbered individual steps of the template's actions in the result. The curly braces show exactly where the xsl:apply-templates element and xsl:value-of elements results begin and end in the result.


<xsl:template match="item[3]">

~~~~ Start of item element's template ~~~~

1. This node: {<xsl:apply-templates/>}

2. First node: {<xsl:value-of select="../item[1]"/>}

3. Last node: {<xsl:value-of select="../item[last()]"/>}

4. Preceding node: 

   {<xsl:value-of select="preceding-sibling::item[1]"/>}

5. Next node: {<xsl:value-of select="following-sibling::item[1]"/>}

6. flavor attribute value of first node: 

   {<xsl:value-of select="../item[1]/@flavor"/>}

~~~~ End of item element's template ~~~~

</xsl:template>

The result of applying this template has the contents of each referenced node between curly braces:


~~~~ Start of item element's template ~~~~

1. This node: {Third node.}

2. First node: {First node.}

3. Last node: {Fourth node.}

4. Preceding node: 

   {Second node.}

5. Next node: {Fourth node.}

6. flavor attribute value of first node: 

   {mint}

~~~~ End of item element's template ~~~~

This template performs six numbered steps between the labels that show the beginning and end of its actions.

  • Line 1 adds the contents of the current item element to the result tree to show where in the source tree document the XSLT processor is during the execution of this template rule.

  • Line 2 has a two-step location path. The first step (..) tells the XSLT processor to look at the node's parent, and the second step tells it to look at the first item child of that parent. Without the second step's predicate in square brackets, the expression ../item would refer to all of the parent node's item children, but because xsl:value-of will only show the first one, the [1] predicate in this case is not completely necessary. It's still worth including because it makes the stylesheet's intent clearer.

    You could include any number you want in that predicate, although a number for which no item exists -- for example, a predicate of [8] when there are four nodes in the list -- will tell the XSLT processor to look for something that isn't there, so it won't get anything. The match pattern in the xsl:template element's start-tag is a good example of selecting a node by its number; it uses [3] to indicate that this template should be applied to the third item child of the node currently being processed.

  • Line 3 uses the last() function in its predicate. The XSLT processor replaces this with the number of nodes in the node set that's been selected by the axis and node test (in this case, by the default child axis and the item node test). This gives the same result as putting the actual number (in this case, 4) between those square brackets. When you put this node test of "item" and predicate of [last()] together, you're asking for the last of the parent node's item elements. When the context node and all of its siblings are item elements, this is the simplest way to get the last sibling, especially if you don't know how many siblings there are.

  • Line 4 (which is actually split over two lines to fit better here) uses the preceding-sibling axis to access the node before the context node. Without the [1] predicate, it would take the first node in the preceding-sibling node set in document order, but with the explicit inclusion of the number 1, the XSLT processor counts from the end of the node set instead of from the beginning. It only counts from the end with axes for which this makes sense; for the preceding-sibling axis, the first sibling node that precedes the context node is the one just before it. XSLT processors also count backwards like this for the ancestor axis (for example, ancestor[1] refers to the parent node and ancestor[2] refers to the grandparent node), the ancestor-or-self axis, and the preceding axis.

  • Line 5 looks like line 4 except that that it uses the following-sibling axis specifier to look at the siblings after the context node. The predicate of [1] is not necessary. When an XSLT instruction like xsl:value-of only gets one following-sibling node, it gets the first one it can find in document order, but including the predicate here makes it easier to see the exact intent of the XPath expression.

  • Line 6 (which is also split over two lines) shows how easily you can get an attribute value from one of these siblings. Its XPath expression repeats the one from line 2 and adds one more step to the location path, @flavor, to get the flavor attribute value of that first item sibling. You could add this location step to any of the XPath expressions in this example to get the corresponding item element's flavor attribute value.

Of course, there's a lot more you can do with XPath, but for grabbing the content or attribute value of an element other than the one that an xsl:template template rule is currently processing, the expressions shown here will stand you in good stead. You can play with these stylesheets and their input yourself by downloading this zip file.