diff --git a/src/Russian/NounDeclension.php b/src/Russian/NounDeclension.php index 485894e..127bd3d 100644 --- a/src/Russian/NounDeclension.php +++ b/src/Russian/NounDeclension.php @@ -377,11 +377,17 @@ public static function getDeclension($word, $animateness = false) if (in_array($last, ['а', 'я'], true) && S::slice($word, -2) != 'мя') { return self::FIRST_DECLENSION; - } elseif (RussianLanguage::isConsonant($last) || in_array($last, ['о', 'е', 'ё'], true) - || ($last == 'ь' && RussianLanguage::isConsonant(S::slice($word, -2, - -1)) && !RussianLanguage::isHissingConsonant(S::slice($word, -2, -1)) - && (in_array($word, static::$masculineWithSoft, - true)) /*|| in_array($word, static::$masculineWithSoftAndRunAwayVowels, true)*/)) { + } elseif ( + RussianLanguage::isConsonant($last) + || in_array($last, ['о', 'е', 'ё'], true) + || ( + $last == 'ь' + && RussianLanguage::isConsonant(S::slice($word, -2, -1)) + && !RussianLanguage::isHissingConsonant(S::slice($word, -2, -1)) + && (in_array($word, static::$masculineWithSoft, true)) + /*|| in_array($word, static::$masculineWithSoftAndRunAwayVowels, true)*/ + ) + ) { return self::SECOND_DECLENSION; } else { return self::THIRD_DECLENSION; diff --git a/src/Russian/NounPluralization.php b/src/Russian/NounPluralization.php index ba95b88..eea60d0 100644 --- a/src/Russian/NounPluralization.php +++ b/src/Russian/NounPluralization.php @@ -204,12 +204,17 @@ protected static function declinateSubstative($word, $animateness) $last = S::slice($word, -1); if (($declension = NounDeclension::getDeclension($word)) == NounDeclension::SECOND_DECLENSION) { - $soft_last = $last == 'й' || (in_array($last, ['ь', 'е', 'ё', 'ю', 'я'], true) - && (( - RussianLanguage::isConsonant(S::slice($word, -2, - -1)) && !RussianLanguage::isHissingConsonant(S::slice($word, -2, -1))) - || S::slice($word, -2, -1) == 'и')); - $prefix = NounDeclension::getPrefixOfSecondDeclension($word, $last); + $soft_last = $last === 'й' || ( + in_array($last, ['ь', 'е', 'ё', 'ю', 'я'], true) + && ( + ( + RussianLanguage::isConsonant(S::slice($word, -2, -1)) + && !RussianLanguage::isHissingConsonant(S::slice($word, -2, -1)) + ) + || S::slice($word, -2, -1) == 'и' + ) + ); + $prefix = NounDeclension::getPrefixOfSecondDeclension($word, $last); } elseif ($declension == NounDeclension::FIRST_DECLENSION) { $soft_last = RussianLanguage::checkLastConsonantSoftness($word); } else { @@ -218,16 +223,17 @@ protected static function declinateSubstative($word, $animateness) $forms = []; - if (in_array($last, ['ч', 'г'], true) + if ( + in_array($last, ['ч', 'г', 'ж', 'ш'], true) || in_array(S::slice($word, -2), ['чь', 'сь', 'ть', 'нь', 'рь', 'дь', 'ль'], true) - || (RussianLanguage::isVowel($last) && in_array(S::slice($word, -2, -1), ['ч', 'к'], - true))) { // before ч, чь, сь, ч+vowel, к+vowel + || (RussianLanguage::isVowel($last) && in_array(S::slice($word, -2, -1), ['ч', 'к'], true)) + ) { // before ч, чь, сь, ч+vowel, к+vowel $forms[Cases::IMENIT] = $prefix . 'и'; - } elseif (in_array($last, ['н', 'ц', 'р', 'т', 'с', 'ж'], true)) { + } elseif (in_array($last, ['н', 'ц', 'р', 'т', 'с'], true)) { $forms[Cases::IMENIT] = $prefix . 'ы'; } else { - $forms[Cases::IMENIT] = RussianLanguage::chooseVowelAfterConsonant($last, $soft_last, $prefix . 'я', - $prefix . 'а'); + // TODO: fix first declension (depends on animateness and gender, see test cases) + $forms[Cases::IMENIT] = RussianLanguage::chooseVowelAfterConsonant($last, $soft_last, $prefix . 'я', $prefix . 'а'); } // RODIT @@ -242,25 +248,27 @@ protected static function declinateSubstative($word, $animateness) } else { $forms[Cases::RODIT] = $prefix; } - } elseif (S::slice($word, -2) == 'ка' && S::slice($word, -3, - -2) !== 'и') { // words ending with -ка: чашка, вилка, ложка, тарелка, копейка, батарейка, аптека - if (S::slice($word, -3, -2) == 'л') { + } elseif (S::slice($word, -2) == 'ка') { // words ending with -ка: чашка, вилка, ложка, тарелка, копейка, батарейка, аптека + if (in_array(S::slice($word, -3, -2), ['б', 'в', 'д', 'з', 'л', 'м', 'н', 'п', 'р', 'с', 'т', 'ф'], true)) { $forms[Cases::RODIT] = S::slice($word, 0, -2) . 'ок'; - } elseif (in_array(S::slice($word, -3, -2), ['й', 'е'], true)) { + } elseif (in_array(S::slice($word, -3, -2), ['ц', 'ч', 'ш', 'щ', 'ж'], true)) { + $forms[Cases::RODIT] = S::slice($word, 0, -2) . 'ек'; + } elseif (S::slice($word, -3, -2) === 'й') { $forms[Cases::RODIT] = S::slice($word, 0, -3) . 'ек'; + } elseif (in_array(S::slice($word, -3, -2), array_merge(['е', 'к'], RussianLanguage::$vowels), true)) { + $forms[Cases::RODIT] = $prefix; } else { - if ($word === 'штука') { - $forms[Cases::RODIT] = S::slice($word, 0, -2) . 'к'; - } else { - $forms[Cases::RODIT] = S::slice($word, 0, -2) . 'ек'; - } + $forms[Cases::RODIT] = S::slice($word, 0, -2) . 'ек'; } - } elseif (in_array($last, ['а'], true)) { // обида, ябеда + } elseif ($last === 'а') { // обида, ябеда $forms[Cases::RODIT] = $prefix; - } elseif (in_array($last, ['я'], true)) { // молния + } elseif ($last === 'я') { // молния $forms[Cases::RODIT] = $prefix . 'й'; - } elseif (RussianLanguage::isHissingConsonant($last) || ($soft_last && $last != 'й') || in_array(S::slice($word, - -2), ['чь', 'сь', 'ть', 'нь', 'дь'], true)) { + } elseif ( + RussianLanguage::isHissingConsonant($last) + || ($soft_last && $last != 'й') + || in_array(S::slice($word, -2), ['чь', 'сь', 'ть', 'нь', 'дь'], true) + ) { $forms[Cases::RODIT] = $prefix . 'ей'; } elseif ($last == 'й' || S::slice($word, -2) == 'яц') { // месяц $forms[Cases::RODIT] = $prefix . 'ев'; diff --git a/src/Russian/RussianLanguage.php b/src/Russian/RussianLanguage.php index feaf67e..5385827 100644 --- a/src/Russian/RussianLanguage.php +++ b/src/Russian/RussianLanguage.php @@ -139,8 +139,10 @@ public static function checkLastConsonantSoftness($word) return true; } - if (S::length($substring) > 1 && in_array(S::slice($substring, 1, 2), ['е', 'ё', 'и', 'ю', 'я', 'ь'], - true)) { // consonants are soft if they are trailed with these vowels + if ( + S::length($substring) > 1 + && in_array(S::slice($substring, 1, 2), ['е', 'ё', 'и', 'ю', 'я', 'ь'], true) // consonants are soft if they are trailed with these vowels + ) { return true; } } diff --git a/tests/Russian/NounPluralizationTest.php b/tests/Russian/NounPluralizationTest.php index 4c6a76b..7826364 100644 --- a/tests/Russian/NounPluralizationTest.php +++ b/tests/Russian/NounPluralizationTest.php @@ -2,6 +2,7 @@ namespace morphos\test\Russian; +use Exception; use morphos\Russian\NounPluralization; use PHPUnit\Framework\TestCase; @@ -15,7 +16,7 @@ class NounPluralizationTest extends TestCase * @param $pluralized2 * @param $pluralized5 * - * @throws \Exception + * @throws Exception */ public function testPluralization($word, $pluralized2, $pluralized5) { @@ -79,7 +80,7 @@ public function pluralizationWordsProvider() * * @param string $case * - * @throws \Exception + * @throws Exception */ public function testPluralizationWithCase($word, $pluralizedOne, $pluralizedMany, $case) { @@ -115,45 +116,71 @@ public function pluralWordsProvider() { return [ // 1 склонение - ['дом', false, ['дома', 'домов', 'домам', 'дома', 'домами', 'домах']], - ['склон', false, ['склоны', 'склонов', 'склонам', 'склоны', 'склонами', 'склонах']], - ['поле', false, ['поля', 'полей', 'полям', 'поля', 'полями', 'полях']], - ['ночь', false, ['ночи', 'ночей', 'ночам', 'ночи', 'ночами', 'ночах']], - ['кирпич', false, ['кирпичи', 'кирпичей', 'кирпичам', 'кирпичи', 'кирпичами', 'кирпичах']], - ['гвоздь', false, ['гвозди', 'гвоздей', 'гвоздям', 'гвозди', 'гвоздями', 'гвоздях']], ['молния', false, ['молния', 'молний', 'молниям', 'молния', 'молниями', 'молниях']], +// TODO: fix first declension +// ['молния', false, ['молнии', 'молний', 'молниям', 'молнии', 'молниями', 'молниях']], +// ['дядя', true, ['дяди', 'дядь', 'дядям', 'дядь', 'дядями', 'дядях']], +// ['судья', true, ['судьи', 'судей', 'судьям', 'судий', 'судьями', 'судьях']], +// ['мужчина', true, ['мужчины', 'мужчин', 'мужчинам', 'мужчин', 'мужчинами', 'мужчинах']], ['тысяча', false, ['тысячи', 'тысяч', 'тысячам', 'тысячи', 'тысячами', 'тысячах']], - ['сообщение', false, ['сообщения', 'сообщений', 'сообщениям', 'сообщения', 'сообщениями', 'сообщениях']], - ['халат', false, ['халаты', 'халатов', 'халатам', 'халаты', 'халатами', 'халатах']], - [ - 'прожектор', - false, - ['прожекторы', 'прожекторов', 'прожекторам', 'прожекторы', 'прожекторами', 'прожекторах'], - ], - ['пирсинг', false, ['пирсинги', 'пирсингов', 'пирсингам', 'пирсинги', 'пирсингами', 'пирсингах']], + ['копейка', false, ['копейки', 'копеек', 'копейкам', 'копейки', 'копейками', 'копейках']], + ['батарейка', false, ['батарейки', 'батареек', 'батарейкам', 'батарейки', 'батарейками', 'батарейках']], + ['улыбка', false, ['улыбки', 'улыбок', 'улыбкам', 'улыбки', 'улыбками', 'улыбках']], + ['заявка', false, ['заявки', 'заявок', 'заявкам', 'заявки', 'заявками', 'заявках']], + ['грядка', false, ['грядки', 'грядок', 'грядкам', 'грядки', 'грядками', 'грядках']], + ['сказка', false, ['сказки', 'сказок', 'сказкам', 'сказки', 'сказками', 'сказках']], + ['тарелка', false, ['тарелки', 'тарелок', 'тарелкам', 'тарелки', 'тарелками', 'тарелках']], + ['сумка', false, ['сумки', 'сумок', 'сумкам', 'сумки', 'сумками', 'сумках']], + ['приманка', false, ['приманки', 'приманок', 'приманкам', 'приманки', 'приманками', 'приманках']], + ['тряпка', false, ['тряпки', 'тряпок', 'тряпкам', 'тряпки', 'тряпками', 'тряпках']], + ['марка', false, ['марки', 'марок', 'маркам', 'марки', 'марками', 'марках']], + ['записка', false, ['записки', 'записок', 'запискам', 'записки', 'записками', 'записках']], + ['ветка', false, ['ветки', 'веток', 'веткам', 'ветки', 'ветками', 'ветках']], + ['антропософка', false, ['антропософки', 'антропософок', 'антропософкам', 'антропософки', 'антропософками', 'антропософках']], + ['книжка', false, ['книжки', 'книжек', 'книжкам', 'книжки', 'книжками', 'книжках']], + ['клецка', false, ['клецки', 'клецек', 'клецкам', 'клецки', 'клецками', 'клецках']], + ['крачка', false, ['крачки', 'крачек', 'крачкам', 'крачки', 'крачками', 'крачках']], + ['чашка', false, ['чашки', 'чашек', 'чашкам', 'чашки', 'чашками', 'чашках']], + ['юкка', false, ['юкки', 'юкк', 'юккам', 'юкки', 'юкками', 'юкках']], + ['драка', false, ['драки', 'драк', 'дракам', 'драки', 'драками', 'драках']], + ['сорока', false, ['сороки', 'сорок', 'сорокам', 'сороки', 'сороками', 'сороках']], + ['зэка', false, ['зэки', 'зэк', 'зэкам', 'зэки', 'зэками', 'зэках']], + ['аптека', false, ['аптеки', 'аптек', 'аптекам', 'аптеки', 'аптеками', 'аптеках']], ['фабрика', false, ['фабрики', 'фабрик', 'фабрикам', 'фабрики', 'фабриками', 'фабриках']], - ['гений', true, ['гения', 'гениев', 'гениям', 'гениев', 'гениями', 'гениях']], + ['подоплёка', false, ['подоплёки', 'подоплёк', 'подоплёкам', 'подоплёки', 'подоплёками', 'подоплёках']], + ['гадюка', false, ['гадюки', 'гадюк', 'гадюкам', 'гадюки', 'гадюками', 'гадюках']], + ['кулебяка', false, ['кулебяки', 'кулебяк', 'кулебякам', 'кулебяки', 'кулебяками', 'кулебяках']], + ['штука', false, ['штуки', 'штук', 'штукам', 'штуки', 'штуками', 'штуках']], + + // 2 склонение + ['любитель', true, ['любители', 'любителей', 'любителям', 'любителей', 'любителями', 'любителях']], + ['матрас', false, ['матрасы', 'матрасов', 'матрасам', 'матрасы', 'матрасами', 'матрасах']], + ['коттедж', false, ['коттеджи', 'коттеджей', 'коттеджам', 'коттеджи', 'коттеджами', 'коттеджах']], + ['камыш', false, ['камыши', 'камышей', 'камышам', 'камыши', 'камышами', 'камышах']], [ 'библиотекарь', true, ['библиотекари', 'библиотекарей', 'библиотекарям', 'библиотекарей', 'библиотекарями', 'библиотекарях'], ], - ['лошадь', false, ['лошади', 'лошадей', 'лошадям', 'лошади', 'лошадями', 'лошадях']], - ['любитель', true, ['любители', 'любителей', 'любителям', 'любителей', 'любителями', 'любителях']], - ['матрас', false, ['матрасы', 'матрасов', 'матрасам', 'матрасы', 'матрасами', 'матрасах']], - ['коттедж', false, ['коттеджы', 'коттеджей', 'коттеджам', 'коттеджы', 'коттеджами', 'коттеджах']], - - // 2 склонение - ['копейка', false, ['копейки', 'копеек', 'копейкам', 'копейки', 'копейками', 'копейках']], - ['штука', false, ['штуки', 'штук', 'штукам', 'штуки', 'штуками', 'штуках']], - ['батарейка', false, ['батарейки', 'батареек', 'батарейкам', 'батарейки', 'батарейками', 'батарейках']], + [ + 'прожектор', + false, + ['прожекторы', 'прожекторов', 'прожекторам', 'прожекторы', 'прожекторами', 'прожекторах'], + ], + ['пирсинг', false, ['пирсинги', 'пирсингов', 'пирсингам', 'пирсинги', 'пирсингами', 'пирсингах']], + ['сообщение', false, ['сообщения', 'сообщений', 'сообщениям', 'сообщения', 'сообщениями', 'сообщениях']], + ['халат', false, ['халаты', 'халатов', 'халатам', 'халаты', 'халатами', 'халатах']], + ['дом', false, ['дома', 'домов', 'домам', 'дома', 'домами', 'домах']], + ['склон', false, ['склоны', 'склонов', 'склонам', 'склоны', 'склонами', 'склонах']], + ['поле', false, ['поля', 'полей', 'полям', 'поля', 'полями', 'полях']], + ['кирпич', false, ['кирпичи', 'кирпичей', 'кирпичам', 'кирпичи', 'кирпичами', 'кирпичах']], + ['гвоздь', false, ['гвозди', 'гвоздей', 'гвоздям', 'гвозди', 'гвоздями', 'гвоздях']], ['письмо', false, ['письма', 'писем', 'письмам', 'письма', 'письмами', 'письмах']], ['пятно', false, ['пятна', 'пятен', 'пятнам', 'пятна', 'пятнами', 'пятнах']], ['волчище', false, ['волчища', 'волчищ', 'волчищам', 'волчища', 'волчищами', 'волчищах']], ['год', false, ['года', 'лет', 'годам', 'года', 'годами', 'годах']], ['месяц', false, ['месяцы', 'месяцев', 'месяцам', 'месяцы', 'месяцами', 'месяцах']], ['новость', false, ['новости', 'новостей', 'новостям', 'новости', 'новостями', 'новостях']], - ['тень', false, ['тени', 'теней', 'теням', 'тени', 'тенями', 'тенях']], ['человек', true, ['люди', 'человек', 'людям', 'людей', 'людьми', 'людях']], ['песец', true, ['песцы', 'песцов', 'песцам', 'песцов', 'песцами', 'песцах']], [ @@ -162,9 +189,13 @@ public function pluralWordsProvider() ['руководители', 'руководителей', 'руководителям', 'руководителей', 'руководителями', 'руководителях'], ], ['голосование', true, ['голосования', 'голосований', 'голосованиям', 'голосований', 'голосованиями', 'голосованиях']], + ['дерево', false, ['деревья', 'деревьев', 'деревьям', 'деревья', 'деревьями', 'деревьях']], + ['гений', true, ['гения', 'гениев', 'гениям', 'гениев', 'гениями', 'гениях']], // 3 склонение - ['дерево', false, ['деревья', 'деревьев', 'деревьям', 'деревья', 'деревьями', 'деревьях']], + ['лошадь', false, ['лошади', 'лошадей', 'лошадям', 'лошади', 'лошадями', 'лошадях']], + ['тень', false, ['тени', 'теней', 'теням', 'тени', 'тенями', 'тенях']], + ['ночь', false, ['ночи', 'ночей', 'ночам', 'ночи', 'ночами', 'ночах']], // Адъективное склонение // мужской род