Страницы

Поиск по вопросам

пятница, 19 октября 2018 г.

Как определить, является ли UTF8 символ буквенным?

Предположим, что у меня есть строка "привет" в кодировке 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
", currentByte, currentByte);
if ('A' <= currentByte && currentByte <= 'z') { printf("latin %c
", currentByte); } } else if (charLength == 2) { unsigned short twobytesymbol = *(unsigned short *)(bytes + i);
printf("(2 bytes) %X
", twobytesymbol);
i++; } else { continue; } }
Вывод: (2 bytes) B0D0 (2 bytes) 8FD1
Я не понимаю, почему в таблице кодов Unicode: коды букв "а" и "я" имеют 16-ричные коды перевёрнутые относительно моих:
"а": d0 b0, "я": d1 8f
Из-за этого я не могу по-человечески проверить весь диапазон русского алфавита. Подозреваю, что тут какая-то очевидность скрывается, которую я пока что не понимаю.


Ответ

@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
", s, UTF8LEN(*s)); if ((UTF8LEN(*s) == 2) && UTF8CONT(s[1])) { ucode = ((*s & 0x1f) << 6) | (s[1] & 0x3f); printf ("ucode = 0x%x
", 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
", Uaz, Lya, Ljo, Ujo);
char *s = "Б№1АГД";
while (*s) { int ucode;
printf ("[%s] %d
", 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, сообщив об ошибке, если длина нулевая). Хотя, я думаю, тут уже все и так понятно.

Комментариев нет:

Отправить комментарий