diff --git a/num2word.py b/num2word.py index 597708e..3d92c24 100644 --- a/num2word.py +++ b/num2word.py @@ -1,7 +1,7 @@ ''' Module: num2word.py Requires: num2word_*.py -Version: 0.1 +Version: 0.2 Author: Taro Ogawa (tso@users.sourceforge.org) @@ -23,6 +23,9 @@ 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. + +History: + 0.2: n2w, to_card, to_ord, to_ordnum now imported correctly ''' import locale as _locale @@ -43,13 +46,15 @@ for _loc in [_locale.getlocale(), _locale.getdefaultlocale()]: for _module in _modules: try: - n2w = __import__(_module) + n2wmod = __import__(_module) break except ImportError: pass try: - to_card, to_ord, to_ordnum = n2w.to_card, n2w.to_ord, n2w.to_ordnum + n2w, to_card, to_ord, to_ordnum, to_year = (n2wmod.n2w, n2wmod.to_card, + n2wmod.to_ord, n2wmod.to_ordnum, + n2wmod.to_year) except NameError: raise ImportError("Could not import any of these modules: %s" % (", ".join(_modules))) diff --git a/num2word_DE.py b/num2word_DE.py index 4ea77c0..59abd9f 100644 --- a/num2word_DE.py +++ b/num2word_DE.py @@ -4,7 +4,7 @@ Requires: num2word_base.py Version: 0.4 Author: - Taro Ogawa (BLAHhydroxideBLAH_removetheBLAHs@inorbit.com) + Taro Ogawa (tso@users.sourceforge.org) Copyright: Copyright (c) 2003, Taro Ogawa. All Rights Reserved. @@ -22,12 +22,16 @@ Usage: to_card(1234567890) to_ord(1234567890) to_ordnum(12) -''' -from num2word_base import Num2Word_Base -#//TODO: Use orthographics +History + 0.4: Use high ascii characters instead of low ascii approximations + add to_currency() and to_year() + +''' +from num2word_EU import Num2Word_EU + #//TODO: Use German error messages -class Num2Word_DE(Num2Word_Base): +class Num2Word_DE(Num2Word_EU): def set_high_numwords(self, high): max = 3 + 6*len(high) @@ -51,12 +55,12 @@ class Num2Word_DE(Num2Word_Base): 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")] + (60, "sechzig"), (50, "f\xFCnfzig"), (40, "vierzig"), + (30, "drei\xDFig")] self.low_numwords = ["zwanzig", "neunzehn", "achtzen", "siebzehn", - "sechzehn", "fuenfzehn", "vierzehn", "dreizehn", - "zwoelf", "elf", "zehn", "neun", "acht", "sieben", - "sechs", "fuenf", "vier", "drei", "zwei", "eins", + "sechzehn", "f\xFCnfzehn", "vierzehn", "dreizehn", + "zw\xF6lf", "elf", "zehn", "neun", "acht", "sieben", + "sechs", "f\xFCnf", "vier", "drei", "zwei", "eins", "null"] self.ords = { "eins" : "ers", "drei" : "drit", @@ -113,6 +117,20 @@ class Num2Word_DE(Num2Word_Base): self.verify_ordinal(value) return str(value) + "te" + + def to_currency(self, val, longval=True, old=False): + if old: + return self.to_splitnum(val, hightxt="mark/s", lowtxt="pfennig/e", + jointxt="und",longval=longval) + return super(Num2Word_DE, self).to_currency(val, jointxt="und", + longval=longval) + + def to_year(self, val, longval=True): + if not (val//100)%10: + return self.to_cardinal(val) + return self.to_splitnum(val, hightxt="hundert", longval=longval) + + n2w = Num2Word_DE() to_card = n2w.to_cardinal @@ -128,6 +146,8 @@ def main(): n2w.test(val) n2w.test(1325325436067876801768700107601001012212132143210473207540327057320957032975032975093275093275093270957329057320975093272950730) + print n2w.to_currency(112121) + print n2w.to_year(2000) if __name__ == "__main__": main() diff --git a/num2word_EN.py b/num2word_EN.py index 0239e29..9470091 100644 --- a/num2word_EN.py +++ b/num2word_EN.py @@ -1,7 +1,7 @@ ''' Module: num2word_EN.py Requires: num2word_EU.py -Version: 1.0 +Version: 1.2 Author: Taro Ogawa (tso@users.sourceforge.org) @@ -23,9 +23,22 @@ Usage: to_card(1234567890) to_ord(1234567890) to_ordnum(1234567890) + to_year(1976) + to_currency(dollars*100 + cents, longval=False) + to_currency((dollars, cents)) + + +History: + 1.2: to_ordinal_num() made shorter and simpler (but slower) + strings in merge() now interpolated + to_year() and to_currency() added + + 1.1: to_ordinal_num() fixed for 11,12,13 ''' +from __future__ import division import num2word_EU + class Num2Word_EN(num2word_EU.Num2Word_EU): def set_high_numwords(self, high): max = 3 + 3*len(high) @@ -35,7 +48,7 @@ class Num2Word_EN(num2word_EU.Num2Word_EU): def setup(self): self.negword = "minus " self.pointword = "point" - self.errmsg_nonnum = "Only numbers may be converted to words." + self.errmsg_nornum = "Only numbers may be converted to words." self.exclude_title = ["and", "point", "minus"] self.mid_numwords = [(1000, "thousand"), (100, "hundred"), @@ -56,18 +69,16 @@ class Num2Word_EN(num2word_EU.Num2Word_EU): "twelve" : "twelfth" } - def merge(self, curr, next): - ctext, cnum, ntext, nnum = curr + next - - if cnum == 1 and nnum < 100: + def merge(self, (ltext, lnum), (rtext, rnum)): + if lnum == 1 and rnum < 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) + elif 100 > lnum > rnum : + return ("%s-%s"%(ltext, rtext), lnum + rnum) + elif lnum >= 100 > rnum: + return ("%s and %s"%(ltext, rtext), lnum + rnum) + elif rnum > lnum: + return ("%s %s"%(ltext, rtext), lnum * rnum) + return ("%s, %s"%(ltext, rtext), lnum + rnum) def to_ordinal(self, value): @@ -88,16 +99,25 @@ class Num2Word_EN(num2word_EU.Num2Word_EU): 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 + return "%s%s"%(value, self.to_ordinal(value)[-2:]) + + + def to_year(self, val, longval=True): + if not (val//100)%10: + return self.to_cardinal(val) + return self.to_splitnum(val, hightxt="hundred", jointxt="and", + longval=longval) + + def to_currency(self, val, longval=True): + return self.to_splitnum(val, hightxt="dollar/s", lowtxt="cent/s", + jointxt="and", longval=longval) + n2w = Num2Word_EN() to_card = n2w.to_cardinal to_ord = n2w.to_ordinal to_ordnum = n2w.to_ordinal_num +to_year = n2w.to_year def main(): for val in [ 1, 11, 12, 21, 31, 33, 71, 80, 81, 91, 99, 100, 101, 102, 155, @@ -105,9 +125,11 @@ def main(): 8280, 8291, 150000, 500000, 1000000, 2000000, 2000001, -21212121211221211111, -2.121212, -1.0000100]: n2w.test(val) - n2w.test(1325325436067876801768700107601001012212132143210473207540327057320957032975032975093275093275093270957329057320975093272950730) - + for val in [1,120,1000,1120,1800, 1976,2000,2010,2099,2171]: + print val, "is", n2w.to_currency(val) + print val, "is", n2w.to_year(val) + if __name__ == "__main__": main() diff --git a/num2word_EN_GB.py b/num2word_EN_GB.py new file mode 100644 index 0000000..4a9b060 --- /dev/null +++ b/num2word_EN_GB.py @@ -0,0 +1,63 @@ +''' +Module: num2word_EN_GB.py +Requires: num2word_EN.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) + to_year(1976) + to_currency(pounds*100 + pence) + to_currency((pounds,pence)) + + +History: + 1.0: Split from num2word_EN with the addition of to_currency() +''' + +from num2word_EN import Num2Word_EN + + +class Num2Word_EN_GB(Num2Word_EN): + def to_currency(self, val, longval=True): + return self.to_splitnum(val, hightxt="pound/s", lowtxt="pence", + jointxt="and", longval=longval) + + +n2w = Num2Word_EN_GB() +to_card = n2w.to_cardinal +to_ord = n2w.to_ordinal +to_ordnum = n2w.to_ordinal_num +to_year = n2w.to_year + +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) + for val in [1,120,1000,1120,1800, 1976,2000,2010,2099,2171]: + print val, "is", n2w.to_currency(val) + print val, "is", n2w.to_year(val) + + +if __name__ == "__main__": + main() diff --git a/num2word_EN_old.py b/num2word_EN_GB_old.py similarity index 70% rename from num2word_EN_old.py rename to num2word_EN_GB_old.py index 94d644c..012c1ee 100644 --- a/num2word_EN_old.py +++ b/num2word_EN_GB_old.py @@ -1,6 +1,6 @@ ''' -Module: num2word_EN_old.py -Requires: num2word_EN.py +Module: num2word_EN_GB_old.py +Requires: num2word_EN_GB_old.py Version: 0.3 Author: @@ -18,17 +18,23 @@ Usage: to_card(1234567890) to_ord(1234567890) to_ordnum(12) -''' -import num2word_EN -class Num2Word_EN_old(num2word_EN.Num2Word_EN): +History: + 0.3: Rename from num2word_EN_old + +Todo: + Currency (pounds/shillings/pence) +''' +from num2word_EN_GB import Num2Word_EN_GB + +class Num2Word_EN_GB_old(Num2Word_EN_GB): def base_setup(self): - sclass = super(num2word_EN.Num2Word_EN, self) + sclass = super(Num2Word_EN_GB, self) self.set_high_numwords = sclass.set_high_numwords sclass.base_setup() - -n2w = Num2Word_EN_old() + +n2w = Num2Word_EN_GB_old() to_card = n2w.to_cardinal to_ord = n2w.to_ordinal to_ordnum = n2w.to_ordinal_num @@ -41,6 +47,9 @@ def main(): n2w.test(val) n2w.test(1325325436067876801768700107601001012212132143210473207540327057320957032975032975093275093275093270957329057320975093272950730) + for val in [1,120,1000,1120,1800, 1976,2000,2010,2099,2171]: + print val, "is", n2w.to_currency(val) + print val, "is", n2w.to_year(val) if __name__ == "__main__": main() diff --git a/num2word_ES.py b/num2word_ES.py index 7eb29ea..ff9c05c 100644 --- a/num2word_ES.py +++ b/num2word_ES.py @@ -1,10 +1,10 @@ ''' Module: num2word_ES.py Requires: num2word_EU.py -Version: 0.2 +Version: 0.3 Author: - Taro Ogawa (BLAHhydroxideBLAH@inorbit.removeBLAHtwice.com) + Taro Ogawa (tso@users.sourceforge.org) Copyright: Copyright (c) 2003, Taro Ogawa. All Rights Reserved. @@ -19,8 +19,13 @@ Data from: Usage: from num2word_ES import to_card, to_ord, to_ordnum to_card(1234567890) -# to_ord(1234567890) -# to_ordnum(12) + to_ord(1234567890) + to_ordnum(12) + +History: + 0.3: Use high ascii characters instead of low ascii approximations + String interpolation where it makes things clearer + add to_currency() ''' from num2word_EU import Num2Word_EU @@ -34,7 +39,7 @@ class Num2Word_ES(Num2Word_EU): max = 3 + 6*len(high) for word, n in zip(high, range(max, 3, -6)): - self.cards[10**(n-3)] = word + "illo'n" + self.cards[10**(n-3)] = word + "ill\xf2n" def setup(self): @@ -50,8 +55,8 @@ class Num2Word_ES(Num2Word_EU): (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", + "vientis\xE8is", "vienticinco", "vienticuatro", + "vientitr\xE8s", "vientid\xF2s", "vientiuno", "viente", "diecinueve", "dieciocho", "diecisiete", "dieciseis", "quince", "catorce", "trece", "doce", "once", "diez", "nueve", "ocho", "siete", "seis", @@ -62,10 +67,10 @@ class Num2Word_ES(Num2Word_EU): 4 : "cuart", 5 : "quint", 6 : "sext", - 7 : "se'ptim", + 7 : "s\xE8ptim", 8 : "octav", 9 : "noven", - 10 : "de'cim" } + 10 : "d\xE8cim" } def merge(self, curr, next): @@ -80,8 +85,8 @@ class Num2Word_ES(Num2Word_EU): if nnum < cnum: if cnum < 100: - return (ctext + " y " + ntext, cnum + nnum) - return (ctext + " " + ntext, cnum + nnum) + return ("%s y %s"%(ctext, ntext), cnum + nnum) + return ("%s %s"%(ctext, ntext), cnum + nnum) elif (not nnum % 1000000) and cnum > 1: ntext = ntext[:-3] + "ones" @@ -110,8 +115,16 @@ class Num2Word_ES(Num2Word_EU): def to_ordinal_num(self, value): self.verify_ordinal(value) # Correct for fem? - return str(value) + "^o" - + return "%s\xB0"%value + + + def to_currency(self, val, longval=True, old=False): + if old: + return self.to_splitnum(val, hightxt="peso/s", lowtxt="peseta/s", + divisor=1000, jointxt="y", longval=longval) + return super(Num2Word_ES, self).to_currency(val, jointxt="y", + longval=longval) + n2w = Num2Word_ES() to_card = n2w.to_cardinal @@ -126,7 +139,9 @@ def main(): n2w.test(val) n2w.test(1325325436067876801768700107601001012212132143210473207540327057320957032975032975093275093275093270957329057320975093272950730) - + print n2w.to_currency(1222) + print n2w.to_currency(1222, old=True) + print n2w.to_year(1222) if __name__ == "__main__": main() diff --git a/num2word_EU.py b/num2word_EU.py index 9cf0fcb..5c0e4dd 100644 --- a/num2word_EU.py +++ b/num2word_EU.py @@ -1,10 +1,10 @@ ''' Module: num2word_EU.py Requires: num2word_base.py -Version: 1.0 +Version: 1.1 Author: - Taro Ogawa (BLAHhydroxideBLAH@inorbit.removeBLAHtwice.com) + Taro Ogawa (tso@users.sourceforge.org) Copyright: Copyright (c) 2003, Taro Ogawa. All Rights Reserved. @@ -15,6 +15,9 @@ Licence: Data from: http://www.uni-bonn.de/~manfear/large.php + +History: + 1.1: add to_currency() ''' from num2word_base import Num2Word_Base @@ -34,3 +37,8 @@ class Num2Word_EU(Num2Word_Base): tens = ["dec", "vigint", "trigint", "quadragint", "quinquagint", "sexagint", "septuagint", "octogint", "nonagint"] self.high_numwords = ["cent"]+self.gen_high_numwords(units, tens, lows) + + def to_currency(self, val, longval=True, jointxt=""): + return self.to_splitnum(val, hightxt="Euro/s", lowtxt="Euro cent/s", + jointxt=jointxt, longval=longval) + diff --git a/num2word_FR.py b/num2word_FR.py index 5055bf3..1ca0072 100644 --- a/num2word_FR.py +++ b/num2word_FR.py @@ -1,7 +1,7 @@ ''' Module: num2word_FR.py Requires: num2word_EU.py -Version: 0.4 +Version: 0.5 Author: Taro Ogawa (tso@users.sourceforge.org) @@ -14,18 +14,23 @@ Licence: http://www.opensource.org/licenses/lgpl-license.php Data from: - http://www.ouc.bc.ca/mola/fr/handouts/numbers.doc. + http://www.ouc.bc.ca/mola/fr/handouts/numbers.doc http://www.realfrench.net/units/Interunit_63.html - + http://www.sover.net/~daxtell/france/Euro/euro.htm + Usage: from num2word_FR import to_card, to_ord, to_ordnum to_card(1234567890) -# to_ord(1234567890) -# to_ordnum(12) + to_ord(1234567890) + to_ordnum(12) + +History: + 0.5: Use high ascii characters instead of low ascii approximations + String interpolation where it makes things clearer + to_currency() added [to_year works by default] ''' from num2word_EU import Num2Word_EU -#//TODO: correct orthographics #//TODO: error messages in French #//TODO: ords class Num2Word_FR(Num2Word_EU): @@ -42,7 +47,7 @@ class Num2Word_FR(Num2Word_EU): 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"] + "cinq", "quatre", "trois", "deux", "un", "z\xE8ro"] def merge(self, curr, next): @@ -60,13 +65,16 @@ class Num2Word_FR(Num2Word_EU): if nnum < cnum < 100: if nnum % 10 == 1 and cnum <> 80: - return (ctext + " et " + ntext, cnum + nnum) - return (ctext + "-" + ntext, cnum + nnum) + return ("%s et %s"%(ctext, ntext), cnum + nnum) + return ("%s-%s"%(ctext, ntext), cnum + nnum) elif nnum > cnum: - return (ctext + " " + ntext, cnum * nnum) - return (ctext + " " + ntext, cnum + nnum) + return ("%s %s"%(ctext, ntext), cnum * nnum) + return ("%s %s"%(ctext, ntext), cnum + nnum) + # Is this right for such things as 1001 - "mille unième" instead of + # "mille premier"?? "millième"?? + def to_ordinal(self,value): self.verify_ordinal(value) if value == 1: @@ -74,7 +82,7 @@ class Num2Word_FR(Num2Word_EU): word = self.to_cardinal(value) if word[-1] == "e": word = word[:-1] - return word + "ie'me" + return word + "i\xE8me" def to_ordinal_num(self, value): @@ -83,6 +91,12 @@ class Num2Word_FR(Num2Word_EU): out += {"1" : "er" }.get(out[-1], "me") return out + def to_currency(self, val, longval=True, old=False): + hightxt = "Euro/s" + if old: + hightxt="franc/s" + return self.to_splitnum(val, hightxt=hightxt, lowtxt="centime/s", + jointxt="et",longval=longval) n2w = Num2Word_FR() to_card = n2w.to_cardinal @@ -97,6 +111,8 @@ def main(): n2w.test(val) n2w.test(1325325436067876801768700107601001012212132143210473207540327057320957032975032975093275093275093270957329057320975093272950730) + print n2w.to_currency(112121) + print n2w.to_year(1996) if __name__ == "__main__": diff --git a/num2word_base.py b/num2word_base.py index 6c7beb7..a9c8893 100644 --- a/num2word_base.py +++ b/num2word_base.py @@ -3,7 +3,7 @@ Module: num2word_base.py Version: 1.0 Author: - Taro Ogawa (BLAHhydroxideBLAH_removetheBLAHs@inorbit.com) + Taro Ogawa (tso@users.sourceforge.org) Copyright: Copyright (c) 2003, Taro Ogawa. All Rights Reserved. @@ -12,27 +12,14 @@ 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 +History: + 1.1: add to_splitnum() and inflect() + add to_year() and to_currency() stubs ''' 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 +from orderedmapping import OrderedMapping class Num2Word_Base(object): @@ -153,11 +140,11 @@ class Num2Word_Base(object): def clean(self, val): out = val - while len(val) <> 1: + while len(val) != 1: out = [] - curr, next = val[:2] - if isinstance(curr, tuple) and isinstance(next, tuple): - out.append(self.merge(curr, next)) + left, right = val[:2] + if isinstance(left, tuple) and isinstance(right, tuple): + out.append(self.merge(left, right)) if val[2:]: out.append(val[2:]) else: @@ -209,6 +196,48 @@ class Num2Word_Base(object): return value + # 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="", + divisor=100, longval=True): + 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: + out.append(self.to_cardinal(low)) + 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) + + def base_setup(self): pass diff --git a/orderedmapping.py b/orderedmapping.py new file mode 100644 index 0000000..bb4e640 --- /dev/null +++ b/orderedmapping.py @@ -0,0 +1,34 @@ +''' +Module: orderedmapping.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 +''' +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 + + def __repr__(self): + out = ["%s: %s"%(repr(item), repr(self[item])) for item in self] + out = ", ".join(out) + return "{%s}"%out