Initial revision

This commit is contained in:
tso
2003-07-17 01:37:11 +00:00
commit f1e887bcd2
8 changed files with 854 additions and 0 deletions

55
num2word.py Normal file
View File

@@ -0,0 +1,55 @@
'''
Module: num2word.py
Requires: num2word_*.py
Version: 0.1
Author:
Taro Ogawa (tso@users.sourceforge.org)
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
Usage:
from num2word import to_card, to_ord, to_ordnum
to_card(1234567890)
to_ord(1234567890)
to_ordnum(12)
Notes:
The module is a wrapper for language-specific modules. It imports the
appropriate modules as defined by locale settings. If unable to
load an appropriate module, an ImportError is raised.
'''
import locale as _locale
# Correct omissions in locale:
# Bugrep these...
_locdict = { "English_Australia" : "en_AU", }
_modules = []
for _loc in [_locale.getlocale(), _locale.getdefaultlocale()]:
_lang = _loc[0]
if _lang:
_lang = _locdict.get(_lang, _lang)
_lang = _lang.upper()
_modules.append("num2word_" + _lang)
_modules.append("num2word_" + _lang.split("_")[0])
for _module in _modules:
try:
n2w = __import__(_module)
break
except ImportError:
pass
try:
to_card, to_ord, to_ordnum = n2w.to_card, n2w.to_ord, n2w.to_ordnum
except NameError:
raise ImportError("Could not import any of these modules: %s"
% (", ".join(_modules)))

134
num2word_DE.py Normal file
View File

@@ -0,0 +1,134 @@
'''
Module: num2word_DE.py
Requires: num2word_base.py
Version: 0.4
Author:
Taro Ogawa (BLAHhydroxideBLAH_removetheBLAHs@inorbit.com)
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
Data from:
- http://german4u2know.tripod.com/nouns/10.html
- http://www.uni-bonn.de/~manfear/large.php
Usage:
from num2word_DE import to_card, to_ord, to_ordnum
to_card(1234567890)
to_ord(1234567890)
to_ordnum(12)
'''
from num2word_base import Num2Word_Base
#//TODO: Use orthographics
#//TODO: Use German error messages
class Num2Word_DE(Num2Word_Base):
def set_high_numwords(self, high):
max = 3 + 6*len(high)
for word, n in zip(high, range(max, 3, -6)):
self.cards[10**n] = word + "illiarde"
self.cards[10**(n-3)] = word + "illion"
def setup(self):
self.negword = "minus "
self.pointword = "Komma"
self.errmsg_nonnum = "Only numbers may be converted to words."
self.errmsg_toobig = "Number is too large to convert to words."
self.exclude_title = []
lows = ["non", "okt", "sept", "sext", "quint", "quadr", "tr", "b", "m"]
units = ["", "un", "duo", "tre", "quattuor", "quin", "sex", "sept",
"okto", "novem"]
tens = ["dez", "vigint", "trigint", "quadragint", "quinquagint",
"sexagint", "septuagint", "oktogint", "nonagint"]
self.high_numwords = ["zent"]+self.gen_high_numwords(units, tens, lows)
self.mid_numwords = [(1000, "tausand"), (100, "hundert"),
(90, "neunzig"), (80, "achtzig"), (70, "siebzig"),
(60, "sechzig"), (50, "fuenfzig"), (40, "vierzig"),
(30, "dreissig")]
self.low_numwords = ["zwanzig", "neunzehn", "achtzen", "siebzehn",
"sechzehn", "fuenfzehn", "vierzehn", "dreizehn",
"zwoelf", "elf", "zehn", "neun", "acht", "sieben",
"sechs", "fuenf", "vier", "drei", "zwei", "eins",
"null"]
self.ords = { "eins" : "ers",
"drei" : "drit",
"acht" : "ach",
"sieben" : "sieb",
"ig" : "igs" }
self.ordflag = False
def merge(self, curr, next):
ctext, cnum, ntext, nnum = curr + next
if cnum == 1:
if nnum < 10**6 or self.ordflag:
return next
ctext = "eine"
if nnum > cnum:
if nnum >= 10**6:
if cnum > 1:
if ntext.endswith("e") or self.ordflag:
ntext += "s"
else:
ntext += "es"
ctext += " "
val = cnum * nnum
else:
if nnum < 10 < cnum < 100:
if nnum == 1:
ntext = "ein"
ntext, ctext = ctext, ntext + "und"
elif cnum >= 10**6:
ctext += " "
val = cnum + nnum
word = ctext + ntext
return (word, val)
def to_ordinal(self, value):
self.verify_ordinal(value)
self.ordflag = True
outword = self.to_cardinal(value)
self.ordflag = False
for key in self.ords:
if outword.endswith(key):
outword = outword[:len(outword) - len(key)] + self.ords[key]
break
return outword + "te"
# Is this correct??
def to_ordinal_num(self, value):
self.verify_ordinal(value)
return str(value) + "te"
n2w = Num2Word_DE()
to_card = n2w.to_cardinal
to_ord = n2w.to_ordinal
to_ordnum = n2w.to_ordinal_num
def main():
for val in [ 1, 11, 12, 21, 31, 33, 71, 80, 81, 91, 99, 100, 101, 102, 155,
180, 300, 308, 832, 1000, 1001, 1061, 1100, 1500, 1701, 3000,
8280, 8291, 150000, 500000, 1000000, 2000000, 2000001,
-21212121211221211111, -2.121212, -1.0000100]:
n2w.test(val)
n2w.test(1325325436067876801768700107601001012212132143210473207540327057320957032975032975093275093275093270957329057320975093272950730)
if __name__ == "__main__":
main()

