File Coverage

blib/lib/Lingua/RU/Inflect.pm
Criterion Covered Total %
statement 141 149 94.6
branch 101 120 84.1
condition 22 27 81.4
subroutine 17 17 100.0
pod 2 2 100.0
total 283 315 89.8


line stmt bran cond sub pod time code
1             package Lingua::RU::Inflect;
2              
3 4     4   210655 use warnings;
  4         34  
  4         104  
4 4     4   17 use strict;
  4         6  
  4         61  
5 4     4   485 use utf8;
  4         18  
  4         19  
6              
7             =encoding utf8
8              
9             =head1 NAME
10              
11             Lingua::RU::Inflect - Inflect russian names.
12              
13             =head1 VERSION
14              
15             Version 0.06
16              
17             =head1 DESCRIPTION
18              
19             Lingua::RU::Inflect is a perl module
20             that provides Russian linguistic procedures
21             such as declension of given names (with some nouns and adjectives too),
22             and gender detection by given name.
23              
24             Choosing of proper forms of varying prepositions
25             which added in 0.02 now is unavailable because it moved
26             to L.
27              
28             =cut
29              
30             our ($REVISION, $DATE);
31             ($REVISION) = q$Revision$ =~ /(\d+)/g;
32             ($DATE)
33             = q$Date$ =~ /: (\d+)\s*$/g;
34              
35              
36             BEGIN {
37 4     4   350 use Exporter ();
  4         7  
  4         497  
38 4     4   12 our ($VERSION, @ISA, @EXPORT, @EXPORT_OK, %EXPORT_TAGS);
39              
40             # set the version for version checking
41 4         4 $VERSION = 0.06;
42              
43 4         55 @ISA = qw(Exporter);
44 4         12 @EXPORT = qw(
45             inflect_given_name detect_gender_by_given_name
46             );
47              
48             # exported package globals
49 4         10 @EXPORT_OK = qw(
50             NOMINATIVE GENITIVE DATIVE
51             ACCUSATIVE INSTRUMENTAL PREPOSITIONAL
52             %CASES
53             MASCULINE FEMININE
54             );
55              
56 4         160 %EXPORT_TAGS = (
57             'subs' => [ qw(
58             inflect_given_name detect_gender_by_given_name
59             ) ],
60             'genders' => [ qw( MASCULINE FEMININE ) ],
61             'cases' => [ qw(
62             NOMINATIVE GENITIVE DATIVE ACCUSATIVE INSTRUMENTAL PREPOSITIONAL
63             %CASES
64             ) ],
65             'all' => [ @EXPORT, @EXPORT_OK ],
66             )
67              
68             }
69              
70             # Cases
71             # Why I can't use loop?!
72             use constant {
73 4         540 NOMINATIVE => -1,
74             GENITIVE => 0,
75             DATIVE => 1,
76             ACCUSATIVE => 2,
77             INSTRUMENTAL => 3,
78             PREPOSITIONAL => 4,
79 4     4   22 };
  4         5  
80              
81             my @CASE_NAMES = qw(
82             NOMINATIVE GENITIVE DATIVE ACCUSATIVE INSTRUMENTAL PREPOSITIONAL
83             );
84             my @CASE_NUMBERS = ( -1 .. 4 );
85              
86 4     4   1824 use List::MoreUtils qw( any mesh );
  4         39780  
  4         18  
87             our %CASES = mesh @CASE_NAMES, @CASE_NUMBERS;
88              
89             # Gender
90             use constant {
91 4         724 FEMININE => 0,
92             MASCULINE => 1,
93 4     4   3630 };
  4         7  
