NCL NameCase Core * * Набор основных функций, который позволяют сделать интерфейс слонения русского и украниского языка * абсолютно одинаковым. Содержит все функции для внешнего взаимодействия с библиотекой. * * @author Андрей Чайка * @version 0.4 * @package NameCaseLib */ class NCLNameCaseCore extends NCL { /** * Готовность системы: * - Все слова идентифицированы (известо к какой части ФИО относится слово) * - У всех слов определен пол * Если все сделано стоит флаг true, при добавлении нового слова флаг сбрасывается на false * @var bool */ private $ready = false; /** * Если все текущие слова было просклонены и в каждом слове уже есть результат склонения, * тогда true. Если было добавлено новое слово флаг збрасывается на false * @var bool */ private $finished = false; /** * Массив содержит елементы типа NCLNameCaseWord. Это все слова которые нужно обработать и просклонять * @var array */ private $words = array(); /** * Переменная, в которую заносится слово с которым сейчас идет работа * @var string */ protected $workingWord = ''; /** * Метод Last() вырезает подстроки разной длины. Посколько одинаковых вызовов бывает несколько, * то все результаты выполнения кешируются в этом массиве. * @var array */ protected $workindLastCache = array(); /** * Номер последнего использованого правила, устанавливается методом Rule() * @var int */ private $lastRule = 0; /** * Массив содержит результат склонения слова - слово во всех падежах * @var array */ protected $lastResult = array(); /** * Массив содержит информацию о том какие слова из массива $this->words относятся к * фамилии, какие к отчеству а какие к имени. Массив нужен потому, что при добавлении слов мы не * всегда знаем какая часть ФИО сейчас, поэтому после идентификации всех слов генерируется массив * индексов для быстрого поиска в дальнейшем. * @var array */ private $index = array(); /** * Метод очищает результаты последнего склонения слова. Нужен при склонении нескольких слов. */ private function reset() { $this->lastRule = 0; $this->lastResult = array(); } /** * Сбрасывает все информацию на начальную. Очищает все слова добавленые в систему. * После выполнения система готова работать с начала. */ public function fullReset() { $this->words = array(); $this->index = array('N' => array(), 'F' => array(), 'S' => array()); $this->reset(); $this->notReady(); } /** * Устанавливает флаги о том, что система не готово и слова еще не были просклонены */ private function notReady() { $this->ready = false; $this->finished = false; } /** * Устанавливает номер последнего правила * @param int $index номер правила которое нужно установить */ protected function Rule($index) { $this->lastRule = $index; } /** * Устанавливает слово текущим для работы системы. Очищает кеш слова. * @param string $word слово, которое нужно установить */ protected function setWorkingWord($word) { //Сбрасываем настройки $this->reset(); //Ставим слово $this->workingWord = $word; //Чистим кеш $this->workindLastCache = array(); } /** * Если $stopAfter = 0, тогда вырезает $length последних букв с текущего слова ($this->workingWord) * Если нет, тогда вырезает $stopAfter букв начиная от $length с конца * @param int $length количество букв с конца * @param int $stopAfter количество букв которые нужно вырезать (0 - все) * @return string требуемая подстрока */ protected function Last($length=1, $stopAfter=0) { //Сколько букв нужно вырезать все или только часть if (!$stopAfter) { $cut = $length; } else { $cut = $stopAfter; } //Проверяем кеш if (!isset($this->workindLastCache[$length][$stopAfter])) { $this->workindLastCache[$length][$stopAfter] = NCLStr::substr($this->workingWord, -$length, $cut); } return $this->workindLastCache[$length][$stopAfter]; } /** * Над текущим словом ($this->workingWord) выполняются правила в порядке указаном в $rulesArray. * $gender служит для указания какие правила использовать мужские ('man') или женские ('woman') * @param string $gender - префикс мужских/женских правил * @param array $rulesArray - массив, порядок выполнения правил * @return boolean если правило было задествовано, тогда true, если нет - тогда false */ protected function RulesChain($gender, $rulesArray) { foreach ($rulesArray as $ruleID) { $ruleMethod = $gender . 'Rule' . $ruleID; if ($this->$ruleMethod()) { return true; } } return false; } /** * Если $string строка, тогда проверяется входит ли буква $letter в строку $string * Если $string массив, тогда проверяется входит ли строка $letter в массив $string * @param string $letter буква или строка, которую нужно искать * @param mixed $string строка или массив, в котором нужно искать * @return bool true если искомое значение найдено */ protected function in($letter, $string) { //Если второй параметр массив if (is_array($string)) { return in_array($letter, $string); } else { if (!$letter or NCLStr::strpos($string, $letter) === false) { return false; } else { return true; } } } /** * Функция проверяет, входит ли имя $nameNeedle в перечень имен $names. * @param string $nameNeedle - имя которое нужно найти * @param array $names - перечень имен в котором нужно найти имя */ protected function inNames($nameNeedle, $names) { if (!is_array($names)) { $names = array($names); } foreach ($names as $name) { if (NCLStr::strtolower($nameNeedle) == NCLStr::strtolower($name)) { return true; } } return false; } /** * Склоняет слово $word, удаляя из него $replaceLast последних букв * и добавляя в каждый падеж окончание из массива $endings. * @param string $word слово, к которому нужно добавить окончания * @param array $endings массив окончаний * @param int $replaceLast сколько последних букв нужно убрать с начального слова */ protected function wordForms($word, $endings, $replaceLast=0) { //Создаем массив с именительный падежом $result = array($this->workingWord); //Убираем в окончание лишние буквы $word = NCLStr::substr($word, 0, NCLStr::strlen($word) - $replaceLast); //Добавляем окончания for ($padegIndex = 1; $padegIndex < $this->CaseCount; $padegIndex++) { $result[$padegIndex] = $word . $endings[$padegIndex - 1]; } $this->lastResult = $result; } /** * В массив $this->words добавляется новый об’єкт класса NCLNameCaseWord * со словом $firstname и пометкой, что это имя * @param string $firstname имя */ public function setFirstName($firstname="") { if ($firstname) { $index = count($this->words); $this->words[$index] = new NCLNameCaseWord($firstname); $this->words[$index]->setNamePart('N'); $this->notReady(); } } /** * В массив $this->words добавляется новый об’єкт класса NCLNameCaseWord * со словом $secondname и пометкой, что это фамилия * @param string $secondname фамилия */ public function setSecondName($secondname="") { if ($secondname) { $index = count($this->words); $this->words[$index] = new NCLNameCaseWord($secondname); $this->words[$index]->setNamePart('S'); $this->notReady(); } } /** * В массив $this->words добавляется новый об’єкт класса NCLNameCaseWord * со словом $fathername и пометкой, что это отчество * @param string $fathername отчество */ public function setFatherName($fathername="") { if ($fathername) { $index = count($this->words); $this->words[$index] = new NCLNameCaseWord($fathername); $this->words[$index]->setNamePart('F'); $this->notReady(); } } /** * Всем словам устанавливается пол, который может иметь следующие значения * - 0 - не определено * - NCL::$MAN - мужчина * - NCL::$WOMAN - женщина * @param int $gender пол, который нужно установить */ public function setGender($gender=0) { foreach ($this->words as $word) { $word->setTrueGender($gender); } } /** * В система заносится сразу фамилия, имя, отчество * @param string $secondName фамилия * @param string $firstName имя * @param string $fatherName отчество */ public function setFullName($secondName="", $firstName="", $fatherName="") { $this->setFirstName($firstName); $this->setSecondName($secondName); $this->setFatherName($fatherName); } /** * В массив $this->words добавляется новый об’єкт класса NCLNameCaseWord * со словом $firstname и пометкой, что это имя * @param string $firstname имя */ public function setName($firstname="") { $this->setFirstName($firstname); } /** * В массив $this->words добавляется новый об’єкт класса NCLNameCaseWord * со словом $secondname и пометкой, что это фамилия * @param string $secondname фамилия */ public function setLastName($secondname="") { $this->setSecondName($secondname); } /** * В массив $this->words добавляется новый об’єкт класса NCLNameCaseWord * со словом $secondname и пометкой, что это фамилия * @param string $secondname фамилия */ public function setSirName($secondname="") { $this->setSecondName($secondname); } /** * Если слово $word не идентифицировано, тогда определяется это имя, фамилия или отчество * @param NCLNameCaseWord $word слово которое нужно идентифицировать */ private function prepareNamePart(NCLNameCaseWord $word) { if (!$word->getNamePart()) { $this->detectNamePart($word); } } /** * Проверяет все ли слова идентифицированы, если нет тогда для каждого определяется это имя, фамилия или отчество */ private function prepareAllNameParts() { foreach ($this->words as $word) { $this->prepareNamePart($word); } } /** * Определяет пол для слова $word * @param NCLNameCaseWord $word слово для которого нужно определить пол */ private function prepareGender(NCLNameCaseWord $word) { if (!$word->isGenderSolved()) { $namePart = $word->getNamePart(); switch ($namePart) { case 'N': $this->GenderByFirstName($word); break; case 'F': $this->GenderByFatherName($word); break; case 'S': $this->GenderBySecondName($word); break; } } } /** * Для всех слов проверяет определен ли пол, если нет - определяет его * После этого расчитывает пол для всех слов и устанавливает такой пол всем словам * @return bool был ли определен пол */ private function solveGender() { //Ищем, может гдето пол уже установлен foreach ($this->words as $word) { if ($word->isGenderSolved()) { $this->setGender($word->gender()); return true; } } //Если нет тогда определяем у каждого слова и потом сумируем $man = 0; $woman = 0; foreach ($this->words as $word) { $this->prepareGender($word); $gender = $word->getGender(); $man+=$gender[NCL::$MAN]; $woman+=$gender[NCL::$WOMAN]; } if ($man > $woman) { $this->setGender(NCL::$MAN); } else { $this->setGender(NCL::$WOMAN); } return true; } /** * Генерируется массив, который содержит информацию о том какие слова из массива $this->words относятся к * фамилии, какие к отчеству а какие к имени. Массив нужен потому, что при добавлении слов мы не * всегда знаем какая часть ФИО сейчас, поэтому после идентификации всех слов генерируется массив * индексов для быстрого поиска в дальнейшем. */ private function generateIndex() { $this->index = array('N' => array(), 'S' => array(), 'F' => array()); foreach ($this->words as $index => $word) { $namepart = $word->getNamePart(); $this->index[$namepart][] = $index; } } /** * Выполнет все необходимые подготовления для склонения. * Все слова идентфицируются. Определяется пол. * Обновляется индекс. */ private function prepareEverything() { if (!$this->ready) { $this->prepareAllNameParts(); $this->solveGender(); $this->generateIndex(); $this->ready = true; } } /** * По указаным словам определяется пол человека: * - 0 - не определено * - NCL::$MAN - мужчина * - NCL::$WOMAN - женщина * @return int текущий пол человека */ public function genderAutoDetect() { $this->prepareEverything(); if (isset($this->words[0])) { return $this->words[0]->gender(); } return false; } /** * Разбивает строку $fullname на слова и возвращает формат в котором записано имя * Формат: * - S - Фамилия * - N - Имя * - F - Отчество * @param string $fullname строка, для которой необходимо определить формат * @return array формат в котором записано имя массив типа $this->words */ public function splitFullName($fullname) { $fullname = trim($fullname); $list = explode(' ', $fullname); foreach ($list as $word) { $this->words[] = new NCLNameCaseWord($word); } $this->prepareEverything(); $formatArr = array(); foreach ($this->words as $word) { $formatArr[] = $word->getNamePart(); } return $this->words; } /** * Склоняет слово $word по нужным правилам в зависимости от пола и типа слова * @param NCLNameCaseWord $word слово, которое нужно просклонять */ private function WordCase(NCLNameCaseWord $word) { $gender = ($word->gender() == NCL::$MAN ? 'man' : 'woman'); $namepart = ''; switch ($word->getNamePart()) { case 'F': $namepart = 'Father'; break; case 'N': $namepart = 'First'; break; case 'S': $namepart = 'Second'; break; } $method = $gender . $namepart . 'Name'; $this->setWorkingWord($word->getWord()); if ($this->$method()) { $word->setNameCases($this->lastResult); $word->setRule($this->lastRule); } else { $word->setNameCases(array_fill(0, $this->CaseCount, $word->getWord())); $word->setRule(-1); } } /** * Производит склонение всех слов, который хранятся в массиве $this->words */ private function AllWordCases() { if (!$this->finished) { $this->prepareEverything(); foreach ($this->words as $word) { $this->WordCase($word); } $this->finished = true; } } /** * Если указан номер падежа $number, тогда возвращается строка с таким номером падежа, * если нет, тогда возвращается массив со всеми падежами текущего слова. * @param NCLNameCaseWord $word слово для котрого нужно вернуть падеж * @param int $number номер падежа, который нужно вернуть * @return mixed массив или строка с нужным падежом */ private function getWordCase(NCLNameCaseWord $word, $number=null) { $cases = $word->getNameCases(); if (is_null($number) or $number < 0 or $number > ($this->CaseCount - 1)) { return $cases; } else { return $cases[$number]; } } /** * Если нужно было просклонять несколько слов, то их необходимо собрать в одну строку. * Эта функция собирает все слова указаные в $indexArray в одну строку. * @param array $indexArray индексы слов, которые необходимо собрать вместе * @param int $number номер падежа * @return mixed либо массив со всеми падежами, либо строка с одним падежом */ private function getCasesConnected($indexArray, $number=null) { $readyArr = array(); foreach ($indexArray as $index) { $readyArr[] = $this->getWordCase($this->words[$index], $number); } $all = count($readyArr); if ($all) { if (is_array($readyArr[0])) { //Масив нужно скелить каждый падеж $resultArr = array(); for ($case = 0; $case < $this->CaseCount; $case++) { $tmp = array(); for ($i = 0; $i < $all; $i++) { $tmp[] = $readyArr[$i][$case]; } $resultArr[$case] = implode(' ', $tmp); } return $resultArr; } else { return implode(' ', $readyArr); } } return ''; } /** * Функция ставит имя в нужный падеж. * * Если указан номер падежа $number, тогда возвращается строка с таким номером падежа, * если нет, тогда возвращается массив со всеми падежами текущего слова. * @param int $number номер падежа * @return mixed массив или строка с нужным падежом */ public function getFirstNameCase($number=null) { $this->AllWordCases(); return $this->getCasesConnected($this->index['N'], $number); } /** * Функция ставит фамилию в нужный падеж. * * Если указан номер падежа $number, тогда возвращается строка с таким номером падежа, * если нет, тогда возвращается массив со всеми падежами текущего слова. * @param int $number номер падежа * @return mixed массив или строка с нужным падежом */ public function getSecondNameCase($number=null) { $this->AllWordCases(); return $this->getCasesConnected($this->index['S'], $number); } /** * Функция ставит отчество в нужный падеж. * * Если указан номер падежа $number, тогда возвращается строка с таким номером падежа, * если нет, тогда возвращается массив со всеми падежами текущего слова. * @param int $number номер падежа * @return mixed массив или строка с нужным падежом */ public function getFatherNameCase($number=null) { $this->AllWordCases(); return $this->getCasesConnected($this->index['F'], $number); } /** * Функция ставит имя $firstName в нужный падеж $CaseNumber по правилам пола $gender. * * Если указан номер падежа $CaseNumber, тогда возвращается строка с таким номером падежа, * если нет, тогда возвращается массив со всеми падежами текущего слова. * @param string $firstName имя, которое нужно просклонять * @param int $CaseNumber номер падежа * @param int $gender пол, который нужно использовать * @return mixed массив или строка с нужным падежом */ public function qFirstName($firstName, $CaseNumber=null, $gender=0) { $this->fullReset(); $this->setFirstName($firstName); if ($gender) { $this->setGender($gender); } return $this->getFirstNameCase($CaseNumber); } /** * Функция ставит фамилию $secondName в нужный падеж $CaseNumber по правилам пола $gender. * * Если указан номер падежа $CaseNumber, тогда возвращается строка с таким номером падежа, * если нет, тогда возвращается массив со всеми падежами текущего слова. * @param string $secondName фамилия, которую нужно просклонять * @param int $CaseNumber номер падежа * @param int $gender пол, который нужно использовать * @return mixed массив или строка с нужным падежом */ public function qSecondName($secondName, $CaseNumber=null, $gender=0) { $this->fullReset(); $this->setSecondName($secondName); if ($gender) { $this->setGender($gender); } return $this->getSecondNameCase($CaseNumber); } /** * Функция ставит отчество $fatherName в нужный падеж $CaseNumber по правилам пола $gender. * * Если указан номер падежа $CaseNumber, тогда возвращается строка с таким номером падежа, * если нет, тогда возвращается массив со всеми падежами текущего слова. * @param string $fatherName отчество, которое нужно просклонять * @param int $CaseNumber номер падежа * @param int $gender пол, который нужно использовать * @return mixed массив или строка с нужным падежом */ public function qFatherName($fatherName, $CaseNumber=null, $gender=0) { $this->fullReset(); $this->setFatherName($fatherName); if ($gender) { $this->setGender($gender); } return $this->getFatherNameCase($CaseNumber); } /** * Склоняет текущие слова во все падежи и форматирует слово по шаблону $format * Формат: * - S - Фамилия * - N - Имя * - F - Отчество * @param string $format строка формат * @return array массив со всеми падежами */ public function getFormattedArray($format) { if (is_array($format)) { return $this->getFormattedArrayHard($format); } $length = NCLStr::strlen($format); $result = array(); $cases = array(); for ($i = 0; $i < $length; $i++) { $symbol = NCLStr::substr($format, $i, 1); if ($symbol == 'S') { $cases['S'] = $this->getSecondNameCase(); } elseif ($symbol == 'N') { $cases['N'] = $this->getFirstNameCase(); } elseif ($symbol == 'F') { $cases['F'] = $this->getFatherNameCase(); } } for ($curCase = 0; $curCase < $this->CaseCount; $curCase++) { $line = ""; for ($i = 0; $i < $length; $i++) { $symbol = NCLStr::substr($format, $i, 1); if ($symbol == 'S') { $line.=$cases['S'][$curCase]; } elseif ($symbol == 'N') { $line.=$cases['N'][$curCase]; } elseif ($symbol == 'F') { $line.=$cases['F'][$curCase]; } else { $line.=$symbol; } } $result[] = $line; } return $result; } /** * Склоняет текущие слова во все падежи и форматирует слово по шаблону $format * Формат: * - S - Фамилия * - N - Имя * - F - Отчество * @param array $format массив с форматом * @return array массив со всеми падежами */ public function getFormattedArrayHard($format) { $result = array(); $cases = array(); foreach ($format as $word) { $cases[] = $word->getNameCases(); } for ($curCase = 0; $curCase < $this->CaseCount; $curCase++) { $line = ""; foreach ($cases as $value) { $line.=$value[$curCase] . ' '; } $result[] = trim($line); } return $result; } /** * Склоняет текущие слова в падеж $caseNum и форматирует слово по шаблону $format * Формат: * - S - Фамилия * - N - Имя * - F - Отчество * @param array $format массив с форматом * @return string строка в нужном падеже */ public function getFormattedHard($caseNum=0, $format=array()) { $result = ""; foreach ($format as $word) { $cases = $word->getNameCases(); $result.= $cases[$caseNum] . ' '; } return trim($result); } /** * Склоняет текущие слова в падеж $caseNum и форматирует слово по шаблону $format * Формат: * - S - Фамилия * - N - Имя * - F - Отчество * @param string $format строка с форматом * @return string строка в нужном падеже */ public function getFormatted($caseNum=0, $format="S N F") { //Если не указан падеж используем другую функцию if (is_null($caseNum)) { return $this->getFormattedArray($format); } //Если формат сложный elseif (is_array($format)) { return $this->getFormattedHard($caseNum, $format); } else { $length = NCLStr::strlen($format); $result = ""; for ($i = 0; $i < $length; $i++) { $symbol = NCLStr::substr($format, $i, 1); if ($symbol == 'S') { $result.=$this->getSecondNameCase($caseNum); } elseif ($symbol == 'N') { $result.=$this->getFirstNameCase($caseNum); } elseif ($symbol == 'F') { $result.=$this->getFatherNameCase($caseNum); } else { $result.=$symbol; } } return $result; } } /** * Склоняет фамилию $secondName, имя $firstName, отчество $fatherName * в падеж $caseNum по правилам пола $gender и форматирует результат по шаблону $format * Формат: * - S - Фамилия * - N - Имя * - F - Отчество * @param string $secondName фамилия * @param string $firstName имя * @param string $fatherName отчество * @param int $gender пол * @param int $caseNum номер падежа * @param string $format формат * @return mixed либо массив со всеми падежами, либо строка */ public function qFullName($secondName="", $firstName="", $fatherName="", $gender=0, $caseNum=0, $format="S N F") { $this->fullReset(); $this->setFirstName($firstName); $this->setSecondName($secondName); $this->setFatherName($fatherName); if ($gender) { $this->setGender($gender); } return $this->getFormatted($caseNum, $format); } /** * Склоняет ФИО $fullname в падеж $caseNum по правилам пола $gender. * Возвращает результат в таком же формате, как он и был. * @param string $fullname ФИО * @param int $caseNum номер падежа * @param int $gender пол человека * @return mixed либо массив со всеми падежами, либо строка */ public function q($fullname, $caseNum=null, $gender=null) { $this->fullReset(); $format = $this->splitFullName($fullname); if ($gender) { $this->setGender($gender); } $this->AllWordCases(); return $this->getFormatted($caseNum, $format); } } ?>