【问题标题】:Discard all characters but the first 10 words before and after a search term丢弃搜索词前后的前 10 个单词以外的所有字符
【发布时间】:2012-07-17 04:13:36
【问题描述】:

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

我想我要做的是从数据库中获取全部内容并使用 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 indeces 的搜索词。但是,上述正则表达式仅应用于一列(我们称此列,即使用上述正则表达式的列,content)。

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

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

更新

我刚刚注意到,当我输入 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})

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

【问题讨论】:

  • 当一个搜索词出现在另一个搜索词之后的 10 个单词内时怎么办?通过这种方法,您将不会包含其后的 10 个单词。这样可以吗?当搜索词真正频繁出现时怎么办?您是否同意这可能会返回整个文本?似乎您可能需要一种稍微昂贵的方法才能做好这件事......
  • preg_replace 是返回空字符串还是 null?如果它返回 null ,那么您的模式中有一个错误,在这种情况下,您可能想要回显它。第一个想法是您需要 preg_quote 这些值,除非您已经在构建数组的过程中将它们分解为字母数字字符。
  • 嗨!我更新了问题以说明错误。我确实同意搜索词彼此太接近是一个问题。感谢那些cmets!我不介意它返回整个文本,因为我在回显之前截断了内容。
  • 可能值得 print_r-ing 您的术语数组或呼应模式 - 可能是您以某种方式获得 '' 或 ' '
  • 我使用Regex Coach 引导我完成匹配模式,它似乎在找不到(new|post) 的匹配项后无限回溯。目标文本只是一个随机的 3 段 lorem ipsum。变量的var_dump 在编辑的问题中

标签: php regex full-text-search preg-replace


【解决方案1】:

如果您在达到回溯限制时遇到问题,通常需要查看once-only subpatterns

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

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

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

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

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

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

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

【讨论】:

  • 我明白了,所以 (?:.*?)\b((?:\w+\W+){0,10})('.implode('|', $terms).')((?:\W*\w+\W+){0,10}) 现在就足够了吗?如果原始问题可以通过正则表达式解决或者我应该手动解析文本有什么想法吗?
  • 应该可以工作,或者至少有帮助,但是是的,我肯定会通过单步方法手动解析文本(嗯,使用正则表达式,但在多个阶段)。这是一个有趣的问题......我个人可能会通过找到每个搜索词的位置,计算出它们周围的范围(以字符而不是单词来衡量),折叠任何重叠,然后只输出前五个左右范围。
猜你喜欢
  • 2018-07-18
  • 2011-03-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-01-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多