Выделите ключевые слова в абзаце

Мне нужно выделить ключевое слово в абзаце, как это делает google в результатах поиска. Предположим, что у меня есть MySQL db с сообщениями в блоге. Когда пользователь ищет определенное ключевое слово, я хочу вернуть сообщения, содержащие эти ключевые слова, но показывать только части сообщений (абзац, содержащий искомое ключевое слово), и выделять эти ключевые слова.

Мой план таков:

  • найти идентификатор сообщения, в котором есть искомое ключевое слово в его содержимом;
  • снова прочитайте содержимое этого сообщения и поместите каждое слово в фиксированный буферный массив (50 слов), пока не найду ключевое слово.

Можете ли вы помочь мне с некоторой логикой или, по крайней мере, сказать мне, если моя логика в порядке? Я нахожусь на этапе обучения PHP.

Если он содержит html (обратите внимание, что это довольно надежное решение):

$string = '<p>foo<b>bar</b></p>'; $keyword = 'foo'; $dom = new DomDocument(); $dom->loadHtml($string); $xpath = new DomXpath($dom); $elements = $xpath->query('//*[contains(.,"'.$keyword.'")]'); foreach ($elements as $element) { foreach ($element->childNodes as $child) { if (!$child instanceof DomText) continue; $fragment = $dom->createDocumentFragment(); $text = $child->textContent; $stubs = array(); while (($pos = stripos($text, $keyword)) !== false) { $fragment->appendChild(new DomText(substr($text, 0, $pos))); $word = substr($text, $pos, strlen($keyword)); $highlight = $dom->createElement('span'); $highlight->appendChild(new DomText($word)); $highlight->setAttribute('class', 'highlight'); $fragment->appendChild($highlight); $text = substr($text, $pos + strlen($keyword)); } if (!empty($text)) $fragment->appendChild(new DomText($text)); $element->replaceChild($fragment, $child); } } $string = $dom->saveXml($dom->getElementsByTagName('body')->item(0)->firstChild); 

Результаты в:

 <p><span class="highlight">foo</span><b>bar</b></p> 

И с:

 $string = '<body><p>foobarbaz<b>bar</b></p></body>'; $keyword = 'bar'; 

Вы получаете (разбитый на несколько строк для удобочитаемости):

 <p>foo <span class="highlight">bar</span> baz <b> <span class="highlight">bar</span> </b> </p> 

Опасайтесь решений, которые не являются str_replace (например, regex или str_replace ), поскольку выделение чего-то типа «div» имеет тенденцию полностью разрушать ваш HTML … Это будет только «выделять» строки в теле, а не внутри тега …


Изменить Так как вы хотите результаты в стиле Google, вот один из способов сделать это:

 function getKeywordStubs($string, array $keywords, $maxStubSize = 10) { $dom = new DomDocument(); $dom->loadHtml($string); $xpath = new DomXpath($dom); $results = array(); $maxStubHalf = ceil($maxStubSize / 2); foreach ($keywords as $keyword) { $elements = $xpath->query('//*[contains(.,"'.$keyword.'")]'); $replace = '<span class="highlight">'.$keyword.'</span>'; foreach ($elements as $element) { $stub = $element->textContent; $regex = '#^.*?((\w*\W*){'. $maxStubHalf.'})('. preg_quote($keyword, '#'). ')((\w*\W*){'. $maxStubHalf.'}).*?$#ims'; preg_match($regex, $stub, $match); var_dump($regex, $match); $stub = preg_replace($regex, '\\1\\3\\4', $stub); $stub = str_ireplace($keyword, $replace, $stub); $results[] = $stub; } } $results = array_unique($results); return $results; } 

Итак, то, что это делает, возвращает массив совпадений с $maxStubSize словами вокруг него (а именно, до половины этого числа до, и половина после) …

Итак, учитывая строку:

 <p>a whole <b>bunch of</b> text <a>here for</a> us to foo bar baz replace out from this string <b>bar</b> </p> 

