在C/C++中将ISO-8859-1字符串转换为UTF-8

25
你或许会认为这很容易获得,但我很难找到一个简单的库函数,可以将C或C++字符串从ISO-8859-1编码转换为UTF-8。 我正在读取以8位ISO-8859-1编码的数据,但需要将其转换为UTF-8字符串以在SQLite数据库和最终的Android应用程序中使用。
我找到了一个商业产品,但目前超出我的预算范围。

4
没什么简单的。您可以使用开源的 ICU 库。 - Hans Passant
3
如果必须这样做,那么最简单的代码就是预先生成一个表格,包含128个(左右)UTF-8字符,它们对应于置顶位设置的8859-1字符。另外128个8859-1字符则不作修改。这样一来,你的代码完全不需要理解Unicode。同时,要注意ISO-8859-1和Windows CP-1252之间的区别。后者在8859-1留下了空隙(未使用的编码点),并添加了一些额外的字符。除非你需要验证输入确实是ISO-8859-1,否则没有必要拒绝接受CP-1252,因为你肯定会遇到误标记的情况。 - Steve Jessop
@Steve:由于UTF-8是可变长度的(在这种情况下,每个字符为1或2个字节),因此查找表不太容易使用。请参见我的答案,它应该同样快且更简单。 - R.. GitHub STOP HELPING ICE
@R.:嗯,“易”是一个相对的术语。stpcpy会有所帮助,前提是你是那种擅长缓冲区大小的程序员。 - Steve Jessop
stpcpy(即使它现在是标准或正在朝着标准的方向发展...?)对于1字节和2字节的复制来说,开销非常大。你最好手动始终复制2个字节,并包含一些代码以跳过第二个指针的推进,如果复制的字节为0,则几乎可以肯定是无分支的。 - R.. GitHub STOP HELPING ICE
7个回答

40

如果你的源编码将始终为ISO-8859-1,那么这很简单。下面是一个循环:

unsigned char *in, *out;
while (*in)
    if (*in<128) *out++=*in++;
    else *out++=0xc2+(*in>0xbf), *out++=(*in++&0x3f)+0x80;

为了安全起见,您需要确保输出缓冲区的大小是输入缓冲区的两倍,否则在循环条件中包含大小限制并检查它。


3
哇,这非常有帮助!我不想再去查找另一个表格查找算法了。现在我们需要将ANSEL转换为UTF-8... - gordonwd
11
这确实回答了问题。但正如我在上面的评论中所说,人们会将CP-1252错误地标记为ISO-8859-1发送给你。 Web服务器是我遇到的例子,这也使我认识到了这个问题,还有一些声称保存为“Latin-1”的文本编辑器,实际上并不是。如果您的源编码始终为ISO-8859-1,那么这是一个相当大的“如果”,可能很难追踪和消除罪犯的责任。 - Steve Jessop
2
@gordon:我不熟悉ANSEL,但你应该知道ISO-8859-1是唯一一个这么容易转换为UTF-8的遗留编码。其他所有编码都需要查找表。正如Steve所说,我的“如果……”是一个很大的假设。 - R.. GitHub STOP HELPING ICE
7
从可维护性的角度来看,这段代码写得相当糟糕。请使用更多的大括号。 - syb0rg
2
@Nick:是的,我指的是0xA0,只是我在脑子里转换成十进制时出现了错误。不过这个评论太旧了,已经无法编辑了。 - R.. GitHub STOP HELPING ICE
显示剩余5条评论

18

我使用以下代码来编写C++:

std::string iso_8859_1_to_utf8(std::string &str)
{
    string strOut;
    for (std::string::iterator it = str.begin(); it != str.end(); ++it)
    {
        uint8_t ch = *it;
        if (ch < 0x80) {
            strOut.push_back(ch);
        }
        else {
            strOut.push_back(0xc0 | ch >> 6);
            strOut.push_back(0x80 | (ch & 0x3f));
        }
    }
    return strOut;
}

请问您能分享一下Latin7版本吗? - Ronalds Mazītis
@RonaldsMazītis 由于Latin7与Unicode没有一对一的映射关系,所以需要使用转换查找表来进行转换,这并不是一件简单的事情。 - Ale

5

3

C++03标准没有直接转换特定字符集的函数。

根据您的操作系统,您可以在Linux上使用iconv(),在Windows上使用MultiByteToWideChar()等方法。 提供大量字符串转换支持的库是开源的ICU库。