113
num2word_EN.py Normal file
View File

@@ -0,0 +1,113 @@
'''
Module: num2word_EN.py
Requires: num2word_EU.py
Version: 1.0
Author:
Taro Ogawa (tso@users.sourceforge.org)
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
Data from:
http://www.uni-bonn.de/~manfear/large.php
Usage:
from num2word_EN import n2w, to_card, to_ord, to_ordnum
to_card(1234567890)
n2w.is_title = True
to_card(1234567890)
to_ord(1234567890)
to_ordnum(1234567890)
'''
import num2word_EU
class Num2Word_EN(num2word_EU.Num2Word_EU):
def set_high_numwords(self, high):
max = 3 + 3*len(high)
for word, n in zip(high, range(max, 3, -3)):
self.cards[10**n] = word + "illion"
def setup(self):
self.negword = "minus "
self.pointword = "point"
self.errmsg_nonnum = "Only numbers may be converted to words."
self.exclude_title = ["and", "point", "minus"]
self.mid_numwords = [(1000, "thousand"), (100, "hundred"),
(90, "ninety"), (80, "eighty"), (70, "seventy"),
(60, "sixty"), (50, "fifty"), (40, "forty"),
(30, "thirty")]
self.low_numwords = ["twenty", "nineteen", "eighteen", "seventeen",
"sixteen", "fifteen", "fourteen", "thirteen",
"twelve", "eleven", "ten", "nine", "eight",
"seven", "six", "five", "four", "three", "two",
"one", "zero"]
self.ords = { "one" : "first",
"two" : "second",
"three" : "third",
"five" : "fifth",
"eight" : "eighth",
"nine" : "ninth",
"twelve" : "twelfth" }
def merge(self, curr, next):
ctext, cnum, ntext, nnum = curr + next
if cnum == 1 and nnum < 100:
return next
elif 100 > cnum > nnum :
return (ctext + "-" + ntext, cnum + nnum)
elif cnum >= 100 > nnum:
return (ctext + " and " + ntext, cnum + nnum)
elif nnum > cnum:
return (ctext + " " + ntext, cnum * nnum)
return (ctext + ", " + ntext, cnum + nnum)
def to_ordinal(self, value):
self.verify_ordinal(value)
outwords = self.to_cardinal(value).split(" ")
lastwords = outwords[-1].split("-")
lastword = lastwords[-1].lower()
try:
lastword = self.ords[lastword]
except KeyError:
if lastword[-1] == "y":
lastword = lastword[:-1] + "ie"
lastword += "th"
lastwords[-1] = self.title(lastword)
outwords[-1] = "-".join(lastwords)
return " ".join(outwords)
def to_ordinal_num(self, value):
self.verify_ordinal(value)
out = str(value)
out += {"1" : "st",
"2" : "nd",
"3" : "rd" }.get(out[-1], "th")
return out
n2w = Num2Word_EN()
to_card = n2w.to_cardinal
to_ord = n2w.to_ordinal
to_ordnum = n2w.to_ordinal_num
def main():
for val in [ 1, 11, 12, 21, 31, 33, 71, 80, 81, 91, 99, 100, 101, 102, 155,
180, 300, 308, 832, 1000, 1001, 1061, 1100, 1500, 1701, 3000,
8280, 8291, 150000, 500000, 1000000, 2000000, 2000001,
-21212121211221211111, -2.121212, -1.0000100]:
n2w.test(val)
n2w.test(1325325436067876801768700107601001012212132143210473207540327057320957032975032975093275093275093270957329057320975093272950730)
if __name__ == "__main__":
main()

