XSLT format numbers

Formatting

1. Formatting numbers and dates
2. Dates
3. formatting first item differently than the rest
4. Zero pad digit string
5. :Formatting output as Java source
6. Word Wrap in PRE
7. format-number and time string
8. Format number to specific layout
9. using format-number() for aligned text output
10. Format number problem
11. format-number, changing the NaN string
12. Removing leading zeros
13. Remove non-digit characters from a string
14. Format number, apostrophe as separator
15.

1.

Formatting numbers and dates

David Marston

>Could someone show me how to convert the results of something
>simple like <xsl:value-of select="cost"/> so that it displays
>as currency, like $12.00.

format-number(cost,'###0.00') causes cost to be formatted with 1-4 places before the decimal point, and 2 places after. The # signals zero suppression, while the 0 signals display of any digit. The spec says (section 12.3, first paragraph) that the currency sign 0x00A4 must not be in the pattern string, but you could concatenate it in front.

>Is there a similar function for converting a date the is in
>yyyymmdd format?

There is no function that is specifically aware of dates, but you can use format-number to produce strings with the desired leading zeroes and punctuation. To map the "mm" field to month names, you could just use xsl:if.

2.

Dates

Richard Light


>I have : <date format="ISO-8601">2000-05-24</date>
>how can i have the date on this format : Vendredi 24 juin 2000
    

This code (not tested, although it's based on a routine which has been) should output "24 mai 2000" [sic] for the date you specify. I haven't a clue how to derive the day of the week in XSLT!

<xsl:template name="convertMonth">
  <xsl:variable name="month-as-number" select="number(substring-
before(substring-after(text(), '-'), '-'))"/>
  <xsl:choose>
    <xsl:when test="$month-as-number=1">janvier</xsl:when>
    <xsl:when test="$month-as-number=2">fvrier</xsl:when>
    <xsl:when test="$month-as-number=3">mars</xsl:when>
    <xsl:when test="$month-as-number=4">avril</xsl:when>
    <xsl:when test="$month-as-number=5">mai</xsl:when>
    <xsl:when test="$month-as-number=6">juin</xsl:when>
    <xsl:when test="$month-as-number=7">juillet</xsl:when>
    <xsl:when test="$month-as-number=8">aot</xsl:when>
    <xsl:when test="$month-as-number=9">septembre</xsl:when>
    <xsl:when test="$month-as-number=10">octobre</xsl:when>
    <xsl:when test="$month-as-number=11">novembre</xsl:when>
    <xsl:when test="$month-as-number=12">decembre</xsl:when>
    <xsl:otherwise><xsl:value-of select="concat('[invalid month: ',
'$month-as-number')"/></xsl:otherwise>
  </xsl:choose>
</xsl:template>

<xsl:template xml:space="preserve" match="date[@format='ISO-8601']">
  <xsl:value-of select="substring-after(substring-after(text(), '-'),
'-')"/>
  <xsl:call-template name="convertMonth"/>
  <xsl:value-of select="substring-before(text(), '-')"/>
</xsl:template>

Mike Kay adds. probably more sensible to do using an external function, typically in Java or JavaScript. Details depend on your choice of XSLT processor.

3.

formatting first item differently than the rest

Bob DuCharme

>I want to format the section title one way if this is the first
<fieldlist>,
>and slightly differently for number 2ff.  

Write two templates, using "fieldlist[1]" for the match pattern in one and "fieldlist[position() > 1]" for the other.

Rick Suiter improves this with The second template doesn't even need the qualification "[position() > 1]". XSLT will apply the more specific one to the first fieldlist, and the generic one with 'match="fieldlist"' to all the rest.

and Jeni T provides an alternative

Conditional processing usually involves either xsl:if or xsl:choose/xsl:when/xsl:otherwise. When you want to do something if something is the case, and not if it isn't, then it's best to use xsl:if:

<xsl:if test="...">
  <!-- if the test is true -->
</xsl:if>
<!-- whatever the case -->

When you want to do one thing if something and something different if something else, then it's best to use xsl:choose/xsl:when/xsl:otherwise:

<xsl:choose>
  <xsl:when test="...">
    <!-- if the test is true -->
  </xsl:when>
  <!-- more xsl:whens -->
  <xsl:otherwise>
    <!-- if none of the xsl:whens have been true -->
  </xsl:otherwise>
</xsl:choose>

In your case, you want to check whether the 'fieldlist' you're currently looking at is the first one. As you are iterating over all the fieldlists, you can use position() for this. position() gives you the position of the current node in the current node list. Within the xsl:for-each, the current node is the fieldlist you're looking at, and the current node list is the list of all the fieldlists that are children of the particular fields element.

