我正试图在我正在开发的一个网站中完成搜索功能。由于我的搜索结果只显示匹配项目内容的摘录,所以我想做的是突出显示搜索结果中的搜索词,并只显示实际包含这些搜索词的文本部分。
我想我应该做的是从数据库中获取整个内容,并使用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
的MATCH()...AGAINST()
在多个列上搜索MyISAM FULLTEXT
索引。但是,上面的正则表达式仅应用于一列中(让我们将此列称为使用上面正则表达式的列content
)。
因此,我的问题是,每当我在其他列上得到匹配,但在content
列上没有得到匹配时,上面的regex就会从content
列中剥离所有文本。这是因为(?:.*?)
子模式在一开始就继续匹配,而从未找到下一个子模式。
我想知道是否有其他方法可以在没有这种副作用的情况下实现regex的原始目的。我目前正在考虑简单地使用preg_match_all
来匹配搜索词及其前后的10个单词。我只需迭代所有匹配项并手动构建预览文本。是的,这是一个不错的解决方案,但鉴于我对regex缺乏经验,我想我还不如试着找到一个解决方案。
更新
我只是注意到,当我放入2个或多个搜索词时,我只会得到空白的contents
。除此之外,它运行得很好。我现在不知道为什么会发生这种事。
更新2
响应preg_last_error()
,我得到这个错误PREG_BACKTRACK_LIMIT_ERROR
。我使用单词new
和post
作为搜索词。
正则表达式和术语的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",因为它在匹配子模式的"之后永远不会回溯。