46
num2word_EN_old.py Normal file
View File

@@ -0,0 +1,46 @@
'''
Module: num2word_EN_old.py
Requires: num2word_EN.py
Version: 0.3
Author:
Taro Ogawa (tso@users.sourceforge.org)
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
Usage:
from num2word_EN_old import to_card, to_ord, to_ordnum
to_card(1234567890)
to_ord(1234567890)
to_ordnum(12)
'''
import num2word_EN
class Num2Word_EN_old(num2word_EN.Num2Word_EN):
def base_setup(self):
sclass = super(num2word_EN.Num2Word_EN, self)
self.set_high_numwords = sclass.set_high_numwords
sclass.base_setup()
n2w = Num2Word_EN_old()
to_card = n2w.to_cardinal
to_ord = n2w.to_ordinal
to_ordnum = n2w.to_ordinal_num
def main():
for val in [ 1, 11, 12, 21, 31, 33, 71, 80, 81, 91, 99, 100, 101, 102, 155,
180, 300, 308, 832, 1000, 1001, 1061, 1100, 1500, 1701, 3000,
8280, 8291, 150000, 500000, 1000000, 2000000, 2000001,
-21212121211221211111, -2.121212, -1.0000100]:
n2w.test(val)
n2w.test(1325325436067876801768700107601001012212132143210473207540327057320957032975032975093275093275093270957329057320975093272950730)
if __name__ == "__main__":
main()

132
num2word_ES.py Normal file
View File

