Выделите ключевые слова в абзаце - PullRequest
2 голосов
/ 02 ноября 2010

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

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

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

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

Ответы [ 7 ]

9 голосов
/ 02 ноября 2010

Если он содержит 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>

Остерегайтесь решений не-dom (например, 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 

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

2 голосов
/ 02 ноября 2010

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

$searchterms;

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

$pos = 0;

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

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

2 голосов
/ 02 ноября 2010

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

$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
}
1 голос
/ 30 октября 2014

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

  • Должно быть целыми словами
  • Должно работать для более чем одного ключевого слова
  • Должен быть только 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.

Надеюсь, это кому-нибудь поможет.

1 голос
/ 03 ноября 2010

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

$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"
    
1 голос
/ 02 ноября 2010

Вы можете попробовать разбить набор результатов поиска в базе данных на массив, используя explode, а затем использовать 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. Нуллам в лорейте.

0 голосов
/ 02 ноября 2010

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

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

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...