Converting from Roman Numerals to Numbers

Problem

You need to convert a Roman numeral to a number.

Solution

Roman numbers do not use a place value system; instead, the number is composed by adding or subtracting the fixed value of the specified Roman numeral characters. If the following character has a lower or equal value, you add; otherwise, you subtract:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" 
  xmlns:math="http://www.ora.com/XSLTCookbook/math">
   
<math:romans>
  <math:roman value="1">i</math:roman>
  <math:roman value="1">I</math:roman>
  <math:roman value="5">v</math:roman>
  <math:roman value="5">V</math:roman>
  <math:roman value="10">x</math:roman>
  <math:roman value="10">X</math:roman>
  <math:roman value="50">l</math:roman>
  <math:roman value="50">L</math:roman>
  <math:roman value="100">c</math:roman>
  <math:roman value="100">C</math:roman>
  <math:roman value="500">d</math:roman>
  <math:roman value="500">D</math:roman>
  <math:roman value="1000">m</math:roman>
  <math:roman value="1000">M</math:roman>
</math:romans>
   
<xsl:variable name="math:roman-nums" select="document('')/*/*/math:roman"/>
   
<xsl:template name="math:roman-to-number">
  <xsl:param name="roman"/>
  
  <xsl:variable name="valid-roman-chars">
    <xsl:value-of select="document('')/*/math:romans"/>
  </xsl:variable>
   
  <xsl:choose>
    <xsl:when test="translate($roman,$valid-roman-chars,'')">NaN</xsl:when>
    <xsl:otherwise>
      <xsl:call-template name="math:roman-to-number-impl">
        <xsl:with-param name="roman" select="$roman"/>
      </xsl:call-template>
    </xsl:otherwise>
  </xsl:choose>  
</xsl:template>
   
<xsl:template name="math:roman-to-number-impl">
  <xsl:param name="roman"/>
  <xsl:param name="value" select="0"/>
  
  <xsl:variable name="len" select="string-length($roman)"/>
  
  <xsl:choose>
    <xsl:when test="not($len)">
      <xsl:value-of select="$value"/>
    </xsl:when>
    <xsl:when test="$len = 1">
      <xsl:value-of select="$value + $math:roman-nums[. = $roman]/@value"/>
    </xsl:when>
    <xsl:otherwise>
      <xsl:variable name="roman-num"  
          select="$math:roman-nums[. = substring($roman, 1, 1)]"/>
      <xsl:choose>
        <xsl:when test="$roman-num/following-sibling::math:roman =
           substring($roman, 2, 1)">
          <xsl:call-template name="math:roman-to-number-impl">
            <xsl:with-param name="roman" select="substring($roman,2,$len - 1)"/>
            <xsl:with-param name="value" select="$value - $roman-num/@value"/>
          </xsl:call-template>
        </xsl:when>
        <xsl:otherwise>
          <xsl:call-template name="math:roman-to-number-impl">
            <xsl:with-param name="roman" select="substring($roman,2,$len - 1)"/>
            <xsl:with-param name="value" select="$value + $roman-num/@value"/>
          </xsl:call-template>
        </xsl:otherwise>
      </xsl:choose>
    </xsl:otherwise>
  </xsl:choose>
  
</xsl:template>
</xsl:stylesheet>

Discussion

The xsl:number element provides a convenient way to convert numbers to Roman numerals; however, for converting from Roman numerals to numbers, you are on your own. The recursive template shown earlier is straightforward and much like that already found in Jeni Tennison’s XSLT and XPath on the Edge (M&T Books, 2001).

There are two small caveats, but they should not cause trouble in most cases. The first is that the previous solution will not work with Roman numerals using mixed case (e.g., IiI). Such odd strings would hardly appear in reasonable data source, but this code will neither reject such input nor arrive at the “correct” value. Adding code to convert to one case allows the code to reject or correctly process these mixed Romans.

The second caveat relates to the fact that there is no standard Roman representation for numbers higher than 1,000. Saxon and Xalan keep stringing Ms together, but another processor might do something else.

If for some reason you object to storing data about Roman numerals in the stylesheet, then the following XPath decodes a Roman numeral:

<xsl:variable name="roman-value" 
     select="($c = 'i' or $c = 'I') * 1 +
            ($c = 'v' or $c = 'V') * 5 +
            ($c = 'x' or $c = 'X') * 10 +
            ($c = 'l' or $c = 'L') * 50 +
            ($c = 'c' or $c = 'C') * 100 +
            ($c = 'd' or $c = 'D') * 500 +
            ($c = 'm' or $c = 'M') * 1000)"/>
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.218.78.102