我正在编写一个需要将其输入从 UTF-8 转码为 ISO-8859-1 (Latin 1) 的应用程序。
一切正常,除了我有时会得到一些变音符号的奇怪编码。例如,带有 2 个点 (0xEB) 的拉丁语 1 E 通常以 UTF-8 0xC3 0xAB 的形式出现,但有时也以 0xC3 0x83 0xC2 0xAB 的形式出现。
这从不同的来源发生了很多次,并注意到第一个和最后一个字符与我的预期相匹配,是否存在我的库不知道的编码规则?
我正在编写一个需要将其输入从 UTF-8 转码为 ISO-8859-1 (Latin 1) 的应用程序。
一切正常,除了我有时会得到一些变音符号的奇怪编码。例如,带有 2 个点 (0xEB) 的拉丁语 1 E 通常以 UTF-8 0xC3 0xAB 的形式出现,但有时也以 0xC3 0x83 0xC2 0xAB 的形式出现。
这从不同的来源发生了很多次,并注意到第一个和最后一个字符与我的预期相匹配,是否存在我的库不知道的编码规则?
某些 Unicode 字符可以以组合和分解的形式表示。例如,德语变音符号 uü
可以由单个字符ü
或u
后跟表示¨
,文本渲染器随后会将其组合。
有关详细信息,请参阅有关Unicode 等效性的 Wikipedia 文章。
因此,Unicode 库通常提供将字符串规范化为一种或另一种形式的方法或函数,以便您可以比较它们。
$ "\xC3\x83\xC2\xAB"
ë
$ use Encode
$ decode 'UTF-8', "\xC3\x83\xC2\xAB"
ë
您已经对 UTF-8 进行了双重编码。Encode::Repair是解决这个问题的一种方法。
(我正在回答您的主题问题,“同一字符可以有 2 种不同的 UTF-8 编码吗? ”,这与帖子中的问题有很大不同。)
(“字符”通常表示字符串元素。它在野兽中是模棱两可的,在这里使用的词不正确。用于视觉表示的 Unicode 术语,字形,是“字形”。)
是的,有多个代码点序列可以导致相同的字素。例如,两者
U+00EB LATIN SMALL LETTER E WITH DIAERESIS
和
U+0065 LATIN SMALL LETTER E
U+0308 COMBINING DIAERESIS
应显示为“ë”。让我们看看你的浏览器是怎么做的:
在 UTF-8 中,这些代码点将被编码为
一种是使用Unicode::Normalize或将字符串规范化为两种格式之一(您的选择)NFC
。NFD
$ perl -MUnicode::Normalize -E'
$x = "\x{00EB}";
$y = "\x{0065}\x{0308}";
say $x eq $y ?1:0;
say NFC($x) eq NFC($y) ?1:0;
say NFD($x) eq NFD($y) ?1:0;
'
0
1
1
UTF-8 中还有一种称为“超长”编码的东西。(特别是 UTF-8,而不是一般的 Unicode。)在 UTF-8 中,Unicode 代码点使用以下四种位模式之一进行编码:
1 0xxxxxxx
2 110xxxxx 10xxxxxx
3 1110xxxx 10xxxxxx 10xxxxxx
4 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
“x”代表要编码的代码点。必须使用最短的,所以 U+00EB 将是
0000 0000 1110 1011
--- ---- ----
----- ------
110xxxxx 10xxxxxx
11000011 10101011
C3 AB
但聪明的人可能会做
0000 0000 1110 1011
---- ---- ---- ----
---- ------ ------
1110xxxx 10xxxxxx 10xxxxxx
11100000 10000011 10101011
E0 83 AB
应用程序应拒绝 E0 83 AB(或至少将其转换为 C3 AB),但有些应用程序不会,这可能会导致安全问题。Perl 的 Encode 模块将该序列视为无效,因此对于 Perl 来说应该不是问题。