Recipe3.1.Formatting Numbers


Recipe 3.1. Formatting Numbers

Problem

You need to display numbers in various formats.

Solution

This problem has two general solutions.

Use xsl:decimal-format in conjunction with format-number( )

The top-level element xsl:decimal-format establishes a named formatting rule that can be referenced by the format-number( ) function whenever that format rule is required. xsl:decimal-format has a rich set of attributes that describe the formatting rules. Table 3-1 explains each attribute and shows its default value in parentheses.

Table 3-1. Attributes of the xsl:decimal-format element

Attribute

Purpose

Name

An optional name for the rule. If absent, this rule becomes the default rule. There can be only one default, and all names must be unique (even when there is a difference in import precedence).

decimal-separator (.)

The character used to separate the whole and fractional parts of a number.

grouping-separator (,)

The character used to separate groups of digits.

infinity (Infinity)

The string that represents infinity.

minus-sign (-)

The character used as a minus sign.

NaN (NaN)

The string representing the numerical value "not a number."

percent (%)

The percent sign.

per-mille ()

The per-mille sign.

zero-digit (0)

The character used in a formatting pattern to indicate where a leading or trailing zero should be placed. Setting this character resets the origin of the numbering system. See Example 3-1.

digit (#)

The character used in a formatting pattern to indicate where a digit will appear, provided it is significant.

pattern-separator (;)

The character used in a formatting pattern to separate the positive subpattern from the negative subpattern.


The format-number( ) function takes the arguments shown in Table 3-2.

Table 3-2. Arguments for format-number( )

Argument

Purpose

value

The value to be formatted

format

A format string (e.g., #,###.00)

name (optional)

A name of an xsl:decimal-format element


Use xsl:number

The most common use of xsl:number is to number nodes sequentially. However, it can also format numbers. When used to perform the later, the relevant attributes are shown in Table 3-3.

Table 3-3. Attributes of xsl:number

Name

Purpose

Value

The number to be formatted.

Format

A format string (described in the discussion).

Lang

A language code as defined by the xml:lang attribute.

letter-value

Must be either alphabetic or traditional and is used to distinguish between different numbering schemes.

grouping-separator

A single character used to separate groups. For example, in the U.S., the comma (,) is standard.

grouping-size

The number of digits in each group.


Table 3-4 shows how formatting tokens are used with a format attribute.

Table 3-4. Example behavior of formatting tokens used with a format attribute

Format token

Example value

Resulting output

1

1

1

1

99

99

01

1

01

001

1

001

a

1

A

a

10

J

a

27

Aa

A

1

A

A

27

AA

i

1

I

i

10

X

I

1

I

I

11

XI


The format string is an alternating sequence of format and punctuation tokens. Using multiple format tokens makes sense only when the value contains a set of numbers.

Discussion

Given the formatting machinery defined earlier, almost any numeric-formatting task can be handled.

Formatting numbers into columns using a fixed number of decimal places

Here we can take advantage of leading and trailing zero padding and then map the leading zeros to spaces and use a trailing minus sign. This solution gives a nice columnar, right-justified output when the final display medium uses a fixed-width font. Example 3-1 through Example 3-3 show more conventional ways of padding. The examples illustrate the behavior of the 0 digit when used as a format character.

Example 3-1. Input
<numbers>   <number>10</number>   <number>3.5</number>   <number>4.44</number>   <number>77.7777</number>   <number>-8</number>   <number>1</number>   <number>444</number>   <number>1.1234</number>   <number>7.77</number>   <number>3.1415927</number>   <number>10</number>   <number>9</number>   <number>8</number>   <number>7</number>   <number>666</number>   <number>5555</number>   <number>-4444444</number>   <number>22.33</number>   <number>18</number>   <number>36.54</number>   <number>43</number>   <number>99999</number>   <number>999999</number>   <number>9999999</number>   <number>32</number>   <number>64</number>   <number>-64.0001</number> </numbers>

Example 3-2. format-numbers-into-columns.xslt
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">      <xsl:output method="text" />     <xsl:variable name="numCols" select="3"/>     <xsl:template match="numbers">   <xsl:for-each select="number[position( ) mod $numCols = 1]">     <xsl:apply-templates           select=". | following-sibling::number[position( ) &lt; $numCols]"           mode="format"/>     <xsl:text>&#xa;</xsl:text>    </xsl:for-each> </xsl:template>     <xsl:template match="number" name="format" mode="format">   <xsl:param name="number" select="." />   <xsl:call-template name="leading-zero-to-space">     <xsl:with-param name="input"                      select="format-number($number,                                           '0000000.0000  ;0000000.0000- ')"/>   </xsl:call-template> </xsl:template>     <xsl:template name="leading-zero-to-space">   <xsl:param name="input"/>   <xsl:choose>     <xsl:when test="starts-with($input,'0')">     <xsl:value-of select="' '"/>     <xsl:call-template name="leading-zero-to-space">       <xsl:with-param name="input" select="substring-after($input,'0')"/>     </xsl:call-template>     </xsl:when>     <xsl:otherwise>       <xsl:value-of select="$input"/>     </xsl:otherwise>   </xsl:choose> </xsl:template>     </xsl:stylesheet>

Example 3-3. Output
     10.0000        3.5000        4.4400      77.7777        8.0000-       1.0000     444.0000        1.1234        7.7700       3.1416       10.0000        9.0000       8.0000        7.0000      666.0000    5555.0000  4444444.0000-      22.3300      18.0000       36.5400       43.0000   99999.0000   999999.0000  9999999.0000      32.0000       64.0000       64.0001-

Formatting money like U.S. accountants

Example 3-4 gives a variation of the previous format template that will make your accountant happy.

Example 3-4. Accountant-friendly format
<xsl:template match="number" name="format" mode="format">   <xsl:param name="number" select="." />   <xsl:text> $ </xsl:text>   <xsl:call-template name="leading-zero-to-space">     <xsl:with-param name="input"                      select="format-number($number,                                           ' 0000000.00 ;(0000000.00)')"/>   </xsl:call-template> </xsl:template>       Output: $       10.00  $        3.50  $       4.44 $       77.78  $       (8.00) $       1.00 $      444.00  $        1.12  $       7.77 $        3.14  $       10.00  $       9.00 $        8.00  $        7.00  $     666.00 $     5555.00  $ (4444444.00) $      22.33 $       18.00  $       36.54  $      43.00 $    99999.00  $   999999.00  $ 9999999.00 $       32.00  $       64.00  $     (64.00)

Formatting numbers for many European countries

Example 3-5 demonstrates the use of a named format.

Example 3-5. European-number format
<xsl:stylesheet version="1.0"       xmlns:xsl="http://www.w3.org/1999/XSL/Transform"      xmlns:str="http://www.ora.com/XSLTCookbook/namespaces/strings">     <xsl:output method="text" />     <!-- From Recipe 2.5 ... --> <xsl:include href="../strings/str.dup.xslt"/>     <xsl:variable name="numCols" select="3"/>           <xsl:decimal-format name="WesternEurope"                         decimal-separator="," grouping-separator="."/>   <xsl:template match="numbers">   <xsl:for-each select="number[position( ) mod $numCols = 1]">     <xsl:apply-templates           select=". | following-sibling::number[position( ) &lt; $numCols]"           mode="format"/>     <xsl:text>&#xa;</xsl:text>    </xsl:for-each> </xsl:template>     <xsl:template match="number" name="format" mode="format">   <xsl:param name="number" select="." />   <xsl:call-template name="pad">         <xsl:with-param name="string"                   select="format-number($number,'#.###,00','WesternEurope')"/>   </xsl:call-template> </xsl:template>     <xsl:template name="pad">   <xsl:param name="string"/>   <xsl:param name="width" select="16"/>   <xsl:call-template name="str:dup">     <xsl:with-param name="input" select="' '"/>     <xsl:with-param name="count" select="$width - string-length($string)"/>   </xsl:call-template>   <xsl:value-of select="$string"/> </xsl:template>     </xsl:stylesheet>       Output:            10,00            3,50            4,44            77,78           -8,00            1,00           444,00            1,12            7,77             3,14           10,00            9,00             8,00            7,00          666,00         5.555,00   -4.444.444,00           22,33            18,00           36,54           43,00        99.999,00      999.999,00    9.999.999,00            32,00           64,00          -64,00

Converting numbers to Roman numerals

Example 3-6 uses xsl:number as a Roman numeral formatter to label columns as rows:

Example 3-6. Roman-numeral format
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"  xmlns:str="http://www.ora.com/XSLTCookbook/namespaces/strings">      <xsl:output method="text" />     <xsl:include href="../strings/str.dup.xslt"/>     <xsl:variable name="numCols" select="3"/>     <xsl:template match="numbers">    <xsl:for-each select="number[position() &lt;= $numCols]">    <xsl:text>            </xsl:text>    <xsl:number value="position()" format="I"/><xsl:text>   </xsl:text> </xsl:for-each>    <xsl:text>&#xa;     </xsl:text> <xsl:for-each select="number[position() &lt;= $numCols]">    <xsl:text>----------------  </xsl:text> </xsl:for-each>    <xsl:text>&#xa;</xsl:text>    <xsl:for-each select="number[position( ) mod $numCols = 1]">     <xsl:call-template name="pad">       <xsl:with-param name="string">         <xsl:number value="position()" format="i"/>       </xsl:with-param>       <xsl:with-param name="width" select="4"/>     </xsl:call-template>|<xsl:text/> <!-- See recipe 7.1-->     <xsl:apply-templates           select=". | following-sibling::number[position( ) &lt; $numCols]"           mode="format"/>     <xsl:text>&#xa;</xsl:text>    </xsl:for-each> </xsl:template>     <xsl:template match="number" name="format" mode="format">   <xsl:param name="number" select="." />   <xsl:call-template name="pad">     <xsl:with-param name="string" select="format-number(.,'#,###.00')"/>   </xsl:call-template> </xsl:template>     <xsl:template name="pad">   <xsl:param name="string"/>   <xsl:param name="width" select="16"/>   <xsl:call-template name="str:dup">     <xsl:with-param name="input" select="' '"/>     <xsl:with-param name="count" select="$width - string-length($string)"/>   </xsl:call-template>   <xsl:value-of select="$string"/> </xsl:template>     </xsl:stylesheet>       Output:             I                II             III      ----------------  --------------  --------------    i|           10.00            3.50            4.44   ii|           77.78           -8.00            1.00  iii|          444.00            1.12            7.77   iv|            3.14           10.00            9.00    v|            8.00            7.00          666.00   vi|        5,555.00   -4,444,444.00           22.33  vii|           18.00           36.54           43.00 viii|       99,999.00      999,999.00    9,999,999.00   ix|           32.00           64.00          -64.00

Creating column numbers like a spreadsheet

Spreadsheets number columns in the alpha sequence A, B, C ... ZZ, and we can do the same using xsl:number (see Example 3-7).

Example 3-7. Spreadsheet-like column numbers
<xsl:template match="numbers">   <xsl:for-each select="number[position( ) &lt;= $numCols]">     <xsl:text>          </xsl:text>     <xsl:number value="position()" format="A"/><xsl:text>   </xsl:text>     <xsl:text>     </xsl:text>   </xsl:for-each>       <xsl:text>&#xa;</xsl:text>    <xsl:for-each select="number[position( ) &lt;= $numCols]">     <xsl:text>  ---------------- </xsl:text>   </xsl:for-each>       <xsl:text>&#xa;</xsl:text>    <xsl:for-each select="number[position( ) mod $numCols = 1]">     <xsl:value-of select="position()"/><xsl:text>|</xsl:text>     <xsl:apply-templates           select=". | following-sibling::number[position( ) &lt; $numCols]"           mode="format"/>     <xsl:text>&#xa;</xsl:text>    </xsl:for-each> </xsl:template>     <xsl:template match="number" name="format" mode="format">   <xsl:param name="number" select="." />   <xsl:call-template name="pad">     <xsl:with-param name="string" select="format-number(.,'#,###.00')"/>   </xsl:call-template>   <xsl:text>   </xsl:text>  </xsl:template>     Output:        A              B              C   ------------  ------------  ------------ 1|     10.0000        3.5000        4.4400 2|     77.7777        8.0000-       1.0000 3|    444.0000        1.1234        7.7700 4|      3.1416       10.0000        9.0000 5|      8.0000        7.0000      666.0000 6|   5555.0000  4444444.0000-      22.3300 7|     18.0000       36.5400       43.0000 8|  99999.0000   999999.0000  9999999.0000 9|     32.0000       64.0000       64.0001-

Formatting numbers using Arabic characters

Numeric characters from other languages can be used by setting the zero digit in xsl:decimal-format to the zero of that language. This example uses the Unicode character 0x660 (Arabic-Indic Digit Zero):

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"  xmlns:str="http://www.ora.com/XSLTCookbook/namespaces/strings">     <xsl:output method="text"  encoding="UTF-8"/>     <xsl:include href="../strings/str.dup.xslt"/>     <!-- This states that zero starts at character 0x660, which implies one is 0x661, etc. --> <xsl:decimal-format name="Arabic" zero-digit="&#x660;"/>        <xsl:template match="numbers">   <xsl:for-each select="number">       <xsl:call-template name="pad">         <xsl:with-param name="string" select="format-number(.,'#,###.00')"/>       </xsl:call-template> = <xsl:text/>       <xsl:value-of select="format-number(.,'#,###.&#x660;&#x660;','Arabic')"/>      <xsl:text>&#xa;</xsl:text>    </xsl:for-each> </xsl:template>     <xsl:template name="pad">   <xsl:param name="string"/>   <xsl:param name="width" select="16"/>   <xsl:value-of select="$string"/>   <xsl:call-template name="str:dup">     <xsl:with-param name="input" select="' '"/>     <xsl:with-param name="count" select="$width - string-length($string)"/>   </xsl:call-template> </xsl:template>     </xsl:stylesheet>

Here is the output of this code:




XSLT Cookbook
XSLT Cookbook: Solutions and Examples for XML and XSLT Developers, 2nd Edition
ISBN: 0596009747
EAN: 2147483647
Year: 2003
Pages: 208
Authors: Sal Mangano

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net