有没有一种方法可以将UTF8转换为ISO-8859-1?

13

我的软件得到了一些UTF8编码的字符串,我需要将其转换为ISO 8859 1。我知道UTF8域比ISO 8859更大。但是UTF8中的数据先前已经从ISO进行了上转换,所以我不应该遗漏任何内容。

我想知道是否有一种简单/直接的方法可以从UTF8转换为iso-8859-1。


1
如果您正在使用一个完成了转换的库,它也应该有一些东西可以将其转换回来。假设您没有更改字符串中的任何字符,那么只需将其返回即可。 - RedX
3个回答

16

以下是一个有用的函数:utf8_to_latin9()。它将转换为ISO-8859-15(包括欧元符号,但ISO-8859-1没有),但也可以正确处理ISO-8859-1->UTF-8->ISO-8859-1往返转换中的UTF-8->ISO-8859-1转换部分。

该函数忽略无效的代码点,类似于iconv的//IGNORE标志,但不会重新组合分解的UTF-8序列;也就是说,它不会将U+006E U+0303转换成U+00F1。我没有重新组合,因为iconv也没有这样做。

该函数非常注意字符串的访问。它绝不会扫描超出缓冲区。输出缓冲区必须比长度多一个字节,因为它总是附加字符串结束的NUL字节。该函数返回输出中字符(字节)的数量,不包括字符串结束的NUL字节。

/* UTF-8 to ISO-8859-1/ISO-8859-15 mapper.
 * Return 0..255 for valid ISO-8859-15 code points, 256 otherwise.
*/
static inline unsigned int to_latin9(const unsigned int code)
{
    /* Code points 0 to U+00FF are the same in both. */
    if (code < 256U)
        return code;
    switch (code) {
    case 0x0152U: return 188U; /* U+0152 = 0xBC: OE ligature */
    case 0x0153U: return 189U; /* U+0153 = 0xBD: oe ligature */
    case 0x0160U: return 166U; /* U+0160 = 0xA6: S with caron */
    case 0x0161U: return 168U; /* U+0161 = 0xA8: s with caron */
    case 0x0178U: return 190U; /* U+0178 = 0xBE: Y with diaresis */
    case 0x017DU: return 180U; /* U+017D = 0xB4: Z with caron */
    case 0x017EU: return 184U; /* U+017E = 0xB8: z with caron */
    case 0x20ACU: return 164U; /* U+20AC = 0xA4: Euro */
    default:      return 256U;
    }
}

