【问题标题】:How to highlight search-matching text on a web page如何突出显示网页上的搜索匹配文本
【发布时间】:2015-10-17 20:19:09
【问题描述】:

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

My daughter was born on January 11, 2011.

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

'#####highlightStart#####'
'#####highlightEnd#####'

问题是,如果我像2011 light 那样进行搜索,那么2011 将首先匹配,给我:

My daughter was born on January 11, #####highlightStart#####2011#####highlightEnd#####.

当搜索light 时,它将匹配#####highlightStart##########highlightEnd##### 中的单词light,这是我不想要的。

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

我的第二个问题与如何处理重叠匹配有关。
比如同样的字符串My daughter was born on January 11, 2011.,如果输入的搜索是Jan anuar,那么会先匹配Jan,给我:

My daughter was born on #####highlightStart#####Jan#####highlightEnd#####uary 11, 2011.

因为分隔文本现在是字符串的一部分,所以第二个搜索词 anuar 将永远不会匹配。

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

我们将不胜感激任何有关如何解决其中一个或两个问题的建议或指导。
谢谢。

【问题讨论】:

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


    【解决方案1】:

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

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

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

    【讨论】:

    • Alex,我很欣赏这个答案,而且我认为从高层次来看,我在概念上理解你的意思,但是一些示例代码会非常有帮助。谢谢。
    • 它不完全是 PHP,但非常相似:geeksforgeeks.org/merging-intervals
    • 两个有用的 cmets,但它们都只处理数字。如何计算两个字符串之间的重叠?
    • 这个想法是这些间隔是字符串中的位置。因此,例如,您通过使用 PREG_OFFSET_CAPTURE 调用 preg_match 在字符串“aabbcc”中搜索“b”关键字。这为您提供了每个匹配项的索引(偏移量)匹配数组。通过将匹配长度添加到其偏移量,您将获得像stackoverflow.com/questions/3630500/… 这样的间隔数组。对搜索查询中的所有关键字执行此操作,然后合并。
    【解决方案2】:

    在这种情况下,我认为使用str_replace 更简单(尽管它并不完美)。

    假设您有一系列要突出显示的术语,我将其称为 $aSearchTerms 以进行论证...并且将突出显示的术语包装在 HTML5 <mark> 标记中是可以接受的(对于为了清晰起见,您已经声明它在网页上,很容易从您的搜索词中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 更改为&lt;mark&gt;Jan&lt;/mark&gt;uary,这意味着anu 将不再匹配January - 然而,这样的东西将满足大多数的使用需求。


    编辑

    Oki - 我不能 100% 确定这是正常的,但我采取了完全不同的方法查看@AlexAtNet 发布的链接:

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

    我所做的是查看字符串中以数字方式找到搜索词的点(索引),并构建了一个开始和结束索引的数组,&lt;mark&gt;&lt;/mark&gt; 标记将是输入。

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

    然后我循环该数组并将原始字符串切割成子字符串并将其粘合在一起,在相关点插入&lt;mark&gt;&lt;/mark&gt; 标签(基于索引)。这应该涵盖您的第二个问题,因此您不会用字符串替换替换字符串。

    完整的代码如下:

    <?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;
    

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

    给定字符串。

    In &lt;a href="#"&gt;January&lt;/a&gt; this year...

    Jan 的搜索/标记将在“Jan”周围插入&lt;mark&gt;/&lt;/mark&gt;,这很好。但是,In Jan 之类的搜索标记将失败,因为有标记:\

    恐怕想不出好办法。

    【讨论】:

    • CD001,感谢您的回答,但这几乎是我已经拥有的,并且通过该解决方案,我遇到了上述两个问题。此外,您没有注意到的一件事是,如果您的数组中的搜索词之一是 'mark'(这不难相信),那么您基本上会遇到我在问题 #1 中描述的相同问题。跨度>
    • 老实说,我可能会忽略重叠匹配...但它可能可以通过递归函数来处理。您对mark 的看法肯定是一个搜索词……如果它还没有像&gt; ... &lt; 那样包装,那么preg_replace() 是否可以查看该词,但这已经非常接近使用RegExps 解析HTML!如果到那时没有其他人回答,我回家后会好好考虑一下……目前正在等待 SVN 同步完成后再下班。
    • 没什么大不了的。这是一个很难解决的问题,我在 SO 上找不到任何其他答案,这就是我问它的原因。 AlexAtNet 似乎至少在第二个(重叠匹配)问题上给了我很好的领先优势。
    • 我最终自己想出了一个类似的解决方案,但你的看起来也不错,非常感谢。
    猜你喜欢
    • 1970-01-01
    • 2018-09-22
    • 1970-01-01
    • 2018-02-11
    • 1970-01-01
    • 2015-10-27
    • 2013-10-25
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多