为什么 PHP mb_convert_case() 和 mb_strtoupper() 将μ(U+00B5 微符号)转换为"Μ"?



我正在尝试编写自己的mb_ucwords()函数,以提供mb_convert_case的快速包装器,以便它可以使用多字节字符串,因为基本ucwords()函数没有。

我遇到了一个问题,其中传入的字符串以μ字符(U+00B5 MICRO SIGN)开头,返回为"Μ"(U+039C希腊大写字母MU)而不是像我认为应该发生的那样被忽略。

我写了一个快速测试脚本来验证一些信息:

function testUtf8($letter) {
echo "CHAR: " . $letter . "n";
echo "Detected Encoding: " . mb_detect_encoding($letter) . "n";
echo "IS VALID UTF-8? " . (mb_check_encoding($letter, 'UTF-8') ? 'YES' : 'NO') . "n";
$lower = mb_strtolower($letter);
$upper = mb_strtoupper($letter);
$conv = mb_convert_case($letter, MB_CASE_TITLE, 'UTF-8');
echo "mb_strtolower(): " . $lower . "(" . mb_ord($lower) . ")n";
echo "mb_strtoupper(): " . $upper . "(" . mb_ord($upper) . ")n";
echo "mb_convert_case(): " . $conv . "(" . mb_ord($conv) . ")n";
echo "n";
echo "Matches RegEx /p{L}/u: " . (preg_match('/p{L}/u', $letter) ? 'YES' : 'NO') . "n";
echo "Matches RegEx /p{N}/u: " . (preg_match('/p{N}/u', $letter) ? 'YES' : 'NO') . "n";
echo "Matches RegEx /p{Xan}/u: " . (preg_match('/p{Xan}/u', $letter) ? 'YES' : 'NO') . "n";
}
testUtf8('µ');

得到的输出是:

CHAR: µ
Detected Encoding: UTF-8
IS VALID UTF-8? YES
mb_strtolower(): µ(181)
mb_strtoupper(): Μ(924)
mb_convert_case(): Μ(924)
Matches RegEx /p{L}/u: YES
Matches RegEx /p{N}/u: NO
Matches RegEx /p{Xan}/u: YES

谁能给我解释一下为什么PHP认为µ是一个"字母"?为什么MB的大写版本是"Μ"?我将通过测试每个单词的第一个字母并验证它是一个有效的unicode"字母"来解决这个问题。在运行转换之前,但正如您所看到的,这对这个字符不起作用,因为/p{L}/u匹配该字符:(

知道我该如何解决这个问题吗?

这是我函数的草稿:

/**
* @param string $string The string to convert
* @param string $encoding Default is UTF-8
* @param string $delim_pattern Pattern used to break $string into words
* @return string
*/
public static function mb_ucwords(
string $string,
string $encoding = 'UTF-8',
string $delim_pattern = '/([/-sv"'\]+)/u'
): string {
$words = preg_split($delim_pattern, $string, -1, PREG_SPLIT_DELIM_CAPTURE);
$output = "";
foreach($words as $word) {
$output .= mb_convert_case($word, MB_CASE_TITLE, $encoding);
}
return $output;
}

目前在PHP7.4上测试此代码

编辑:

显然这是一个希腊字母,也是micro的符号,M是该希腊字母的大写版本。我不知道该如何处理……

在Unicode 2中,µ(U+00B5 MICRO SIGN)被更改为具有μ (U+03BC GREEK SMALL LETTER MU)的兼容性分解。同时,将其类别由符号改为字母,以匹配μ (U+03BC GREEK SMALL letter MU)。这意味着U+00B5不应该在新文本中使用;它仅用于与非unicode字符集的兼容性。在某些规范化形式下,它们被认为是相同的字符。

在Unicode 3.0中,它被更新为以M (U+039C GREEK CAPITAL LETTER MU)作为其大写映射,给出您现在看到的结果。

不幸的是,由于µ(U+00B5 MICRO SIGN)基本上已被弃用,如果你使用它,你就得靠自己了。在调用mb_convert_case之前,可以将字符串的第一个字符与µ(U+00B5 MICRO SIGN)进行比较。但是,不能保证某些系统不会将其静默地转换为μ (U+03BC GREEK SMALL LETTER MU),例如,如果它对字符串进行规范化。如果你永远不会使用μ (U+03BC GREEK SMALL LETTER MU),你也可以对该字符进行特殊处理。

在不破坏对希腊文本支持的情况下处理此问题的万无一失的方法是使用某种标记语言或富文本来指示该字符用作符号而不是字母,然后在执行大小写转换时解析该字符。但那显然是一项更大的工程。

可以这么简单

function mb_ucfirst($string)
{
$main_encoding = "cp1250"; 
$inner_encoding = "utf-8";
$string = iconv($main_encoding, $inner_encoding, $string);
$strlen = mb_strlen($string);
$firstChar = mb_substr($string, 0, 1, $inner_encoding);
$then = mb_substr($string, 1, $strlen - 1, $inner_encoding);
return iconv($inner_encoding, $main_encoding , mb_strtoupper($firstChar, $inner_encoding) . $then );
}

保持µ,而我是测试它。