Выделите ключевые слова в абзаце
Мне нужно выделить ключевое слово в абзаце, как это делает Google в результатах поиска. Давайте предположим, что у меня есть база данных MySQL с сообщениями в блоге. Когда пользователь ищет определенное ключевое слово, я хочу вернуть посты, содержащие эти ключевые слова, но показать только части постов (абзац, содержащий искомое ключевое слово) и выделить эти ключевые слова.
Мой план такой:
- найти идентификатор сообщения, в котором содержится искомое ключевое слово;
- снова прочитайте содержание этого поста и поместите каждое слово в фиксированный буферный массив (50 слов), пока я не найду ключевое слово.
Можете ли вы помочь мне с логикой, или, по крайней мере, сказать мне, если моя логика в порядке? Я нахожусь в стадии изучения PHP.
7 ответов
Если он содержит 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>
Остерегайтесь не-дом решений (как 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 как $search) { $para = str_replace ($ search, " $ search strong>", $ абзац); } $ pos = 0; для ($i = 0; $i < 4; $i++) { $pos = stripos($ абзац, "", $ pos); $ section [$ i] = substr ($ абзац, $pos - 100, 200); }
который даст вам массив маленьких предложений (по 200 символов в каждом), которые вы сможете использовать по своему усмотрению. Также может быть полезным поиск ближайшего пространства в местах вырезания и вырезание оттуда, чтобы избежать полуслов. О, и вы также должны проверить на ошибки, но я оставлю это, но до вас.
Вы можете попытаться разбить ваш набор результатов поиска в базе данных, используя explode
а затем использовать array_search()
на каждый результат поиска. Установить $distance
Переменная в приведенном ниже примере, сколько слов вы хотели бы видеть по обе стороны от первого совпадения $keyword
,
В этом примере я включил текст lorum ipsum в качестве примера абзаца базы данных и установил $keyword
на "скелериск". Вы, очевидно, замените их в своем коде.
//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
Возвращает:
"Вестибул пытки. Integer condimentum magna dictum purus vehicleula и scelerisque mauris viverra. Нуллам в лорейте. Ut dolor libero"
Я нашел этот пост, когда выполнял поиск, чтобы выделить результаты поиска по ключевым словам. Мои требования были:
- Должны быть целые слова
- Должно работать для более чем одного ключевого слова
- Должен быть только PHP
Я получаю свои данные из 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.
Я надеюсь, что это помогает кому-то.
Вот решение для простого текста:
$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"
Если вы новичок, это будет не так просто, как кто-то может подумать...
Я думаю, что вы должны сделать следующие шаги:
- построить запрос, основанный на том, что пользователь искал (остерегайтесь инъекций SQL)
- получить результаты и организовать их (массив должен быть в порядке)
- построить HTML-код из предыдущего массива
На третьем шаге вы можете использовать некоторые регулярные выражения, чтобы заменить ключевые слова для поиска пользователя жирным эквивалентом. str_replace тоже может работать...
Я надеюсь, что это поможет... Если бы вы могли предоставить структуру вашей базы данных, может быть, я могу дать вам более точные советы...