LINUX.ORG.RU

Юникод и перемещение указателя строки на N символов

 


3

2

На ЛОРе многие активно продвигают юникод, пытаясь убеждать, что это универсально и современно, и не так уж много ест ресурсов. Но, разве можно нормально работать с подстроками в юникоде? Выношу вопрос отдельно, поскольку интересно посмотреть практическое решение, а не только утверждения, что это можно делать специализированными функциями. Глянул я эту документацию по wchar.h и так ничего и не понял.

Как сдвинуть указатель на N символов? На N байт указатель передвинуть проще пареной репы:

strptr + N
А как передвинуть указатель на строку в юникоде на N юникодных символов специализированными функциями?

★★★★★

Какой из юникодов? UTF-8, UTF-16, UTF-32 или урезанный UCS-2

German_1984 ★★
()

А как передвинуть указатель на строку в юникоде на N юникодных символов специализированными функциями?

Не надо передвигать никаких указателей.

Вначале вы двигаете указатели, а затем получаете переполнения буфера. Доверь низкоуровневую работу со строками примитивам твоего ЯП (и не используй C и прочей хрени где есть арифметика указателей)

Xenius ★★★★★
()
Последнее исправление: Xenius (всего исправлений: 1)
Ответ на: комментарий от Xenius

Как это «не надо»? А как ещё работать с подстроками? Или есть какой-то высокоуровневый аналог бейсиковского MID$, который выполняет всю работу без необходимости разбивать её на перемещение указателя и задействования wmemcpy()?

Кстати, да, забыл написать, что речь про C.

saahriktu ★★★★★
() автор топика
Последнее исправление: saahriktu (всего исправлений: 1)
Ответ на: комментарий от saahriktu

А как ещё работать с подстроками?

А зачем работать с подстроками? Что ты вообще пытаешься сделать?

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

Например, вот, кусок шелла, который я пилил 10 лет назад:

        if (strncmp(clstr, "cd ", 3) == 0) {
            if (strncmp(clstr + 3, "~", 1) == 0) {
                (void) chdir(getenv("HOME"));
                clstr += 2;
            }
            (void) chdir(clstr + 3);
            continue;
        }
Для однобайтных кодировок это работает прекрасно.

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

На ЛОРе многие активно продвигают юникод, пытаясь убеждать, что это универсально и современно

Господь с вами. Лет 10 назад может так и было, но сейчас он уже везде и убеждать никого, к счастью, не надо.

Как сдвинуть указатель на N символов? На N байт указатель передвинуть проще пареной репы

Да ровно так же. Строка, естественно, в wchar_t. Конвертация в wchar_t - mbstowcs/wcstombs. Можно и не конвертить, длина текущего символа узнаётся через mblen.

Но на самом деле задачи «сдвинуть указатель» не существует на практике - смещения в строке образуются после поиска подстроки, а это одинаково работает и с unicode и с байтами.

slovazap ★★★★★
()
Последнее исправление: slovazap (всего исправлений: 1)
Ответ на: комментарий от saahriktu

Для однобайтных кодировок это работает прекрасно.

А то, что после cd может быть более одного пробела ты конечно не учитываешь.
Хороший пример того, как делать не надо.

Надо как-то так:

proc ::defined_commands::cd {directory} {
	cd [file normalize $directory]
}
...
set command [split commandstring \s]
set command [lmap token $command {substitute stuff}]
...
if {[set foo [lindex $command 0]] in $valid_commands} {
	::defined_commands::$foo [lrange $command 1 end]
} else {
	puts stderr "myshell: ${foo}: command not found"
}
(в cd ещё должна быть обработка исключений и вывод сообщений типа is not a directory)

Xenius ★★★★★
()
Последнее исправление: Xenius (всего исправлений: 2)
Ответ на: комментарий от saahriktu

В С нет возможности выделить подстроку из строки? И сторонних библиотек, расширяющих возможность работы со строками, нет?

