Accent-insensitive substring matching

2019-07-15 08:42发布

I have a search functionality that obtains data from an InnoDB table (utf8_spanish_ci collation) and displays it in an HTML document (UTF-8 charset). The user types a substring and obtains a list of matches where the first substring occurrence is highlighted, e.g.:

Matches for "AL":

Álava
<strong>Al</strong>bacete
<strong>Al</strong>mería
Ciudad Re<strong>al</strong>
Málaga

As you can see from the example, the search ignores both case and accent differences (MySQL takes care of it automatically). However, the code I'm using to hightlight matches fails to do the latter:

<?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) );
}

?>

Is there a sensible way to fix this that doesn't imply hard-coding all possible variations?

Update: System specs are PHP/5.2.14 and MySQL/5.1.48

2条回答
做个烂人
2楼-- · 2019-07-15 09:00

use PEAR I18N_UnicodeNormalizer-1.0.0

include('…');

echo preg_replace(
 '/(\P{L})/ui', // replace all except members of Unicode class "letters", case insensitive
 '', // with nothing → drop accents
 I18N_UnicodeNormalizer::toNFKD('ÅÉÏÔÙåéïôù') // ù → u + `
);

→ AEIOUaeiou

查看更多
Juvenile、少年°
3楼-- · 2019-07-15 09:13

You could use the Normalizer to normalize the string to Normalization Form KD (NFKD) where the characters are getting decomposed, so Á (U+00C1) is getting decomposed to the combination of the letter A (U+0041) and the combining mark ́ (U+0301):

$str = Normalizer::normalize($str, Normalizer::FORM_KD);

Then you modify the search pattern to match those optional marks:

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

The replacement is then done with preg_replace:

preg_replace($pattern, '<strong>$0</strong>', htmlspecialchars($str))

So the full method is:

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));
}
查看更多
登录 后发表回答