/* Convert an UTF-8 string to ISO-8859-15.
 * All invalid sequences are ignored.
 * Note: output == input is allowed,
 * but   input < output < input + length
 * is not.
 * Output has to have room for (length+1) chars, including the trailing NUL byte.
*/
size_t utf8_to_latin9(char *const output, const char *const input, const size_t length)
{
    unsigned char             *out = (unsigned char *)output;
    const unsigned char       *in  = (const unsigned char *)input;
    const unsigned char *const end = (const unsigned char *)input + length;
    unsigned int               c;

    while (in < end)
        if (*in < 128)
            *(out++) = *(in++); /* Valid codepoint */
        else
        if (*in < 192)
            in++;               /* 10000000 .. 10111111 are invalid */
        else
        if (*in < 224) {        /* 110xxxxx 10xxxxxx */
            if (in + 1 >= end)
                break;
            if ((in[1] & 192U) == 128U) {
                c = to_latin9( (((unsigned int)(in[0] & 0x1FU)) << 6U)
                             |  ((unsigned int)(in[1] & 0x3FU)) );
                if (c < 256)
                    *(out++) = c;
            }
            in += 2;

        } else
        if (*in < 240) {        /* 1110xxxx 10xxxxxx 10xxxxxx */
            if (in + 2 >= end)
                break;
            if ((in[1] & 192U) == 128U &&
                (in[2] & 192U) == 128U) {
                c = to_latin9( (((unsigned int)(in[0] & 0x0FU)) << 12U)
                             | (((unsigned int)(in[1] & 0x3FU)) << 6U)
                             |  ((unsigned int)(in[2] & 0x3FU)) );
                if (c < 256)
                    *(out++) = c;
            }
            in += 3;

        } else
        if (*in < 248) {        /* 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx */
            if (in + 3 >= end)
                break;
            if ((in[1] & 192U) == 128U &&
                (in[2] & 192U) == 128U &&
                (in[3] & 192U) == 128U) {
                c = to_latin9( (((unsigned int)(in[0] & 0x07U)) << 18U)
                             | (((unsigned int)(in[1] & 0x3FU)) << 12U)
                             | (((unsigned int)(in[2] & 0x3FU)) << 6U)
                             |  ((unsigned int)(in[3] & 0x3FU)) );
                if (c < 256)
                    *(out++) = c;
            }
            in += 4;

        } else
        if (*in < 252) {        /* 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx */
            if (in + 4 >= end)
                break;
            if ((in[1] & 192U) == 128U &&
                (in[2] & 192U) == 128U &&
                (in[3] & 192U) == 128U &&
                (in[4] & 192U) == 128U) {
                c = to_latin9( (((unsigned int)(in[0] & 0x03U)) << 24U)
                             | (((unsigned int)(in[1] & 0x3FU)) << 18U)
                             | (((unsigned int)(in[2] & 0x3FU)) << 12U)
                             | (((unsigned int)(in[3] & 0x3FU)) << 6U)
                             |  ((unsigned int)(in[4] & 0x3FU)) );
                if (c < 256)
                    *(out++) = c;
            }
            in += 5;

        } else
        if (*in < 254) {        /* 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx */
            if (in + 5 >= end)
                break;
            if ((in[1] & 192U) == 128U &&
                (in[2] & 192U) == 128U &&
                (in[3] & 192U) == 128U &&
                (in[4] & 192U) == 128U &&
                (in[5] & 192U) == 128U) {
                c = to_latin9( (((unsigned int)(in[0] & 0x01U)) << 30U)
                             | (((unsigned int)(in[1] & 0x3FU)) << 24U)
                             | (((unsigned int)(in[2] & 0x3FU)) << 18U)
                             | (((unsigned int)(in[3] & 0x3FU)) << 12U)
                             | (((unsigned int)(in[4] & 0x3FU)) << 6U)
                             |  ((unsigned int)(in[5] & 0x3FU)) );
                if (c < 256)
                    *(out++) = c;
            }
            in += 6;

        } else
            in++;               /* 11111110 and 11111111 are invalid */

    /* Terminate the output string. */
    *out = '\0';

    return (size_t)(out - (unsigned char *)output);
}
请注意,在to_latin9()函数中,您可以为特定代码点添加自定义音译,但只能使用单个字符替换。
目前编写的函数可以安全地进行原位转换:输入和输出指针可以相同。输出字符串永远不会比输入字符串更长。如果您的输入字符串有一个额外的字节空间(例如,它具有终止符NUL),则可以安全地使用上述函数将其从UTF-8转换为ISO-8859-1/15。我故意这样编写它,因为它在嵌入式环境中应该会节省一些工作量,尽管这种方法在定制和扩展方面有点受限。
编辑:
我在此答案的编辑中包含了一对转换函数,用于Latin-1/9到/from UTF-8的转换(ISO-8859-1或-15到/from UTF-8);主要区别是那些函数返回一个动态分配的副本,并保留原始字符串不变。

15

iconv - 执行字符集转换

size_t iconv(iconv_t cd, char **inbuf, size_t *inbytesleft, char **outbuf, size_t *outbytesleft);

iconv_t iconv_open(const char *tocode, const char *fromcode);

tocode"ISO_8859-1",而 fromcode"UTF-8"

工作示例:

#include <iconv.h>
#include <stdio.h>