На худой конец можешь сам написать функцию, которая выделяет подстроку из строки, там три строчки.

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

В С нет возможности выделить подстроку из строки?

Через перемещение указателя на N символов всё работает. Как для однобайтных кодировок я знаю. Я спрашиваю: как перемещать указатель на N символов для юникода?

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

Например, вот, кусок шелла, который я пилил 10 лет назад

А если после cd более одного пробела, или таб? Хочешь пилить шелл, пили нормальную токенизацию. Вот тут описано. Тогда вопрос сам собой отпадёт, потому что тебе придётся посмотреть на каждый символ и определить границы токенов.

Раз ты пишешь shell, то нужно принять во внимание, что имя файла в UNIX может быть любой последовательностью байт, и состоять из читаемых символов не обязано. А все стандартные команды POSIX в UTF-8 выглядят так же, как в ASCII.

В общем случае я бы советовал взять полноценную библиотеку для Юникода, например, ICU. Если же очень надо вручную, то Юникод предлагает три формы кодирования (UTF-8, UTF-16, UTF-32) и семь схем кодирования (UTF-16 и UTF-32 разбиты на little-endian, big-endian и с неизвестным порядком байт). Надо сперва решить, с какой из них ты работаешь.

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

В том и дело, что перемещение указателя — это более низкоуровневая операция. Она в частном случае, когда 1 символ == 1 байт, работает, а в других — нет. В других языках есть более высокоуровневые операции для работы с символами, а не байтами.

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

Строка, естественно, в wchar_t

Ответ, видимо, был здесь. Спасибо. Привые думать о строках как о массиве данных типа char.

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

Ответ, видимо, был здесь. Спасибо. Привые думать о строках как о массиве данных типа char.

Только учти, что wchar_t не обязан вмещать весь Юникод (он вообще по стандарту не связан с Юникодом). Тогда уж, по крайней мере, бери char32_t.

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

Я думал, что ты уже на wchar_t переписал. Думал, что и с ним не твой фокус работает, т.к. сам совсем немного имел с ним дело и уже давно.

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

Да, проверил, работает.

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

int main(void)
{
	setlocale(LC_ALL, "ru_RU.UTF-8");
	wprintf(L"Тест\n" + 2);
	return 0;
}
 ./test_unicode 
ст
te111011010
()
Последнее исправление: te111011010 (всего исправлений: 1)
Ответ на: комментарий от saahriktu

Наличие защит от дурака уже другой вопрос.

Дело не столько в наличии защит от дурака, сколько в том, что когда ты пишешь на C, не допускать ошибок почти невозможно. А система типов в C довольно слабая, поэтому она тебе не поможет их выловить при компиляции и они пойдут в продакшн. Вот в ATS, который компилируется потом в C с этим получше. Если возможно, я бы вообще C не стал использовать.

Я спрашиваю: как перемещать указатель на N символов для юникода?

Если UTF-8, то всё просто, по байту можно определить, является ли он первым байтом символа или нет. Если UTF-16, то надо проверять, находится ли слово в диапазоне суррогатных пар, и если да, то проверить два соседних слова, если UTF-32, то просто увеличивать указатель, но эта кодировка очень уж много памяти жрёт.

Xenius ★★★★★
()
Последнее исправление: Xenius (всего исправлений: 1)
Ответ на: комментарий от saahriktu

За такое убивать надо.

#include <string.h>
#include <stdio.h>

int main() {
  char cmd1[] = "cd ~";
  char cmd2[] = "cd someDir";
  char *p = index(cmd1, '~');
  printf("home found: %s\n", p);
  size_t off = strspn(cmd2, "cd ");
  printf("dir: %s\n", (cmd2 + off));
  return 0;
}
invy ★★★★★
()
Ответ на: комментарий от saahriktu

Я спрашиваю: как перемещать указатель на N символов для юникода?

