Chapter 9. The XPath 2.0 Numeric, Constructor, and Context Functions

Introducing the XPath 2.0 Functions

This chapter starts our coverage of the many functions built into XPath 2.0. Although we've seen structural changes in XPath 2.0 compared to XPath 1.0 in the preceding two chapters, on a sheer size basis, there's no question that all the new functions added to XPath 2.0 are the biggest change.

The specification for the XPath 2.0 functions appears in the document XQuery 1.0 and XPath 2.0 Functions and Operators, which is at http://www.w3.org/TR/xquery-operators.

This document is an integral part of the XPath 2.0 specification. Not only are these functions now built into XPath 2.0, but they're also built into XML Query 1.0 and XSLT 2.0. The functions we're going to discuss in this and the following chapters were specifically designed to work on the XPath 2.0 data types, which we covered in Chapter 7, “Introducing XPath 2.0?”

Formally, each function is given by its signature, which shows you how to use the function. A signature shows what parameters you pass to a function, and the type of return value, if there is one:

function-name($parameter-name as parameter-type, ...) as return-type

For example, the signature of the node-name function, which returns the name of the node you pass to it as a string, looks like this:

node-name($srcval as node) as xs:string

Function Namespaces

The function name itself for all the functions we'll be taking a look at is an XML QName, followed by parentheses. The functions and operators in the XQuery 1.0 and XPath 2.0 Functions and Operators document use various namespaces, which you should know about.

For data types, the xs namespace is used for standard XML-schema types, as before; this namespace corresponds to “http://www.w3.org/2001/XMLSchema”. And the xdt namespace, which corresponds to “http://www.w3.org/2003/05/xpath-datatypes”, is the namespace for the data types specifically added for XPath 2.0.

The namespace for functions is identified by the fn prefix. Its value is “http://www.w3.org/2003/05/xpath-functions”. You can call functions with this namespace prefix; for example, the node-name function is referred to as fn:node-name in the XQuery 1.0 and XPath 2.0 Functions and Operators document:

fn:node-name($srcval as node) as xs:string

Saxon supports many of the XPath 2.0 functions we're going to see, but you do not use the fn prefix when calling these functions in Saxon.

There's also a fourth prefix defined in the XQuery 1.0 and XPath 2.0 Functions and Operators document—the op prefix, which corresponds to the namespace “http://www.w3.org/2003/05/xpath-operators for operators”, and which you use with operators. The operator functions are included in the XQuery 1.0 and XPath 2.0 Functions and Operators document to make clear how the various XPath 2.0 operators work on various data types, and they're there to back up the standard operators, which we saw in Chapter 8, “XPath 2.0 Expressions, Sequences, and Operators.” For example, the * operator on numeric values has a corresponding function named op:numeric-multiply:

op:multiply($operand1 as numeric, $operand2 as numeric) as numeric

The op functions are not directly accessible in XPath 2.0, so you cannot call them directly (some software may make them accessible, but Saxon does not). They're in the specification to make it clear how the operators we covered in Chapter 8 work on various data types—we've already covered those operators, so we won't give much coverage to the op functions here. However, if you need to know exactly how, say, xs:date values work when you subtract one from another, you can get all the details by looking at how op:subtract-dates is defined for xs:date values in the XQuery 1.0 and XPath 2.0 Functions and Operators document.

The XQuery 1.0 and XPath 2.0 Functions and Operators document starts off with the specification for the accessor functions, which work on nodes, and we'll start there too.

Using Accessor Functions

The XPath 2.0 Data Model describes a set of accessor functions for use with different types of nodes, and some of those accessor functions are made available through general functions. Here they are:

  • fn:node-name returns the name of a node.

  • fn:string returns the string value of the argument.

  • fn:data takes a sequence of items and returns a sequence of atomic values.

  • fn:base-uri returns a node's base URI.

  • fn:document-uri returns the document's URI.

We'll take a closer look at these functions here.

The fn:node-name Function

The fn:node-name function returns the expanded name of nodes that can have names (for other node kinds, it returns the empty sequence). Here is the signature for this function—the question mark at the end of the return type indicates in XPath 2.0 that the empty sequence can be returned:

