XSLT has defined a set of built-in functions that can be used in expressions.
These are complementary to the functions already available through XPath. These
XPath functions include string functions like starts-with()
, numeric
functions like sum()
and others like id()
. These have
been covered earlier in this chapter. We will not describe all available XSLT
functions here; we will just select a few. You can find all functions in Appendix
D.
format-number(number, string, string)
The format-number
function converts the numeric first argument
to a string. To do this, it uses the second argument as a format specifier and
the third (optional) argument as a reference to a decimal-format
element. First, we will look how the function behaves if we do not specify our
own decimal formats. Say we leave the third parameter out. The format that we
can pass to the second parameter can hold two formats at a time: one for positive
numbers, one for negative numbers. They are separated by the semicolon. The
format is built up from these special characters:
Symbol |
Meaning |
|
digit |
|
digit, hides leading and trailing zeroes |
|
decimal separator |
|
grouping separator |
|
negative prefix |
|
multiply by 100 and show as percentage |
|
multiply by 1000 and show per mille |
|
any other character can serve as prefix or suffix |
So using this function would give the following results:
Numeric Value |
Format |
Result String |
2 |
|
DM 2.00 |
2 |
|
DM 2.00 |
Numeric Value |
Format |
Result String |
12.3456 |
|
012.35 |
12.3456 |
|
12.35 |
123.456 |
|
1,23.46 |
0.456 |
|
45.6% |
Different countries use different characters for separating the decimal part
of a floating-point number from the integer part and for grouping the digits.
That's why XSLT allows you to change the special characters to other symbols.
This can be done by including an xsl:decimal-format
element as
a top level element in your document. The format-number
function
can refer to this decimal format by name. Consider this example:
<xsl:decimal-format
name = qname
decimal-separator
= char
grouping-separator = char
infinity = string
minus-sign = char
NaN = string
percent = char
per-mille = char
zero-digit = char
digit = char
pattern-separator = char />
The name
attribute is for referring to the format from a format-number
function. All other attributes are for overruling the default character. This
new character must be used in specifying the format string and will also be
the output character:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
>
<xsl:decimal-format name="numfrmt" decimal-separator=","
grouping-separator="." />
<xsl:template match="/">
<xsl:value-of select="format-number(1567.8, '#.###,00', 'numfrmt')"/>
</xsl:template>
</xsl:stylesheet>
The output of this stylesheet will be 1.567,80.
current()
The current()
function returns the current context. This can be
very useful inside subqueries in XPath expressions. It allows you to make constructs
in an XPath expression that are similar to SQL inner joins, combining and comparing
values from different contexts. Suppose we have this XML document:
<?xml version="1.0"?>
<Music>
<Song>
<title>Dancing in the street</title>
<artist>David Bowie</artist>
<artist>Mick
Jagger</artist>
</Song>
<Song>
<title>State of shock</title>
<artist>Michael
Jackson</artist>
<artist>Mick Jagger</artist>
</Song>
...
We want to generate a list of all songs in the collection with their artists,
but for every artist we want to include a link to the other songs in the collection
that the artist performs. We will make the list in HTML, creating links from
the artist's songs to the entry of that song. To do this, we use a template
for the artist
element like this:
<xsl:template match="artist">
<xsl:for-each
select="//Song[artist/text() = current()/text()]
[title != current()/ancestor::Song/title]
">
<a>
<xsl:attribute name="href">#Song<xsl:number/></xsl:attribute>
<xsl:value-of select="title"/>
</a><br/>
</xsl:for-each>
</xsl:template>
It generates a piece of XML looking like this:
<a href="#Song3">Under pressure</a>
<br>
<a href="#Song4">Knock on wood</a>
<br>
The essential part here is the XPath expression in the for-each
element. It selects the set of all song
elements and filters the
set by introducing two predicates.
The first predicate: [artist/text()
checks if the text of the
=
current()/text()]artist
element (relative to the selected
song
element) is equal to the text of the current context. The
current context is the context of the for-each element, not the context of the
predicate. This will select those song elements that have an artist
element with the same name as the current artist. This will include the current
song (which obviously has the same title as the current song).
The second predicate: [title
checks if the title of the predicate's context (a
!=
current()/ancestor::Song/title]song
element)
is equal to the title of our current context's song ancestor. The element is
included in the filtered set if the titles are not equal. The function of this
predicate is to remove the song itself from the list of references to other
songs. The complete stylesheet, called artist.xsl
, can be downloaded
from the Wrox web site.
document(object, node-set)
The document()
function is specified to combine information from
several documents into one destination document. Suppose you have a directory
with a load of articles in XML format. All have references to other documents
in the same directory. The part that defines the references to other articles
looks like:
<Article>
<Authors>
<Author>Teun
Duynstee </Author>
</Authors>
<Title>An
interesting article </Title>
<Intro> ... </Intro>
<Body> ... </Body>
<Related>
<Item type="URL" loc="http://www.asptoday.com/art2">Some
other article</Item>
<Item type="local" loc="2"/>
</Related>
</Article>
As you can see, there are two kinds of references; references by URL and local
references. The local references point to other articles of the same format
in the same directory. The files are called art1.xml
, art2.xml
,
etc… The local reference in the example refers to art2.xml
(indicated
by loc="2"
).
What we would like to do now is to generate HTML documents that display a styled version of the XML article. The referenced articles are particularly tricky. We want them to appear like this:
- Some other article [external]
- A great article (by: James Britt)
Note how the first article is displayed using only content from within the source document, while the second reference displays data about the referenced document that is not available in the source (the title and author). How is this done?
<xsl:template match="Item">
<xsl:if test="@type='URL'">
<a href="{@loc}"><xsl:value-of select="."/></a>
</xsl:if>
<xsl:if test="@type='local'">
<a href="art{@loc}.xml"><xsl:value-of select="document(concat('art',
@loc,
'.xml'))/Article/Title"/></a>
(by: <xsl:apply-templates select="document(concat('art', @loc,
'.xml'))/Article/Authors/Author"/>)
</xsl:if>
<br/>
</xsl:template>
The template for Item
elements is split into two parts. The first
part is executed if an Item
has type='URL'
. This generated
an HTML link with the loc
attribute as its href
attribute
and the contained text as its title. This is simple – we've done this before.
Note the use of an attribute value template in the literal element a
.
The second part of the template is more interesting. If the type of the reference
is local
, then the href
is constructed in a specific
way, concatenating a string together. Now for the title of the referred document:
from the select
attribute of a value-of
element, we
call the document function, passing it the relative URL and a small XPath expression,
indicating the fragment that is referred to. The result is that the value-of
action is executed on the title of the referred document, effectively outputting
the content of the title
element. After that, you see the apply-templates
being used with a remote fragment.
In this example, we used only one parameter, a string value. It is also possible
to pass in a node set. If you do so, the document()
function will
convert all nodes to their string values and use these as URIs. The document()
function then returns a node set of external documents. The second, optional,
parameter indicates the base URI to use for relative URI references.
generate-id(node-set)
The generate-id()
function does just what you would expect: it
generates a unique identifier. The identifier is always a string of alphanumeric
characters, making sure that it can be used as an XML qualified name (think
of filling an ID type attribute). The identifier depends on the first element
in the passed node set. If no node set is passed, the context node is used.
The implementation is free to choose any convenient way to generate a unique
string, as long as it is always the same string for the same node.
Comments