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, CasesHelper; |
9
|
|
|
|
10
|
|
|
const FIRST_DECLENSION = 1; |
11
|
|
|
const SECOND_DECLENSION = 2; |
12
|
|
|
const THIRD_DECLENSION = 3; |
13
|
|
|
|
14
|
|
|
protected $abnormalExceptions = array( |
15
|
|
|
'бремя', |
16
|
|
|
'вымя', |
17
|
|
|
'темя', |
18
|
|
|
'пламя', |
19
|
|
|
'стремя', |
20
|
|
|
'пламя', |
21
|
|
|
'время', |
22
|
|
|
'знамя', |
23
|
|
|
'имя', |
24
|
|
|
'племя', |
25
|
|
|
'семя', |
26
|
|
|
); |
27
|
|
|
|
28
|
|
|
static protected $masculineWithSoft = array( |
29
|
|
|
'камень', |
30
|
|
|
'олень', |
31
|
|
|
'конь', |
32
|
|
|
'ячмень', |
33
|
|
|
'путь', |
34
|
|
|
'парень', |
35
|
|
|
'зверь', |
36
|
|
|
'шкворень', |
37
|
|
|
'пень', |
38
|
|
|
'пельмень', |
39
|
|
|
'тюлень', |
40
|
|
|
'выхухоль', |
41
|
|
|
'табель', |
42
|
|
|
'рояль', |
43
|
|
|
'шампунь', |
44
|
|
|
'конь', |
45
|
|
|
'лось', |
46
|
|
|
'гвоздь', |
47
|
|
|
'медведь', |
48
|
|
|
'день', |
49
|
|
|
'рубль', |
50
|
|
|
); |
51
|
|
|
|
52
|
|
|
static protected $immutableWords = array( |
53
|
|
|
'евро', |
54
|
|
|
'пенни', |
55
|
|
|
); |
56
|
|
|
|
57
|
|
|
public function isMutable($word, $animateness = false) { |
58
|
|
|
$word = lower($word); |
59
|
|
|
if (in_array(slice($word, -1), array('у', 'и', 'е', 'о', 'ю')) || in_array($word, self::$immutableWords)) |
60
|
|
|
return false; |
61
|
|
|
return true; |
62
|
|
|
} |
63
|
|
|
|
64
|
|
|
static public function getDeclension($word) { |
65
|
|
|
$word = lower($word); |
66
|
|
|
$last = slice($word, -1); |
67
|
|
|
if (in_array($last, ['а', 'я']) && slice($word, -2) != 'мя') { |
68
|
|
|
return 1; |
69
|
|
|
} else if (self::isConsonant($last) || in_array($last, ['о', 'е', 'ё']) || ($last == 'ь' && self::isConsonant(slice($word, -2, -1)) && !RussianLanguage::isHissingConsonant(slice($word, -2, -1)) && in_array($word, self::$masculineWithSoft))) { |
70
|
|
|
return 2; |
71
|
|
|
} else { |
72
|
|
|
return 3; |
73
|
|
|
} |
74
|
|
|
} |
75
|
|
|
|
76
|
|
|
public function getCases($word, $animateness = false) { |
77
|
|
|
$word = lower($word); |
78
|
|
|
|
79
|
|
|
if (in_array($word, self::$immutableWords)) { |
80
|
|
|
return array( |
81
|
|
|
self::IMENIT => $word, |
82
|
|
|
self::RODIT => $word, |
83
|
|
|
self::DAT => $word, |
84
|
|
|
self::VINIT => $word, |
85
|
|
|
self::TVORIT => $word, |
86
|
|
|
self::PREDLOJ => $this->choosePrepositionByFirstLetter($word, 'об', 'о').' '.$word, |
87
|
|
|
); |
88
|
|
|
} else if (isset($this->abnormalExceptions[$word])) { |
89
|
|
|
$prefix = slice($word, -1); |
90
|
|
|
return array( |
91
|
|
|
self::IMENIT => $word, |
92
|
|
|
self::RODIT => $prefix.'и', |
93
|
|
|
self::DAT => $prefix.'и', |
94
|
|
|
self::VINIT => $word, |
95
|
|
|
self::TVORIT => $prefix, |
96
|
|
|
self::PREDLOJ => $this->choosePrepositionByFirstLetter($prefix, 'об', 'о').' '.$prefix.'и', |
97
|
|
|
); |
98
|
|
|
} |
99
|
|
|
|
100
|
|
|
switch (self::getDeclension($word)) { |
101
|
|
|
case self::FIRST_DECLENSION: |
102
|
|
|
return $this->declinateFirstDeclension($word); |
103
|
|
|
case self::SECOND_DECLENSION: |
104
|
|
|
return $this->declinateSecondDeclension($word, $animateness); |
105
|
|
|
case self::THIRD_DECLENSION: |
106
|
|
|
return $this->declinateThirdDeclension($word); |
107
|
|
|
} |
108
|
|
|
} |
109
|
|
|
|
110
|
|
|
public function declinateFirstDeclension($word) { |
111
|
|
|
$word = lower($word); |
112
|
|
|
$prefix = slice($word, 0, -1); |
113
|
|
|
$last = slice($word, -1); |
114
|
|
|
$soft_last = $this->checkLastConsonantSoftness($word); |
115
|
|
|
$forms = array( |
116
|
|
|
Cases::IMENIT => $word, |
117
|
|
|
); |
118
|
|
|
|
119
|
|
|
// RODIT |
120
|
|
|
$forms[Cases::RODIT] = $this->chooseVowelAfterConsonant($last, $soft_last || (in_array(slice($word, -2, -1), array('г', 'к', 'х'))), $prefix.'и', $prefix.'ы'); |
121
|
|
|
|
122
|
|
|
// DAT |
123
|
|
|
$forms[Cases::DAT] = self::getPredCaseOf12Declensions($word, $last, $prefix); |
124
|
|
|
|
125
|
|
|
// VINIT |
126
|
|
|
$forms[Cases::VINIT] = $this->chooseVowelAfterConsonant($last, $soft_last && slice($word, -2, -1) != 'ч', $prefix.'ю', $prefix.'у'); |
127
|
|
|
|
128
|
|
|
// TVORIT |
129
|
|
View Code Duplication |
if ($last == 'ь') |
|
|
|
|
130
|
|
|
$forms[Cases::TVORIT] = $prefix.'ой'; |
131
|
|
|
else { |
132
|
|
|
$forms[Cases::TVORIT] = $this->chooseVowelAfterConsonant($last, $soft_last, $prefix.'ей', $prefix.'ой'); |
133
|
|
|
} |
134
|
|
|
|
135
|
|
|
// if ($last == 'й' || (self::isConsonant($last) && !RussianLanguage::isHissingConsonant($last)) || $this->checkLastConsonantSoftness($word)) |
136
|
|
|
// $forms[Cases::TVORIT] = $prefix.'ей'; |
137
|
|
|
// else |
138
|
|
|
// $forms[Cases::TVORIT] = $prefix.'ой'; # http://morpher.ru/Russian/Spelling.aspx#sibilant |
139
|
|
|
|
140
|
|
|
// PREDLOJ the same as DAT |
141
|
|
|
$forms[Cases::PREDLOJ] = $this->choosePrepositionByFirstLetter($forms[Cases::DAT], 'об', 'о').' '.$forms[Cases::DAT]; |
142
|
|
|
return $forms; |
143
|
|
|
} |
144
|
|
|
|
145
|
|
|
public function declinateSecondDeclension($word, $animateness = false) { |
146
|
|
|
$word = lower($word); |
147
|
|
|
$last = slice($word, -1); |
148
|
|
|
$soft_last = $last == 'й' || (in_array($last, ['ь', 'е', 'ё', 'ю', 'я']) && (self::isConsonant(slice($word, -2, -1)) || slice($word, -2, -1) == 'и')); |
149
|
|
|
$prefix = self::getPrefixOfFirstDeclension($word, $last); |
150
|
|
|
$forms = array( |
151
|
|
|
Cases::IMENIT => $word, |
152
|
|
|
); |
153
|
|
|
|
154
|
|
|
// RODIT |
155
|
|
|
$forms[Cases::RODIT] = $this->chooseVowelAfterConsonant($last, $soft_last, $prefix.'я', $prefix.'а'); |
156
|
|
|
|
157
|
|
|
// DAT |
158
|
|
|
$forms[Cases::DAT] = $this->chooseVowelAfterConsonant($last, $soft_last, $prefix.'ю', $prefix.'у'); |
159
|
|
|
|
160
|
|
|
// VINIT |
161
|
|
|
if (in_array($last, ['о', 'е', 'ё'])) |
162
|
|
|
$forms[Cases::VINIT] = $word; |
163
|
|
|
else { |
164
|
|
|
$forms[Cases::VINIT] = self::getVinitCaseByAnimateness($forms, $animateness); |
165
|
|
|
} |
166
|
|
|
|
167
|
|
|
// TVORIT |
168
|
|
|
// if ($last == 'ь') |
169
|
|
|
// $forms[Cases::TVORIT] = $prefix.'ом'; |
170
|
|
|
// else if ($last == 'й' || (self::isConsonant($last) && !RussianLanguage::isHissingConsonant($last))) |
171
|
|
|
// $forms[Cases::TVORIT] = $prefix.'ем'; |
172
|
|
|
// else |
173
|
|
|
// $forms[Cases::TVORIT] = $prefix.'ом'; # http://morpher.ru/Russian/Spelling.aspx#sibilant |
174
|
|
|
if (RussianLanguage::isHissingConsonant($last) || $last == 'ц') { |
175
|
|
|
$forms[Cases::TVORIT] = $prefix.'ем'; |
176
|
|
|
} else if (in_array($last, ['й'/*, 'ч', 'щ'*/]) || $soft_last) { |
177
|
|
|
$forms[Cases::TVORIT] = $prefix.'ем'; |
178
|
|
|
} else { |
179
|
|
|
$forms[Cases::TVORIT] = $prefix.'ом'; |
180
|
|
|
} |
181
|
|
|
|
182
|
|
|
// PREDLOJ |
183
|
|
|
$forms[Cases::PREDLOJ] = self::getPredCaseOf12Declensions($word, $last, $prefix); |
184
|
|
|
$forms[Cases::PREDLOJ] = $this->choosePrepositionByFirstLetter($forms[Cases::PREDLOJ], 'об', 'о').' '.$forms[Cases::PREDLOJ]; |
185
|
|
|
|
186
|
|
|
return $forms; |
187
|
|
|
} |
188
|
|
|
|
189
|
|
|
public function declinateThirdDeclension($word) { |
190
|
|
|
$word = lower($word); |
191
|
|
|
$prefix = slice($word, 0, -1); |
192
|
|
|
return array( |
193
|
|
|
Cases::IMENIT => $word, |
194
|
|
|
Cases::RODIT => $prefix.'и', |
195
|
|
|
Cases::DAT => $prefix.'и', |
196
|
|
|
Cases::VINIT => $word, |
197
|
|
|
Cases::TVORIT => $prefix.'ью', |
198
|
|
|
Cases::PREDLOJ => $this->choosePrepositionByFirstLetter($prefix, 'об', 'о').' '.$prefix.'и', |
199
|
|
|
); |
200
|
|
|
} |
201
|
|
|
|
202
|
|
|
public function getCase($word, $case, $animateness = false) { |
203
|
|
|
$case = self::canonizeCase($case); |
204
|
|
|
$forms = $this->getCases($word, $animateness); |
205
|
|
|
return $forms[$case]; |
206
|
|
|
} |
207
|
|
|
|
208
|
|
|
static public function getPrefixOfFirstDeclension($word, $last) { |
209
|
|
|
if ($word == 'день') |
210
|
|
|
$prefix = 'дн'; |
211
|
|
|
else if (in_array($last, ['о', 'е', 'ё', 'ь', 'й'])) |
212
|
|
|
$prefix = slice($word, 0, -1); |
213
|
|
|
else |
214
|
|
|
$prefix = $word; |
215
|
|
|
return $prefix; |
216
|
|
|
} |
217
|
|
|
|
218
|
|
|
static public function getVinitCaseByAnimateness(array $forms, $animate) { |
219
|
|
|
if ($animate) |
220
|
|
|
return $forms[Cases::RODIT]; |
221
|
|
|
else |
222
|
|
|
return $forms[Cases::IMENIT]; |
223
|
|
|
} |
224
|
|
|
|
225
|
|
|
static public function getPredCaseOf12Declensions($word, $last, $prefix) { |
226
|
|
|
if (slice($word, -2) == 'ий') { |
227
|
|
|
if ($last == 'ё') |
228
|
|
|
return $prefix.'е'; |
229
|
|
|
else |
230
|
|
|
return $prefix.'и'; |
231
|
|
|
} else { |
232
|
|
|
return $prefix.'е'; |
233
|
|
|
} |
234
|
|
|
} |
235
|
|
|
} |
236
|
|
|
|
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.