Преобразование из 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, используя соответствующий текстовый редактор. Затем вы можете использовать эти данные для построения таблицы преобразования.