1#!/usr/bin/env python3
2# -*- coding: utf-8 -*-
3
4# Скрипт предварительной обработки текста для
5# синтезатора речи RHVoice Ольги Яковлевой
6# By Capricorn2001 & vantu5z
7
8from re import sub, finditer
9
10from .templates import (samples_1, samples_2, samples_3, samples_4,
11                        units, zh_units,
12                        forms,
13                        pre_acc,
14                        i_mu, i_sr, i_zh, i_mn,
15                        r_ca, r_mn, r_mu, r_sr, r_zh,
16                        d_ca, d_mn, d_mu, d_sr, d_zh,
17                        v_ca, v_zh,
18                        t_ca, t_mn, t_mu, t_sr, t_zh,
19                        p_ca, p_mn, p_mu, p_sr, p_zh,
20                        adj_pad, mn_pad, mu_pad, sr_pad, zh_pad,
21                        greekletters, letternames)
22from .functions import (condition, cardinal, ordinal, roman2arabic,
23                        substant, feminin, daynight, fraction)
24from .words_forms import Words, M_GENDER, Z_GENDER, S_GENDER
25
26# Для определения атрибутов слов
27words = Words()
28
29
30def text_prepare(text):
31    """
32    =================================
33    Основная функция обработки текста
34    =================================
35    """
36
37    # предварительная обработка текста
38    for sample in samples_1:
39        text = sub(sample[0], sample[1], text)
40
41    # =================
42    # Единицы измерения
43    # =================
44
45    # Винительный падеж
46
47    mask = (r'\b('
48            r'(состав[авеийлотшщьюя]{2,6}|превы[сш][авеийлотшщьюя]{2,5}) (бы |)'
49            r')'
50            r'((\d+,|)(\d+) - |)(\d+,|)(\d+)_' + units)
51    for m in finditer(mask, text):
52        new = m.group(1)
53        if m.group(4):
54            if m.group(5):
55                new += decimal(m.group(5)[:-1], m.group(6), 5)
56            else:
57                if m.group(9) in zh_units:
58                    new += feminin(m.group(6), 5)
59                else:
60                    new += m.group(6)
61            new += ' - '
62        if m.group(7):
63            new += decimal(m.group(7)[:-1], m.group(8), 5) + ' '
64            new += forms[m.group(9)][2]
65        else:
66            if m.group(9) in zh_units:
67                new += feminin(m.group(8), 5)
68            else:
69                new += m.group(8)
70            new += ' ' + substant(m.group(8), m.group(9), 5)
71        text = text.replace(m.group(), new, 1)
72
73    # например: "диаметром в 2 см -> диаметром в 2 сантиметра"
74    mask = (r'\b([А-Яа-яё]{3,})'
75            r'( (ориентировочно |примерно |приблизительно |)в )'
76            r'((\d+,|)(\d+) - |)(\d+,|)(\d+)_' + units)
77    for m in finditer(mask, text):
78        if m.group(1).lower() in pre_acc:
79            new = m.group(1) + m.group(2)
80            if m.group(4):
81                if m.group(5):
82                    new += decimal(m.group(5)[:-1], m.group(6), 5)
83                else:
84                    if m.group(9) in zh_units:
85                        new += feminin(m.group(6), 5)
86                    else:
87                        new += m.group(6)
88                new += ' - '
89            if m.group(7):
90                new += decimal(m.group(7)[:-1], m.group(8), 5) + ' '
91                new += forms[m.group(9)][2]
92            else:
93                if m.group(9) in zh_units:
94                    new += feminin(m.group(8), 5)
95                else:
96                    new += m.group(8)
97                new += ' ' + substant(m.group(8), m.group(9), 5)
98            text = text.replace(m.group(), new, 1)
99
100    # Родительный падеж
101    # пример: "С 5 см до -> С пяти сантиметров до"
102    mask = (r'\b([Сс] (почти |примерно |приблизительно |плюс |минус |))'
103            r'(\d+,|)(\d+)_' + units + ' до ')
104    for m in finditer(mask, text):
105        new = m.group(1)
106        if m.group(3):
107            new += fraction(m.group(3)[:-1], m.group(4), 1)
108            new += ' ' + forms[m.group(5)][2]
109        else:
110            new += m.group(4)
111            new += ' ' + substant(m.group(4), m.group(5), 1)
112        text = text.replace(m.group(), new + ' до ', 1)
113
114    # пример: "от 1 до 4 км -> от одного до четырёх километров"
115    for m in finditer(r'\b([Оо]т |[Сс]о? )(\d+,|)(\d+)( до (\d+,|)\d+_)' + units, text):
116        if m.group(2):
117            number = fraction(m.group(2)[:-1], m.group(3), 1)
118        else:
119            number = cardinal(m.group(3), r_ca)
120            if condition(m.group(3)) and m.group(6) in zh_units:
121                number = number[:-2] + 'й'
122        new = m.group(1) + number + m.group(4) + m.group(6)
123        text = text.replace(m.group(), new, 1)
124
125    mask = (r'\b('
126            r'[Бб]олее|[Мм]енее|[Бб]ольше|[Мм]еньше|[Вв]ыше|[Нн]иже|[Дд]альше|'
127            r'[Оо]коло|[Сс]выше|[Дд]ля|[Дд]о|[Ии]з|[Оо]т|[Вв]место|'
128            r'[Вв] размере|[Бб]лиже|[Вв] течение|[Нн]ач[инаетсялоь]{2,7} с|'
129            r'[Вв]ладел[аеимухцыь]{2,5}|[Дд]остиг[авеийлнотшщюуья]{,5}|'
130            r'[Пп]ротив|[Пп]орядка|[Пп]осле'
131            r')'
132            r'( плюс | минус | )((\d+,|)(\d+)( - | или | и )'
133            r'(плюс |минус |)|)(\d+,|)(\d+)_' + units)
134    for m in finditer(mask, text):
135        if m.group(3):
136            if m.group(4):
137                prenum = fraction(m.group(4)[:-1], m.group(5), 1)
138            else:
139                prenum = cardinal(m.group(5), r_ca)
140                if condition(m.group(5)) and m.group(10) in zh_units:
141                    prenum = prenum[:-2] + 'й'
142            prenum += m.group(6) + m.group(7)
143        else:
144            prenum = ''
145        if m.group(8):
146            number = fraction(m.group(8)[:-1], m.group(9), 1) + ' ' + forms[m.group(10)][2]
147        else:
148            number = cardinal(m.group(9), r_ca)
149            if condition(m.group(9)) and m.group(10) in zh_units:
150                number = number[:-2] + 'й'
151            number += ' ' + substant(m.group(9), m.group(10), 1)
152        new = m.group(1) + m.group(2) + prenum  + number
153        text = text.replace(m.group(), new, 1)
154
155    # Дательный падеж
156    mask = (r'\b('
157            r'([Кк]|рав[нагеийлмоcуюыхья]{2,6})'
158            r'( всего | почти | примерно | приблизительно | плюс | минус | )'
159            r')'
160            r'(\d+,|)(\d+)_' + units)
161    for m in finditer(mask, text):
162        if m.group(4):
163            number = fraction(m.group(4)[:-1], m.group(5), 2) + ' ' + forms[m.group(6)][2]
164        else:
165            number = m.group(5) + ' ' + substant(m.group(5), m.group(6), 2)
166        text = text.replace(m.group(), m.group(1) + number, 1)
167    # С предлогом "по" при указании количества
168    for m in finditer(r'\b([Пп]о (\d*1(000){0,3}))_' + units, text):
169        new = m.group(1) + ' ' + substant(m.group(2), m.group(4), 2)
170        text = text.replace(m.group(), new, 1)
171
172    # Творительный падеж
173    mask = (r'\b(([Мм]ежду|[Пп]о сравнению с|[Вв]ладе[авеийлмтюшщья]{1,7}) '
174            r'(почти |приблизительно |примерно |плюс |минус |))'
175            r'((\d+,|)(\d+)'
176            r'( [-и] (почти |приблизительно |примерно |плюс |минус |))|)'
177            r'(\d+,|)(\d+)_' + units)
178    for m in finditer(mask, text):
179        new = m.group(1)
180        a = m.group(4) and not m.group(5)
181        if a and condition(m.group(6)) and m.group(11) in zh_units:
182            new += cardinal(m.group(6), t_ca)[:-2] + 'ой' + m.group(7)
183        else:
184            new += m.group(4)
185        if m.group(9):
186            new += fraction(m.group(9)[:-1], m.group(10), 3) + ' '
187            new += forms[m.group(11)][2]
188        else:
189            new += m.group(10) + ' ' + substant(m.group(10), m.group(11), 3)
190        text = text.replace(m.group(), new, 1)
191
192    # Предложный падеж
193    mask = (r'\b([Вв]|[Оо]б?|[Пп]ри)'
194            r'(( плюс | минус | )(\d+,|)(\d+)( [-и] | или )| )'
195            r'(почти |примерно |приблизительно |плюс |минус |)'
196            r'(\d+,|)(\d+)_' + units)
197    for m in finditer(mask, text):
198        if m.group(2) == ' ':
199            pre = ' '
200        else:
201            if m.group(4):
202                pre = fraction(m.group(4)[:-1], m.group(5), 4) + ' ' + forms[m.group(10)][2]
203            else:
204                pre = m.group(5)
205            pre = m.group(3) + pre + m.group(6)
206        number = m.group(7)
207        if m.group(8):
208            number += fraction(m.group(8)[:-1], m.group(9), 4) + ' ' + forms[m.group(10)][2]
209        else:
210            number += m.group(9) + ' ' + substant(m.group(9), m.group(10), 4)
211        text = text.replace(m.group(), m.group(1) + pre + number, 1)
212
213    # Предлог "по" при указании количества
214    for m in finditer(r'\b([Пп]о )(\d*[02-9]1|1)_' + units, text):
215        new = m.group(1) + m.group(2) + ' ' + substant(m.group(2), m.group(3), 2)
216        text = text.replace(m.group(), new, 1)
217
218    # Именительный
219    for m in finditer(r'\b(\d+,\d+)_' + units, text):
220        new = m.group(1) + ' ' + forms[m.group(2)][2]
221        text = text.replace(m.group(), new, 1)
222    for m in finditer(r'\b(\d+)_' + units, text):
223        new = m.group(1) + ' ' + substant(m.group(1), m.group(2))
224        text = text.replace(m.group(), new, 1)
225
226    mask = (r'('
227            r'тысяч[аимх]{,3}|'
228            r'(миллион|миллиард|триллион)(|ами|а[мх]?|ов)'
229            r')_' + units)
230    for m in finditer(mask, text):
231        new = m.group(1) + ' ' + forms[m.group(4)][1]
232        text = text.replace(m.group(), new, 1)
233
234    # Время в формате (h)h ч (m)m мин
235    for m in finditer(r'\b(\d{1,2}) ?ч ?(\d{1,2}) ?мин\b', text):
236        if condition(m.group(1)):
237            hours = ' час '
238        elif m.group(1) in ('2', '3', '4', '02', '03', '04', '22', '23', '24'):
239            hours = ' часа '
240        else:
241            hours = ' часов '
242        if condition(m.group(2)):
243            minutes = ' минута'
244        elif m.group(2) in ('2', '3', '4', '02', '03', '04', '22', '23', '24', '32', '33', '34', '42', '43', '44', '52', '53', '54'):
245            minutes = ' минуты'
246        else:
247            minutes = ' минут'
248        new = m.group(1) + hours + feminin(m.group(2)) + minutes
249        text = text.replace(m.group(), new, 1)
250
251    # Время в формате (ч)ч:мм/(ч)ч.мм
252
253    for m in finditer(r'\b(([Вв]|[Нн]а) \d{1,2})[:.](\d\d)\b', text):
254        minutes = ' ' + feminin(m.group(3), 5)
255        text = text.replace(m.group(), m.group(1) + minutes, 1)
256
257    for m in finditer(r'\b([Кк] )(\d{1,2}):(\d\d)\b', text):
258        hours = cardinal(m.group(2), d_ca)
259        minutes = cardinal(m.group(3), d_ca)
260        if m.group(3) == '00':
261            minutes = '00'
262        else:
263            if m.group(3)[0] == '0':
264                minutes = '0_' + minutes
265            minutes = feminin(minutes, 2)
266        new = m.group(1) + hours + ' ' + minutes
267        text = text.replace(m.group(), new, 1)
268
269    mask = (r'\b([Дд]о |[Пп]осле |[Оо]коло |[Сс] )'
270            r'(\d{1,2})[:.](\d\d)\b')
271    for m in finditer(mask, text):
272        hours = cardinal(m.group(2), r_ca)
273        minutes = cardinal(m.group(3), r_ca)
274        if m.group(3) == '00':
275            minutes = '00'
276        else:
277            if m.group(3)[0] == '0':
278                minutes = '0_' + minutes
279            minutes = feminin(minutes, 1)
280        new = m.group(1) + hours + ' ' + minutes
281        text = text.replace(m.group(), new, 1)
282
283    # =======================
284    # Порядковые числительные
285    # =======================
286
287    # Чтение римских цифр в датах
288
289    mask = (r'\b(([IVX]+)( (-|или|и|по)( в (конце |начале |середине |)| ))|)'
290            r'([IVX]+)( в?в\.)')
291    for m in finditer(mask, text):
292        if m.group(1):
293            pre = roman2arabic(m.group(2)) + m.group(3)
294        else: pre = ''
295        new = pre + roman2arabic(m.group(7)) + m.group(8)
296        text = text.replace(m.group(), new, 1)
297
298    mask = (r'\b([IVX]+)( [-и] )([IVX]+)'
299            r'( век(ами?|ах?|ов)| (тысяче|сто)лети(ями?|ях?|й))\b')
300    for m in finditer(mask, text):
301        ending = m.group(4)[-1]
302        if ending == 'а':
303            num1 = ordinal(roman2arabic(m.group(1)), i_mu)
304            num2 = ordinal(roman2arabic(m.group(3)), i_mu)
305        elif ending == 'я':
306            num1 = ordinal(roman2arabic(m.group(1)), i_sr)
307            num2 = ordinal(roman2arabic(m.group(3)), i_sr)
308        elif ending == 'в' or ending == 'й':
309            num1 = ordinal(roman2arabic(m.group(1)), r_mu)
310            num2 = ordinal(roman2arabic(m.group(3)), r_mu)
311        elif ending == 'м':
312            num1 = ordinal(roman2arabic(m.group(1)), d_mu)
313            num2 = ordinal(roman2arabic(m.group(3)), d_mu)
314        elif ending == 'и':
315            num1 = ordinal(roman2arabic(m.group(1)), t_mu)
316            num2 = ordinal(roman2arabic(m.group(3)), t_mu)
317        else:
318            num1 = ordinal(roman2arabic(m.group(1)), p_mu)
319            num2 = ordinal(roman2arabic(m.group(3)), p_mu)
320        text = text.replace(m.group(), num1 + m.group(2) + num2 + m.group(4), 1)
321
322    for m in finditer(r'\b([Сс]о? )([IVX]+) по ', text):
323        new = m.group(1) + roman2arabic(m.group(2)) + '-го по '
324        text = text.replace(m.group(), new, 1)
325
326    # применение шаблонов
327    for sample in samples_2:
328        text = sub(sample[0], sample[1], text)
329
330    # например: "во 2 окне -> во втором окне"
331    mask = (r'\b([Вв]о?|[Нн]а|[Оо]б?|[Пп]ри) '
332            r'(\d*[02-9]|\d*1\d) ([а-яё]+)\b')
333    for m in finditer(mask, text):
334        attr = words.get_attr(m.group(3))
335        number = ''
336        if attr.have([S_GENDER, M_GENDER], False, [5]):
337            number = ordinal(m.group(2), p_mu)
338        elif attr.have([Z_GENDER], False, [2, 5]):
339            number = ordinal(m.group(2), p_zh)
340        if number:
341            new = m.group(1) + ' ' + number + ' ' + m.group(3)
342            text = text.replace(m.group(), new, 1)
343
344    # например: "со 2 примером -> со вторым примером"
345    mask = (r'\b([Сс]о? )(\d*1\d|\d*[02-9]?[02-9]) ([а-яё]+)\b')
346    for m in finditer(mask, text):
347        number = ''
348        attr = words.get_attr(m.group(3))
349        if attr.have([M_GENDER, S_GENDER], False, [4]):
350            number = ordinal(m.group(2), t_mu)
351        elif attr.have([Z_GENDER], False, [2, 4, 5]):
352            number = ordinal(m.group(2), t_zh)
353        if number:
354            new = m.group(1) + number + ' ' + m.group(3)
355            text = text.replace(m.group(), new, 1)
356
357    # например: "на 8-м этаже -> на восьмом этаже"
358    for m in finditer(r'(\d+)-(м|й) ([а-яё]+)\b', text):
359        number = ''
360        attr = words.get_attr(m.group(3))
361        if m.group(2) == 'м':
362            if attr.have([M_GENDER, S_GENDER], None, [4]):
363                number = ordinal(m.group(1), t_mu)
364            elif attr.have([M_GENDER, S_GENDER], None, [5]):
365                number = ordinal(m.group(1), p_mu)
366        elif m.group(2) == 'й':
367            if attr.have([Z_GENDER], False, [2, 4, 5]):
368                number = ordinal(m.group(1), t_zh)
369        if number:
370            text = text.replace(m.group(), number + ' ' + m.group(3), 1)
371
372    for m in finditer(r'(\d+)-е (([а-яё]+[ео]е ){,2}([а-яё]+[ео]))\b', text):
373        if words.have(m.group(4), [S_GENDER], False, [0, 3]):
374            new = ordinal(m.group(1), i_sr) + ' ' + m.group(2)
375            text = text.replace(m.group(), new, 1)
376
377    for m in finditer(r'\b(\d*11|\d*[05-9]) ([а-яё]+)\b', text):
378        attr = words.get_attr(m.group(2))
379        if attr.have([M_GENDER], False, [3]) and not attr.have(case=[0]):
380            new = ordinal(m.group(1), r_mu) + ' ' + m.group(2)
381            text = text.replace(m.group(), new, 1)
382
383    for m in finditer(r'\b(\d*11|\d*[02-9]) ([а-яё]+)\b', text):
384        if words.have(m.group(2), [Z_GENDER], False, [3]):
385            new = ordinal(m.group(1), r_mu)[:-3] + 'ую ' + m.group(2)
386            text = text.replace(m.group(), new, 1)
387
388#    for m in finditer(r'(\d+)-ю ([а-яё]+)\b', text):
389#        if words.have(m.group(2), [Z_GENDER], False, [3]):
390#            new = ordinal(m.group(1), v_zh) + ' ' + m.group(2)
391#            text = text.replace(m.group(), new)
392
393    mask = (r'\b(?<!.)(?<!,)(?<!:)(\d*[02-9][05-9]|\d*1\d|[5-9]) ([а-яё]+)\b')
394    for m in finditer(mask, text):
395        number = ''
396        attr = words.get_attr(m.group(2))
397        if  attr.have([M_GENDER, S_GENDER], False, [1]):
398            number = ordinal(m.group(1), r_mu)
399        if  attr.have([Z_GENDER], False, [1]):
400            number = ordinal(m.group(1), r_zh)
401        if number:
402            new = number + ' ' + m.group(2)
403            text = text.replace(m.group(), new, 1)
404
405    for sample in samples_3:
406        for m in finditer(sample[0], text):
407            text = text.replace(m.group(), eval(sample[1]), 1)
408
409    # Прилагательные, в состав которых входят числительные (3-кратный и т.п.)
410    for m in finditer(r'\b((\d+) - |)(\d+)-([а-яё]{5,})\b', text):
411        if m.group(1) == '': pre = ''
412        else:
413            if m.group(2)[-3:] == '000':
414                pre = cardinal(m.group(2)[:-3], r_ca) + 'тысяче - '
415            else: pre = cardinal(m.group(2), r_ca) + ' - '
416        if m.group(3)[-3:] == '000':
417            num = cardinal(m.group(3)[:-3], r_ca) + 'тысяче'
418        else: num = cardinal(m.group(3), r_ca)
419        num = pre + num
420        num = sub('ста', 'сто', num)
421        num = sub(r'(одной тысячи|одноготысяче)', 'тысяче', num)
422        num = sub(r'\bодного', 'одно', num)
423        text = text.replace(m.group(), num + '-' + m.group(4), 1)
424
425    # Количественные числительные
426
427    # Родительный падеж
428    mask = (r'\b([Оо]т|[Сс])'
429            r'( почти | примерно | приблизительно | плюс | минус | )'
430            r'((\d+,|)(\d+)( [-и] | или )|)(\d+,|)(\d+)'
431            r'('
432            r' до( почти | примерно | приблизительно | плюс | минус | )'
433            r'((\d+,|)\d+( [-и] | или )|)(\d+,|)\d+'
434            r'( ([а-яё]+([иы]х|[ео]й|[ео]го) |и более |и менее |)([а-яё]+)|)'
435            r')\b')
436    for m in finditer(mask, text):
437        if m.group(3):
438            if m.group(4):
439                pre = fraction(m.group(4)[:-1], m.group(5), 1)
440            else:
441                pre = cardinal(m.group(5), r_ca)
442                if pre[-6:] == 'одного' and m.group(18) is not None:
443                    if words.have(m.group(18), [Z_GENDER], None, [1]):
444                        pre = pre[:-2] + 'й'
445                    elif m.group(18) == 'суток':
446                        pre = pre[:-3] + 'их'
447            pre += m.group(6)
448        else:
449            pre = ''
450        if m.group(7):
451            number = fraction(m.group(7)[:-1], m.group(8), 1)
452        else:
453            number = cardinal(m.group(8), r_ca)
454        if number[-6:] == 'одного' and m.group(18) is not None:
455            if words.have(m.group(18), [Z_GENDER], None, [1]):
456                number = number[:-2] + 'й'
457            elif m.group(18) == 'суток':
458                number = number[:-3] + 'их'
459        new = m.group(1) + m.group(2) + pre + number + m.group(9)
460        text = text.replace(m.group(), new, 1)
461
462    # Родительный падеж второго числительного в конструкции
463    # "числительное + существительное + вместо/из/против + числительное"
464    mask = (r'\b((\d+ )([а-яё]{3,})( вместо | из | против ))(\d+,|)(\d+)\b')
465    for m in finditer(mask, text):
466        attr = words.get_attr(m.group(3))
467        if m.group(5):
468            number = decimal(m.group(5)[:-1], m.group(6), 1)
469        else:
470            number = cardinal(m.group(6), r_ca)
471            if condition(m.group(6)) and attr.have([Z_GENDER]):
472                number = number[:-2] + 'й'
473            elif number[-6:] == 'одного' and m.group(3) in ('сутки', 'суток', 'суткам', 'сутками','сутках'):
474                number = number[:-3] + 'их'
475        new = m.group(1) + number
476        text = text.replace(m.group(), new, 1)
477
478    mask = (r'\b('
479            r'[Бб]олее|[Мм]енее|[Бб]ольше|[Мм]еньше|[Вв]ыше|[Нн]иже|[Дд]альше|'
480            r'[Дд]ороже|[Дд]ешевле|[Оо]коло|[Сс]выше|[Сс]реди|[Дд]ля|[Дд]о|'
481            r'[Ии]з|[Оо]т|[Бб]ез|[Уу]|[Вв]место|[Вв] возрасте|[Вв] размере|'
482            r'[Бб]лиже|[Вв] пределах|[Вв] течение|[Нн]а протяжении|'
483            r'[Нн]ач[инаетялсьо]{2,7} с|[Пп]орядка|[Пп]осле|[Пп]ротив|'
484            r'[Дд]остиг[авеийлнотшщюуья]{,5}|[Вв]ладел[аеимухцыь]{2,5}|'
485            r'[Сс]тарше|[Мм]оложе|не превы[шаеситьло]{3,4}'
486            r')'
487            r'( всех | следующих | целых | примерно | приблизительно '
488            r'| почти | плюс | минус | )'
489            r'((\d+,|)(\d+)( - | или )|)(\d+,|)(\d+)'
490            r'( ([а-яё]+([иы]х|[ео]й|[ео]го) |и более |и менее |)'
491            r'([а-яё]{3,})|)\b')
492    for m in finditer(mask, text):
493        if m.group(3):
494            if m.group(4):
495                pre = fraction(m.group(4)[:-1], m.group(5), 1)
496            else:
497                pre = cardinal(m.group(5), r_ca)
498            if condition(m.group(5)) and m.group(12) is not None:
499                attr = words.get_attr(m.group(12))
500                if m.group(9) and attr.have([Z_GENDER], None, [1]):
501                    pre = pre[:-2] + 'й'
502                elif m.group(12) == 'суток':
503                    pre = pre[:-3] + 'их'
504            pre += m.group(6)
505        else:
506            pre = ''
507        if m.group(7):
508            number = fraction(m.group(7)[:-1], m.group(8), 1)
509        else:
510            number = cardinal(m.group(8), r_ca)
511            if m.group(12):
512                attr = words.get_attr(m.group(12))
513                if condition(m.group(8)) and attr.have(Z_GENDER, False, [1]):
514                    number = number[:-2] + 'й'
515                elif m.group(12) == 'суток' and number[-6:] == 'одного':
516                    number = number[:-3] + 'их'
517        new = m.group(1) + m.group(2) + pre + number + m.group(9)
518        text = text.replace(m.group(), new, 1)
519
520    # Предлог "с" + родительный падеж
521    mask = (r'\b([Сс]о? )((\d+)( [-и] | или )|)(\d+) ([а-яё]+)\b')
522    for m in finditer(mask, text):
523        attr = words.get_attr(m.group(6))
524        if attr.have(None, None, [1]):
525            if m.group(2):
526                prenum = cardinal(m.group(3), r_ca)
527                if condition(m.group(3)) and attr.have([Z_GENDER], None, [1]):
528                    prenum = prenum[:-2] + 'й'
529                prenum += m.group(4)
530            else:
531                prenum = ''
532            prenum = m.group(1) + prenum
533            number = cardinal(m.group(5), r_ca)
534            if attr.have([Z_GENDER], False, [1]):
535                number = number[:-2] + 'й'
536            new = prenum + number + ' ' + m.group(6)
537            text = text.replace(m.group(), new, 1)
538
539    mask = (r'(\A|\(| )((\d+) - |)(1|\d*[02-9]1)'
540            r'(( [а-яё]+[ео]го | )([а-яё]+))\b')
541    for m in finditer(mask, text):
542        attr = words.get_attr(m.group(7))
543        a = attr.have([M_GENDER, S_GENDER], False, [1])
544        b = attr.have([M_GENDER], False, [3])
545        if a and not b:
546            number = cardinal(m.group(4), r_ca)
547            if m.group(2) == '':
548                pre = ''
549            else:
550                pre = cardinal(m.group(3), r_ca)
551                pre += ' - '
552            new = m.group(1) + pre + number + m.group(5)
553            text = text.replace(m.group(), new, 1)
554
555    mask = (r'(\A|\(| )((\d+)( [-и] | или )|)(\d*[02-9][234]|[234])'
556            r'(( [а-яё]+[иы]х | )([а-яё]+))\b(.)')
557    for m in finditer(mask, text):
558        attr = words.get_attr(m.group(8))
559        a = attr.have([M_GENDER, S_GENDER, Z_GENDER], True, [1])
560        b = attr.have([M_GENDER], True, [3])
561        if a and not b:
562            if m.group(2) == '':
563                number = ''
564            else:
565                number = cardinal(m.group(3), r_ca) + m.group(4)
566                if attr.have(gender=Z_GENDER) and number[-2:] == 'го':
567                    number = number[:-2] + 'й'
568            new = (m.group(1) + number + cardinal(m.group(5), r_ca) +
569                   m.group(6) + m.group(9))
570            text = text.replace(m.group(), new, 1)
571
572    # Творительный падеж
573    # Исключение
574    mask = (r'\b((состав(ил[аио]?|[ия]т|ля[ею]т)|потеря(л[аио]?|[ею]т)) \d+) '
575            r'(погибшими|ранеными|убитыми)'
576            r'(( и \d+) (погибшими|ранеными|убитыми)|)\b')
577    for m in finditer(mask, text):
578        if m.group(6):
579            new = m.group(7) + '_' + m.group(8)
580        else:
581            new = ''
582        new = m.group(1) + '_' + m.group(5) + new
583        text = text.replace(m.group(), new, 1)
584
585    mask = (r'(?<!\d,)\b('
586            r'(\d+)'
587            r'( - | или | и (почти |приблизительно |примерно |плюс |минус |))|'
588            r')'
589            r'(\d+) '
590            r'([а-яё]+([аиыья]ми|[ео]м|[еиоы]й|ью))\b')
591    for m in finditer(mask, text):
592        if m.group(1):
593            pre = cardinal(m.group(2), t_ca)
594            if condition(m.group(2)):
595                a = words.have(m.group(6), [Z_GENDER], False, [4])
596                b = words.have(m.group(6)[:-2], [Z_GENDER], False, [0])
597                c = words.have(m.group(6)[:-3] + 'ь', [Z_GENDER], False, [0])
598                if a or b or c:
599                    pre = pre[:-2] + 'ой'
600            pre += m.group(3)
601        else:
602            pre = ''
603        number = ''
604        if condition(m.group(5)):
605            attr = words.get_attr(m.group(6))
606            if attr.have([M_GENDER, S_GENDER], False, [4]):
607                number = cardinal(m.group(5), t_ca)
608            elif attr.have([Z_GENDER], False, [4]):
609                number = cardinal(m.group(5), t_ca)[:-2] + 'ой'
610            elif m.group(6) == 'сутками':
611                number = cardinal(m.group(5), t_ca) + 'и'
612        elif m.group(6)[-2:] == 'ми':
613            number = cardinal(m.group(5), t_ca)
614        if number:
615            new = pre + number + ' ' + m.group(6)
616            text = text.replace(m.group(), new, 1)
617
618    # Предлоги творительного падежа
619
620    mask = (r'\b(([Мм]ежду|[Нн]ад|[Пп]еред|[Пп]о сравнению с) '
621            r'(почти |приблизительно |примерно |плюс |минус |))'
622            r'((\d+,|)(\d+)'
623            r'( [-и] | или )'
624            r'(почти |приблизительно |примерно |плюс |минус |)|)'
625            r'(\d+,|)(\d+)\b')
626    for m in finditer(mask, text):
627        pre = m.group(1)
628        if m.group(4):
629            if m.group(5):
630                pre += fraction(m.group(5)[:-1], m.group(6), 3)
631            else:
632                pre += cardinal(m.group(6), t_ca)
633            pre = pre + m.group(7) + m.group(8)
634        if m.group(9):
635            number = fraction(m.group(9)[:-1], m.group(10), 3)
636        else:
637            number = cardinal(m.group(10), t_ca)
638        text = text.replace(m.group(), pre + number, 1)
639
640    # Предложный падеж
641    mask = (r'\b([Вв]|[Нн]а|[Оо]б?|[Пп]ри)'
642            r'('
643            r'( почти | примерно | приблизительно | плюс | минус | )'
644            r'(\d+)( [-и] | или )| '
645            r')'
646            r'(почти |примерно |приблизительно |плюс |минус |)(\d+)'
647            r'( ([а-яё]+([иы]х|[ео][йм]) |)([а-яё]+([ая]х|е|и|у)))\b')
648    for m in finditer(mask, text):
649        if m.group(2) == ' ':
650            pre = ' '
651        else:
652            pre = m.group(3) + cardinal(m.group(4), p_ca)
653            a = words.have(m.group(11), None, False, [2, 5])
654            b = words.have(m.group(11)[:-1] + 'м', [Z_GENDER], True, [2])
655            if condition(m.group(4)) and (a or b):
656                pre = pre[:-1] + 'й'
657            elif m.group(11) == 'сутках':
658                pre = pre[:-2] + 'их'
659            pre += m.group(5)
660        number = ''
661        if m.group(12) == 'ах' or m.group(12) == 'ях':
662            number = cardinal(m.group(7), p_ca)
663        if condition(m.group(7)):
664            attr = words.get_attr(m.group(11))
665            if attr.have([M_GENDER, S_GENDER], False, [5]):
666                number = cardinal(m.group(7), p_ca)
667            elif attr.have([Z_GENDER], False, [2, 5]):
668                number = cardinal(m.group(7), p_ca)[:-1] + 'й'
669            elif m.group(11) == 'сутках':
670                number = cardinal(m.group(7), p_ca)[:-2] + 'их'
671        elif m.group(12) == 'ах' or m.group(12) == 'ях':
672            number = cardinal(m.group(7), p_ca)
673        if number:
674            new = m.group(1) + pre + m.group(6) + number + m.group(8)
675            text = text.replace(m.group(), new, 1)
676
677    for m in finditer(r'\b(\d+) ([а-яё]+)\b', text):
678        attr = words.get_attr(m.group(2))
679        a = attr.have(None, True, [5])
680        b = condition(m.group(1))
681        c = attr.have([M_GENDER, S_GENDER], False, [5])
682        if a or (b and c):
683            new = cardinal(m.group(1), p_ca) + ' ' + m.group(2)
684            text = text.replace(m.group(), new, 1)
685
686    # Предлоги предложного падежа
687    mask = (r'\b([Оо]б?|[Пп]ри)'
688            r'( (\d+)( [-и] | или )| )(\d+)\b')
689    for m in finditer(mask, text):
690        number = ' '
691        if m.group(2) != ' ':
692            number += cardinal(m.group(3), p_ca) + m.group(4)
693        new = m.group(1) + number + cardinal(m.group(5), p_ca)
694        text = text.replace(m.group(), new, 1)
695
696    # Винительный падеж
697
698    mask = (r'\b([Вв]|[Нн]а|[Зз]а|[Пп]ро|[Чч]ерез|состав[аеилотя]{2,4})'
699            r'( (\d+)( -| или)|) (\d+)'
700            r'(( [а-яё]+([ая]я|[ую]ю|[ео]е|[иы][йх]) | )([а-яё]+))\b')
701    for m in finditer(mask, text):
702        if m.group(2):
703            pre = cardinal(m.group(3), v_ca)
704            if pre[-3:] == 'дин':
705                attr = words.get_attr(m.group(9))
706                if attr.have([M_GENDER], False, [3]):
707                    pre = pre[:-2] + 'ного'
708                elif attr.have([Z_GENDER], False, [3]):
709                    pre = pre[:-2] + 'ну'
710                elif attr.have([S_GENDER], False, [0, 3]):
711                    pre = pre[:-2] + 'но'
712            pre += m.group(4) + ' '
713        else:
714            pre = ''
715        number = cardinal(m.group(5), v_ca)
716        if number[-3:] == 'дин':
717            attr = words.get_attr(m.group(9))
718            if attr.have([M_GENDER], False, [3]):
719                number = number[:-2] + 'ного'
720            elif attr.have([Z_GENDER], False, [3]):
721                number = number[:-2] + 'ну'
722            elif attr.have([S_GENDER], False, [0, 3]):
723                number = number[:-2] + 'но'
724        new = m.group(1) + ' ' + pre + number + m.group(6)
725        text = text.replace(m.group(), new, 1)
726
727    # Женский род (иминетельный/винительный падежи)
728    mask = (r'(\A|\(| )(((\d+)( - | или | и ))|)(\d+,|)(\d+)'
729            r'(( [а-яё]+([ая]я|[иы][ех])|) ([а-яё]+))')
730    for m in finditer(mask, text):
731        attr = words.get_attr(m.group(11))
732        a = attr.have([Z_GENDER], None, [1])
733        b = attr.have([Z_GENDER], False, [0])
734        if (a or b):
735            new = m.group(1)
736            if m.group(2):
737                new += feminin(m.group(4)) + m.group(5)
738            if m.group(6):
739                new += m.group(6) + m.group(7) + m.group(8)
740            else:
741                new += feminin(m.group(7)) + m.group(8)
742            text = text.replace(m.group(), new, 1)
743
744#    for m in finditer(r'\b(\d*[02-9]1|1)(( [а-яё]+[ео]го | )([а-яё]+))\b', text):
745#        if (words.have(m.group(4), [M_GENDER], False, [3])
746#                and not words.have(m.group(4), [M_GENDER], False, [0])):
747#            new = cardinal(m.group(1), v_ca)[:-2] + 'ного' + m.group(2)
748#            text = text.replace(m.group(), new, 1)
749
750    # Вин. пад. муж. рода числительных, оканчивающихся на 1 кроме 11
751    mask = (r'(\s|\A|\(| )((\d+) - |)(1|\d*[02-9]1)'
752            r'(( [а-яё]+[ео]го | )([а-яё]+))\b')
753    for m in finditer(mask, text):
754        attr = words.get_attr(m.group(7))
755        if attr.have([M_GENDER], False, [3]):
756            number = cardinal(m.group(4), v_ca)[:-2] + 'ного'
757            if m.group(2) == '':
758                pre = ''
759            else:
760                pre = cardinal(m.group(3), v_ca)
761                if condition(m.group(3)):
762                    pre = pre[:-2] + 'ного'
763                elif pre[-3:] == 'два':
764                    pre = pre[:-1] + 'ух'
765                elif pre[-3:] == 'три' or pre[-3:] == 'ыре':
766                    pre = pre[:-1] + 'ёх'
767                pre += ' - '
768            new = m.group(1) + pre + number + m.group(5)
769            text = text.replace(m.group(), new, 1)
770    # Вин. пад. жен. рода
771    for m in finditer(r'\b(\d*[02-9]1|1)(( [а-яё]+[ую]ю | )([а-яё]+))', text):
772        if words.have(m.group(4), [Z_GENDER], False, [3]):
773            new = cardinal(m.group(1), v_ca)[:-2] + 'ну' + m.group(2)
774            text = text.replace(m.group(), new, 1)
775
776    mask = (r'\b(\d*[02-9][2-4]|[2-4])'
777            r'(( [а-яё]+[иы]х | )([а-яё]+))')
778    for m in finditer(mask, text):
779        if words.have(m.group(4), [M_GENDER], True, [3]):
780            number = cardinal(m.group(1), v_ca)
781            if number[-3:] == 'два':
782                number = number[:-1] + 'ух'
783            else:
784                number = number[:-1] + 'ёх'
785            text = text.replace(m.group(), number + m.group(2), 1)
786
787    for m in finditer(r'\b([Вв] )(\d+)( раз[а]?)\b', text):
788        new = m.group(1) + cardinal(m.group(2), v_ca) + m.group(3)
789        text = text.replace(m.group(), new, 1)
790
791    # Средний род (именительный/винительный падежи)
792    for m in finditer(r'\b(\d*[02-9]1|1) (([а-яё]+[ео]е |)([а-яё]+[ео]))\b', text):
793        if words.have(m.group(4), [S_GENDER], False, [0, 3]):
794            if len(m.group(1)) > 1:
795                if int(m.group(1)[:-1]) != 0:
796                    number = m.group(1)[:-1] + '0_одно'
797                else:
798                    number = m.group(1)[:-1] + '_одно'
799            else:
800                number = m.group(1)[:-1] + 'одно'
801            text = text.replace(m.group(), number + ' ' + m.group(2), 1)
802
803    # Дательный падеж
804    mask = (r'(?<!-)\b((\d+)( [-и] | или )|)(\d+)'
805            r'(( [а-яё]+([иы]м|[ео]му) | )([а-яё]+([аиыя]м|у|ю|е)))\b')
806    for m in finditer(mask, text):
807        if m.group(1) == '':
808            pre = ''
809        else:
810            pre = ' ' + cardinal(m.group(2), d_ca)
811            attr = words.get_attr(m.group(8))
812            a = attr.have([Z_GENDER], None, [2])
813            b = attr.have([Z_GENDER], False, [5])
814            if condition(m.group(2)) and (a or b):
815                pre = pre[:-2] + 'й'
816            elif m.group(8) == 'суткам':
817                pre = pre[:-3] + 'им'
818            pre += m.group(3)
819        number = ''
820        if condition(m.group(4)):
821            if words.have(m.group(8), [M_GENDER, S_GENDER], False, [2]):
822                number = cardinal(m.group(4), d_ca)
823            elif words.have(m.group(8), [Z_GENDER], False, [2, 5]):
824                number = cardinal(m.group(4), d_ca)[:-2] + 'й'
825            elif m.group(8) == 'суткам':
826                number = cardinal(m.group(4), d_ca)[:-3] + 'им'
827        elif m.group(9) == 'ам' or m.group(9) == 'ям':
828            number = cardinal(m.group(4), d_ca)
829        if number:
830            text = text.replace(m.group(), pre + number + m.group(5), 1)
831
832    # Предлоги дательного падежа
833    mask = (r'\b((\A|\n|\(| )[Кк]|рав[нагеийлмоcуюыхья]{2,6})'
834            r'( (\d+)( [-и] | или )| )(\d+)\b')
835    for m in finditer(mask, text):
836        number = ' '
837        if m.group(3) != ' ':
838            number += cardinal(m.group(4), d_ca) + m.group(5)
839        new = m.group(1) + number + cardinal(m.group(6), d_ca)
840        text = text.replace(m.group(), new, 1)
841
842    # Существует только во множественном числе
843    for m in finditer(r'\b((\d+) - |)((\d+) (сутки|суток))', text):
844        if m.group(1):
845            pre = daynight(m.group(2), m.group(5)) + '-'
846        else:
847            pre = ''
848        new = pre + daynight(m.group(4), m.group(5)) + ' ' + m.group(5)
849        text = text.replace(m.group(), new, 1)
850
851    # Предлог "по" при указании количества
852    for m in finditer(r'\b([Пп]о )(\d*1(000){1,3})\b', text):
853        new = m.group(1) + cardinal(m.group(2), d_ca)
854        text = text.replace(m.group(), new, 1)
855
856    # Десятичные дроби в именительном падеже
857    for m in finditer(r'\b(\d+),(\d+)(\b|\Z)', text):
858        new = fraction(m.group(1), m.group(2)) + m.group(3)
859        text = text.replace(m.group(), new, 1)
860
861    # Например: "все небо" -> "всё небо"
862    for m in finditer(r'\b([Вв]с)е ([а-яё]+)\b', text):
863        if words.have(m.group(2), [S_GENDER], False, [0, 3]):
864            text = text.replace(m.group(), m.group(1) + 'ё ' + m.group(2), 1)
865
866    # Буквы греческого алфавита
867    for j in greekletters:
868        text = text.replace(j, letternames[greekletters.index(j)//2], 1)
869
870    # Окончателная обработка
871    for sample in samples_4:
872        text = sub(sample[0], sample[1], text)
873
874    return text
875