同一个字符是否可以有两个不同的UTF-8编码?

6
我正在编写一个应用程序,需要将输入从UTF-8转码为ISO-8859-1(Latin 1)。
所有工作都很正常,但是有时某些umlaut字符的编码会变得奇怪。例如,带2个点的拉丁字母E(0xEB)通常以UTF-8 0xC3 0xAB形式出现,但有时也以0xC3 0x83 0xC2 0xAB的形式出现。
这种情况发生了多次,来自不同的来源,并且注意到第一个和最后一个字符与我预期的匹配,我的库可能缺少一些编码规则吗?
3个回答

11
某些Unicode字符可以用组合分解形式表示。例如,德语的umlaut-u ü可以通过单个字符ü或者u后跟¨来表示,文本渲染器会将它们组合起来。
请参阅维基百科上关于Unicode等价性的详细信息。
因此,Unicode库通常提供方法或函数将字符串规范化为一种形式,以便进行比较。

+1 这是我在阅读了这篇文章之后仍然不知道的一件事情:http://www.joelonsoftware.com/articles/Unicode.html - Ozair Kafray
1
-1 这个答案不适用于 Gene Vincent 遇到的问题。 - daxim
4
怎么样?他问了编码规则,我告诉了他。 - DarkDust
1
@DarkDust 问的是 0xC3 0x83 0xC2 0xAB 表示 ë。那不是分解形式;那是双重编码。 - cjm

9
$ "\xC3\x83\xC2\xAB"
ë
$ use Encode

$ decode 'UTF-8', "\xC3\x83\xC2\xAB"
ë

您的UTF-8已经双重编码。使用Encode::Repair可以解决这个问题。

4
我是一名有用的助手,可以为您翻译文本。

(我正在回答您的问题“同一个字符可以有两个不同的UTF-8编码吗?”,这与帖子内部的问题有很大区别。)

(“字符”通常表示字符串元素。它含糊不清,在这里使用它并不正确。Unicode术语对于视觉表示即字形的术语是“grapheme”。)

是的,多个代码点序列可以导致相同的字形。例如,下面两个都是可能的:

U+00EB  LATIN SMALL LETTER E WITH DIAERESIS

并且

U+0065  LATIN SMALL LETTER E
U+0308  COMBINING DIAERESIS

应该显示为“ë”。让我们看看您的浏览器如何处理:
  • U+00EB: “ë”
  • U+0065,0308: “ë”

在UTF-8中,这些码点将被编码为

  • U+00EB: C3 AB
  • U+0065: 65
  • U+0308: CC 88

人们可以使用Unicode::NormalizeNFCNFD将字符串规范化为两种格式之一(由您选择)。

$ 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 来说,这不应该是一个问题。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接