fn:node-name($srcval as node) as xs:QName?

Expanded names consist of a pair of values, a namespace URI, and a local name. In consequence, the way this function actually returns its values is implementation-specific.

The fn:string Function

The fn:string function returns the string value of a node. Here's how you use this function—this function can take one argument or an empty sequence, which XPath represents by adding a question mark after the argument type:

fn:string($srcval as item?) as xs:string

Here, fn:string returns the value of $srcval represented as a xs:string. If you don't pass an argument to this function, $srcval defaults to the context item (which is represented by a dot, .). If you pass an empty sequence, you'll get an empty string, “”, back.

String representations in XPath 2.0 are nearly the same as in XPath 1.0, with some small differences—for example, the representations of positive and negative infinity are now 'INF' and '-INF' rather than 'Infinity' and '-Infinity'. You can see an example in ch09_01.xsl, Listing 9.1—in this case, we're displaying the string value of the <planet> elements in our planetary data document.

Example 9.1. An XSLT Example Using the XPath Function fn:string (ch09_01.xsl)

<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns
An XSLT Example Using the XPath Function fn:string (ch09_01.xsl):xs="http://www.w3.org/2001/XMLSchema">

    <xsl:template match="//planet">
        <xsl:value-of select="string(.)"/>
    </xsl:template>

</xsl:stylesheet>

You can see the results when you apply this stylesheet to our planetary data document, including the string value of each <planet> element:

C:Saxon>java net.sf.saxon.Transform ch02_01.xml ch09_02.xsl
<?xml version="1.0" encoding="UTF-8"?>

        Mercury
        .0553
        58.65
        1516
        .983
        43.4

        Venus
        .815
        116.75
        3716
        .943
        66.8

        Earth
        1
        1
        2107
        1
        128.4

This function is handy for turning data of various types into strings. Because of XPath 2.0's reliance on strong data typing, this is an important function—for example, if you want to pass a non-string variable's value to a string function like fn:concat, you should convert that value to a string using the fn:string function first.

The fn:data Function

The fn:data function converts a sequence of items (which can include nodes and atomic values) into a sequence of atomic values. Here's how you use this function in general—the * symbol here means “zero or more of,” which is how you represent a sequence in function signatures:

fn:data($srcval as item*) as xdt:anyAtomicType*

If an item in the passed sequence is already an atomic value, it's returned in the returned sequence. On the other hand, if an item in the passed sequence is a node, its typed value is returned (see http://www.w3.org/TR/xpath-datamodel/#dm-typed-value for a description of exactly how typed values for nodes are calculated). If the node has not been validated, its typed value is simply its string value, given the type xdt:untypedAtomic.

The fn:base-uri Function

This accessor function returns the base URI for a node. Here's how you use this function:

fn:base-uri($srcval as node) as xs:string?

In XML documents, you set a base URI with the XML xml:base attribute. You can see an example in ch09_03.xml, where we're setting a base URI for our planetary data document in a new version, ch09_02.xml (Listing 9.2).

Example 9.2. Setting a Base URI (ch09_02.xml)

<?xml version="1.0"?>
<?xml-stylesheet type="text/xsl" href="ch01_02.xsl"?>
<planets xml:base="http://www.XPathCorp.com">

    <planet>
        <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>
        <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>
        <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 we can determine the base URI of each <planet> element in a new XSLT 2.0 stylesheet, as you see in ch09_03.xsl (Listing 9.3).

Example 9.3. Reading a Base URI (ch09_03.xsl)

<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns
Reading a Base URI (ch09_03.xsl):xs="http://www.w3.org/2001/XMLSchema">

    <xsl:template match="//planet">
        <xsl:value-of select="base-uri(.)"/>
    </xsl:template>

</xsl:stylesheet>

And here's the result when you apply this stylesheet to our new XML document—you can see the base URI of the <planet> elements in this example:

C:Saxon>java net.sf.saxon.Transform ch09_03.xml ch09_04.xsl
<?xml version="1.0" encoding="UTF-8"?>
    http://www.XPathCorp.com
    http://www.XPathCorp.com
    http://www.XPathCorp.com

The fn:document-uri Function

