【问题标题】:UTF-8 to UCS2 conversion using icu resulting in gibberish使用 icu 将 UTF-8 转换为 UCS2 导致乱码
【发布时间】:2014-04-09 07:54:49
【问题描述】:

这是a previous one的后续问题

该问题中的问题已解决,现在代码按预期进行,但是 utf-8 到 ucs2 转换的最终输出是乱码。我的意思是最终文本的十六进制值无论如何都与 utf-8 版本不对应。我知道它们是不同的编码,但两者之间似乎没有任何映射。

转换的输入是“ĩ”,输出是“ÿþ)^A”。在十六进制中,“ĩ”(utf-8 值)的值为 c4a9,“ÿþ)^A”(ucs2 值)的值为“00FF 00FE 0029 0001”。

我希望有人对此行为有一个解释,或者可以告诉我我在代码中做错了什么。

新更新的代码是:

UErrorCode resultCode = U_ZERO_ERROR;

UConverter* pLatinOneConv = ucnv_open("ISO-8859-1", &resultCode);

// Change the callback to error out instead of the default            
const void* oldContext;
UConverterFromUCallback oldFromAction;
UConverterToUCallback oldToAction;
ucnv_setFromUCallBack(pLatinOneConv, UCNV_FROU_CALLBACK_STOP, NULL, &oldFromAction, &oldContext, &resultCode);
ucnv_setToUCallBack(pLatinOneConv, UCNV_TO_U_CALLBACK_STOP, NULL, &oldToAction, &oldContext, &resultCode);

int32_t outputLength = 0;
int bodySize = uniString.length();
int targetSize = bodySize * 4;
char* target = new char[targetSize];                       

printf("Body: %s\n", uniString.c_str());
if (U_SUCCESS(resultCode))
{
    outputLength = ucnv_fromAlgorithmic(pLatinOneConv, UCNV_UTF8, target, targetSize, uniString.c_str(),
        uniString.length(), &resultCode);
    ucnv_close(pLatinOneConv);
}
printf("ISO-8859-1 just tried to convert '%s' to '%s' with error '%i' and length '%i'", uniString.c_str(), 
    outputLength ? target : "invalid_char", resultCode, outputLength);

if (resultCode == U_INVALID_CHAR_FOUND || resultCode == U_ILLEGAL_CHAR_FOUND || resultCode == U_TRUNCATED_CHAR_FOUND)
{
    if (resultCode == U_INVALID_CHAR_FOUND)
    {
        resultCode = U_ZERO_ERROR;
        printf("Unmapped input character, cannot be converted to Latin1");                    
        // segment Text, if necessary, and add UUIDs copy existing pPdu's addresses and optionals
        UConverter* pUscTwoConv = ucnv_open("UCS-2", &resultCode);
        if (U_SUCCESS(resultCode))
        {
            printf("Text Body: %s\n", uniString.c_str());
            outputLength = ucnv_fromAlgorithmic(pUscTwoConv, UCNV_UTF8, target, targetSize, uniString.c_str(),
                uniString.length(), &resultCode);
            ucnv_close(pUscTwoConv);
        }
        printf("UCS-2 just tried to convert '%s' to '%s' with error '%i' and length '%i'", uniString.c_str(), 
            outputLength ? target : "invalid_char", resultCode, outputLength);

        if (U_SUCCESS(resultCode))
        {
            pdus = SegmentText(target, pPdu, SEGMENT_SIZE_UNICODE_MAX, true);
        }
    }
    else
    {
        printf("DecodeText(): Text contents does not appear to be valid UTF-8");
    }
}
else
{
    printf("DecodeText(): Text successfully converted to Latin1");
    std::string newBody(target, outputLength);
    pdus = SegmentText(newBody, pPdu, SEGMENT_SIZE_MAX);
}

【问题讨论】:

  • 如果您想将 utf8 转换为 ucs2(无论这意味着什么),Latin-1 在您的代码中做了什么?
  • 无论如何,U+fffe 是字节顺序标记,U+0129 是带波浪号的 i,因此您的转换至少部分正确。
  • @n.m. Latin1 在那里作为支票。基本上,我们正在测试是否可以先转码为 latin1,如果失败,则失败来自无效字符,我们将其转换为 ucs2。 ucs2 是 utf-16 的另一个名称,至少对于 libicu 而言。
  • 我看了一点代码。目前尚不清楚您为什么要尝试printf UTF-8 编码和 UTF-16 编码的字符串到同一个文件。它不适用于许多有效字符串。你如何看待你的十六进制值?你需要ICU吗?这是一个非常庞大且复杂的库。对于简单的任务libiconv 可能更合适。
  • 现在一切都将在屏幕输出,我通过获取输出并手动查找所有内容来获取十六进制值。我使用fileformat.info 作为我的参考。你的意思是它不适用于有效的字符串?我知道它存在一些输出问题,但可以对其进行解码。我们在这种情况下使用 icu 作为测试。我们可以选择在我们的软件中进行很多这样的转换。所以 libiconv 可能会让我们度过这个难关,但可能不是其他人。我现在也在质疑这是否不是字节顺序问题。

