PHP preg_replace: 以case/diacritic-insensitive方式高亮匹配一个键的整个单词

PHP preg_replace: highlight whole words matching a key in case/diacritic-insensitive way

我需要在 UTF-8 $文本中突出显示与 $key 匹配的单个单词或短语(整个单词,而不是子字符串)。这种匹配必须既不区分大小写又不区分变音符号。突出显示的文本必须保持原样(包括 uppercase/lowercase 个字符和变音符号,如果存在)。

下面的表达式实现了一半的目标:

$text = preg_replace( "/\b($key)\b/i", '<div class="highlight"></div>', $text );

它不区分大小写并匹配整个单词,但如果匹配 $key 的 $text 部分包含 $key 中不存在的变音符号,则不会突出显示这些部分。 例如。我想在传递 $key = "bjorn kallstrom".

的 $text 中突出显示 "Björn Källström"

欢迎提出任何绝妙的想法(使用 preg_replace 或其他 PHP 函数)。

仅通过函数调用是不可能的,您必须实现它。

  1. 从 HTML ($document->documentElement->textContent)
  2. 中提取文本
  3. 将文本拆分为单词并对其进行规范化 - 保留原件 ($words[$normalized][] = $original) - 基本上这为您提供了每个规范化单词的变体列表。
  4. 拆分和规范化搜索查询
  5. 从搜索查询中编译 RegEx 模式以匹配 ((word1_v1|word1_v2)\s*(word2_v1|word2_v2))u 并验证 (^(word1_v1|word1_v2)\s*(word2_v1|word2_v2)$)u
  6. 遍历您 HTML 文档中的文本节点 $xpath->evaluate('//text()')
  7. 使用preg_split()通过搜索字符串分隔文本,捕获定界符(搜索匹配)
  8. 遍历该列表,如果不是搜索字符串匹配,则将它们添加为文本节点,否则添加 HTML 结构以突出显示
  9. 去除原文节点

一个想法是将键转换为模式,用一个字符替换所有有问题的字符 class:

$corr = ['a' => '[aàáâãäå]', 'o' => '[oòóôõö]',/* etc. */];

$key = 'bjorn kallstrom';

$pattern = '/\b' . strtr($key, $corr) . '\b/iu';

$text = preg_replace($pattern, '<em class="highlight">[=10=]</em>', $text);

请注意,由于您正在处理 unicode 字符,因此需要使用 u 修饰符来避免意外行为,尤其是在单词边界方面。

如果您的键已经包含重音字符,请先将它们转换为 ascii:

$key = 'björn kallstrom';
$key = iconv('UTF-8', 'ASCII//TRANSLIT', $key);

(如果您获得 ? 代替字母,这意味着您的语言环境设置为 C 或 POSIX。在这种情况下,将它们更改为 en_US.UTF-8,或您系统中可用的另一个。请参阅 setlocale)

另请参阅非常有用的 intl classes:Normalizer and Transliterator.

注意:如果要突出显示多个键,请一次完成所有操作。将数组按长度排序(最长的优先使用mb_strlen),使用array_map将键音译为ascii,并使用|对数组进行内爆。目标是获得模式: '/\b(?:' . implode('|', $keys) . ')\b/iu'bj[oòóôõö]rn k[aàáâãäå]llstr[oòóôõö]mbj[oòóôõö]rn 之前单独(例如)。