LINUX.ORG.RU

Работа со строками в ASCII и UTF-8


0

0

Есть программа, использующая 8-битные кодировки: ASCII, KOI-8 и т.п. Требуется исправить её, чтобы понимала UTF-8. Возникли 2 вопроса:

1. Правильно ли я понимаю, что функции strcat, strcmp, strcpy, strlen, strtod, strtok в современном линуксе уже поддерживают UTF?

2. Как получить n-й символ в строке? С 1-байтными просто: line[n], а как быть с переменной длиной символа? (Например, строка из русских букв с пробелами и знаками препинания.)

Заранее спасибо?

★★★★★

1) функции str* всегда работают только с байтами. 2) предварительно перевести из utf-8 в utf-32 (или ucs-4).

dmitry_vk ★★★
()
Ответ на: комментарий от dmitry_vk

> 1) функции str* всегда работают только с байтами.

Спасибо. Уже поэкспериментировал и понял, что неправ.

А существуют ли их эквиваленты для произвольной длины символа?

> 2) предварительно перевести из utf-8 в utf-32 (или ucs-4).

Чем? Только iconv, или есть что-то ещё?

question4 ★★★★★
() автор топика
Ответ на: комментарий от dmitry_vk

Заодно сразу 2 вопроса:

1. Как сравнивать русские буквы без учёта регистра?

2. Существует ли функция сравнения, считающая идентичными, например, "е" и "ё"?

question4 ★★★★★
() автор топика
Ответ на: комментарий от ananas

> смотри в сторону wcs*

Спасибо. Можно поподробнее? В каком мане все эти функции сведены вместе (как string(3))?

question4 ★★★★★
() автор топика
Ответ на: комментарий от question4

не знаю, есть ли общий ман для них, но практически у каждой str*() есть свой аналог wcs*() для работы с wchar_t* вместо char*

ananas ★★★★★
()
Ответ на: комментарий от ananas

> практически у каждой str*() есть свой аналог wcs*()

Это я понял, просто не все они на wcs начинаются, а общий список не нашёл.

question4 ★★★★★
() автор топика
Ответ на: комментарий от const86

> man wchar.h

Спасибо, про .h не сообразил.

question4 ★★★★★
() автор топика
Ответ на: комментарий от question4

>> И еще man setlocale, иначе не заработает.

> Спасибо, но зачем она нужна? Только для функций с *mb*?

Угу. Но вместо них, наверное, лучше юзать iconv, особенно если используется несколько кодировок.

const86 ★★★★★
()
Ответ на: комментарий от mannaz

> ICU спасет тебя (правда, либа довольно жирная)

Спасибо, но, действительно, хотелось бы ограничиться минимумом библиотек. В идеале — одной glibc. Попробую только если другие варианты не заработают.

> По второму пункту имеет смысл ограничиться какой-нибудь libutf8

Что-то не могу найти. Как пакет называется? libutf8?

question4 ★★★★★
() автор топика
Ответ на: комментарий от ttnl

> GLib позволяет сравнительно просто работать с юникодом. Описание: http://library.gnome.org/devel/glib/stable/glib-Unicode-Manipulation.html

Похоже, самое подходящее. Можно по-быстрому заменить большинство вызовов str* без написания сложных обвязок к wc*. Хоть и не хочется в программу добавлять зависимость от glib. Спасибо.

question4 ★★★★★
() автор топика
Ответ на: комментарий от const86

>> *mb*

> Но вместо них, наверное, лучше юзать iconv, особенно если используется несколько кодировок.

Только utf-8, но для iconv проще задавать начальную кодировку.

question4 ★★★★★
() автор топика
Ответ на: комментарий от sobrano_bez_parity

>> Получить n-й символ: g_utf8_offset_to_pointer (str, n)

> Если за такое не убивать, то за что тогда убивать?

Что тебе не нравится?

question4 ★★★★★
() автор топика
Ответ на: комментарий от question4

> 2) предварительно перевести из utf-8 в utf-32 (или ucs-4).

Чем? Только iconv, или есть что-то ещё?

Преобразуем:

// строка в utf-8
const char* string = "some utf-8 text";

// получаем нужный размер буфера
int size = UTF8_WCHAR(NULL, string, 0) + 1;

// выделяем память под буфер (не забываем об освобождении)
wchar_t* out = new wchar_t[size];

// копируем utf-8 строку с преобразованием в out
mbstowcs(out, string, size);

Дальше стандартные манипуляции с буфером out - wcslen, wcscpy, etc...

andreyu ★★★★★
()
Ответ на: комментарий от sobrano_bez_parity

>> Получить n-й символ: g_utf8_offset_to_pointer (str, n)

>Если за такое не убивать, то за что тогда убивать?


Если ты обоснуешь, почему за такое надо убивать, то возможно я отвечу
на твой вопрос.

