3

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

我想我要做的是从数据库中获取全部内容并使用在搜索词周围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...

MySQL正在通过'sMATCH()...AGAINST()搜索搜索词以查找MyISAM FULLTEXT多列上的索引。但是,上述正则表达式仅应用于一列(我们称此列,即使用上述正则表达式的列content)。

所以我的问题是,每当我在其他列而不是列上得到匹配时content,上面的正则表达式就会从content列中删除所有文本。这是因为一(?:.*?)开始的子模式会继续匹配,而不会找到下一个子模式。

我想知道是否有任何其他方法可以在没有这种副作用的情况下实现正则表达式的原始目的。我目前正在考虑简单地使用preg_match_all来匹配搜索词及其前后的 10 个单词。我将遍历所有匹配项并手动构建预览文本。是的,这是一个不错的解决方案,但考虑到我对正则表达式的经验不足,我想我不妨尝试找到解决方案。

更新

我只是注意到,contents当我输入 2 个或更多搜索词时,我只会变得空白。除此之外,它完美无缺。我现在不知道为什么会这样。

更新 2

回声preg_last_error(),我得到这个错误PREG_BACKTRACK_LIMIT_ERROR。我使用这些词newpost作为搜索词。

正则表达式的Avar_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})

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

4

1 回答 1

1

如果您在达到回溯限制时遇到问题,您通常希望查看仅一次的子模式

但是,在这种情况下,您的主要问题似乎是(?:.*?)(?:\w+\W+){0,10}. 以字符串 'hello world!' 为例,暂时忽略{0,10}. 这将匹配两种模式,如下所示:

  • ''和'你好'
  • “h”和“你好”
  • “他”和“你”
  • 'hel' 和 'lo'
  • “地狱”和“o”
  • “你好”和“世界!”
  • “你好 w”和“orld!”
  • “你好 wo”和“rld!”
  • “你好”和“ld!”
  • “你好世界”和“d!”

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

  • ''和'你好'
  • “你好”和“世界!”

编辑:这是一个为什么一次性子模式在这里不起作用的例子:

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

在本例中,我们期望结果为“xc”,但是子模式贪婪地匹配“ba”,然后从不回溯,因此错过了匹配。我们可以使模式不贪婪,但随后我们会得到结果“bxc”,因为它在匹配子模式的“”后永远不会回溯。

于 2012-07-18T09:10:27.810 回答