When you pass it a node, the fn:document-uri function returns the URI of the document that contains the node, which is useful, for example, if you want to display an error that lists the current document's name. Here's how you use this function:

fn:document-uri($srcval as node) as xs:string?

You can see an example putting fn:document-uri to work in ch09_04.xsl (Listing 9.4). Here, we're simply displaying the URI of the current document to test this function.

Example 9.4. Using the fn:document-uri Function (ch09_04.xsl)

<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns
Using the fn:document-uri Function (ch09_04.xsl):xs="http://www.w3.org/2001/XMLSchema">

    <xsl:template match="/">
        <xsl:value-of select="document-uri(.)"/>
    </xsl:template>

</xsl:stylesheet>

And here's the result you get when apply this stylesheet to ch09_02.xml—in this case the result is the location on disk of ch09_02.xml:

C:Saxon>java net.sf.saxon.Transform ch09_03.xml ch09_04.xsl
<?xml version="1.0" encoding="UTF-8"?>
file:/C:/Saxon/ch09_02.xml

Using the fn:error Function

The fn:error function makes the XPath processor display an error message and stop processing. You can pass the error message you want displayed to this function like this—there is no return type, which XPath indicates with a return type of “none” (“none” is a special type defined in XPath 2.0, and it's not actually usable in the XPath language with XPath processors—it's just to indicate there is no return type):

fn:error($srcval as item?) as none

You can see an example in ch09_05.xsl (Listing 9.5). In this case, we're checking the number of a particular item we have in stock and reporting the results.

Example 9.5. Using the fn:error Function (ch09_05.xsl)

<xsl:stylesheet version="2.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema">
    <xsl:variable name="inStock" select="80" />

    <xsl:template match="/">
       <xsl:value-of select="if ($inStock > 0)
           then concat('Number in stock: ', string($inStock))
           else error('Invalid argument')"/>
    </xsl:template>

</xsl:stylesheet>

Here's the result using Saxon as the example is written:

<?xml version="1.0" encoding="UTF-8"?>
Number in stock: 80

However, if we change the number in stock to –20 like this:

<xsl:stylesheet version="2.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema">
    <xsl:variable name="inStock" select="-20" />

    <xsl:template match="/">
       <xsl:value-of select="if ($inStock > 0)
           then concat('Number in stock: ', string($inStock))
           else error('Invalid argument')"/>
    </xsl:template>

</xsl:stylesheet>

Then running Saxon on this example raises this error and makes Saxon quit:

C:Saxon>java net.sf.saxon.Transform ch09_02.xml ch09_05.xsl
Error at xsl:value-of on line 7 of file:/C:/Saxon/ch09_05.xsl:
  Invalid argument
Transformation failed: Run-time errors were reported

In this way, you can cause an error and make the XPath processor report that error—the string we passed to the fn:error function is displayed by Saxon.

Constructor Functions

You can use constructor functions to create typed values in XPath 2.0. There is a constructor function for every built-in atomic type except xs:NOTATION. Here's what they look like:

pref:type($srcval as xdt:anyAtomicType) as pref:type

Here, pref is the prefix of the type you want to create, either xs or xdt. A constructor function has the same name as the type you want to create—for example, to create an xs:double with a value of 3.1415, you can use the xs:double constructor function like this:

xs:double("3.1415")

