Formatting Decimal Numbers

We’ve already seen several ways of formatting decimal numbers using <xsl:number>. However, if we’re going to work with numbers, we’ll almost certainly have to deal with decimals. XSLT defines the format-number() function and the <xsl:decimal-format> element to do just that. We’ll use <xsl:decimal-format> to define a pattern for formatting numbers, and then we’ll use format-number() to apply a pattern to a number.

This stylesheet has several examples of <xsl:decimal-format> and format-number():

<?xml version="1.0" encoding="utf-8"?>
<!-- decimal-format.xsl -->
<xsl:stylesheet version="1.0"
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

  <xsl:output method="text"/>

  <!-- This format has no name, so it's assumed to be the default. -->
  <xsl:decimal-format decimal-separator="," grouping-separator="."/>

  <xsl:decimal-format name="us_default"/>

  <xsl:decimal-format name="other_options" NaN="[not a number]"
    infinity="unfathomably huge"/>

  <xsl:decimal-format name="hash_mark" digit="!"/>

  <xsl:template match="/">
    <xsl:text>&#xA;Tests of &lt;xsl:decimal-format&gt; and </xsl:text>
    <xsl:text>format-number():</xsl:text>
    <xsl:text>&#xA;&#xA;  1. format-number(3728493.3882, </xsl:text>
    <xsl:text>'#.###,##') : </xsl:text>
    <xsl:value-of 
      select="format-number(3728493.3882, '#.###,##')"/>

    <xsl:text>&#xA;&#xA;  2. format-number(3728493.3882, </xsl:text>
    <xsl:text>'#,###.##', 'us_default') : </xsl:text>
    <xsl:value-of 
      select="format-number(3728493.3882, '#,###.##', 'us_default')"/>

    <xsl:text>&#xA;&#xA;  3. format-number(number(1) div 0, '#.#') : </xsl:text>
    <xsl:value-of select="format-number(number(1) div 0, '#.#')"/>

    <xsl:text>&#xA;&#xA;  4. format-number(number(1) div 0, '#.#', </xsl:text>
    <xsl:text>'other_options') : &#xA;       </xsl:text>
    <xsl:value-of 
      select="format-number(number(1) div 0, '#.#', 'other_options')"/>

    <xsl:text>&#xA;&#xA;  5. format-number(number('blue') * </xsl:text>
    <xsl:text>number('orange'), '#') : </xsl:text>
    <xsl:value-of 
      select="format-number(number('blue') * number('orange'), '#')"/>

    <xsl:text>&#xA;&#xA;  6. format-number(number('blue') * </xsl:text>
    <xsl:text>number('orange'), '#', 'other_options') : </xsl:text>
    <xsl:text>&#xA;       </xsl:text>
    <xsl:value-of 
      select="format-number(number('blue') * number('orange'), '#', 
              'other_options')"/>

    <xsl:text>&#xA;&#xA;  7. format-number(42, '#!', </xsl:text>
    <xsl:text>'hash_mark') : </xsl:text>
    <xsl:value-of select="format-number(42, '#!', 'hash_mark')"/>
  </xsl:template>

</xsl:stylesheet>

When we run this stylesheet against any document, we get this output:

Tests of <xsl:decimal-format> and format-number():

  1. format-number(3728493.3882, '#.###,##') : 3.728.493,39

  2. format-number(3728493.3882, '#,###.##', 'us_default') : 3,728,493.39

  3. format-number(number(1) div 0, '#.#') : Infinity

  4. format-number(number(1) div 0, '#.#', 'other_options') :
       unfathomably huge

  5. format-number(number('blue') * number('orange'), '#') : NaN

  6. format-number(number('blue') * number('orange'), '#', 'other_options') :
       [not a number]

  7. format-number(42, '#!', 'hash_mark') : #42

This is an XSLT 1.0 stylesheet, but changing the version attribute to 2.0 generates the same results. We’ll discuss each line in the output and point out some differences in the way XSLT 2.0 processes numbers as we go.

  1. This example formats a number using the default format we defined. Because our stylesheet has a <xsl:decimal-format> element without a name, this format is used unless the name of another <xsl:decimal-format> element is used. We defined the default format to use periods as the thousands separator and a comma as the decimal point. Notice that to get this to work we had to use the period and comma appropriately in the formatting string. The numeric value itself uses the decimal point as usual.

  2. This is the same example as before, only we pass the name of a number format as the third argument. Notice that the decimal format us_default doesn’t have any attributes; that means it uses a period as the decimal point and a comma as the thousands separator.

  3. This generates the default value for infinity, which is the string Infinity. In XSLT 1.0, the expression 1 div 0 generates the same result.

    [2.0] In XSLT 2.0, any value that is an xs:integer or xs:decimal cannot have the value infinity, so 1 div 0 won’t run at all. Calling the function number(1) converts 1 into the xs:double equivalent, so number(1) div 0 works. (That’s a lot of work to divide a number by zero, but it does explain some of the details of how math works in XSLT 2.0.)

  4. This generates the value for infinity defined in the number format other_options. The same details apply here as in the previous example; the expression 1 div 0 doesn’t work in XSLT 2.0.

  5. This generates NaN. In XSLT 1.0, the expression 'blue' * 'orange' generates the same result.

    [2.0] In XSLT 2.0, the expression 'blue' * 'orange' doesn’t work because the multiplication symbol requires two numbers. Using the number() function turns each of the strings into the numeric value NaN. To generate NaN in XSLT 2.0, number('NaN') does the trick, as do number('blue'), number('orange'), and number('any old string at all').

  6. This generates [not a number], the value defined in the number format other_options. (The same restrictions for XSLT 2.0 apply here as well.)

  7. This generates #42. This uses the number format hash_mark, which defines an exclamation point as the digit character in the picture string. This allows us to put the hash mark into the picture string.

Get XSLT, 2nd Edition now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.