Кроме strchr/islower/toupper и других функций, работающих с одиночными символами, все строковые функции (ну, может почти все) нормально работают с utf-8 (если разделители в strtok/strspn и т.п. это обычный ascii (в типичных задачах это почти всегда так)).
И хотя вместо strchr можно использовать strstr, все же, несколько ориентрованных на utf-8 функций могут быть полезны.
Например, вот эти две:
// returns length in bytes of utf-8 symbol
// for invalid symbols returns 1 too
// see https://ru.wikipedia.org/wiki/UTF-8
int
utf8_len (const char *s)
{
static int step[32] = {[0 ... 15] = 1, // 0xxx xxxx ascii
[16 ... 23] = 1, // 10xx xxxx invalid
[24 ... 27] = 2, // 110x xxxx 2-bytes utf-8
3, 3, // 1110 xxxx 3 bytes utf-8
4, // 1111 0xxx 4 bytes utf-8
1 // 1111 1xxx invalid
};
return step[((unsigned char *)s)[0] >> 3];
}
// for valid utf-8 symbol put unicode to `*puc` and zero terminated utf8 bytes to `utf8_sym[]`
//
// returns length in bytes of valid utf-8 symbol
// 0 if invalid first byte
// -N if invalid N-th byte
int
utf8_symbol (const char *str, int *puc, char utf8_sym[])
{
utf8_sym[0] = *str; utf8_sym[1] = 0;
int s = utf8_len(str);
static int mask[5] = {0, 0, 0x1f, 0xf, 0x7};
int uc = 0;
if (s == 1 && (*str & 0x80))
return 0;
uc = *str & mask[s];
for (int i = 1; i < s; i++) {
if ((str[i] & 0xc0) != 0x80)
return -i;
utf8_sym[i] = str[i]; utf8_sym[i + 1] = 0;
uc = (uc << 6) | (str[i] & 0x3f);
}
if (puc)
*puc = uc;
return s;
}
На основе utf8_len()
легко сделать аналог strlen()
, возвращающую длину строки в символах utf-8 (понятно, что utf8_len работает на порядок быстрее, чем utf8_symbol, которая выполняет кучу дополнительной работы).
Возвращщаемый utf8_symbol
unicode можно проверять на вхождение в диапазон символов кириллицы (от 0x0400 до 0x04ff, русские буквы от 0x0410 до 0x044f, 0x0401 (Ё) и 0x0451 (ё)) и сделать функцию is_rus_letter() или что-то в таком духе.
Если ориентироваться на работу с русскими буквами, то вообще можно делать вот такую перекодировку:
// gets valid 2-bytes utf-8 symbol
// returns remap russian unicode to 0 ... 65 (А = 0 ... я = 63; Ё = 64, ё = 65)
// or unicode for other (not russian) 2-bytes utf-8
// call this function only if utf8_symbol() (well, may be utf8_len() too) returns 2
// see https://symbl.cc/en/unicode/blocks/cyrillic/
int
get_rus_code (const char *s)
{
int u_code = ((s[0] & 0x1f) << 6) | (s[1] & 0x3f); // unicode for 2-bytes utf-8
// remap russian unicode to 0 ... 63,
if (u_code >= 0x410 && u_code <= 0x44f)
return u_code - 0x410; // А ... я
if (u_code == 0x401)
return 64; // Ё
if (u_code == 0x451)
return 65; // ё
return u_code; // for other valid 2-bytes utf-8 the result is from 0x80 to 0x7FF
}
Используя эту функцию легко анализировать русские буквы на upper/lower (первые 32 это upper), гласные/согласные (битовая маска ложится в uint64_t
).
(Кстати, тут можно заметить, что буквы Ёё
явно лишние в нашем языке -))
Ну, в качестве дополнения (и иллюстации к первому абзацу ответа) вот функция, позволяющая читать строку в utf-8 по словам:
// returns position of word in `s[]`
// or EOF if there are no words
// puts length of the word (in bytes) into `*wlen`
int
get_word (const char *s, int *wlen)
{
#define W_SEPARATORS " \n\r\t"
int pfx_l = strspn(s, W_SEPARATORS); // skip initial spaces
if (!s[pfx_l])
return EOF;
*wlen = strcspn(s + pfx_l, W_SEPARATORS); // all non-spaces are taken as a word
return pfx_l;
}
И пример ее использования:
....
char *cur_ptr = str;
for (int word_start = 0, word_len = 0;
(word_start = get_word(cur_ptr, &word_len)) != EOF;
cur_ptr += (word_start + word_len)) {
char word[word_len + 1];
memcpy(word, cur_ptr + word_start , word_len); word[word_len] = 0;
....
}
Если есть вопросы, не стесняйтесь, спрашивайте в комментариях