@@ -0,0 +1,132 @@
'''
Module: num2word_ES.py
Requires: num2word_EU.py
Version: 0.2
Author:
Taro Ogawa (BLAHhydroxideBLAH@inorbit.removeBLAHtwice.com)
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
Data from:
http://www.smartphrase.com/Spanish/sp_numbers_voc.shtml
Usage:
from num2word_ES import to_card, to_ord, to_ordnum
to_card(1234567890)
# to_ord(1234567890)
# to_ordnum(12)
'''
from num2word_EU import Num2Word_EU
#//TODO: correct orthographics
#//TODO: error messages
class Num2Word_ES(Num2Word_EU):
#//CHECK: Is this sufficient??
def set_high_numwords(self, high):
max = 3 + 6*len(high)
for word, n in zip(high, range(max, 3, -6)):
self.cards[10**(n-3)] = word + "illo'n"
def setup(self):
lows = ["cuatr", "tr", "b", "m"]
self.high_numwords = self.gen_high_numwords([], [], lows)
self.negword = "menos "
self.pointword = "punto"
self.errmsg_nonnum = "Only numbers may be converted to words."
self.errmsg_toobig = "Number is too large to convert to words."
self.gender_stem = "o"
self.exclude_title = ["y", "menos", "punto"]
self.mid_numwords = [(1000, "mil"), (100, "cien"), (90, "noventa"),
(80, "ochenta"), (70, "setenta"), (60, "sesenta"),
(50,"cincuenta"), (40,"cuarenta")]
self.low_numwords = ["vientinueve", "vientiocho", "vientisiete",
"vientise'is", "vienticinco", "vienticuatro",
"vientitre's", "vientido's", "vientiuno",
"viente", "diecinueve", "dieciocho", "diecisiete",
"dieciseis", "quince", "catorce", "trece", "doce",
"once", "diez", "nueve", "ocho", "siete", "seis",
"cinco", "cuatro", "tres", "dos", "uno", "cero"]
self.ords = { 1 : "primer",
2 : "segund",
3 : "tercer",
4 : "cuart",
5 : "quint",
6 : "sext",
7 : "se'ptim",
8 : "octav",
9 : "noven",
10 : "de'cim" }
def merge(self, curr, next):
ctext, cnum, ntext, nnum = curr + next
if cnum == 1:
if nnum < 1000000:
return next
ctext = "un"
elif cnum == 100:
ctext += "t" + self.gender_stem
if nnum < cnum:
if cnum < 100:
return (ctext + " y " + ntext, cnum + nnum)
return (ctext + " " + ntext, cnum + nnum)
elif (not nnum % 1000000) and cnum > 1:
ntext = ntext[:-3] + "ones"
if nnum == 100:
if cnum == 5:
ctext = "quinien"
ntext = ""
elif cnum == 7:
ctext = "sete"
elif cnum == 9:
ctext = "nove"
ntext += "t" + self.gender_stem + "s"
else:
ntext = " " + ntext
return (ctext + ntext, cnum * nnum)
def to_ordinal(self, value):
self.verify_ordinal(value)
try:
return self.ords[value] + self.gender_stem
except KeyError:
return self.to_cardinal(value)
def to_ordinal_num(self, value):
self.verify_ordinal(value)
# Correct for fem?
return str(value) + "^o"
n2w = Num2Word_ES()
to_card = n2w.to_cardinal
to_ord = n2w.to_ordinal
to_ordnum = n2w.to_ordinal_num
def main():
for val in [ 1, 11, 12, 21, 31, 33, 71, 80, 81, 91, 99, 100, 101, 102, 155,
180, 300, 308, 832, 1000, 1001, 1061, 1100, 1500, 1701, 3000,
8280, 8291, 150000, 500000, 1000000, 2000000, 2000001,
-21212121211221211111, -2.121212, -1.0000100]:
n2w.test(val)
n2w.test(1325325436067876801768700107601001012212132143210473207540327057320957032975032975093275093275093270957329057320975093272950730)
if __name__ == "__main__":
main()

36
num2word_EU.py Normal file
View File

@@ -0,0 +1,36 @@
'''
Module: num2word_EU.py
Requires: num2word_base.py
Version: 1.0
Author:
Taro Ogawa (BLAHhydroxideBLAH@inorbit.removeBLAHtwice.com)
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
Data from:
http://www.uni-bonn.de/~manfear/large.php
'''
from num2word_base import Num2Word_Base
class Num2Word_EU(Num2Word_Base):
def set_high_numwords(self, high):
max = 3 + 6*len(high)
for word, n in zip(high, range(max, 3, -6)):
self.cards[10**n] = word + "illiard"
self.cards[10**(n-3)] = word + "illion"
def base_setup(self):
lows = ["non","oct","sept","sext","quint","quadr","tr","b","m"]
units = ["", "un", "duo", "tre", "quattuor", "quin", "sex", "sept",
"octo", "novem"]
tens = ["dec", "vigint", "trigint", "quadragint", "quinquagint",
"sexagint", "septuagint", "octogint", "nonagint"]
self.high_numwords = ["cent"]+self.gen_high_numwords(units, tens, lows)

103
num2word_FR.py Normal file
View File