This expression returns an xs:double value of 3.1415. Here are the legal constructor functions that are supported for the built-in types:

  • xdt:untypedAtomic($srcval as xdt:anyAtomicType) as xdt:untypedAtomic

  • xdt:dayTimeDuration($srcval as xdt:anyAtomicType) as xdt:dayTimeDuration

  • xdt:yearMonthDuration($srcval as xdt:anyAtomicType) as xdt:yearMonthDuration

  • xs:anyURI($srcval as xdt:anyAtomicType) as xs:anyURI

  • xs:QName($srcval as xdt:anyAtomicType) as xs:QName

  • xs:base64Binary($srcval as xdt:anyAtomicType) as xs:base64Binary

  • xs:boolean($srcval as xdt:anyAtomicType) as xs:boolean

  • xs:byte($srcval as xdt:anyAtomicType) as xs:byte

  • xs:date($srcval as xdt:anyAtomicType) as (xs:date, xdt:dayTimeDuration)

  • xs:dateTime($srcval as xdt:anyAtomicType) as (xs:dateTime, xdt:dayTimeDuration)

  • xs:decimal($srcval as xdt:anyAtomicType) as xs:decimal

  • xs:double($srcval as xdt:anyAtomicType) as xs:double

  • xs:duration($srcval as xdt:anyAtomicType) as xs:duration

  • xs:ENTITY($srcval as xdt:anyAtomicType) as xs:ENTITY

  • xs:float($srcval as xdt:anyAtomicType) as xs:float

  • xs:gDay($srcval as xdt:anyAtomicType) as xs:gDay

  • xs:gMonth($srcval as xdt:anyAtomicType) as xs:gMonth

  • xs:gMonthDay($srcval as xdt:anyAtomicType) as xs:gMonthDay

  • xs:gYear($srcval as xdt:anyAtomicType) as xs:gYear

  • xs:gYearMonth($srcval as xdt:anyAtomicType) as xs:gYearMonth

  • xs:hexBinary($srcval as xdt:anyAtomicType) as xs:hexBinary

  • xs:ID($srcval as xdt:anyAtomicType) as xs:ID

  • xs:IDREF($srcval as xdt:anyAtomicType) as xs:IDREF

  • xs:int($srcval as xdt:anyAtomicType) as xs:int

  • xs:integer($srcval as xdt:anyAtomicType) as xs:integer

  • xs:language($srcval as xdt:anyAtomicType) as xs:language

  • xs:long($srcval as xdt:anyAtomicType) as xs:long

  • xs:Name($srcval as xdt:anyAtomicType) as xs:Name

  • xs:NCName($srcval as xdt:anyAtomicType) as xs:NCName

  • xs:negativeInteger($srcval as xdt:anyAtomicType) as xs:negativeInteger

  • xs:NMTOKEN($srcval as xdt:anyAtomicType) as xs:NMTOKEN

  • xs:nonNegativeInteger($srcval as xdt:anyAtomicType) as xs:nonNegativeInteger

  • xs:nonPositiveInteger($srcval as xdt:anyAtomicType) as xs:nonPositiveInteger

  • xs:normalizedString($srcval as xdt:anyAtomicType) as xs:normalizedString

  • xs:positiveInteger($srcval as xdt:anyAtomicType) as xs:positiveInteger

  • xs:short($srcval as xdt:anyAtomicType) as xs:short

  • xs:string($srcval as xdt:anyAtomicType) as xs:string

  • xs:time($srcval as xdt:anyAtomicType) as (xs:time, xdt:dayTimeDuration)

  • xs:token($srcval as xdt:anyAtomicType) as xs:token

  • xs:unsignedByte($srcval as xdt:anyAtomicType) as xs:unsignedByte

  • xs:unsignedInt($srcval as xdt:anyAtomicType) as xs:unsignedInt

  • xs:unsignedLong($srcval as xdt:anyAtomicType) as xs:unsignedLong

  • xs:unsignedShort($srcval as xdt:anyAtomicType) as xs:unsignedShort

We saw an example in Chapter 7, where we created an xs:date value like this:

<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns
Constructor Functions:xs="http://www.w3.org/2001/XMLSchema">

    <xsl:template match="/">
        <xsl:value-of select="xs:date('2004-09-02')"/>
    </xsl:template>

</xsl:stylesheet>

You can also have constructor functions for user-defined types if you derive them by restriction from primitive types. For example, in Chapter 7, we declared a derived type by restriction from the xs:string type named StateAbbreviation like this, restricting it to two-character strings like AZ or CA:

<simpleType name='xdat:StateAbbreviation'>
    <restriction base='xs:string'>
      <pattern value='[A-Z]{2}'/>
    </restriction>
</simpleType>

After you declare a user-defined type like this, a constructor function is made available to users:

xdat:StateAbbreviation($srcval as xdt:anyAtomicType) as xdat:StateAbbreviation

Using Context Functions

