Предположим, что у меня есть строка "привет" в кодировке 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
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, сообщив об ошибке, если длина нулевая).
Хотя, я думаю, тут уже все и так понятно.
Комментариев нет:
Отправить комментарий