@@ -0,0 +1,103 @@
'''
Module: num2word_FR.py
Requires: num2word_EU.py
Version: 0.4
Author:
Taro Ogawa (tso@users.sourceforge.org)
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
Data from:
http://www.ouc.bc.ca/mola/fr/handouts/numbers.doc.
http://www.realfrench.net/units/Interunit_63.html
Usage:
from num2word_FR import to_card, to_ord, to_ordnum
to_card(1234567890)
# to_ord(1234567890)
# to_ordnum(12)
'''
from num2word_EU import Num2Word_EU
#//TODO: correct orthographics
#//TODO: error messages in French
#//TODO: ords
class Num2Word_FR(Num2Word_EU):
def setup(self):
self.negword = "moins "
self.pointword = "virgule"
self.errmsg_nonnum = "Only numbers may be converted to words."
self.errmsg_toobig = "Number is too large to convert to words."
self.exclude_title = ["et", "virgule", "moins"]
self.mid_numwords = [(1000, "mille"), (100, "cent"),
(80, "quatre-vingts"), (60, "soixante"),
(50, "cinquante"), (40, "quarante"),
(30, "trente")]
self.low_numwords = ["vingt", "dix-neuf", "dix-huit", "dix-sept",
"seize", "quinze", "quatorze", "treize", "douze",
"onze", "dix", "neuf", "huit", "sept", "six",
"cinq", "quatre", "trois", "deux", "un", "ze'ro"]
def merge(self, curr, next):
ctext, cnum, ntext, nnum = curr + next
if cnum == 1:
if nnum < 1000000:
return next
else:
if (not (cnum - 80)%100 or not cnum%100) and ctext[-1] == "s":
ctext = ctext[:-1]
if (cnum<1000 and nnum <> 1000 and ntext[-1] <> "s"
and not nnum%100):
ntext += "s"
if nnum < cnum < 100:
if nnum % 10 == 1 and cnum <> 80:
return (ctext + " et " + ntext, cnum + nnum)
return (ctext + "-" + ntext, cnum + nnum)
elif nnum > cnum:
return (ctext + " " + ntext, cnum * nnum)
return (ctext + " " + ntext, cnum + nnum)
def to_ordinal(self,value):
self.verify_ordinal(value)
if value == 1:
return "premier"
word = self.to_cardinal(value)
if word[-1] == "e":
word = word[:-1]
return word + "ie'me"
def to_ordinal_num(self, value):
self.verify_ordinal(value)
out = str(value)
out += {"1" : "er" }.get(out[-1], "me")
return out
n2w = Num2Word_FR()
to_card = n2w.to_cardinal
to_ord = n2w.to_ordinal
to_ordnum = n2w.to_ordinal_num
def main():
for val in [ 1, 11, 12, 21, 31, 33, 71, 80, 81, 91, 99, 100, 101, 102, 155,
180, 300, 308, 832, 1000, 1001, 1061, 1100, 1500, 1701, 3000,
8280, 8291, 150000, 500000, 1000000, 2000000, 2000001,
-21212121211221211111, -2.121212, -1.0000100]:
n2w.test(val)
n2w.test(1325325436067876801768700107601001012212132143210473207540327057320957032975032975093275093275093270957329057320975093272950730)
if __name__ == "__main__":
main()

235
num2word_base.py Normal file
View File

@@ -0,0 +1,235 @@
'''
Module: num2word_base.py
Version: 1.0
Author:
Taro Ogawa (BLAHhydroxideBLAH_removetheBLAHs@inorbit.com)
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
Data from:
http://www.uni-bonn.de/~manfear/large.php
'''
from __future__ import generators
class OrderedMapping(dict):
def __init__(self, *pairs):
self.order = []
for key, val in pairs:
self[key] = val
def __setitem__(self, key, val):
if key not in self:
self.order.append(key)
super(OrderedMapping, self).__setitem__(key, val)
def __iter__(self):
for item in self.order:
yield item
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
while len(val) <> 1:
out = []
curr, next = val[:2]
if isinstance(curr, tuple) and isinstance(next, tuple):
out.append(self.merge(curr, next))
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
def base_setup(self):
pass
def setup(self):
pass
def test(self, value):
try:
_card = self.to_cardinal(value)
except:
_card = "invalid"
try:
_ord = self.to_ordinal(value)
except:
_ord = "invalid"
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))