Какой минимальный набор функций C необходимо реализовать для работы с UTF-8 в этом случае?

Рейтинг: -1Ответов: 1Опубликовано: 22.02.2023

Какой минимально необходимый набор функций из вашего опыта нужно реализовать для работы с кодировкой UTF-8, чтобы поверх них можно было писать другие типичные функции для работы со строками уже без перекодирования байт?

Ответы

▲ 2Принят

Кроме 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;
    
    ....
  }

Если есть вопросы, не стесняйтесь, спрашивайте в комментариях