【问题标题】:How to turn std::string that contains utf-16 encoded text in it into utf-16 wstring?如何将包含 utf-16 编码文本的 std::string 转换为 utf-16 wstring?
【发布时间】:2011-08-26 22:11:56
【问题描述】:

所以我们得到一个类似Новая папка 的字符串,它是 utf-16 编码行的 utf-8 表示(Новая папка 在 utf-16 中)我们想把这个字符串转换成 wstring 而不改变编码.. 意思是字面上把所有数据从字符串到 wstring 没有任何转换。所以我们会得到带有Новая папка 内容的wstring。这种事情怎么办?

更新: 我的意思是 - 我们在字符串中拥有正确 utf-16 字符串的所有数据。我们所需要做的就是将该数据放入 wstring... 这意味着如果 wstring 包含可能恰好是 0000 的 wchar,我们必须将 2 个字符串字符 0000 放在一起才能得到它。那是我不知道该怎么做。

更新2 我是如何来到这里的——我有义务在我的服务器上使用的 C++ 库是 C 风格的解析器。它以 std::string 的形式返回我的用户请求地址。而我让我的客户以这种格式向我发送请求。

url_encode(UTF16toUTF8(wstring)) //pseudocode.

在哪里

string UTF16toUTF8(const wstring & in)
{
    string out;
    unsigned int codepoint;
    bool completecode = false;
    for (wstring::const_iterator p = in.begin();  p != in.end();  ++p)
    {
        if (*p >= 0xd800 && *p <= 0xdbff)
        {
            codepoint = ((*p - 0xd800) << 10) + 0x10000;
            completecode = false;
        }
        else if (!completecode && *p >= 0xdc00 && *p <= 0xdfff)
        {
            codepoint |= *p - 0xdc00;
            completecode = true;
        }
        else
        {
            codepoint = *p;
            completecode = true;
        }
        if (completecode)
        {
            if (codepoint <= 0x7f)
                out.push_back(codepoint);
            else if (codepoint <= 0x7ff)
            {
                out.push_back(0xc0 | ((codepoint >> 6) & 0x1f));
                out.push_back(0x80 | (codepoint & 0x3f));
            }
            else if (codepoint <= 0xffff)
            {
                out.push_back(0xe0 | ((codepoint >> 12) & 0x0f));
                out.push_back(0x80 | ((codepoint >> 6) & 0x3f));
                out.push_back(0x80 | (codepoint & 0x3f));
            }
            else
            {
                out.push_back(0xf0 | ((codepoint >> 18) & 0x07));
                out.push_back(0x80 | ((codepoint >> 12) & 0x3f));
                out.push_back(0x80 | ((codepoint >> 6) & 0x3f));
                out.push_back(0x80 | (codepoint & 0x3f));
            }
        }
    }
    return out;
}

std::string url_encode( std::string sSrc )
{
    const char SAFE[256] =
    {
        /*      0 1 2 3  4 5 6 7  8 9 A B  C D E F */
        /* 0 */ 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
        /* 1 */ 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
        /* 2 */ 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
        /* 3 */ 1,1,1,1, 1,1,1,1, 1,1,0,0, 0,0,0,0,

        /* 4 */ 0,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,1,
        /* 5 */ 1,1,1,1, 1,1,1,1, 1,1,1,0, 0,0,0,0,
        /* 6 */ 0,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,1,
        /* 7 */ 1,1,1,1, 1,1,1,1, 1,1,1,0, 0,0,0,0,

        /* 8 */ 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
        /* 9 */ 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
        /* A */ 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
        /* B */ 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,

        /* C */ 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
        /* D */ 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
        /* E */ 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
        /* F */ 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0
    };
    const char DEC2HEX[16 + 1] = "0123456789ABCDEF";
    const unsigned char * pSrc = (const unsigned char *)sSrc.c_str();
    const int SRC_LEN = sSrc.length();
    unsigned char * const pStart = new unsigned char[SRC_LEN * 3];
    unsigned char * pEnd = pStart;
    const unsigned char * const SRC_END = pSrc + SRC_LEN;

    for (; pSrc < SRC_END; ++pSrc)
    {
        if (SAFE[*pSrc]) 
            *pEnd++ = *pSrc;
        else
        {
            // escape this char
            *pEnd++ = '%';
            *pEnd++ = DEC2HEX[*pSrc >> 4];
            *pEnd++ = DEC2HEX[*pSrc & 0x0F];
        }
    }

    std::string sResult((char *)pStart, (char *)pEnd);
    delete [] pStart;
    return sResult;
}

