You can use the <xsl:sort> element to sort node sets. You
use this element inside <xsl:apply-templates> and then use its
select attribute to specify what to sort on. For example, here's how
I sort the planets based on density:
<?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>
Planets
</TITLE>
</HEAD>
<BODY>
<H1>Planets sorted by density</H1>
<TABLE>
<TD>Planet</TD>
<TD>Mass</TD>
<TD>Day</TD>
<TD>Density</TD>
<xsl:apply-templates>
<xsl:sort select="DENSITY"/>
</xsl:apply-templates>
</TABLE>
</BODY>
</HTML>
</xsl:template>
<xsl:template match="PLANET">
<TR>
<TD><xsl:apply-templates select="NAME"/></TD>
<TD><xsl:apply-templates select="MASS"/></TD>
<TD><xsl:apply-templates select="DAY"/></TD>
<TD><xsl:apply-templates
select="DENSITY"/></TD>
</TR>
</xsl:template>
</xsl:stylesheet>
Here are the results of this transformation:
<HTML>
<HEAD>
<TITLE>
Planets
</TITLE>
</HEAD>
<BODY>
<H1>Planets sorted by density</H1>
<TABLE>
<TD>Planet</TD><TD>Mass</TD><TD>Day</TD><TD>Density</TD>
<TR>
<TD>Venus</TD><TD>.815</TD><TD>116.75</TD><TD>.943</TD>
</TR>
<TR>
<TD>Mercury</TD><TD>.0553</TD><TD>58.65</TD><TD>.983</TD>
</TR>
<TR>
<TD>Earth</TD><TD>1</TD><TD>1</TD><TD>1</TD>
</TR>
</TABLE>
</BODY>
</HTML>
You can see this HTML page in Figure 13.2.
Figure 13.2 Sorting elements.
Note that, by default, <xsl:sort> performs an alphabetic
sort, which means that 10 will come before 2. You can perform a true
numeric sort by setting the data-type attribute to "number", like
this:
<xsl:sort data-type="number" select="DENSITY"/>
You can also create descending sorts by setting the
<xsl:sort> element's order attribute to "descending".
Using xsl:if
You can make choices based on the input document using the
<xsl:if> _element. To use this element, you simply set its test
attribute to an expression that evaluates to a Boolean value.
Here's an example. In this case, I'll list the planets one after
the other and add a HTML horizontal rule, <HR>, element after
the last element-but only after the last element. I can do that with
<xsl:if>, 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>
<HEAD>
<TITLE>
Planets
</TITLE>
</HEAD>
<BODY>
<xsl:apply-templates select="PLANET"/>
</BODY>
</HTML>
</xsl:template>
<xsl:template match="PLANET">
<P>
<xsl:value-of select="NAME"/>
is planet number <xsl:value-of
select="position()"/> from the sun.
</P>
<xsl:if test="position() =
last()"><xsl:element name="HR"/></xsl:if>
</xsl:template>
</xsl:stylesheet>
Here is the result; as you can see, the <HR> element appears
after only the last planet has been listed:
<HTML>
<HEAD>
<TITLE>
Planets
</TITLE>
</HEAD>
<BODY>
<P>Mercury
is planet number 1 from the sun.
</P>
<P>Venus
is planet number 2 from the sun.
</P>
<P>Earth
is planet number 3 from the sun.
</P>
<HR>
</BODY>
</HTML>
Using xsl:choose
The <xsl:choose> element is much like the Java switch
statement, which enables you to compare a test value against several
possible matches. Suppose that we add COLOR attributes to each
<PLANET> element in planets.xml:
<?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>
Now say that we want to display the names of the various planets,
formatted in different ways using HTML <B>, <I>, and
<U> tags, depending on the value of the COLOR attribute. I can
do this with an <xsl:choose> element. Each case in the
<xsl:choose> element is specified with an <xsl:when>
element, and you specify the actual test for the case with the test
attribute. Here's what it looks like:
<?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>
Planets
</TITLE>
</HEAD>
<BODY>
<xsl:apply-templates select="PLANET"/>
</BODY>
</HTML>
</xsl:template>
<xsl:template match="PLANET">
<xsl:choose>
<xsl:when
test="@COLOR = 'RED'">
<B>
<xsl:value-of select="NAME"/>
</B>
</xsl:when>
<xsl:when
test="@COLOR = 'WHITE'">
<I>
<xsl:value-of select="NAME"/>
</I>
</xsl:when>
<xsl:when
test="@COLOR = 'BLUE'">
<U>
<xsl:value-of select="NAME"/>
</U>
</xsl:when>
<xsl:otherwise>
<PRE>
<xsl:value-of
select="."/>
</PRE>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
</xsl:stylesheet>
Note also the <xsl:otherwise> element in this example, which
acts the same way as the default: case in a switch statement-that is,
if no other case matches, the <xsl:otherwise> element is
applied. Here is the result of this XSLT:
<HTML>
<HEAD>
<TITLE>
Planets
</TITLE>
</HEAD>
<BODY>
<B>Mercury</B>
<I>Venus</I>
<U>Earth</U>
</BODY>
</HTML>