Completed
Push — master ( 0d947c...778e4d )
by f
01:51
created

src/Russian/GeographicalNamesInflection.php (1 issue)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
namespace morphos\Russian;
3
4
use morphos\S;
5
6
/**
7
 * Rules are from: https://ru.wikipedia.org/wiki/%D0%A1%D0%BA%D0%BB%D0%BE%D0%BD%D0%B5%D0%BD%D0%B8%D0%B5_%D0%B3%D0%B5%D0%BE%D0%B3%D1%80%D0%B0%D1%84%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%B8%D1%85_%D0%BD%D0%B0%D0%B7%D0%B2%D0%B0%D0%BD%D0%B8%D0%B9_%D0%B2_%D1%80%D1%83%D1%81%D1%81%D0%BA%D0%BE%D0%BC_%D1%8F%D0%B7%D1%8B%D0%BA%D0%B5
8
 */
9
class GeographicalNamesInflection extends \morphos\BaseInflection implements Cases
10
{
11
    use RussianLanguage, CasesHelper;
12
13
    protected static $abbreviations = [
14
        'сша',
15
        'оаэ',
16
        'ссср',
17
        'юар',
18
    ];
19
20
    protected static $delimiters = [
21
        ' ',
22
        '-на-',
23
        '-',
24
    ];
25
26
    protected static $ovAbnormalExceptions = [
27
        'осташков',
28
    ];
29
30
    protected static $immutableParts = [
31
        'санкт',
32
        'йошкар',
33
        'улан',
34
    ];
35
36
    protected static $runawayVowelsExceptions = [
37
        'торжо*к',
38
        'волоче*к',
39
        'орё*л',
40
    ];
41
42
    protected static $misspellings = [
43
        'орел' => 'орёл',
44
    ];
45
46
    /**
47
     * @return array|bool
48
     */
49 9 View Code Duplication
    protected static function getRunAwayVowelsList()
50
    {
51 9
        $runawayVowelsNormalized = [];
52 9
        foreach (static::$runawayVowelsExceptions as $word) {
53 9
            $runawayVowelsNormalized[str_replace('*', null, $word)] = S::indexOf($word, '*') - 1;
54
        }
55 9
        return $runawayVowelsNormalized;
56
    }
57
58
    /**
59
     * Проверяет, склоняемо ли название
60
     * @param string $name Название
61
     * @return bool
62
     */
63 2
    public static function isMutable($name)
64
    {
65 2
        $name = S::lower($name);
66
67
        // // ends with 'ы' or 'и': plural form
68
        // if (in_array(S::slice($name, -1), array('и', 'ы')))
69
        //     return false;
70
71 2
        if (in_array($name, static::$abbreviations, true) || in_array($name, static::$immutableParts, true)) {
72 2
            return false;
73
        }
74
75
        if (strpos($name, ' ') !== false) {
76
            // explode() is not applicable because Geographical unit may have few words
77
            $first_part = S::slice($name, 0, S::findFirstPosition($name, ' '));
78
            $last_part = S::slice($name,
79
                S::findLastPosition($name, ' ') + 1);
80
81
            // город N, село N, хутор N, район N, поселок N, округ N, республика N
82
            // N область, N край
83
            if (in_array($first_part, ['город', 'село', 'хутор', 'район', 'поселок', 'округ', 'республика'], true)
84
                || in_array($last_part, ['край', 'область'], true)) {
85
                return true;
86
            }
87
88
            // пгт N
89
            if ($first_part === 'пгт')
90
                return false;
91
        }
92
93
        // ends with 'е' or 'о', but not with 'ово/ёво/ево/ино/ыно'
94
        if (in_array(S::slice($name, -1), ['е', 'о'], true) && !in_array(S::slice($name, -3, -1), ['ов', 'ёв', 'ев', 'ин', 'ын'], true)) {
95
            return false;
96
        }
97
        return true;
98
    }
99
100
    /**
101
     * Получение всех форм названия
102
     * @param string $name
103
     * @return array
104
     * @throws \Exception
105
     */
106 39
    public static function getCases($name)
107
    {
108 39
        $name = S::lower($name);
109
110
        // Проверка на неизменяемость и сложное название
111 39
        if (in_array($name, static::$immutableParts, true)) {
112 1
            return array_fill_keys([static::IMENIT, static::RODIT, static::DAT, static::VINIT, static::TVORIT, static::PREDLOJ], S::name($name));
113
        }
114
115 39
        if (strpos($name, ' ') !== false) {
116 9
            $first_part = S::slice($name, 0, S::findFirstPosition($name, ' '));
117
            // город N, село N, хутор N, пгт N
118 9
            if (in_array($first_part, ['город', 'село', 'хутор', 'пгт', 'район', 'поселок', 'округ', 'республика'], true)) {
119 3
                if ($first_part !== 'пгт')
120 3
                    return static::composeCasesFromWords([
121 3
                        $first_part !== 'республика'
122 2
                            ? NounDeclension::getCases($first_part)
123 3
                            : array_map(['\\morphos\\S', 'name'], NounDeclension::getCases($first_part)),
124 3
                        array_fill_keys(static::getAllCases(), S::name(S::slice($name, S::length($first_part) + 1)))
125
                    ]);
126
                else
127
                    return array_fill_keys([static::IMENIT, static::RODIT, static::DAT, static::VINIT, static::TVORIT, static::PREDLOJ], 'пгт '.S::name(S::slice($name, 4)));
0 ignored issues
show
It seems like $name defined by \morphos\S::lower($name) on line 108 can also be of type boolean; however, morphos\S::slice() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
128
            }
129
130 6
            $last_part = S::slice($name,
131 6
                S::findLastPosition($name, ' ') + 1);
132
            // N область, N край
133 6
            if (in_array($last_part, ['край', 'область'], true)) {
134 2
                return static::composeCasesFromWords([static::getCases(S::slice($name, 0, S::findLastPosition($name, ' '))), NounDeclension::getCases($last_part)]);
135
            }
136
        }
137
138
        // Сложное название через пробел, '-' или '-на-'
139 36
        foreach (static::$delimiters as $delimiter) {
140 36
            if (strpos($name, $delimiter) !== false) {
141 6
                $parts = explode($delimiter, $name);
142 6
                $result = [];
143 6
                foreach ($parts as $i => $part) {
144 6
                    $result[$i] = static::getCases($part);
145
                }
146 36
                return static::composeCasesFromWords($result, $delimiter);
147
            }
148
        }
149
150
        // Исправление ошибок
151 36
        if (array_key_exists($name, static::$misspellings)) {
152
            $name = static::$misspellings[$name];
153
        }
154
155
        // Само склонение
156 36
        if (!in_array($name, static::$abbreviations, true)) {
157 34
            switch (S::slice($name, -2)) {
158
                // Нижний, Русский
159 34
                case 'ий':
160 3
                    $prefix = S::name(S::slice($name, 0, -2));
161
                    return [
162 3
                        static::IMENIT => $prefix.'ий',
163 3
                        static::RODIT => $prefix.(static::isVelarConsonant(S::slice($name, -3, -2)) ? 'ого' : 'его'),
164 3
                        static::DAT => $prefix.(static::isVelarConsonant(S::slice($name, -3, -2)) ? 'ому' : 'ему'),
165 3
                        static::VINIT => $prefix.'ий',
166 3
                        static::TVORIT => $prefix.'им',
167 3
                        static::PREDLOJ => $prefix.(static::chooseEndingBySonority($prefix, 'ем', 'ом')),
168
                    ];
169
170
                // Ростовская
171 33
                case 'ая':
172 1
                    $prefix = S::name(S::slice($name, 0, -2));
173
                    return [
174 1
                        static::IMENIT => $prefix.'ая',
175 1
                        static::RODIT => $prefix.'ой',
176 1
                        static::DAT => $prefix.'ой',
177 1
                        static::VINIT => $prefix.'ую',
178 1
                        static::TVORIT => $prefix.'ой',
179 1
                        static::PREDLOJ => $prefix.'ой',
180
                    ];
181
182
                // Россошь
183 32
                case 'шь':
184 1
                    $prefix = S::name(S::slice($name, 0, -1));
185
                    return [
186 1
                        static::IMENIT => $prefix.'ь',
187 1
                        static::RODIT => $prefix.'и',
188 1
                        static::DAT => $prefix.'и',
189 1
                        static::VINIT => $prefix.'ь',
190 1
                        static::TVORIT => $prefix.'ью',
191 1
                        static::PREDLOJ => $prefix.'и',
192
                    ];
193
194
                // Грозный, Благодарный
195 31
                case 'ый':
196 2
                    $prefix = S::name(S::slice($name, 0, -2));
197
                    return [
198 2
                        static::IMENIT => $prefix.'ый',
199 2
                        static::RODIT => $prefix.'ого',
200 2
                        static::DAT => $prefix.'ому',
201 2
                        static::VINIT => $prefix.'ый',
202 2
                        static::TVORIT => $prefix.'ым',
203 2
                        static::PREDLOJ => $prefix.'ом',
204
                    ];
205
206
                // Ставрополь, Ярославль, Электросталь
207 29
                case 'ль':
208 2
                    $prefix = S::name(S::slice($name, 0, -1));
209
210 2 View Code Duplication
                    if ($name === 'электросталь')
211
                        return [
212 1
                            static::IMENIT => $prefix.'ь',
213 1
                            static::RODIT => $prefix.'и',
214 1
                            static::DAT => $prefix.'и',
215 1
                            static::VINIT => $prefix.'ь',
216 1
                            static::TVORIT => $prefix.'ью',
217 1
                            static::PREDLOJ => $prefix.'и',
218
                        ];
219
220
                    return [
221 1
                        static::IMENIT => $prefix.'ь',
222 1
                        static::RODIT => $prefix.'я',
223 1
                        static::DAT => $prefix.'ю',
224 1
                        static::VINIT => $prefix.'ь',
225 1
                        static::TVORIT => $prefix.'ем',
226 1
                        static::PREDLOJ => $prefix.'е',
227
                    ];
228
229
                // Тверь, Анадырь
230 27
                case 'рь':
231 2
                    $prefix = S::name(S::slice($name, 0, -1));
232 2
                    $last_vowel = S::slice($prefix, -2, -1);
233
                    return [
234 2
                        static::IMENIT => $prefix . 'ь',
235 2
                        static::RODIT => $prefix . (static::isBinaryVowel($last_vowel) ? 'и' : 'я'),
236 2
                        static::DAT => $prefix . (static::isBinaryVowel($last_vowel) ? 'и' : 'ю'),
237 2
                        static::VINIT => $prefix . 'ь',
238 2
                        static::TVORIT => $prefix . (static::isBinaryVowel($last_vowel) ? 'ью' : 'ем'),
239 2
                        static::PREDLOJ => $prefix . (static::isBinaryVowel($last_vowel) ? 'и' : 'е'),
240
                    ];
241
242
                // Березники, Ессентуки
243 25
                case 'ки':
244 2
                    $prefix = S::name(S::slice($name, 0, -1));
245
                    return [
246 2
                        static::IMENIT => $prefix . 'и',
247 2
                        static::RODIT => $name == 'луки' ? $prefix : $prefix . 'ов',
248 2
                        static::DAT => $prefix . 'ам',
249 2
                        static::VINIT => $prefix . 'и',
250 2
                        static::TVORIT => $prefix . 'ами',
251 2
                        static::PREDLOJ => $prefix . 'ах',
252
                    ];
253
254
                // Пермь, Кемь
255 24
                case 'мь':
256 1
                    $prefix = S::name(S::slice($name, 0, -1));
257
                    return [
258 1
                        static::IMENIT => $prefix . 'ь',
259 1
                        static::RODIT => $prefix . 'и',
260 1
                        static::DAT => $prefix . 'и',
261 1
                        static::VINIT => $prefix . 'ь',
262 1
                        static::TVORIT => $prefix . 'ью',
263 1
                        static::PREDLOJ => $prefix . 'и',
264
                    ];
265
266
                // Рязань, Назрань
267 23
                case 'нь':
268 1
                    $prefix = S::name(S::slice($name, 0, -1));
269
                    return [
270 1
                        static::IMENIT => $prefix . 'ь',
271 1
                        static::RODIT => $prefix . 'и',
272 1
                        static::DAT => $prefix . 'и',
273 1
                        static::VINIT => $prefix . 'ь',
274 1
                        static::TVORIT => $prefix . 'ью',
275 1
                        static::PREDLOJ => $prefix . 'и',
276
                    ];
277
278
                // Набережные
279 22
                case 'ые':
280 1
                    $prefix = S::name(S::slice($name, 0, -1));
281
                    return [
282 1
                        static::IMENIT => $prefix . 'е',
283 1
                        static::RODIT => $prefix . 'х',
284 1
                        static::DAT => $prefix . 'м',
285 1
                        static::VINIT => $prefix . 'е',
286 1
                        static::TVORIT => $prefix . 'ми',
287 1
                        static::PREDLOJ => $prefix . 'х',
288
                    ];
289
290
                // Челны
291 22
                case 'ны':
292 1
                    $prefix = S::name(S::slice($name, 0, -1));
293
                    return [
294 1
                        static::IMENIT => $prefix . 'ы',
295 1
                        static::RODIT => $prefix . 'ов',
296 1
                        static::DAT => $prefix . 'ам',
297 1
                        static::VINIT => $prefix . 'ы',
298 1
                        static::TVORIT => $prefix . 'ами',
299 1
                        static::PREDLOJ => $prefix . 'ах',
300
                    ];
301
302
                // Великие
303 21
                case 'ие':
304 1
                    $prefix = S::name(S::slice($name, 0, -1));
305
                    return [
306 1
                        static::IMENIT => $prefix.'е',
307 1
                        static::RODIT => $prefix.'х',
308 1
                        static::DAT => $prefix.'м',
309 1
                        static::VINIT => $prefix.'е',
310 1
                        static::TVORIT => $prefix.'ми',
311 1
                        static::PREDLOJ => $prefix.'х',
312
                    ];
313
314
                // Керчь
315 20
                case 'чь':
316 1
                    $prefix = S::name(S::slice($name, 0, -1));
317
                    return [
318 1
                        static::IMENIT => $prefix.'ь',
319 1
                        static::RODIT => $prefix.'и',
320 1
                        static::DAT => $prefix.'и',
321 1
                        static::VINIT => $prefix.'ь',
322 1
                        static::TVORIT => $prefix.'ью',
323 1
                        static::PREDLOJ => $prefix.'и',
324
                    ];
325
            }
326
327 19
            switch (S::slice($name, -1)) {
328 19
                case 'р':
329
                    // Бор
330
                    $prefix = S::name(S::slice($name, 0, -1));
331
                    return [
332
                        static::IMENIT => $prefix.'р',
333
                        static::RODIT => $prefix.'ра',
334
                        static::DAT => $prefix.'ру',
335
                        static::VINIT => $prefix.'р',
336
                        static::TVORIT => $prefix.'ром',
337
                        static::PREDLOJ => $prefix.'ру',
338
                    ];
339
340 19 View Code Duplication
                case 'ы':
341
                    // Чебоксары, Шахты
342 1
                    $prefix = S::name(S::slice($name, 0, -1));
343
                    return [
344 1
                        static::IMENIT => $prefix.'ы',
345 1
                        static::RODIT => $prefix,
346 1
                        static::DAT => $prefix.'ам',
347 1
                        static::VINIT => $prefix.'ы',
348 1
                        static::TVORIT => $prefix.'ами',
349 1
                        static::PREDLOJ => $prefix.'ах',
350
                    ];
351
352 18
                case 'я':
353
                    // Азия
354 1
                    $prefix = S::name(S::slice($name, 0, -1));
355
                    return [
356 1
                        static::IMENIT => S::name($name),
357 1
                        static::RODIT => $prefix.'и',
358 1
                        static::DAT => $prefix.'и',
359 1
                        static::VINIT => $prefix.'ю',
360 1
                        static::TVORIT => $prefix.'ей',
361 1
                        static::PREDLOJ => $prefix.'и',
362
                    ];
363
364 17 View Code Duplication
                case 'а':
365
                    // Москва, Рига
366 5
                    $prefix = S::name(S::slice($name, 0, -1));
367
                    return [
368 5
                        static::IMENIT => $prefix.'а',
369 5
                        static::RODIT => $prefix.(static::isVelarConsonant(S::slice($name, -2, -1)) ? 'и' : 'ы'),
370 5
                        static::DAT => $prefix.'е',
371 5
                        static::VINIT => $prefix.'у',
372 5
                        static::TVORIT => $prefix.'ой',
373 5
                        static::PREDLOJ => $prefix.'е',
374
                    ];
375
376 12
                case 'й':
377
                    // Ишимбай
378 2
                    $prefix = S::name(S::slice($name, 0, -1));
379
                    return [
380 2
                        static::IMENIT => $prefix . 'й',
381 2
                        static::RODIT => $prefix . 'я',
382 2
                        static::DAT => $prefix . 'ю',
383 2
                        static::VINIT => $prefix . 'й',
384 2
                        static::TVORIT => $prefix . 'ем',
385 2
                        static::PREDLOJ => $prefix . 'е',
386
                    ];
387
            }
388
389 10
            if (static::isConsonant(S::slice($name,  -1)) && !in_array($name, static::$ovAbnormalExceptions, true)) {
390 9
                $runaway_vowels_list = static::getRunAwayVowelsList();
391
392
                // if run-away vowel in name
393 9
                if (isset($runaway_vowels_list[$name])) {
394 3
                    $runaway_vowel_offset = $runaway_vowels_list[$name];
395 3
                    $prefix = S::name(S::slice($name, 0, $runaway_vowel_offset) . S::slice($name, $runaway_vowel_offset + 1));
396
                } else {
397 6
                    $prefix = S::name($name);
398
                }
399
400
                // Париж, Валаам, Киев
401
                return [
402 9
                    static::IMENIT => S::name($name),
403 9
                    static::RODIT => $prefix . 'а',
404 9
                    static::DAT => $prefix . 'у',
405 9
                    static::VINIT => S::name($name),
406 9
                    static::TVORIT => $prefix . (static::isVelarConsonant(S::slice($name, -2, -1)) ? 'ем' : 'ом'),
407 9
                    static::PREDLOJ => $prefix . 'е',
408
                ];
409
            }
410
411
            // ов, ово, ёв, ёво, ев, ево, ...
412 2
            $suffixes = ['ов', 'ёв', 'ев', 'ин', 'ын'];
413 2
            if ((in_array(S::slice($name, -1), ['е', 'о'], true) && in_array(S::slice($name, -3, -1), $suffixes, true)) || in_array(S::slice($name, -2), $suffixes, true)) {
414
                // ово, ёво, ...
415 1
                if (in_array(S::slice($name, -3, -1), $suffixes, true)) {
416
                    $prefix = S::name(S::slice($name, 0, -1));
417
                }
418
                // ов, её, ...
419 1
                elseif (in_array(S::slice($name, -2), $suffixes, true)) {
420 1
                    $prefix = S::name($name);
421
                }
422
                return [
423 1
                    static::IMENIT => S::name($name),
424 1
                    static::RODIT => $prefix.'а',
425 1
                    static::DAT => $prefix.'у',
426 1
                    static::VINIT => S::name($name),
427 1
                    static::TVORIT => $prefix.'ым',
428 1
                    static::PREDLOJ => $prefix.'е',
429
                ];
430
            }
431
        }
432
433
        // if no rules matches or name is immutable
434 3
        $name = in_array($name, static::$abbreviations, true) ? S::upper($name) : S::name($name);
435 3
        return array_fill_keys([static::IMENIT, static::RODIT, static::DAT, static::VINIT, static::TVORIT, static::PREDLOJ], $name);
436
    }
437
438
    /**
439
     * Получение одной формы (падежа) названия.
440
     * @param string $name  Название
441
     * @param integer $case Падеж. Одна из констант \morphos\Russian\Cases или \morphos\Cases.
442
     * @see \morphos\Russian\Cases
443
     * @return string
444
     * @throws \Exception
445
     */
446
    public static function getCase($name, $case)
447
    {
448
        $case = static::canonizeCase($case);
449
        $forms = static::getCases($name);
450
        return $forms[$case];
451
    }
452
}
453