lesya 0.0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- lesya/__init__.py +3 -0
- lesya/language/__init__.py +0 -0
- lesya/language/case_helpers.py +138 -0
- lesya/language/name_case.py +409 -0
- lesya/language/name_constants.py +131 -0
- lesya/language/nc_core.py +371 -0
- lesya-0.0.1.dist-info/LICENSE +19 -0
- lesya-0.0.1.dist-info/METADATA +109 -0
- lesya-0.0.1.dist-info/RECORD +11 -0
- lesya-0.0.1.dist-info/WHEEL +5 -0
- lesya-0.0.1.dist-info/top_level.txt +1 -0
lesya/__init__.py
ADDED
|
File without changes
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
class Gender:
|
|
2
|
+
"""
|
|
3
|
+
A class containing constants for gender determination
|
|
4
|
+
"""
|
|
5
|
+
MALE = 'male'
|
|
6
|
+
FEMALE = 'female'
|
|
7
|
+
MAN = 'male'
|
|
8
|
+
WOMAN = 'female'
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class CaseUA:
|
|
12
|
+
"""
|
|
13
|
+
A class containing constants for grammatical cases in Ukrainian
|
|
14
|
+
"""
|
|
15
|
+
NOMINATIVE = "nominative" # "Називний відмінок (Nominative)" # Nominative case
|
|
16
|
+
GENITIVE = "genitive" # "Родовий відмінок (Genitive)" # Genitive case
|
|
17
|
+
DATIVE = "dative" # "Давальний відмінок (Dative)" # Dative case
|
|
18
|
+
ACCUSATIVE = "accusative" # "Знахідний відмінок (Accusative)" # Accusative case
|
|
19
|
+
INSTRUMENTAL = "instrumental" # "Орудний відмінок (Instrumental)" # Instrumental case
|
|
20
|
+
PREPOSITIONAL = "prepositional" # "Місцевий відмінок (Prepositional)" # Prepositional case
|
|
21
|
+
VOCATIVE = "vocative" # "Кличний відмінок (Vocative)" # Vocative case
|
|
22
|
+
|
|
23
|
+
@classmethod
|
|
24
|
+
def __getitem__(cls, number: int):
|
|
25
|
+
if number in range(7):
|
|
26
|
+
return {0: cls.NOMINATIVE,
|
|
27
|
+
1: cls.GENITIVE,
|
|
28
|
+
2: cls.DATIVE,
|
|
29
|
+
3: cls.ACCUSATIVE,
|
|
30
|
+
4: cls.INSTRUMENTAL,
|
|
31
|
+
5: cls.PREPOSITIONAL,
|
|
32
|
+
6: cls.VOCATIVE}.get(number)
|
|
33
|
+
raise TypeError("The number must be in the range from 0 to 6")
|
|
34
|
+
|
|
35
|
+
def all(self, lang='en'):
|
|
36
|
+
if lang == 'en':
|
|
37
|
+
return [self.NOMINATIVE, self.GENITIVE, self.DATIVE, self.ACCUSATIVE, self.INSTRUMENTAL, self.PREPOSITIONAL,
|
|
38
|
+
self.VOCATIVE]
|
|
39
|
+
elif lang == 'ua':
|
|
40
|
+
return ['називний', 'родовий', 'давальний', 'знахідний', 'орудний', 'місцевий', 'кличний']
|
|
41
|
+
|
|
42
|
+
@staticmethod
|
|
43
|
+
def count():
|
|
44
|
+
return 7
|
|
45
|
+
|
|
46
|
+
@classmethod
|
|
47
|
+
def __len__(cls):
|
|
48
|
+
return cls.count()
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class NamePart:
|
|
52
|
+
FIRSTNAME = 'firstname'
|
|
53
|
+
LASTNAME = 'lastname'
|
|
54
|
+
PATRONYMIC = 'patronymic'
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class NCLNameCaseWord:
|
|
58
|
+
"""
|
|
59
|
+
A class representing a word with additional properties and methods for handling name cases and gender determination.
|
|
60
|
+
"""
|
|
61
|
+
def __init__(self, word):
|
|
62
|
+
self.word = word.lower()
|
|
63
|
+
self.word_orig = word
|
|
64
|
+
self.name_part = None
|
|
65
|
+
self.gender_man = 0
|
|
66
|
+
self.gender_woman = 0
|
|
67
|
+
self.gender_solved = None
|
|
68
|
+
# mask to define origin capital and string letters: X, or x
|
|
69
|
+
self.letter_mask = ''
|
|
70
|
+
# array of cases for the word
|
|
71
|
+
self.name_cases = []
|
|
72
|
+
# self.rule = 0
|
|
73
|
+
self.create_mask(word)
|
|
74
|
+
|
|
75
|
+
def create_mask(self, word):
|
|
76
|
+
"""
|
|
77
|
+
Generating mask for the word
|
|
78
|
+
"""
|
|
79
|
+
for letter in word:
|
|
80
|
+
if letter.isupper():
|
|
81
|
+
self.letter_mask += 'X'
|
|
82
|
+
else:
|
|
83
|
+
self.letter_mask += 'x'
|
|
84
|
+
|
|
85
|
+
def return_mask(self):
|
|
86
|
+
""" Convert the word to the capital / small letter format according to its mask
|
|
87
|
+
"""
|
|
88
|
+
mask_length = len(self.letter_mask)
|
|
89
|
+
for i, case in enumerate(self.name_cases):
|
|
90
|
+
case_length = len(case)
|
|
91
|
+
max_len = min(case_length, mask_length)
|
|
92
|
+
new_case = ''
|
|
93
|
+
for j in range(max_len):
|
|
94
|
+
letter = case[j]
|
|
95
|
+
if self.letter_mask[j] == 'X':
|
|
96
|
+
letter = letter.upper()
|
|
97
|
+
new_case += letter
|
|
98
|
+
new_case += case[max_len:case_length]
|
|
99
|
+
self.name_cases[i] = new_case
|
|
100
|
+
|
|
101
|
+
def set_name_cases(self, name_cases, is_return_mask=True):
|
|
102
|
+
self.name_cases = name_cases
|
|
103
|
+
if is_return_mask:
|
|
104
|
+
self.return_mask()
|
|
105
|
+
|
|
106
|
+
def get_name_cases(self):
|
|
107
|
+
return self.name_cases
|
|
108
|
+
|
|
109
|
+
def gender(self):
|
|
110
|
+
if not self.gender_solved:
|
|
111
|
+
if self.gender_man >= self.gender_woman:
|
|
112
|
+
self.gender_solved = Gender.MALE
|
|
113
|
+
else:
|
|
114
|
+
self.gender_solved = Gender.FEMALE
|
|
115
|
+
return self.gender_solved
|
|
116
|
+
|
|
117
|
+
def set_gender(self, man, woman):
|
|
118
|
+
self.gender_man = man
|
|
119
|
+
self.gender_woman = woman
|
|
120
|
+
|
|
121
|
+
def set_true_gender(self, gender):
|
|
122
|
+
self.gender_solved = gender
|
|
123
|
+
|
|
124
|
+
def get_gender(self):
|
|
125
|
+
return {Gender.MALE: self.gender_man, Gender.FEMALE: self.gender_woman}
|
|
126
|
+
|
|
127
|
+
def is_gender_solved(self):
|
|
128
|
+
return bool(self.gender_solved)
|
|
129
|
+
|
|
130
|
+
def __repr__(self):
|
|
131
|
+
return f'{self.word_orig} - {self.name_cases}'
|
|
132
|
+
|
|
133
|
+
def __eq__(self, other):
|
|
134
|
+
if isinstance(other, str):
|
|
135
|
+
return self.word_orig == other
|
|
136
|
+
elif isinstance(other, NCLNameCaseWord):
|
|
137
|
+
return self.word_orig == other.word_orig
|
|
138
|
+
return False
|
|
@@ -0,0 +1,409 @@
|
|
|
1
|
+
from lesya.language.nc_core import NameCaseCore, NamePart
|
|
2
|
+
from lesya.language.name_constants import NAMES, FEMALE_NAMES, LASTNAME_ENDINGS2, LASTNAME_ENDINGS3
|
|
3
|
+
from lesya.language.case_helpers import CaseUA
|
|
4
|
+
|
|
5
|
+
NAMES_LIST = {n.lower() for n in NAMES + FEMALE_NAMES}
|
|
6
|
+
NAMES = {n.lower() for n in NAMES}
|
|
7
|
+
FEMALE_NAMES = {n.lower() for n in FEMALE_NAMES}
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class NameCaseUa(NameCaseCore):
|
|
11
|
+
def __init__(self):
|
|
12
|
+
super().__init__()
|
|
13
|
+
self.vowels = 'аеиоуіїєюя'
|
|
14
|
+
self.consonant = "бвгджзйклмнпрстфхцчшщ"
|
|
15
|
+
self.shyplyachi = "жчшщ"
|
|
16
|
+
self.neshyplyachi = "бвгдзклмнпрстфхц"
|
|
17
|
+
self.myaki = 'ьюяєї'
|
|
18
|
+
self.gubni = 'мвпбф'
|
|
19
|
+
|
|
20
|
+
@property
|
|
21
|
+
def forms(self):
|
|
22
|
+
if self.finished:
|
|
23
|
+
all_forms = dict()
|
|
24
|
+
for i, case in enumerate(self.cases):
|
|
25
|
+
all_forms[case] = ' '.join([w.name_cases[i] for w in self.words])
|
|
26
|
+
return all_forms
|
|
27
|
+
return None
|
|
28
|
+
|
|
29
|
+
def get_firstname(self):
|
|
30
|
+
words = [w for w in self.words if w.name_part == NamePart.FIRSTNAME]
|
|
31
|
+
return words[0].word_orig if words else None
|
|
32
|
+
|
|
33
|
+
def get_lastname(self):
|
|
34
|
+
words = [w for w in self.words if w.name_part == NamePart.LASTNAME]
|
|
35
|
+
return words[0].word_orig if words else None
|
|
36
|
+
|
|
37
|
+
def get_patronym(self):
|
|
38
|
+
words = [w for w in self.words if w.name_part == NamePart.PATRONYMIC]
|
|
39
|
+
return words[0].word_orig if words else None
|
|
40
|
+
|
|
41
|
+
@staticmethod
|
|
42
|
+
def inverse_gkh(letter):
|
|
43
|
+
return {'г': 'з', 'к': 'ц', 'х': 'с'}.get(letter, letter)
|
|
44
|
+
|
|
45
|
+
@staticmethod
|
|
46
|
+
def inverse_gk2(letter):
|
|
47
|
+
return {'к': 'ч', 'г': 'ж'}.get(letter, letter)
|
|
48
|
+
|
|
49
|
+
@staticmethod
|
|
50
|
+
def is_apostrof(char):
|
|
51
|
+
return char in "'`’\""
|
|
52
|
+
|
|
53
|
+
@staticmethod
|
|
54
|
+
def first_last_vowel(word, vowels):
|
|
55
|
+
for char in word[::-1]:
|
|
56
|
+
if char in vowels:
|
|
57
|
+
return char
|
|
58
|
+
return None
|
|
59
|
+
|
|
60
|
+
def get_base_word(self, word):
|
|
61
|
+
base_word = word.lower()
|
|
62
|
+
while base_word[-1] in self.vowels + 'ь':
|
|
63
|
+
base_word = base_word[:-1]
|
|
64
|
+
return base_word.lower()
|
|
65
|
+
|
|
66
|
+
def male_rule1(self):
|
|
67
|
+
last2 = self.working_word[-2]
|
|
68
|
+
if self.working_word[-1] == 'а':
|
|
69
|
+
self.word_forms(self.working_word,
|
|
70
|
+
[last2 + 'и', self.inverse_gkh(last2) + 'і', last2 + 'у', last2 + 'ою',
|
|
71
|
+
self.inverse_gkh(last2) + 'і', last2 + 'о'], 2)
|
|
72
|
+
return True
|
|
73
|
+
elif self.working_word[-1] == 'я':
|
|
74
|
+
if last2 == 'і':
|
|
75
|
+
self.word_forms(self.working_word, ['ї', 'ї', 'ю', 'єю', 'ї', 'є'], 1)
|
|
76
|
+
return True
|
|
77
|
+
else:
|
|
78
|
+
self.word_forms(self.working_word,
|
|
79
|
+
[last2 + 'і', self.inverse_gkh(last2) + 'і', last2 + 'ю',
|
|
80
|
+
last2 + 'ею', self.inverse_gkh(last2) + 'і',
|
|
81
|
+
last2 + 'е'], 2)
|
|
82
|
+
return True
|
|
83
|
+
return False
|
|
84
|
+
|
|
85
|
+
def male_rule2(self):
|
|
86
|
+
if self.working_word[-1] == 'р':
|
|
87
|
+
base_word = self.working_word
|
|
88
|
+
if self.working_word in ('Ігор', 'Лазар') or self.working_word.lower().endswith('якір'):
|
|
89
|
+
endings = ['я', 'еві', 'я', 'ем', 'еві', 'е']
|
|
90
|
+
if base_word[-2] == 'і':
|
|
91
|
+
base_word = base_word[:-2] + 'о' + base_word[-1]
|
|
92
|
+
else:
|
|
93
|
+
# це має працювати на іменах (Федір -Федора, Сидір-Сидора)
|
|
94
|
+
# а не на прізвищах (Кушнір -Кушніра)
|
|
95
|
+
if base_word[-2] == 'і':
|
|
96
|
+
word = [w for w in self.words if w == self.working_word]
|
|
97
|
+
if word[0].name_part == NamePart.FIRSTNAME:
|
|
98
|
+
base_word = base_word[:-2] + 'о' + base_word[-1]
|
|
99
|
+
endings = ['а', 'у', 'а', 'ом', 'ові', 'е']
|
|
100
|
+
self.word_forms(base_word, endings)
|
|
101
|
+
return True
|
|
102
|
+
return False
|
|
103
|
+
|
|
104
|
+
def male_rule3(self):
|
|
105
|
+
last2 = self.working_word[-2]
|
|
106
|
+
if self.working_word[-1] in self.consonant + 'оь':
|
|
107
|
+
group = self.detect_second_group(self.working_word)
|
|
108
|
+
base_word = self.get_base_word(self.working_word)
|
|
109
|
+
last = base_word[-1]
|
|
110
|
+
if (last not in 'йм' and base_word[-2] == 'і'
|
|
111
|
+
and not base_word.lower() in ('світ', 'цвіт')
|
|
112
|
+
and self.working_word not in ('Гліб', 'Леонід')
|
|
113
|
+
and not self.working_word[-2:] in ('ік', 'іч')):
|
|
114
|
+
if self.working_word[-4:] in ('бідь', 'мінь'):
|
|
115
|
+
# заміна на -е-
|
|
116
|
+
base_word = base_word[:-2] + 'е' + base_word[-1]
|
|
117
|
+
elif self.working_word[-4:] in ('сіль'):
|
|
118
|
+
# не змінюється
|
|
119
|
+
pass
|
|
120
|
+
else:
|
|
121
|
+
# заміна на -о-
|
|
122
|
+
base_word = base_word[:-2] + 'о' + base_word[-1]
|
|
123
|
+
|
|
124
|
+
if (base_word[0] in ('о', 'О') and self.first_last_vowel(base_word, self.vowels + 'гк') == 'е'
|
|
125
|
+
and not self.working_word[-2:] in ('сь', 'ць', 'нь', 'ть', 'дь', 'ль', 'рь')):
|
|
126
|
+
delim = base_word.rfind('е')
|
|
127
|
+
base_word = base_word[:delim] + base_word[delim + 1:]
|
|
128
|
+
if group == 1:
|
|
129
|
+
if self.working_word[-2:] == 'ок' and self.working_word[-3:] != 'оок':
|
|
130
|
+
self.word_forms(self.working_word, ['ка', 'кові', 'ка', 'ком', 'кові', 'че'], 2)
|
|
131
|
+
return True
|
|
132
|
+
elif self.working_word[-2:] in ('ов', 'ев', 'єв') and not self.working_word in ('Лев', 'Остромов'):
|
|
133
|
+
self.word_forms(base_word, [last + 'а', last + 'у', last + 'а', last + 'им', last + 'у',
|
|
134
|
+
self.inverse_gk2(last) + 'е'], 1)
|
|
135
|
+
return True
|
|
136
|
+
elif self.working_word[-2:] == 'ін':
|
|
137
|
+
self.word_forms(self.working_word, ['а', 'у', 'а', 'ом', 'у', 'е'])
|
|
138
|
+
return True
|
|
139
|
+
else:
|
|
140
|
+
if self.working_word.lower() == 'пес':
|
|
141
|
+
base_word = 'пс'
|
|
142
|
+
if self.working_word[-3:] in ('ких', 'гих', 'чих', 'дих'):
|
|
143
|
+
return False
|
|
144
|
+
self.word_forms(base_word, [last + 'а', last + 'у', last + 'а', last + 'ом', last + 'ові',
|
|
145
|
+
self.inverse_gk2(last) + 'е'], 1)
|
|
146
|
+
return True
|
|
147
|
+
if group == 2:
|
|
148
|
+
self.word_forms(base_word, ['а', 'у', 'а', 'ем', 'еві', 'е'])
|
|
149
|
+
return True
|
|
150
|
+
if group == 3:
|
|
151
|
+
if self.working_word[-2:] == 'ей' and self.working_word[-3] in self.gubni:
|
|
152
|
+
base_word = self.working_word[:-2] + '’'
|
|
153
|
+
self.word_forms(base_word, ['я', 'єві', 'я', 'єм', 'єві', 'ю'])
|
|
154
|
+
return True
|
|
155
|
+
elif self.working_word[-1] == 'й' or last2 == 'і':
|
|
156
|
+
if self.working_word[-3:] == 'ній':
|
|
157
|
+
self.word_forms(self.working_word, ['ього', 'ьому', 'ього', 'ім', 'ьому', 'ій'], 2)
|
|
158
|
+
else:
|
|
159
|
+
self.word_forms(self.working_word, ['я', 'єві', 'я', 'єм', 'єві', 'ю'], 1)
|
|
160
|
+
return True
|
|
161
|
+
elif self.working_word[-3:] == 'ець':
|
|
162
|
+
base_word = self.get_exclusions(base_word)
|
|
163
|
+
self.word_forms(base_word, ['ця', 'цеві', 'ця', 'цем', 'цеві', 'цю'], 2)
|
|
164
|
+
return True
|
|
165
|
+
elif self.working_word[-3:] in ('єць', 'яць'):
|
|
166
|
+
self.word_forms(self.working_word, ['йця', 'йцеві', 'йця', 'йцем', 'йцеві', 'йцю'], 3)
|
|
167
|
+
return True
|
|
168
|
+
else:
|
|
169
|
+
if self.working_word[-4:] in ('вель', 'вень', 'день'):
|
|
170
|
+
base_word = self.working_word[:-3] + base_word[-1]
|
|
171
|
+
self.word_forms(base_word, ['я', 'еві', 'я', 'ем', 'еві', 'ю'])
|
|
172
|
+
return True
|
|
173
|
+
return False
|
|
174
|
+
|
|
175
|
+
def male_rule4(self):
|
|
176
|
+
if self.working_word[-1] == 'і':
|
|
177
|
+
self.word_forms(self.working_word, ['их', 'им', 'их', 'ими', 'их', 'і'], 1)
|
|
178
|
+
return True
|
|
179
|
+
return False
|
|
180
|
+
|
|
181
|
+
def male_rule5(self):
|
|
182
|
+
if self.working_word[-2:] in ['ий', 'ой']:
|
|
183
|
+
self.word_forms(self.working_word, ['ого', 'ому', 'ого', 'им', 'ому', 'ий'], 2)
|
|
184
|
+
return True
|
|
185
|
+
return False
|
|
186
|
+
|
|
187
|
+
def noun_is_not_declined(self):
|
|
188
|
+
""" exclusions for words that are not declined """
|
|
189
|
+
if self.working_word[-1] in self.vowels and self.working_word[-2] in self.vowels:
|
|
190
|
+
return True
|
|
191
|
+
if self.working_word[-2:] in ('ьє', 'ні', 'лі', 'рі', 'ьї', 'те', 'се', 'же', 'хе',):
|
|
192
|
+
return True
|
|
193
|
+
if self.working_word.lower() in ('педро', 'дюма', 'дідро', ):
|
|
194
|
+
return True
|
|
195
|
+
return False
|
|
196
|
+
|
|
197
|
+
def get_exclusions(self, base_word):
|
|
198
|
+
if base_word[-3] in self.gubni + "н" and sum([1 for ch in base_word if ch in self.vowels]) > 1:
|
|
199
|
+
# Кравець, правдивець, але не Швець
|
|
200
|
+
return base_word[:-2] + 'ц*'
|
|
201
|
+
if base_word[-3:] == 'лец' and sum([1 for ch in base_word if ch in self.vowels]) > 1:
|
|
202
|
+
# Слепець, але не Лець
|
|
203
|
+
return base_word[:-3] + 'льц*'
|
|
204
|
+
return self.working_word
|
|
205
|
+
|
|
206
|
+
def female_rule1(self):
|
|
207
|
+
last2 = self.working_word[-2]
|
|
208
|
+
if self.working_word[-1] == 'а':
|
|
209
|
+
self.word_forms(self.working_word,
|
|
210
|
+
[last2 + 'и', self.inverse_gkh(last2) + 'і', last2 + 'у', last2 + 'ою',
|
|
211
|
+
self.inverse_gkh(last2) + 'і', last2 + 'о'], 2)
|
|
212
|
+
return True
|
|
213
|
+
elif self.working_word[-2:] == 'яя':
|
|
214
|
+
self.word_forms(self.working_word, ['ьої', 'ій', 'ю', 'ьою', 'ій', 'яя'], 2)
|
|
215
|
+
return True
|
|
216
|
+
elif self.working_word[-1] == 'я':
|
|
217
|
+
if last2 in self.vowels or self.is_apostrof(last2):
|
|
218
|
+
self.word_forms(self.working_word, ['ї', 'ї', 'ю', 'єю', 'ї', 'є'], 1)
|
|
219
|
+
return True
|
|
220
|
+
else:
|
|
221
|
+
self.word_forms(self.working_word,
|
|
222
|
+
[last2 + 'і', self.inverse_gkh(last2) + 'і', last2 + 'ю',
|
|
223
|
+
last2 + 'ею', self.inverse_gkh(last2) + 'і',
|
|
224
|
+
last2 + 'е'], 2)
|
|
225
|
+
return True
|
|
226
|
+
return False
|
|
227
|
+
|
|
228
|
+
def female_rule2(self):
|
|
229
|
+
if self.working_word[-1] in self.consonant + 'ь':
|
|
230
|
+
base_word = self.get_base_word(self.working_word)
|
|
231
|
+
apostrof = ''
|
|
232
|
+
duplicate = ''
|
|
233
|
+
last = base_word[-1]
|
|
234
|
+
last2 = base_word[-2]
|
|
235
|
+
if last in self.gubni and last2 in self.vowels:
|
|
236
|
+
apostrof = '’'
|
|
237
|
+
if last in 'дтзсцлн':
|
|
238
|
+
duplicate = last
|
|
239
|
+
if self.working_word[-1] == 'ь':
|
|
240
|
+
self.word_forms(base_word, ['і', 'і', 'ь', duplicate + apostrof + 'ю', 'і', 'е'])
|
|
241
|
+
return True
|
|
242
|
+
else:
|
|
243
|
+
self.word_forms(base_word, ['і', 'і', '', duplicate + apostrof + 'ю', 'і', 'е'])
|
|
244
|
+
return True
|
|
245
|
+
return False
|
|
246
|
+
|
|
247
|
+
def female_rule3(self):
|
|
248
|
+
last2 = self.working_word[-2]
|
|
249
|
+
if self.working_word[-2:] == 'ая':
|
|
250
|
+
self.word_forms(self.working_word, ['ої', 'ій', 'ую', 'ою', 'ій', 'ая'], 2)
|
|
251
|
+
return
|
|
252
|
+
if self.working_word[-1] == 'а' and (self.working_word[-2] in 'чнв' or self.working_word[-3:-1] in ['ьк']):
|
|
253
|
+
self.word_forms(self.working_word,
|
|
254
|
+
[last2 + 'ої', last2 + 'ій', last2 + 'у', last2 + 'ою',
|
|
255
|
+
last2 + 'ій', last2 + 'о'], 2)
|
|
256
|
+
return True
|
|
257
|
+
return False
|
|
258
|
+
|
|
259
|
+
def detect_second_group(self, word):
|
|
260
|
+
base_word = word
|
|
261
|
+
stack = []
|
|
262
|
+
while base_word[-1] in self.vowels + 'ь':
|
|
263
|
+
stack.append(base_word[-1])
|
|
264
|
+
base_word = base_word[0: -1]
|
|
265
|
+
last = 'Z'
|
|
266
|
+
if stack:
|
|
267
|
+
last = stack[- 1]
|
|
268
|
+
base_word_end = base_word[-1]
|
|
269
|
+
if base_word_end in self.neshyplyachi and last not in self.myaki:
|
|
270
|
+
return 1
|
|
271
|
+
elif base_word_end in self.shyplyachi and last not in self.myaki:
|
|
272
|
+
return 2
|
|
273
|
+
else:
|
|
274
|
+
return 3
|
|
275
|
+
|
|
276
|
+
def male_first_name(self):
|
|
277
|
+
if self.noun_is_not_declined():
|
|
278
|
+
return False
|
|
279
|
+
return self.rules_chain('male', [1, 2, 3])
|
|
280
|
+
|
|
281
|
+
def female_first_name(self):
|
|
282
|
+
return self.rules_chain('female', [1, 2])
|
|
283
|
+
|
|
284
|
+
def male_second_name(self):
|
|
285
|
+
if self.noun_is_not_declined():
|
|
286
|
+
return False
|
|
287
|
+
return self.rules_chain('male', [5, 1, 2, 3, 4])
|
|
288
|
+
|
|
289
|
+
def female_second_name(self):
|
|
290
|
+
return self.rules_chain('female', [3, 1])
|
|
291
|
+
|
|
292
|
+
def male_father_name(self):
|
|
293
|
+
if self.working_word[-2:] in ['ич', 'іч']:
|
|
294
|
+
self.word_forms(self.working_word, ['а', 'у', 'а', 'ем', 'у', 'у'])
|
|
295
|
+
return True
|
|
296
|
+
return False
|
|
297
|
+
|
|
298
|
+
def female_father_name(self):
|
|
299
|
+
if self.working_word[-3:] == 'вна':
|
|
300
|
+
self.word_forms(self.working_word, ['и', 'і', 'у', 'ою', 'і', 'о'], 1)
|
|
301
|
+
return True
|
|
302
|
+
return False
|
|
303
|
+
|
|
304
|
+
def gender_by_first_name(self, word):
|
|
305
|
+
self.working_word = word.word
|
|
306
|
+
man = 0
|
|
307
|
+
woman = 0
|
|
308
|
+
if self.working_word[-1] == 'й':
|
|
309
|
+
man += 0.9
|
|
310
|
+
if self.working_word in NAMES:
|
|
311
|
+
man += 30
|
|
312
|
+
if self.working_word in FEMALE_NAMES:
|
|
313
|
+
woman += 30
|
|
314
|
+
if self.working_word[-2:] in {'ро', 'яр', 'ус', 'ис', 'ст', 'ив', 'им', 'ам', 'іт', 'ід', 'ат', 'іб', 'нн',
|
|
315
|
+
'ап', 'ік', 'ів', 'ас', 'ир', 'ил', 'ет', 'ип', 'ик', 'ол', 'лк', 'ім', 'єр',
|
|
316
|
+
'ок', 'ур', 'їл', 'ох', 'сл', 'ад', 'др', 'ор', 'ар', 'ав', 'сь', 'ій', 'ло',
|
|
317
|
+
'ко', 'ен', 'ин', 'юб', 'ін', 'ид', 'од', 'ем', 'ум', 'рт', 'ян', 'ег', 'он'}:
|
|
318
|
+
man += 0.5
|
|
319
|
+
if self.working_word[-3:] in {"'я", 'ая', 'га', 'да', 'дь', 'ер', 'ея', 'за', 'ит', 'иф', 'йя', 'на', 'па',
|
|
320
|
+
'ря', 'ся', 'тя', 'фа', 'ха', 'ша', 'ія'}:
|
|
321
|
+
woman += 0.5
|
|
322
|
+
if self.working_word[-1] in self.consonant:
|
|
323
|
+
man += 0.01
|
|
324
|
+
if self.working_word[-1] == 'ь':
|
|
325
|
+
man += 0.02
|
|
326
|
+
|
|
327
|
+
word.set_gender(man, woman)
|
|
328
|
+
|
|
329
|
+
def gender_by_patronym(self, word):
|
|
330
|
+
self.working_word = word.word
|
|
331
|
+
if self.working_word[-2:] in ('ич', 'іч'):
|
|
332
|
+
word.set_gender(10, 0)
|
|
333
|
+
if self.working_word[-2:] == 'на':
|
|
334
|
+
word.set_gender(0, 12)
|
|
335
|
+
|
|
336
|
+
def detect_name_part(self, word):
|
|
337
|
+
name_part = word.word
|
|
338
|
+
self.working_word = name_part
|
|
339
|
+
|
|
340
|
+
first = 0
|
|
341
|
+
second = 0
|
|
342
|
+
father = 0
|
|
343
|
+
|
|
344
|
+
if self.working_word[-3:] in ('вна', 'чна', 'ліч') or self.working_word[-4:] in ('ьмич', 'ович', 'огли'):
|
|
345
|
+
father += 3
|
|
346
|
+
if (self.working_word[-3:] == 'тин'
|
|
347
|
+
or self.working_word[-4:] in ('ьмич', 'юбов', 'івна', 'явка', 'орив', 'кіян')):
|
|
348
|
+
first += 0.5
|
|
349
|
+
if name_part in NAMES_LIST:
|
|
350
|
+
first += 10
|
|
351
|
+
if self.working_word[-2:] in LASTNAME_ENDINGS2:
|
|
352
|
+
second += 0.4
|
|
353
|
+
if self.working_word[-3:] in LASTNAME_ENDINGS3:
|
|
354
|
+
second += 0.4
|
|
355
|
+
|
|
356
|
+
max_val = max(first, second, father)
|
|
357
|
+
if first == max_val:
|
|
358
|
+
word.name_part = NamePart.FIRSTNAME
|
|
359
|
+
elif second == max_val:
|
|
360
|
+
word.name_part = NamePart.LASTNAME
|
|
361
|
+
else:
|
|
362
|
+
word.name_part = NamePart.PATRONYMIC
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
class Lesya:
|
|
366
|
+
def __init__(self, name, gender=None):
|
|
367
|
+
self._core = NameCaseUa()
|
|
368
|
+
self._core.q(name, gender=gender)
|
|
369
|
+
self.forms = self._core.forms
|
|
370
|
+
|
|
371
|
+
@property
|
|
372
|
+
def nominative(self):
|
|
373
|
+
return self._core[CaseUA.NOMINATIVE]
|
|
374
|
+
|
|
375
|
+
@property
|
|
376
|
+
def genitive(self):
|
|
377
|
+
return self._core[CaseUA.GENITIVE]
|
|
378
|
+
|
|
379
|
+
@property
|
|
380
|
+
def dative(self):
|
|
381
|
+
return self._core[CaseUA.DATIVE]
|
|
382
|
+
|
|
383
|
+
@property
|
|
384
|
+
def accusative(self):
|
|
385
|
+
return self._core[CaseUA.ACCUSATIVE]
|
|
386
|
+
|
|
387
|
+
@property
|
|
388
|
+
def instrumental(self):
|
|
389
|
+
return self._core[CaseUA.INSTRUMENTAL]
|
|
390
|
+
|
|
391
|
+
@property
|
|
392
|
+
def prepositional(self):
|
|
393
|
+
return self._core[CaseUA.PREPOSITIONAL]
|
|
394
|
+
|
|
395
|
+
@property
|
|
396
|
+
def vocative(self):
|
|
397
|
+
return self._core[CaseUA.VOCATIVE]
|
|
398
|
+
|
|
399
|
+
def __getitem__(self, item):
|
|
400
|
+
if not self._core.finished:
|
|
401
|
+
return None
|
|
402
|
+
if isinstance(item, int) and item in range(self._core.case_count):
|
|
403
|
+
return self._core.forms[self._core.cases[item]]
|
|
404
|
+
if isinstance(item, str):
|
|
405
|
+
if item in self._core.cases:
|
|
406
|
+
return self._core.forms[item]
|
|
407
|
+
if item in CaseUA().all():
|
|
408
|
+
index = CaseUA().all().index(item)
|
|
409
|
+
return self[index]
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
NAMES = (
|
|
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
|
+
FEMALE_NAMES = (
|
|
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
|
+
FOREIGN_NAMES = (
|
|
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
|
+
with open('lesya/language/language_data/family-names.csv') as f:
|
|
127
|
+
lines = [l.strip() for l in f.readlines()]
|
|
128
|
+
|
|
129
|
+
LASTNAME_ENDINGS2 = {l[-2:] for l in lines}
|
|
130
|
+
LASTNAME_ENDINGS3 = {l[-3:] for l in lines if len(l) > 4}
|
|
131
|
+
LASTNAME_ENDINGS4 = {l[-4:] for l in lines if len(l) > 6}
|
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
from lesya.language.case_helpers import NamePart, CaseUA, NCLNameCaseWord, Gender
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class NameCaseCore:
|
|
5
|
+
"""
|
|
6
|
+
Base class for name case handling.
|
|
7
|
+
"""
|
|
8
|
+
def __init__(self):
|
|
9
|
+
self.case_count = 7
|
|
10
|
+
# System readiness:
|
|
11
|
+
# - All words are identified (know which part of FIO the word belongs to)
|
|
12
|
+
# - Gender is determined for all words
|
|
13
|
+
# If all is done, the flag is set to true, adding a new word resets the flag to false
|
|
14
|
+
self._ready = False
|
|
15
|
+
# If all current words have been declined and each word already has a declination result,
|
|
16
|
+
# then true. Adding a new word resets the flag to false
|
|
17
|
+
self._finished = False
|
|
18
|
+
# Array contains elements of type NCLNameCaseWord. These are all the words to be processed and declined
|
|
19
|
+
self.words = []
|
|
20
|
+
# Variable into which the current working word is placed
|
|
21
|
+
self.working_word = None
|
|
22
|
+
# Array contains the result of the declination of the word - the word in all cases
|
|
23
|
+
self.last_result = []
|
|
24
|
+
# Array contains information about which words from the array <var>$this->words</var> belong to
|
|
25
|
+
# the surname, which to the patronymic, and which to the first name. The array is needed because when adding words
|
|
26
|
+
# we do not always know which part of the FIO it is, so after identifying all words, an array of
|
|
27
|
+
# indexes for quick search is generated.
|
|
28
|
+
self.index = {NamePart.FIRSTNAME: [], NamePart.LASTNAME: [], NamePart.PATRONYMIC: []}
|
|
29
|
+
self.cases = CaseUA().all('ua')
|
|
30
|
+
|
|
31
|
+
def __repr__(self):
|
|
32
|
+
return f'{self.words}'
|
|
33
|
+
|
|
34
|
+
def forms(self):
|
|
35
|
+
pass
|
|
36
|
+
|
|
37
|
+
@property
|
|
38
|
+
def ready(self):
|
|
39
|
+
return self._ready
|
|
40
|
+
|
|
41
|
+
@property
|
|
42
|
+
def finished(self):
|
|
43
|
+
return self._finished
|
|
44
|
+
|
|
45
|
+
def full_reset(self):
|
|
46
|
+
"""
|
|
47
|
+
Resets all information to the initial state. Clears all words added to the system.
|
|
48
|
+
After execution, the system is ready to work from scratch.
|
|
49
|
+
:return: NCLNameCaseCore
|
|
50
|
+
"""
|
|
51
|
+
self.words = []
|
|
52
|
+
self.last_result = []
|
|
53
|
+
self.index = {NamePart.FIRSTNAME: [], NamePart.LASTNAME: [], NamePart.PATRONYMIC: []}
|
|
54
|
+
self.not_ready()
|
|
55
|
+
return self
|
|
56
|
+
|
|
57
|
+
def not_ready(self):
|
|
58
|
+
"""
|
|
59
|
+
Sets flags that the system is not ready and the words have not yet been declined.
|
|
60
|
+
"""
|
|
61
|
+
self._ready = False
|
|
62
|
+
self._finished = False
|
|
63
|
+
|
|
64
|
+
def rules_chain(self, gender, rules_array):
|
|
65
|
+
for rule_id in rules_array:
|
|
66
|
+
rule_method = f"{gender}_rule{rule_id}"
|
|
67
|
+
if getattr(self, rule_method)():
|
|
68
|
+
return True
|
|
69
|
+
return False
|
|
70
|
+
|
|
71
|
+
def word_forms(self, word, endings, replace_last=0):
|
|
72
|
+
result = [self.working_word]
|
|
73
|
+
# word = NCLStr.substr(word, 0, len(word) - replace_last)
|
|
74
|
+
word = word[0:len(word) - replace_last]
|
|
75
|
+
for case_index in range(1, self.case_count):
|
|
76
|
+
result.append(word + endings[case_index - 1])
|
|
77
|
+
self.last_result = result
|
|
78
|
+
|
|
79
|
+
def set_first_name(self, firstname=""):
|
|
80
|
+
if firstname:
|
|
81
|
+
index = len(self.words)
|
|
82
|
+
self.words.append(NCLNameCaseWord(firstname))
|
|
83
|
+
self.words[index].name_part = NamePart.FIRSTNAME
|
|
84
|
+
self.not_ready()
|
|
85
|
+
return self
|
|
86
|
+
|
|
87
|
+
def set_second_name(self, secondname=""):
|
|
88
|
+
if secondname:
|
|
89
|
+
index = len(self.words)
|
|
90
|
+
self.words.append(NCLNameCaseWord(secondname))
|
|
91
|
+
self.words[index].name_part = NamePart.LASTNAME
|
|
92
|
+
self.not_ready()
|
|
93
|
+
return self
|
|
94
|
+
|
|
95
|
+
def set_father_name(self, fathername=""):
|
|
96
|
+
if fathername:
|
|
97
|
+
index = len(self.words)
|
|
98
|
+
self.words.append(NCLNameCaseWord(fathername))
|
|
99
|
+
self.words[index].name_part = NamePart.PATRONYMIC
|
|
100
|
+
self.not_ready()
|
|
101
|
+
return self
|
|
102
|
+
|
|
103
|
+
def set_gender(self, gender):
|
|
104
|
+
for word in self.words:
|
|
105
|
+
word.set_true_gender(gender)
|
|
106
|
+
return self
|
|
107
|
+
|
|
108
|
+
def set_full_name(self, second_name="", first_name="", father_name=""):
|
|
109
|
+
self.set_first_name(first_name)
|
|
110
|
+
self.set_second_name(second_name)
|
|
111
|
+
self.set_father_name(father_name)
|
|
112
|
+
return self
|
|
113
|
+
|
|
114
|
+
def set_name(self, firstname=""):
|
|
115
|
+
return self.set_first_name(firstname)
|
|
116
|
+
|
|
117
|
+
def set_last_name(self, secondname=""):
|
|
118
|
+
return self.set_second_name(secondname)
|
|
119
|
+
|
|
120
|
+
def set_sir_name(self, secondname=""):
|
|
121
|
+
return self.set_second_name(secondname)
|
|
122
|
+
|
|
123
|
+
def prepare_name_part(self, word):
|
|
124
|
+
if not word.name_part:
|
|
125
|
+
self.detect_name_part(word)
|
|
126
|
+
|
|
127
|
+
def prepare_all_name_parts(self):
|
|
128
|
+
for word in self.words:
|
|
129
|
+
self.prepare_name_part(word)
|
|
130
|
+
|
|
131
|
+
def prepare_gender(self, word):
|
|
132
|
+
if not word.is_gender_solved():
|
|
133
|
+
name_part = word.name_part
|
|
134
|
+
if name_part == NamePart.FIRSTNAME:
|
|
135
|
+
self.gender_by_first_name(word)
|
|
136
|
+
elif name_part == NamePart.PATRONYMIC:
|
|
137
|
+
self.gender_by_patronym(word)
|
|
138
|
+
elif name_part == NamePart.LASTNAME:
|
|
139
|
+
self.gender_by_lastname(word)
|
|
140
|
+
|
|
141
|
+
def solve_gender(self):
|
|
142
|
+
for word in self.words:
|
|
143
|
+
if word.is_gender_solved():
|
|
144
|
+
self.set_gender(word.gender())
|
|
145
|
+
return True
|
|
146
|
+
man, woman = 0, 0
|
|
147
|
+
for word in self.words:
|
|
148
|
+
self.prepare_gender(word)
|
|
149
|
+
gender = word.get_gender()
|
|
150
|
+
man += gender[Gender.MAN]
|
|
151
|
+
woman += gender[Gender.WOMAN]
|
|
152
|
+
self.set_gender(Gender.MAN if man > woman else Gender.WOMAN)
|
|
153
|
+
return True
|
|
154
|
+
|
|
155
|
+
def generate_index(self):
|
|
156
|
+
self.index = {NamePart.FIRSTNAME: [], NamePart.LASTNAME: [], NamePart.PATRONYMIC: []}
|
|
157
|
+
for index, word in enumerate(self.words):
|
|
158
|
+
name_part = word.name_part
|
|
159
|
+
self.index[name_part].append(index)
|
|
160
|
+
|
|
161
|
+
def prepare_everything(self):
|
|
162
|
+
if not self._ready:
|
|
163
|
+
self.prepare_all_name_parts()
|
|
164
|
+
self.solve_gender()
|
|
165
|
+
self.generate_index()
|
|
166
|
+
self._ready = True
|
|
167
|
+
|
|
168
|
+
def split_full_name(self, fullname):
|
|
169
|
+
fullname = fullname.strip()
|
|
170
|
+
words_list = [w for w in fullname.split() if w]
|
|
171
|
+
for word in words_list:
|
|
172
|
+
self.words.append(NCLNameCaseWord(word))
|
|
173
|
+
self.prepare_everything()
|
|
174
|
+
return self.words
|
|
175
|
+
|
|
176
|
+
def word_case(self, word):
|
|
177
|
+
gender = 'male' if word.gender() == Gender.MAN else 'female'
|
|
178
|
+
name_part = {NamePart.FIRSTNAME: 'first', NamePart.LASTNAME: 'second', NamePart.PATRONYMIC: 'father'}.get(
|
|
179
|
+
word.name_part)
|
|
180
|
+
method = f'{gender}_{name_part}_name'
|
|
181
|
+
tmp = word.word_orig
|
|
182
|
+
cur_words = tmp.split('-')
|
|
183
|
+
o_cur_words = []
|
|
184
|
+
result = ['']*self.case_count
|
|
185
|
+
cnt = len(cur_words)
|
|
186
|
+
for k, cur_word in enumerate(cur_words):
|
|
187
|
+
is_norm_rules = True
|
|
188
|
+
o_ncw = NCLNameCaseWord(cur_word)
|
|
189
|
+
if word.name_part == NamePart.LASTNAME and cnt > 1 and k < cnt - 1:
|
|
190
|
+
if cur_word.lower() not in ('тулуз'):
|
|
191
|
+
self.detect_name_part(o_ncw)
|
|
192
|
+
is_norm_rules = o_ncw.name_part == NamePart.LASTNAME
|
|
193
|
+
else:
|
|
194
|
+
is_norm_rules = False
|
|
195
|
+
|
|
196
|
+
self.working_word = cur_word
|
|
197
|
+
|
|
198
|
+
if is_norm_rules and getattr(self, method)():
|
|
199
|
+
result_tmp = self.last_result
|
|
200
|
+
else:
|
|
201
|
+
result_tmp = [cur_word] * self.case_count
|
|
202
|
+
|
|
203
|
+
o_ncw.set_name_cases(result_tmp)
|
|
204
|
+
o_cur_words.append(o_ncw)
|
|
205
|
+
|
|
206
|
+
for o_ncw in o_cur_words:
|
|
207
|
+
namecases = o_ncw.get_name_cases()
|
|
208
|
+
for k, namecase in enumerate(namecases):
|
|
209
|
+
if result[k]:
|
|
210
|
+
result[k] = result[k] + '-' + namecase
|
|
211
|
+
else:
|
|
212
|
+
result[k] = namecase
|
|
213
|
+
|
|
214
|
+
word.set_name_cases(result, False)
|
|
215
|
+
|
|
216
|
+
def all_word_cases(self):
|
|
217
|
+
if not self._finished:
|
|
218
|
+
self.prepare_everything()
|
|
219
|
+
for word in self.words:
|
|
220
|
+
self.word_case(word)
|
|
221
|
+
self._finished = True
|
|
222
|
+
|
|
223
|
+
def get_word_case(self, word, number=None):
|
|
224
|
+
cases = word.get_name_cases()
|
|
225
|
+
if number is None or number < 0 or number > (self.case_count - 1):
|
|
226
|
+
return cases
|
|
227
|
+
else:
|
|
228
|
+
return cases[number]
|
|
229
|
+
|
|
230
|
+
def get_cases_connected(self, index_array, number=None):
|
|
231
|
+
ready = []
|
|
232
|
+
for index in index_array:
|
|
233
|
+
ready.append(self.get_word_case(self.words[index], number))
|
|
234
|
+
|
|
235
|
+
all_cases = len(ready)
|
|
236
|
+
if all_cases:
|
|
237
|
+
if isinstance(ready[0], list):
|
|
238
|
+
result = []
|
|
239
|
+
for case in range(self.case_count):
|
|
240
|
+
tmp = []
|
|
241
|
+
for i in range(all_cases):
|
|
242
|
+
tmp.append(ready[i][case])
|
|
243
|
+
result.append(' '.join(tmp))
|
|
244
|
+
return result
|
|
245
|
+
else:
|
|
246
|
+
return ' '.join(ready)
|
|
247
|
+
return ''
|
|
248
|
+
|
|
249
|
+
def get_firstname_case(self, number=None):
|
|
250
|
+
self.all_word_cases()
|
|
251
|
+
return self.get_cases_connected(self.index[NamePart.FIRSTNAME], number)
|
|
252
|
+
|
|
253
|
+
def get_lastname_case(self, number=None):
|
|
254
|
+
self.all_word_cases()
|
|
255
|
+
return self.get_cases_connected(self.index[NamePart.LASTNAME], number)
|
|
256
|
+
|
|
257
|
+
def get_patronymic_case(self, number=None):
|
|
258
|
+
self.all_word_cases()
|
|
259
|
+
return self.get_cases_connected(self.index[NamePart.PATRONYMIC], number)
|
|
260
|
+
|
|
261
|
+
def get_formatted_array(self, format_):
|
|
262
|
+
if isinstance(format_, list):
|
|
263
|
+
return self.get_formatted_array_forced(format_)
|
|
264
|
+
result = []
|
|
265
|
+
cases = {
|
|
266
|
+
NamePart.LASTNAME: self.get_cases_connected(self.index[NamePart.LASTNAME]),
|
|
267
|
+
NamePart.FIRSTNAME: self.get_cases_connected(self.index[NamePart.FIRSTNAME]),
|
|
268
|
+
NamePart.PATRONYMIC: self.get_cases_connected(self.index[NamePart.PATRONYMIC])
|
|
269
|
+
}
|
|
270
|
+
for cur_case in range(self.case_count):
|
|
271
|
+
line = ""
|
|
272
|
+
for symbol in format_.split():
|
|
273
|
+
if symbol == 'S':
|
|
274
|
+
line += cases[NamePart.LASTNAME][cur_case]
|
|
275
|
+
elif symbol == 'N':
|
|
276
|
+
line += cases[NamePart.FIRSTNAME][cur_case]
|
|
277
|
+
elif symbol == 'F':
|
|
278
|
+
line += cases[NamePart.PATRONYMIC][cur_case]
|
|
279
|
+
else:
|
|
280
|
+
line += symbol
|
|
281
|
+
result.append(line)
|
|
282
|
+
return result
|
|
283
|
+
|
|
284
|
+
def get_formatted_array_forced(self, format_):
|
|
285
|
+
result = []
|
|
286
|
+
cases = []
|
|
287
|
+
for word in format_:
|
|
288
|
+
cases.append(word.get_name_cases())
|
|
289
|
+
for curCase in range(self.case_count):
|
|
290
|
+
line = ""
|
|
291
|
+
for value in cases:
|
|
292
|
+
line += value[curCase] + ' '
|
|
293
|
+
result.append(line.strip())
|
|
294
|
+
return result
|
|
295
|
+
|
|
296
|
+
@staticmethod
|
|
297
|
+
def get_formatted_forced(case_num=0, format_=None):
|
|
298
|
+
format_ = format_ if format_ else []
|
|
299
|
+
result = ""
|
|
300
|
+
for word in format_:
|
|
301
|
+
cases = word.get_name_cases()
|
|
302
|
+
result += cases[case_num] + ' '
|
|
303
|
+
return result.strip()
|
|
304
|
+
|
|
305
|
+
def get_formatted(self, case_num=0, format_="S N F"):
|
|
306
|
+
self.all_word_cases()
|
|
307
|
+
if case_num is None or not case_num:
|
|
308
|
+
return self.get_formatted_array(format_)
|
|
309
|
+
elif isinstance(format_, list):
|
|
310
|
+
return self.get_formatted_forced(case_num, format_)
|
|
311
|
+
else:
|
|
312
|
+
result = ""
|
|
313
|
+
for symbol in format_.split():
|
|
314
|
+
if symbol == 'S':
|
|
315
|
+
result += self.get_lastname_case(case_num)
|
|
316
|
+
elif symbol == 'N':
|
|
317
|
+
result += self.get_firstname_case(case_num)
|
|
318
|
+
elif symbol == 'F':
|
|
319
|
+
result += self.get_patronymic_case(case_num)
|
|
320
|
+
else:
|
|
321
|
+
result += symbol
|
|
322
|
+
return result
|
|
323
|
+
|
|
324
|
+
def q(self, fullname, case_num=None, gender=None):
|
|
325
|
+
self.full_reset()
|
|
326
|
+
format_ = self.split_full_name(fullname)
|
|
327
|
+
if gender:
|
|
328
|
+
self.set_gender(gender)
|
|
329
|
+
return self.get_formatted(case_num, format_)
|
|
330
|
+
|
|
331
|
+
def male_first_name(self):
|
|
332
|
+
return False
|
|
333
|
+
|
|
334
|
+
def female_first_name(self):
|
|
335
|
+
return False
|
|
336
|
+
|
|
337
|
+
def male_second_name(self):
|
|
338
|
+
return False
|
|
339
|
+
|
|
340
|
+
def female_second_name(self):
|
|
341
|
+
return False
|
|
342
|
+
|
|
343
|
+
def male_father_name(self):
|
|
344
|
+
return False
|
|
345
|
+
|
|
346
|
+
def female_father_name(self):
|
|
347
|
+
return False
|
|
348
|
+
|
|
349
|
+
def gender_by_first_name(self, word):
|
|
350
|
+
pass
|
|
351
|
+
|
|
352
|
+
def gender_by_lastname(self, word):
|
|
353
|
+
pass
|
|
354
|
+
|
|
355
|
+
def gender_by_patronym(self, word):
|
|
356
|
+
pass
|
|
357
|
+
|
|
358
|
+
def detect_name_part(self, word):
|
|
359
|
+
pass
|
|
360
|
+
|
|
361
|
+
def __getitem__(self, item):
|
|
362
|
+
if not self._finished:
|
|
363
|
+
return None
|
|
364
|
+
if isinstance(item, int) and item in range(self.case_count):
|
|
365
|
+
return self.forms[self.cases[item]]
|
|
366
|
+
if isinstance(item, str):
|
|
367
|
+
if item in self.cases:
|
|
368
|
+
return self.forms[item]
|
|
369
|
+
if item in CaseUA().all():
|
|
370
|
+
index = CaseUA().all().index(item)
|
|
371
|
+
return self[index]
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
Copyright (c) 2018 The Python Packaging Authority
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
5
|
+
in the Software without restriction, including without limitation the rights
|
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
8
|
+
furnished to do so, subject to the following conditions:
|
|
9
|
+
|
|
10
|
+
The above copyright notice and this permission notice shall be included in all
|
|
11
|
+
copies or substantial portions of the Software.
|
|
12
|
+
|
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
19
|
+
SOFTWARE.
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: lesya
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: Python package to form Ukrainian names cases : 'Леся Українка' -- 'Лесі Українки' , 'Лесі Українці', 'Лесею Українкою' ... etc.
|
|
5
|
+
Author: Dmytro Ustynov
|
|
6
|
+
Author-email: Dmytro Ustynov <ustynov.dev@gmail.com>
|
|
7
|
+
License: Copyright (c) 2018 The Python Packaging Authority
|
|
8
|
+
|
|
9
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
10
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
11
|
+
in the Software without restriction, including without limitation the rights
|
|
12
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
13
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
14
|
+
furnished to do so, subject to the following conditions:
|
|
15
|
+
|
|
16
|
+
The above copyright notice and this permission notice shall be included in all
|
|
17
|
+
copies or substantial portions of the Software.
|
|
18
|
+
|
|
19
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
20
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
21
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
22
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
23
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
24
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
25
|
+
SOFTWARE.
|
|
26
|
+
Project-URL: Homepage, https://github.com/ustynov-py-dev/lesya.git
|
|
27
|
+
Project-URL: Issues, https://github.com/ustynov-py-dev/lesya/issues
|
|
28
|
+
Classifier: Programming Language :: Python :: 3
|
|
29
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
30
|
+
Classifier: Operating System :: OS Independent
|
|
31
|
+
Requires-Python: >=3.8
|
|
32
|
+
Description-Content-Type: text/markdown
|
|
33
|
+
License-File: LICENSE
|
|
34
|
+
|
|
35
|
+
# Lesya
|
|
36
|
+
|
|
37
|
+
Lesya is a simple Python package for declining Ukrainian personal names into Ukrainian grammatical cases (_відмінки_): NOMINATIVE, GENITIVE, DATIVE, ACCUSATIVE, INSTRUMENTAL, PREPOSITIONAL, and VOCATIVE (_називний, родовий, давальний, знахідний, орудний, місцевий, кличний_).
|
|
38
|
+
|
|
39
|
+
## Usage example
|
|
40
|
+
|
|
41
|
+
The most common usage is to convert a name into all cases:
|
|
42
|
+
|
|
43
|
+
```python
|
|
44
|
+
from lesya import Lesya
|
|
45
|
+
|
|
46
|
+
person = Lesya('Леся Українка')
|
|
47
|
+
print(person.nominative) # Леся Українка
|
|
48
|
+
print(person.genitive) # Лесі Українки
|
|
49
|
+
print(person.dative) # Лесі Українці
|
|
50
|
+
print(person.accusative) # Лесю Українку
|
|
51
|
+
print(person.instrumental) # Лесею Українкою
|
|
52
|
+
print(person.prepositional) # Лесі Українці
|
|
53
|
+
print(person.vocative) # Лесе Українко
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
You can also get a name in a specific case:
|
|
57
|
+
|
|
58
|
+
```python
|
|
59
|
+
from lesya import Lesya
|
|
60
|
+
from lesya import CaseUA
|
|
61
|
+
|
|
62
|
+
person = Lesya('Тарас Григорович Шевченко')
|
|
63
|
+
print(person[CaseUA.DATIVE]) # Тарасу Григоровичу Шевченку
|
|
64
|
+
print(person[CaseUA.PREPOSITIONAL]) # Тарасові Григоровичу Шевченкові
|
|
65
|
+
print(person['орудний']) # Тарасом Григоровичем Шевченком
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Double names support
|
|
69
|
+
|
|
70
|
+
Lesya works well with double last names:
|
|
71
|
+
|
|
72
|
+
```python
|
|
73
|
+
from lesya import Lesya
|
|
74
|
+
|
|
75
|
+
person = Lesya('Іван Семенович Нечуй-Левицький')
|
|
76
|
+
print(person.forms)
|
|
77
|
+
# Output:
|
|
78
|
+
{
|
|
79
|
+
'називний': 'Іван Семенович Нечуй-Левицький',
|
|
80
|
+
'родовий': 'Івана Семеновича Нечуя-Левицького',
|
|
81
|
+
'давальний': 'Івану Семеновичу Нечуєві-Левицькому',
|
|
82
|
+
'знахідний': 'Івана Семеновича Нечуя-Левицького',
|
|
83
|
+
'орудний': 'Іваном Семеновичем Нечуєм-Левицьким',
|
|
84
|
+
'місцевий': 'Іванові Семеновичу Нечуєві-Левицькому',
|
|
85
|
+
'кличний': 'Іване Семеновичу Нечую-Левицький'
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## `forms` attribute
|
|
90
|
+
|
|
91
|
+
You can get all cases at once using the forms attribute. It returns a dictionary where the keys are case names (in lowercase Ukrainian), and the values are the corresponding declined names.
|
|
92
|
+
|
|
93
|
+
## Foreign names support
|
|
94
|
+
|
|
95
|
+
Lesya supports foreign names. However, since it does not use an ML model to automatically detect a person's gender, it works better if you explicitly provide the gender:
|
|
96
|
+
|
|
97
|
+
```python
|
|
98
|
+
from lesya import Lesya
|
|
99
|
+
from lesya import CaseUA
|
|
100
|
+
from lesya import Gender
|
|
101
|
+
|
|
102
|
+
person = Lesya('Джозеф Байден', gender='male')
|
|
103
|
+
print(person[CaseUA.DATIVE]) # Джозефу Байдену
|
|
104
|
+
print(person[CaseUA.PREPOSITIONAL]) # Джозефові Байденові
|
|
105
|
+
|
|
106
|
+
person = Lesya('Камала Гаріс', gender=Gender.FEMALE)
|
|
107
|
+
print(person[CaseUA.DATIVE]) # Камалі Гаріс
|
|
108
|
+
print(person[CaseUA.PREPOSITIONAL]) # Камалі Гаріс
|
|
109
|
+
```
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
lesya/__init__.py,sha256=3y0FfeVwUx8tdy9tiRJx_YnJ2pQXQzBBym-X2lKZ7ZU,122
|
|
2
|
+
lesya/language/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
|
+
lesya/language/case_helpers.py,sha256=hmQoMqZ_BKvnWFd6nYir_fYBjOFBJSGZtolgVW9uKek,4649
|
|
4
|
+
lesya/language/name_case.py,sha256=86pCwS676D_sd-nS9KcMrLra0exnKSe9gVTRL0meWH8,17665
|
|
5
|
+
lesya/language/name_constants.py,sha256=IQpG9oNgH_Mkb4jQb8hQoULfIXZOyPp_z1tKkB3iYkc,22141
|
|
6
|
+
lesya/language/nc_core.py,sha256=fB101ZwrFsxUdpAGawKteha3dN2U6b8nkurm2OzrOq0,13003
|
|
7
|
+
lesya-0.0.1.dist-info/LICENSE,sha256=2bm9uFabQZ3Ykb_SaSU_uUbAj2-htc6WJQmS_65qD00,1073
|
|
8
|
+
lesya-0.0.1.dist-info/METADATA,sha256=wrsgKbWxiq9Oi1_4I5DfOIbWR1rMvNeLQP-1K7-DoHY,4990
|
|
9
|
+
lesya-0.0.1.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
|
|
10
|
+
lesya-0.0.1.dist-info/top_level.txt,sha256=G6dTztx6aA4OObUA0lP5EBa39WTk7XJzGvfMMV9F4IE,6
|
|
11
|
+
lesya-0.0.1.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
lesya
|