So, you can do:

<xsl:choose>
  <xsl:when test="position() = 1">
    <!-- processing of the first fieldlist -->
  </xsl:when>
  <xsl:otherwise>
    <!-- processing of the other fieldlists -->
  </xsl:otherwise>
</xsl:choose>

And depending on how different the processing is, you might find xsl:if appropriate as well:

<xsl:if test="position() = 1">
  <!-- processing of the first fieldlist -->
</xsl:if>
<!-- processing of *all* the fieldlists -->

4.

Zero pad digit string

Robert C. Lyons


> Padding a number:
> Requirement: zero pad a digit string 
> Example: 456 --> 000456
> Where the new string length is a passed parameter to the 
> padding template.
    

I believe that format-number( "456", "000000" ) would return "000456". The 2nd arg is the format pattern.

In your case, the number of zeroes in the format pattern must be equal to the "new string length." You can use the substring function to construct a format pattern with the appropriate number of zeroes as follows:

substring( $long_string_of_zeroes, 1, $new_string_length )

Note that I haven't tested any of this.

Chris Maden offers

How about

<xsl:number format="000000" select="some-node"/>

Note that the format attribute's value is an attribute value template, so it can be derived from something in the source document.

Robin@kitsite.com offers

<xsl:template name="format-batchnum">
  <xsl:param name="batchnum"/>
  <xsl:param name="numbatchdigit" select="12"/>

  <xsl:choose>
    <xsl:when test="string-length($batchnum)>=$numbatchdigit">
      <xsl:value-of select="$batchnum"/>
    </xsl:when>
    <xsl:otherwise>
      <xsl:text>0</xsl:text>
      <xsl:call-template name="format-batchnum">
        <xsl:with-param name="batchnum" select="$batchnum"/>
        <xsl:with-param name="numbatchdigit" select="$numbatchdigit -1"/>
      </xsl:call-template> 
    </xsl:otherwise>
  </xsl:choose>
</xsl:template>

although format-number may be a better way to do it really.

5.

:Formatting output as Java source

Mike Kay

I have a XML document which is a description of an user interface. I want to generate Java source code. The problem is, the XSLT for a single Java line goes over many lines:

<xsl:value-of 
  select="../parent::node()/@name"/>.setBackground(new
java.awt.Color 
   (<xsl:value-of select="color/@red"/>, 
    <xsl:value-of select="color/@green"/>,
    <xsl:value-of select="color/@blue"/>));

Here, the output is for example

myPanel.setBackground(new java.awt.Color         
   (204,
    204,
    204));

And it should be

myPanel.setBackground(new java.awt.Color(204,204,204));

I already set (at the beginning of my document)

<xsl:output method="text"/>
<xsl:strip-space elements = "*"/>

Answer:

Use <xsl:text> to control output of newlines.

E.g. change
    <xsl:value-of select="color/@green"/>,
to
    <xsl:value-of select="color/@green"/><xsl:text>,</xsl:text>
or to
    <xsl:value-of select="color/@green"/>,<xsl:text/>

Newlines in the stylesheet are significant if they appear before or after non-whitespace characters, they are insignificant if they appear between two elements.

6.

Word Wrap in PRE

Steve Muench

>How can I have the text maintain its format and word wrap at the
> same time?
    

Here's an example from my soon-to-be-released book "Building Oracle XML Applications" where we work through building an XSLT-powered online discussion forum application. I use a combination of the following two templates to format code listings in the application.

You can use the routines by importing "UtilText.xsl" into your stylesheet. Then, presuming your code example is selected by the expression "programlisting", you could use this with the code:

       <span style="font-family: monospace">
         <xsl:call-template name="br-replace">
           <xsl:with-param name="text">
             <xsl:call-template name="sp-replace">
               <xsl:with-param select="programlisting"/>
             </xsl:call-template>
           </xsl:with-param>
         </xsl:call-template>
       </span>

the inner call-template replaces occurrences of pairs of consecutive spaces with a pair of non-breaking spaces represented by &#160;. The outer call-template replaces line breaks with a <br/> tag.

<!-- UtilText.xsl: Common text formatting routines -->
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
  <!-- Replace new lines with html <br> tags -->
  <xsl:templaQ:Word Wrap and PRE