Вызов getKeywordStubs($string, array('bar', 'bunch')) приведет к:

 array(4) { [0]=> string(75) "here for us to foo <span class="highlight">bar</span> baz replace out from " [3]=> string(34) "<span class="highlight">bar</span>" [4]=> string(62) "a whole <span class="highlight">bunch</span> of text here for " [7]=> string(39) "<span class="highlight">bunch</span> of" } 

Итак, вы можете создать свой результат, отсортировав список по strlen а затем выбрав два самых длинных совпадения … (предположим php 5.3+):

 usort($results, function($str1, $str2) { return strlen($str2) - strlen($str1); }); $description = implode('...', array_slice($results, 0, 2)); 

Результат:

 here for us to foo <span class="highlight">bar</span> baz replace out...a whole <span class="highlight">bunch</span> of text here for 

Я надеюсь, что это поможет … (Я чувствую, что это немного … раздуто … Я уверен, что есть лучшие способы сделать это, но вот один из способов) …

Возможно, вы могли бы сделать что-то подобное, когда вы подключились к базе данных:

 $keyword = $_REQUEST["keyword"]; //fetch the keyword from the request $result = mysql_query("SELECT * FROM `posts` WHERE `content` LIKE '%". mysql_real_escape_string($keyword)."%'"); //ask the database for the posttexts while ($row = mysql_fetch_array($result)) {//do the following for each result: $text = $row["content"];//we're only interested in the content at the moment $text=substr ($text, strrpos($text, $keyword)-150, 300); //cut out $text=str_replace($keyword, '<strong>'.$keyword.'</strong>', $text); //highlight echo htmlentities($text); //print it echo "<hr>";//draw a line under it } 

Если вы хотите вырезать соответствующие параграфы, после выполнения вышеописанной функции str_replace вы можете использовать stripos (), чтобы найти положение этих сильных секций, и использовать смещение этого местоположения с помощью substr (), чтобы вырезать раздел пункт, такой как:

 $ searchterms;

 foreach ($ searchterms as $ search)
 {
 $ paragraph = str_replace ($ search, "<strong> $ search </ strong>", $ paragraph);
 }

 $ pos = 0;

 для ($ i = 0; $ i <4; $ i ++)  
 {  
 $ pos = stripos ($ paragraph, "<strong>", $ pos);  
 $ section [$ i] = substr ($ paragraph, $ pos - 100, 200);
 }

который даст вам массив небольших предложений (по 200 символов) для использования, как вы пожелаете. Также может быть полезно найти ближайшее место из мест для резки и вырезать оттуда, чтобы предотвратить полуслов. О, и вам также нужно проверить ошибки, но я оставлю это, но до вас.

Вы можете попытаться array_search() набор результатов поиска базы данных в массив, используя array_search() а затем использовать array_search() для каждого результата поиска. Задайте переменную $distance в приведенном ниже примере, сколько слов вы хотите появиться по обе стороны от первого совпадения $keyword .

В этом примере я включил текст lorum ipsum в качестве примера примера базы данных и установил $keyword в scelerisque. Вы, очевидно, замените их в своем коде.

 //example paragraph text $lorum = 'Nunc nec magna at nibh imperdiet dignissim quis eu velit. vel mattis odio rutrum nec. Etiam sit amet tortor nibh, molestie vestibulum tortor. Integer condimentum magna dictum purus vehicula et scelerisque mauris viverra. Nullam in lorem erat. Ut dolor libero, tristique et pellentesque sed, mattis eget dui. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. .'; //turn paragraph into array $ipsum = explode(' ',$lorum); //set keyword $keyword = 'scelerisque'; //set excerpt distance $distance = 10; //look for keyword in paragraph array, return array key of first match $match_key = array_search($keyword,$ipsum); if(!empty($match_key)){ foreach($ipsum as $key=>$value){ //if paragraph array key inside excerpt distance if($key > $match_key-$distance and $key< $match_key+$distance){ //if array key matches keyword key, bold the word if($key == $match_key){ $word = '<b>'.$value.'</b>'; } else{ $word = $value; } //create excerpt array to hold words within distance $excerpt[] = $word; } } //turn excerpt array into a string $excerpt = implode(' ',$excerpt); } //print the string echo $excerpt; 

