PHP str_ireplace如何实现重音不敏感匹配替换保留原文
问题说明
需要实现PHP端重音不敏感的搜索词高亮效果,匹配规则和MySQL设置utf8字符集后的重音不敏感查询对齐:
- 文本中出现带重音的
Français时,替换为<mark>Français</mark> - 文本中出现无重音的
Francais时,替换为<mark>Francais</mark> - 全程保留文本原始拼写,不得修改原有重音字符。
之前尝试的两种方案都存在问题:
- 原生
str_ireplace函数为重音敏感匹配,无法识别带/不带重音的同根词,比如Francais匹配不到Français - 用重音映射表直接转换原文本去重音后匹配,会破坏原文本的重音格式,无法满足保留原始拼写的要求。
实现方案
核心逻辑:不修改原始文本,单独生成一份去除所有重音的小写「对照文本」,在对照文本中定位所有匹配位置,再根据位置从原文本中截取对应长度的原始字符串套高亮标签,从后往前替换避免位置偏移。
完整代码
首先写去重音的工具函数,优先用PHP国际化扩展的转写器覆盖全量重音字符,无扩展时用手写映射表兜底:
function removeAccents(string $str): string { if (extension_loaded('intl')) { $transliterator = Transliterator::create('NFD; [:Nonspacing Mark:] Remove; NFC;'); return $transliterator->transliterate($str); } $accentMap = [ 'Š'=>'S', 'š'=>'s', 'Ž'=>'Z', 'ž'=>'z', 'À'=>'A', 'Á'=>'A', 'Â'=>'A', 'Ã'=>'A', 'Ä'=>'A', 'Å'=>'A', 'Æ'=>'A', 'Ç'=>'C', 'È'=>'E', 'É'=>'E', 'Ê'=>'E', 'Ë'=>'E', 'Ì'=>'I', 'Í'=>'I', 'Î'=>'I', 'Ï'=>'I', 'Ñ'=>'N', 'Ò'=>'O', 'Ó'=>'O', 'Ô'=>'O', 'Õ'=>'O', 'Ö'=>'O', 'Ø'=>'O', 'Ù'=>'U', 'Ú'=>'U', 'Û'=>'U', 'Ü'=>'U', 'Ý'=>'Y', 'Þ'=>'B', 'ß'=>'Ss', 'à'=>'a', 'á'=>'a', 'â'=>'a', 'ã'=>'a', 'ä'=>'a', 'å'=>'a', 'æ'=>'a', 'ç'=>'c', 'è'=>'e', 'é'=>'e', 'ê'=>'e', 'ë'=>'e', 'ì'=>'i', 'í'=>'i', 'î'=>'i', 'ï'=>'i', 'ð'=>'o', 'ñ'=>'n', 'ò'=>'o', 'ó'=>'o', 'ô'=>'o', 'õ'=>'o', 'ö'=>'o', 'ø'=>'o', 'ù'=>'u', 'ú'=>'u', 'û'=>'u', 'ý'=>'y', 'þ'=>'b', 'ÿ'=>'y' ]; return strtr($str, $accentMap); }
再写高亮逻辑,所有字符串操作使用mb_*多字节函数避免utf8乱码:
function accentInsensitiveHighlight(string $fullText, string $wordToSearch): string { $searchLen = mb_strlen($wordToSearch); if ($searchLen === 0) return $fullText; // 生成无重音的小写对照版本,用于匹配定位 $normalizedText = mb_strtolower(removeAccents($fullText)); $normalizedSearch = mb_strtolower(removeAccents($wordToSearch)); // 收集所有匹配位置 $matches = []; $offset = 0; while (($pos = mb_strpos($normalizedText, $normalizedSearch, $offset)) !== false) { $matches[] = $pos; $offset = $pos + $searchLen; } // 从后往前替换,避免插入标签导致的位置偏移 foreach (array_reverse($matches) as $pos) { // 直接截取原文本对应位置的内容,完全保留原始拼写 $originalSegment = mb_substr($fullText, $pos, $searchLen); $fullText = mb_substr($fullText, 0, $pos) . '<mark>' . $originalSegment . '</mark>' . mb_substr($fullText, $pos + $searchLen); } return $fullText; }
使用方式
直接替换原有str_ireplace的逻辑即可:
// 原有逻辑 // $markReplace = "<mark>" . $wordToSearch . "</mark>"; // $fullText = str_ireplace($wordToSearch, $markReplace, $fullText); // 替换为 $fullText = accentInsensitiveHighlight($fullText, $wordToSearch); echo $fullText;
效果验证
传入原文本J'aime le Français, je préfère ça au francais de base.,搜索词为francais时,输出结果为:J'aime le <mark>Français</mark>, je préfère ça au <mark>francais</mark> de base.
两个不同拼写的匹配项都被正确高亮,原有重音字符完全保留,没有被修改。
注意事项
- 运行环境需要开启
mbstring扩展,该扩展为PHP默认内置扩展,绝大多数生产环境都已默认开启 - 建议安装
intl扩展,转写器支持的重音字符覆盖范围远大于手写映射表,能匹配更多特殊重音组合
内容的提问来源于stack exchange,提问作者user14337309
相关产品推荐
相关产品推荐

