XSLT and XPath define a small set of functions to manipulate text and
numbers. These allow you to concatenate strings, extract substrings,
determine the length of a string, and perform other similar tasks.
While these features do not approach the capabilities offered by a
programming language like Java, they do allow for some of the most
common string manipulation tasks.
3.4.1. Number Formatting
The format-number(
)
function is provided by XSLT to convert
numbers such as 123 into formatted numbers such as
$123.00. The function takes the following form:
string format-number(number, string, string?)
The first parameter is the number to format, the second is a format
string, and the third (optional) is the name of an
<xsl:decimal-format> element. We will cover
only the first two parameters in this book. Interestingly enough, the
behavior of the format-number( ) function is
defined by the JDK 1.1.x version of the
java.text.DecimalFormat class. For complete
information on the syntax of the second argument, refer to the
JavaDocs for JDK 1.1.x.
Outputting currencies is a common use for the
format-number( ) function. The pattern
$#,##0.00 can properly format a number into just
about any U.S. currency. Table 3-2 demonstrates
several possible inputs and results for this pattern.
Table 3-2. Formatting currencies using $#,##0.00
Number
|
Result
|
0
|
$0.00
|
0.9
|
$0.90
|
0.919
|
$0.92
|
10
|
$10.00
|
1000
|
$1,000.00
|
12345.12345
|
$12,345.12
|
The XSLT code to utilize this function may look something like this:
<xsl:value-of select="format-number(amt,'$#,##0.00')"/>
It is assumed that amt is some element in the XML
data,[12] such as <amt>1000</amt>.
The # and 0 characters are
placeholders for digits and behave exactly as
java.text.DecimalFormat specifies. Basically,
0 is a placeholder for any digit, while
# is a placeholder that is absent when the input
value is 0.
Besides currencies, another common format is
percentages. To output a percentage,
end the format pattern with a % character. The
following XSLT code shows a few examples:
<!-- outputs 0% -->
<xsl:value-of select="format-number(0,'0%')"/>
<!-- outputs 10% -->
<xsl:value-of select="format-number(0.1,'0%')"/>
<!-- outputs 100% -->
<xsl:value-of select="format-number(1,'0%')"/>
As before, the first parameter to the format-number(
) function is the actual number to be formatted, and the
second parameter is the pattern. The 0 in the pattern indicates that
at least one digit should always be displayed. The
% character also has the side effect of
multiplying the value by 100 so it is displayed as a percentage.
Consequently, 0.15 is displayed as 15%, and 1 is displayed as 100%.
To test more patterns, the XML data shown in Example 3-8 can be used. This works in conjunction with
numberFormatting.xslt to display every
combination of format and number listed in the XML data.
Example 3-8. numberFormatting.xml
<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="numberFormatting.xslt"?>
<numberFormatting>
<formatSamples>
<!-- add more <format> elements to test more combinations-->
<format>$#,##0.00</format>
<format>#.#</format>
<format>0.#</format>
<format>0.0</format>
<format>0%</format>
<format>0.0#</format>
</formatSamples>
<numberSamples>
<!-- add more <number> elements to test more combinations -->
<number>-10</number>
<number>-1</number>
<number>0</number>
<number>0.000123</number>
<number>0.1</number>
<number>0.9</number>
<number>0.91</number>
<number>0.919</number>
<number>1</number>
<number>10</number>
<number>100</number>
<number>1000</number>
<number>10000</number>
<number>12345.12345</number>
<number>55555.55555</number>
</numberSamples>
</numberFormatting>
The
stylesheet,
numberFormatting.xslt, is shown in Example 3-9. Comments in the code explain what happens at
each step. To test new patterns and numbers, just edit the XML data
and apply the transformation again. Since the XML file references the
stylesheet with <?xml-stylesheet?>, you can
simply load the XML into an XSLT compliant web browser and click on
the Reload button to see changes as they are made.
Example 3-9. numberFormatting.xslt
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html"/>
<xsl:template match="/">
<html>
<body>
<!-- loop over each of the sample formats -->
<xsl:for-each select="numberFormatting/formatSamples/format">
<h2>
<!-- show the format as a heading -->
<xsl:value-of select="."/>
</h2>
<table border="1" cellpadding="2" cellspacing="0">
<tr>
<th>Number</th>
<th>Result</th>
</tr>
<!-- pass the format as a parameter to the template that
shows each number -->
<xsl:apply-templates select="/numberFormatting/numberSamples/number">
<xsl:with-param name="fmt" select="."/>
</xsl:apply-templates>
</table>
</xsl:for-each>
</body>
</html>
</xsl:template>
<!-- output the number followed by the result of the format-number function -->
<xsl:template match="number">
<xsl:param name="fmt"/>
<tr>
<td align="right">
<xsl:value-of select="."/>
</td>
<td align="right">
<!-- the first param is a dot, representing the text content
of the <number> element -->
<xsl:value-of select="format-number(.,$fmt)"/>
</td>
</tr>
</xsl:template>
</xsl:stylesheet>
This stylesheet first loops over the list of
<format> elements:
<xsl:for-each select="numberFormatting/formatSamples/format">
Within the loop, all of the <number>
elements are selected. This means that every format is applied to
every number:
<xsl:apply-templates select="/numberFormatting/numberSamples/number">
3.4.2. Text Formatting
Several text-formatting functions
are defined by the XPath specification, allowing code in an XSLT
stylesheet to perform such operations as concatenating two or more
strings, extracting a substring, and computing the length of a
string. Unlike strings in Java, all strings in XSLT and XPath are
indexed from position 1 instead of position 0.
Let's suppose that a stylesheet defines the following variables:
<xsl:variable name="firstName" select="'Eric'"/>
<xsl:variable name="lastName" select="'Burke'"/>
<xsl:variable name="middleName" select="'Matthew'"/>
<xsl:variable name="fullName"
select="concat($firstName, ' ', $middleName, ' ', $lastName)"/>
In the first three variables, apostrophes are used to indicate that
the values are strings. Without the apostrophes, the XSLT processor
would treat these as XPath expressions and attempt to select nodes
from the XML input data. The third variable,
fullName, demonstrates how the concat(
) function is used to concatenate two or more strings
together. The function simply takes a comma-separated list of strings
as arguments and returns the concatenated results. In this case, the
value for fullName is "Eric Matthew
Burke."
Table 3-3 provides additional examples of string
functions. The variables in this table are the same ones from the
previous example. In the first column, the return type of the
function is listed first, followed by the function name and the list
of parameters. The second and third columns provide an example usage
and the output from that example.
Table 3-3. String function examples
Function syntax
|
Example
|
Output
|
string concat
(string,string,string*)
|
concat($firstName, ' ', $lastName)
|
Eric Burke
|
boolean starts-with
(string,string)
|
starts-with($firstName, 'Er')
|
true
|
boolean contains(string,string)
|
contains($fullName, 'Smith')
|
false
|
string substring-before
(string,string)
|
substring-before($fullName, ' ')
|
Eric
|
string substring-after
(string,string)
|
substring-after($fullName, ' ')
|
Matthew Burke
|
string substring
(string,number,number?)
|
substring($middleName,1,1)
|
M
|
number string-length(string?)
|
string-length($fullName)
|
18
|
string normalize-space(string?)
|
normalize-space(' testing ')
|
testing
|
string translate
(string,string,string)
|
translate('test','aeiou','AEIOU')
|
tEst
|
All string comparisons, such as starts-with() and
contains( ), are case-sensitive. There is no
concept of case-insensitive comparison in XSLT. One potential
workaround is to convert both strings to upper- or lowercase, and
then perform the comparison. Converting a string to upper- or
lowercase is not directly supported by a function in the current
implementation of XSLT, but the translate( )
function can be used to perform the task. The following XSLT snippet
converts a string from lower- to uppercase:
translate($text,
'abcdefghijklmnopqrstuvwxyz',
'ABCDEFGHIJKLMNOPQRSTUVWXYZ')
In the substring-before( ) and
substring-after( ) functions, the second argument
contains a delimiter string. This delimiter does not have to be a
single character, and an empty string is returned if the delimiter is
not found. These functions could be used to parse formatted data such
as dates:
<date>06/25/1999</date>
The XSLT used to extract the month, day, and year looks like this:
<xsl:variable name="dateStr" select="//date"/>
<xsl:variable name="dayYear" select="substring-after($dateStr, '/')"/>
Month: <xsl:value-of select="substring-before($dateStr, '/')"/> <br/>
Day: <xsl:value-of select="substring-before($dayYear, '/')"/> <br/>
Year: <xsl:value-of select="substring-after($dayYear, '/')"/>
In the first line of code, the dateStr variable is
initialized to contain the full date. The next line then creates the
dayYear variable, which contains everything after
the first / character -- at this point,
dateStr=06/25/1999 and
dayYear=25/1999. In Java, this is slightly easier
because you simply create an instance of the
StringTokenizer class and iterate through the
tokens or use the lastIndexOf( ) method of
java.lang.String to locate the second
/. With XSLT, the options are somewhat more
limited. The remaining lines continue chopping up the variables into
substrings, again delimiting on the
/ character. The output is as follows:
Month: 06
Day: 25
Year: 1999
Another form of the substring( ) function takes
one or two number arguments, indicating the starting index and the
optional length of the substring. If the second
number is omitted, the substring continues until
the end of the input string. The starting index always begins at
position 1, so substring("abcde",2,3) returns
bcd, and substring("abcde",2)
returns bcde.