重音不敏感的子串匹配

Álv*_*lez 2 php collation utf-8

我有一个搜索功能,从InnoDB表(utf8_spanish_ci排序规则)获取数据并将其显示在HTML文档(UTF-8charset)中.用户键入子字符串并获取突出显示第一个子字符串出现的匹配列表,例如:

Matches for "AL":

Álava
<strong>Al</strong>bacete
<strong>Al</strong>mería
Ciudad Re<strong>al</strong>
Málaga
Run Code Online (Sandbox Code Playgroud)

从示例中可以看出,搜索忽略了大小写和重音差异(MySQL会自动处理它).但是,我用于高亮匹配的代码无法执行后者:

<?php

private static function highlightTerm($full_string, $match){
    $start = mb_stripos($full_string, $match);
    $length = mb_strlen($match);

    return
        htmlspecialchars( mb_substr($full_string, 0, $start)) .
        '<strong>' . htmlspecialchars( mb_substr($full_string, $start, $length) ) . '</strong>' .
        htmlspecialchars( mb_substr($full_string, $start+$length) );
}

?>
Run Code Online (Sandbox Code Playgroud)

是否有一种明智的方法来解决这个并不意味着对所有可能的变化进行硬编码?

更新:系统规格是PHP/5.2.14和MySQL/5.1.48

Gum*_*mbo 5

您可以使用规范化器将字符串规范化规范化形式KD(NFKD),其中字符被分解,因此Á(U + 00C1)被分解为字母A(U + 0041)和组合标记?(U + 的组合) 0301):

$str = Normalizer::normalize($str, Normalizer::FORM_KD);
Run Code Online (Sandbox Code Playgroud)

然后修改搜索模式以匹配这些可选标记:

$pattern = '/('.preg_replace('/\p{L}/u', '$0\p{Mn}?', preg_quote($term, '/')).')/ui';
Run Code Online (Sandbox Code Playgroud)

然后用preg_replace以下方法完成替换:

preg_replace($pattern, '<strong>$0</strong>', htmlspecialchars($str))
Run Code Online (Sandbox Code Playgroud)

所以完整的方法是:

private static function highlightTerm($str, $term) {
    $str = Normalizer::normalize($str, Normalizer::FORM_KD);
    $pattern = '/('.preg_replace('/\p{L}/u', '$0\p{Mn}?', preg_quote($term, '/')).')/ui';
    return preg_replace($pattern, '<strong>$0</strong>', htmlspecialchars($str));
}
Run Code Online (Sandbox Code Playgroud)