Using XML Queries and Transformations

Built-in Functions

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

0

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

X

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 .00

DM 2.00

2

DM ##.00

DM 2.00

Numeric Value

Format

Result String

12.3456

000.00

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()
=
current()/text()]
checks if the text of the 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
!=
current()/ancestor::Song/title]
checks if the title of the predicate's context (a 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.

You might also like...

Comments

Contribute

Why not write for us? Or you could submit an event or a user group in your area. Alternatively just tell us what you think!

Our tools

We've got automatic conversion tools to convert C# to VB.NET, VB.NET to C#. Also you can compress javascript and compress css and generate sql connection strings.

“Computer science education cannot make anybody an expert programmer any more than studying brushes and pigment can make somebody an expert painter” - Eric Raymond