如何正确地将 USC-2 little endian 转换为 UTF-8?
Posted
技术标签:
【中文标题】如何正确地将 USC-2 little endian 转换为 UTF-8?【英文标题】:How to properly convert USC-2 little endian into UTF-8? 【发布时间】:2017-04-12 02:21:57 【问题描述】:我有一个文件,行尾是windows风格的\r\n
;它以 USC-2 little endian 编码。
说这是我的文件fruit.txt
(USC-2 little endian):
所以我在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
更糟糕的是,如果我在 Notepad++ 中打开它,它就是这个样子
我可以通过将编码强制转换回 USC-2 来解决这个问题:
我的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 可能存在错误。我已经尝试了每个人的建议,并且将语言环境灌输到流中只是完全没有输出。我知道只提供了两个本地语言环境,“C”和“POSIX”。我打算尝试 Visual Studio,但没有足够的网速下载 4GB。我使用过 @Andrei R. 建议的 ICU,效果很好。
我很想使用标准库,但我可以接受。如果您需要此解决方案,请查看我的代码:https://pastebin.com/qudy7yva
【问题讨论】:
这是 Windows 吗?你是通过将控制台文本复制到编辑器中得到的 NP++ 图片吗? (在有人说 NP++ 是 Windows 程序之前,它在 Wine 上运行良好) 是的,这是 Windows。我通过像这样运行我的程序获得了 log.txt:./program.exe > log.txt。我正在使用 MSYS2 的 g++ 6.3.0 好吧,那么您应该知道 Windows 控制台(适用于所有版本的 Windows)无法处理 UTF8。有些东西开箱即用,有些东西有变通方法,但 100% 正确的行为是不可能的(例如,因为他们无意修复的一些 CRT 错误(因为工作太多))。 > 重定向不是你自己程序的一部分,所以我不会太依赖它。 ...即。尝试直接从您的程序写入文件。 无缘无故重写控制台核心和所有相关的 CRT 内容不会是 Msys2 所做的事情。对于支持 bash 语法等,它不是必需的,而且比它更难。 【参考方案1】:代码本身没有问题。
真正的问题是您的输入文件一开始就不是有效的 UTF-16LE(您使用 std::codecvt_utf8_utf16
需要 UTF-16,而不是 UCS-2)。这清楚地显示在您的 Notepad++ 屏幕截图中。
顺便说一句,文件数据看起来像一个带有 BOM 的 UTF-16LE 文件(ÿþ
是 UTF-16LE BOM,当被视为 8 位 ANSI 时)按原样附加到没有 BOM 的 UCS-2BE(或 UTF-16BE)文件。
您需要修复输入文件,使整个文件从头到尾都是有效的 UTF-16LE(前面有或没有 BOM,而不是中间)。
那么你已有的代码就可以工作了。
【讨论】:
The real problem is that your input file is NOT... is clearly shown in your Notepad++ screenshots
。我认为屏幕截图来自输出。
...新的截图(这次输入)看起来没问题。
这不是有效的 USC-2/UTF-16 LE 吗? drive.google.com/file/d/0B8-ysHxtvszydlA0cFJUVXFFSEU/…
@deviantfan 很好地了解了屏幕截图是输出,而不是输入。
@DustinGoodson 该文件非常好。我看不出带有显示代码的输入文件可以产生屏幕截图中显示的输出的任何可能方式。但很明显,std::wifstream
没有吞下输入 BOM。您需要imbue
一个区域设置,其方面启用std::consume_header
标志。有关示例,请参见 this answer。【参考方案2】:
与 unicode 之间的转换通常不是那么简单。看看ICU库,我相信这是迄今为止最完整的c/c++编码转换库。
还有一些依赖于平台的方式,例如WideCharToMultibyte (Win) 或iconv (Linux)。或者,对于 Qt,您可以使用 QString::fromUtf16
。可能您必须自己反转字节顺序。
【讨论】:
converting to/from unicode is in general not so trivial.
这是从 Unicode 到 Unicode 的转换......没有ICU也可以管理【参考方案3】:
对于您的情况,主要问题是您使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;
【讨论】:
我没有得到任何输出。我开始认为这是一个编译器错误:/ 我正在使用“Microsoft (R) C/C++ Optimizing Compiler Version 19.00.24210 for x64”。你的编译器是什么? 好的。我目前正在下载 Visual Studio 以尝试使用另一个编译器。我通常使用 MSYS2 中的 g++以上是关于如何正确地将 USC-2 little endian 转换为 UTF-8?的主要内容,如果未能解决你的问题,请参考以下文章
如何正确地将静态“正确细节”UITableViewCell 内容与图像对齐?