std::string url_decode( std::string sSrc )
{
    // Note from RFC1630:  "Sequences which start with a percent sign
    // but are not followed by two hexadecimal characters (0-9, A-F) are reserved
    // for future extension"

    const char HEX2DEC[256] = 
    {
        /*       0  1  2  3   4  5  6  7   8  9  A  B   C  D  E  F */
        /* 0 */ -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
        /* 1 */ -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
        /* 2 */ -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
        /* 3 */  0, 1, 2, 3,  4, 5, 6, 7,  8, 9,-1,-1, -1,-1,-1,-1,

        /* 4 */ -1,10,11,12, 13,14,15,-1, -1,-1,-1,-1, -1,-1,-1,-1,
        /* 5 */ -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
        /* 6 */ -1,10,11,12, 13,14,15,-1, -1,-1,-1,-1, -1,-1,-1,-1,
        /* 7 */ -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,

        /* 8 */ -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
        /* 9 */ -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
        /* A */ -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
        /* B */ -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,

        /* C */ -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
        /* D */ -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
        /* E */ -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
        /* F */ -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1
    };

    const unsigned char * pSrc = (const unsigned char *)sSrc.c_str();
    const int SRC_LEN = sSrc.length();
    const unsigned char * const SRC_END = pSrc + SRC_LEN;
    const unsigned char * const SRC_LAST_DEC = SRC_END - 2;   // last decodable '%' 

    char * const pStart = new char[SRC_LEN];
    char * pEnd = pStart;

    while (pSrc < SRC_LAST_DEC)
    {
        if (*pSrc == '%')
        {
            char dec1, dec2;
            if (-1 != (dec1 = HEX2DEC[*(pSrc + 1)])
                && -1 != (dec2 = HEX2DEC[*(pSrc + 2)]))
            {
                *pEnd++ = (dec1 << 4) + dec2;
                pSrc += 3;
                continue;
            }
        }

        *pEnd++ = *pSrc++;
    }

    // the last 2- chars
    while (pSrc < SRC_END)
        *pEnd++ = *pSrc++;

    std::string sResult(pStart, pEnd);
    delete [] pStart;
    return sResult;
}

