Finding Relatives
by Bob DuCharme
October 04, 2000
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.