How to transform a number (1,2,3, etc) into an ordinal number (1st, 2nd, 3rd, etc) using xslt
Asked Answered
P

3

5

Pretty simple question, how can I transform a number (1, 2, 3, etc) into a print friendly ordinal number (1st, 2nd, 3rd, etc) using xslt?

Currently the following works for 1-20 but we may be seeing larger sets of entities getting ranked soon:

<xsl:template name="FormatRanking">
    <xsl:param name="Value"></xsl:param>

    <xsl:choose>
        <xsl:when test="$Value = '1'">
            <xsl:value-of select="$Value"/>st
        </xsl:when>
        <xsl:when test="$Value = '2'">
            <xsl:value-of select="$Value"/>nd
        </xsl:when>
        <xsl:when test="$Value = '3'">
            <xsl:value-of select="$Value"/>rd
        </xsl:when>
        <xsl:otherwise>
            <xsl:value-of select="$Value"/>th
        </xsl:otherwise>
    </xsl:choose>

</xsl:template> 

The only way I would know how to do this would be to change the xsl:when's:

<xsl:when test="$Value = '1'">
<xsl:when test="$Value = '2'">
<xsl:when test="$Value = '3'">

to (not even sure if this is correct):

<xsl:when test="$Value = '1' or $Value = '21' or $Value = '31' ...">
<xsl:when test="$Value = '2' or $Value = '22' or $Value = '33' ...">
<xsl:when test="$Value = '3' or $Value = '22' or $Value = '33' ...">

I'd like to do something similar to this Is there an easy way to create ordinals in C#? but I'm not sure if it's possible in Xslt.

At this point we only need an English solution.

Pasho answered 29/7, 2009 at 19:42 Comment(3)
Your faith in the abilities of XSLT is touching, if wildly unrealisticLeadin
@skaffman: I guess you're underestimating the abilities of XSLT.Tigon
After all, XSLT is supposedly turing complete.Ell
T
9

Here's the solution from "Is there an easy way to create ordinals in C#?", translated to XSLT:

<xsl:template name="FormatRanking">
  <xsl:param name="Value" select="0" />

  <xsl:value-of select="$Value"/>

  <!-- a little parameter sanity check (integer > 0) -->
  <xsl:if test="
    translate($Value, '0123456789', '') = ''
    and
    $Value > 0
  ">
    <xsl:variable name="mod100" select="$Value mod 100" />
    <xsl:variable name="mod10"  select="$Value mod 10" />

    <xsl:choose>
      <xsl:when test="$mod100 = 11 or $mod100 = 12 or $mod100 = 13">th</xsl:when>
      <xsl:when test="$mod10 = 1">st</xsl:when>
      <xsl:when test="$mod10 = 2">nd</xsl:when>
      <xsl:when test="$mod10 = 3">rd</xsl:when>
      <xsl:otherwise>th</xsl:otherwise>
    </xsl:choose>
  </xsl:if>
</xsl:template>
Tigon answered 30/7, 2009 at 9:21 Comment(1)
@Tigon Thanks a lot!!! This is something I've been looking for. You're a genius :)Olivette
M
5

I'm not saying it's a good idea to do this in xslt, but...

  <xsl:template name="FormatRanking">
    <xsl:param name="Value"></xsl:param>

    <xsl:choose>
            <xsl:when test="substring($Value,string-length($Value)-1) = '11'">
                    <xsl:value-of select="$Value"/>th
            </xsl:when>
            <xsl:when test="substring($Value,string-length($Value)-1)  = '12'">
                    <xsl:value-of select="$Value"/>th
            </xsl:when>
            <xsl:when test="substring($Value,string-length($Value)-1)  = '13'">
                    <xsl:value-of select="$Value"/>th
            </xsl:when>
            <xsl:when test="substring($Value,string-length($Value)) = '1'">
                    <xsl:value-of select="$Value"/>st
            </xsl:when>
            <xsl:when test="substring($Value,string-length($Value))  = '2'">
                    <xsl:value-of select="$Value"/>nd
            </xsl:when>
            <xsl:when test="substring($Value,string-length($Value))  = '3'">
                    <xsl:value-of select="$Value"/>rd
            </xsl:when>
            <xsl:otherwise>
                    <xsl:value-of select="$Value"/>th
            </xsl:otherwise>
    </xsl:choose>
  </xsl:template>

Edit: or a bit neater solution:

  <xsl:template name="FormatRanking">
    <xsl:param name="Value"></xsl:param>

    <xsl:variable name="penultimateChar" select="substring($Value,string-length($Value)-1, 1)"/>
    <xsl:variable name="lastChar" select="substring($Value,string-length($Value))"/>

    <xsl:value-of select="$Value"/>
    <xsl:choose>
            <xsl:when test="$penultimateChar= '1'">
                    <xsl:text>th </xsl:text>
            </xsl:when>
            <xsl:when test="$lastChar = '1'">
                    <xsl:text>st </xsl:text>
             </xsl:when>
            <xsl:when test="$lastChar = '2'">
                    <xsl:text>nd </xsl:text>
            </xsl:when>
            <xsl:when test="$lastChar = '3'">
                    <xsl:text>rd </xsl:text>
            </xsl:when>
            <xsl:otherwise>
                    <xsl:text>th </xsl:text>
            </xsl:otherwise>
    </xsl:choose>

Marlee answered 29/7, 2009 at 20:50 Comment(2)
+1 for solution #2, this is quite close. I'd refrain from applying string functions to numbers, though, as long as there's a way to do it with number functions.Tigon
Thank you for the suggestions Alohci, I was originally thinking of doing something like solution #2 but didn't know what string functions to use. However I'm going with Tomalak's solution that mirrors the C# idea and uses math to do it instead of string functions.Pasho
O
0

IMO, by using regular expressions (XSLT 2.0), this can be done more concisely:

<xsl:template name="FormatRanking">
  <xsl:param name="Value"/>
  <xsl:choose>
    <xsl:when test="matches(string($Value), '.+[^1][123]$')">
      <xsl:value-of select="replace(replace(replace(string($Value), '1$', '1st'), '2$', '2nd'), '3$', '3rd')" />
    </xsl:when>
    <xsl:otherwise>
      <xsl:value-of select="concat(string($Value), 'th')" />
    </xsl:otherwise>
  </xsl:choose>
</xsl:template>

This checks for applicability of the special rule before using a regular expression which recognises number strings ending on 1/2/3, excluding 11/12/13. Only then special handling is applied. Otherwise the "th" is just appended.

Please note that the $Value is converted explicitly to a string datatype before applying string operations to avoid potentially funny situations. The return value is anyway implicitly a string.

However, just as a side comment, this only works for the English language. I'd be interested in a true multilingual approach....

Otorhinolaryngology answered 15/11, 2021 at 10:41 Comment(1)
This question is quite old and the answers are obviously intended for XSLT 1.0. In XSLT 2.0 this becomes rather trivial using <xsl:number value="$Value" format="1" ordinal="yes"/>. And yes, the specification covers multiple languages, though a processor may not support all or any of them.Bistro

© 2022 - 2024 — McMap. All rights reserved.