ai-content-maker/.venv/Lib/site-packages/num2words/lang_RU.py

502 lines
21 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# -*- coding: utf-8 -*-
# Copyright (c) 2003, Taro Ogawa. All Rights Reserved.
# Copyright (c) 2013, 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 .base import Num2Word_Base
from .utils import get_digits, splitbyx
GENDER_PLURAL_INDEXES = {
'm': 0, 'masculine': 0, 'м': 0, 'мужской': 0,
'f': 1, 'feminine': 1, 'ж': 0, 'женский': 0,
'n': 2, 'neuter': 2, 'с': 0, 'средний': 0,
'p': 3, 'plural': 3
}
CASE_INDEXES = {
'n': 0, 'nominative': 0, 'и': 0, 'именительный': 0,
'g': 1, 'genitive': 1, 'р': 1, 'родительный': 1,
'd': 2, 'dative': 2, 'д': 2, 'дательный': 2,
'a': 3, 'accusative': 3, 'в': 3, 'винительный': 3,
'i': 4, 'instrumental': 4, 'т': 4, 'творительный': 4,
'p': 5, 'prepositional': 5, 'п': 5, 'предложный': 5
}
# Default values
D_CASE = 'n'
D_PLURAL = False
D_GENDER = 'm'
D_ANIMATE = True
def get_num_element(cases_dict, num, **kwargs):
return case_classifier_element(cases_dict[num], **kwargs)
def case_classifier_element(classifier, case=D_CASE, plural=D_PLURAL,
gender=D_GENDER, animate=D_ANIMATE):
case = classifier[CASE_INDEXES[case]]
if isinstance(case, str):
return case
if plural:
gender = case[GENDER_PLURAL_INDEXES['plural']]
else:
gender = case[GENDER_PLURAL_INDEXES[gender]]
if isinstance(gender, str):
return gender
if animate:
return gender[0]
return gender[1]
# format:
# {n : [case_1 .. case_5]}
# case: text or [gender_1 .. gender_3 plural_4]
# gender: text or [animate, inanimate]
ONES = {
0: ['ноль', 'ноля', 'нолю', 'ноль', 'нолём', 'ноле'],
1: [['один', 'одна', 'одно', 'одни'],
['одного', 'одной', 'одного', 'одних'],
['одному', 'одной', 'одному', 'одним'],
[['одного', 'один'], 'одну', 'одно', ['одних', 'одни']],
['одним', 'одной', 'одним', 'одними'],
['одном', 'одной', 'одном', 'одних']],
2: [['два', 'две', 'два', 'двое'],
['двух'] * 3 + ['двоих'],
['двум'] * 3 + ['двоим'],
[['двух', 'два'], ['двух', 'две'], 'два', 'двоих'],
['двумя'] * 3 + ['двоими'],
['двух'] * 3 + ['двоих']],
3: [['три'] * 3 + ['трое'],
['трёх'] * 3 + ['троих'],
['трём'] * 3 + ['троим'],
[['трёх', 'три'], ['трёх', 'три'], 'три', 'троих'],
['тремя'] * 3 + ['троими'],
['трёх'] * 3 + ['троих']],
4: [['четыре'] * 3 + ['четверо'],
['четырёх'] * 3 + ['четверых'],
['четырём'] * 3 + ['четверым'],
[['четырёх', 'четыре'], ['четырёх', 'четыре'], 'четыре', 'четверых'],
['четырьмя'] * 3 + ['четверыми'],
['четырёх'] * 3 + ['четверых']],
5: ['пять', 'пяти', 'пяти', 'пять', 'пятью', 'пяти'],
6: ['шесть', 'шести', 'шести', 'шесть', 'шестью', 'шести'],
7: ['семь', 'семи', 'семи', 'семь', 'семью', 'семи'],
8: ['восемь', 'восьми', 'восьми', 'восемь', 'восемью', 'восьми'],
9: ['девять', 'девяти', 'девяти', 'девять', 'девятью', 'девяти']
}
ONES_ORD_PREFIXES = {0: 'нулев', 1: 'перв', 2: 'втор', 4: 'четвёрт', 5: 'пят',
6: 'шест', 7: 'седьм', 8: 'восьм', 9: 'девят'}
ONES_ORD_POSTFIXES_GROUPS = {0: 0, 1: 1, 2: 0, 4: 1, 5: 1, 6: 0, 7: 0, 8: 0,
9: 1}
CASE_POSTFIXES = [[{0: 'ой', 1: 'ый'}, 'ая', 'ое', 'ые'],
['ого', 'ой', 'ого', 'ых'],
['ому', 'ой', 'ому', 'ым'],
[['ого', {0: 'ой', 1: 'ый'}], 'ую', 'ое', ['ых', 'ые']],
['ым', 'ой', 'ым', 'ыми'],
['ом', 'ой', 'ом', 'ых']]
def get_cases(prefix, post_group):
return [[
prefix + postfix if isinstance(postfix, str) else
[prefix + animate if isinstance(animate, str) else
prefix + animate[post_group]
for animate in postfix] if isinstance(postfix, list) else
prefix + postfix[post_group]
for postfix in case]
for case in CASE_POSTFIXES]
def get_ord_classifier(prefixes, post_groups):
if isinstance(post_groups, int):
post_groups = {n: post_groups for n, i in prefixes.items()}
return {
num: get_cases(prefix, post_groups[num])
for num, prefix in prefixes.items()
}
ONES_ORD = {
3: [['третий', 'третья', 'третье', 'третьи'],
['третьего', 'третьей', 'третьего', 'третьих'],
['третьему', 'третьей', 'третьему', 'третьим'],
[['третьего', 'третий'], 'третью', 'третье', ['третьих', 'третьи']],
['третьим', 'третьей', 'третьим', 'третьими'],
['третьем', 'третьей', 'третьем', 'третьих']],
}
ONES_ORD.update(
get_ord_classifier(ONES_ORD_PREFIXES, ONES_ORD_POSTFIXES_GROUPS)
)
TENS_PREFIXES = {1: 'один', 2: 'две', 3: 'три', 4: 'четыр', 5: 'пят',
6: 'шест', 7: 'сем', 8: 'восем', 9: 'девят'}
TENS_POSTFIXES = ['надцать', 'надцати', 'надцати', 'надцать', 'надцатью',
'надцати']
TENS = {0: ['десять', 'десяти', 'десяти', 'десять', 'десятью', 'десяти']}
TENS.update({
num: [prefix + postfix for postfix in TENS_POSTFIXES]
for num, prefix in TENS_PREFIXES.items()
})
TENS_ORD_PREFIXES = {0: "десят"}
TENS_ORD_PREFIXES.update({
num: prefix + 'надцат' for num, prefix in TENS_PREFIXES.items()
})
TENS_ORD = get_ord_classifier(TENS_ORD_PREFIXES, 1)
TWENTIES = {
2: ['двадцать', 'двадцати', 'двадцати', 'двадцать', 'двадцатью',
'двадцати'],
3: ['тридцать', 'тридцати', 'тридцати', 'тридцать', 'тридцатью',
'тридцати'],
4: ['сорок', 'сорока', 'сорока', 'сорок', 'сорока', 'сорока'],
5: ['пятьдесят', 'пятидесяти', 'пятидесяти', 'пятьдесят', 'пятьюдесятью',
'пятидесяти'],
6: ['шестьдесят', 'шестидесяти', 'шестидесяти', 'шестьдесят',
'шестьюдесятью', 'шестидесяти'],
7: ['семьдесят', 'семидесяти', 'семидесяти', 'семьдесят', 'семьюдесятью',
'семидесяти'],
8: ['восемьдесят', 'восьмидесяти', 'восьмидесяти', 'восемьдесят',
'восемьюдесятью', 'восьмидесяти'],
9: ['девяносто', 'девяноста', 'девяноста', 'девяносто', 'девяноста',
'девяноста'],
}
TWENTIES_ORD_PREFIXES = {2: 'двадцат', 3: 'тридцат', 4: 'сороков',
5: 'пятидесят', 6: 'шестидесят', 7: 'семидесят',
8: 'восьмидесят', 9: 'девяност'}
TWENTIES_ORD_POSTFIXES_GROUPS = {2: 1, 3: 1, 4: 0, 5: 1, 6: 1, 7: 1, 8: 1,
9: 1}
TWENTIES_ORD = get_ord_classifier(TWENTIES_ORD_PREFIXES,
TWENTIES_ORD_POSTFIXES_GROUPS)
HUNDREDS = {
1: ['сто', 'ста', 'ста', 'сто', 'ста', 'ста'],
2: ['двести', 'двухсот', 'двумстам', 'двести', 'двумястами', 'двухстах'],
3: ['триста', 'трёхсот', 'трёмстам', 'триста', 'тремястами', 'трёхстах'],
4: ['четыреста', 'четырёхсот', 'четырёмстам', 'четыреста', 'четырьмястами',
'четырёхстах'],
5: ['пятьсот', 'пятисот', 'пятистам', 'пятьсот', 'пятьюстами', 'пятистах'],
6: ['шестьсот', 'шестисот', 'шестистам', 'шестьсот', 'шестьюстами',
'шестистах'],
7: ['семьсот', 'семисот', 'семистам', 'семьсот', 'семьюстами', 'семистах'],
8: ['восемьсот', 'восьмисот', 'восьмистам', 'восемьсот', 'восемьюстами',
'восьмистах'],
9: ['девятьсот', 'девятисот', 'девятистам', 'девятьсот', 'девятьюстами',
'девятистах'],
}
HUNDREDS_ORD_PREFIXES = {
num: case[1] if num != 1 else 'сот' for num, case in HUNDREDS.items()
}
HUNDREDS_ORD = get_ord_classifier(HUNDREDS_ORD_PREFIXES, 1)
THOUSANDS_PREFIXES = {2: 'миллион', 3: 'миллиард', 4: 'триллион',
5: 'квадриллион', 6: 'квинтиллион', 7: 'секстиллион',
8: 'септиллион', 9: 'октиллион', 10: 'нониллион'}
THOUSANDS_POSTFIXES = [('', 'а', 'ов'),
('а', 'ов', 'ов'),
('у', 'ам', 'ам'),
('', 'а', 'ов'),
('ом', 'ами', 'ами'),
('е', 'ах', 'ах')]
THOUSANDS = {
1: [['тысяча', 'тысячи', 'тысяч'],
['тысячи', 'тысяч', 'тысяч'],
['тысяче', 'тысячам', 'тысячам'],
['тысячу', 'тысячи', 'тысяч'],
['тысячей', 'тысячами', 'тысячами'],
['тысяче', 'тысячах', 'тысячах']]
}
THOUSANDS.update({
num: [
[prefix + postfix for postfix in case] for case in THOUSANDS_POSTFIXES
] for num, prefix in THOUSANDS_PREFIXES.items()
})
def get_thousands_elements(num, case):
return THOUSANDS[num][CASE_INDEXES[case]]
THOUSANDS_ORD_PREFIXES = {1: 'тысячн'}
THOUSANDS_ORD_PREFIXES.update({
num: prefix + 'н' for num, prefix in THOUSANDS_PREFIXES.items()
})
THOUSANDS_ORD = get_ord_classifier(THOUSANDS_ORD_PREFIXES, 1)
class Num2Word_RU(Num2Word_Base):
CURRENCY_FORMS = {
'RUB': (
('рубль', 'рубля', 'рублей'), ('копейка', 'копейки', 'копеек')
),
'EUR': (
('евро', 'евро', 'евро'), ('цент', 'цента', 'центов')
),
'USD': (
('доллар', 'доллара', 'долларов'), ('цент', 'цента', 'центов')
),
'UAH': (
('гривна', 'гривны', 'гривен'), ('копейка', 'копейки', 'копеек')
),
'KZT': (
('тенге', 'тенге', 'тенге'), ('тиын', 'тиына', 'тиынов')
),
'BYN': (
('белорусский рубль', 'белорусских рубля', 'белорусских рублей'),
('копейка', 'копейки', 'копеек')
),
'UZS': (
('сум', 'сума', 'сумов'), ('тийин', 'тийина', 'тийинов')
),
'PLN': (
('польский злотый', 'польских слотых', 'польских злотых'),
('грош', 'гроша', 'грошей')
),
}
def setup(self):
self.negword = "минус"
self.pointword = ('целая', 'целых', 'целых')
self.pointword_ord = get_cases("цел", 1)
def to_cardinal(self, number, case=D_CASE, plural=D_PLURAL,
gender=D_GENDER, animate=D_ANIMATE):
n = str(number).replace(',', '.')
if '.' in n:
left, right = n.split('.')
decimal_part = self._int2word(int(right), cardinal=True,
gender='f')
return u'%s %s %s %s' % (
self._int2word(int(left), cardinal=True, gender='f'),
self.pluralize(int(left), self.pointword),
decimal_part,
self.__decimal_bitness(right)
)
else:
return self._int2word(int(n), cardinal=True, case=case,
plural=plural, gender=gender,
animate=animate)
def __decimal_bitness(self, n):
if n[-1] == "1" and n[-2:] != "11":
return self._int2word(10 ** len(n), cardinal=False, gender='f')
return self._int2word(10 ** len(n), cardinal=False, case='g',
plural=True)
def pluralize(self, n, forms):
if n % 100 in (11, 12, 13, 14):
return forms[2]
if n % 10 == 1:
return forms[0]
if n % 10 in (2, 3, 4):
return forms[1]
return forms[2]
def to_ordinal(self, number, case=D_CASE, plural=D_PLURAL, gender=D_GENDER,
animate=D_ANIMATE):
self.verify_ordinal(number)
n = str(number).replace(',', '.')
return self._int2word(int(n), cardinal=False, case=case, plural=plural,
gender=gender, animate=animate)
def _money_verbose(self, number, currency):
if currency == 'UAH':
return self._int2word(number, gender='f')
return self._int2word(number, gender='m')
def _cents_verbose(self, number, currency):
if currency in ('UAH', 'RUB', 'BYN'):
return self._int2word(number, gender='f')
return self._int2word(number, gender='m')
def _int2word(self, n, feminine=False, cardinal=True, case=D_CASE,
plural=D_PLURAL, gender=D_GENDER, animate=D_ANIMATE):
"""
n: number
feminine: not used - for backward compatibility
cardinal:True - cardinal
False - ordinal
case: 'n' - nominative
'g' - genitive
'd' - dative
'a' - accusative
'i' - instrumental
'p' - prepositional
plural: True - plural
False - singular
gender: 'f' - masculine
'm' - feminine
'n' - neuter
animate: True - animate
False - inanimate
"""
# For backward compatibility
if feminine:
gender = 'f'
kwargs = {'case': case, 'plural': plural, 'gender': gender,
'animate': animate}
if n < 0:
return ' '.join([self.negword, self._int2word(abs(n),
cardinal=cardinal,
**kwargs)])
if n == 0:
return get_num_element(ONES, 0, **kwargs) if cardinal else \
get_num_element(ONES_ORD, 0, **kwargs)
words = []
chunks = list(splitbyx(str(n), 3))
ord_join = chunks[-1] == 0 # join in one word if ending on 'тысячный'
i = len(chunks)
rightest_nonzero_chunk_i = i - 1 - max(
[i for i, e in enumerate(chunks) if e != 0])
for x in chunks:
chunk_words = []
i -= 1
if x == 0:
continue
n1, n2, n3 = get_digits(x)
if cardinal:
chunk_words.extend(
self.__chunk_cardianl(n3, n2, n1, i, **kwargs)
)
if i > 0:
chunk_words.append(
self.pluralize(x, get_thousands_elements(i, case)))
# ordinal, not joined like 'двухтысячный'
elif not (ord_join and rightest_nonzero_chunk_i == i):
chunk_words.extend(
self.__chunk_ordinal(n3, n2, n1, i, **kwargs)
)
if i > 0:
t_case = case if rightest_nonzero_chunk_i == i else 'n'
chunk_words.append(
self.pluralize(x, get_thousands_elements(i, t_case)))
# ordinal, joined
else:
chunk_words.extend(
self.__chunk_ordinal_join(n3, n2, n1, i, **kwargs)
)
if i > 0:
chunk_words.append(
get_num_element(THOUSANDS_ORD, i, **kwargs))
chunk_words = [''.join(chunk_words)]
words.extend(chunk_words)
return ' '.join(words)
def __chunk_cardianl(self, hundreds, tens, ones, chunk_num, **kwargs):
words = []
if hundreds > 0:
words.append(get_num_element(HUNDREDS, hundreds, **kwargs))
if tens > 1:
words.append(get_num_element(TWENTIES, tens, **kwargs))
if tens == 1:
words.append(get_num_element(TENS, ones, **kwargs))
elif ones > 0:
if chunk_num == 0:
w_ones = get_num_element(ONES, ones, **kwargs)
elif chunk_num == 1:
# Thousands are feminine
f_kwargs = kwargs.copy()
f_kwargs['gender'] = 'f'
w_ones = get_num_element(ONES, ones, **f_kwargs)
else:
w_ones = get_num_element(ONES, ones, **kwargs)
words.append(w_ones)
return words
def __chunk_ordinal(self, hundreds, tens, ones, chunk_num, **kwargs):
words = []
if hundreds > 0:
if tens == 0 and ones == 0:
words.append(get_num_element(HUNDREDS_ORD, hundreds, **kwargs))
else:
words.append(get_num_element(HUNDREDS, hundreds))
if tens > 1:
if ones == 0:
words.append(get_num_element(TWENTIES_ORD, tens, **kwargs))
else:
words.append(get_num_element(TWENTIES, tens))
if tens == 1:
words.append(get_num_element(TENS_ORD, ones, **kwargs))
elif ones > 0:
if chunk_num == 0:
w_ones = get_num_element(ONES_ORD, ones, **kwargs)
# тысячный, миллионнный и т.д.
elif chunk_num > 0 and ones == 1 and hundreds == 0 and tens == 0:
w_ones = None
elif chunk_num == 1:
# Thousands are feminine
w_ones = get_num_element(ONES, ones, gender='f')
else:
w_ones = get_num_element(ONES, ones)
if w_ones:
words.append(w_ones)
return words
def __chunk_ordinal_join(self, hundreds, tens, ones, chunk_num, **kwargs):
words = []
if hundreds > 1:
words.append(get_num_element(HUNDREDS, hundreds, case='g'))
elif hundreds == 1:
words.append(get_num_element(HUNDREDS, hundreds)) # стО, not стА
if tens == 9:
words.append(get_num_element(TWENTIES, tens)) # девяностО, not А
elif tens > 1:
words.append(get_num_element(TWENTIES, tens, case='g'))
if tens == 1:
words.append(get_num_element(TENS, ones, case='g'))
elif ones > 0:
if chunk_num == 0:
w_ones = get_num_element(ONES_ORD, ones, **kwargs)
# тысячный, миллионнный и т.д., двадцатиодномиллионный
elif chunk_num > 0 and ones == 1 and tens != 1:
if tens == 0 and hundreds == 0:
w_ones = None
else:
w_ones = get_num_element(ONES, 1, gender='n')
else:
w_ones = get_num_element(ONES, ones, case='g')
if w_ones:
words.append(w_ones)
return words