$excerpt возвращается: "vestibulum tortor. Integer condimentum magna dictum purus vehicleula et scelerisque mauris viverra. Nullam in lorem erat. Ut dolor libero,

Вот решение для простого текста:

 $str = 'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.'; $keywords = array('co'); $wordspan = 5; $keywordsPattern = implode('|', array_map(function($val) { return preg_quote($val, '/'); }, $keywords)); $matches = preg_split("/($keywordsPattern)/ui", $str, -1, PREG_SPLIT_DELIM_CAPTURE); for ($i = 0, $n = count($matches); $i < $n; ++$i) { if ($i % 2 == 0) { $words = preg_split('/(\s+)/u', $matches[$i], -1, PREG_SPLIT_DELIM_CAPTURE); if (count($words) > ($wordspan+1)*2) { $matches[$i] = '…'; if ($i > 0) { $matches[$i] = implode('', array_slice($words, 0, ($wordspan+1)*2)) . $matches[$i]; } if ($i < $n-1) { $matches[$i] .= implode('', array_slice($words, -($wordspan+1)*2)); } } } else { $matches[$i] = '<b>'.$matches[$i].'</b>'; } } echo implode('', $matches); 

С текущим шаблоном "/($keywordsPattern)/ui" подсловы совпадают и подсвечиваются. Но вы можете изменить это, если хотите:

  • Если вы хотите совместить только целые слова, а не просто подслов, используйте границы слов \b :

     "/\b($keywordsPattern)\b/ui" 
  • Если вы хотите совместить подзаголовки, но выделяете все слово, используйте передние слова « \w впереди» и после ключевых слов:

     "/(\w*?(?:$keywordsPattern)\w*)/ui" 

Я нашел это сообщение, когда делал поиск того, как выделить результаты поиска по ключевым словам. Мои требования:

  • Должны быть целые слова
  • Должно работать более одного ключевого слова
  • Должен быть только PHP

Я извлекаю свои данные из MySQL данных MySQL , которая не содержит элементов, по форме формы, в которой хранятся данные.

Вот код, который я нашел наиболее полезным:

 $keywords = array("fox","jump","quick"); $string = "The quick brown fox jumps over the lazy dog"; $test = "The quick brown fox jumps over the lazy dog"; // used to compare values at the end. if(isset($keywords)) // For keyword search this will highlight all keywords in the results. { foreach($keywords as $word) { $pattern = "/\b".$word."\b/i"; $string = preg_replace($pattern,"<span class=\"highlight\">".$word."</span>", $string); } } // We must compare the original string to the string altered in the loop to avoid having a string printed with no matches. if($string === $test) { echo "No match"; } else { echo $string; } 

Вывод:

 The <span class="highlight">quick</span> brown <span class="highlight">fox</span> jumps over the lazy dog. 

Я надеюсь, что это помогает кому-то.

Если вы новичок, это будет не так просто, как может подумать кто-то …

Я думаю, вы должны сделать следующие шаги:

  1. построить запрос на основе того, что пользователь искал (остерегайтесь инъекций sql)
  2. получить результаты и организовать их (массив должен быть точным)
  3. построить html-код из предыдущего массива

На третьем шаге вы можете использовать некоторое регулярное выражение для замены искомых ключевых слов, выделенных полужирным шрифтом. str_replace тоже может работать …

Надеюсь, это поможет … Если бы вы могли предоставить свою структуру базы данных, я могу дать вам несколько более точных подсказок …