1
|
|
|
<?php |
2
|
|
|
namespace morphos\Russian; |
3
|
|
|
|
4
|
|
|
use morphos\S; |
5
|
|
|
|
6
|
|
|
/** |
7
|
|
|
* Rules are from http://morpher.ru/Russian/Noun.aspx |
8
|
|
|
*/ |
9
|
|
|
class Plurality extends \morphos\Plurality implements Cases { |
10
|
|
|
use RussianLanguage, CasesHelper; |
11
|
|
|
|
12
|
|
|
const ONE = 1; |
13
|
|
|
const TWO_FOUR = 2; |
14
|
|
|
const FIVE_OTHER = 3; |
15
|
|
|
|
16
|
|
|
static protected $neuterExceptions = array( |
17
|
|
|
'поле', |
18
|
|
|
'море', |
19
|
|
|
); |
20
|
|
|
|
21
|
|
|
static protected $immutableWords = array( |
22
|
|
|
'евро', |
23
|
|
|
'пенни', |
24
|
|
|
); |
25
|
|
|
|
26
|
|
|
static protected $runawayVowelsExceptions = array( |
27
|
|
|
'писе*ц', |
28
|
|
|
'песе*ц', |
29
|
|
|
'глото*к', |
30
|
|
|
); |
31
|
|
|
|
32
|
|
|
static protected $runawayVowelsNormalized = false; |
33
|
|
|
|
34
|
|
|
static protected function getRunAwayVowelsList() { |
35
|
|
|
if (self::$runawayVowelsNormalized === false) { |
36
|
|
|
self::$runawayVowelsNormalized = array(); |
|
|
|
|
37
|
|
|
foreach (self::$runawayVowelsExceptions as $word) { |
38
|
|
|
self::$runawayVowelsNormalized[str_replace('*', null, $word)] = S::indexOf($word, '*') - 1; |
39
|
|
|
} |
40
|
|
|
} |
41
|
|
|
return self::$runawayVowelsNormalized; |
42
|
|
|
} |
43
|
|
|
|
44
|
|
|
static public function pluralize($word, $count = 2, $animateness = false) { |
45
|
|
|
switch (self::getNumeralForm($count)) { |
46
|
|
|
case self::ONE: |
47
|
|
|
return $word; |
48
|
|
|
case self::TWO_FOUR: |
49
|
|
|
return GeneralDeclension::getCase($word, self::RODIT, $animateness); |
50
|
|
|
case self::FIVE_OTHER: |
51
|
|
|
return Plurality::getCase($word, self::RODIT, $animateness); |
52
|
|
|
} |
53
|
|
|
} |
54
|
|
|
|
55
|
|
|
static public function getNumeralForm($count) { |
56
|
|
|
if($count > 100) { |
57
|
|
|
$count %= 100; |
58
|
|
|
} |
59
|
|
|
$ending = $count % 10; |
60
|
|
|
if (($count > 20 && $ending == 1) || $count == 1) |
61
|
|
|
return self::ONE; |
62
|
|
|
else if (($count > 20 && in_array($ending, range(2, 4))) || in_array($count, range(2, 4))) |
63
|
|
|
return self::TWO_FOUR; |
64
|
|
|
else |
65
|
|
|
return self::FIVE_OTHER; |
66
|
|
|
} |
67
|
|
|
|
68
|
|
|
static public function getCase($word, $case, $animateness = false) { |
69
|
|
|
$case = self::canonizeCase($case); |
70
|
|
|
$forms = self::getCases($word, $animateness); |
71
|
|
|
return $forms[$case]; |
72
|
|
|
} |
73
|
|
|
|
74
|
|
|
static public function getCases($word, $animateness = false) { |
75
|
|
|
$word = S::lower($word); |
76
|
|
|
|
77
|
|
|
if (in_array($word, self::$immutableWords)) { |
78
|
|
|
return array( |
79
|
|
|
self::IMENIT => $word, |
80
|
|
|
self::RODIT => $word, |
81
|
|
|
self::DAT => $word, |
82
|
|
|
self::VINIT => $word, |
83
|
|
|
self::TVORIT => $word, |
84
|
|
|
self::PREDLOJ => self::choosePrepositionByFirstLetter($word, 'об', 'о').' '.$word, |
85
|
|
|
); |
86
|
|
|
} |
87
|
|
|
|
88
|
|
|
// Адъективное склонение (Сущ, образованные от прилагательных и причастий) - прохожий, существительное |
89
|
|
View Code Duplication |
if (in_array(S::slice($word, -2), array('ой', 'ий', 'ый', 'ая', 'ое', 'ее')) && $word != 'гений') { |
|
|
|
|
90
|
|
|
return self::declinateAdjective($word, $animateness); |
91
|
|
|
} |
92
|
|
|
|
93
|
|
|
// Субстантивное склонение (существительные) |
94
|
|
|
return self::declinateSubstative($word, $animateness); |
95
|
|
|
} |
96
|
|
|
|
97
|
|
|
static protected function declinateSubstative($word, $animateness) { |
98
|
|
|
$prefix = S::slice($word, 0, -1); |
99
|
|
|
$last = S::slice($word, -1); |
100
|
|
|
|
101
|
|
|
$runaway_vowels_list = static::getRunAwayVowelsList(); |
102
|
|
|
if (isset($runaway_vowels_list[$word])) { |
103
|
|
|
$vowel_offset = $runaway_vowels_list[$word]; |
104
|
|
|
$word = S::slice($word, 0, $vowel_offset) . S::slice($word, $vowel_offset + 1); |
105
|
|
|
} |
106
|
|
|
|
107
|
|
|
if (($declension = GeneralDeclension::getDeclension($word)) == GeneralDeclension::SECOND_DECLENSION) { |
108
|
|
|
$soft_last = $last == 'й' || (in_array($last, ['ь', 'е', 'ё', 'ю', 'я']) && (self::isConsonant(S::slice($word, -2, -1)) || S::slice($word, -2, -1) == 'и')); |
109
|
|
|
$prefix = GeneralDeclension::getPrefixOfSecondDeclension($word, $last); |
110
|
|
|
} else if ($declension == GeneralDeclension::FIRST_DECLENSION) { |
111
|
|
|
$soft_last = self::checkLastConsonantSoftness($word); |
112
|
|
|
} else { |
113
|
|
|
$soft_last = S::slice($word, -2) == 'сь'; |
114
|
|
|
} |
115
|
|
|
|
116
|
|
|
$forms = array(); |
117
|
|
|
|
118
|
|
|
if ($last == 'ч' || in_array(S::slice($word, -2), array('чь', 'сь')) || (self::isVowel($last) && in_array(S::slice($word, -2, -1), array('ч', 'к')))) // before ч, чь, сь, ч+vowel, к+vowel |
119
|
|
|
$forms[Cases::IMENIT] = $prefix.'и'; |
120
|
|
View Code Duplication |
else if ($last == 'н' || $last == 'ц') |
|
|
|
|
121
|
|
|
$forms[Cases::IMENIT] = $prefix.'ы'; |
122
|
|
|
else |
123
|
|
|
$forms[Cases::IMENIT] = self::chooseVowelAfterConsonant($last, $soft_last, $prefix.'я', $prefix.'а'); |
124
|
|
|
|
125
|
|
|
// RODIT |
126
|
|
|
if ($word == 'письмо') |
127
|
|
|
$forms[Cases::RODIT] = 'писем'; |
128
|
|
|
else if (in_array($last, array('о', 'е'))) { |
129
|
|
|
// exceptions |
130
|
|
|
if (in_array($word, self::$neuterExceptions)) |
131
|
|
|
$forms[Cases::RODIT] = $prefix.'ей'; |
132
|
|
View Code Duplication |
else if (S::slice($word, -2, -1) == 'и') |
|
|
|
|
133
|
|
|
$forms[Cases::RODIT] = $prefix.'й'; |
134
|
|
|
else |
135
|
|
|
$forms[Cases::RODIT] = $prefix; |
136
|
|
|
} |
137
|
|
|
else if (S::slice($word, -2) == 'ка') { // words ending with -ка: чашка, вилка, ложка, тарелка, копейка, батарейка |
138
|
|
|
if (S::slice($word, -3, -2) == 'л') $forms[Cases::RODIT] = S::slice($word, 0, -2).'ок'; |
139
|
|
|
else if (S::slice($word, -3, -2) == 'й') $forms[Cases::RODIT] = S::slice($word, 0, -3).'ек'; |
140
|
|
|
else $forms[Cases::RODIT] = S::slice($word, 0, -2).'ек'; |
141
|
|
|
} |
142
|
|
|
else if (in_array($last, array('а'))) // обида, ябеда |
143
|
|
|
$forms[Cases::RODIT] = $prefix; |
144
|
|
|
else if (in_array($last, array('я'))) // молния |
145
|
|
|
$forms[Cases::RODIT] = $prefix.'й'; |
146
|
|
|
else if (RussianLanguage::isHissingConsonant($last) || ($soft_last && $last != 'й') || in_array(S::slice($word, -2), array('чь', 'сь'))) |
147
|
|
|
$forms[Cases::RODIT] = $prefix.'ей'; |
148
|
|
View Code Duplication |
else if ($last == 'й') |
|
|
|
|
149
|
|
|
$forms[Cases::RODIT] = $prefix.'ев'; |
150
|
|
|
else // (self::isConsonant($last) && !RussianLanguage::isHissingConsonant($last)) |
151
|
|
|
$forms[Cases::RODIT] = $prefix.'ов'; |
152
|
|
|
|
153
|
|
|
// DAT |
154
|
|
|
$forms[Cases::DAT] = self::chooseVowelAfterConsonant($last, $soft_last && S::slice($word, -2, -1) != 'ч', $prefix.'ям', $prefix.'ам'); |
155
|
|
|
|
156
|
|
|
// VINIT |
157
|
|
|
$forms[Cases::VINIT] = GeneralDeclension::getVinitCaseByAnimateness($forms, $animateness); |
158
|
|
|
|
159
|
|
|
// TVORIT |
160
|
|
|
// my personal rule |
161
|
|
|
if ($last == 'ь' && $declension == GeneralDeclension::THIRD_DECLENSION && !in_array(S::slice($word, -2), array('чь', 'сь'))) { |
162
|
|
|
$forms[Cases::TVORIT] = $prefix.'ми'; |
163
|
|
|
} else { |
164
|
|
|
$forms[Cases::TVORIT] = self::chooseVowelAfterConsonant($last, $soft_last && S::slice($word, -2, -1) != 'ч', $prefix.'ями', $prefix.'ами'); |
165
|
|
|
} |
166
|
|
|
|
167
|
|
|
// PREDLOJ |
168
|
|
|
$forms[Cases::PREDLOJ] = self::chooseVowelAfterConsonant($last, $soft_last && S::slice($word, -2, -1) != 'ч', $prefix.'ях', $prefix.'ах'); |
169
|
|
|
$forms[Cases::PREDLOJ] = self::choosePrepositionByFirstLetter($forms[Cases::PREDLOJ], 'об', 'о').' '.$forms[Cases::PREDLOJ]; |
170
|
|
|
return $forms; |
171
|
|
|
} |
172
|
|
|
|
173
|
|
|
/** |
174
|
|
|
* Rules are from http://rusgram.narod.ru/1216-1231.html |
175
|
|
|
*/ |
176
|
|
|
static protected function declinateAdjective($word, $animateness) { |
177
|
|
|
$prefix = S::slice($word, 0, -2); |
178
|
|
|
$vowel = self::isHissingConsonant(S::slice($prefix, -1)) ? 'и' : 'ы'; |
179
|
|
|
return array( |
180
|
|
|
Cases::IMENIT => $prefix.$vowel.'е', |
181
|
|
|
Cases::RODIT => $prefix.$vowel.'х', |
182
|
|
|
Cases::DAT => $prefix.$vowel.'м', |
183
|
|
|
Cases::VINIT => $prefix.$vowel.($animateness ? 'х' : 'е'), |
184
|
|
|
Cases::TVORIT => $prefix.$vowel.'ми', |
185
|
|
|
Cases::PREDLOJ => self::choosePrepositionByFirstLetter($prefix, 'об', 'о').' '.$prefix.$vowel.'х', |
186
|
|
|
); |
187
|
|
|
} |
188
|
|
|
} |
189
|
|
|
|
Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.
Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..