94              
95             =head1 SYNOPSIS
96              
97             Inflects russian names which represented in UTF-8.
98              
99             Perhaps a little code snippet.
100              
101             use Lingua::RU::Inflect;
102              
103             my @name = qw/Петрова Любовь Степановна/;
104             # Transliteration of above line is: Petrova Lyubov' Stepanovna
105              
106             my $gender = detect_gender_by_given_name(@name);
107             # $gender == FEMININE
108              
109             my @genitive = inflect_given_name(GENITIVE, @name);
110             # @genitive == qw/Петровой Любови Степановны/;
111             # Transliteration of above line is: Petrovoy Lyubovi Stepanovny
112              
113             One-liners also can be used
114              
115             perl -Ilib -Mcommon::sense -MLingua::RU::Inflect=:all \
116             -E 'say join " ", inflect_given_name(GENITIVE, qw/Перец Лев Ильич/)'
117             # Перца Льва Ильича
118             # Transliteration of above line is: Pertsa L'va Il'icha
119              
120             =head1 TO DO
121              
122             1. Inflect any nouns, any words, anything...
123              
124             =head1 EXPORT
125              
126             Function C and
127             C are exported by default.
128              
129             Also you can export only case names:
130              
131             use Lingua::RU::Inflect qw/:cases/;
132              
133             Or only subs and genders
134              
135             use Lingua::RU::Inflect qw/:subs :genders/;
136              
137             Or everything: subs, aliases, genders and case names:
138              
139             use Lingua::RU::Inflect qw/:all/; # or
140             use Lingua::RU::Inflect qw/:cases :genders :subs/;
141              
142             =head1 FUNCTIONS
143              
144             =head2 detect_gender_by_given_name
145              
146             Try to detect gender by name. Up to three arguments expected:
147             lastname, firstname, patronym.
148              
149             Return C, C for successful detection
150             or C when function can't detect gender.
151              
152             =head3 Detection rules
153              
154             When name match some rule, rest of rules are ignored.
155              
156             =over 4
157              
158             =item 1
159              
160             Patronym (russian отчество — otchestvo), if presented, gives unambiguous
161             detection rules: feminine patronyms ends with “na”, masculine ones ends
162             with “ich” and “ych”.
163              
164             =item 2
165              
166             Most of russian feminine firstnames ends to vowels “a” and “ya”.
167             Most of russian masculine firstnames ends to consonants.
168              
169             There's exists exceptions for both rules: feminine names such as russian
170             name Lubov' (Любовь) and foreign names Ruf' (Руфь), Rachil' (Рахиль)
171             etc. Masculine names also often have affectionate diminutive forms:
172             Alyosha (Алёша) for Alexey (Алексей), Kolya (Коля) for Nickolay
173             (Николай) etc. Some affectionate diminutive names are ambiguous: Sasha
174             (Саша) is diminutive name for feminine name Alexandra (Александра) and
175             for masculine name Alexander (Александр), Zhenya (Женя) is diminutive
176             name for feminine name Eugenia (Евгения) and for masculine name Eugene
177             (Евгений) etc.
178              
179             These exceptions are processed.
180              
181             When got ambiguous result, function try to use next rule.
182              
183             =item 3
184              
185             Most of russian lastnames derived from possessive nouns (and names).
186             Feminine forms of these lastnames ends to “a”.
187             Some lastnames derived from adjectives. Feminine forms of these
188             lastnames ends to “ya”.
189              
190             =back
191              
192             =cut
193              
194             sub detect_gender_by_given_name {
195 103     103 1 1008 my ( $lastname, $firstname, $patronym ) = @_;
196 103   100     175 map { $_ ||= '' } ( $lastname, $firstname, $patronym );
  309         719  
197 103         132 my $ambiguous = 0;
198              
199             # Detect by patronym
200             # Russian
201 103 100       228 return FEMININE if $patronym =~ /на$/;
202 98 100       198 return MASCULINE if $patronym =~ /[иы]ч$/;
203             # Tatar and Azerbaijani
204 4 100   4   35 return FEMININE if $patronym =~ /\bкызы$/i;
  4         12  
  4         45  
  89         185  
205 87 100       167 return MASCULINE if $patronym =~ /\b(оглы|улы)$/i;
206             # Icelandic
207 83 100       136 return FEMININE if $patronym =~ /доттир$/;
208 82 100       126 return MASCULINE if $patronym =~ /сон$/;
209              
210             # Detect by firstname
211             # Drop all names except first
212 81         266 $firstname =~ s/[\s\-].*//;
213              
214             # Process exceptions
215 81 100   12385   300 return FEMININE if any { $firstname eq $_ } ( &_FEMININE_NAMES );
  12385         11873  
216 69 100   10217   456 return MASCULINE if any { $firstname eq $_ } ( &_MASCULINE_NAMES );
  10217         9985  
217              
218             map {
219 50 100 50     265 $ambiguous++ && last if $firstname eq $_;
  350         601  
220             } ( &_AMBIGUOUS_NAMES );
221              
222 50 100       103 unless ( $ambiguous ) {
223             # Feminine firstnames ends to vowels
224 44 100       167 return FEMININE if $firstname =~ /[ая]$/;
225             # Masculine firstnames ends to consonants
226 37 50       158 return MASCULINE if $firstname !~ /[аеёиоуыэюя]$/;
227             } # unless
228              
229             # Detect by lastname
230             # possessive names
231 6 100       32 return FEMININE if $lastname =~ /(ев|ин|ын|ёв|ов)а$/;
232 4 100       32 return MASCULINE if $lastname =~ /(ев|ин|ын|ёв|ов)$/;
233             # adjectives
234 2 50       16 return FEMININE if $lastname =~ /(ая|яя)$/;
235 2 50       18 return MASCULINE if $lastname =~ /(ий|ый)$/;
236              
237             # Unknown or ambiguous name
238 2         8 return undef;
239             }
240              
241             =head2 _inflect_given_name
242              
243             Inflects name of given gender to given case.
244             Up to 5 arguments expected:
245             I, I, I, I, I.
246             I, I, I must be in Nominative.
247              
248             Returns list which contains inflected I, I, I.
249              
250             =cut
251              
252             sub _inflect_given_name {
253 62     62   90 my $gender = shift;
254 62         73 my $case = shift;
255              
256 62 50       99 return @_ if $case eq NOMINATIVE;
257             return
258 62 50 33     199 if $case < GENITIVE
259             || $case > PREPOSITIONAL;
260              
261 62         102 my ( $lastname, $firstname, $patronym ) = @_;
262 62   100     87 map { $_ ||= '' } ( $lastname, $firstname, $patronym );
  186         365  
263              
264             # Patronyms
265             {
266 62 100       102 last unless $patronym;
267              
268 7 100       22 last if $patronym =~ s/на$/qw(ны не ну ной не)[$case]/e;
  2         8  
269 5 100       10 last if $patronym =~ s/ыч$/qw(ыча ычу ыча ычем ыче)[$case]/e;
  1         5  
270 4         16 $patronym =~ s/ич$/qw(ича ичу ича ичем иче)[$case]/e;
  4         13  
271 4         16 $patronym =~ s/(Иль|Кузьм|Фом)ичем$/$1ичом/;
272             }
273              
274             # Firstnames
275             {
276 62 100       73 last unless $firstname;
  62         91  
277              
278             # Exceptions
279 60         99 $firstname =~ s/^Лев$/Льв/;
280 60         105 $firstname =~ s/^Павел$/Павл/;
281 60         84 $firstname =~ s/^Пётр$/Петр/;
282 60         66 $firstname =~ s/^Христос$/Христ/;
283              
284             # Names which ends to vowels “o”, “yo”, “u”, “yu”, “y”, “i”, “e”, “ye”
285             # and to pairs of vowels except “yeya”, “iya”
286             # can not be inflected
287              
288 60 100       170 last if $firstname =~ /[еёиоуыэю]$/i;
289 55 50       134 last if $firstname =~ /[аеёиоуыэюя]а$/i;
290 55 50       111 last if $firstname =~ /[аёоуыэюя]я$/i;
291             last
292             if (
293 55 100 100     187 !defined $gender
      100        
294             || $gender == FEMININE
295             )
296             && $firstname =~ /[бвгджзклмнйпрстфхцчшщ]$/i;
297              
298 54 50       99 last if $firstname =~ s/ия$/qw(ии ии ию ией ие)[$case]/e;
  0         0  
299 54 100       97 last if $firstname =~ s/([гжйкхчшщ])а$/$1.qw(и е у ой е)[$case]/e;
  2         10  
300 52 100       112 last if $firstname =~ s/а$/qw(ы е у ой е)[$case]/e;
  11         48  
301 41 100       66 last if $firstname =~ s/мя$/qw(мени мени мя менем мени)[$case]/e; # common nouns such as “Imya” (Name)
  1         34  
302 40 100       73 last if $firstname =~ s/я$/qw(и е ю ей е)[$case]/e;
  4         15  
303 36 100       66 last if $firstname =~ s/й$/qw(я ю я ем е)[$case]/e;
  3         12  
304              
305             # Same endings, but different gender
306 33 100       53 if ( $gender == MASCULINE ) {
    50          
307 32 100       61 last if $firstname =~ s/ь$/qw(я ю я ем е)[$case]/e;
  1         5  
308             }
309             elsif ( $gender == FEMININE ) {
310 1 50       4 last if $firstname =~ s/ь$/qw(и и ь ью и)[$case]/e;
  1         6  
311             }
312              
313             # Rest of names which ends to consonants
314 31         56 $firstname .= qw(а у а ом е)[$case];
315             } # Firstnames
316              
317             # Lastnames
318             {
319 62 100       66 last unless $lastname;
  62         79  
  62         84  
320 60 100       92 last unless defined $gender;
321              
322             # Indeclinable
323 59 100       131 last if $lastname =~ /[еёиоуыэю]$/i;
324 58 50       164 last if $lastname =~ /[аеёиоуыэюя]а$/i;
325             # Lastnames such as “Belaya” and “Sinyaya”
326             # which ends to “aya” and “yaya” must be inflected
327 58 50       133 last if $lastname =~ /[ёоуыэю]я$/i;
328              
329             # Undeclinable lastnames -ikh and -ykh
330             last
331 58 100 100     218 if $lastname =~ /ых$/i
332             && $lastname !~ /^(Булт|(|От|Пере|Роз)д|Жм|П)ых$/i;
333              
334             last
335 56 100 100     281 if $lastname =~ /(кар|[гжкнхшщь])их$/i
336             && $lastname !~ /^(Мин|Мюнн)их$/;
337             # TODO Add more German exceptions
338             # See http://wiki-de.genealogy.net/Kategorie:Familienname_mit_gleicher_Endung
339              
340             # Feminine lastnames
341             last
342 51 100 66     118 if $lastname =~ /(ин|ын|ев|ёв|ов)а$/
343 5         18 && $lastname =~ s/а$/qw(ой ой у ой ой)[$case]/e;
344             # TODO Does not process usual worls: Podkova, Sova etc
345             # TODO Decide/search what can I do with ambigous names:
346             # Kalina, Mashina, Smorodina etc
347              
348             # Rest of masculine lastnames
349 46 100       71 if ( $gender == FEMININE ) {
350             # As adjectives
351 4 50       9 last if $lastname =~ s/ая$/qw(ой ой ую ой ой)[$case]/e;
  0         0  
352 4 50       9 last if $lastname =~ s/яя$/qw(ей ей юю ей ей)[$case]/e;
  0         0  
353             }
354             else { # MASCULINE
355              
356             # Exceptions
357 42         53 $lastname =~ s/^Христос$/Христ/;
358 42         98 $lastname =~ s/([аеёиоуыэюя]л)ец$/$1ьц/i;
359 42         93 $lastname =~ s/([аеёиоуыэюя][бвгджзкмнйпрстфхцчшщ])ец$/$1ц/i;
360 42         73 $lastname =~ s/([аеёиоуыэюя])ец$/$1йц/;
361 42         57 $lastname =~ s/(З)аяц$/$1айц/;
362              
363             # Possessive
364             last
365 42 100 66     128 if $lastname =~ /(ин|ын|ев|ёв|ов)$/
366             && ( $lastname .= qw(а у а ым е)[$case] );
367              
368             # Adjective
369 38 100       71 last if $lastname =~ s/кий$/qw(кого кому кого ким ком)[$case]/e;
  1         5  
370 37 50       60 last if $lastname =~ s/ий$/qw(его ему его им ем)[$case]/e;
  0         0  
371 37 50       67 last if $lastname =~ s/ый$/qw(ого ому ого ым ом)[$case]/e;
  0         0  
372 37 100       54 last if $lastname =~ s/ой$/qw(ого ому ого ым ом)[$case]/e;
  1         5  
373              
374             # Other
375 36 100       65 last if $lastname =~ s/([гжйкхчшщ])а$/$1.qw(и е у ой е)[$case]/e;
  2         10  
376 34 100       50 last if $lastname =~ s/а$/qw(ы е у ой е)[$case]/e;
  1         4  
377 33 100       62 last if $lastname =~ s/мя$/qw(мени мени мя менем мени)[$case]/e;
  1         4  
378              
379 32 100       53 last if $lastname =~ /ая$/;
380 31 50       52 last if $lastname =~ s/я$/qw(и е ю ёй е)[$case]/e;
  0         0  
381 31 50       41 last if $lastname =~ s/й$/qw(я ю й ем е)[$case]/e;
  0         0  
382 31 50       53 last if $lastname =~ s/ь$/qw(я ю я ем е)[$case]/e;
  0         0  
383 31         50 $lastname .= qw(а у а ом е)[$case];
384             } # if
385             } # Lastnames
386              
387 62         474 return ( $lastname, $firstname, $patronym );
388             } # sub _inflect_given_name
389              
390              
391             =head2 inflect_given_name
392              
393             Detects gender by given name and inflect parts of this name.
394              
395             Expects for up to 4 arguments:
396             I, I, I, I
397              
398             Available I are: C, C, C,
399             C, C, C.
400              
401             It returns list which contains
402             inflected I, I, I
403              
404             =cut
405              
406             sub inflect_given_name {
407 63     63 1 192 my $case = shift;
408 63 100       139 return @_ if $case eq NOMINATIVE;
409 62         121 my @name = _inflect_given_name(
410             detect_gender_by_given_name( @_ ), $case, @_
411             );
412             } # sub inflect_given_name
413              
414              
415             # Exceptions:
416              
417             # Masculine names which ends to vowels “a” and “ya”
418             sub _MASCULINE_NAMES () {
419 69     69   1045 return qw(
420             Аба Азарья Акива Аккужа Аникита Алёша Али Альберто Андрюха Андрюша
421             Арчи Аса
422             Байгужа Боря Бруно Вафа Вано Ваня Вася Витя Вова Володя
423             Габдулла Габидулла Гаврила Гаврило Гадельша Гайнулла Гайса Гайфулла
424             Галилео Галимша Галиулла Гарри Гата Гдалья Гийора Гиля Гога Гоша Гошеа
425             Данила Данило Данко Дарко Джанни Джеффри Джонни Джордано Джошуа Джиханша Дима
426             Жора Зайнулла Закария Зия Зосима Зхарья Зыя Зяма
427             Идельгужа Иегуда Иехуда Иешуа Изя Илия Ильмурза Илья Илюха Илюша
428             Иона Исайя Иуда Иудушка Йегошуа Йегуда Йехуда Йедидья Йося
429             Карагужа Коля Коленька Костя Кузьма Кузенька Кузя
430             Лео Лёва Лёвушка Лёха Лёша Лёшенька Луи Лука Ларри
431             Марио Марданша Метью Микола Мирза Миха Михайло Миша Мишка Мойша Моня
432             Муртаза Муса Мусса Мустафа Мэтью Нафаня Никита Никола Николя Нэта
433             Нэхэмья Овадья Отто Петя Птахья Рахматулла Риза Рома
434             Савва Садко Сафа Серёга Серёжа Сила Симха Сэадья
435             Тао Тео Тоби Товия Толя Томми
436             Федя Фима Фока Фома Фредди
437             Хамза Хананья Харви Харли Хосе Хью Хьюго Цфанья Чарли
438             Шалва Шахна Шломо Шота Шрага
439             Эзра Элайджа Элиягу Элияху Элиша Элькана Энзо Энрике Энрико Эрнесто
440             Юмагужа Юра Ярулла Яхья Яша
441             )
442             }
443              
444             # Feminine names which ends to consonants
445             sub _FEMININE_NAMES () {
446 81     81   1215 return qw(
447             Абигейл Айгуль Айгюль Айзиряк Айнур Айрис Алсу
448             Альфинур Амели Анне Асылгюль
449             Бадар Бадиян Банат Бедер Бибикамал Бибинур Брижит Бриджит
450             Гайниджамал Гайникамал Гарриет Гаухар Гиффат Грейс
451             Гузель Гулендем Гульбадиян Гульдар Гульджамал Гульджихан Гульехан
452             Гульзар Гульжан Гулькей Гульназ Гульнар Гульнур Гульсем Гульсесек
453             Гульсибар Гульчачак Гульшат Гульшаян Гульюзум Гульямал Гюзель Гюльчатай
454             Дейзи Джамал Джанет Джаухар Дженет Джихан Дильбар Диляфруз Дэйзи
455             Жанет Жульет Жюльет
456             Зайнаб Зайнап Зейнаб Зубарджат Зуберьят Изабель Ильсёяр Ингрид
457             Камяр Карасес Карин Катрин Кейт Кэролайн Кэт Кэтрин Кямар
458             Лаурелин Лили Любовь Люси Ляйсан
459             Магинур Магруй Маргарет Марго Марлен Марьям Мери Мерилин Минджихан
460             Минлегюль Миньеган Мэри
461             Наркас Натали Нинель Нурджиган Нелли Нэлли Одри Патти Пэм
462             Райхан Раушан Рахель Рахиль Рейчел Рут Руфь Рэйчел
463             Сагадат Сагдат Сарбиназ Сарвар Сафин Сахибджамал Скарлет Софи Софико
464             Суваржат Сулпан Сумбуль Сурур Сюмбель Сясак Тамар Тансулпан
465             Умегульсум Уммегюльсем
466             Фарваз Фархинур Фиби Фирдаус Флоренс Хаджар Хажар Харриет Хаят Хуршид
467             Чечек Чулпан Шамсинур
468             Эбигейл Эбигейль Эвелин Эдит Элизабет Элен Элис Элли Эмили Энн
469             Эстер Эсфирь Этель
470             Юдифь Юдит Юндуз Ямал
471             )
472             }
473              
474             # Ambiguous names which can be masculine and feminine
475             sub _AMBIGUOUS_NAMES () {
476 50     50   103 return qw(
477             Валя Женя Мина Мишель Паша Саша Шура
478             )
479             }
480              
481             =head1 AUTHOR
482              
483             Alexander Sapozhnikov, C<< >>
484              
485             =head1 BUGS
486              
487             Please report any bugs or feature requests
488             to C, or through the web interface
489             at L.
490             I will be notified, and then
491             you'll automatically be notified of progress on your bug as I make changes.
492              
493             =head1 SUPPORT
494              
495             You can find documentation for this module with the perldoc command.
496              
497             perldoc Lingua::RU::Inflect
498              
499             You can also look for information at:
500              
501             =over 4
502              
503             =item * RT: CPAN's request tracker
504              
505             L
506              
507             =item * AnnoCPAN: Annotated CPAN documentation
508              
509             L
510              
511             =item * CPAN Ratings
512              
513             L
514              
515             =item * Search CPAN
516              
517             L
518              
519             =item * Public repository at github
520              
521             L
522              
523             =back
524              
525             =head1 SEE ALSO
526              
527             Russian translation of this documentation available
528             at F
529              
530             =head1 ACKNOWLEDGEMENTS
531              
532             L
533             and L
534             (in Russian) for rules of declension.
535              
536             L for directory of names.
537              
538             =head1 COPYRIGHT & LICENSE
539              
540             Copyright 2009-2018 Alexander Sapozhnikov.
541              
542             This program is free software; you can redistribute it and/or modify it
543             under the terms of either: the GNU General Public License as published
544             by the Free Software Foundation; or the Artistic License.
545              
546             See http://dev.perl.org/licenses/ for more information.
547              
548             =cut
549              
550             1;