标签: c++ unicode utf-8 icu ucs2


【解决方案1】:

ICU 转换为您提供了正确的结果,但您不太清楚如何处理它们,并成功地将它们转换为乱码。以下是您做错的事情,或多或少是按顺序排列的。

一个

您在(如现有证据所示)本机使用 Latin-1 的系统上打印非拉丁语 1 数据。

当您打印 UTF-8 时,这并没有那么糟糕,因为 UTF-8 的设计目的是不会破坏使用 8 位字符数据的东西太难。您会看到乱码,但至少您会看到所有数据,并且能够将其转换回合理的数据。

UTF-16(顺便说一下,它在 1996 年取代了 UCS-2)不是那么好。 UTF-16 编码的字符串包含两个字节长的代码单元。这两个字节中的任何一个都可以为零。 (所有编码为 UTF-16 的 ASCII 字符都有一个零字节)。只要另一个字节不为零,整个字符就是非 NULL。但是,您的printfstrlen 等不知道 is 另一个字节。他们认为你给他们喂的是 Latin-1,他们会在第一个零字节处停止(他们将其解释为 NULL 字符)。

幸运的是,ĩ 字符在其 UTF-16 编码中没有零字节,所以这次你成功了。

如何正确操作?绝不是printffputs,而是fwrite/std::ostream::write;从不strcpy,总是memcpy;从不strlen,但始终将长度保存在一个单独的变量中。

两个

您在屏幕上打印此数据

您的屏幕可以以不同且有趣的方式解释(大概)从 0 到 31 的字节,通常是跟随它们的字节。例如,移动光标、发出哔哔声或更改文本颜色。您正在打印的 UTF-16 数据在其编码中绝对可以包含任何字节,即使源包含完全普通的可打印 Unicode 字符。所以几乎任何事情都可能发生。

再次幸运的是,您尝试转换的单个字符在其 UTF-16 表示中不包含有害字节。

如何正确操作?如果您需要打印一些内容以快速查看,请为所有或仅打印不可打印字符的十六进制代码。

 void print_bytes (FILE* fp, const unsigned char* s, int len,
                    bool escape_all) {
   // note: explicit length, *never* strlen!
   // note: unsigned char, you need it
   int i;
   for (i = 0; i < len; ++i, ++s)
   {
      if (escape_all || ! isprint(*s)) {
        fprintf ("\\x%02x", *s);
      } 
      else {
        fputc(*s, fp);
      }
   }
 }

三个

您在 fileinfo 上查找从屏幕上获得的 Latin-1 字符,因此将它们解释为 Unicode 字符,然后获取它们的 16 位字符代码(每个字符一个 16 位代码)并将它们解释为字节。

没什么好说的。只是不要那样做。您有一个以可读的十六进制表示形式打印字节的函数。用它。或者,使用任意数量的免费提供的程序来显示甚至让您编辑这样的表示。

当然,这并不是说您不应该使用 fileinfo。做对了,这基本上意味着知道你的编码是什么,以及任何给定的字符编码与其 Unicode 代码点有何不同(尽管有时相似)。

四个

本段不是关于错误本身,而是关于与您发布的任何代码不对应的开发人员的直觉(或缺乏直觉)。

尽管存在上述所有错误,但您还是设法获得了几乎不错的数据。您在所有偶数位置都有 00,这可能意味着您的整数位大小有问题,您需要摆脱这些零。完成此操作后,您将留下 FFFE 作为前两个字节,您应该将其识别为 BOM。您怀疑您有字节顺序问题,但您没有尝试通过改变 UTF-16 风格(UTF-16LE 与 UTF-16BE)来解决它。

这些是任何 Unicode 开发人员都应该能够几乎本能地应用的东西。


Unicode 庞大而复杂,比大多数人意识到的要复杂得多。这只是最开始的开始。


请为此答案提出改进建议。

【讨论】:

  • 我确实继承了这段代码。所以我并不惊讶我在这一点上存在巨大的信息空白。感谢您的详细回复。很多东西要筛选和理解。我将对此进行一些消化,并将发布我所做的任何后续更改,假设我可以根据需要使其正常工作。
猜你喜欢
  • 2017-02-01
  • 2011-09-04
  • 1970-01-01
  • 2012-06-30
  • 2011-06-26
  • 2014-02-02
  • 1970-01-01
  • 2013-05-20
  • 1970-01-01
相关资源
最近更新 更多