| """Convert to and from Roman numerals""" | 
 |  | 
 | __author__ = "Mark Pilgrim (f8dy@diveintopython.org)" | 
 | __version__ = "1.4" | 
 | __date__ = "8 August 2001" | 
 | __copyright__ = """Copyright (c) 2001 Mark Pilgrim | 
 |  | 
 | This program is part of "Dive Into Python", a free Python tutorial for | 
 | experienced programmers.  Visit http://diveintopython.org/ for the | 
 | latest version. | 
 |  | 
 | This program is free software; you can redistribute it and/or modify | 
 | it under the terms of the Python 2.1.1 license, available at | 
 | http://www.python.org/2.1.1/license.html | 
 | """ | 
 |  | 
 | import re | 
 |  | 
 | #Define exceptions | 
 | class RomanError(Exception): pass | 
 | class OutOfRangeError(RomanError): pass | 
 | class NotIntegerError(RomanError): pass | 
 | class InvalidRomanNumeralError(RomanError): pass | 
 |  | 
 | #Define digit mapping | 
 | romanNumeralMap = (('M',  1000), | 
 |                    ('CM', 900), | 
 |                    ('D',  500), | 
 |                    ('CD', 400), | 
 |                    ('C',  100), | 
 |                    ('XC', 90), | 
 |                    ('L',  50), | 
 |                    ('XL', 40), | 
 |                    ('X',  10), | 
 |                    ('IX', 9), | 
 |                    ('V',  5), | 
 |                    ('IV', 4), | 
 |                    ('I',  1)) | 
 |  | 
 | def toRoman(n): | 
 |     """convert integer to Roman numeral""" | 
 |     if not (0 < n < 5000): | 
 |         raise OutOfRangeError("number out of range (must be 1..4999)") | 
 |     if int(n) != n: | 
 |         raise NotIntegerError("decimals can not be converted") | 
 |  | 
 |     result = "" | 
 |     for numeral, integer in romanNumeralMap: | 
 |         while n >= integer: | 
 |             result += numeral | 
 |             n -= integer | 
 |     return result | 
 |  | 
 | #Define pattern to detect valid Roman numerals | 
 | romanNumeralPattern = re.compile(""" | 
 |     ^                   # beginning of string | 
 |     M{0,4}              # thousands - 0 to 4 M's | 
 |     (CM|CD|D?C{0,3})    # hundreds - 900 (CM), 400 (CD), 0-300 (0 to 3 C's), | 
 |                         #            or 500-800 (D, followed by 0 to 3 C's) | 
 |     (XC|XL|L?X{0,3})    # tens - 90 (XC), 40 (XL), 0-30 (0 to 3 X's), | 
 |                         #        or 50-80 (L, followed by 0 to 3 X's) | 
 |     (IX|IV|V?I{0,3})    # ones - 9 (IX), 4 (IV), 0-3 (0 to 3 I's), | 
 |                         #        or 5-8 (V, followed by 0 to 3 I's) | 
 |     $                   # end of string | 
 |     """ ,re.VERBOSE) | 
 |  | 
 | def fromRoman(s): | 
 |     """convert Roman numeral to integer""" | 
 |     if not s: | 
 |         raise InvalidRomanNumeralError('Input can not be blank') | 
 |     if not romanNumeralPattern.search(s): | 
 |         raise InvalidRomanNumeralError('Invalid Roman numeral: %s' % s) | 
 |  | 
 |     result = 0 | 
 |     index = 0 | 
 |     for numeral, integer in romanNumeralMap: | 
 |         while s[index:index+len(numeral)] == numeral: | 
 |             result += integer | 
 |             index += len(numeral) | 
 |     return result |