если правильно - то воспользоваться соответствующими библиотеками

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

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

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

Но кроме этого нужно ещё учитывать, что входная строка может быть невалидной и обрабатывать это правильно и никогда не удалять невалидный символ, лучше его заменять на U+FFFD который специально для этого придуман.

Xenius ★★★★★
()

Пропускай s[i] & 0xC0 == 0x80. Все остальное — начало кодепоинта (ascii если 0, utf-8 если 0xC0), либо невалид. Можешь инлайн написать, типа char *skip_utf8like(char *, size_t).

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

если UTF-32, то просто увеличивать указатель

А вот хрен. Дело в том, что в юникоде есть составные символы, например, латинский символ + модификатор (умляут, грав, акут и т.п.), что кодируется двумя кодпоинтами. Так что просто передвигать указатель что-то там подсчитывая нельзя, нужно использовать libicu, или пилить собственные таблицы.

no-such-file ★★★★★
()
Ответ на: комментарий от no-such-file

что кодируется двумя кодпоинтами

В большинстве случаев таки одним. Но тут всё зависит от того, что конкретно ты считаешь и вообще пытаешься сделать.

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

большинстве случаев

Дело в том, что для этих случаев возможны и равноценны представления как из одного кодпоинта, так и составное. Причем OS X для имен файлов выбирает второе.

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

и они пойдут в продакшн

Так в том-то и дело, что до конца всё это допилено и не было. Кусок кода приведён для примера в ответ на вопрос «А зачем работать с подстроками?». Понятное дело, что если работать с ними грамотно, то там надо всё это перепиливать, но без работы с подстроками такие вещи нереализуемы впринципе.

если правильно - то воспользоваться соответствующими библиотеками

Выше уже найден правильный ответ - юзать не массивы типа char, а массивы соответствующих типов. Тогда всё продолжает работать через «strptr + N».

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

Он и в UTF-8 будет работать прекрасно ибо для младших 7 бит значения ASCII и UTF-8 совпадают.

RiseOfDeath ★★★★
()
Последнее исправление: RiseOfDeath (всего исправлений: 1)
Ответ на: комментарий от no-such-file

Чот у меня и без двухкодпоинтов получается:

~/C/utf-8-string$ ./test привет oops Männer öäü
привет 6
oops 4
Männer 6
öäü 3
На пока, reprimand, этот код выглядит как говно, но то что нужно уже делает. Скину как допилю. В будни завал, а в выхи хочется спать и ничего не делать ))

Ишь ты какое обострение в последнее время на утф-8 — за последнюю неделю аж четыр треда на лоре.

deep-purple ★★★★★
()
Последнее исправление: deep-purple (всего исправлений: 1)
Ответ на: комментарий от saahriktu

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

const char *get_utf8_substr( const char *in, size_t index )
{
    const uint8_t *p = in;
    for( ; *p, index; index-- ) {
        uint8_t  sym = *p;
        uint32_t cnt = (sym & 0x80) == 0;
        while( sym & 0x80 ) {
            cnt++;
            sym <<= 1;
        }
        p += cnt;
    }
    return p;
}
int main(void)
{
    const char *data = "utf8 стринг äåéóöøœáßðfðgðffïhäáóöoó";
    const char *p = data;
    int i;
    while( *p ) {
        printf( "%s\n", p = get_utf8_substr(data, i++) );
    }

    return 0;
}
utf8 стринг äåéóöøœáßðfðgðffïhäáóöoó
tf8 стринг äåéóöøœáßðfðgðffïhäáóöoó
f8 стринг äåéóöøœáßðfðgðffïhäáóöoó
8 стринг äåéóöøœáßðfðgðffïhäáóöoó
 стринг äåéóöøœáßðfðgðffïhäáóöoó