There is also a set of functions designed to get information about the evaluation context. Here are those functions, some of which we've seen in XPath 1.0:

  • fn:position returns the position of the context item within the sequence of items currently being processed.

  • fn:last returns the number of items in the sequence of items currently being processed.

  • fn:current-dateTime returns the current xs:dateTime.

  • fn:current-date returns the current xs:date.

  • fn:current-time returns the current xs:time.

  • fn:default-collation returns the value of the default collation property from the static context.

  • fn:implicit-timezone returns the value of the implicit timezone property from the evaluation context.

We'll take a closer look at these functions here, starting with fn:position.

The fn:position Function

XPath 2.0 supports our old friend from XPath 1.0, the fn:position() function. In XPath 2.0, this function returns an xs:integer value holding the position of the context item among its siblings:

fn:position() as xs:integer?

Here's an example; say that you wanted to format the names of the planets in our planetary data document like this (note the extra annotation for Venus):

1 Mercury.
2 Venus (the planet of love).
3 Earth.

To make this happen, we'll loop over all the <planet> elements using an <xsl:for-each> element:

<xsl:template match="planets">
    <xsl:for-each select="planet">
        .
        .
        .
    </xsl:for-each>
</xsl:template>

We can number each planet's name using the position function like this:

<xsl:template match="planets">
   <xsl:for-each select="planet">
       <xsl:value-of select="position()"/>
       <xsl:text> </xsl:text>
       <xsl:value-of select="name"/>
    .
    .
    .
       <br/>
   </xsl:for-each>
</xsl:template>

And we can also add the annotation for Venus like this:

<xsl:template match="planets">
   <xsl:for-each select="planet">
       <xsl:value-of select="position()"/>
       <xsl:text> </xsl:text>
       <xsl:value-of select="name"/>
       <xsl:value-of select="if (position() = 2) then
           ' (the planet of love).' else '.'"/>
    .
    .
    .
       <br/>
   </xsl:for-each>
</xsl:template>

All that's left is to separate each planet's entry onto a different line, and we'll use a <br/> element for that, as you can see in this example's code, ch09_06.xsl, which appears in Listing 9.6.

Example 9.6. Using the fn:position Function (ch09_06.xsl)

<xsl:stylesheet version="2.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

    <xsl:template match="planets">
       <xsl:for-each select="planet">
           <xsl:value-of select="position()"/>
           <xsl:text> </xsl:text>
           <xsl:value-of select="name"/>
           <xsl:value-of select="if (position() = 2) then
               ' (the planet of love).' else '.'"/>
           <br/>
           <br/>
       </xsl:for-each>
    </xsl:template>

</xsl:stylesheet>

Here's what you get when you apply this stylesheet to our planetary data document:

C:Saxon>java net.sf.saxon.Transform ch02_01.xml ch09_06.xsl
<?xml version="1.0" encoding="UTF-8"?>
1 Mercury.
<br/>
2 Venus (the planet of love).
<br/>
3 Earth.
<br/>

The fn:last Function

The last() function in XPath 1.0 appears in XPath 2.0 as fn:last(), which returns an xs:integer indicating the number of items in the sequence of items currently being processed. Here's its signature:

fn:last() as xs:integer?

THERE'S NO first() FUNCTION

As in XPath 1.0, there's no first() function—instead, you test whether the position() function returns a value of 1.

Here's an example using the last() function. Say that we want to adapt our previous XSLT example to create an HTML document in which we enclose the planetary list with horizontal rule, <hr/>, elements. To convert the output of our XSLT stylesheet from XML to HTML, we only need to make the document element of the output document <html>, which causes the XSLT processor to assume the output document is HTML:

<xsl:template match="planets">
    <html>
    .
    .
    .
    </html>
</xsl:template>

Then we just add the other HTML we need, and add an <xsl:if> element to check for the beginning of the list, where we add the first <hr/> element:

<xsl:template match="planets">
    <html>
        <head>
            <title>
                The Planets
            </title>
        </head>

        <body>
            <h1>
                The Planets
            </h1>
            <xsl:for-each select="planet">
                <xsl:if test="position() = 1"><hr/></xsl:if>
                <xsl:value-of select="position()"/>
                <xsl:text> </xsl:text>
                <xsl:value-of select="name"/>
                <xsl:value-of select="if (position() = 2)
                    then ' (the planet of love).'
                    else '.'"/>
                    .
                    .
                    .
                <br/>
            </xsl:for-each>
        </body>
    </html>
