Book HomeJava and XSLTSearch this book

3.4. Formatting Text and Numbers

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.

[12] The XSLT specification does not define what happens if the XML data does not contain a valid number.

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.



Library Navigation Links

Copyright © 2002 O'Reilly & Associates. All rights reserved.