ttnl ★★★★★
()
Ответ на: комментарий от andreyu

int size = UTF8_WCHAR(NULL, string, 0) + 1;

Где эта функция? Не нашёл.

wchar_t* out = new wchar_t[size];

Это C++? Программа на простом C.

mbstowcs(out, string, size);

А я с mbsrtowcs возился :)

Я сделал так:

int strcasecmp_utf(const char *s1, const char *s2){
        int l1,l2,result;
        wchar_t *w1,*w2;
        size_t st;
        l1=strlen(s1)+1; l2=strlen(s2)+1;
        w1=calloc(l1, 4); w2=calloc(l2, 4);
        mbstowcs(w1, s1, l1); mbstowcs(w2, s2, l2);
        result=wcscasecmp(w1,w2);
        free(w2); free(w1);
        return result;
};
Вызывается вместо strcasecmp. Для русского языка проверил — работает.

question4 ★★★★★
() автор топика
Ответ на: комментарий от question4

> > int size = UTF8_WCHAR(NULL, string, 0) + 1;
> Где эта функция? Не нашёл.


Прошу прощения, копировал код из кросс-платформенной игры. Не все вычистил. UTF8_WCHAR - это обычный дефайн для mbstowcs.

> > wchar_t* out = new wchar_t[size];

> Это C++? Программа на простом C.


Ну тогда замените new на malloc или то, что вам подходит лучше.

> w1=calloc(l1, 4);


Тут видимо лучше сделать так:
w1=calloc(l1, sizeof(wchar_t));

Но для сравнения двух строк с учетом регистра не нужно их преобразовывать. Достаточно сравнить длины строк, и если они равны, то сравнить и память - memcmp().

andreyu ★★★★★
()
Ответ на: комментарий от andreyu

> sizeof(wchar_t)

Да, надо будет такие места вычистить. Но под линуксом оно всегда 4.

> Но для сравнения двух строк с учетом регистра не нужно их преобразовывать.

strcasecmp и wcscasecmp как раз без учёта регистра :) Ради преобразований регистра (и ещё определения длины) всё и затеял. Хотя для проверки делал и вариант с просто strcmp и wcscmp.

question4 ★★★★★
() автор топика
Ответ на: комментарий от question4

> > sizeof(wchar_t)
> Да, надо будет такие места вычистить. Но под линуксом оно всегда 4.


На всех архитектурах? ;)

andreyu ★★★★★
()
Ответ на: комментарий от andreyu

> На всех архитектурах? ;)

Википедия говорит, что да. Проверял на х86 и х86_64.

Разве что в системе будет не glibc, а что-нибудь ещё.

question4 ★★★★★
() автор топика
Ответ на: комментарий от question4

Хватило просто установить Konqueror.

question4 ★★★★★
() автор топика
Ответ на: комментарий от question4

> > На всех архитектурах? ;)
> Википедия говорит, что да. Проверял на х86 и х86_64.

> Разве что в системе будет не glibc, а что-нибудь ещё.


Полагаю, что glibc не может влиять на размер wchar_t. Unicode гайдлайн говорит, что размер wchar_t может быть и 8 бит. И вообще это compiler-specific.
Посему стоит глянуть в стандарт.

andreyu ★★★★★
()
Ответ на: комментарий от andreyu

> Полагаю, что glibc не может влиять на размер wchar_t.

Таки да, у меня определено в файлах GCC. Не туда смотрел.

В ICC: на x86 — long int, на x86-64 и "Итаниуме" — int.

> Unicode гайдлайн говорит, что размер wchar_t может быть и 8 бит. И вообще это compiler-specific. Посему стоит глянуть в стандарт.

Если верить ссылающимся на стандарт манам, не менее 2 байт и не длиннее, чем long для данной архитектуры. В каком-то Borland C++ нашёл даже char.

question4 ★★★★★
() автор топика
Ответ на: комментарий от question4

>> info libc

Информация по wcstok() там не соответствует действительности (указаны 2 параметра вместо 3). В мане 1999 года — соответствует. Ищу куда писать багрепорт...

question4 ★★★★★
() автор топика

В общем, написал обвязку для нескольких функций, которые мне нужны. Покритикуйте, пожалуйста.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <locale.h>
#include <wchar.h>

int strcasecmp_u(const char *s1, const char *s2){
        int l1,l2,result;
        wchar_t *w1,*w2;
        l1=strlen(s1)+1; l2=strlen(s2)+1;
        w1=calloc(l1, sizeof(wchar_t)); w2=calloc(l2, sizeof(wchar_t));
        mbstowcs(w1, s1, l1);
        mbstowcs(w2, s2, l2);
        result=wcscasecmp(w1, w2);
        free(w2); free(w1);
        return result;
};

