如何突出显示网页上的搜索匹配文字

时间:2015-07-27 15:03:44

标签: php regex search pattern-matching preg-replace

我试图编写一个PHP函数,它会在网页上显示一些文本,然后根据一些输入的搜索术语,突出显示文本的相应部分。不幸的是,我有几个问题 为了更好地解释我遇到的两个问题,让我们假设正在搜索以下无关字符串并将显示在网页上:

My daughter was born on January 11, 2011.

我的第一个问题是,如果输入了多个搜索词,那么我用来标记第一个词的任何匹配的开头和结尾的任何占位符文本都可以与第二个词相匹配。
例如,我目前正在使用以下分隔字符串来标记匹配的开头和结尾(我在函数末尾使用preg_replace函数将分隔符转换为HTML {{1标签):

span

问题是,如果我执行'#####highlightStart#####' '#####highlightEnd#####' 之类的搜索,那么2011 light会先匹配,然后给我:

2011

在搜索My daughter was born on January 11, #####highlightStart#####2011#####highlightEnd#####. 时,它会匹配lightlight中我不想要的单词#####highlightStart#####

有一种想法是创建一些可能永远不会被搜索的非常模糊的分隔字符串(可能是外语),但我不能保证任何特定的字符串永远不会被搜索到它只是看起来像一个真正的kludgy解决方案。基本上,我认为有更好的方法来做到这一点 关于这第一点的任何建议都将不胜感激。

我的第二个问题与如何处理重叠匹配有关 例如,使用相同的字符串#####highlightEnd#####,如果输入的搜索结果为My daughter was born on January 11, 2011.,那么Jan anuar将首先匹配,并告诉我:

Jan

由于分隔文本现在是字符串的一部分,因此第二个搜索字词My daughter was born on #####highlightStart#####Jan#####highlightEnd#####uary 11, 2011. 永远不会匹配。

关于这个问题,我很困惑,真的不知道如何解决它 我觉得我需要以某种方式分别对原始字符串进行所有搜索操作,然后以某种方式将它们组合在一起,但同样,我又失去了如何做到这一点。
或许这是一种更好的解决方案,但我不知道会是什么。

对于如何解决其中一个或两个问题的任何建议或指示将不胜感激 谢谢。

2 个答案:

答案 0 :(得分:1)

不要修改原始字符串并将匹配项存储在单个数组中,要么以奇数开始,要么以偶数元素结尾,要么将它们存储在记录中(两个项目的数组)。

在搜索了几个关键字后,最终得到了几个匹配的数组。因此,现在的任务是如何合并两个段列表,生成覆盖区域的段。在对列表进行排序时,这是一项简单的任务,可以在O(n)时间内解决。

然后只需将高亮标记插入到结果数组中记录的位置。

答案 1 :(得分:1)

在这种情况下,我认为使用str_replace更简单(虽然它不会很完美)。

假设您想要突出显示一系列术语,我会为了论证而将其称为$aSearchTerms ...并将突出显示的术语包含在HTML5中{{ 1}}标记是可以接受的(为了便于阅读,您已经说过它会在网页上发布,并且从您的搜索字词中<mark>很容易):< / p>

strip_tags()

它并不完美,因为使用该数组中的数据,第一遍会将$aSearchTerms = ['Jan', 'anu', 'Feb', '11']; $sinContent = "My daughter was born on January 11, 2011."; foreach($aSearchTerms as $sinTerm) { $sinContent = str_replace($sinTerm, "<mark>{$sinTerm}</mark>", $sinContent); } echo $sinContent; // outputs: My d<mark>au</mark>ghter was born on <mark>Jan</mark>uary <mark>11</mark>, 20<mark>11</mark>. 更改为January,这意味着<mark>Jan</mark>uary将不再匹配 J anu ary - 这样的事情将涵盖大多数的使用需求。

修改

Oki - 我并非100%确定这是理智的,但我采取了一种完全不同的方法来查看@AlexAtNet发布的链接:

https://stackoverflow.com/a/3631016/886824

我所做的是查看字符串中以数字方式找到搜索词的点(索引),并构建一个开始和结束索引的数组,其中anu和{{1} }标签将被输入。

然后使用上面的答案将这些开始和结束索引合并在一起 - 这涵盖了重叠匹配问题。

然后我循环播放该数组并将原始字符串切割成子字符串并将其粘合在一起,在相关点(基于索引)插入<mark></mark>标记。这应该涵盖您的第二个问题,因此您没有替换字符串替换字符串替换。

完整的代码如下:

<mark>

不可避免的问题! 在已经使用HTML的内容上使用此功能可能会失败。

给出字符串。

</mark>

<?php $sContent = "Captain's log, January 11, 2711 - Uranus"; $ainSearchTerms = array('Jan', 'asduih', 'anu', '11'); //lower-case it for substr_count $sContentForSearching = strtolower($sContent); //array of first and last positions of the terms within the string $aTermPositions = array(); //loop through your search terms and build a multi-dimensional array //of start and end indexes for each term foreach($ainSearchTerms as $sinTerm) { //lower-case the search term $sinTermLower = strtolower($sinTerm); $iTermPosition = 0; $iTermLength = strlen($sinTermLower); $iTermOccursCount = substr_count($sContentForSearching, $sinTermLower); for($i=0; $i<$iTermOccursCount; $i++) { //find the start and end positions for this term $iStartIndex = strpos($sContentForSearching, $sinTermLower, $iTermPosition); $iEndIndex = $iStartIndex + $iTermLength; $aTermPositions[] = array($iStartIndex, $iEndIndex); //update the term position $iTermPosition = $iEndIndex + $i; } } //taken directly from this answer https://stackoverflow.com/a/3631016/886824 //just replaced $data with $aTermPositions //this sorts out the overlaps so that 'Jan' and 'anu' will merge into 'Janu' //in January - whilst still matching 'anu' in Uranus // //This conveniently sorts all your start and end indexes in ascending order usort($aTermPositions, function($a, $b) { return $a[0] - $b[0]; }); $n = 0; $len = count($aTermPositions); for ($i = 1; $i < $len; ++$i) { if ($aTermPositions[$i][0] > $aTermPositions[$n][1] + 1) $n = $i; else { if ($aTermPositions[$n][1] < $aTermPositions[$i][1]) $aTermPositions[$n][1] = $aTermPositions[$i][1]; unset($aTermPositions[$i]); } } $aTermPositions = array_values($aTermPositions); //finally chop your original string into the bits //where you want to insert <mark> and </mark> if($aTermPositions) { $iLastContentChunkIndex = 0; $soutContent = ""; foreach($aTermPositions as $aChunkIndex) { $soutContent .= substr($sContent, $iLastContentChunkIndex, $aChunkIndex[0] - $iLastContentChunkIndex) . "<mark>" . substr($sContent, $aChunkIndex[0], $aChunkIndex[1] - $aChunkIndex[0]) . "</mark>"; $iLastContentChunkIndex = $aChunkIndex[1]; } //... and the bit on the end $soutContent .= substr($sContent, $iLastContentChunkIndex); } //this *should* output the following: //Captain's log, <mark>Janu</mark>ary <mark>11</mark>, 27<mark>11</mark> - Ur<mark>anu</mark>s echo $soutContent; 的搜索/标记会在{1}周围插入In <a href="#">January</a> this year...&#39; Jan&#39;这很好。但是,Jan之类的搜索标记会因为标记的方式而失败:\

我不敢想出一个我害怕的好方法。