丢弃搜索词前后的前10个单词以外的所有字符


Discard all characters but the first 10 words before and after a search term

我正试图在我正在开发的一个网站中完成搜索功能。由于我的搜索结果只显示匹配项目内容的摘录,所以我想做的是突出显示搜索结果中的搜索词,并只显示实际包含这些搜索词的文本部分。

我想我应该做的是从数据库中获取整个内容,并使用preg_replace在搜索词周围插入<span>元素,同时只提取词前后的前10个词。这就是它的正则表达式部分:

(?:.*?)((?:'w+'W+){0,10})('.implode('|', $terms).')((?:'W*'w+'W+){0,10})

基本上,我试图通过使用非捕获子模式来"丢弃"除搜索词之前的前10个单词外的所有文本,然后获得词之前的10个单词,然后是词本身,然后是接下来的10个词。

这是preg_replace:中的替换文本

''1<span class="search-term search-term-content">''2</span>''3...

搜索项通过MySQLMATCH()...AGAINST()在多个列上搜索MyISAM FULLTEXT索引。但是,上面的正则表达式仅应用于一列中(让我们将此列称为使用上面正则表达式的列content)。

因此,我的问题是,每当我在其他列上得到匹配,但在content列上没有得到匹配时,上面的regex就会从content列中剥离所有文本。这是因为(?:.*?)子模式在一开始就继续匹配,而从未找到下一个子模式。

我想知道是否有其他方法可以在没有这种副作用的情况下实现regex的原始目的。我目前正在考虑简单地使用preg_match_all来匹配搜索词及其前后的10个单词。我只需迭代所有匹配项并手动构建预览文本。是的,这是一个不错的解决方案,但鉴于我对regex缺乏经验,我想我还不如试着找到一个解决方案。

更新

我只是注意到,当我放入2个或多个搜索词时,我只会得到空白的contents。除此之外,它运行得很好。我现在不知道为什么会发生这种事。

更新2

响应preg_last_error(),我得到这个错误PREG_BACKTRACK_LIMIT_ERROR。我使用单词newpost作为搜索词。

正则表达式和术语的var_dump显示如下:

@(?:.*?)((?:'w+'W+){0,10})(new|post)((?:'W*'w+'W+){0,10})@i
array
  0 => string 'new' (length=3)
  1 => string 'post' (length=4)

更新3

我用Regex Coach引导我完成匹配模式,在找不到与(new|post)匹配的模式后,它似乎倒退了太多。目标文本只是一个随机的3段lorem ipsum。我想我需要为这个任务找到一个更好的正则表达式。

更新4

使用Once-Only子模式解决了这个问题。虽然我不知道它的细节,但我只是重读了PHP手册,并阅读了其中Once-Only子模式有助于避免太多回溯的一部分。这是新的正则表达式:

(?:.*?)((?>'w+'W+){0,10})('.implode('|', $terms).')((?:'W*'w+'W+){0,10})

但我仍然愿意为更好的正则表达式提供建议。谢谢

如果您在达到回溯限制方面遇到问题,您通常希望只查看一次子模式。

然而,在这种情况下,您的主要问题似乎是(?:.*?)之后是(?:'w+'W+){0,10}。以字符串"你好,世界!"为例,暂时忽略CCD_ 24。这将与以下两种模式相匹配:

  • "answers"你好"
  • "h"answers"ello"
  • 他和洛
  • "hel"answers"lo"
  • "地狱"answers"o"
  • "你好"answers"世界!"
  • 你好w和world
  • "你好,我"answers"rld!"
  • 你好,wor和ld
  • 你好,沃尔和d

阻止这种冗余回溯的最简单方法是在(?:.*?)子模式之后添加一个字边界检查('b)。这将减少这些与的潜在匹配

  • "answers"你好"
  • "你好"answers"世界!"

编辑:下面是一个示例,说明为什么一次性子模式在这里不起作用:

preg_replace('/(?>[a-z]{0,2})a/','x','bac')

在这个例子中,我们期望结果是"xc",但是子模式贪婪地匹配"ba",然后从不回溯,从而错过了匹配。我们可以使模式变得不规则,但随后我们会得到结果"bxc",因为它在匹配子模式的"之后永远不会回溯。