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>
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 M
s 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)"/>