From ff1acaf5cf3462054114a7b106dd55168b2b19eb Mon Sep 17 00:00:00 2001 From: wapmorgan Date: Wed, 20 Sep 2017 23:22:13 +0300 Subject: [PATCH] Improve geo names inflection --- src/BaseInflection.php | 20 ++++++ src/CasesHelper.php | 13 ++++ src/Russian/GeographicalNamesInflection.php | 29 +++++++- src/Russian/RussianLanguage.php | 78 +++++++++++++++++++-- 4 files changed, 132 insertions(+), 8 deletions(-) diff --git a/src/BaseInflection.php b/src/BaseInflection.php index 37ac6c9..4099e5f 100644 --- a/src/BaseInflection.php +++ b/src/BaseInflection.php @@ -6,10 +6,30 @@ abstract class BaseInflection implements Cases public static function isMutable($name) { } + public static function getCases($name) { } + public static function getCase($name, $case) { } + + /** + * Составляет один массив с падежами из нескольких массивов падежей разных слов + * @param array $words Двумерный массив слов и их падежей + * @param string $delimiter Разделитель между падежами слов + * @return array Одномерный массив падежей + */ + public static function composeCasesFromWords(array $words, $delimiter = ' ') { + $cases = []; + foreach (CasesHelper::getAllCases() as $case) { + $composed_case = []; + foreach ($words as $wordCases) { + $composed_case[] = $wordCases[$case]; + } + $cases[$case] = implode($delimiter, $composed_case); + } + return $cases; + } } diff --git a/src/CasesHelper.php b/src/CasesHelper.php index c525620..b0872ad 100644 --- a/src/CasesHelper.php +++ b/src/CasesHelper.php @@ -40,4 +40,17 @@ public static function canonizeCase($case) throw new Exception('Invalid case: '.$case); } } + + public static function getAllCases() + { + return [ + Cases::NOMINATIVE, + Cases::GENITIVE, + Cases::GENETIVE, + Cases::DATIVE, + Cases::ACCUSATIVE, + Cases::ABLATIVE, + Cases::PREPOSITIONAL, + ]; + } } diff --git a/src/Russian/GeographicalNamesInflection.php b/src/Russian/GeographicalNamesInflection.php index 6b89715..462b985 100644 --- a/src/Russian/GeographicalNamesInflection.php +++ b/src/Russian/GeographicalNamesInflection.php @@ -20,12 +20,25 @@ class GeographicalNamesInflection extends \morphos\BaseInflection implements Cas public static function isMutable($name) { $name = S::lower($name); + // // ends with 'ы' or 'и': plural form // if (in_array(S::slice($name, -1), array('и', 'ы'))) // return false; + if (in_array($name, self::$abbreviations)) { return false; } + + // N край + if (S::slice($name, -5) == ' край') { + return static::isMutable(S::slice($name, 0, -5)); + } + + // город N + if (S::slice($name, 0, 6) == 'город ') { + return true; + } + // ends with 'е' or 'о', but not with 'ово/ёво/ево/ино/ыно' if (in_array(S::slice($name, -1), array('е', 'о')) && !in_array(S::slice($name, -3, -1), array('ов', 'ёв', 'ев', 'ин', 'ын'))) { return false; @@ -37,6 +50,20 @@ public static function getCases($name) { $name = S::lower($name); + // N край + if (S::slice($name, -5) == ' край') { + return self::composeCasesFromWords([static::getCases(S::slice($name, 0, -5)), NounDeclension::getCases('край')]); + + } + + // город N + if (S::slice($name, 0, 6) == 'город ') { + return self::composeCasesFromWords([ + NounDeclension::getCases('город'), + array_combine(self::getAllCases(), array_fill(0, 6, S::slice($name, -6))) + ]); + } + // check for name of two words if (strpos($name, ' ') !== false) { $parts = explode(' ', $name); @@ -69,7 +96,7 @@ public static function getCases($name) self::DAT => $prefix.(self::isVelarConsonant(S::slice($name, -3, -2)) ? 'ому' : 'ему'), self::VINIT => $prefix.'ий', self::TVORIT => $prefix.'им', - self::PREDLOJ => self::choosePrepositionByFirstLetter($prefix, 'об', 'о').' '.$prefix.'ем', + self::PREDLOJ => self::choosePrepositionByFirstLetter($prefix, 'об', 'о').' '.$prefix.(self::chooseEndingBySonority($prefix, 'ем', 'ом')), ); } else if (S::slice($name, -2) == 'ый') { // Грозный, Благодарный diff --git a/src/Russian/RussianLanguage.php b/src/Russian/RussianLanguage.php index ab10fa8..7484982 100644 --- a/src/Russian/RussianLanguage.php +++ b/src/Russian/RussianLanguage.php @@ -6,6 +6,9 @@ trait RussianLanguage { + /** + * @var array Все гласные + */ public static $vowels = array( 'а', 'е', @@ -19,6 +22,9 @@ trait RussianLanguage 'я', ); + /** + * @var array Все согласные + */ public static $consonants = array( 'б', 'в', @@ -43,6 +49,9 @@ trait RussianLanguage 'щ', ); + /** + * @var array Пары согласных + */ public static $pairs = array( 'б' => 'п', 'в' => 'ф', @@ -52,29 +61,50 @@ trait RussianLanguage 'з' => 'с', ); - public static $deafConsonants = ['х', 'ч', 'щ']; - public static $sonorousConsonants = ['л', 'м', 'н', 'р']; - + /** + * @var array Звонкие согласные + */ + public static $sonorousConsonants = ['б', 'в', 'г', 'д', 'з', 'ж', 'л', 'м', 'н', 'р']; + /** + * @var array Глухие согласные + */ + public static $deafConsonants = ['п', 'ф', 'к', 'т', 'с', 'ш', 'х', 'ч', 'щ']; + + /** + * Проверка гласной + */ public static function isVowel($char) { return in_array($char, self::$vowels); } + /** + * Проверка согласной + */ public static function isConsonant($char) { return in_array($char, self::$consonants); } - public static function isDeafConsonant($char) + /** + * Проверка звонкости согласной + */ + public static function isSonorousConsonant($char) { - return in_array($char, self::$deafConsonants); + return in_array($char, self::$sonorousConsonants); } - public static function isSonorousConsonant($char) + /** + * Проверка глухости согласной + */ + public static function isDeafConsonant($char) { - return in_array($char, self::$sonorousConsonants); + return in_array($char, self::$deafConsonants); } + /** + * Щипящая ли согласная + */ public static function isHissingConsonant($consonant) { return in_array(S::lower($consonant), array('ж', 'ш', 'ч', 'щ')); @@ -85,17 +115,26 @@ protected static function isVelarConsonant($consonant) return in_array(S::lower($consonant), array('г', 'к', 'х')); } + /** + * Подсчет слогов + */ public static function countSyllables($string) { return S::chars_count($string, self::$vowels); } + /** + * Проверка парности согласной + */ public static function isPaired($consonant) { $consonant = S::lower($consonant); return array_key_exists($consonant, self::$pairs) || (array_search($consonant, self::$pairs) !== false); } + /** + * Проверка мягкости последней согласной + */ public static function checkLastConsonantSoftness($word) { if (($substring = S::last_position_for_one_of_chars(S::lower($word), self::$consonants)) !== false) { @@ -108,6 +147,9 @@ public static function checkLastConsonantSoftness($word) return false; } + /** + * Выбор предлога по первой букве + */ public static function choosePrepositionByFirstLetter($word, $prepositionWithVowel, $preposition) { if (in_array(S::upper(S::slice($word, 0, 1)), array('А', 'О', 'И', 'У', 'Э'))) { @@ -117,6 +159,9 @@ public static function choosePrepositionByFirstLetter($word, $prepositionWithVow } } + /** + * Выбор окончания в зависимости от мягкости + */ public static function chooseVowelAfterConsonant($last, $soft_last, $after_soft, $after_hard) { if ((RussianLanguage::isHissingConsonant($last) && !in_array($last, array('ж', 'ч'))) || /*self::isVelarConsonant($last) ||*/ $soft_last) { @@ -185,4 +230,23 @@ public static function about($word) return 'о '.$word; } + + /** + * Выбирает первое или второе окончание в зависимости от звонкости/глухости в конце слова. + * @param string $word Слово (или префикс), на основе звонкости которого нужно выбрать окончание + * @param string $ifSonorous Окончание, если слово оканчивается на звонкую согласную + * @param string $ifDead Окончание, если слово оканчивается на глухую согласную + * @return string Первое или второе окончание + */ + public static function chooseEndingBySonority($word, $ifSononous, $ifDeaf) + { + $last = S::slice($word, -1); + var_dump($last); + if (self::isSonorousConsonant($last)) + return $ifSononous; + if (self::isDeafConsonant($last)) + return $ifDeaf; + + throw new \Exception('Not implemented'); + } }