5

我正在使用需要 utf8 编码的 std::string 变量的代码。我希望能够处理用户提供的可能具有 utf-16 编码的文件(我在设计时不知道编码,但最终希望能够处理 utf8/16/32),请阅读它行逐行,并将每一行作为 utf8 编码的 std::string 转发到代码的其余部分。

我有 c++11(实际上是 c++11 的当前 MSVC 子集)和 boost 1.55.0 可以使用。我最终需要代码才能在 Linux 和 Windows 变体上工作。目前,我只是在 Windows 上使用 Visual Studio 2013 Update 4 进行原型设计,在 Windows 7 上运行。我对其他依赖项持开放态度,但他们需要有一个已建立的跨平台(即 windows 和 *nix)轨道记录,不应该是 GPL/LGPL。

我一直在假设我似乎无法找到验证方法,并且我的代码不起作用。

一个假设是,由于我最终希望这些文件中的每一行都包含在 std::string 变量中,因此我应该使用带有正确构造的 codecvt 的 std::ifstream ,以便可以将传入的 utf16 流转换为 utf8。

这个假设现实吗?我认为,另一种选择是我必须对文本文件进行一些编码检查,然后根据结果选择 wifstream/wstring 或 ifstream/string,这似乎比我开始时更没有吸引力. 当然,如果这是正确的(或唯一现实的)道路,我愿意接受。

我意识到无论如何我可能需要做一些编码检测,但现在,我不太关心编码检测部分,只关注将 utf16 文件内容转换为 utf8 std::string。

我尝试了各种不同的语言环境和编解码器组合,但都没有奏效。以下是我认为可能有效但无效的最新版本:

void
SomeRandomClass::readUtf16LeFile( const std::string& theFileName )
{
    boost::locale::generator gen;
    std::ifstream file( theFileName );
    auto utf8Locale = gen.generate( "UTF-8" );
    std::locale cvtLocale( utf8Locale,
                           new std::codecvt_utf8_utf16<char>() );

    file.imbue( utf8Locale );
    std::string line;

    std::cout.imbue( utf8Locale );
    for ( int i = 0; i < 3; i++ )
    {
        std::getline( file, line );
        std::cout << line << std::endl;
    }
}

我在这段代码中看到的行为是每次调用 getline() 的结果都是一个空字符串,无论文件内容如何。

如果我省略上述方法的第 3 行和第 5 行,则相同的代码在同一文件的 utf8 编码版本上可以正常工作(意味着每个 getline() 调用都返回一个正确编码的非空字符串)。

无论出于何种原因,我在 SO 或http://en.cppreference.com/或野外其他地方的任何地方都找不到任何试图做同样事情的人的例子。

欢迎所有想法/建议(符合上述要求)。

4

1 回答 1

9

读取 UTF-16 写入 UTF-8

您必须澄清的第一个问题是关于您正在阅读的UTF16的变体:

  • 是 UTF-16LE(即在 windows 下生成的)吗?
  • 是 UTF-16BE(默认由 wstream 生成)吗?
  • 是带有BOM的 UTF16吗?

下一个问题是要知道您是否真的可以在控制台上输出您的 UTF8 或 UTF16,要知道默认的 Windows 控制台确实会导致头疼。

第 1 步:确保问题与 Win 控制台无关

所以这里有一个小代码来读取 UTF-16LE 并使用本机 Windows 功能检查内容(您只需<windows.h>在控制台应用程序中包含):

    wifstream is16(filename);
    is16.imbue(locale(is16.getloc(), new codecvt_utf16<wchar_t, 0x10ffff, little_endian>()));
    wstring wtext, wline;
    for (int i = 0; getline(is16, wline); i++)
        wtext += wline + L"\n";
    MessageBoxW(NULL, wtext.c_str(), L"UTF16-Little Endian", MB_OK);

如果您的文件是带有 BOM 的 UTF-16,只需替换litte_endianconsume_header.

第 2 步:将您的 utf16 字符串转换回 utf8 字符串

您必须使用字符串转换器:

    wstring_convert<std::codecvt_utf8_utf16<wchar_t>, wchar_t> converter;

    wifstream is16(filename);
    is16.imbue(locale(is16.getloc(), new codecvt_utf16<wchar_t, 0x10ffff, little_endian>()));
    wstring wline;
    string u8line; 
    for (int i = 0; i < 10 && getline(is16, wline); i++) {
         u8line = converter.to_bytes(wline);
         cout << u8line<<endl; 
    }

这将在 win 控制台上很好地显示 ascii 字符。但是,所有 utf8 编码都会显示为垃圾(除非您比我更成功地将控制台设置为显示 unicode 字体)。

第 3 步:使用文件检查 utf8 编码

由于 Win 控制台非常不擅长,最好的办法是将您生成的字符集直接写入文件并使用文本编辑器(例如 Notepad++)打开该文件,该编辑器可以显示编码。

注意事项:所有这些都是仅使用标准库(除了 intermediary MessageBoxW())及其语言环境来完成的。

进一步的步骤

如果要检测编码,首先要查看文件开头是否有 BOM(打开二进制输入,默认为“C”语言环境):

char bom_utf8[]{0xEF, 0xBB, 0xBF};
char bom_utf16be[] { 0xFE, 0xFF};
char bom_utf16le[] { 0xFf, 0xFe};
char bom_utf32be[] { 0, 0, 0xFf, 0xFe};
char bom_uff32le[] { 0xFf, 0xFe, 0, 0};

只需加载前几个字节,并与此数据进行比较。

如果你找到了,没关系。如果没有,您将不得不遍历该文件。

如果您期望使用西方语言,一个快速的近似值如下:如果您发现大量空字节 (>25% <50%),则可能是 utf16。如果您发现超过 50% 的空值,则可能是 utf32。

但更精确的方法可能是有意义的。例如,要验证文件是否为 UTF16,您只需实现一个小型状态机,检查第一个字的高字节是否在 0xD8 和 0xDB 之间,下一个字的高字节在 0xDC 和 0xDF 之间。什么是高什么是低当然取决于它是小端还是大端。

对于UTF8,这是一个类似的做法,但是状态机稍微复杂一些,因为第一个字符的位模式定义了必须跟随多少个字符,并且每个跟随者都必须有一个位模式(c & 0xC0) == 0x80

于 2015-03-29T22:36:49.747 回答