如何正确转换USC-2的小端到UTF-8?

问题描述:

我有一个文件,行结束符是windows的风格\r\n;它被编码在USC-2的小端中。如何正确转换USC-2的小端到UTF-8?

说这是我的文件fruit.txt(USC-2小端):

input file

于是我在一个std::wifstream打开它,并尝试分析内容:

// open the file 
    std::wifstream file("fruit.txt"); 
    if(! file.is_open()) throw std::runtime_error(std::strerror(errno)); 

// create container for the lines 
    std::forward_list<std::string> lines; 

// Add each line to the container 
    std::wstring line; 
    while(std::getline(file,line)) lines.emplace_front(wstring_to_string(line)); 

如果我尝试打印到cout ...

// Printing to cout 
    for(auto it = lines.cbegin(); it != lines.cend(); ++it) 
     std::cout << *it << std::endl; 

...这就是它输出:

Cherry 
Banana 
ÿþApple 

更糟糕的是,如果我在记事本++打开它,这就是它看起来像

Null characters everywhere

我可以排序,通过整治强制转换的编码返回到USC-2,这导致这样的:

enter image description here

wstring_to_string功能被定义为:

std::string wstring_to_string(const std::wstring& wstr) { 
    std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> convert; 
    return convert.to_bytes(wstr); 
} 

这个世界正在发生什么?我怎样才能得到一个正常的UTF-8字符串?我也尝试过这种方法:How to read utf-16 file into utf-8 std::string line by line,但是填充std::wifstream首先导致完全没有输出。有人能帮助指导我以最好的方式将USC-2 LE数据转换为可读的UTF-8数据吗?

编辑我认为可能是由MSYS2提供的mingw64/mingw-w64-x86_64-gcc 6.3.0-2的一个bug。我已经尝试过所有人的建议,并将语言环境嵌入到流中,只是根本没有输出。我知道只有两个本地语言环境,“C”和“POSIX”。我打算尝试Visual Studio,但没有足够的网络速度用于4GB下载。我曾使用过ICU,像@Andrei R.建议的那样,它工作得很好。

我会喜欢使用标准库,但我确定这一点。如果您需要此解决方案,请查看我的代码:https://pastebin.com/qudy7yva

+0

这是Windows吗?通过将控制台文本复制到编辑器,您获得了NP ++图片吗? (在有人说NP ++是Windows程序之前,它在Wine上运行良好) – deviantfan

+0

是的,这是Windows。通过运行我的程序来获得log.txt,如下所示:./program.exe> log.txt。我使用的是MSYS2的g ++ 6.3.0 –

+1

那么你应该知道Windows控制台(对于所有版本的Windows)都不能处理UTF8。有些东西可以直接使用,有些东西有解决方法,但100%正确的行为是不可能的(例如,由于一些CRT错误,他们无意修复(因为工作太多))。 >重定向不是你自己程序的一部分,所以我不会太依赖它。 – deviantfan

转换为/从unicode通常不是那么平凡。看看ICU库,我相信这是迄今为止最完整的c/C++编码转换库。

也有平台依赖的方式,如WideCharToMultibyte (Win)iconv (Linux)。或者,使用Qt,您可以使用QString::fromUtf16。可能你必须自己扭转排列顺序。

+1

'转换成unicode/unicode通常不是那么简单.'这是一个转换从Unicode到Unicode ......无需ICU即可管理 – deviantfan

代码本身很好。

真正的问题是您的输入文件不是有效的UTF-16LE开头(您使用的std::codecvt_utf8_utf16需要UTF-16,而不是UCS-2)。这清楚地显示在Notepad ++屏幕截图中。

副手,文件数据看起来像一个BOM一个UTF-16LE文件(ÿþ是UTF-16LE BOM作为8位ANSI观察时)被所附原样到UCS-2BE的端部(或UTF-16BE)文件没有BOM。

您需要修复输入文件,以便整个文件从开始到结束(有或没有前面的BOM,而不是中间)都是有效的UTF-16LE。

然后你已经有的代码将工作。

+1

'真正的问题是您的输入文件不是......可以清晰地显示在您的Notepad ++屏幕截图中。我认为截图来自输出。 – deviantfan

+1

我用'fruit.txt'的屏幕截图编辑了我的答案, –

+0

...和新的屏幕截图(这次输入)看起来不错。 – deviantfan

对于您的情况,主要问题是您使wifstream以错误的方式读取文件。如果你在wstring_to_string中打印wstr的大小,你会发现它不是你所期望的。

https://*.com/a/19698449/4005852

设置正确的语言环境会解决这个问题。

std::string wstring_to_string(const std::wstring& wstr) { 
    std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> convert; 
    return convert.to_bytes(wstr); 
} 

int main() 
{ 
// open the file 
    std::wifstream file("fruit.txt", std::ios::binary); 
    file.imbue(std::locale(file.getloc(), 
      new std::codecvt_utf16<wchar_t, 0x10ffff, std::little_endian>)); 
    if(! file.is_open()) throw std::runtime_error(std::strerror(errno)); 

// create container for the lines 
    std::forward_list<std::string> lines; 

// Add each line to the container 
    std::wstring line; 
    file.get(); // remove BOM 
    while(std::getline(file,line)) lines.emplace_front(wstring_to_string(line)); 

// Printing to cout 
    for(auto it = lines.cbegin(); it != lines.cend(); ++it) 
     std::cout << *it << std::endl; 

    return 0; 
} 
+0

我根本没有输出。我开始认为这是一个编译器错误:/ –

+0

我正在使用“Microsoft(R)C/C++ Optimizing Compiler Version 19.00.24210 for x64”。你的编译器是什么? –

+0

好的。我目前正在下载Visual Studio以尝试使用另一个编译器。我通常使用MSYS2的g ++ –