c++ - 使用ICU将UTF-8转换为UCS2导致乱码

标签 c++ unicode utf-8 icu ucs2

这是a previous one的后续问题

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

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

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

新的更新代码是:

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);
}

最佳答案

ICU转换可为您提供正确的结果,但您不太了解如何处理它们,并成功将其转换为乱码。这是您做错了的事情,或多或少是有序的。



您在非拉丁1数据上运行非拉丁1数据(根据现有证据表明)。

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

UTF-16(后来在1996年取代了UCS-2)不是那么好。一个UTF-16编码的字符串包含两个字节长的代码单元。这两个字节中的任何一个都完全可以为零。 (所有编码为UTF-16的ASCII字符都有一个零字节)。只要另一个字节不为零,则整个字符为非NULL。但是,您的printfstrlen等不知道还有另一个字节。他们认为您正在喂他们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既庞大又复杂,比大多数人意识到的复杂得多。这仅仅是开始的开始。



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

关于c++ - 使用ICU将UTF-8转换为UCS2导致乱码,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/22228565/

相关文章:

java - 为什么在使用 += 将整数连接到字符串时 g++ 不发出警告/错误

string - 从字符串中提取单个 unicode 字符

ios - Unicode不间断空格在标签末尾被删除

java - 如何在 Java 中从一个字符集解码文本并将其转换为另一个字符集?

c++ - 内联汇编代码无法在 Visual C++ 2010 Express 中编译

c++ - 在类模板的成员枚举上重载运算符

c++ - C++17 之前是否存在 if 语句中的初始化?

javascript - Javascript 中标识符的 Unicode 转义序列

linux - UTF-8 文件名在 linux 终端中返回 Not Found

java - 为什么在链 byte[] → String → byte[] 中使用 UTF-8 字符集时输入和输出不同?