int main (void) {
    iconv_t cd = iconv_open("ISO_8859-1", "UTF-8");
    if (cd == (iconv_t) -1) {
        perror("iconv_open failed!");
        return 1;
    }

    char input[] = "Test äöü";
    char *in_buf = &input[0];
    size_t in_left = sizeof(input) - 1;

    char output[32];
    char *out_buf = &output[0];
    size_t out_left = sizeof(output) - 1;

    do {
        if (iconv(cd, &in_buf, &in_left, &out_buf, &out_left) == (size_t) -1) {
            perror("iconv failed!");
            return 1;
        }
    } while (in_left > 0 && out_left > 0);
    *out_buf = 0;

    iconv_close(cd);

    printf("%s -> %s\n", input, output);
    return 0;
}

1
谢谢,我遇到的主要问题是我忘了说明我的软件运行在嵌入式Linux上,而iconv不可用。 - fazineroso
你可以为你的Linux编译iconv。你的Linux是否使用glibc?如果是,它有一个兼容的实现称为gconv:http://www.gnu.org/software/libc/manual/html_node/glibc-iconv-Implementation.html - J-16 SDiZ
1
谢谢分享。您应该调用iconv_close()以释放由iconv_open()分配的资源。只需要在printf("%s -> %s\n", input, output);之前放置iconv_close(cd);即可。 - silvioprog
@silvioprog,感谢您的评论!我已经添加了这行代码。 - Kijewski

0
以下示例还使用了iconv库。 即使您有一个包含混合UTF-8和ISO-8859-1字符的文件(例如,如果您有一个UTF-8文件并在使用ISO-8859-1的环境中编辑它),它也可以正常工作。
int Utf8ToLatin1(char* input, char* output, size_t size)
{
    size_t in_left = size;
    size_t out_left = size;

    char *in_buf    = input;
    char *out_buf   = output;

    iconv_t cd = iconv_open("ISO_8859-1", "UTF-8");
    if (cd == (iconv_t)-1) {
        (void) fprintf(stderr, "iconv_open() failed, msg encoding will be kept!");
        strncpy(output, input, size);
        return -1;
    }

    do {
        if (iconv(cd, &in_buf, &in_left, &out_buf, &out_left) == (size_t) -1) {
            if (errno == EILSEQ) {
                /* Input conversion stopped due to an input byte that
                 * does not belong to the input codeset.
                 */
                printf("Input conversion stopped due to an input byte that does not belong to the input codeset.\n");
                *out_buf= *in_buf;
                out_buf++   ;out_left--;
                in_buf++    ;in_left--;

            } else if (errno == E2BIG) {
                /* Input conversion stopped due to lack of space in
                 * the output buffer.
                 */
                printf("Input conversion stopped due to lack of space in the output buffer.\n");
                perror("iconv failed!, propably the encoding is already Latin, msg encoding will be kept!\n");
                strncpy(output, input, size);
                return -1;
            } else if (errno == EINVAL) {
                /* Input conversion stopped due to an incomplete
                 * character or shift sequence at the end of the
                 * input buffer.
                 */
                printf("Input conversion stopped due to an incomplete character or shift sequence at the end of the input buffer.\n");
                *out_buf= *in_buf;
                out_buf++   ;out_left--;
                in_buf++    ;in_left--;
            }

        }
    } while (in_left > 0 && out_left > 0);
    *out_buf = 0;

    iconv_close(cd);

    printf("*********************************************************\n");
    printf("ISO-8859-1:\n %s\n", input, output);
    return 0;

}

请不要使用strncpy(output, input, size);如果输出缓冲区有size个字节或更多,则输出将不会以空字符结尾。此外,最后的*out_buf = 0;可能会写入目标数组的末尾之外。 - chqrlie
此外,OP在评论中写道,目标系统上不可用iconv,但限制可能在8年时间内改变了。 - chqrlie

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