File: lang_IT.py

package info (click to toggle)
python-num2words 0.5.14-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 1,832 kB
  • sloc: python: 27,073; makefile: 3
file content (241 lines) | stat: -rw-r--r-- 8,838 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
# -*- coding: utf-8 -*-
# Copyright (c) 2018-2019, Filippo Costa.  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 .lang_EU import Num2Word_EU

# Globals
# -------

ZERO = "zero"

CARDINAL_WORDS = [
    ZERO, "uno", "due", "tre", "quattro", "cinque", "sei", "sette", "otto",
    "nove", "dieci", "undici", "dodici", "tredici", "quattordici", "quindici",
    "sedici", "diciassette", "diciotto", "diciannove"
]

ORDINAL_WORDS = [
    ZERO, "primo", "secondo", "terzo", "quarto", "quinto", "sesto", "settimo",
    "ottavo", "nono", "decimo", "undicesimo", "dodicesimo", "tredicesimo",
    "quattordicesimo", "quindicesimo", "sedicesimo", "diciassettesimo",
    "diciottesimo", "diciannovesimo"
]

# The script can extrapolate the missing numbers from the base forms.
STR_TENS = {2: "venti", 3: "trenta", 4: "quaranta", 6: "sessanta"}

# These prefixes are used for extremely big numbers.
EXPONENT_PREFIXES = [
    ZERO, "m", "b", "tr", "quadr", "quint", "sest", "sett", "ott", "nov", "dec"
]


GENERIC_DOLLARS = ('dollaro', 'dollari')
GENERIC_CENTS = ('centesimo', 'centesimi')
CURRENCIES_UNA = ('GBP')


# Main class
# ==========

