10.3. Elements
Regardless of whether you consider XSLT to be a markup language, a scripting language, or just a pain in the fanny, it is, first and foremost, a dialect of XML and, therefore, must adhere to all of XML's rules. And I mean all of XML's rules because if it isn't well formed, then end of game. Fortunately, we've been there and done that already, which gives us the opportunity to look at the various XSLT elements available to us. Table 10-1 provides a high-level overview of these elementsnot quite an orbital overview, but close. Don't worry; we cover some of these elements in much greater detail shortly.
Table 10-1. XSLT ElementsElement | Attributes | Description |
---|
apply-imports | | Applies external templates that have been imported using the import element. | apply-templates | select optional mode optional | Applies templates that were defined locally. | attribute | name namespace optional | Specifies an attribute for the preceding element. | attribute-set | name use-attribute-sets optional | Defines a named set of attributes that can be used to specify a list of attributes en mass instead of individually. | call-template | name | Used to invoke a named template. | choose | | Indicates the beginning of a case structure. | comment | | Used to create comments in the output document. | copy | use-attribute-sets optional | Copies the current node and namespaces to the output document. However, it does not copy the children of the current node. | copy-of | select | Copies the node or nodes specified by the select attribute to the output document. | decimal-format | decimal-separator optional digit optional grouping-separator optional infinity optional minus-sign optional name optional NaN optional pattern-separator optional per-mille optional percent optional zero-digit optional | Defines the appearance of numbers formatted using the format-number() function. | element | name namespace use-attribute-sets optional | Used to create an element in the output document. | fallback | | Specifies to the XSL processor alternative code to run in case an XSL element is not supported. | for-each | select | Loops through the node set specified by the select attribute. | if | test | Executes the enclosed XSL when the result of the test is TRue. It is important to remember that no else clause exists for the if element. In these instances, the choose, when, and otherwise elements should be used. | import | href | Imports an external style sheet, which is the same as including a style sheet. | include | href | Includes an external style sheet, which is the same as importing a style sheet. | key | name match use | Defines a search key that is used to locate specific nodes based upon their value or the value of another node. | message | terminate optional | Writes a programmer-defined message to the output document. | namespace-alias | stylesheet-prefix result-prefix | Replaces the namespace specified with the stylesheet-prefix attribute on the input stylesheet with the namespace specified with the result-prefix attribute on the output document. | number | level optional count optional from optional value optional format optional lang optional letter-value optional group-separator optional grouping-size optional | Used to write a formatted number to the output document. | otherwise | | Defines the default action for a case structure (choose). | output | method optional version optional encoding optional omit-xml-declaration optional standalone optional doctype-public optional doctype-system optional cdata-section-elements optional indent optional media-type optional | Defines the format of the output document. | param | name select optional | Used to specify template, stylesheet, and transform input parameters. | preserve-space | elements | Defines the elements for which whitespace is to be preserved on the output document. | processing-instruction | name | Writes an XML processing instruction to the output document. | sort | select optional lang optional data-type optional order optional case-order optional | Sorts a node set. | strip-space | elements | Defines the elements for which whitespace is not to be preserved on the output document. | stylesheet | id optional extension-element-prefixes optional exclude-result-prefixes optional version | Defines the XSL document as a style sheet to the XSLT processor. | template | match optional name optional priority optional mode optional | Defines a template, which is essentially an XSL function. | text | disable-output-escaping optional | Indicates that the enclosed is text. | transform | id optional extension-element-prefixes optional exclude-result-prefixes optional version | Defines the XSL document as a style sheet to the XSLT processor, identical to the stylesheet element. | value-of | select disable-output-escaping optional | Writes the information specified by the select attribute to the output document. | variable | name select optional | Defines either a local or global variable to the XSLT processor. | when | test | Defines the individual cases of a case structure (choose). | with-param | name select optional | Defines the parameters to a template. |
10.3.1. In the Beginning
In the beginning, all your data was painted on the wall of a cave somewhere, and it was good. Depending on the available light, it was human readable, self-describing, colorful, and even pretty. Unfortunately, civilization has advanced to the point that cave paintings just can't express the sheer volume of information available to us today. Enter XML, which, like its distant ancestor, is also human readable, self-describing, and, if you're using an XML editor such as Stylus Studio, both colorful and pretty.
Although it might seem to some people that we've come full circle in our data storage, from cave paintings to XML, there is a distinct advantage to XML. Unlike a cave painting, which pretty much just sits there on the wall looking about the same as it did 40,000 years ago, XML is a bit more portable. With the addition of XSLT, XML is also elastic and flexible. I'm sold on the concept, how about you? Good. The only issue remaining is how to start developing an XSL style sheet.
All XSL style sheets begin with one of two elements, either the stylesheet element or the TRansform element. They are interchangeable because both do exactly the same thing, although I recommend not using the transform element during months with r's. Wait, maybe that was oystersI have a tendency to confuse the two.
The next part of the style sheet is the output element, which essentially describes the format of the output. This is where you make the commitment of whether the output document will be XML, HTML, text, or, gasp, even XSLT. Not big on commitment? Not a problem. Just leave out the output element, and the output defaults to XML. Of course, come to think of it, that, too, is a form of commitment.
The next "standard" part of an XSL style sheet is the first template, the one that starts the whole ball rolling. However, before we get there, I should point out that between the first element and the first template is where some really useful elements go. Parameters from the outside world and global variables are just two examples. In fact, let's take a look at Table 10-2, which indicates where elements can be defined in a style sheet and what effect location can have on their behavior.
Table 10-2. XSL Style Sheet Elements and Where They Can Be DefinedElement | Defined Where |
---|
apply-imports | Either root or element level | apply-templates | Either root or element level | attribute | Element level | attribute-set | Root level | call-template | Element level | choose | Element level | comment | Either root or element level | copy | Element level | copy-of | Element level | decimal-format | Root level | element | Element level | fallback | Element level | if | Element level | import | Root level | include | Root level | key | Root level | message | Element level | namespace-alias | Root level | number | Root level | otherwise | Element level | output | Root level | preserve-space | Root level | processing-instruction | Root level | sort | Element level | strip-space | Root level | stylesheet | Root level | template | Root level | text | Element level | TRansform | Root level | value-of | Element level | variable | Element level | when | Element level | with-param | Element level |
At last we've come to the first template of the style sheet. Unfortunately, it is kind of anticlimatic because 99 percent of all style sheets start with a template element that looks just like this:
Boring, isn't it? Yes, you can make it more specific and have it look for a particular element that should be in the input document. I don't recommend it, though, because it will only cause problems someday when, for some reason, that specific element is not in the input document. Then comes the inevitable yelling, the finger pointing, and the peasants with pitchforks and torches again. Not a pretty picture.
10.3.2. Templates and How to Use Them
After the initial template, the one that establishes the current location as the root, what are some of the other ways to use templates?
Earlier I stated that templates could have names, although it wasn't required. In XSLT, these named templates fill pretty much the same niche that functions do in a language such as JavaScript or PHP. They can accept parameters and return results. In my opinion, if it looks like a duck and walks like a duck, the odds are, it is a duck. Unless it is a goose, but that is kind of like duckzilla, so it isn't a problem.
Let's take a look at what a typical, although useless, named function looks like. Shown in Listing 10-6, its purpose is to accept two numbers, add them, and return the result.
Listing 10-6. Named Template
<xsl:template name="add">
<xsl:param name="a" />
<xsl:param name="b" />
<xsl:value-of select="number($a) + number($b)" />
</xsl:template>
|
Thankfully, this is one of those times when something both seems simple and actually is simple, as long as you remember that dollar signs aren't required at definition but are required when used. However, the same thing can't always be said for templates invoked using XPathbut before we go there, perhaps it would be better to take a look at two more mundane templates. Using the XML shown way back in Listing 10-4, the style sheets shown in Listings 10-7 and 10-8 do exactly the same thing in a slightly different manner.
Listing 10-7. A Pure XSLT Example
<?xml version="1.0"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:preserve-space elements="text"/>
<xsl:template match="/">
<xsl:element name="div">
<xsl:apply-templates select="//library"/>
</xsl:element>
</xsl:template>
<xsl:template match="library">
<xsl:element name="table">
<xsl:attribute name="width">100%</xsl:attribute>
<xsl:for-each select="book">
<xsl:element name="tr">
<xsl:for-each select="*">
<xsl:element name="td">
<xsl:attribute name="width">33%</xsl:attribute>
<xsl:value-of select="."/>
<xsl:if test="string-length(.) = 0">
<xsl:text> </xsl:text>
</xsl:if>
</xsl:element>
</xsl:for-each>
</xsl:element>
</xsl:for-each>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
|
Listing 10-8. An XSLT/XHTML Hybrid Example
<?xml version="1.0"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:preserve-space elements="text"/>
<xsl:template match="/">
<xsl:element name="div">
<xsl:apply-templates select="//library"/>
</xsl:element>
</xsl:template>
<xsl:template match="library">
<xsl:element name="table">
<xsl:attribute name="width">100%</xsl:attribute>
<xsl:for-each select="book">
<tr>
<xsl:for-each select="*">
<td width="33%">
<xsl:value-of select="."/>
<xsl:if test="string-length(.) = 0">
<xsl:text> </xsl:text>
</xsl:if>
</td>
</xsl:for-each>
</tr>
</xsl:for-each>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
|
Confused? Don't be. Because XSLT and XHTML are both dialects of XML, there is absolutely nothing wrong with mixing the two. At first glance, the style sheet shown in Listing 10-8 might seem to be a little like a mutt, part this and part that. But as weird as it seems, it is much more common than the purebred solution from Listing 10-7.
Earlier I stated that templates invoked using XPath aren't always simple because, at times, more than one template matches. If you don't expect this, it could, at the very least, be an embarrassment. However, there is a way to specify which template to use when more than one matches the criteria.
The mode attribute, which is on both the template and apply-templates elements, is used to specify which template to use when a particular select could result in more than one match. Listing 10-9, a merging of Listings 10-7 and 10-8, has an example of this. The only difference, other than the merging, is the addition of a mode attribute for the mutt template and a new applytemplates element, also with a mode attribute.
Listing 10-9. Distinguishing Template Matches Using Mode
<?xml version="1.0"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:preserve-space elements="text"/>
<xsl:template match="/">
<xsl:element name="div">
<xsl:apply-templates select="//library" />
<xsl:apply-templates select="//library" mode="mutt" />
</xsl:element>
</xsl:template>
<xsl:template match="library">
<xsl:element name="table">
<xsl:attribute name="width">100%</xsl:attribute>
<xsl:for-each select="book">
<xsl:element name="tr">
<xsl:for-each select="*">
<xsl:element name="td">
<xsl:attribute name="width">33%</xsl:attribute>
<xsl:value-of select="." />
<xsl:if test="string-length(.) = 0">
<xsl:text> </xsl:text>
</xsl:if>
</xsl:element>
</xsl:for-each>
</xsl:element>
</xsl:for-each>
</xsl:element>
</xsl:template>
<xsl:template match="library" mode="mutt">
<xsl:element name="table">
<xsl:attribute name="width">100%</xsl:attribute>
<xsl:for-each select="book">
<tr>
<xsl:for-each select="*">
<td width="33%">
<xsl:value-of select="." />
<xsl:if test="string-length(.) = 0">
<xsl:text> </xsl:text>
</xsl:if>
</td>
</xsl:for-each>
</tr>
</xsl:for-each>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
|
The mode attribute provides additional criteria for the match. Instead of the XPath being the only criteria, the mode is also used. So a simple XPath match alone is not enough; there also has to be a match to the mode. This leads to some interesting possibilities, such as when the mode name is unknown. Just use an asterisk as the mode name and use the mode to indicate the depth, or something along those lines.
10.3.3. Decisions, Decisions
As in the majority of programming languages, XSLT provides flow control in the way of decision structures. Excluding apply-templates, which can be used for some similar functionality, there is the if element and a case structure, called choose. Basically, it is all easy stuff, but two issues with XSLT decisions can cause many developers problems.
The first of these issues is how to test for greater than and less than, and still keep the document well formed. Fortunately, the previous chapter covered this problem when discussing XPath. The only remaining issue is one that causes quite a number of headaches: a lack of an else for the if element.
Lack of an else might seem like, if not an insurmountable problem, at least an annoying problem. Because of this lack, the choose element is used more often in languages with an else. Listing 10-10 is an example of a workaround for this lack of an else statement.
Listing 10-10. A Workaround
<?xml version='1.0'?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<xsl:variable name="value" select="7" />
<xsl:element name="div">
<xsl:choose>
<xsl:when test="($value mod 2) = 0">Even</xsl:when>
<xsl:otherwise>Not even</xsl:otherwise>
</xsl:choose>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
|
10.3.4. Sorting Out Looping
XSL style sheets have a built-in mechanism for sorting node sets, which can be rather useful when information needs to be arranged in a specific sequence. As with everything in XSL, sorting is accomplished through the use of an element, which, appropriately, is called sort. Interesting how these things work out, isn't it?
Listings 10-11 and 10-12 both show examples of the use of the sort element, with a couple minor differences. For example, Listing 10-11 uses a for-each element to navigate through the node set, which is sorted into ascending sequence. In Listing 10-12, an apply-templates is used, and the node set is sorted into descending sequence.
Listing 10-11. A for-each Sort Example
<?xml version='1.0'?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<xsl:element name="table">
<xsl:attribute name="width">100%</xsl:attribute>
<xsl:for-each select="//book">
<xsl:sort select="title" order="ascending" />
<xsl:element name="tr">
<xsl:for-each select="*">
<xsl:element name="td">
<xsl:value-of select="." />
</xsl:element>
</xsl:for-each>
</xsl:element>
</xsl:for-each>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
|
Listing 10-12. A template sort Example
<?xml version='1.0'?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:template match="/">
<xsl:element name="table">
<xsl:attribute name="width">100%</xsl:attribute>
<xsl:apply-templates select="//book">
<xsl:sort select="title" order="descending" />
</xsl:apply-templates>
</xsl:element>
</xsl:template>
<xsl:template match="*">
<xsl:element name="tr">
<xsl:for-each select="*">
<xsl:element name="td">
<xsl:value-of select="." />
</xsl:element>
</xsl:for-each>
</xsl:element>
</xsl:template>
</xsl:stylesheet>
|
|