стринг äåéóöøœáßðfðgðffïhäáóöoó
тринг äåéóöøœáßðfðgðffïhäáóöoó
ринг äåéóöøœáßðfðgðffïhäáóöoó
инг äåéóöøœáßðfðgðffïhäáóöoó
нг äåéóöøœáßðfðgðffïhäáóöoó
г äåéóöøœáßðfðgðffïhäáóöoó
 äåéóöøœáßðfðgðffïhäáóöoó
äåéóöøœáßðfðgðffïhäáóöoó
åéóöøœáßðfðgðffïhäáóöoó
éóöøœáßðfðgðffïhäáóöoó
óöøœáßðfðgðffïhäáóöoó
öøœáßðfðgðffïhäáóöoó
øœáßðfðgðffïhäáóöoó
œáßðfðgðffïhäáóöoó
áßðfðgðffïhäáóöoó
ßðfðgðffïhäáóöoó
ðfðgðffïhäáóöoó
fðgðffïhäáóöoó
ðgðffïhäáóöoó
gðffïhäáóöoó
ðffïhäáóöoó
ffïhäáóöoó
fïhäáóöoó
ïhäáóöoó
häáóöoó
äáóöoó
áóöoó
óöoó
öoó
oó
ó

но лучше libicu взять, да.

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

Дело в том, что для этих случаев возможны и равноценны представления как из одного кодпоинта, так и составное.

Только ведут себя по-разному. Например у меня в программах на Qt и firefox если поставить курсор после å и нажать backspace, удалится только кружочек, а вот если сделать это после å, то удалится весь символ. Вот в Gtk они ведут себя одинаково.

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

это поведение редактора, а не «двойной символ»

Å это один символ с точки зрения UTF8

anonymous
()
Ответ на: комментарий от saahriktu
#include <unistr.h>
#define CD_STR "cd "
#define CD_LEN u8_strlen(CD_STR)
#define HOME_STR "~/"
#define HOME_LEN u8_strlen(HOME_STR)

uint8_t* u8_skip(uint8_t* str, size_t cnt) {
  ucs4_t ch;
  uint8_t* res = str;
  
  for (int i = 0; i < cnt; i++)
    u8_next(&ch, res);
  return res;
}

<>

        if (u8_strncmp(clstr, CD_STR, CD_LEN) == 0) {
            tmpstr = u8_skip(clstr, CD_LEN);
            if (u8_strncmp(tmpstr, HOME_STR, HOME_LEN) == 0) {
                (void) chdir(getenv("HOME"));
                tmpstr = u8_skip(tmpstr, HOME_LEN);
            }
            (void) chdir(tmpstr);
            continue;
        }

Немного изменил, потому что с детства не люблю арифметику указателей и магические константы, заодно немного исправил твою ошибку (~/ должно обрабатываться иначе, чем ~user/, осталось добавить логику для обработки ~user/). Что здесь такого страшного и сложного?

redgremlin ★★★★★
()

На ЛОРе многие активно продвигают юникод, пытаясь убеждать, что это универсально и современно

С новым годом! Пусть наступивший 2005 принесет тебе еще больше новостей.

cdshines ★★★★★
()

Найди библиотеку для работы с UTF-8 и используй её функционал.

PS wchar использовать не надо. И вообще стандартные функции для работы со строками использовать не надо. В C/C++ нет нормальной стандартной библиотеки для работы со строками.

Legioner ★★★★★
()
Последнее исправление: Legioner (всего исправлений: 1)
Ответ на: комментарий от anonymous

Ошибочка:

    int i = 1;
Но практичнее:
const char *get_utf8_substr( const char *in, size_t index )
{
    const uint8_t *p = in;
    for( ; *p && index; index-- )
        while( 0x80 == (*++p & 0xC0) );
    return p;
}

bormant ★★★★★
()
Последнее исправление: bormant (всего исправлений: 2)
Ответ на: комментарий от RiseOfDeath

Ну в 21-м веке это уточнение мало где имеет смысл. Сейчас даже в идентификаторах языков программирования можно использовать юникод. Я так сходу и не придумаю, где может хватить только ANSII-строк.

