utf-8 和 Latin-1 之间可打印字符的编码差异是否可以解决?

Can the encoding difference for printable characters between utf-8 and Latin-1 be resolved?

我读到 Latin-1 和 UTF-8 对于可打印字符应该没有区别。我认为 latin-1 'Ä' 会两次映射到 utf-8。 一次到多字节版本,一次直接。

为什么好像不是这样?

看起来标准确实可以包含任何看起来像连续字节但不是连续字节的内容,因为 latin-1 中的含义不会丢失任何内容。

我是否只是缺少一个标志或允许我像所描述的那样转换数据的东西,或者我是否缺少更大的图景?

这是一个 C# 示例:

我系统上的输出是

    static void Main(string[] args)
    {
        DecodeTest("ascii7", " ~", new byte[] { 0x20, 0x7E });
        DecodeTest("Latin-1", "Ä", new byte[] { 0xC4 });
        DecodeTest("UTF-8", "Ä", new byte[] { 0xc3, 0x84 });
    }

    private static void DecodeTest(string testname, string expected, byte[] encoded)
    {
        var utf8 = Encoding.UTF8;
        string ascii7_actual = utf8.GetString(encoded, 0, encoded.Length);
        //Console_Write(encoded);
        AssertEqual(testname, expected, ascii7_actual);
    }

    private static void AssertEqual(string testname, string expected, string actual)
    {
        Console.WriteLine("Test: " + testname);
        if (actual != expected)
        {
            Console.WriteLine("\tFail");
            Console.WriteLine("\tExpected: '" + expected + "' but was '" + actual + "'");
        }
        else
        {
            Console.WriteLine("\tPass");
        }
    }

    private static void Console_Write(byte[] ascii7_encoded)
    {
        bool more = false;
        foreach (byte b in ascii7_encoded)
        {
            if (more)
            {
                Console.Write(", ");
            }
            Console.Write("0x{0:X}", b);
            more = true;
        }
    }

I read that there should be no difference between Latin-1 and UTF-8 for printable characters.

你看错了。对于 US-ASCII 范围(U+0000 到 U+007F)中的字符,Latin-1(以及许多其他编码,包括 ISO 8859 系列的其余部分)和 UTF-8 之间没有区别。 所有 个其他字符都不同。

I thought that a latin-1 'Ä' would map twice into utf-8. Once to the Multi byte Version and once directly.

要实现这一点,需要 UTF-8 是有状态的,或者以其他方式使用流中较早的信息来了解是将八位字节解释为直接映射还是多字节编码的一部分。 UTF-8 的一大优点是它是无状态的。

Why does it seem like this is not the case?

因为这是完全错误的。

It certainly seems like the standard could include anything that looks like a continuation byte but is not a continuation as the meaning within latin-1 without losing anything.

如果不失去无状态的质量,就无法做到这一点,这意味着损坏会破坏错误后的整个文本,而不仅仅是一个字符。

Am I just missing a flag or something that would allow me to convert the data like described, or am I missing the bigger picture?

不,您只是对 UTF-8 and/or Latin-1 的工作原理有一个完全错误的想法。

一个标志将消除 UTF-8 在无状态和自同步方面的简单性(您总是可以立即判断您是在单八位字节字符、字符的开头还是字符的中途) 正如刚才提到的。它还会消除 UTF-8 在算法方面的简单性。所有 UTF-8 编码映射如下。

从代码点映射到编码:

  1. 考虑字符的位 xxxx… 例如对于 U+0027 他们是 100111 对于 U+1F308 他们是 11111001100001000.

  2. 从以下选项中找出最小的:

    0xxxxxxx

    110xxxxx 10xxxxxx

    1110xxxx 10xxxxxx 10xxxxxx

    11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

所以 U+0027001001110x27 并且 U+1F30811110000 10011111 10001100 100010000xF0 0x9F 0x8C 0x88.

要从八位字节到代码点,请撤消此操作。

要映射到 Latin 1,您只需将字符放入一个八位位组中(这显然只有在 U+0000 到 U+00FF 范围内时才有效)。

如您所见,U+0000U+007F 范围之外的字符不可能在 UTF-8 和 Latin-1 中具有匹配的编码。 ("Latin 1" 也是 CP-1252 的名称,它是一种 Microsoft 编码,可以放置更多可打印字符,但仍然只是 UTF-8 所涵盖字符的一小部分)。

有一种方法理论上可以使一个字符具有多个 UTF-8 编码,但它被明确禁止。考虑一下,不是将 U+0027 的位放入单个单元 00100111,我们也可以零填充并将其放入 11000000 10100111 编码为 0xC0 0xA7。同样的解码算法会让我们回到 U+0027(试试看)。然而,除了在使用这种同义词编码时引入不必要的复杂性之外,这也引入了安全问题,并且确实存在由接受超长 UTF-8 的代码引起的现实世界安全漏洞。

也许您需要一个扫描功能来决定需要哪个解码器?

试试这个:

/// <summary>
/// Count valid UTF8-Bytes
/// </summary>
/// <returns>
/// -1 = invalid UTF8-Bytes (may Latin1)
/// 0 = ASCII only 7-Bit
/// n = Count of UTF8-Bytes
/// </returns>
public static int Utf8CodedCharCounter(byte[] value) // result: 
{
  int utf8Count = 0;
  for (int i = 0; i < value.Length; i++)
  {
    byte c = value[i];
    if ((c & 0x80) == 0) continue; // valid 7 Bit-ASCII -> skip

    if ((c & 0xc0) == 0x80) return -1; // wrong UTF8-Char

    // 2-Byte UTF8
    i++; if (i >= value.Length || (value[i] & 0xc0) != 0x80) return -1; // wrong UTF8-Char
    if ((c & 0xe0) == 0xc0) { utf8Count++; continue; }

    // 3-Byte UTF8
    i++; if (i >= value.Length || (value[i] & 0xc0) != 0x80) return -1; // wrong UTF8-Char
    if ((c & 0xf0) == 0xe0) { utf8Count++; continue; }

    // 4-Byte UTF8
    i++; if (i >= value.Length || (value[i] & 0xc0) != 0x80) return -1; // wrong UTF8-Char
    if ((c & 0xf8) == 0xf0) { utf8Count++; continue; }

    return -1; // invalid UTF8-Length
  }
  return utf8Count;
}

并更新您的代码:

private static void DecodeTest(string testname, string expected, byte[] encoded)
{
  var decoder = Utf8CodedCharCounter(encoded) >= 0 ? Encoding.UTF8 : Encoding.Default;
  string ascii7_actual = decoder.GetString(encoded, 0, encoded.Length);
  //Console_Write(encoded);
  AssertEqual(testname, expected, ascii7_actual);
}

结果:

Test: ascii7
        Pass
Test: Latin-1
        Pass
Test: UTF-8
        Pass