</xsl:template>

All that's left is to add the final <hr/> element at the end of the list, and that works as you can see in ch09_07.xsl (Listing 9.7).

Example 9.7. Using the fn:last Function (ch09_07.xsl)

<xsl:stylesheet version="2.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

    <xsl:template match="planets">
        <html>
            <head>
                <title>
                    The Planets
                </title>
            </head>

            <body>
                <h1>
                    The Planets
                </h1>
                <xsl:for-each select="planet">
                    <xsl:if test="position() = 1"><hr/></xsl:if>
                    <xsl:value-of select="position()"/>
                    <xsl:text> </xsl:text>
                    <xsl:value-of select="name"/>
                    <xsl:value-of select="if (position() = 2)
                        then ' (the planet of love).'
                        else '.'"/>
                    <xsl:if test="position() = last()"><hr/></xsl:if>
                    <br/>
                </xsl:for-each>
            </body>
        </html>
    </xsl:template>

</xsl:stylesheet>

To store our results in an HTML document named results.html, you can use this command with Saxon:

C:Saxon>java net.sf.saxon.Transform ch02_01.xml ch09_07.xsl > results.html

And you can see the result in Figure 9.1.

Using the last() function.

Figure 9.1. Using the last() function.

The fn:current-dateTime Function

This function returns the current date and time, with timezone. Here's the signature of this function:

fn:current-dateTime() as dateTime

This function returns an xs:dateTime value, and you can see an example in ch09_08.xsl, Listing 9.8, where we're just displaying the current time and date.

Example 9.8. Using the fn:current-dateTime Function (ch09_08.xsl)

<xsl:stylesheet version="2.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

    <xsl:template match="/">
        <xsl:value-of select="current-dateTime()"/>
    </xsl:template>

</xsl:stylesheet>

Here's the kind of result you get when you run this example through Saxon:

<?xml version="1.0" encoding="UTF-8"?>
2003-09-19T17:00:25.255Z

The fn:current-date Function

The fn:current-date() function just returns an xs:date object, with timezone information, that holds the current date. Here's how you use this function:

fn:current-date() as xs:date

You can see an example in ch09_09.xsl, Listing 9.9, where we're displaying the current date.

Example 9.9. Using the fn:current-date Function (ch09_09.xsl)

<xsl:stylesheet version="2.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

    <xsl:template match="/">
        <xsl:value-of select="current-date()"/>
    </xsl:template>

</xsl:stylesheet>

Here's the kind of result you get from Saxon using this example:

<?xml version="1.0" encoding="UTF-8"?>
2003-09-19

The fn:current-time Function

As you can gather from its name, the fn:current-time() function returns the current time. Here's how you use it:

fn:current-time() as time

The return value is an xs:time value, including timezone. You can see an example in ch09_10.xsl (Listing 9.10).

Example 9.10. Using the fn:current-time Function (ch09_10.xsl)

<xsl:stylesheet version="2.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

    <xsl:template match="/">
        <xsl:value-of select="current-time()"/>
    </xsl:template>

</xsl:stylesheet>

And here's what you might see when you run this example:

<?xml version="1.0" encoding="UTF-8"?>
17:24:23.503Z

Using Numeric Functions

XPath 2.0 also has a number of numeric functions, designed to work with these numeric types:

  • xs:decimal

  • xs:integer

  • xs:float

  • xs:double

These functions also apply to types derived by restriction from these types.

The XQuery 1.0 and XPath 2.0 Functions and Operators document lists a large number of op functions that specify how various numeric operators work on different data types. Here are some of the op functions described—you can tell what operator each function is describing by its name:

  • op:numeric-add—. Addition (+)

  • op:numeric-subtract—. Subtraction (–)

  • op:numeric-multiply—. Multiplication (*)

  • op:numeric-divide—. Division (div)

  • op:numeric-integer-divide—. Integer division (idiv)

  • op:numeric-mod—. Modulus (mod)

  • op:numeric-unary-plus—. Unary plus (+)

  • op:numeric-unary-minus—. Unary minus (negation) (–)

