функция подсветки слов

Тема в разделе "Как сделать...", создана пользователем dmx, 28 окт 2015.

  1. dmx

    dmx

    Регистр.:
    22 июн 2011
    Сообщения:
    676
    Симпатии:
    557
    Нужно сделать подсветку ключевых слов в тексте
    Найдена вот такая функция:

    PHP:
    <?php
    /*
    Функция подсветки слов
    $text - текст
    $keywords - ключевые слова

    $onlystring - определяет способ вывода
      * false - выводим весь текст
      * true-выводим только строки, содержащие ключевые слова

    $integrity - определяет параметры поиска
      * false - любое совпадение
      * true - фразу целиком

    $fontcolor - каким цветом подсвечивать
    */
        
    function light_text($text$keywords$onlystring false$integrity true$fontcolor "red")
        {
    //Пробелы
            
    $keywords trim($keywords);
            
    $text trim($text);
        
    //Проверка полученных данных
            
    if(empty($text) || empty($keywords))
                return 
    false;
        
    //Спецсимволы PCRE
            
    $keywords preg_quote($keywords'#');

        
    //Ищем html, style, script, title
            
    $re '#<script[^>]*?>.*?</script>|<style[^>]*?>.*?</style\s*?>|<title\s*?>.*?</title\s*?>|<.*?>#is';
            
    preg_match_all($re$text$html);
        
    //Удаляем одинаковые элементы, сортируем для того чтобы сбросить ключи
            
    $html array_unique($html[0]);
            
    sort($html);

        
    //Массив с указателями
            
    $j 1420;
            
    $i 0;
            while(
    $i<count($html))
            {
                
    $token[$i] = html_entity_decode('&#'$j .';'ENT_NOQUOTES'UTF-8');
                
    $tags[$i] = $html[$i];
                
    $i++;
                
    $j++;
            }

        
    //Замена html, style, script, title указателями
            
    if(!empty($html))
                
    $text str_replace($tags$token$text);

        
    //Поиск
            
    if(!empty($integrity))
            {
    //Если ищем всю фразу
                
    $re '('$keywords .')';
            }
            else
            {
    //Любое совпадение
            //Массив с ключевыми словами
                
    $keywords explode(' '$keywords);
            
    //Удаляем пустые элементы
                
    foreach($keywords as $k=>$v)
                {
                    if(empty(
    $v)) unset($keywords[$k]);
                }

                
    $re '('implode('|'$keywords) .')';
            }

            
    //Ищем и подсвечиваем
            
    if($onlystring)
            {
    //Если выводим только строки
                
    $re_for_string '#.*'$re .'.*#ui';
                
    preg_match_all($re_for_string$text$text);
                
    $text implode("\n"$text[0]);
                if(!empty(
    $token))
                    
    $text str_replace($token''$text);
            }

            
    $re '#'$re .'#uis';

            
    $text preg_replace($re'<font color="'$fontcolor .'">\\1</font>'$text);
        
    //Вставляем html,css,js на место
            
    if(!empty($html))
                
    $text str_replace($token$html$text);

        
    //Возвращаем
            
    return $text;

        }


    $text 'А вот не объявить ли новый конкурс к Новому году приуроченный?


    Задачка небольшенькая по объему, но достаточно интересная по реализации.

    Итак.
    Требуется написать скрипт подсветки ключевых слов поиска.

    Скрипт должен быть универсальным, в виде функции или класса, легко интегрируемый в любую систему.

    Особенности таких скриптов, которые нужно учесть.

    1. Подсвечиваться должны несколько ключевых слов.
    2. Регистр не учитываем.
    3. Слова могут повторяться и идти вразнобой.
    4. Сам скрипт поиска не нужен, только механизм подсветки.
    5. Результат примерно как в тексте задачи, если бы в скрипт передали фразу "скрипт подсветки слов поиска"


    Ну разумеется корректность, оптимальность, безопасность, читабельность.
    Стартуем, как наберем минимум 10 человек. Принимать участие могут все желающие.
    Заявляться в этой ветке.

    Оценивать будут члены жюри, желающие эксперты тоже здесь заявляйтесь.
    Если желающих не будет, я сам вынесу вердикт.
    Экспериментов больше не будет.

    Ну и из песни слов не выкинуть - приз.
    По традиции любая книга по программированию, которую можно купить в интернет-магазине.

    Смелей, товарищи! Помните, главное не участие, главное победа!


    PS Совсем забыл. Дополнительный функционал приветствуется. Допустим обрезка текста до и после найденных слов, настройка вариантов и прочее.
    Вобщем чем круче скрипт, тем больше шансов.'
    ;

    $keywords 'скрипт подсветки слов поиска';

    echo  
    light_text($text$keywords);
    подсвечивает только точное слово. Типа: слово
    А надо чтоб учитывало и склонения или как их назвать. т.е полностью слово
    так же, хотелось бы не подсвечивать слова меньше 2 символов (различные предлоги).
    Ну и оставить только саму функцию, лишнего не надо ))
     
  2. Denixxx

    Denixxx

    Регистр.:
    7 фев 2014
    Сообщения:
    247
    Симпатии:
    194
    Понятно.
    Ну я думаю тут вопрос гораздо шире — ведь эти словоформы должны и искаться в БД, и подсвечиваться.
    Потому что поддержка русского языка при FULLTEXT поиске в MySQL мягко говоря — сосёт.
    Наиболее близко подходит для этого Стеммер Портера.
    Я переделывал и применял его в таком виде:
    PHP:
    class Stemmer {
        
    //Предлоги и стоп слова — будут заменены на пробел
        
    private $stop_words = array(
        
    '  ',' без ',' в ',' для ',' и ',' из ',' к ',' на ',' над ',' по ',' под ',' подле ',' про ',' при ',' с ',' о ',' от ',' у ',"\n","\r"
        
    );
        private 
    $vowel "аеёиоуыэюя";
        private 
    $regexPerfectiveGerunds = array(
        
    "(в|вши|вшись)$",
        
    "(ив|ивши|ившись|ыв|ывши|ывшись)$"
        
    );
        private 
    $regexAdjective "(ее|ие|ые|ое|ими|ыми|ей|ий|ый|ой|ем|им|ым|ом|его|ого|ему|ому|их|ых|ую|юю|ая|яя|ою|ею)$";
        private 
    $regexParticiple = array(
        
    "(ем|нн|вш|ющ|щ)",
        
    "(ивш|ывш|ующ)"
        
    );
        private 
    $regexReflexives "(ся|сь)$";
        private 
    $regexVerb = array(
        
    "(ла|на|ете|йте|ли|й|л|ем|н|ло|но|ет|ют|ны|ть|ешь|нно)$",
        
    "(ила|ыла|ена|ейте|уйте|ите|или|ыли|ей|уй|ил|ыл|им|ым|ен|ило|ыло|ено|ят|ует|уют|ит|ыт|ены|ить|ыть|ишь|ую|ю)$"
        
    );
        private 
    $regexNoun "(а|ев|ов|ие|ье|е|иями|ями|ами|еи|ии|и|ией|ей|ой|ий|й|иям|ям|ием|ем|ам|ом|о|у|ах|иях|ях|ы|ь|ию|ью|ю|ия|ья|я)$";
        private 
    $regexSuperlative "(ейш|ейше)$";
        private 
    $regexDerivational "(ост|ость)$";
        private 
    $regexI "и$";
        private 
    $regexNN "нн$";
        private 
    $regexSoftSign "ь$";
        private 
    $regexOkEk "(ок|ек)$";
        private 
    $word '';
        private 
    $RV 0;
        private 
    $R2 0;
      
        public function 
    __construct($to_lower=true,$remove_punctuation=true,$replace_yo=true)    {
            
    //Приводить слова в нижний регистр?
            
    $this->to_lower $to_lower;
            
    //Удалить пунктуацию?
            
    $this->remove_punctuation $remove_punctuation;
            
    //Заменять ё на е?
            
    $this->replace_yo $replace_yo;
        }
      
        public function 
    getText($text,$limit=false,$strip_tags=true,$remove_stop_words=true,$before=' ',$after='')    {
            if (
    $strip_tags$text strip_tags($text);
            if (
    $remove_stop_words$text str_replace($this->stop_words' ',$text);
            
    $result '';
            
    $words=explode(' '$text);
            if (empty(
    $words)) return '';
            
    //if ($limit) $words=array_slice($words, 0, $limit);
            
    foreach ($words as $word) {
                
    $word $this->getWordBase($word);
                if (!empty(
    $word)){
                    if (
    $limit$lim[] = $before.$word.$after;
                    else 
    $result .= $before.$word.$after;
                }
            }  
            if (!empty(
    $lim)) $result.=implode('',array_slice($lim,0,$limit));
            return 
    $result;
        }
      
        public function 
    getWordBase($word)    {
            
    mb_internal_encoding('UTF-8');
            if (!empty(
    $this->to_lower)) $word mb_strtolower($word);
            if (!empty(
    $this->remove_punctuation)) $word=str_replace(array('?','!',',','.',':',';','«','»',' —','"','\''), ''$word);
            if (!empty(
    $this->replace_yo)) $word=str_replace('ё''е'$word);
            
    $this->word trim($word);
            if (empty(
    $word)) return '';
            
    $this->findRegions();
            
    //Шаг 1
            //Найти окончание PERFECTIVE GERUND. Если оно существует – удалить его и завершить этот шаг.
            
    if (!$this->removeEndings($this->regexPerfectiveGerunds$this->RV)) {
                
    //Иначе, удаляем окончание REFLEXIVE (если оно существует).
                
    $this->removeEndings($this->regexReflexives$this->RV);
                
    //Затем в следующем порядке пробуем удалить окончания: ADJECTIVAL, VERB, NOUN. Как только одно из них найдено – шаг завершается.
                
    if (!($this->removeEndings(
                array(
                    
    $this->regexParticiple[0] . $this->regexAdjective,
                    
    $this->regexParticiple[1] . $this->regexAdjective
                
    ),
                
    $this->RV
                
    ) || $this->removeEndings($this->regexAdjective$this->RV))
                ) {
                    if (!
    $this->removeEndings($this->regexVerb$this->RV)) {
                        
    $this->removeEndings($this->regexNoun$this->RV);
                    }
                }
            }
            
    //Шаг 2
            //Если слово оканчивается на и – удаляем и.
            
    $this->removeEndings($this->regexI$this->RV);
            
    //Шаг 3
            //Если в R2 найдется окончание DERIVATIONAL – удаляем его.
            
    $this->removeEndings($this->regexDerivational$this->R2);
            
    //Шаг 4
            //Возможен один из трех вариантов:
            //Если слово оканчивается на нн – удаляем последнюю букву.
            
    if ($this->removeEndings($this->regexNN$this->RV)) {
                
    $this->word .= 'н';
            }
            
    //Если слово оканчивается на SUPERLATIVE – удаляем его и снова удаляем последнюю букву, если слово оканчивается на нн.
            
    $this->removeEndings($this->regexSuperlative$this->RV);
            
    //Если слово оканчивается на ь – удаляем его.
            
    $this->removeEndings($this->regexSoftSign$this->RV);
            
    //Добавлена поправка удаления ок/ек
            
    $this->removeEndings($this->regexOkEk$this->RV);
            return 
    $this->word;
        }
      
        public function 
    removeEndings($regex$region)    {
            
    $prefix mb_substr($this->word0$region'utf8');
            
    $word substr($this->word,strlen($prefix));
            if (
    is_array($regex)) {
                if (
    preg_match('/.+[а|я]' $regex[0] . '/u'$word)) {
                    
    $this->word $prefix preg_replace('/' $regex[0] . '/u'''$word);
                    return 
    true;
                }
                
    $regex $regex[1];
            }
            if (
    preg_match('/.+' $regex '/u'$word)) {
                
    $this->word $prefix preg_replace('/' $regex '/u'''$word);
                return 
    true;
            }
            return 
    false;
        }
      
        private function 
    findRegions()    {
            
    $state 0;
            
    $wordLength mb_strlen($this->word'utf8');
            for (
    $i 1$i $wordLength$i++) {
                
    $prevChar mb_substr($this->word$i 11'utf8');
                
    $char mb_substr($this->word$i1'utf8');
                switch (
    $state) {
                    case 
    0:
                        if (
    $this->isVowel($char)) {
                        
    $this->RV $i 1;
                        
    $state 1;
                    }
                    break;
                    case 
    1:
                    if (
    $this->isVowel($prevChar) && !$this->isVowel($char)) {
                        
    $state 2;
                    }
                    break;
                    case 
    2:
                    if (
    $this->isVowel($prevChar) && !$this->isVowel($char)) {
                        
    $this->R2 $i 1;
                    return;
                    }
                    break;
                }
            }
        }
      
        private function 
    isVowel($char) {
                return (
    strpos($this->vowel$char) !== false);
        }
    }

    $text='  
    <p class="showtext"><strong>На наших матрицах НЕТ битых пикселей в отличие от большинства конкурентов! Проверено!</strong></p>
    <p class="helptext"><strong>В списке совместимостей с моделями ноутбуков указаны лишь некоторые (наиболее распространенные) модели ноутбуков, но это далеко не полный список. Если вы не знаете точно, подойдет ли данная матрица к вашему ноутбуку, свяжитесь с нами, мы вас обязательно проконсультируем.</strong></p><br>
    <h2>Матрица 08.9" CLAA089NA0CCW Chunghwa - доп. информация :</h2>
    <p class="helptext">Описанный на данной странице товар является новым и соответствует матрицам, устанавливаемым в серийные ноутбуки. На этот товар, как и на все остальные экраны для ноутбуков, действует <strong>гарантия от нашего магазина</strong>. <b>Матрица 08.9" CLAA089NA0CCW Chunghwa</b> - это оригинальный дисплей, совместимый со многими моделями ноутбуков. Если вы планировали купить данный товар, но в настоящее время его нет в наличии — свяжитесь с нами, и мы подберем для вас 100%-совместимую матрицу другого производителя.</p><br>
    '
    ;
    $stemmer = new Stemmer;
    echo 
    '<h2>Исходный текст:</h2><hr>'.$text.'<hr>';

    echo 
    '<h2>Результат</h2><hr>'.$stemmer->getText($text);

    Этот пример берёт текст и прогоняет через Стеммер. В результате мы получаем набор словоформ, которые можно искать в тексте, подсвечивать и т.д.
    Теги, окончания, знаки препинания и прочее вырезаются — остаются только корни слов.
    Думаю, переделаете под себя без проблем.
     
    Последнее редактирование: 28 окт 2015
    dmx нравится это.
  3. Denixxx

    Denixxx

    Регистр.:
    7 фев 2014
    Сообщения:
    247
    Симпатии:
    194
    В посте ограничение символов, потому дополню.
    Алгоритм обычно такой, короче:
    1. При сохранении текста обрабатываешь текст Стеммером и полученный набор корней слов сохраняешь в спец. поле для поиска.
    2. При поиске основное поле текста не используется, а запрос обрабатывается Стеммером и потом ищется не в исходном тексте, а в спец. поле.
    При этом спец. поле индексируешь в БД — т.к. там нет тегов и лишних знаков, это поле примерно в 5 раз меньше исходного текста.
    В результате и поиск будет прост и быстр (не Фуллтекст, а простым LIKE с логическим OR между словами).
    3. При выводе найденного текста подсвечиваешь слова своей функцией.
    Вроде всё.
    Изначальный функционал MySQL гораздо хуже. И это вряд ли лечится — когда искал подобное, предлагали Сфинкс и др. страшные вещи, недоступные на виртуальном хостинге.
     
    dmx нравится это.
  4. javx

    javx

    Регистр.:
    28 авг 2015
    Сообщения:
    528
    Симпатии:
    246
    Я бы решил проблему с окончаниями следующим образом: регуляркой искал "/($key.*)/" - любой символ но не пробел. Далее обрамляем полученное вхождение нужными стилями через функцию по замене.
     
    dmx нравится это.
  5. dmx

    dmx

    Регистр.:
    22 июн 2011
    Сообщения:
    676
    Симпатии:
    557
    воот!) можешь это оформить в коде?
     
  6. javx

    javx

    Регистр.:
    28 авг 2015
    Сообщения:
    528
    Симпатии:
    246
    PHP:
    <?php
    header
    ("Content-Type: text/html; charset=utf-8");
    $text "я мудак и я ебу собак";
    $key "муд";


    function 
    lol($text,$key){
        if(
    iconv_strlen($key) > 2){
    preg_match_all("|$key.*\s|Ui",$text,$match);
    $style '<span style="color:red">'.$match[0][0].'</span>';
    $text str_replace($match[0][0], $style$text);
        }
        return 
    $text;
    }

    echo 
    lol($text,$key);
    нужно доделать $key = array('муд','соб'); что бы с массивами работало, но я хочу баиньки :confused:
     
    dmx нравится это.
  7. dmx

    dmx

    Регистр.:
    22 июн 2011
    Сообщения:
    676
    Симпатии:
    557
    так как в $key будут попадать предлоги, нужно это вычистить. и независим от регистра.
    буду ждать :)
     
  8. javx

    javx

    Регистр.:
    28 авг 2015
    Сообщения:
    528
    Симпатии:
    246
    PHP:
    <?php
    header
    ("Content-Type: text/html; charset=utf-8");
    $text "я мудак и я ебу собак ";
    $key = array('соб','муд');

    function 
    lol($text,$key){
       
        foreach(
    $key as $key_ok){
        if(
    iconv_strlen($key_ok) > 2){
    preg_match_all("|$key_ok.*\s|Ui",$text,$match);
    $style '<span style="color:red">'.$match[0][0].'</span>';
    $text str_replace($match[0][0], $style$text);
        }
    }
        
       return 
    $text;
    }

    echo 
    lol($text,$key);
    ?>
     
    dmx нравится это.