Завершить (возможно) многобайтовую строку?

Я работаю над кодом C и изо всех сил пытаюсь найти способ завершить строку после определенного количества символов. Например, мне нужно завершить строку data после 3 символов. Если это простая строка ASCII, я могу сделать что-то вроде

data[3] = 0;

Но в моем случае любой из символов может быть многобайтным, например È или À. Как лучше всего завершить такую ​​строку после заданного количества символов?

ОБНОВИТЬ:

В принципе:

char s_mon[7];

setlocale(LC_ALL, "");
strftime(s_mon, 7, "%b", tick_time);

Текущий язык — французский. Месяц хранится как "févr.". Нужно, чтобы это было «fév», но оно должно быть универсальным, чтобы такие записи, как «mars», также можно было сократить до 3 символов.


person Yuriy Galanter    schedule 13.02.2017    source источник
comment
Что такое декларация для data   -  person Ed Heal    schedule 13.02.2017
comment
В зависимости от кодировки (вы должны знать, какую кодировку вы используете) существуют четкие правила использования многобайтовых последовательностей. Вы анализируете строку слева направо, учитывая эти правила, пока не пройдете N-й символ. Затем вы можете поставить ноль (например, для UTF-8 достаточно одного 0).   -  person linuxfan says Reinstate Monica    schedule 13.02.2017
comment
È, À не требуют мультибайта. С 8859-1 это просто коды 200 и 192. Что это за символ набор, который вы используете? Как код вообще читал текст?   -  person chux - Reinstate Monica    schedule 13.02.2017
comment
Обновил пост с примером.   -  person Yuriy Galanter    schedule 13.02.2017
comment
strftime возвращает количество символов.   -  person stark    schedule 13.02.2017
comment
@stark Я думал, что он вернул количество байтов?   -  person Yuriy Galanter    schedule 13.02.2017
comment
В любом случае, если число, которое он возвращает, не равно 0, то он уже поместил нулевой терминатор в вашу строку.   -  person stark    schedule 13.02.2017


Ответы (1)


Закончилось повторным использованием этого utf_str_to_upper функция. Функция корректно работает с многобайтовыми значениями. Он также преобразует строку в верхний регистр (что мне также нужно), но при необходимости эту функциональность можно отключить.

Единственная модификация, которую я сделал, это передать второй параметр limit, который ограничен символами, в котором мне нужно обрезать строку. Затем функция вставляет терминатор в правильную позицию байта и возвращает позицию байта. Вот полный код:

uint8_t utf8_str_to_upper(char* s, uint8_t limit) {

    uint8_t char_no = 0;
    uint8_t* p;

    for (p = (uint8_t*)s; *p; ++p) {

        // (<128) ascii character
        // U+00000000 – U+0000007F: 0xxxxxxx
        if (*p < 0b10000000) {
            if (*p >= 0x61 && *p <= 0x7A) {
                *p = *p - 0x20; // a~z -> A~Z
            }

        // (<192) unexpected continuation byte
        } else if (*p < 0b11000000) {

        // (<224) 2 byte sequence
        // U+00000080 – U+000007FF: 110xxxxx 10xxxxxx
        } else if (*p < 0b11100000) {
            uint16_t code = ((uint16_t)(p[0] & 0b00011111) << 6) | (p[1] & 0b00111111);
            if (
                (code >= 0x00E0 && code <= 0x00F6) || // à~ö -> À~Ö
                (code >= 0x00F8 && code <= 0x00FE)    // ø~þ -> Ø~Þ
            ) {
                code -= 0x0020;
                p[0] = 0b11000000 | ((code >> 6) & 0b00011111);
                p[1] = 0b10000000 | ( code       & 0b00111111);
            }
            ++p;

        // (<240) 3 byte sequence
        // U+00000800 – U+0000FFFF: 1110xxxx 10xxxxxx 10xxxxxx
        } else if (*p < 0b11110000) {
            p += 2;

        // (<248) 4 byte sequence
        // U+00010000 – U+001FFFFF: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
        } else if (*p < 0b11111000) {
            p += 3;

        // (<252) 5 byte sequence
        // U+00200000 – U+03FFFFFF: 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
        } else if (*p < 0b11111100) {
            p += 4;

        // (<254) 6 byte sequence
        // U+04000000 – U+7FFFFFFF: 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
        } else if (*p < 0b11111110) {
            p += 5;
        }

        if (limit) {
            char_no++;

            if (char_no == limit) {
               *(p + 1) = 0;
               return p-(uint8_t*)s + 1;
               break;
            }

        }

    }

    return p-(uint8_t*)s + 1;
}
person Yuriy Galanter    schedule 13.02.2017
comment
Обратите внимание, что в Unicode é может быть представлен либо как предварительно комбинированный символ, либо как базовый символ + диакритический знак, поэтому сначала вам необходимо нормализовать ввод в соответствующую форму, например: в NFC, чтобы получить é как предварительно составленный символ. . - person ninjalj; 14.02.2017
comment
@ninjalj на самом деле в этом случае (Pebble Dev) я должен решить, что составленные символы в многобайтовые должны отображаться правильно. Эта функция, которую я использовал, является частью пакета, который имеет дело с этим github.com/jrmobley/pebble. -utf8 - person Yuriy Galanter; 14.02.2017