1
|
|
|
<?php |
2
|
|
|
namespace morphos\Russian; |
3
|
|
|
|
4
|
|
|
/** |
5
|
|
|
* Rules are from http://morpher.ru/Russian/Noun.aspx |
6
|
|
|
*/ |
7
|
|
|
class GeneralDeclension extends \morphos\GeneralDeclension implements Cases { |
8
|
|
|
use RussianLanguage; |
9
|
|
|
|
10
|
|
|
const FIRST_DECLENSION = 1; |
11
|
|
|
const SECOND_DECLENSION = 2; |
12
|
|
|
const THIRD_DECLENSION = 3; |
13
|
|
|
|
14
|
|
|
const FIRST_SCHOOL_DECLENSION = 2; |
15
|
|
|
const SECOND_SCHOOL_DECLENSION = 1; |
16
|
|
|
const THIRD_SCHOOL_DECLENSION = 3; |
17
|
|
|
|
18
|
|
|
protected $exceptions = array( |
19
|
|
|
'бремя', |
20
|
|
|
'вымя', |
21
|
|
|
'темя', |
22
|
|
|
'пламя', |
23
|
|
|
'стремя', |
24
|
|
|
'пламя', |
25
|
|
|
'время', |
26
|
|
|
'знамя', |
27
|
|
|
'имя', |
28
|
|
|
'племя', |
29
|
|
|
'семя', |
30
|
|
|
); |
31
|
|
|
|
32
|
|
|
public function hasForms($word, $animate = false) { |
33
|
|
|
$word = lower($word); |
34
|
|
|
if (in_array(slice($word, -1), array('у', 'и', 'е', 'о', 'ю'))) |
35
|
|
|
return false; |
36
|
|
|
return true; |
37
|
|
|
} |
38
|
|
|
|
39
|
|
|
public function getDeclension($word) { |
40
|
|
|
$word = lower($word); |
41
|
|
|
$last = slice($word, -1); |
42
|
|
|
if ($this->isConsonant($last) || in_array($last, ['о', 'е', 'ё']) || ($last == 'ь' && $this->isConsonant(slice($word, -2, -1)) && !$this->isHissingConsonant(slice($word, -2, -1)))) { |
43
|
|
|
return 1; |
44
|
|
|
} else if (in_array($last, ['а', 'я']) && slice($word, -2) != 'мя') { |
45
|
|
|
return 2; |
46
|
|
|
} else { |
47
|
|
|
return 3; |
48
|
|
|
} |
49
|
|
|
} |
50
|
|
|
|
51
|
|
|
public function getForms($word, $animate = false) { |
52
|
|
|
$word = lower($word); |
53
|
|
|
|
54
|
|
|
if (isset($this->exceptions[$word])) { |
55
|
|
|
$prefix = slice($word, -1); |
56
|
|
|
return array( |
57
|
|
|
self::IMENIT => $word, |
58
|
|
|
self::RODIT => $prefix.'и', |
59
|
|
|
self::DAT => $prefix.'и', |
60
|
|
|
self::VINIT => $word, |
61
|
|
|
self::TVORIT => $prefix, |
62
|
|
|
self::PREDLOJ => $this->choosePrepositionByFirstLetter($prefix, 'об', 'о').' '.$prefix.'и', |
63
|
|
|
); |
64
|
|
|
} |
65
|
|
|
|
66
|
|
|
switch ($this->getDeclension($word)) { |
67
|
|
|
case self::FIRST_DECLENSION: |
68
|
|
|
return $this->declinateFirstDeclension($word, $animate); |
69
|
|
|
case self::SECOND_DECLENSION: |
70
|
|
|
return $this->declinateSecondDeclension($word); |
71
|
|
|
case self::THIRD_DECLENSION: |
72
|
|
|
return $this->declinateThirdDeclension($word); |
73
|
|
|
} |
74
|
|
|
} |
75
|
|
|
|
76
|
|
|
public function declinateFirstDeclension($word, $animate = false) { |
77
|
|
|
$word = lower($word); |
78
|
|
|
$last = slice($word, -1); |
79
|
|
|
$soft_last = $last == 'й' || (in_array($last, ['ь', 'е', 'ё', 'ю', 'я']) && $this->isConsonant(slice($word, -2, -1))); |
80
|
|
|
$prefix = $this->getPrefixOfFirstDeclension($word, $last); |
81
|
|
|
$forms = array( |
82
|
|
|
Cases::IMENIT_1 => $word, |
83
|
|
|
); |
84
|
|
|
|
85
|
|
|
// RODIT_2 |
86
|
|
|
$forms[Cases::RODIT_2] = $this->chooseVowelAfterConsonant($last, $soft_last, $prefix.'я', $prefix.'а'); |
87
|
|
|
|
88
|
|
|
// DAT_3 |
89
|
|
|
$forms[Cases::DAT_3] = $this->chooseVowelAfterConsonant($last, $soft_last, $prefix.'ю', $prefix.'у'); |
90
|
|
|
|
91
|
|
|
// VINIT_4 |
92
|
|
|
if (in_array($last, ['о', 'е', 'ё'])) |
93
|
|
|
$forms[Cases::VINIT_4] = $word; |
94
|
|
|
else { |
95
|
|
|
$forms[Cases::VINIT_4] = $this->getVinitCaseByAnimateness($forms, $animate); |
96
|
|
|
} |
97
|
|
|
|
98
|
|
|
// TVORIT_5 |
99
|
|
|
// if ($last == 'ь') |
100
|
|
|
// $forms[Cases::TVORIT_5] = $prefix.'ом'; |
101
|
|
|
// else if ($last == 'й' || ($this->isConsonant($last) && !$this->isHissingConsonant($last))) |
102
|
|
|
// $forms[Cases::TVORIT_5] = $prefix.'ем'; |
103
|
|
|
// else |
104
|
|
|
// $forms[Cases::TVORIT_5] = $prefix.'ом'; # http://morpher.ru/Russian/Spelling.aspx#sibilant |
105
|
|
|
if ($this->isHissingConsonant($last) || $last == 'ц') { |
106
|
|
|
$forms[Cases::TVORIT_5] = $prefix.'ем'; |
107
|
|
|
} else if (in_array($last, ['й'/*, 'ч', 'щ'*/]) || $soft_last) { |
108
|
|
|
$forms[Cases::TVORIT_5] = $prefix.'ем'; |
109
|
|
|
} else { |
110
|
|
|
$forms[Cases::TVORIT_5] = $prefix.'ом'; |
111
|
|
|
} |
112
|
|
|
|
113
|
|
|
// PREDLOJ_6 |
114
|
|
|
$forms[Cases::PREDLOJ_6] = $this->getPredCaseOf12Declensions($word, $last, $prefix); |
115
|
|
|
$forms[Cases::PREDLOJ_6] = $this->choosePrepositionByFirstLetter($forms[Cases::PREDLOJ_6], 'об', 'о').' '.$forms[Cases::PREDLOJ_6]; |
116
|
|
|
|
117
|
|
|
return $forms; |
118
|
|
|
} |
119
|
|
|
|
120
|
|
|
public function declinateSecondDeclension($word) { |
121
|
|
|
$word = lower($word); |
122
|
|
|
$prefix = slice($word, 0, -1); |
123
|
|
|
$last = slice($word, -1); |
124
|
|
|
$soft_last = $this->checkLastConsonantSoftness($word); |
125
|
|
|
$forms = array( |
126
|
|
|
Cases::IMENIT_1 => $word, |
127
|
|
|
); |
128
|
|
|
|
129
|
|
|
// RODIT_2 |
130
|
|
|
$forms[Cases::RODIT_2] = $this->chooseVowelAfterConsonant($last, $soft_last, $prefix.'и', $prefix.'ы'); |
131
|
|
|
|
132
|
|
|
// DAT_3 |
133
|
|
|
$forms[Cases::DAT_3] = $this->getPredCaseOf12Declensions($word, $last, $prefix); |
134
|
|
|
|
135
|
|
|
// VINIT_4 |
136
|
|
|
$forms[Cases::VINIT_4] = $this->chooseVowelAfterConsonant($last, $soft_last, $prefix.'ю', $prefix.'у'); |
137
|
|
|
|
138
|
|
|
// TVORIT_5 |
139
|
|
|
if ($last == 'ь') |
140
|
|
|
$forms[Cases::TVORIT_5] = $prefix.'ой'; |
141
|
|
|
else { |
142
|
|
|
$forms[Cases::TVORIT_5] = $this->chooseVowelAfterConsonant($last, $soft_last, $prefix.'ей', $prefix.'ой'); |
143
|
|
|
} |
144
|
|
|
|
145
|
|
|
// if ($last == 'й' || ($this->isConsonant($last) && !$this->isHissingConsonant($last)) || $this->checkLastConsonantSoftness($word)) |
146
|
|
|
// $forms[Cases::TVORIT_5] = $prefix.'ей'; |
147
|
|
|
// else |
148
|
|
|
// $forms[Cases::TVORIT_5] = $prefix.'ой'; # http://morpher.ru/Russian/Spelling.aspx#sibilant |
149
|
|
|
|
150
|
|
|
// PREDLOJ_6 the same as DAT_3 |
151
|
|
|
$forms[Cases::PREDLOJ_6] = $this->choosePrepositionByFirstLetter($forms[Cases::DAT_3], 'об', 'о').' '.$forms[Cases::DAT_3]; |
152
|
|
|
return $forms; |
153
|
|
|
} |
154
|
|
|
|
155
|
|
|
public function declinateThirdDeclension($word) { |
156
|
|
|
$word = lower($word); |
157
|
|
|
$prefix = slice($word, 0, -1); |
158
|
|
|
return array( |
159
|
|
|
Cases::IMENIT_1 => $word, |
160
|
|
|
Cases::RODIT_2 => $prefix.'и', |
161
|
|
|
Cases::DAT_3 => $prefix.'и', |
162
|
|
|
Cases::VINIT_4 => $word, |
163
|
|
|
Cases::TVORIT_5 => $prefix.'ью', |
164
|
|
|
Cases::PREDLOJ_6 => $this->choosePrepositionByFirstLetter($prefix, 'об', 'о').' '.$prefix.'и', |
165
|
|
|
); |
166
|
|
|
} |
167
|
|
|
|
168
|
|
|
public function pluralizeAllDeclensions($word, $animate = false) { |
169
|
|
|
$word = lower($word); |
170
|
|
|
$prefix = slice($word, 0, -1); |
171
|
|
|
$last = slice($word, -1); |
172
|
|
|
|
173
|
|
|
if (($declension = $this->getDeclension($word)) == self::FIRST_DECLENSION) { |
174
|
|
|
$soft_last = $last == 'й' || (in_array($last, ['ь', 'е', 'ё', 'ю', 'я']) && $this->isConsonant(slice($word, -2, -1))); |
175
|
|
|
$prefix = $this->getPrefixOfFirstDeclension($word, $last); |
176
|
|
|
} else if ($declension == self::SECOND_DECLENSION) { |
177
|
|
|
$soft_last = $this->checkLastConsonantSoftness($word); |
178
|
|
|
} else { |
179
|
|
|
$soft_last = false; |
180
|
|
|
} |
181
|
|
|
|
182
|
|
|
$forms = array( |
183
|
|
|
Cases::IMENIT_1 => $this->chooseVowelAfterConsonant($last, $soft_last, $prefix.'я', $prefix.'а'), |
184
|
|
|
); |
185
|
|
|
|
186
|
|
|
// RODIT_2 |
187
|
|
View Code Duplication |
if ($this->isHissingConsonant($last) || ($soft_last && $last != 'й')) |
|
|
|
|
188
|
|
|
$forms[Cases::RODIT_2] = $prefix.'ей'; |
189
|
|
|
else if ($last == 'й') |
190
|
|
|
$forms[Cases::RODIT_2] = $prefix.'ев'; |
191
|
|
|
else // ($this->isConsonant($last) && !$this->isHissingConsonant($last)) |
192
|
|
|
$forms[Cases::RODIT_2] = $prefix.'ов'; |
193
|
|
|
|
194
|
|
|
// DAT_3 |
195
|
|
|
$forms[Cases::DAT_3] = $this->chooseVowelAfterConsonant($last, $soft_last, $prefix.'ям', $prefix.'ам'); |
196
|
|
|
|
197
|
|
|
// VINIT_4 |
198
|
|
|
$forms[Cases::VINIT_4] = $this->getVinitCaseByAnimateness($forms, $animate); |
199
|
|
|
|
200
|
|
|
// TVORIT_5 |
201
|
|
|
// my personal rule |
202
|
|
View Code Duplication |
if ($last == 'ь' && $declension == self::THIRD_DECLENSION) { |
|
|
|
|
203
|
|
|
$forms[Cases::TVORIT_5] = $prefix.'ми'; |
204
|
|
|
} else { |
205
|
|
|
$forms[Cases::TVORIT_5] = $this->chooseVowelAfterConsonant($last, $soft_last, $prefix.'ями', $prefix.'ами'); |
206
|
|
|
} |
207
|
|
|
|
208
|
|
|
// PREDLOJ_6 |
209
|
|
|
$forms[Cases::PREDLOJ_6] = $this->chooseVowelAfterConsonant($last, $soft_last, $prefix.'ях', $prefix.'ах'); |
210
|
|
|
$forms[Cases::PREDLOJ_6] = $this->choosePrepositionByFirstLetter($forms[Cases::PREDLOJ_6], 'об', 'о').' '.$forms[Cases::PREDLOJ_6]; |
211
|
|
|
return $forms; |
212
|
|
|
} |
213
|
|
|
|
214
|
|
|
public function getForm($word, $animate = false, $form) { |
215
|
|
|
$forms = $this->getForms($word, $animate); |
216
|
|
|
return $forms[$form]; |
217
|
|
|
} |
218
|
|
|
|
219
|
|
|
protected function getPrefixOfFirstDeclension($word, $last) { |
220
|
|
|
if (in_array($last, ['о', 'е', 'ё', 'ь', 'й'])) |
221
|
|
|
$prefix = slice($word, 0, -1); |
222
|
|
|
else |
223
|
|
|
$prefix = $word; |
224
|
|
|
return $prefix; |
225
|
|
|
} |
226
|
|
|
|
227
|
|
|
protected function getVinitCaseByAnimateness(array $forms, $animate) { |
228
|
|
|
if ($animate) |
229
|
|
|
return $forms[Cases::RODIT_2]; |
230
|
|
|
else |
231
|
|
|
return $forms[Cases::IMENIT_1]; |
232
|
|
|
} |
233
|
|
|
|
234
|
|
|
protected function getPredCaseOf12Declensions($word, $last, $prefix) { |
235
|
|
|
if (slice($word, -2) == 'ий') { |
236
|
|
|
if ($last == 'ё') |
237
|
|
|
return $prefix.'е'; |
238
|
|
|
else |
239
|
|
|
return $prefix.'и'; |
240
|
|
|
} else { |
241
|
|
|
return $prefix.'е'; |
242
|
|
|
} |
243
|
|
|
} |
244
|
|
|
|
245
|
|
|
protected function chooseVowelAfterConsonant($last, $soft_last, $after_soft, $after_hard) { |
246
|
|
|
if ($this->isHissingConsonant($last) || $this->isVelarConsonant($last) || $soft_last) { |
247
|
|
|
return $after_soft; |
248
|
|
|
} else { |
249
|
|
|
return $after_hard; |
250
|
|
|
} |
251
|
|
|
} |
252
|
|
|
} |
253
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.