突出显示段落中的关键字

时间:2010-11-02 19:27:56

标签: php search string

我需要在一个段落中突出显示一个关键字,就像Google在搜索结果中所做的那样。我们假设我有一个带有博客文章的MySQL数据库。当用户搜索某个关键字时,我希望返回包含这些关键字的帖子,但只显示部分帖子(包含搜索关键字的段落)并突出显示这些关键字。

我的计划是:

  • 找到在其内容中包含搜索关键字的帖子ID;
  • 再次阅读该帖子的内容,并将每个单词放入一个固定的缓冲区数组(50个单词),直到找到该关键字。

你能帮助我一些逻辑,或者至少告诉我我的逻辑是否合适?我正处于PHP学习阶段。

7 个答案:

答案 0 :(得分:9)

如果它包含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解决方案(例如regexstr_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 

我希望有所帮助......(我觉得这有点......臃肿......我确信有更好的方法可以做到这一点,但这是一种方式)......

答案 1 :(得分:2)

当你连接到数据库时,也许你可以做这样的事情:

$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
}

答案 2 :(得分:2)

如果你想删除相关的段落,在完成上面提到的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个字符),以便您按照自己的意愿使用。从切割位置搜索最近的空间并从那里切割以防止半字也可能是有益的。哦,你还需要检查错误,但我会离开你,但由你决定。

答案 3 :(得分:1)

您可以尝试使用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。整数调味品,大麻和其他 mauris viverra。诺尔拉姆在lorem erat.Ut dolor libero,”

答案 4 :(得分:1)

这是纯文本的解决方案:

$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"
    

答案 5 :(得分:1)

我在搜索如何突出显示关键字搜索结果时发现了这篇文章。我的要求是:

  • 必须是全文
  • 必须适用于多个关键字
  • 必须是仅限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.

我希望这有助于某人。

答案 6 :(得分:0)

如果你是初学者,这可能不像人们想象的那么容易......

我认为您应该执行以下步骤:

  1. 根据用户搜索的内容(谨防sql注入)构建查询
  2. 获取结果并组织它们(数组应该没问题)
  3. 从前一个数组构建html代码
  4. 在第三步中,您可以使用一些正则表达式将用户搜索的关键字替换为粗体等效项。 str_replace也可以工作......

    我希望这会有所帮助...... 如果你能提供你的数据库结构,我可以给你一些更精确的提示......