These functions are not callable from XPath 2.0 expressions; they underpin the operators in the language. However, you can work with a set of numeric functions in the fn namespace. Each of the fn functions returns a value of the same type as the type of its argument. If the argument is the empty sequence, the empty sequence is returned, and if the argument is xdt:untypedAtomic, it is converted to xs:double.

Here's an overview of the callable numeric functions:

  • fn:floor returns the largest number with no fractional part that is less than or equal to the value you pass.

  • fn:ceiling returns the smallest number with no fractional part that is greater than or equal to the value you pass.

  • fn:round rounds to the nearest number with no fractional part.

  • fn:round-half-to-even accepts a number and a rounding precision value and returns a number rounded to the precision you've requested. Note that if the fractional part of the value you pass is .5, the result is the number whose least significant digit is even.

We'll take a look at them here.

The fn:floor Function

The fn:floor function returns the largest number—that is, the closest to positive infinity—that has no fractional part, such that the returned number is not greater than the value you pass to this function. Here's the signature of this function:

fn:floor($srcval as numeric?) as numeric?

Here are some examples:

fn:floor(2.3) returns 2
fn:floor(1.5) returns 1
fn:floor(-9.5) returns -10

And here's an example putting this function to work in an XSLT 2.0 stylesheet. In this example, we'll determine whether a number is an integer, and we start by assigning that number to a variable named $number:

<xsl:stylesheet version="2.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:variable name="number" select="1.5" />
        .
        .
        .

If floor($number) = $number, the number is indeed an integer, and we can insert the message "That number is an integer." in the result document like this:

<xsl:stylesheet version="2.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:variable name="number" select="1.5" />
        .
        .
        .
    <xsl:template match="/">
        <xsl:value-of select="if (floor($number) = $number)
            then 'That number is an integer.'
        .
        .
        .
    </xsl:template>

On the other hand, if floor($number) != $number, we can insert the message "That number is not an integer." in the result document, as you see in ch09_11.xsl (Listing 9.11).

Example 9.11. Using the fn:floor Function (ch09_11.xsl)

<xsl:stylesheet version="2.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:variable name="number" select="1.5" />

    <xsl:template match="/">
        <xsl:value-of select="if (floor($number) = $number)
            then 'That number is an integer.'
            else 'That number is not an integer.'"/>
    </xsl:template>

</xsl:stylesheet>

And here's the result when you use Saxon on this stylesheet—as you can see, this code knows that 1.5 is not an integer:

<?xml version="1.0" encoding="UTF-8"?>
That number is not an integer.

The fn:ceiling Function

The fn:ceiling function returns the smallest—that is, closest to negative infinity—number with no fractional part, such that the returned number is not less than the value you pass this function. Here's what this function's signature looks like:

fn:ceiling($srcval as numeric?) as numeric?

Here are some examples:

fn:ceiling(9.3) returns 10.
fn:ceiling(1.5) returns 2.
fn:ceiling(-4.5) returns -4.

Here's an example using fn:ceiling. In this case, say you have one and a half truckloads of iron ore to move and want to know how many trucks you'll need. We start by setting up a variable, $numberofTruckLoads:

<xsl:stylesheet version="2.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:variable name="numberofTruckLoads" select="1.5" />
        .
        .
        .

And passing this variable to the fn:ceiling function tells us how many trucks we'll need to transport the load; you can see the XPath code in ch09_12.xsl (Listing 9.12).

Example 9.12. Using the fn:ceiling Function (ch09_12.xsl)

<xsl:stylesheet version="2.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:variable name="numberofTruckLoads" select="1.5" />

    <xsl:template match="/">
        <xsl:value-of select="concat('You will need ',
            string(ceiling($numberofTruckLoads)), ' trucks.')"/>
    </xsl:template>

</xsl:stylesheet>

Here's the result:

<?xml version="1.0" encoding="UTF-8"?>
You will need 2 trucks.

The fn:round Function

The fn:round function is XPath 2.0's main rounding function. The fn:round function returns the number with no fractional part that is closest to the value you pass. If there are two such numbers, this function returns the one closest to positive infinity. Here's what the signature of this function looks like:

