From 08779b28b27c40f072b6b13116b83df227c40519 Mon Sep 17 00:00:00 2001 From: Brett Anthoine Date: Mon, 18 Jul 2016 08:30:45 +0200 Subject: [PATCH 1/5] Add support for italian. --- num2words/__init__.py | 2 + num2words/lang_IT.py | 204 ++++++++++++++++++++++++++++++++++++++++++ tests/test_it.py | 82 +++++++++++++++++ 3 files changed, 288 insertions(+) create mode 100644 num2words/lang_IT.py create mode 100644 tests/test_it.py diff --git a/num2words/__init__.py b/num2words/__init__.py index 6de57a2..7f878d4 100644 --- a/num2words/__init__.py +++ b/num2words/__init__.py @@ -32,6 +32,7 @@ from . import lang_NO from . import lang_DK from . import lang_PT_BR from . import lang_HE +from . import lang_IT CONVERTER_CLASSES = { 'en': lang_EN.Num2Word_EN(), @@ -50,6 +51,7 @@ CONVERTER_CLASSES = { 'dk': lang_DK.Num2Word_DK(), 'pt_BR': lang_PT_BR.Num2Word_PT_BR(), 'he': lang_HE.Num2Word_HE() + 'it': lang_IT.Num2Word_IT(), } def num2words(number, ordinal=False, lang='en'): diff --git a/num2words/lang_IT.py b/num2words/lang_IT.py new file mode 100644 index 0000000..a46a29c --- /dev/null +++ b/num2words/lang_IT.py @@ -0,0 +1,204 @@ +# -*- encoding: utf-8 -*- +# The PHP License, version 3.01 +# Copyright (c) 1999 - 2010 The PHP Group. All rights reserved. +# +# This source file is subject to version 3.01 of the PHP license, +# that is available at http://www.php.net/license/3_01.txt +# If you did not receive a copy of the PHP license and are unable to +# obtain it through the world-wide-web, please send a note to +# license@php.net so we can mail you a copy immediately. +# +# This code is a direct port to Python of the PHP code of the +# Number_Words package that can be found on pear at the URL : +# http://pear.php.net/package/Numbers_Words +# + +from __future__ import unicode_literals +from .lang_EU import Num2Word_EU + +import re +import math + +class Num2Word_IT(object): + def __init__(self): + self._minus = "meno " + + self._exponent = { + 0 : ('',''), + 3 : ('mille','mila'), + 6 : ('milione','miloni'), + 12 : ('miliardo','miliardi'), + 18 : ('trillone','trilloni'), + 24 : ('quadrilione','quadrilioni')} + + self._digits = ['zero', 'uno', 'due', 'tre', 'quattro', 'cinque', 'sei', 'sette', 'otto', 'nove'] + + self._sep = '' + + def _toWords(self, num, power=0): + str_num = str(num) + # The return string; + ret = '' + + # add a the word for the minus sign if necessary + if num < 0: + ret = self._sep + self._minus + + if len(str_num) > 6: + current_power = 6 + # check for highest power + if self._exponent.has_key(power): + # convert the number above the first 6 digits + # with it's corresponding $power. + snum = str_num[0:-6] + if snum != '': + ret = ret + self._toWords(int(snum), power + 6) + + num = int(str_num[-6:]) + if num == 0: + return ret + + elif num == 0 or str_num == '': + return ' ' + self._digits[0] + ' ' + else: + current_power = len(str_num) + + # See if we need "thousands" + thousands = math.floor(num / 1000) + if thousands == 1: + ret = ret + self._sep + 'mille' + self._sep + elif thousands > 1: + ret = ret + self._toWords(int(thousands), 3) + self._sep + + # values for digits, tens and hundreds + h = int(math.floor((num / 100) % 10)) + t = int(math.floor((num / 10) % 10)) + d = int(math.floor(num % 10)) + + # centinaia: duecento, trecento, etc... + if h == 1: + if ((d==0) and (t == 0)):# is it's '100' use 'cien' + ret = ret + self._sep + 'cento' + else: + ret = ret + self._sep + 'cento' + elif h == 2 or h == 3 or h == 4 or h == 6 or h == 8: + ret = ret + self._sep + self._digits[h] + 'cento' + elif h == 5: + ret = ret + self._sep + 'cinquecento' + elif h == 7: + ret = ret + self._sep + 'settecento' + elif h == 9: + ret = ret + self._sep + 'novecento' + + # decine: venti trenta, etc... + if t == 9: + if d == 1 or d == 8: + ret = ret + self._sep + 'novant' + else: + ret = ret + self._sep + 'novanta' + if t == 8: + if d == 1 or d == 8: + ret = ret + self._sep + 'ottant' + else: + ret = ret + self._sep + 'ottanta' + if t == 7: + if d == 1 or d == 8: + ret = ret + self._sep + 'settant' + else: + ret = ret + self._sep + 'settanta' + if t == 6: + if d == 1 or d == 8: + ret = ret + self._sep + 'sessant' + else: + ret = ret + self._sep + 'sessanta' + if t == 5: + if d == 1 or d == 8: + ret = ret + self._sep + 'cinquant' + else: + ret = ret + self._sep + 'cinquanta' + if t == 4: + if d == 1 or d == 8: + ret = ret + self._sep + 'quarant' + else: + ret = ret + self._sep + 'quaranta' + if t == 3: + if d == 1 or d == 8: + ret = ret + self._sep + 'trent' + else: + ret = ret + self._sep + 'trenta' + if t == 2: + if d == 0: + ret = ret + self._sep + 'venti' + elif (d == 1 or d == 8): + ret = ret + self._sep + 'vent' + self._digits[d] + else: + ret = ret + self._sep + 'venti' + self._digits[d] + if t == 1: + if d == 0: + ret = ret + self._sep + 'dieci' + elif d == 1: + ret = ret + self._sep + 'undici' + elif d == 2: + ret = ret + self._sep + 'dodici' + elif d == 3: + ret = ret + self._sep + 'tredici' + elif d == 4: + ret = ret + self._sep + 'quattordici' + elif d == 5: + ret = ret + self._sep + 'quindici' + elif d == 6: + ret = ret + self._sep + 'sedici' + elif d == 7: + ret = ret + self._sep + 'diciassette' + elif d == 8: + ret = ret + self._sep + 'diciotto' + elif d == 9: + ret = ret + self._sep + 'diciannove' + + # add digits only if it is a multiple of 10 and not 1x or 2x + if t != 1 and t != 2 and d > 0: + # don't add 'e' for numbers below 10 + if t != 0: + # use 'un' instead of 'uno' when there is a suffix ('mila', 'milloni', etc...) + if (power > 0) and ( d == 1): + ret = ret + self._sep + 'e un' + else: + ret = ret + self._sep + '' + self._digits[d] + else: + if power > 0 and d == 1: + ret = ret + self._sep + 'un ' + else: + ret = ret + self._sep + self._digits[d] + + if power > 0: + if self._exponent.has_key(power): + lev = self._exponent[power] + + if lev is None: + return None + + # if it's only one use the singular suffix + if d == 1 and t == 0 and h == 0: + suffix = lev[0] + else: + suffix = lev[1] + + if num != 0: + ret = ret + self._sep + suffix + + return ret + + + def to_cardinal(self, number): + return self._toWords(number) + + def to_ordinal_num(self, number): + pass + + def to_ordinal(self,value): + raise NotImplementedError() + +n2w = Num2Word_IT() +to_card = n2w.to_cardinal +to_ord = n2w.to_ordinal +to_ordnum = n2w.to_ordinal_num diff --git a/tests/test_it.py b/tests/test_it.py new file mode 100644 index 0000000..07cd3a2 --- /dev/null +++ b/tests/test_it.py @@ -0,0 +1,82 @@ +# -*- encoding: utf-8 -*- +# Copyright (c) 2015, Savoir-faire Linux inc. All Rights Reserved. + +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301 USA + +from __future__ import unicode_literals + +from unittest import TestCase + +from num2words import num2words + +class Num2WordsITTest(TestCase): + + def test_number(self): + + test_cases = ( + (1,'uno'), + (2,'due'), + (3,'tre'), + (11,'undici'), + (12,'dodici'), + (16,'sedici'), + (19,'diciannove'), + (20,'venti'), + (21,'ventuno'), + (26,'ventisei'), + (30,'trenta'), + (31,'trentuno'), + (40,'quaranta'), + (43,'quarantatre'), + (50,'cinquanta'), + (55,'cinquantacinque'), + (60,'sessanta'), + (67,'sessantasette'), + (70,'settanta'), + (79,'settantanove'), + (100,'cento'), + (101,'centouno'), + (199,'centonovantanove'), + (203,'duecentotre'), + (287,'duecentoottantasette'), + (300,'trecento'), + (356,'trecentocinquantasei'), + (410,'quattrocentodieci'), + (434,'quattrocentotrentaquattro'), + (578,'cinquecentosettantotto'), + (689,'seicentoottantanove'), + (729,'settecentoventinove'), + (894,'ottocentonovantaquattro'), + (999,'novecentonovantanove'), + (1000,'mille'), + (1001,'milleuno'), + (1097,'millenovantasette'), + (1104,'millecentoquattro'), + (1243,'milleduecentoquarantatre'), + (2385,'duemilatrecentoottantacinque'), + (3766,'tremilasettecentosessantasei'), + (4196,'quattromilacentonovantasei'), + (5846,'cinquemilaottocentoquarantasei'), + (6459,'seimilaquattrocentocinquantanove'), + (7232,'settemiladuecentotrentadue'), + (8569,'ottomilacinquecentosessantanove'), + (9539,'novemilacinquecentotrentanove'), + (1000000,'un milione'), + (1000001,'un milioneuno'), + # (1000000100,'un miliardocento'), # DOES NOT WORK TODO: FIX + ) + + for test in test_cases: + self.assertEqual(num2words(test[0], lang='it'), test[1]) + From c9ecd07cbfffaa740e5c471fd5dacc9efde9dd3e Mon Sep 17 00:00:00 2001 From: Brett Anthoine Date: Mon, 18 Jul 2016 11:27:51 +0200 Subject: [PATCH 2/5] Add italian ordinals. --- num2words/lang_IT.py | 15 ++++++++++++++- tests/test_it.py | 14 ++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/num2words/lang_IT.py b/num2words/lang_IT.py index a46a29c..00dfebf 100644 --- a/num2words/lang_IT.py +++ b/num2words/lang_IT.py @@ -196,9 +196,22 @@ class Num2Word_IT(object): pass def to_ordinal(self,value): - raise NotImplementedError() + if 0 <= value <= 10: + return ["primo", "secondo", "terzo", "quarto", "quinto", "sesto", "settimo", "ottavo", "nono", "decimo"][value - 1] + else: + as_word = self._toWords(value) + if as_word.endswith("dici"): + return re.sub("dici$", "dicesimo", as_word) + elif as_word.endswith("to"): + return re.sub("to$", "tesimo", as_word) + elif as_word.endswith("ta"): + return re.sub("ta$", "tesimo", as_word) + else: + return as_word + "simo" + n2w = Num2Word_IT() to_card = n2w.to_cardinal to_ord = n2w.to_ordinal to_ordnum = n2w.to_ordinal_num + diff --git a/tests/test_it.py b/tests/test_it.py index 07cd3a2..57bf646 100644 --- a/tests/test_it.py +++ b/tests/test_it.py @@ -35,6 +35,7 @@ class Num2WordsITTest(TestCase): (20,'venti'), (21,'ventuno'), (26,'ventisei'), + (28,'ventotto'), (30,'trenta'), (31,'trentuno'), (40,'quaranta'), @@ -80,3 +81,16 @@ class Num2WordsITTest(TestCase): for test in test_cases: self.assertEqual(num2words(test[0], lang='it'), test[1]) + def test_ordinal(self): + + test_cases = ( + (1,'primo'), + (8,'ottavo'), + (12,'dodicesimo'), + (14,'quattordicesimo'), + (28,'ventottesimo'), + (100,'centesimo'), + ) + + for test in test_cases: + self.assertEqual(num2words(test[0], lang='it', ordinal=True), test[1]) From bd1f4faf1d1a1e80bc8843874754c2da8c3d7688 Mon Sep 17 00:00:00 2001 From: Brett Anthoine Date: Tue, 10 Jan 2017 14:39:09 +0100 Subject: [PATCH 3/5] Update license header --- num2words/lang_IT.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/num2words/lang_IT.py b/num2words/lang_IT.py index 00dfebf..000068c 100644 --- a/num2words/lang_IT.py +++ b/num2words/lang_IT.py @@ -1,17 +1,17 @@ # -*- encoding: utf-8 -*- -# The PHP License, version 3.01 -# Copyright (c) 1999 - 2010 The PHP Group. All rights reserved. -# -# This source file is subject to version 3.01 of the PHP license, -# that is available at http://www.php.net/license/3_01.txt -# If you did not receive a copy of the PHP license and are unable to -# obtain it through the world-wide-web, please send a note to -# license@php.net so we can mail you a copy immediately. -# -# This code is a direct port to Python of the PHP code of the -# Number_Words package that can be found on pear at the URL : -# http://pear.php.net/package/Numbers_Words # +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301 USA from __future__ import unicode_literals from .lang_EU import Num2Word_EU From 58a4e8e18b704cc06902aae8631c9e60d37580f9 Mon Sep 17 00:00:00 2001 From: Brett Anthoine Date: Wed, 1 Feb 2017 08:49:34 +0100 Subject: [PATCH 4/5] Fix syntax error --- num2words/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/num2words/__init__.py b/num2words/__init__.py index 7f878d4..57c4d57 100644 --- a/num2words/__init__.py +++ b/num2words/__init__.py @@ -50,8 +50,8 @@ CONVERTER_CLASSES = { 'no': lang_NO.Num2Word_NO(), 'dk': lang_DK.Num2Word_DK(), 'pt_BR': lang_PT_BR.Num2Word_PT_BR(), - 'he': lang_HE.Num2Word_HE() - 'it': lang_IT.Num2Word_IT(), + 'he': lang_HE.Num2Word_HE(), + 'it': lang_IT.Num2Word_IT() } def num2words(number, ordinal=False, lang='en'): From 0b80fd42855bf0cb278d8dd858b61cffbba8df97 Mon Sep 17 00:00:00 2001 From: Brett Anthoine Date: Wed, 1 Feb 2017 16:13:24 +0100 Subject: [PATCH 5/5] Use in operator instead of has_key for python3 support --- num2words/lang_IT.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/num2words/lang_IT.py b/num2words/lang_IT.py index 000068c..227883b 100644 --- a/num2words/lang_IT.py +++ b/num2words/lang_IT.py @@ -47,7 +47,7 @@ class Num2Word_IT(object): if len(str_num) > 6: current_power = 6 # check for highest power - if self._exponent.has_key(power): + if power in self._exponent: # convert the number above the first 6 digits # with it's corresponding $power. snum = str_num[0:-6] @@ -171,7 +171,7 @@ class Num2Word_IT(object): ret = ret + self._sep + self._digits[d] if power > 0: - if self._exponent.has_key(power): + if power in self._exponent: lev = self._exponent[power] if lev is None: