|
The Developer's Resource & Community Site
|
(Reproduced with kind permision of Wrox Press: https://www.wrox.com)
Previous Page...
eval and script
First of all a warning: be careful – the eval
and script
elements are not specified in the XSLT recommendation. They are Microsoft extensions, available only in their MSXML
implementation. So, if you want to be able to switch XSLT implementations later
on, do not use these features.
Still, the elements are so convenient that we will cover
them here. After all, most Visual Basic programmers will be using the MSXML
library anyway. It is the most widely available implementation on the Windows
platform. Microsoft has stated that they will continue support for the eval
and script
elements in later versions, so if you plan to stick with Microsoft, there is no
reason for not using eval.
So what is it then? The eval element allows you to generate a
text node in the destination document using script. If you have created Active
Server Pages or Windows Scripting Host scripts before, you will be familiar
with the ActiveX Scripting Engine. This is a generic scripting platform that
several languages can be plugged into. Standard available scripting languages
are VBScript and JScript.
The script node can hold a piece of script that can hold
function definitions. These can be called from the eval
element.
This way you can do something like this:
<xsl:template
match="TEMP[@scale='F']">
<xsl:eval language="VBScript">
Celsius(this.getAttribute('value'))
</xsl:eval>
</xsl:template>
<xsl:script
language="VBScript"><![CDATA[
Function Celsius(fDegrees)
Celsius = (fDegrees - 32) * 5 / 9
End
Function
]]>
</xsl:script>
If this template is used on an XML element like this:
<TEMP scale="F"
value="80"/>
It would generate this in the output document:
Using this extension, more complex calculations can be
placed inside the script block. As scripting languages will sometimes contain
characters that are not allowable in XML, it is good practice to place the
script blocks in CDATA nodes.
The eval element contains a function call
to the Celsius
function in the script element. The return value of the function is
outputted by the eval element to the destination document. Note the use
of this
to refer to the context node.
Functions defined inside a script
element can be called from an eval element, but also from
attributes that are evaluated as an expression (see for example the if
element in the section "Control of Flow").
Commands
apply-templates
The xsl:apply-templates element
is the most typical example of a command element (an element that starts the
processing of a node). Because it is so important, we have already covered it
before. However, there are more ways to invoke another template.
apply-imports
When we described the import element, we saw that sometimes the main XSLT document contains templates that
match the same XPath expression as one of the templates in the imported document.
In these cases, the template in the main document overrides the imported
template. Using this feature, XSLT authors can create new transformations based
on existing ones, extending them with new templates or changing existing
templates. If you are familiar with object-oriented design, you will like this
idea.
If you are overruling a template in your document, you will
often want to invoke the original template from your newer implementation.
Let's look at an example.
The source document contains data about books and the data
about a book's author is always coded in an AUTHOR tag. A typical AUTHOR
tag looks like this:
<AUTHOR firstname="Teun"
lastname="Duynstee" initials="L.W.A."
nobelprize="no"/>
Several different XSLT transformations exist in our organization,
all dealing with book information. Some repeating transformations were put
together in a stylesheet that is frequently imported into other XSLT documents.
It looks like this:
<?xml version="1.0"?>
<xsl:stylesheet
xmlns:xsl="https://www.w3.org/1999/XSL/Transform">
<!—- many other templates -->
<xsl:template
match="AUTHOR">
<xsl:value-of match="@firstname"/>
<xsl:text> </xsl:text>
<xsl:value-of match="@lastname"/>
</xsl:template>
</xsl:stylesheet>
The template transforms any matched AUTHOR
attribute to a text node consisting of the firstname attribute joined to the lastname
attribute by a single space. In our sample source, the output would be
something like:
Now we want to write an XSLT document that will transform
XML data about a book into an HTML document. In the HTML document, the author's
full name should appear, formatted in italics. We already have a transformation
rule that creates the full name of the author, but it does no formatting. We
can now import the standard author transformation and modify it slightly in our
overriding template:
<?xml version="1.0"?>
<xsl:stylesheet
xmlns:xsl="https://www.w3.org/1999/XSL/Transform">
<xsl:import
href="booklib.xsl"/>
<xsl:template
match="AUTHOR">
<I><xsl:apply-import/></I>
</xsl:template>
</xsl:stylesheet>
The new template just specifies the literal element I
that will cause italic fonts in HTML, and inside the I
element, it calls the template which it overrules from the import. The result
would be this:
call-template
The call-template element is another
element that can be used to organize
the templates in your document. For calling a template with call-template,
the template must have a name attribute. It works like apply-templates,
but there is no change of context node. In fact call-template
is very much like calling a function in a procedural programming language.
Here's an example:
<xsl:template name="fullname
output">
<xsl:value-of match="@firstname"/>
<xsl:text> </xsl:text>
<xsl:value-of match="@lastname"/>
</xsl:template>
<xsl:template
match="AUTHOR[@nobelprize='no']">
<xsl:call-template name="fullname output"/>
</xsl:template>
<xsl:template
match="AUTHOR[@nobelprize='yes']">
<B>
<xsl:call-template name="fullname output"/>
</B>
</xsl:template>
The two templates for AUTHOR elements (for Nobel prize
winners and others) both call the same template for formatting the attributes
into a full name string.
If a template is called by name the match
attribute is ignored and vice versa.
What if Several Templates Match?
If the XSLT processor is searching for a template to use for
transforming a certain node, it's possible that it will find several templates
that match (and are of the same mode – check the coming section on 'Modes').
The XSLT specification defines a set of rules to determine which template is
the most appropriate. These rules are quite complex, but the general idea is not so hard to understand. In order of
increasing importance:
q
An imported template is less important than one
associated with the document (i.e. not imported).
q
The template imported later is more important than one
imported earlier.
q
A high priority template is more important than a low
priority template.
q
A more specific match attribute is more important
than a more general matching expression.
When the processor starts searching for an appropriate
template, it first creates a set of all available templates. From this set, it
removes all templates that don't match. After that it removes templates that
have a lower import precedence than others. When several documents are
imported, and especially when imports are nested, things can get rather
complicated. What the processor does is to build a tree of
imported documents. Suppose Document A imports B and C (in that order), B
imports D and C imports E, the tree would look like this:
Note how Document B is lower in the tree than Document C.
This is because imports that occur later in the document prevail over imports
done before. The higher in the tree a document is (in an absolute sense, not in
a hierarchical sense), the higher the import precedence. The built-in templates
are treated as if they are imported at the very beginning of the main document
and therefore have the lowest priority of all.
If several templates remain (i.e. there is more than one
matching template in the same document), the template with the highest priority
is chosen. The priority of a template can be set using the priority
attribute on the template element. This attribute can have any numeric
value, both positive and negative. If the priority attribute is not set, a
default priority is calculated for the template. This default priority depends
on the match
attribute. It is always a value between –0.5 and 0.5. Priority values are
assigned as follows:
Kind of Pattern
|
Examples
|
Priority
|
Specified name along the child or attribute axis
|
child::PARA
TITLE
@fullname
|
0
|
Unspecified name along the
child or attribute axis
|
*
attribute::*
|
-0.25
|
Only a node test
|
node()
text()
processing-instruction()
|
-0.5
|
All other cases
|
PARA/LINE
preceding::*
*[@*='yes']
|
0.5
|
If several templates exist in the same document with the same
priority it causes an error, but most XSLT processors will not stall but will
instead pick the one nearest to the bottom of the document. This happens quite
often. In many cases, developers use no priority attributes. Because many
matching patterns cause a priority of 0.5, templates with the same priority
will no doubt occur.
Modes
If you need to have some templates used in some special cases, but not always, you can try using different
modes. The mode
attribute is an optional attribute on both the template
and apply-templates
elements. By calling apply-templates with the mode
attribute set, you instruct the processor to use only templates that have the
same mode
attribute. This way, you can create several sets of templates that don't
interfere with each other. Each mode also has its own built-in templates.
Looking at the code sample below, we have two templates now
that both match on AUTHOR, but one is in the default mode and the other one
in the formal mode:
<xsl:template
match="AUTHOR">
<xsl:value-of match="@firstname"/>
<xsl:text> </xsl:text>
<xsl:value-of match="@lastname"/>
</xsl:template>
<xsl:template match="AUTHOR"
mode="formal">
<xsl:value-of match="@initials"/>
<xsl:text> </xsl:text>
<xsl:value-of match="@lastname"/>
</xsl:template>
<xsl:template
match="BOOK[@target-audience='management']">
Authors:
<xsl:apply-templates select="AUTHOR"
mode="formal"/>
</xsl:template>
In formal mode, the name of the author is generated in a
more formal format (using initials instead of first name). The template that
processes the content of BOOK elements that have their target-audience
attribute set to 'management' decides to display the authors' names in a
formal way using the 'formal' mode. Presumably, other templates invoke the
default templates for AUTHOR. Removing all templates with another mode
attribute from the set happens before anything else.
The IE5 Case
When IE5 was released, the XSLT specification was far from
ready, so the implementation shipped in IE5 does not conform to the
recommendation, more on that in the section "The IE5 Implementation".
One word of caution though should be mentioned here. The MSXML library in IE5 does
not implement any of the rules for assessing priorities between templates.
The strategy followed by the IE5 XSLT engine is shockingly
simple – it tries to find a matching template in the document from the bottom
upwards. As soon as it finds one, it stops. This means that templates placed
lower in the document automatically have a higher priority. For example, if you
had this code:
<xsl:template
match="AUTHOR">
<I><xsl:value-of select="@name"/></I>
</xsl:template>
<xsl:template match="*">
<xsl:apply-templates/>
</xsl:template>
IE5 will never use the first template, because it will
always find and use the lower one, which matches AUTHOR
elements just as well as the first template. Be aware of this behavior when
developing for IE5.
message
A very special and not very commonly used element is the message
element. It is designed to give stylesheet authors the opportunity to issue
warnings and error messages at certain times. Only one attribute can be
specified: terminate.
If terminate
is set to yes,
the processor stops after issuing the message, if it is set to no,
the processor will attempt to continue.
The specification does not specify what a processor should
do when such a message is issued. Possibilities could include raising errors,
displaying message boxes, or writing to a log file. The command line based
processors mentioned before (SAXON and XT) will generate a message on the
console, while the developer's preview of MSXML does not yet implement xsl:message.
The content of the message element will be the content
of the issued message. This example will display a warning whenever the
stylesheet is used on a PET element.
<xsl:template match="PET">
<xsl:message>
Warning: Displaying information on pets in not yet implemented
</xsl:message>
</xsl:template>
Control of Flow
Like most programming languages, XSLT includes a few
keywords to let you control the flow of the processing. These include if/then
construct, while
loops and some extras. Like anything in XSLT, these features are implemented as
XML elements.
if
The if element does one of the most common things in programming – checking a condition
and executing an action if the condition is met. A simple example is:
<xsl:template
match="AUTHOR">
<xsl:if test="@name">
<I><xsl:value-of select="@name"/></I>
</xsl:if>
</xsl:template>
The if element has a test
attribute. If the XPath expression in it returns false
(an empty node list), the content of the element is not executed. In the
example, the XPath query @name returns true
if an attribute with name name exists. The if
element cannot be combined with an else element (as is common in many
languages). If you need an else, you have to use the choose/when/otherwise
construct. Several if elements can be used nested.
choose/when/otherwise
For more complex choices the choose,
when
and otherwise
elements can be used. The elements when and otherwise
can only be used within a choose
element. The processor will check every when element from the top down. A when
element works exactly like an if element. The first time the test
attribute returns true, the content of that when
element is executed. After this, the processor jumps past the choose
element without further checking. If none of the when
elements can be executed, the otherwise element will be executed
(if there is one).
<xsl:template
match="AUTHOR">
<xsl:choose>
<xsl:when test="@name = 'Duynstee'">
<B><xsl:value-of select="@name"/></B>
</xsl:when>
<xsl:when test="@name">
<I><xsl:value-of select="@name"/></I>
</xsl:when>
<xsl:otherwise>
No name available
</xsl:otherwise>
<xsl:choose>
</xsl:template>
In this example, the name attribute of the AUTHOR
element we are processing is first checked for being 'Duynstee'. If it is, the
value of the attribute it generated in the output as content of a B
element and the processor jumps past the xsl:choose element in the stylesheet.
If it is not, the next check is performed. If this is the case, the value of
the name attribute is generated as content of an I
element. If the second check fails too, then (and only then) will the processor
execute the content of the xsl:otherwise element, which means
generating the string 'No name available'.
for-each
For looping through a set of nodes,
XSLT specifies the for-each element. The for-each element has a select
attribute holding an XPath expression. The content of the for-each
element is executed for each result from this query. Inside the loop, the
context is moved to the current result set element.
<xsl:template
match="PUBLISHER">
<xsl:for-each
select="child::BOOK">
<xsl:value-of select="attribute::title"/>
<xsl:for-each select="child::AUTHOR">
<xsl:value-of select="attribute::name"/>
</ xsl:for-each>
<xsl:text> </xsl:text><!—Insert a new line after
each book -->
</ xsl:for-each>
</xsl:template>
In this example, for-each elements are nested. Because
the context moves with the for-each loop, the select
attribute of the inner loop is evaluated in the context of the results of the
outer loop. This is of course what you would expect.
Note that there is not much difference between an apply-templates
element with an appropriate template and a for-each loop. Basically, a for-each
loop is an in-line template. However, a for-each loop is generally more easy
to read, but separating the content over several templates makes reuse of the
inner template easier.
sort
Whenever XSLT iterates along a set
of nodes, it can be useful to set the order of iteration. By default this is
always document order, but the destination format may expect another order (or
you are transforming data to HTML and you need to display in a set order).
Sorting can be used on the elements apply-templates
and for-each.
The sort
element is inserted as a child element of the apply-templates
or for-each
element. Consider this example:
<xsl:template
match="FAMILY">
<xsl:apply-templates select="PERSON">
<xsl:sort select="@lastname"/>
<xsl:sort select="@firstname"/>
</xsl:apply-templates>
</xsl:template>
The select attribute holds an XPath
expression. This expression is evaluated for each of the nodes in the set. The
result of this expression determines the position in the sorted set.
By inserting multiple sort elements, the result set can be
sorted primarily on the first criterion, and secondarily on the second one. In
this case, the apply-templates
element selects a set of all PERSON elements. These are sorted
first on their lastname
attribute, then on their firstname attribute. Only after the
sorting does the XSLT processor start searching for the appropriate matching
templates.
A number of extra optional attributes can be used with the sort
element. The most important ones are:
q
order, for specifying 'ascending' or 'descending'
sort order.
q
data-type, for sorting numerically
or alphabetically.
The default is alphabetically,
causing 10 to be smaller than 9. Available values for data-type
are 'text'
and 'number'.
Other values can also be used, but the meaning is not specified by the XSLT specification.
q
case-order, for specifying lower-first
or upper-first.
When case-order
is lower-first,
A sorts after a. There is no functionality for case insensitivity.
Previous Page...
Next Page...
©1999 Wrox Press Limited, US and UK.
|