Преобразование из ISO-8859-15 (Latin9) в UTF-8?

Мне нужно преобразовать некоторые строки, отформатированные в кодировке Latin9, в UTF-8. Я не могу использовать iconv, поскольку он не включен в мою встроенную систему. Знаете ли вы, есть ли какой-то код для этого?

2 ответа

Кодовые точки 1 в 127 одинаковы как в Latin-9 (ISO-8859-15), так и в UTF-8.

Кодовая точка 164 на латыни-9 это U+20AC, \xe2\x82\xac = 226 130 172 в UTF-8.
Кодовая точка 166 на латыни-9 это U+0160, \xc5\xa0 = 197 160 в UTF-8.
Кодовая точка 168 на латыни-9 это U+0161, \xc5\xa1 = 197 161 в UTF-8.
Кодовая точка 180 на латыни-9 это U+017D, \xc5\xbd = 197 189 в UTF-8.
Кодовая точка 184 на латыни-9 это U+017E, \xc5\xbe = 197 190 в UTF-8.
Кодовая точка 188 на латыни-9 это U+0152, \xc5\x92 = 197 146 в UTF-8.
Кодовая точка 189 на латыни-9 это U+0153, \xc5\x93 = 197 147 в UTF-8.
Кодовая точка 190 на латыни-9 это U+0178, \xc5\xb8 = 197 184 в UTF-8.

Кодовые точки 128 .. 191 (кроме перечисленных выше) в латинице-9 все сопоставляются с \xc2\x80 .. \xc2\xbf = 194 128 .. 194 191 в UTF-8.

Кодовые точки 192 .. 255 на латинице-9 все отображаются в \xc3\x80 .. \xc3\xbf = 195 128 .. 195 191 в UTF-8.

Это означает, что кодовые точки Latin-9 1..127 имеют длину в один байт в UTF-8, кодовая точка 164 имеет длину три байта, а остальные (128..163 и 165..255) имеют длину два байта.

Если вы сначала просканируете входную строку Latin-9, вы можете определить длину результирующей строки UTF-8. Если вы хотите или должны - в конце концов, вы работаете над встроенной системой - вы можете выполнить преобразование на месте, работая в обратном направлении от конца к началу.

Редактировать:

Вот две функции, которые вы можете использовать для преобразования в любом случае. Они возвращают динамически выделенную копию, которая вам нужна free() после использования. Они только возвращаются NULL когда происходит ошибка (нехватка памяти, errno == ENOMEM). Если дано NULL или пустая строка для преобразования, функции возвращают пустую динамически размещаемую строку.

Другими словами, вы всегда должны звонить free() на указатель, возвращаемый этими функциями, когда вы закончите с ними. (free(NULL) разрешено и ничего не делает.)

latin9_to_utf8() было проверено, чтобы произвести точно такой же результат, как iconv если вход не содержит нулевых байтов. Функция использует стандартные строки C, т.е. нулевой байт указывает на конец строки.

utf8_to_latin9() было проверено, чтобы произвести точно такой же результат, как iconv если вход содержит только кодовые точки Unicode также в ISO-8859-15, и не содержит нулевых байтов. Когда заданы случайные строки UTF-8, функция отображает восемь кодовых точек в латинском-1 в латинские-9 эквивалентов, то есть знак валюты в евро; iconv либо игнорирует их, либо учитывает эти ошибки.

utf8_to_latin9() поведение означает, что функции подходят как дляLatin 1->UTF-8->Latin 1 а также Latin 9->UTF-8->Latin9 круглые поездки.

#include <stdlib.h>     /* for realloc() and free() */
#include <string.h>     /* for memset() */
#include <errno.h>      /* for errno */

/* Create a dynamically allocated copy of string,
 * changing the encoding from ISO-8859-15 to UTF-8.
*/
char *latin9_to_utf8(const char *const string)
{
    char   *result;
    size_t  n = 0;

    if (string) {
        const unsigned char  *s = (const unsigned char *)string;

        while (*s)
            if (*s < 128) {
                s++;
                n += 1;
            } else
            if (*s == 164) {
                s++;
                n += 3;
            } else {
                s++;
                n += 2;
            }
    }

    /* Allocate n+1 (to n+7) bytes for the converted string. */
    result = malloc((n | 7) + 1);
    if (!result) {
        errno = ENOMEM;
        return NULL;
    }

    /* Clear the tail of the string, setting the trailing NUL. */
    memset(result + (n | 7) - 7, 0, 8);

    if (n) {
        const unsigned char  *s = (const unsigned char *)string;
        unsigned char        *d = (unsigned char *)result;

        while (*s)
            if (*s < 128) {
                *(d++) = *(s++);
            } else
            if (*s < 192) switch (*s) {
                case 164: *(d++) = 226; *(d++) = 130; *(d++) = 172; s++; break;
                case 166: *(d++) = 197; *(d++) = 160; s++; break;
                case 168: *(d++) = 197; *(d++) = 161; s++; break;
                case 180: *(d++) = 197; *(d++) = 189; s++; break;
                case 184: *(d++) = 197; *(d++) = 190; s++; break;
                case 188: *(d++) = 197; *(d++) = 146; s++; break;
                case 189: *(d++) = 197; *(d++) = 147; s++; break;
                case 190: *(d++) = 197; *(d++) = 184; s++; break;
                default:  *(d++) = 194; *(d++) = *(s++); break;
            } else {
                *(d++) = 195;
                *(d++) = *(s++) - 64;
            }
    }

    /* Done. Remember to free() the resulting string when no longer needed. */
    return result;
}