<!-- UtilText.xsl: Common text formatting routines -->
<xsl:stylesheet 
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
  <!-- Replace new lines with html <br> tags -->
  <xsl:template name="br-replace">
    <xsl:param name="text"/>
    <xsl:variable name="cr" select="'&#xa;'"/>
    <xsl:choose>
      <!-- If the value of the $text parameter 
              contains a carriage return... -->
      <xsl:when test="contains($text,$cr)">
        <!-- Return the substring of $text before the carriage return -->
        <xsl:value-of select="substring-before($text,$cr)"/>
        <!-- And construct a <br/> element -->
        <br/>
        <!--
         | Then invoke this same br-replace template again, passing the
         | substring *after* the carriage return as the new "$text" to
         | consider for replacement
         +-->
        <xsl:call-template name="br-replace">
          <xsl:with-param name="text" select="substring-after($text,$cr)"/>
        </xsl:call-template>
      </xsl:when>
      <xsl:otherwise>
        <xsl:value-of select="$text"/>
      </xsl:otherwise>
   </xsl:choose>
  </xsl:template>
  <!-- Replace two consecutive spaces w/ 2 non-breaking spaces -->
  <xsl:template name="sp-replace">
    <xsl:param name="text"/>
    <!-- NOTE: There are two spaces   ** here below -->
    <xsl:variable name="sp"><xsl:text>  </xsl:text></xsl:variable>
    <xsl:choose>
      <xsl:when test="contains($text,$sp)">
        <xsl:value-of select="substring-before($text,$sp)"/>
        <xsl:text>&#160;&#160;</xsl:text>
        <xsl:call-template name="sp-replace">
          <xsl:with-param name="text" select="substring-after($text,$sp)"/>
        </xsl:call-template>
      </xsl:when>
      <xsl:otherwise>
        <xsl:value-of select="$text"/>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>
</xsl:stylesheet>
te name="br-replace">
    <xsl:param name="text"/>
    <xsl:variable name="cr" select="'&#xa;'"/>
    <xsl:choose>
      <!-- If the value of the $text parameter contains a carriage return... -->
      <xsl:when test="contains($text,$cr)">
        <!-- Return the substring of $text before the carriage return -->
        <xsl:value-of select="substring-before($text,$cr)"/>
        <!-- And construct a <br/> element -->
        <br/>
        <!--
         | Then invoke this same br-replace template again, passing the
         | substring *after* the carriage return as the new "$text" to
         | consider for replacement
         +-->
        <xsl:call-template name="br-replace">
          <xsl:with-param name="text" select="substring-after($text,$cr)"/>
        </xsl:call-template>
      </xsl:when>
      <xsl:otherwise>
        <xsl:value-of select="$text"/>
      </xsl:otherwise>
   </xsl:choose>
  </xsl:template>
  <!-- Replace two consecutive spaces w/ 2 non-breaking spaces -->
  <xsl:template name="sp-replace">
    <xsl:param name="text"/>
    <!-- NOTE: There are two spaces   ** here below -->
    <xsl:variable name="sp"><xsl:text>  </xsl:text></xsl:variable>
    <xsl:choose>
      <xsl:when test="contains($text,$sp)">
        <xsl:value-of select="substring-before($text,$sp)"/>
        <xsl:text>&#160;&#160;</xsl:text>
        <xsl:call-template name="sp-replace">
          <xsl:with-param name="text" select="substring-after($text,$sp)"/>
        </xsl:call-template>
      </xsl:when>
      <xsl:otherwise>
        <xsl:value-of select="$text"/>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>
</xsl:stylesheet>

7.

format-number and time string

David Marston

>How to use  format-number when trying
>to make numbers look like:
>02:59:12
>hours:minutes:seconds
	

I think format-number works well, but you have to think about it in a different way. The format-number function is designed to render one rational number in base 10 without scientific notation. Crucial to your need is that it will only do one stream of leading zeroes. Thus, you need to render three numbers (hours, minutes, seconds) in succession, with slightly different formats for each.

The overall string would be produced by this formula, which could be
used in an xsl:value-of instruction:
concat(format-number($hours,'#0'),':',
       format-number($minutes,'00'),':',
       format-number($seconds,'00.###'))

The format of the decimal portion of the seconds may be adjusted to suit taste.

If the original data is a single quantity of one time unit (hours, minutes, seconds, even days) you just use div, mod, floor(), and factors like 60 and 3600 as necessary.

If you start with $data containing the amount of time in seconds,
$seconds is ($data mod 60)
$minutes is ((floor($data div 60)) mod 60)
$hours  is  (floor($data div 3600))