当然我调用 url_decode,但我得到一个字符串..( 所以我希望现在我的问题更清楚了。

【问题讨论】:

  • @Kabumbus:在你的字符串中是高字节还是低字节?如果您的字符串中有12 34,您希望在您的字符串中获得12343412
  • @Kalumbus:我现在完全糊涂了。我又改变了主意,认为您真的想将 UTF-8 转换为 UTF-16,而不仅仅是进行一些字节移位。但谁知道呢。如果您确实需要 UTF-8 到 UTF-16,Nicol Bolas 上面发布的链接将起作用。
  • -1 真的不清楚问了什么。
  • @Kalumbus,您能否发布一个使用字节和单词值而不是使用非常令人困惑的外来字符的示例。

标签: c++ string encoding std wstring


【解决方案1】:

为了解决您的问题,我正在修改以下内容:

std::string wrong("Новая папка");
std::wstring correct( (wchar_t*)wrong.data() );

根据http://www.cplusplus.com/reference/string/string/data/,data() 成员函数应该为我们提供原始 char* 并且简单地转换为 (wchar_t*) 应该会导致它将 00 和 00 粘在一起形成 0000,正如您在示例中所描述的那样.

我个人不喜欢这样选角,但到目前为止我想出的就是这些。

编辑 - 你用的是哪个库?它是否带有其他一些功能来扭转它所做的事情?

如果它很受欢迎,肯定有人以前遇到过这个问题。他们是怎么解决的?

编辑 2 - 这是一种令人作呕的方式,使用 malloc,一些假设原始字符串中不会有任何一半的代码点,以及另一个可怕的演员表。 :(

std::string wrong("Новая папка");
wchar_t *lesswrong = (wchar_t*) malloc (wrong.size()/sizeof(wchar_t) + sizeof(wchar_t));
lesswrong = (wchar_t*)wrong.data();
lesswrong[wrong.size()] = '\0';
std::wstring correct( lesswrong );

这不可能是正确的。就算能用也太丑了。

编辑 3 - 和 Kerrick sadi 一样,这是一种更好的方法。

std::string wrong("Новая папка");
std::wstring correct( (wchar_t*)wrong.data(), wrong.size()/2 );

【讨论】:

  • 您可能还需要在此处传递大小参数,因为data() 不提供 (wchar-)null 终止的字符串。
  • 如果sizeof(wchar_t) 不等于 2,这将不起作用。在 Mac OS X 上,sizeof(wchar_t) 对于 64 位程序为 4。
  • 只有在字节顺序正确的情况下才能工作,而且您缺少 Kerrek 提到的空终止符。
  • 这假设数据是在这台机器上编码的,所以字节顺序应该匹配,但你是对的,在一台 unix 机器上,这会将 2 个 utf16 代码点填充到一个 utf32 代码点中。我做了另一个假设,假设他使用的库将使用与此代码相同的 wchar_t 大小(我承认这似乎是一个延伸)。
  • @Sqeaky : std::wstring correct( (wchar_t*)wrong.data(), wrong.size() ); 绝对不正确。构造函数需要字符数,但wrong.size() 给出的是字节数。
【解决方案2】:

如果我理解正确,您有一个包含UTF-16 编码字符串的std::string 对象,并且您希望在不更改编码的情况下将其转换为std::wstring。如果我是正确的,那么,您不必进行编码转换,也不必转换表示,而只需转换存储。

您还认为该字符串可能被错误地编码为UTF-8。但是,UTF-8 是可变长度编码,但您错误解释数据的长度(Новая папка 长度为 22 个字符)正好是原始数据长度的两倍(Новая папка 长度为 11 个字符)。这就是为什么我怀疑这可能只是存储错误而不是编码错误的情况。

下面的代码就是这样做的:

std::wstring convert_utf16_string_to_wstring(const std::string& input) {
    assert((input.size() & 1) == 0);
    size_t len = input.size() / 2;
    std::wstring output;
    output.resize(len);

    for (size_t i = 0; i < len; ++i) {
        unsigned char chr1 = (unsigned char)input[2 * i];
        unsigned char chr2 = (unsigned char)input[2 * i + 1];

        // Note: this line suppose that you use `UTF-16-BE` both for
        // the std::string and the std::wstring. You'll have to swap
        // chr1 & chr2 if this is not the case.
        unsigned short val = (chr2 << 8)|(chr1);
        output[i] = (wchar_t)(val);
    }

    return output;
}

如果您知道在所有平台上您的目标 sizeof(wchar_t) 等于 2(这不是一个用于 64 位程序的 Mac OS 例如sizeof(wchar_t) 等于 4),那么您可以使用简单的转换:

std::wstring convert_utf16_string_to_wstring(const std::string& input) {
    assert(sizeof(wchar_t) == 2); // A static assert would be better here
    assert((input.size() & 1) == 0);
    return input.empty()
        ? std::wstring()
        : std::wstring((wchar_t*)input[0], input.size() / 2);
}

【讨论】:

  • 这不会从 UTF-8 转换为 UTF-16,它只是将 UTF-16 的字节表示转换为字表示。
  • 好吧,我得到了⿯뾍껯뾢ꃯ뾿⃯뾯ꃯ뾯ꫯ뾠,这更像是真正的字母,但有些地方出了问题..(
  • @bdonlan:我完全不清楚 OP 是否需要 UTF-8 到 UTF-16 的转换。我已经两次改变主意了。
  • @bdonlan:此 OP 不希望从 UTF-16 转换为 UTF-8。他有一个std::string,其中包含在UTF-16 中编码的数据,他希望将这些数据存储在std::wstring 中。
  • @Kabumbus:抱歉,我将chr1chr2 倒置在其中一行中。我已经用我期望的正确版本编辑了我的帖子。顺便说一句,如果sizeof(wchar_t) == 2 那么你可能只做一个memcpy(&amp;output[0], &amp;input[0], input.size()) 而不是循环。
猜你喜欢
  • 2011-11-01
  • 2013-09-26
  • 2012-06-20
  • 2019-03-13
  • 2012-06-30
  • 1970-01-01
  • 2015-09-21
  • 1970-01-01
相关资源
最近更新 更多