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
|
|
|
$ending = $count % 10; |
57
|
|
|
if (($count > 20 && $ending == 1) || $count == 1) |
58
|
|
|
return self::ONE; |
59
|
|
|
else if (($count > 20 && in_array($ending, range(2, 4))) || in_array($count, range(2, 4))) |
60
|
|
|
return self::TWO_FOUR; |
61
|
|
|
else |
62
|
|
|
return self::FIVE_OTHER; |
63
|
|
|
} |
64
|
|
|
|
65
|
|
|
static public function getCase($word, $case, $animateness = false) { |
66
|
|
|
$case = self::canonizeCase($case); |
67
|
|
|
$forms = self::getCases($word, $animateness); |
68
|
|
|
return $forms[$case]; |
69
|
|
|
} |
70
|
|
|
|
71
|
|
|
static public function getCases($word, $animateness = false) { |
72
|
|
|
$word = S::lower($word); |
73
|
|
|
$prefix = S::slice($word, 0, -1); |
74
|
|
|
$last = S::slice($word, -1); |
75
|
|
|
|
76
|
|
|
$runaway_vowels_list = static::getRunAwayVowelsList(); |
77
|
|
|
if (isset($runaway_vowels_list[$word])) { |
78
|
|
|
$vowel_offset = $runaway_vowels_list[$word]; |
79
|
|
|
$word = S::slice($word, 0, $vowel_offset) . S::slice($word, $vowel_offset + 1); |
80
|
|
|
} |
81
|
|
|
|
82
|
|
|
if (in_array($word, self::$immutableWords)) { |
83
|
|
|
return array( |
84
|
|
|
self::IMENIT => $word, |
85
|
|
|
self::RODIT => $word, |
86
|
|
|
self::DAT => $word, |
87
|
|
|
self::VINIT => $word, |
88
|
|
|
self::TVORIT => $word, |
89
|
|
|
self::PREDLOJ => self::choosePrepositionByFirstLetter($word, 'об', 'о').' '.$word, |
90
|
|
|
); |
91
|
|
|
} |
92
|
|
|
|
93
|
|
|
if (($declension = GeneralDeclension::getDeclension($word)) == GeneralDeclension::SECOND_DECLENSION) { |
94
|
|
|
$soft_last = $last == 'й' || (in_array($last, ['ь', 'е', 'ё', 'ю', 'я']) && (self::isConsonant(S::slice($word, -2, -1)) || S::slice($word, -2, -1) == 'и')); |
95
|
|
|
$prefix = GeneralDeclension::getPrefixOfFirstDeclension($word, $last); |
96
|
|
|
} else if ($declension == GeneralDeclension::FIRST_DECLENSION) { |
97
|
|
|
$soft_last = self::checkLastConsonantSoftness($word); |
98
|
|
|
} else { |
99
|
|
|
$soft_last = S::slice($word, -2) == 'сь'; |
100
|
|
|
} |
101
|
|
|
|
102
|
|
|
$forms = array(); |
103
|
|
|
|
104
|
|
|
if ($last == 'ч' || in_array(S::slice($word, -2), array('чь', 'сь')) || (self::isVowel($last) && in_array(S::slice($word, -2, -1), array('ч', 'к')))) // before ч, чь, сь, ч+vowel, к+vowel |
105
|
|
|
$forms[Cases::IMENIT] = $prefix.'и'; |
106
|
|
View Code Duplication |
else if ($last == 'н' || $last == 'ц') |
|
|
|
|
107
|
|
|
$forms[Cases::IMENIT] = $prefix.'ы'; |
108
|
|
|
else |
109
|
|
|
$forms[Cases::IMENIT] = self::chooseVowelAfterConsonant($last, $soft_last, $prefix.'я', $prefix.'а'); |
110
|
|
|
|
111
|
|
|
// RODIT |
112
|
|
|
if ($word == 'письмо') |
113
|
|
|
$forms[Cases::RODIT] = 'писем'; |
114
|
|
|
else if (in_array($last, array('о', 'е'))) { |
115
|
|
|
// exceptions |
116
|
|
|
if (in_array($word, self::$neuterExceptions)) |
117
|
|
|
$forms[Cases::RODIT] = $prefix.'ей'; |
118
|
|
View Code Duplication |
else if (S::slice($word, -2, -1) == 'и') |
|
|
|
|
119
|
|
|
$forms[Cases::RODIT] = $prefix.'й'; |
120
|
|
|
else |
121
|
|
|
$forms[Cases::RODIT] = $prefix; |
122
|
|
|
} |
123
|
|
|
else if (S::slice($word, -2) == 'ка') { // words ending with -ка: чашка, вилка, ложка, тарелка, копейка, батарейка |
124
|
|
|
if (S::slice($word, -3, -2) == 'л') $forms[Cases::RODIT] = S::slice($word, 0, -2).'ок'; |
125
|
|
|
else if (S::slice($word, -3, -2) == 'й') $forms[Cases::RODIT] = S::slice($word, 0, -3).'ек'; |
126
|
|
|
else $forms[Cases::RODIT] = S::slice($word, 0, -2).'ек'; |
127
|
|
|
} |
128
|
|
|
else if (in_array($last, array('а'))) // обида, ябеда |
129
|
|
|
$forms[Cases::RODIT] = $prefix; |
130
|
|
|
else if (in_array($last, array('я'))) // молния |
131
|
|
|
$forms[Cases::RODIT] = $prefix.'й'; |
132
|
|
|
else if (RussianLanguage::isHissingConsonant($last) || ($soft_last && $last != 'й') || in_array(S::slice($word, -2), array('чь', 'сь'))) |
133
|
|
|
$forms[Cases::RODIT] = $prefix.'ей'; |
134
|
|
View Code Duplication |
else if ($last == 'й') |
|
|
|
|
135
|
|
|
$forms[Cases::RODIT] = $prefix.'ев'; |
136
|
|
|
else // (self::isConsonant($last) && !RussianLanguage::isHissingConsonant($last)) |
137
|
|
|
$forms[Cases::RODIT] = $prefix.'ов'; |
138
|
|
|
|
139
|
|
|
// DAT |
140
|
|
|
$forms[Cases::DAT] = self::chooseVowelAfterConsonant($last, $soft_last && S::slice($word, -2, -1) != 'ч', $prefix.'ям', $prefix.'ам'); |
141
|
|
|
|
142
|
|
|
// VINIT |
143
|
|
|
$forms[Cases::VINIT] = GeneralDeclension::getVinitCaseByAnimateness($forms, $animateness); |
144
|
|
|
|
145
|
|
|
// TVORIT |
146
|
|
|
// my personal rule |
147
|
|
|
if ($last == 'ь' && $declension == GeneralDeclension::THIRD_DECLENSION && !in_array(S::slice($word, -2), array('чь', 'сь'))) { |
148
|
|
|
$forms[Cases::TVORIT] = $prefix.'ми'; |
149
|
|
|
} else { |
150
|
|
|
$forms[Cases::TVORIT] = self::chooseVowelAfterConsonant($last, $soft_last && S::slice($word, -2, -1) != 'ч', $prefix.'ями', $prefix.'ами'); |
151
|
|
|
} |
152
|
|
|
|
153
|
|
|
// PREDLOJ |
154
|
|
|
$forms[Cases::PREDLOJ] = self::chooseVowelAfterConsonant($last, $soft_last && S::slice($word, -2, -1) != 'ч', $prefix.'ях', $prefix.'ах'); |
155
|
|
|
$forms[Cases::PREDLOJ] = self::choosePrepositionByFirstLetter($forms[Cases::PREDLOJ], 'об', 'о').' '.$forms[Cases::PREDLOJ]; |
156
|
|
|
return $forms; |
157
|
|
|
} |
158
|
|
|
} |
159
|
|
|
|
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..