If you start with $data containing the amount of time in minutes,
$seconds is (($data * 60) mod 60)
$minutes is (floor($data) mod 60)
$hours  is  (floor($data div 60))

If you start with $data containing the amount of time in hours,
$seconds is (($data * 3600) mod 60)
$minutes is (floor($data * 60) mod 60)
$hours  is  (floor($data))
	

The above formulae allow decimal fractions of a second to fall through, which you'll trim in the formatting anyway. They also allow $hours to be arbitrarily large, so you may want to expand the format string (###0) or add another calculation for days.

8.

Format number to specific layout

eni Tennison

In the format pattern, the # stands for an optional digit and the 0 stands for a required digit. So:

  format-number(12345, '#,##0.00') => '12,345.00'
  format-number(1.5,   '#,##0.00') => '1.50'
  format-number(0.543, '#,##0.00') => '0.54'
  format-number(12345, '#,###.##') => '12,345'
  format-number(1.5,   '#,###.##') => '1.5'
  format-number(0.543, '#,###.##') => '.54'

You need to have #s as well as 0s to allow you to specify the grouping size in numbers without getting leading 0s all over the place, and to specify the maximum number of decimal digits without forcing there to be decimal digits.

Ken adds:

Basically, as far as I can tell format-number() can only be used to pad numbers with zero digits, not with any other character.

That would require non-Java processors to implement bug-emulation mode as I feel quite certain that the Java library failure to recognize the negative pattern grammar of the format pattern syntax wouldn't want to be copied in an implementation:

Consider:

format-number(-12345, '#,###.00;(#)')

The above produces "(12,345)" in Java-based processors and (I'm told) "(12345)" in C++-based processors. I believe the C++-based processors are correct. For all of my tests of negative number syntax in the grammar, they are *all* being ignored and supplanted by the positive number syntax in Java-based processors.

Where does one draw the line between empirical evidence and functional definition? This question is being raised in the OASIS XSLT/XPath conformance committee.

9.

using format-number() for aligned text output

G. Ken Holman

Is it possible to define the Alignment of a Text/Number in XSLT ?

Not for text, but I had assumed one could for number. I've shown below a trick for doing text alignment.

But ... I think my test has revealed another facet of format-number() that is open for interpretation.

I was anticipating format-number(price,'####0.00') to always produce a string of 8 characters in length, yet both processors I have (XT and Saxon) are Java based and are using the Java library which is known to have other problems for this function.

Any alternative interpretations out there for the specification wording of: "digit specifies the character used for a digit in the format pattern; the default value is the number sign character (#)"? Would anyone else expect the "#" to produce a space if no digit is there? If that isn't the behaviour, then what is the utility of the "#"?


T:\ftemp>type eric.xml
<items>
<item>
<name>Pen</name>
<price>1.25</price>
</item>
<item>
<name>Pencil</name>
<price>0.5</price>
</item>
<item>
<name>Bag</name>
<price>12.38</price>
</item>
</items>
T:\ftemp>type eric.xsl
<?xml version="1.0"?><!--filename.xsl-->
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                 version="1.0">

<xsl:variable name="pad" select="'              '"/>
<xsl:variable name="pad-len" select="string-length($pad)"/>

<xsl:template match="/">
   <xsl:for-each select="//item">
     <xsl:value-of select="name"/>
     <xsl:value-of 
	  select="substring($pad,1,$pad-len - string-length(name))"/>
     <xsl:text>|</xsl:text>
     <xsl:value-of select="format-number(price,'####0.00')"/>
     <xsl:text>
</xsl:text>
   </xsl:for-each>
</xsl:template>

</xsl:stylesheet>

T:\ftemp>xt eric.xml eric.xsl
<?xml version="1.0" encoding="utf-8"?>
Pen           |1.25
Pencil        |0.50
Bag           |12.38

Mike Kay tags on with

The JDK 1.1 specification of these patterns, which XSLT relies on, is one of the vaguest bits of specification I have ever seen. The details I give in my book are established almost entirely by trial and error.

I think that before the decimal point, the only use of "#" is to indicate where the thousands separator should go, e.g. #,##0.00. After the decimal point, trailing zeroes are omitted if "#" is used rather than "0".

10.

Format number problem

Michael Kay



> I want to use the function  format-number to to put a number 
> in a  money  format.  This works when the number is either 
> not signed or negatively signed. The XML we got from our 
> client has a "+" sign like this example:

> <xsl:value-of select="format-number(+00003345351.89,'$#,###.00')"/>

XPath 1.0 doesn't allow a leading plus sign in a number. You can get rid of it using translate($num, '+', '').

XPath 1.0 predates XML Schema: its authors did a good job, but predicting the contents of XML Schema would have been nothing short of miraculous.

XPath 2.0 fixes this.

11.

format-number, changing the NaN string

Joerg Heinicke





> In the following, if the  'SHARE_PRICE' is
> not number, it would return me NaN

>  <xsl:value-of select="format-number(SHARE_PRICE, '###,##0.00')"/>

>   Without any if/when statements, is there anyway to overwrite it 
> so that it returns NA or something else I specify insteadof NaN?

See the rec

Using

<xsl:decimal-format NaN="another string"/>

Replaces NaN with the specified string

12.

Removing leading zeros

Norma Yeazell




>I am trying to remove leading zeros from a String like "000747-918-1212"
>  Please let me know if there is any function available in XSL to do this.

Have a look at format-number(), you will need something like:

<xsl:value-of select="format-number(.,0)"/> 

Michael Kay adds

Of course, it's easier in XSLT 2.0:

<xsl:value-of select="replace(., '^0*', '')"/>

13.

Remove non-digit characters from a string

Michael Kay



# What is the equivalent of the following logic in XSLT
# PhoneNumber=(678)898-4274
# for (int i = 0; i < phoneNumber.length(); i++) {
#                 if (Character.isDigit(phoneNumber.charAt(i))) 
# out = out + phoneNumber.charAt(i);
#             }

<xsl:value-of select="translate(phoneNumber, translate(phoneNumber,
'0123456789', ''), '')"/>