Legioner ★★★★★
()
Ответ на: комментарий от deep-purple

На пока, reprimand, этот код выглядит как говно, но то что нужно уже делает. Скину как допилю. В будни завал, а в выхи хочется спать и ничего не делать ))

скинь потыкать
можно и не в паблик

Ишь ты какое обострение в последнее время на утф-8 — за последнюю неделю аж четыр треда на лоре.

скорее обострение Си на ЛОР-е

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

И вообще стандартные функции для работы со строками использовать не надо. В C/C++ нет нормальной стандартной библиотеки для работы со строками.

а что не так с ними? ну, если не учитывать кодировочную волокиту с вытекающими

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

а что не так с ними?

С однобитовыми — их не хватает для юникода.

С wchar_t — во-первых не гарантируется, что он 4-байтовый (т.е. его уже заранее не хватает на все символы и он сходу подхватывает все проблемы однобайтовых API), во-вторых он бессмысленно транжирит место в оперативной памяти. Ну и опять же API непонятно чему соответствует. У юникода каждый год новый стандарт и непонятно, на что рассчитывать.

ну, если не учитывать кодировочную волокиту с вытекающими

С кодировками волокиты никакой нет, на самом деле. UTF-8 это крайне примитивная кодировка и её хватает для 90% случаев. Всё остальное — мало где используемая экзотика, в стандарте точно не нужно. Волокита скорее с юникодными базами данных, всякими классификациями — что есть буква, что есть цифра, что есть пробел и тд. Но дело нужное, от этого никуда не денешься.

Legioner ★★★★★
()
Последнее исправление: Legioner (всего исправлений: 1)
Ответ на: комментарий от Legioner

С wchar_t — во-первых не гарантируется, что он 4-байтовый

Тем не менее, на x86_64 вес в байтах:

char: 1
wchar_t: 4
char16_t: 2
char32_t: 4

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

Ну если ты готов ограничить свои платформы только теми, где это так, пожалуйста. Насколько я знаю, в Windows у MSVC будет 2 байта. А это нехилый такой процент программ.

Legioner ★★★★★
()
Ответ на: комментарий от no-such-file

Брось примерчик скопировать. Я не понял о чем ты. Еще может у меня и локаль такая не установлена. Кароч брось словечек на тест.

А, вон там выше есть интересные строки. Ну потестю. Дома.

deep-purple ★★★★★
()
Последнее исправление: deep-purple (всего исправлений: 1)
Ответ на: комментарий от Legioner

Так и запишем: юникода не существует. Вот то ли дело char. Он везде 1 байт. И никаких проблем. Но, 1 байт. Что идеально подходит для однобайтных кодировок. Следовательно, однобайтные кодировки существуют. А юникод - нет. Это иллюзия, которая создана костылями, которые везде свои.

saahriktu ★★★★★
() автор топика
Ответ на: комментарий от no-such-file

Эээ, так не пойдет:

console.log("e\u0301\u0324", "e\u0301\u0324".length); // è̤ 3
$ ./test привет oops Männer öäü é̤
привет 6
oops 4
Männer 6
öäü 3
é̤ 3
Всмысле, у меня так же как и в кансоли браузера. Оно считает это тремя символами, что, вообще-то корректно.

deep-purple ★★★★★
()
Последнее исправление: deep-purple (всего исправлений: 1)
Ответ на: комментарий от no-such-file

А вот пихпих, те же яйцы:

var_dump("é̤", strlen("é̤"), mb_strlen("é̤", "UTF-8"));
// string(5) "é̤"
// int(5)
// int(3)
И картинко с поиском в чармапе (немаэ): http://s14.postimg.org/ptk3wx9j5/umlaut.png

А по одиночке он их нашел в чармапе.

deep-purple ★★★★★
()
Последнее исправление: deep-purple (всего исправлений: 2)
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.