int strncasecmp_u(const char *s1, const char *s2, size_t n){
        int l1,l2,result;
        wchar_t *w1,*w2;
        l1=strlen(s1)+1; l2=strlen(s2)+1;
        w1=calloc(l1, sizeof(wchar_t)); w2=calloc(l2, sizeof(wchar_t));
        mbstowcs(w1, s1, l1);
        mbstowcs(w2, s2, l2);
        result=wcsncasecmp(w1, w2, n);
        free(w2); free(w1);
        return result;
};

size_t strcspn_u(const char *s, const char *reject){
        size_t ls,lr,result;
        wchar_t *ws,*wr;
        ls=strlen(s)+1; lr=strlen(reject)+1;
        ws=calloc(ls, sizeof(wchar_t)); wr=calloc(lr, sizeof(wchar_t));
        mbstowcs(ws, s, ls); mbstowcs(wr, reject, lr);
        result=wcscspn(ws, wr);
        free(wr); free(ws);
        return result;
};

size_t strspn_u(const char *s, const char *accept) {
        size_t ls, la, result;
        wchar_t *ws, *wa;
        ls=strlen(s)+1; la=strlen(accept)+1;
        ws=calloc(ls, sizeof(wchar_t)); wa=calloc(la, sizeof(wchar_t));
        mbstowcs(ws, s, ls); mbstowcs(wa, accept, la);
        result=wcsspn(ws, wa);
        free(wa); free(ws);
        return result;
};

size_t strcspn_u(const char *s, const char *reject){
        size_t ls,lr,result;
        wchar_t *ws,*wr;
        ls=strlen(s)+1; lr=strlen(reject)+1;
        ws=calloc(ls, sizeof(wchar_t)); wr=calloc(lr, sizeof(wchar_t));
        mbstowcs(ws, s, ls); mbstowcs(wr, reject, lr);
        result=wcscspn(ws, wr);
        free(wr); free(ws);
        return result;
};

size_t strspn_u(const char *s, const char *accept) {
        size_t ls, la, result;
        wchar_t *ws, *wa;
        ls=strlen(s)+1; la=strlen(accept)+1;
        ws=calloc(ls, sizeof(wchar_t)); wa=calloc(la, sizeof(wchar_t));
        mbstowcs(ws, s, ls); mbstowcs(wa, accept, la);
        result=wcsspn(ws, wa);
        free(wa); free(ws);
        return result;
};

char *strtok_u_ptr; wchar_t *strtok_u_ptrw;
//Yes, globals are wrong. But strtok() is just as wrong.
char *strtok_u(char *s, const char *delim){
        size_t ls, ld, converted;
        wchar_t *ws, *wd, *wresult;
        char *result, buffer[MB_CUR_MAX];
        int ccv, symbols_to_skip;
        if (s!=NULL) {strtok_u_ptr=s;}; //otherwise use saved address
        if (strtok_u_ptr==NULL) {return NULL;}; //nothing left
        ls=strlen(strtok_u_ptr)+1; ld=strlen(delim)+1;
        ws=calloc(ls, sizeof(wchar_t)); wd=calloc(ld, sizeof(wchar_t));
        wd=calloc(ld, sizeof(wchar_t));
        mbstowcs(ws, strtok_u_ptr, ls);  mbstowcs(wd, delim, ld);
        wresult=wcstok(ws, wd, &strtok_u_ptrw);
        converted=wcstombs(strtok_u_ptr, ws, ls);
        result=strtok_u_ptr; //correct if doesn't start with separator
        // now run as many cycles, as there are separators at the start: 
        // wresult-ws
        symbols_to_skip=((long int)wresult-(long int)ws)/sizeof(wchar_t);
        for (ccv=0; ccv<symbols_to_skip; ccv++) {
                //add separator length in multibyte form
                result = (char*)( (long int)result + wctomb(buffer, ws[ccv]) );
        };
        // check if a separator was replaced with '\0'
        if (converted<ls-1) {
                strtok_u_ptr+=converted+1;
                wcstombs(strtok_u_ptr, strtok_u_ptrw, ls-converted);
        } else {
                strtok_u_ptr=NULL; // no separators left
        };
        free(wd); free(ws);
        return result;
};

#define strchr_u(s,c) strstr(s,c)
#define strrchr_u(s,c) rindex_u(s,c)
#define index_u(s,c) strstr(s,c)

char *rindex_u(const char *s, const char *c){
        const char *tmp1,*tmp2;
        tmp1=s;
        tmp2=NULL;
        tmp1=strstr(tmp1,c);
        while (tmp1!=NULL) {
                tmp1=strstr(tmp1,c);
                if (tmp1==NULL) { return (char *) tmp2; }
                else { tmp2=tmp1; tmp1++; };
        };
        return (char *) tmp2;
};

question4 ★★★★★
() автор топика
Ответ на: комментарий от question4

> mbstowcs(w1, s1, l1)