fn:round($srcval as numeric?) as numeric?

Here are some examples—note in particular that fn:round(-9.5) returns –9, not –10:

fn:round(1.2) returns 1.
fn:round(6.5) returns 7.
fn:round(6.4999999) returns 6.
fn:round(-9.5) returns -9.

USING fn:round AND fn:floor

fn:round($srcval) returns the same value as fn:floor($srcval + 0.5).

Here's an example using this function in XSLT 2.0; say you're in charge of the movie reviews of a newspaper and have to multiply the average number of stars awarded to a movie, like 3.73, by your reviewers and come up with a whole number. You can use the fn:round function to do that, as you see in ch09_13.xsl (Listing 9.13).

Example 9.13. Using the fn:round Function (ch09_13.xsl)

<xsl:stylesheet version="2.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:variable name="movieName" select="Casablanca" />
    <xsl:variable name="numberOfStars" select="3.73" />

    <xsl:template match="/">
        <xsl:value-of select="concat('Casablanca got ',
            string(round($numberOfStars)), ' stars.')"/>
    </xsl:template>

</xsl:stylesheet>

And here's the result:

<?xml version="1.0" encoding="UTF-8"?>
Casablanca got 4 stars. 

The fn:round-half-to-even Function

The fn:round-half-to-even function gives you a chance to specify the rounding precision yourself. There are two ways to use it—passing it a value, and passing a value and precision:

fn:round-half-to-even($srcval as numeric?) as numeric?
fn:round-half-to-even($srcval as numeric?, $precision as integer) as numeric?

The first version of this function returns the nearest value to the value you pass that has no fractional part. If two values are equally close—that is, the fractional part of the value you pass is exactly .5—the value returned is the one whose least significant digit is even.

If you pass a $precision value to this function, you can set the precision yourself. In this case, the value returned is the nearest value to $srcval that is a multiple of 1/10 to the power of $precision. As with the other form of this function, if two values are equally close—that is, the fractional part of the value you pass is exactly .5—the value returned is the one whose least significant digit is even.

Here is an example:

fn:round-half-to-even(3.5) returns 4.

On the other hand, the next example also returns 4:

fn:round-half-to-even(4.5) returns 4.

Here's an example that uses a precision of 2 (here we're using the standard exponentiation syntax you use in XPath 2.0; note that 1E+3 = 1000, 1E-1 = 1/10, and so on):

fn:round-half-to-even(9.477512E+3, 2) returns 9477.51E0.

You can also round to positive powers of 10 if you pass a negative value as the precision you want. For example, here's how you can round to the nearest 100:

fn:round-half-to-even(12312.63, -2) returns 12300.

AGGREGATE FUNCTIONS

There are also some numeric functions that are specifically designed to work on sequences, called aggregate functions, such as max, min, count, avg, and sum, that are coming up in Chapter 12, “XPath 2.0 Node and Sequence Functions.”

In Brief

  • The specification for the XPath 2.0 functions is in the document named XQuery 1.0 and XPath 2.0 Functions and Operators, which you can find at http://www.w3.org/TR/xquery-operators.

  • The namespace for functions is tied to the fn prefix. Its URL is http://www.w3.org/2003/05/xpath-functions, and you can call these functions directly (although when you use Saxon, you omit the fn: prefix). The other prefix defined in the XQuery 1.0 and XPath 2.0 Functions and Operators document is the op prefix, which identifies the operator functions. These functions are not directly callable; these functions are designed to indicate how the XPath 2.0 operators work with various data types.

  • The XPath 2.0 functions fn:node-name, fn:string, fn:data, fn:base-uri, and fn:document-uri are generic functions that you use with nodes.

  • The fn:error function gives you some control over the processing of XSLT stylesheets—calling this function lets you display an error message and halt the XSLT processor.

  • The context functions return information about the current context. These functions are fn:position, fn:last, fn:current-dateTime, fn:current-date, fn:current-time, fn:default-collation, and fn:implicit-timezone.

  • The numeric functions in this chapter. These functions are fn:floor, fn:ceiling, fn:round, and fn:round-half-to-even.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.119.107.96