[epub, mobi] Paragraph indentation improvements plus support for initials in chapters
[librarian.git] / librarian / util.py
1 # Functions to convert between integers and Roman numerals. Doctest examples included.
2 # by Paul Winkler 
3 # http://code.activestate.com/recipes/81611-roman-numerals/
4 # PSFL (GPL compatible)
5
6 def int_to_roman(input):
7     """
8     Convert an integer to Roman numerals.
9
10     Examples:
11     >>> int_to_roman(0)
12     Traceback (most recent call last):
13     ValueError: Argument must be between 1 and 3999
14
15     >>> int_to_roman(-1)
16     Traceback (most recent call last):
17     ValueError: Argument must be between 1 and 3999
18
19     >>> int_to_roman(1.5)
20     Traceback (most recent call last):
21     TypeError: expected integer, got <type 'float'>
22
23     >>> for i in range(1, 21): print int_to_roman(i)
24     ...
25     I
26     II
27     III
28     IV
29     V
30     VI
31     VII
32     VIII
33     IX
34     X
35     XI
36     XII
37     XIII
38     XIV
39     XV
40     XVI
41     XVII
42     XVIII
43     XIX
44     XX
45     >>> print int_to_roman(2000)
46     MM
47     >>> print int_to_roman(1999)
48     MCMXCIX
49     """
50     if type(input) != type(1):
51         raise TypeError, "expected integer, got %s" % type(input)
52     if not 0 < input < 4000:
53         raise ValueError, "Argument must be between 1 and 3999"    
54     ints = (1000, 900,  500, 400, 100,  90, 50,  40, 10,  9,    5,  4,    1)
55     nums = ('M',  'CM', 'D', 'CD','C', 'XC','L','XL','X','IX','V','IV','I')
56     result = ""
57     for i in range(len(ints)):
58         count = int(input / ints[i])
59         result += nums[i] * count
60         input -= ints[i] * count
61     return result
62
63 def roman_to_int(input):
64     """
65     Convert a roman numeral to an integer.
66     
67     >>> r = range(1, 4000)
68     >>> nums = [int_to_roman(i) for i in r]
69     >>> ints = [roman_to_int(n) for n in nums]
70     >>> print r == ints
71     1
72
73     >>> roman_to_int('VVVIV')
74     Traceback (most recent call last):
75      ...
76     ValueError: input is not a valid roman numeral: VVVIV
77     >>> roman_to_int(1)
78     Traceback (most recent call last):
79      ...
80     TypeError: expected string, got <type 'int'>
81     >>> roman_to_int('a')
82     Traceback (most recent call last):
83      ...
84     ValueError: input is not a valid roman numeral: A
85     >>> roman_to_int('IL')
86     Traceback (most recent call last):
87      ...
88     ValueError: input is not a valid roman numeral: IL
89     """
90     if type(input) != type(""):
91         raise TypeError, "expected string, got %s" % type(input)
92     input = input.upper()
93     nums = ['M', 'D', 'C', 'L', 'X', 'V', 'I']
94     ints = [1000, 500, 100, 50,  10,  5,    1]
95     places = []
96     for c in input:
97         if not c in nums:
98             raise ValueError, "input is not a valid roman numeral: %s" % input
99     for i in range(len(input)):
100         c = input[i]
101         value = ints[nums.index(c)]
102         # If the next place holds a larger number, this value is negative.
103         try:
104             nextvalue = ints[nums.index(input[i +1])]
105             if nextvalue > value:
106                 value *= -1
107         except IndexError:
108             # there is no next place.
109             pass
110         places.append(value)
111     sum = 0
112     for n in places: sum += n
113     # Easiest test for validity...
114     if int_to_roman(sum) == input:
115         return sum
116     else:
117         raise ValueError, 'input is not a valid roman numeral: %s' % input