Тут лучше для начала вызывать с w1 == NULL, что бы выяснить, какой размер буфера нужен. Подробности в мане.

andreyu ★★★★★
()
Ответ на: комментарий от question4

> Покритикуйте, пожалуйста.

У тебя в парах функций код отличается только на один вызов библиотечной функции. Зачем копипастить?

kemm
()
Ответ на: комментарий от kemm

> У тебя в парах функций код отличается только на один вызов библиотечной функции. Зачем копипастить?

Как сделать правильно?

question4 ★★★★★
() автор топика
Ответ на: комментарий от question4

> Как сделать правильно?

Любым традицонным методом избавления от копипасты... Например, вынести дублирующийся кусок в отдельную static-функцию, которая по доп. параметру будет вызывать нужную библиотечную функцию; или сделать макросы типа MYSTR_PROLOG/MYSTR_EPILOG, в которые вынести повторяющиеся куски до и после вызова библиотечной функции...

kemm
()
Ответ на: комментарий от andreyu

> Тут лучше для начала вызывать с w1 == NULL, что бы выяснить, какой размер буфера нужен. Подробности в мане.

Сделал. Но чем это лучше? Помимо того, что требует меньше памяти.

question4 ★★★★★
() автор топика
8 ноября 2009 г.
Ответ на: комментарий от question4

В продолжение затронутой темы - может я один такой на свете, но найти решение своей задачки, связанной с представлением символа UTF-8 двумя последовательными байтами за 3 дня поиска по доступным ресурсам так и не удалось.

Поясню: в "однобайтном" мире я могу написать for (i=32;i<127;i++) printf("%d -- %c\n", i, i); и получу таблицу соответствия печатных символов их кодам ASCII.

Более того, можно изобразить что-то вроде: while((c = getc(stdin)) != EOF) printf("%d -- %c\n", c, c); и получить код ASCII введённых с клавиатуры символов. Но (!) попытка ввода с той же клавиатуры киррилических (т.е. двухбайтных) символов приводит к их "расщеплению" на "старший" и "младший" байты с выводом, скажем, для буквы "А" десятичных значений 208 и 144 (аналогично: "Я" - 208 и 175, "а" - 208 и 176, "я" - 209 и 143).

Таким образом, логика кодирования кириллици абсолютно ясна, но загадка, как "собрать" расщеплённый символ обратно для его вывода по коду ?

root66
()
Ответ на: комментарий от root66

> while((c = getc(stdin)) != EOF) printf("%d -- %c\n", c, c);

> логика кодирования кириллици абсолютно ясна, но загадка, как "собрать" расщеплённый символ обратно для его вывода по коду ?

Локаль UTF?

getc возвращает char, приведённый к типу int. Поэтому getc берёт за раз по одному байту из многобайтного символа.

Насколько я понимаю, нужно или пользоваться функциями, читающими в массивы (например, fgets), или анализировать эти байты и собирать из них символы в UTF, или работать с функциями, читающими "широкие" символы, например, fgetwc (не пробовал, не знаю, что будет с кодировкой).

question4 ★★★★★
() автор топика
Ответ на: комментарий от question4

Кодировка UTF-8 (ОС RHEL 5.4). Сейчас это - стандарт. На него и ориентируюсь.

Создать строку двухбайтных символов действительно возможно. Хотя бы как-то так:

char str[80]; scanf("%39s", str);

Здесь засада только в заполнении строки (двухбайтные символы занимают по паре знакомест), а смешанные строки типа "internet-портал" - вообще ужас (может зависеть от того, какой дефис стоит в середине: простой (однобайтный) или двойной)!

Вопрос остаётся: как такую строку выводить посимвольно (!). Просто printf("%s", str) - не спортивно. Хочется символ-за-символом (к примеру, пропуская/замещая определённые символы) ?

root66
()

Ох уж эти любители юникода... Ну не мазохисты ли? Кто вам мешает везде использовать КОИ8-Р?

Eddy_Em ☆☆☆☆☆
()
Ответ на: комментарий от root66

> Вопрос остаётся: как такую строку выводить посимвольно

Преобразовать в массив wchar_t и выводить по одному символу. Как мне советовали выше, man wchar.h.

question4 ★★★★★
() автор топика
Ответ на: комментарий от Eddy_Em

> Кто вам мешает везде использовать КОИ8-Р?

№, —, «, », „, ”, французский язык и GTK-2.

question4 ★★★★★
() автор топика
Ответ на: комментарий от question4

Вывести уже введённый с клавиатуры символ так действительно можно. Но сгенерировать свой собственный по номеру символа - нет.

Скажем, если в массиве встретился "А", вместо него вывести "а". С численным представлением однобайтных символов такой алгоритм пишется в три строки. А с двумя байтами на символ что-то ни как :-(

Идеи есть ?

root66
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.