C++标准没有提供直接在字符集之间进行转换的函数。 - Cheers and hth. - Alf

2
Unicode专家提供了一些表格,如果面对的是Windows 1252而不是真正的ISO-8859-1,则这些表格可能会有所帮助。其中最权威的似乎是这个,它将CP1252中的每个码点映射到Unicode中的一个码点。将Unicode编码为UTF-8是一项简单的练习。
直接解析该表并在编译时形成查找表并不困难。

-1

代码

isolat1ToUTF8(unsigned char* out, int *outlen,
              const unsigned char* in, int *inlen) {
    unsigned char* outstart = out;
    const unsigned char* base = in;
    const unsigned char* processed = in;
    unsigned char* outend = out + *outlen;
    const unsigned char* inend;
    unsigned int c;
    int bits;

    inend = in + (*inlen);
    while ((in < inend) && (out - outstart + 5 < *outlen)) {
    c= *in++;

    /* assertion: c is a single UTF-4 value */
        if (out >= outend)
        break;
        if      (c <    0x80) {  *out++=  c;                bits= -6; }
        else                  {  *out++= ((c >>  6) & 0x1F) | 0xC0;  bits=  0; }
 
        for ( ; bits >= 0; bits-= 6) {
            if (out >= outend)
            break;
            *out++= ((c >> bits) & 0x3F) | 0x80;
        }
    processed = (const unsigned char*) in;
    }
    *outlen = out - outstart;
    *inlen = processed - base;
    return(0);
}

我觉得这可能会有帮助!对于我上次被删除的评论,我很抱歉!如果需要的话,我可以给你链接,里面有一个.c文件中的完整解释。我从中得到了这个。干杯!


将代码图像的链接作为Stackoverflow答案不符合标准。链接可能会失效,而且无法直接复制图像中的代码。 - Andrew Henle
1
你好,欢迎来到Stack Overflow!请注意,这里的代码应该以格式化的源代码文本形式呈现,而不是图像。请阅读网站的帮助部分以获取更多信息!此外,这个问题已经超过12年了,虽然写最新答案回答旧问题很好,但你的答案似乎包含了与此问题下某些答案中已有的代码非常相似的代码。 - hyde
@AndrewHenle 你好!这样更好吗? - o0Evolved0o
虽然这个链接可能回答了问题,但最好在此处包含答案的基本部分并提供参考链接。如果链接页面更改,仅链接的答案可能会失效。- [来自审查] (https://stackoverflow.com/review/late-answers/32324127) - Nol4635

-2

将ISO-8859-1转换为UTF-8只涉及编码算法,因为ISO-8859-1是Unicode的子集。所以您已经有了Unicode代码点。请查看维基百科上的算法。

C++方面——将其与iostreams集成——要困难得多。

我建议您绕过这座山,而不是试图钻过它或攀登它,也就是实现一个简单的字符串转换器。

祝好运。


算法并不完全平凡,特别是对于初学者和中级 C 程序员来说,经常会错误地使用 char *,而需要使用 unsigned char * 的情况。更重要的非平凡性在于 UTF-8 的定义,具体来说,您需要拒绝代理码点和超出范围的值。幸运的是,在只需要处理 ISO-8859-1 输入的编码器中不会出现这些问题,但如果您编写了这样一个有限的编码器,很可能会有人在稍后扩展输入范围时误用它而没有添加任何检查。 - R.. GitHub STOP HELPING ICE
@MichałLeon:Unicode 不是一种编码方式。Unicode 有许多不同的编码方式,包括 UTF-8 和 UTF-16。Unicode 的前 256 个代码点与 Latin 1(又称 ISO-8859-1)相同。注意:强调并不能使你与微不足道的事实更加符合。下次,与其大声喊叫和投反对票,不如考虑简单地核实事实,或者询问任何你不理解的内容。 - Cheers and hth. - Alf
@Martin:Unicode代码点128到255的块被称为Unicode的"Latin-1 supplement",因为它与Latin-1相同。Unicode是Latin-1的直接扩展。你的评论荒谬无稽,这种技术词汇可能会影响非技术人员,并表明你在恶意挑衅。我认为你在恶意挑衅。 - Cheers and hth. - Alf
@MichałLeon:好的,抱歉。我或许应该猜到:我多年来一直帮助一个视力极差的学生,她经常看不到那里明明有的东西。Latin-1 在 OP 的帖子、我的回答、所有我的评论以及除了一个之外的其他回答中都已经指定了。 - Cheers and hth. - Alf

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接