class Num2Word_IT(Num2Word_EU):
    CURRENCY_FORMS = {
        'EUR': (('euro', 'euro'), GENERIC_CENTS),
        'USD': (GENERIC_DOLLARS, GENERIC_CENTS),
        'GBP': (('sterlina', 'sterline'), ('penny', 'penny')),
        'CNY': (('yuan', 'yuan'), ('fen', 'fen')),
    }
    MINUS_PREFIX_WORD = "meno "
    FLOAT_INFIX_WORD = " virgola "

    def setup(self):
        Num2Word_EU.setup(self)

    def __init__(self):
        pass

    def float_to_words(self, float_number, ordinal=False):
        if ordinal:
            prefix = self.to_ordinal(int(float_number))
        else:
            prefix = self.to_cardinal(int(float_number))
        float_part = str(float_number).split('.')[1]
        postfix = " ".join(
            # Drops the trailing zero and comma
            [self.to_cardinal(int(c)) for c in float_part]
        )
        return prefix + Num2Word_IT.FLOAT_INFIX_WORD + postfix

    def tens_to_cardinal(self, number):
        tens = number // 10
        units = number % 10
        if tens in STR_TENS:
            prefix = STR_TENS[tens]
        else:
            prefix = CARDINAL_WORDS[tens][:-1] + "anta"
        postfix = omitt_if_zero(CARDINAL_WORDS[units])
        return phonetic_contraction(prefix + postfix)

    def hundreds_to_cardinal(self, number):
        hundreds = number // 100
        prefix = "cento"
        if hundreds != 1:
            prefix = CARDINAL_WORDS[hundreds] + prefix
        postfix = omitt_if_zero(self.to_cardinal(number % 100))
        return phonetic_contraction(prefix + postfix)

    def thousands_to_cardinal(self, number):
        thousands = number // 1000
        if thousands == 1:
            prefix = "mille"
        else:
            prefix = self.to_cardinal(thousands) + "mila"
        postfix = omitt_if_zero(self.to_cardinal(number % 1000))
        # "mille" and "mila" don't need any phonetic contractions
        return prefix + postfix

    def big_number_to_cardinal(self, number):
        digits = [c for c in str(number)]
        length = len(digits)
        if length >= 66:
            raise NotImplementedError("The given number is too large.")
        # This is how many digits come before the "illion" term.
        #   cento miliardi => 3
        #   dieci milioni => 2
        #   un miliardo => 1
        predigits = length % 3 or 3
        multiplier = digits[:predigits]
        exponent = digits[predigits:]
        # Default infix string: "milione", "biliardo", "sestilione", ecc.
        infix = exponent_length_to_string(len(exponent))
        if multiplier == ["1"]:
            prefix = "un "
        else:
            prefix = self.to_cardinal(int("".join(multiplier)))
            # Plural form      ~~~~~~~~~~~
            infix = " " + infix[:-1] + "i"
        # Read as: Does the value of exponent equal 0?
        if set(exponent) != set("0"):
            postfix = self.to_cardinal(int("".join(exponent)))
            if " e " in postfix:
                infix += ", "
            else:
                infix += " e "
        else:
            postfix = ""
        return prefix + infix + postfix

    def to_cardinal(self, number):
        if number < 0:
            string = Num2Word_IT.MINUS_PREFIX_WORD + self.to_cardinal(-number)
        elif isinstance(number, float):
            string = self.float_to_words(number)
        elif number < 20:
            string = CARDINAL_WORDS[int(number)]
        elif number < 100:
            string = self.tens_to_cardinal(int(number))
        elif number < 1000:
            string = self.hundreds_to_cardinal(int(number))
        elif number < 1000000:
            string = self.thousands_to_cardinal(int(number))
        else:
            string = self.big_number_to_cardinal(number)
        return accentuate(string)

    def to_ordinal(self, number):
        tens = number % 100
        # Italian grammar is poorly defined here ¯\_(ツ)_/¯:
        #   centodecimo VS centodieciesimo VS centesimo decimo?
        is_outside_teens = not 10 < tens < 20
        if number < 0:
            return Num2Word_IT.MINUS_PREFIX_WORD + self.to_ordinal(-number)
        elif number % 1 != 0:
            return self.float_to_words(number, ordinal=True)
        elif number < 20:
            return ORDINAL_WORDS[int(number)]
        elif is_outside_teens and tens % 10 == 3:
            # Gets rid of the accent
            return self.to_cardinal(number)[:-1] + "eesimo"
        elif is_outside_teens and tens % 10 == 6:
            return self.to_cardinal(number) + "esimo"
        else:
            string = self.to_cardinal(number)[:-1]
            if string[-3:] == "mil":
                string += "l"
            return string + "esimo"

    def to_currency(self, val, currency='EUR', cents=True, separator=' e',
                    adjective=False):
        result = super(Num2Word_IT, self).to_currency(
            val, currency=currency, cents=cents, separator=separator,
            adjective=adjective)
        # Handle exception. In italian language is "un euro",
        # "un dollaro" etc. (not "uno euro", "uno dollaro").
        # There is an exception, some currencies need "una":
        # e.g. "una sterlina"
        if currency in CURRENCIES_UNA:
            list_result = result.split(" ")
            if list_result[0] == "uno":
                list_result[0] = list_result[0].replace("uno", "una")
                result = " ".join(list_result)
        result = result.replace("uno", "un")
        return result

# Utils
# =====


def phonetic_contraction(string):
    return (string
            .replace("oo", "o")  # ex. "centootto"
            .replace("ao", "o")  # ex. "settantaotto"
            .replace("io", "o")  # ex. "ventiotto"
            .replace("au", "u")  # ex. "trentauno"
            .replace("iu", "u")  # ex. "ventiunesimo"
            )


def exponent_length_to_string(exponent_length):
    # We always assume `exponent` to be a multiple of 3. If it's not true, then
    # Num2Word_IT.big_number_to_cardinal did something wrong.
    prefix = EXPONENT_PREFIXES[exponent_length // 6]
    if exponent_length % 6 == 0:
        return prefix + "ilione"
    else:
        return prefix + "iliardo"


def accentuate(string):
    # This is inefficient: it may do several rewritings when deleting
    # half-sentence accents. However, it is the easiest method and speed is
    # not crucial (duh), so...
    return " ".join(
        # Deletes half-sentence accents and accentuates the last "tre"
        [w.replace("tré", "tre")[:-3] + "tré"
         # We shouldn't accentuate a single "tre": is has to be a composite
         # word.                ~~~~~~~~~~
         if w[-3:] == "tre" and len(w) > 3
         # Deletes half-sentence accents anyway
         #     ~~~~~~~~~~~~~~~~~~~~~~
         else w.replace("tré", "tre")
         for w in string.split()
         ])


def omitt_if_zero(number_to_string):
    return "" if number_to_string == ZERO else number_to_string