File Coverage

blib/lib/Lingua/RU/Inflect.pm
Criterion Covered Total %
statement 126 135 93.3
branch 86 106 81.1
condition 16 21 76.1
subroutine 15 15 100.0
pod 2 2 100.0
total 245 279 87.8


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