Recipe3.3.Converting from Roman Numerals to Numbers


Recipe 3.3. 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">     <ckbk:romans>   <ckbk:roman value="1">i</ckbk:roman>   <ckbk:roman value="1">I</ckbk:roman>   <ckbk:roman value="5">v</ckbk:roman>   <ckbk:roman value="5">V</ckbk:roman>   <ckbk:roman value="10">x</ckbk:roman>   <ckbk:roman value="10">X</ckbk:roman>   <ckbk:roman value="50">l</ckbk:roman>   <ckbk:roman value="50">L</ckbk:roman>   <ckbk:roman value="100">c</ckbk:roman>   <ckbk:roman value="100">C</ckbk:roman>   <ckbk:roman value="500">d</ckbk:roman>   <ckbk:roman value="500">D</ckbk:roman>   <ckbk:roman value="1000">m</ckbk:roman>   <ckbk:roman value="1000">M</ckbk:roman> </ckbk:romans>     <xsl:variable name="ckbk:roman-nums" select="document('')/*/*/ckbk:roman"/>     <xsl:template name="ckbk:roman-to-number">   <xsl:param name="roman"/>      <xsl:variable name="valid-roman-chars">     <xsl:value-of select="document('')/*/ckbk:romans"/>   </xsl:variable>       <xsl:choose>     <!-- returns true if there are any non-Roman characters in the string -->     <xsl:when test="translate($roman,$valid-roman-chars,'')">NaN</xsl:when>     <xsl:otherwise>       <xsl:call-template name="ckbk:roman-to-number-impl">         <xsl:with-param name="roman" select="$roman"/>       </xsl:call-template>     </xsl:otherwise>   </xsl:choose>   </xsl:template>     <xsl:template name="ckbk: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 + $ckbk:roman-nums[. = $roman]/@value"/>     </xsl:when>     <xsl:otherwise>       <xsl:variable name="roman-num"             select="$ckbk:roman-nums[. = substring($roman, 1, 1)]"/>       <xsl:choose>         <xsl:when test="$roman-num/following-sibling::ckbk:roman =            substring($roman, 2, 1)">           <xsl:call-template name="ckbk: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="ckbk: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 1.0 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)"/>

In XSLT 2.0, you can use a nested if-then-else expression or use a lookup within a sequence:

<xsl:variable name="roman-value" select="(1,5,10,50,100,500,1000)      [index-of(('I', 'V', 'X', 'L', 'C', 'D', 'M'),upper-case($c))]"/>




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