2003-07-17 01:37:11 +00:00
|
|
|
'''
|
|
|
|
|
Module: num2word_base.py
|
|
|
|
|
Version: 1.0
|
|
|
|
|
|
|
|
|
|
Author:
|
2005-06-18 16:20:11 +00:00
|
|
|
Taro Ogawa (tso@users.sourceforge.org)
|
2003-07-17 01:37:11 +00:00
|
|
|
|
|
|
|
|
Copyright:
|
|
|
|
|
Copyright (c) 2003, Taro Ogawa. All Rights Reserved.
|
|
|
|
|
|
|
|
|
|
Licence:
|
|
|
|
|
This module is distributed under the Lesser General Public Licence.
|
|
|
|
|
http://www.opensource.org/licenses/lgpl-license.php
|
|
|
|
|
|
2005-06-18 16:20:11 +00:00
|
|
|
History:
|
|
|
|
|
1.1: add to_splitnum() and inflect()
|
|
|
|
|
add to_year() and to_currency() stubs
|
2003-07-17 01:37:11 +00:00
|
|
|
'''
|
|
|
|
|
|
2013-05-27 14:49:40 -04:00
|
|
|
from __future__ import unicode_literals
|
2005-06-18 16:20:11 +00:00
|
|
|
from orderedmapping import OrderedMapping
|
2003-07-17 01:37:11 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
class Num2Word_Base(object):
|
|
|
|
|
def __init__(self):
|
|
|
|
|
self.cards = OrderedMapping()
|
|
|
|
|
self.is_title = False
|
|
|
|
|
self.precision = 2
|
|
|
|
|
self.exclude_title = []
|
|
|
|
|
self.negword = "(-) "
|
|
|
|
|
self.pointword = "(.)"
|
|
|
|
|
self.errmsg_nonnum = "type(%s) not in [long, int, float]"
|
|
|
|
|
self.errmsg_floatord = "Cannot treat float %s as ordinal."
|
|
|
|
|
self.errmsg_negord = "Cannot treat negative num %s as ordinal."
|
|
|
|
|
self.errmsg_toobig = "abs(%s) must be less than %s."
|
|
|
|
|
|
|
|
|
|
self.base_setup()
|
|
|
|
|
self.setup()
|
|
|
|
|
self.set_numwords()
|
|
|
|
|
|
|
|
|
|
self.MAXVAL = 1000 * self.cards.order[0]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def set_numwords(self):
|
|
|
|
|
self.set_high_numwords(self.high_numwords)
|
|
|
|
|
self.set_mid_numwords(self.mid_numwords)
|
|
|
|
|
self.set_low_numwords(self.low_numwords)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def gen_high_numwords(self, units, tens, lows):
|
|
|
|
|
out = [u + t for t in tens for u in units]
|
|
|
|
|
out.reverse()
|
|
|
|
|
return out + lows
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def set_mid_numwords(self, mid):
|
|
|
|
|
for key, val in mid:
|
|
|
|
|
self.cards[key] = val
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def set_low_numwords(self, numwords):
|
|
|
|
|
for word, n in zip(numwords, range(len(numwords) - 1, -1, -1)):
|
|
|
|
|
self.cards[n] = word
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def splitnum(self, value):
|
|
|
|
|
for elem in self.cards:
|
|
|
|
|
if elem > value:
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
out = []
|
|
|
|
|
if value == 0:
|
|
|
|
|
div, mod = 1, 0
|
|
|
|
|
else:
|
|
|
|
|
div, mod = divmod(value, elem)
|
|
|
|
|
|
|
|
|
|
if div == 1:
|
|
|
|
|
out.append((self.cards[1], 1))
|
|
|
|
|
else:
|
|
|
|
|
if div == value: # The system tallies, eg Roman Numerals
|
|
|
|
|
return [(div * self.cards[elem], div*elem)]
|
|
|
|
|
out.append(self.splitnum(div))
|
|
|
|
|
|
|
|
|
|
out.append((self.cards[elem], elem))
|
|
|
|
|
|
|
|
|
|
if mod:
|
|
|
|
|
out.append(self.splitnum(mod))
|
|
|
|
|
|
|
|
|
|
return out
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def to_cardinal(self, value):
|
|
|
|
|
try:
|
|
|
|
|
assert long(value) == value
|
|
|
|
|
except (ValueError, TypeError, AssertionError):
|
|
|
|
|
return self.to_cardinal_float(value)
|
|
|
|
|
|
|
|
|
|
self.verify_num(value)
|
|
|
|
|
|
|
|
|
|
out = ""
|
|
|
|
|
if value < 0:
|
|
|
|
|
value = abs(value)
|
|
|
|
|
out = self.negword
|
|
|
|
|
|
|
|
|
|
if value >= self.MAXVAL:
|
|
|
|
|
raise OverflowError(self.errmsg_toobig % (value, self.MAXVAL))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
val = self.splitnum(value)
|
|
|
|
|
words, num = self.clean(val)
|
|
|
|
|
return self.title(out + words)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def to_cardinal_float(self, value):
|
|
|
|
|
try:
|
|
|
|
|
float(value) == value
|
|
|
|
|
except (ValueError, TypeError, AssertionError):
|
|
|
|
|
raise TypeError(self.errmsg_nonnum % value)
|
|
|
|
|
|
|
|
|
|
pre = int(value)
|
|
|
|
|
post = abs(value - pre)
|
|
|
|
|
|
|
|
|
|
out = [self.to_cardinal(pre)]
|
|
|
|
|
if self.precision:
|
|
|
|
|
out.append(self.title(self.pointword))
|
|
|
|
|
|
|
|
|
|
for i in range(self.precision):
|
|
|
|
|
post *= 10
|
|
|
|
|
curr = int(post)
|
|
|
|
|
out.append(str(self.to_cardinal(curr)))
|
|
|
|
|
post -= curr
|
|
|
|
|
|
|
|
|
|
return " ".join(out)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def merge(self, curr, next):
|
|
|
|
|
raise NotImplementedError
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def clean(self, val):
|
|
|
|
|
out = val
|
2005-06-18 16:20:11 +00:00
|
|
|
while len(val) != 1:
|
2003-07-17 01:37:11 +00:00
|
|
|
out = []
|
2005-06-18 16:20:11 +00:00
|
|
|
left, right = val[:2]
|
|
|
|
|
if isinstance(left, tuple) and isinstance(right, tuple):
|
|
|
|
|
out.append(self.merge(left, right))
|
2003-07-17 01:37:11 +00:00
|
|
|
if val[2:]:
|
|
|
|
|
out.append(val[2:])
|
|
|
|
|
else:
|
|
|
|
|
for elem in val:
|
|
|
|
|
if isinstance(elem, list):
|
|
|
|
|
if len(elem) == 1:
|
|
|
|
|
out.append(elem[0])
|
|
|
|
|
else:
|
|
|
|
|
out.append(self.clean(elem))
|
|
|
|
|
else:
|
|
|
|
|
out.append(elem)
|
|
|
|
|
val = out
|
|
|
|
|
return out[0]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def title(self, value):
|
|
|
|
|
if self.is_title:
|
|
|
|
|
out = []
|
|
|
|
|
value = value.split()
|
|
|
|
|
for word in value:
|
|
|
|
|
if word in self.exclude_title:
|
|
|
|
|
out.append(word)
|
|
|
|
|
else:
|
|
|
|
|
out.append(word[0].upper() + word[1:])
|
|
|
|
|
value = " ".join(out)
|
|
|
|
|
return value
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def verify_ordinal(self, value):
|
|
|
|
|
if not value == long(value):
|
|
|
|
|
raise TypeError, self.errmsg_floatord %(value)
|
|
|
|
|
if not abs(value) == value:
|
|
|
|
|
raise TypeError, self.errmsg_negord %(value)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def verify_num(self, value):
|
|
|
|
|
return 1
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def set_wordnums(self):
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def to_ordinal(value):
|
|
|
|
|
return self.to_cardinal(value)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def to_ordinal_num(self, value):
|
|
|
|
|
return value
|
|
|
|
|
|
|
|
|
|
|
2005-06-18 16:20:11 +00:00
|
|
|
# Trivial version
|
|
|
|
|
def inflect(self, value, text):
|
|
|
|
|
text = text.split("/")
|
|
|
|
|
if value == 1:
|
|
|
|
|
return text[0]
|
|
|
|
|
return "".join(text)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#//CHECK: generalise? Any others like pounds/shillings/pence?
|
|
|
|
|
def to_splitnum(self, val, hightxt="", lowtxt="", jointxt="",
|
2011-07-07 17:42:29 +03:00
|
|
|
divisor=100, longval=True, cents = True):
|
2005-06-18 16:20:11 +00:00
|
|
|
out = []
|
|
|
|
|
try:
|
|
|
|
|
high, low = val
|
|
|
|
|
except TypeError:
|
|
|
|
|
high, low = divmod(val, divisor)
|
|
|
|
|
if high:
|
|
|
|
|
hightxt = self.title(self.inflect(high, hightxt))
|
|
|
|
|
out.append(self.to_cardinal(high))
|
|
|
|
|
if low:
|
|
|
|
|
if longval:
|
|
|
|
|
if hightxt:
|
|
|
|
|
out.append(hightxt)
|
|
|
|
|
if jointxt:
|
|
|
|
|
out.append(self.title(jointxt))
|
|
|
|
|
elif hightxt:
|
|
|
|
|
out.append(hightxt)
|
|
|
|
|
if low:
|
2011-07-07 17:42:29 +03:00
|
|
|
if cents:
|
|
|
|
|
out.append(self.to_cardinal(low))
|
|
|
|
|
else:
|
|
|
|
|
out.append("%02d" % low)
|
2005-06-18 16:20:11 +00:00
|
|
|
if lowtxt and longval:
|
|
|
|
|
out.append(self.title(self.inflect(low, lowtxt)))
|
|
|
|
|
return " ".join(out)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def to_year(self, value, **kwargs):
|
|
|
|
|
return self.to_cardinal(value)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def to_currency(self, value, **kwargs):
|
|
|
|
|
return self.to_cardinal(value)
|
|
|
|
|
|
|
|
|
|
|
2003-07-17 01:37:11 +00:00
|
|
|
def base_setup(self):
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def setup(self):
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test(self, value):
|
|
|
|
|
try:
|
|
|
|
|
_card = self.to_cardinal(value)
|
|
|
|
|
except:
|
|
|
|
|
_card = "invalid"
|
2005-06-19 02:24:11 +00:00
|
|
|
|
2003-07-17 01:37:11 +00:00
|
|
|
try:
|
|
|
|
|
_ord = self.to_ordinal(value)
|
|
|
|
|
except:
|
|
|
|
|
_ord = "invalid"
|
2005-06-19 02:24:11 +00:00
|
|
|
|
2003-07-17 01:37:11 +00:00
|
|
|
try:
|
|
|
|
|
_ordnum = self.to_ordinal_num(value)
|
|
|
|
|
except:
|
|
|
|
|
_ordnum = "invalid"
|
|
|
|
|
|
|
|
|
|
print ("For %s, card is %s;\n\tord is %s; and\n\tordnum is %s." %
|
|
|
|
|
(value, _card, _ord, _ordnum))
|