You can use an involved syntax with the <xsl:template>
element's match attribute, and an even more involved syntax with the
select attribute of the <xsl:apply-templates>,
<xsl:value-of>, <xsl:for-each>, <xsl:copy-of>, and
<xsl:sort> elements. We'll see them both in this chapter,
starting with the syntax you can use with the match attribute.
Matching the Root Node
As we've already seen, you can match the root node with /, like
this:
<xsl:template match="/">
<HTML>
<xsl:apply-templates/>
</HTML>
</xsl:template>
Matching Elements
You can match specific XML elements simply by giving their name,
as we've also seen:
<xsl:template match="PLANETS">
<HTML>
<xsl:apply-templates/>
</HTML>
</xsl:template>
Matching Children
You can use the / operator to separate element names when you want
to refer to a child of a particular node. For example, say that you
wanted to create a rule that applies only to <NAME> elements
that are children of <PLANET> elements. In that case, you can
match to the expression "PLANET/NAME". Here's a rule that will
surround the text of such elements in an <H3> element:
<xsl:template match="PLANET/NAME">
<H3><xsl:value-of select="."/></H3>
</xsl:template>
Notice the expression "." here. You use "." with the select
attribute to specify the current node, as we'll see when discussing
the select attribute.
You can also use the * character as a wildcard, standing for any
element _(* can match only elements). For example, this rule applies
to all <NAME> elements that are grandchildren of <PLANET>
elements:
<xsl:template match="PLANET/*/NAME">
<H3><xsl:value-of select="."/></H3>
</xsl:template>
Matching Element Descendants
In the previous section, I used the expression "PLANET/NAME" to
match all <NAME> elements that are direct children of
<PLANET> elements, and I used the expression "PLANET/*/NAME" to
match all <NAME> elements that are grand-_children of
<PLANET> elements. However, there's an easier way to perform
both matches: Just use the expression "PLANET//NAME", which matches
all <NAME> elements that are inside <PLANET> elements, no
matter how many levels deep. (The matched elements are called
descendants of the <PLANET> element). In other words,
"PLANET//NAME" matches "PLANET/NAME", "PLANET/*/NAME",
"PLANET/*/*/NAME", and so on:
<xsl:template match="PLANETS//NAME">
<H3><xsl:value-of select="."/></H3>
</xsl:template>
Matching Attributes
You can match attributes if you preface their name with @. Here's
an example; in this case, I'll display the data in planets.xml in an
HTML table. You might note, however, that the units for the various
measurements are stored in attributes, like this:
<PLANET>
<NAME>Earth</NAME>
<MASS UNITS="(Earth =
1)">1</MASS>
<DAY UNITS="days">1</DAY>
<RADIUS
UNITS="miles">2107</RADIUS>
<DENSITY UNITS="(Earth =
1)">1</DENSITY>
<DISTANCE UNITS="million
miles">128.4</DISTANCE><!--At perihelion-->
</PLANET>
To recover the units and display them as well as the values for
the mass and so on, I'll match the UNITS attribute with @UNITS.
Here's how that looks-note that I'm using the element
<xsl:text> elementto insert a space into the output document
(more on <xsl:text> later):
<?xml version="1.0"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/PLANETS">
<HTML>
<HEAD>
<TITLE>
The
Planets Table
</TITLE>
</HEAD>
<BODY>
<H1>
The Planets Table
</H1>
<TABLE>
<TD>Name</TD>
<TD>Mass</TD>
<TD>Radius</TD>
<TD>Day</TD>
<xsl:apply-templates/>
</TABLE>
</BODY>
</HTML>
</xsl:template>
<xsl:template match="PLANET">
<TR>
<TD><xsl:value-of select="NAME"/></TD>
<TD><xsl:apply-templates select="MASS"/></TD>
<TD><xsl:apply-templates select="RADIUS"/></TD>
</TR>
</xsl:template>
<xsl:template match="MASS">
<xsl:value-of
select="."/>
<xsl:text>
</xsl:text>
<xsl:value-of
select="@UNITS"/>
</xsl:template>
<xsl:template match="RADIUS">
<xsl:value-of
select="."/>
<xsl:text>
</xsl:text>
<xsl:value-of
select="@UNITS"/>
</xsl:template>
<xsl:template match="DAY">
<xsl:value-of
select="."/>
<xsl:text>
</xsl:text>
<xsl:value-of
select="@UNITS"/>
</xsl:template>
</xsl:stylesheet>
Now the resulting HTML table includes not only values, but also
their units of measurement. (The spacing leaves a little to be
desired, but HTML browsers will have no problem with it; we'll take a
look at ways of handling whitespace later in this chapter.)
<HTML>
<HEAD>
<TITLE>
The Planets Table
</TITLE>
</HEAD>
<BODY>
<H1>
The Planets Table
</H1>
<TABLE>
<TD>Name</TD><TD>Mass</TD><TD>Radius</TD><TD>Day</TD>
<TR>
<TD>Mercury</TD><TD>.0553 (Earth =
1)</TD><TD>1516 miles</TD>
</TR>
<TR>
<TD>Venus</TD><TD>.815 (Earth =
1)</TD><TD>3716 miles</TD>
</TR>
<TR>
<TD>Earth</TD><TD>1 (Earth =
1)</TD><TD>2107 miles</TD>
</TR>
</TABLE>
</BODY>
</HTML>
You can also use the @* wildcard to select all attributes of an
element. For example, "PLANET/@*" selects all attributes of
<PLANET> elements.
Matching by ID
You can also match elements that have a specific ID value using
the pattern id(). To use this selector, you must give elements an ID
attribute, and you must declare that attribute of type ID, as you can
do in a DTD. Here's an example rule that adds the text of all
elements that have the ID Christine:
<xsl:template match = "id('Christine')">
<H3><xsl:value-of
select="."/></H3>
</xsl:template>
Matching Comments
You can match the text of comments with the pattern comment(). You
should not store data that should go into the output document in
comments in the input document, of course. However, you might want to
convert comments from the <!--comment--> form into something
another markup language might use, such as a <COMMENT>
element.
Here's an example; planet.xml was designed to include comments so
that we could see how to extract them:
<PLANET>
<NAME>Venus</NAME>
<MASS UNITS="(Earth =
1)">.815</MASS>
<DAY UNITS="days">116.75</DAY>
<RADIUS
UNITS="miles">3716</RADIUS>
<DENSITY UNITS="(Earth =
1)">.943</DENSITY>
<DISTANCE UNITS="million
miles">66.8</DISTANCE><!--At perihelion-->
</PLANET>
To extract comments and put them into <COMMENT> elements,
I'll include a rule just for comments:
<?xml version="1.0"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="PLANETS">
<HTML>
<xsl:apply-templates/>
</HTML>
</xsl:template>
<xsl:template match="comment()">
<COMMENT>
<xsl:value-of
select="."/>
</COMMENT>
</xsl:template>
</xsl:stylesheet>
Here's what the result is for Venus, where I've transformed the
comment into a <COMMENT> element:
Venus
.815
116.75
3716
.943
66.8<COMMENT>At perihelion</COMMENT>
Note that the text for the other elements in the <PLANET>
element is also inserted into the output document. The reason for
that is that the default rule for each element is to include its text
in the output document. Because I haven't provided a rule for
elements, their text is simply included in the output document. I'll
take a closer look at default rules later in the chapter.
Matching Text Nodes with text()
You can match the text in a node with the pattern text(). There's
really not much reason to ever use text(), however, because XSLT
includes a default rule: If there are no other rules for a text node,
the text in that node is inserted into the output document. If you
were to make that default rule explicit, it might look like this:
<xsl:template match="text()">
<xsl:value-of select="."/>
</xsl:template>
You can override this rule by not sending the text in text nodes
to the output document, like this:
<xsl:template match="text()">
</xsl:template>
In the previous example, you can see that a great deal of text
made it from the input document to the output document because there
was no explicit rule besides the default one for text nodes-the only
output rule that I used was for comments. If you turn off the default
rule for text nodes by adding the previous two lines to the version
of planets.xsl used in the previous example, the text of those text
nodes does not go into the output document. This is the result:
<HTML>
<COMMENT>At perihelion</COMMENT>
<COMMENT>At perihelion</COMMENT>
<COMMENT>At perihelion</COMMENT>
</HTML>
Matching Processing Instructions
You can use the pattern processing-instruction() to match
processing instructions.
<xsl:template match="/processing-instruction()">
<I>
Found a processing
instruction.
</I>
</xsl:template>
You can also specify what processing instruction you want to match
by giving the name of the processing instruction (excluding <? and
?>), as in this case, where I'm matching the processing
instruction <?xml-include?>:
<xsl:template
match="/processing-instruction(xml-include)">
<I>
Found an xml-include
processing instruction.
</I>
</xsl:template>
One of the major reasons that XML makes a distinction between the
root node (at the very beginning of the document) and the document
node is so that you have access to the processing instructions and
other nodes in the document's prolog.
Using the Or Operator
You can match to a number of possible patterns, which is very
useful when your documents get a little more involved than the ones
we've been using so far in this chapter. Here's an example; in this
case, I want to display <NAME> and <MASS> elements in
bold, which I'll do with the HTML <B> tag. To match either
<NAME>or<MASS> elements, I'll use the Or operator, which
is a vertical bar (|), in a new rule, like this:
<?xml version="1.0"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="PLANETS">
<HTML>
<xsl:apply-templates/>
</HTML>
</xsl:template>
<xsl:template match="PLANET">
<P>
<xsl:apply-templates/>
</P>
</xsl:template>
<xsl:template match="NAME | MASS">
<B>
<xsl:apply-templates/>
</B>
</xsl:template>
</xsl:stylesheet>
Here are the results; note that the name and mass values are both
enclosed in <B> elements. (Also note that, because of the XSL
default rules, the text from the other child elements of the
<PLANET> element is also displayed.)
<HTML>
<P>
<B>Mercury</B>
<B>.0553</B>
58.65
1516
.983
43.4
</P>
<P>
<B>Venus</B>
<B>.815</B>
116.75
3716
.943
66.8
</P>
<P>
<B>Earth</B>
<B>1</B>
1
2107
1
128.4
</P>
</HTML>
You can use any valid pattern with the | operator, such as
expressions like PLANET | PLANET//NAME, and you can use multiple |
operators, such as NAME | MASS | DAY, and so on.
Testing with []
You can use the [] operator to test whether a certain condition is
true. For example, you can test the following:
n The value of an attribute in a given string
n The value of an element
n Whether an element encloses a particular child,
attribute, or other element
n The position of a node in the node tree
Here are some examples:
n This expression matches <PLANET> elements
that have child <NAME> _elements:
<xsl:template match = "PLANET[NAME]">
n This expression matches any element that has a
<NAME> child element:
<xsl:template match = "*[NAME]">
n This expression matches any <PLANET> element
that has either a <NAME> or _a <MASS> child element:
<xsl:template match="PLANET[NAME | MASS]">
Say that we gave the <PLANET> elements in planets.xml a new
attribute-_COLOR-which holds the planet's color:
<?xml version="1.0"?>
<?xml-stylesheet type="text/xml" href="planets.xsl"?>
<PLANETS>
<PLANET COLOR="RED">
<NAME>Mercury</NAME>
<MASS UNITS="(Earth =
1)">.0553</MASS>
<DAY UNITS="days">58.65</DAY>
<RADIUS
UNITS="miles">1516</RADIUS>
<DENSITY UNITS="(Earth =
1)">.983</DENSITY>
<DISTANCE UNITS="million
miles">43.4</DISTANCE><!--At perihelion-->
</PLANET>
<PLANET COLOR="WHITE">
<NAME>Venus</NAME>
<MASS UNITS="(Earth =
1)">.815</MASS>
<DAY UNITS="days">116.75</DAY>
<RADIUS
UNITS="miles">3716</RADIUS>
<DENSITY UNITS="(Earth =
1)">.943</DENSITY>
<DISTANCE UNITS="million
miles">66.8</DISTANCE><!--At perihelion-->
</PLANET>
<PLANET COLOR="BLUE">
<NAME>Earth</NAME>
<MASS UNITS="(Earth =
1)">1</MASS>
<DAY UNITS="days">1</DAY>
<RADIUS
UNITS="miles">2107</RADIUS>
<DENSITY UNITS="(Earth =
1)">1</DENSITY>
<DISTANCE UNITS="million
miles">128.4</DISTANCE><!--At perihelion-->
</PLANET>
</PLANETS>
This expression matches <PLANET> elements that have COLOR
attributes:
<xsl:template match="PLANET[@COLOR]">
What if you wanted to match planets whose COLOR attribute was
BLUE? You can do that with the = operator, like this:
<?xml version="1.0"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="PLANETS">
<HTML>
<xsl:apply-templates/>
</HTML>
</xsl:template>
<xsl:template match="PLANET[@COLOR =
'BLUE']">
The <xsl:value-of select="NAME"/> is blue.
</xsl:template>
<xsl:template match="text()">
</xsl:template>
</xsl:stylesheet>
This style sheet filters out all planets whose color is blue and
omits the others by turning off the default rule for text nodes.
Here's the result:
<HTML>
The Earth is blue.
</HTML>
In fact, the expressions you can use in the [] operators are W3C
XPath expressions. XPath expressions give you ways of specifying
nodes in an XML document using a fairly involved syntax. And because
the select attribute, which we're about to cover, uses XPath, I'll
take a look at XPath as well.
Specifying Patterns for the select Attribute
I've taken a look at the kinds of expressions that you can use
with the <xsl:template> element's match attribute. You can use
an even more involved syntax with the select attribute of the
<xsl:apply-templates>, <xsl:value-of>,
<xsl:for-each>, <xsl:copy-of>, and <xsl:sort>
elements.
The select attribute uses XPath expressions, which is a W3C
recommendation as of November 16, 1999. You can find the XPath
specification at www.w3.org/TR/xpath.
We've seen that you can use the match attribute to find nodes by
name, child element(s), attributes, or even descendant. We've also
seen that you can make some tests to see whether elements or
attributes have certain values. You can do all that and more with the
XPath specification supported by the select attribute, including
finding nodes by parent or sibling elements, as well as much more
involved tests. XPath is much more of a true language than the
expressions you can use with the match attribute; for example, XPath
expressions can return not only lists of nodes, but also Boolean,
string, and numeric values.
The XML for Java package has a handy example program,
ApplyXPath.java, that enables you to apply an XPath expression to a
_document and see what the results would be. This is great for
testing. For _example, if I applied the XPath expression
"PLANET/NAME" to planets.xml, here is what the result would look
like, displaying the values of all <NAME> elements that are
children of <PLANET> elements (the <output> tags are
added by ApplyXPath):
%java ApplyXPath planets.xml PLANET/NAME
<output>
<NAME>Mercury</NAME><NAME>Venus</NAME><NAME>Earth</NAME></output>
XPath expressions are more powerful than the match expressions
we've seen; for one thing, they're not restricted to working with the
current node or child nodes because you can work with parent nodes,
ancestor nodes, and more. Specifying what node you want to work in
relation to is called _specifying an axis in XPath. I'll take a look
at XPath syntax in detail next.