Numbering and formatting output

Auto-numbering headings, formatting money and percentages, and the date formatting that only exists from XSLT 2.0 onwards.

xsl:number

<xsl:template match="h2">
  <h2>
    <xsl:number level="multiple" count="h1|h2" format="1.1." />
    <xsl:value-of select="."/>
  </h2>
</xsl:template>

<!-- Let the source carry the number, formatting it as roman numerals -->
<xsl:number value="@part" format="I" />

<!-- Numbering list items per section, restarting inside each section -->
<xsl:number level="any" count="li" from="section" format="1" />
AttributeMeaningCommon values
levelHow siblings and ancestors are countedsingle, multiple, any
countWhich nodes contribute a numberA pattern, e.g. h1|h2
fromWhere counting restartsA pattern such as section
formatNumber style1, 01, a, I, with separators
valueNumber an explicit value instead of a nodeAny numeric expression
grouping-separatorThousands separator, or .

format-number and dates

<xsl:decimal-format name="eu" decimal-separator="," grouping-separator="."/>

<xsl:value-of select="format-number(1234.5, '#,##0.00')"/>                    <!-- 1,234.50 -->
<xsl:value-of select="format-number(0.075, '0.0%')"/>                        <!-- 7.5% -->
<xsl:value-of select="format-number(42, '000')"/>                            <!-- 042 -->
<xsl:value-of select="format-number(1234.5, '#.##0,00', 'eu')"/>             <!-- 1.234,50 -->
<xsl:value-of select="format-number(-12.3, '0.00;(0.00)')"/>                 <!-- (12.30) -->

<!-- XSLT 2.0 date formatting; in 1.0 use substring() or a host extension -->
<xsl:value-of select="format-date(current-date(), '[D01] [MNn] [Y0001]')"/>
<xsl:value-of select="format-dateTime(@updated, '[Y0001]-[M01]-[D01] [H01]:[m01]')"/>
  • The pattern is a picture, not a mask: # is optional, 0 is mandatory, and the grouping separator is taken from the decimal format.
  • A negative subpattern after a semicolon lets you print accounting-style negatives.
  • In XSLT 1.0, format-date does not exist — a common workaround is a named template with a lookup table of month names.
  • Round-tripping a decimal through a double can lose precision; for money, format at the presentation layer and keep the exact value in the source.
💡
Locale is a parameter, not a stylesheet constant. Pass the locale in from the calling application and select a decimal format with it, otherwise every number in the site changes shape the moment a second language is added.

FAQ

Why does my heading numbering restart in every section?
Because the default level is single, which counts siblings only. Use level="multiple" with a count pattern covering the whole hierarchy to get 1.2.3 style numbering.
Can I format a date in XSLT 1.0?
Not with format-date, which is 2.0. Use substring() plus a lookup table, or pass an already-formatted string in as a parameter from the application.

Output methods and real use cases Whitespace, text and special characters

Last refreshed 2026-09-18.