14.

Format number, apostrophe as separator

David Carlisle, Mike Kay


I have a little problem with the format of my numbers... Living in
Switzerland, I would like to use a separator other than a comma (,)...
Is it possible to use ' ???
By just replacing the commas by an apostrophe, it obviously doesn't work...
Is there a way to do this???

<xsl:value-of select='format-number(montant, "###,###,###")' />

You just have to obey (both) XPath and XML rules for strings,

for format-number you want an argument that is a string containing a ', to get an XPath string literal containing ' you need to delimit it with " so format-number(... , " ... ' ...") now you want to put all of that in an XML attribute, as it contains both a ' and a " you need to quote one of them using &apos; or &quot; so that you can use it to delimit the attribute value,


select="format-number(... , &quot; ... ' ...&quot;)"
or
select='format-number(... , " ... &apos; ...")'

something like this:

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

<xsl:output method="text"/>

<xsl:decimal-format name="foo"
 grouping-separator="'" digit="#" zero-digit="0" decimal-separator="."/>

<xsl:template match="/">
: <xsl:value-of select="format-number(123456789.12,'###,###.000')"/>
: <xsl:value-of 
      select="format-number(123456789.12,"###'###.000",'foo')"/>
</xsl:template>

</xsl:stylesheet>

If executed with saxon7 produces what I would expect which is

$ saxon7 fn.xsl fn.xsl

: 123,456,789.120
: 123'456'789.120

However saxon7 is mainly an xslt2 implementation and this is an XSLT1 question but saxon6 produces

$ saxon fn.xsl fn.xsl

: 123,456,789.120
: 123456789###.000

which is odd, but actually it looks like it isn't saxon bug really, that was with instant saxon on windows (ie using Microsoft's JVM) saxon6 using sun's JVM produces the same as saxon7:

$ saxon fn.xsl fn.xsl

: 123,456,789.120
: 123'456'789.120

Michael Kay adds

Actually, format-number() in XSLT 1.0 is defined by reference to the JDK 1.1 specification of the DecimalFormat class, which treats apostrophes in the picture string specially. I don't have access to the JDK 1.1 spec right now, but it's entirely possible that the behavior of Instant Saxon is "correct" (because it's using a JDK 1.1 Java implementation) and that the behavior under later Java VMs is technically incorrect, even if it's more usable. However, the JDK 1.1 spec for DecimalFormat is very woolly, so anything goes, really.

Fortunately in XSLT 2.0 the dependency on a particular version of Java has been removed.

15.


My XML data contains an element whose contents represent a date in YYYYMMDD
format. I'm trying to get this to produce YYYY/MM/DD in the output.

This would work if you defined "/" in your xsl:decimal-format as a grouping-separator. (It might, anyway: format-number() in XSLT 1.0 is badly under-specified).

Adding 
<xsl:decimal-format name="date" grouping-separator="/"/> and using 

<xsl:value-of select="format-number(//E1EDK03[IDDAT='012']/DATUM,
         '####/##/##', 'date')"/>

gives me 20/05/01/15