Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/docutils/utils/roman.py: 91%

34 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2023-06-07 06:06 +0000

1"""Convert to and from Roman numerals""" 

2 

3__author__ = "Mark Pilgrim (f8dy@diveintopython.org)" 

4__version__ = "1.4" 

5__date__ = "8 August 2001" 

6__copyright__ = """Copyright (c) 2001 Mark Pilgrim 

7 

8This program is part of "Dive Into Python", a free Python tutorial for 

9experienced programmers. Visit http://diveintopython.org/ for the 

10latest version. 

11 

12This program is free software; you can redistribute it and/or modify 

13it under the terms of the Python 2.1.1 license, available at 

14http://www.python.org/2.1.1/license.html 

15""" 

16 

17import re 

18 

19 

20# Define exceptions 

21class RomanError(Exception): pass 

22class OutOfRangeError(RomanError): pass 

23class NotIntegerError(RomanError): pass 

24class InvalidRomanNumeralError(RomanError): pass 

25 

26 

27# Define digit mapping 

28romanNumeralMap = (('M', 1000), 

29 ('CM', 900), 

30 ('D', 500), 

31 ('CD', 400), 

32 ('C', 100), 

33 ('XC', 90), 

34 ('L', 50), 

35 ('XL', 40), 

36 ('X', 10), 

37 ('IX', 9), 

38 ('V', 5), 

39 ('IV', 4), 

40 ('I', 1)) 

41 

42 

43def toRoman(n): 

44 """convert integer to Roman numeral""" 

45 if not (0 < n < 5000): 

46 raise OutOfRangeError("number out of range (must be 1..4999)") 

47 if int(n) != n: 

48 raise NotIntegerError("decimals can not be converted") 

49 

50 result = "" 

51 for numeral, integer in romanNumeralMap: 

52 while n >= integer: 

53 result += numeral 

54 n -= integer 

55 return result 

56 

57 

58# Define pattern to detect valid Roman numerals 

59romanNumeralPattern = re.compile(""" 

60 ^ # beginning of string 

61 M{0,4} # thousands - 0 to 4 M's 

62 (CM|CD|D?C{0,3}) # hundreds - 900 (CM), 400 (CD), 0-300 (0 to 3 C's), 

63 # or 500-800 (D, followed by 0 to 3 C's) 

64 (XC|XL|L?X{0,3}) # tens - 90 (XC), 40 (XL), 0-30 (0 to 3 X's), 

65 # or 50-80 (L, followed by 0 to 3 X's) 

66 (IX|IV|V?I{0,3}) # ones - 9 (IX), 4 (IV), 0-3 (0 to 3 I's), 

67 # or 5-8 (V, followed by 0 to 3 I's) 

68 $ # end of string 

69 """, re.VERBOSE) 

70 

71 

72def fromRoman(s): 

73 """convert Roman numeral to integer""" 

74 if not s: 

75 raise InvalidRomanNumeralError('Input can not be blank') 

76 

77 if not romanNumeralPattern.search(s): 

78 raise InvalidRomanNumeralError('Invalid Roman numeral: %s' % s) 

79 

80 result = 0 

81 index = 0 

82 for numeral, integer in romanNumeralMap: 

83 while s[index:index+len(numeral)] == numeral: 

84 result += integer 

85 index += len(numeral) 

86 return result