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