#c #utf_8 #unicode
Предположим, что у меня есть строка "привет" в кодировке UTF8. Эта строка находится
в C-строке char *str.
Я хочу узнать, как мне определить, является ли, например, первый символ этой строки
буквенным (A-Za-zА-Яа-я).
В своём другом вопросе я вроде бы прояснил, "как узнать, какое количество байт занимает
UTF8 символ в char * строке", откуда следует простое ветвление:
1) если символ однобайтный - его просто можно проверить посредством
if ('A' <= currentByte && currentByte <= 'z') {}
2) если символ двубайтный - его нужно проверять на русские буквы - вот здесь и начинается
вопрос: Я могу сделать каст этого 2-х байтного символ в 2-байтное число и сравнить
его со всеми подряд 16-ричными кодами русского алфавита, которые известны.
3) если символ более чем 2-байтный - игнориуем этот случай, так как латинский+русский
алфавит помещаются в 1+2 байтах в UTF8.
Что я не понимаю, это как сделать проверку 16-ричного кода двубайтового символа на
соответствие всем 16-ричным кодам русского алфавита быстрой и компактной?
Буду признателен за помощь. Спасибо.
Вот моя наивная попытка:
const char *bytes = source.bytes; // Здесь лежит UTF-8 строка из двух символов - "ая";
for (int i = 0; i < source.length; i++) {
unsigned char currentByte = (unsigned char)bytes[i];
size_t charLength = utf8_char_length[currentByte];
if (charLength == 1) {
printf("character %c %d\n", currentByte, currentByte);
if ('A' <= currentByte && currentByte <= 'z') {
printf("latin %c\n", currentByte);
}
} else if (charLength == 2) {
unsigned short twobytesymbol = *(unsigned short *)(bytes + i);
printf("(2 bytes) %X\n", twobytesymbol);
i++;
} else {
continue;
}
}
Вывод:
(2 bytes) B0D0
(2 bytes) 8FD1
Я не понимаю, почему в таблице кодов Unicode: коды букв "а" и "я" имеют 16-ричные
коды перевёрнутые относительно моих:
"а": d0 b0, "я": d1 8f
Из-за этого я не могу по-человечески проверить весь диапазон русского алфавита. Подозреваю,
что тут какая-то очевидность скрывается, которую я пока что не понимаю.
Ответы
Ответ 1
@Stanislaw Pankevich, простое начало #include#include char utf8len[256] = { // len = utf8len[c] & 0x7 cont = utf8len[c] & 0x8 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0 - 15 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 16 - 31 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 32 - 47 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 48 - 63 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 64 - 79 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 80 - 95 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 96 - 111 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 112 - 127 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, // 80 - 8f 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, // 90 - 9f 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, // a0 - af 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, // b0 - bf 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // c0 - cf 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // d0 - df 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, // e0 - ef 4, 4, 4, 4, 4, 4, 4, 4, // f0 - f7 5, 5, 5, 5, // f8, f9, fa, fb 6, 6, // fc, fd 0, 0 // fe, ff }; #define UTF8LEN(c) (utf8len[(unsigned char)(c)] & 0x7) #define UTF8CONT(c) (utf8len[(unsigned char)(c)] & 0x8) int main (int ac, char *av[]) { char *s = "Б№1АГД"; while (*s) { int ucode; printf ("[%s] %d\n", s, UTF8LEN(*s)); if ((UTF8LEN(*s) == 2) && UTF8CONT(s[1])) { ucode = ((*s & 0x1f) << 6) | (s[1] & 0x3f); printf ("ucode = 0x%x\n", ucode); s++; } s++; } } все основано на таблице длин utf-8 символов, определяемой по первому байту и проверке, что следующие байты в этом utf-8 символе должны начинаться с 0x80 avp@avp-ubu1:~/hashcode$ gcc utf.c avp@avp-ubu1:~/hashcode$ ./a.out [Б№1АГД] 2 ucode = 0x411 [№1АГД] 3 [��1АГД] 0 [�1АГД] 0 [1АГД] 1 [АГД] 2 ucode = 0x410 [ГД] 2 ucode = 0x413 [Д] 2 ucode = 0x414 avp@avp-ubu1:~/hashcode$ Я не проверяю здесь ascii и печатаю только коды русских (точнее любых 2-х байтных) символов. Можно добавить несколько полезных макросов и получить #define UTF8LEN(c) (utf8len[(unsigned char)(c)] & 0x7) #define UTF8CONT(c) (utf8len[(unsigned char)(c)] & 0x8) #define RUSUCODE(s) ({ char *_s = (s); \ (((*_s & 0x1f) << 6) | (_s[1] & 0x3f)); }) #define ISRUSUC(c) ( { int _uc = (c); \ ((0x410 <= _uc && _uc <= 0x44f) || _uc == 0x401 || _uc == 0x451); }) #define ISRUS(s) (ISRUSUC(RUSUCODE(s))) int main (int ac, char *av[]) { char *rus = "АяёЁ"; int Uaz = RUSUCODE(rus), Lya = RUSUCODE(rus + 2), Ujo = RUSUCODE(rus + 6), Ljo = RUSUCODE(rus + 4); printf ("А 0x%x я 0x%x ё 0x%x Ё 0x%x\n", Uaz, Lya, Ljo, Ujo); char *s = "Б№1АГД"; while (*s) { int ucode; printf ("[%s] %d\n", s, UTF8LEN(*s)); if ((UTF8LEN(*s) == 2) && UTF8CONT(s[1])) { ucode = RUSUCODE(s); printf ("ucode = 0x%x %s ", ucode, ISRUS(s) ? "Да" : "No"); if (ISRUSUC(ucode)) puts("rus letter"); else puts(" ???"); s++; } s++; } } Но, лучше сразу ориентироваться на обработку любых utf-8 символов. (Как говорил мой знакомый автомеханик -- "делать надо хорошо, фигово само получится"). На самом деле, получив длину utf-8 символа из UTF8LEN() можно сразу увеличить s на нее (или на 1, сообщив об ошибке, если длина нулевая). Хотя, я думаю, тут уже все и так понятно. Ответ 2
Перевести в wchar_t и использовать iswalpha? Определяет любые буквы, не только русские/английские. (В C++ есть ещё isalpha с локалью в качестве параметра, есть ли такое в чистом C?)Ответ 3
Ответ @avp невероятно крут, но все-таки надежнее использовать библиотеки: glib — довольно тяжелая, но полезная библиотека с кучей всего, в том числе работой с Юникодом, популярна в Linux, ибо служит основой для GNOME. icu — специальная библиотека конкретно про Юникод, используется например Apple под капотом NSString.
Комментариев нет:
Отправить комментарий