/* Create a dynamically allocated copy of string,
 * changing the encoding from UTF-8 to ISO-8859-15.
 * Unsupported code points are ignored.
*/
char *utf8_to_latin9(const char *const string)
{
    size_t         size = 0;
    size_t         used = 0;
    unsigned char *result = NULL;

    if (string) {
        const unsigned char  *s = (const unsigned char *)string;

        while (*s) {

            if (used >= size) {
                void *const old = result;

                size = (used | 255) + 257;
                result = realloc(result, size);
                if (!result) {
                    if (old)
                        free(old);
                    errno = ENOMEM;
                    return NULL;
                }
            }

            if (*s < 128) {
                result[used++] = *(s++);
                continue;

            } else
            if (s[0] == 226 && s[1] == 130 && s[2] == 172) {
                result[used++] = 164;
                s += 3;
                continue;

            } else
            if (s[0] == 194 && s[1] >= 128 && s[1] <= 191) {
                result[used++] = s[1];
                s += 2;
                continue;

            } else
            if (s[0] == 195 && s[1] >= 128 && s[1] <= 191) {
                result[used++] = s[1] + 64;
                s += 2;
                continue;

            } else
            if (s[0] == 197 && s[1] == 160) {
                result[used++] = 166;
                s += 2;
                continue;

            } else
            if (s[0] == 197 && s[1] == 161) {
                result[used++] = 168;
                s += 2;
                continue;

            } else
            if (s[0] == 197 && s[1] == 189) {
                result[used++] = 180;
                s += 2;
                continue;

            } else
            if (s[0] == 197 && s[1] == 190) {
                result[used++] = 184;
                s += 2;
                continue;

            } else
            if (s[0] == 197 && s[1] == 146) {
                result[used++] = 188;
                s += 2;
                continue;

            } else
            if (s[0] == 197 && s[1] == 147) {
                result[used++] = 189;
                s += 2;
                continue;

            } else
            if (s[0] == 197 && s[1] == 184) {
                result[used++] = 190;
                s += 2;
                continue;

            }

            if (s[0] >= 192 && s[0] < 224 &&
                s[1] >= 128 && s[1] < 192) {
                s += 2;
                continue;
            } else
            if (s[0] >= 224 && s[0] < 240 &&
                s[1] >= 128 && s[1] < 192 &&
                s[2] >= 128 && s[2] < 192) {
                s += 3;
                continue;
            } else
            if (s[0] >= 240 && s[0] < 248 &&
                s[1] >= 128 && s[1] < 192 &&
                s[2] >= 128 && s[2] < 192 &&
                s[3] >= 128 && s[3] < 192) {
                s += 4;
                continue;
            } else
            if (s[0] >= 248 && s[0] < 252 &&
                s[1] >= 128 && s[1] < 192 &&
                s[2] >= 128 && s[2] < 192 &&
                s[3] >= 128 && s[3] < 192 &&
                s[4] >= 128 && s[4] < 192) {
                s += 5;
                continue;
            } else
            if (s[0] >= 252 && s[0] < 254 &&
                s[1] >= 128 && s[1] < 192 &&
                s[2] >= 128 && s[2] < 192 &&
                s[3] >= 128 && s[3] < 192 &&
                s[4] >= 128 && s[4] < 192 &&
                s[5] >= 128 && s[5] < 192) {
                s += 6;
                continue;
            }

            s++;
        }
    }

    {
        void *const old = result;

        size = (used | 7) + 1;

        result = realloc(result, size);
        if (!result) {
            if (old)
                free(old);
            errno = ENOMEM;
            return NULL;
        }

        memset(result + used, 0, size - used);
    }

    return (char *)result;
}

В то время как iconv() это правильное решение для преобразований набора символов в целом, две функции, приведенные выше, безусловно, полезны во встроенной или иной ограниченной среде.

Должно быть относительно легко создать таблицу преобразования из 128-255 латинских кодов 9 в последовательности байтов UTF-8. Вы можете даже использовать iconv, чтобы сделать это. Или вы можете создать файл с 128-255 латинскими кодами и преобразовать его в UTF-8, используя соответствующий текстовый редактор. Затем вы можете использовать эти данные для построения таблицы преобразования.

Другие вопросы по тегам