LINUX.ORG.RU

Слетает выравнивание для строк с русскими символами. Unicode в Си.

 , , , ,


1

3

Всех приветствую!

Все таки решил создать новую тему.

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

Проблема вот в чем: если в printf-е мы ставим «%30s», то у нас выводится сначало 30-length(str) пробелов, потом наша строка str длиной length(str).

Но это не работает, если в строке есть не англ. символы.

Чтобы проверить какое будет поведение под Windows и Linux, а также с char* и wchar_t*, набросал программку:



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

#include <locale.h>
#include <wchar.h>
#include <uchar.h> // for char16_t


char *str_dup(char const *in)
{
    size_t len = strlen(in);
    char *out = malloc(len+1);
    strncpy(out, in, len+1);
    return out;
}

wchar_t *wstr_dup(wchar_t const *in)
{
    size_t len = wcslen(in);
    wchar_t *out = malloc((len+1)* sizeof(wchar_t));
    wcsncpy(out, in, (len+1) );
    return out;
}

wchar_t* convert_to_wstr(const char* cstr)
{
    mbstate_t state;
    memset(&state, 0, sizeof(state) );
    size_t out_sz = 1 + mbsrtowcs(NULL, &cstr, 0, &state);
    
    wchar_t* ws = malloc(out_sz*sizeof(wchar_t) );
    mbstowcs(ws, cstr, out_sz);
    return ws;
}

char* convert_to_cstr(const wchar_t* wstr)
{
    mbstate_t state;
    memset(&state, 0, sizeof(state) );
    size_t out_sz = 1 + wcsrtombs(NULL, &wstr, 0, &state);
    
    char* cs = malloc(out_sz*sizeof(char) );
    wcstombs(cs, wstr, out_sz);
    return cs;
}

/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
typedef struct CSTRUCT
{
	char* data_only_engl; // только англ. буквы
        char* data_from_file; // строка из файла
        char* data_from_prog; // строка в программе
} CSTRUCT;

CSTRUCT* cs_create(const char* cstr)
{
	CSTRUCT* cs = malloc(sizeof(CSTRUCT));

	cs->data_only_engl = str_dup("Hello Friend");
	cs->data_from_prog = str_dup(cstr);

	FILE *input = fopen("text.txt", "r");
    char buff[128];
    memset(buff, '\0', 128);
    fgets(buff, 128, input);
    	cs->data_from_file = str_dup(buff);
	return cs;
}

void cs_print(CSTRUCT* cs)
{
	fprintf(stdout,"------------------------------|\n");
	const char FMT[] = "%30s| %2ld\n";
	fprintf(stdout, FMT, cs->data_only_engl, strlen(cs->data_only_engl) );
	fprintf(stdout, FMT, cs->data_from_prog, strlen(cs->data_from_prog) );
	fprintf(stdout, FMT, cs->data_from_file, strlen(cs->data_from_file) );
	fprintf(stdout,"123456789012345678901234567890|\n"); // ровно 30 символов
}

void cs_free(CSTRUCT** cs)
{
	free( (*cs)->data_from_prog);
	free( (*cs)->data_from_file);
	free( (*cs)->data_only_engl);
	free(*cs);
}
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/



typedef struct WSTRUCT
{
	wchar_t* data_only_engl;
        wchar_t* data_from_file;
        wchar_t* data_from_prog;
} WSTRUCT;


WSTRUCT* ws_create(const char* str)
{
	WSTRUCT* ws = malloc(sizeof(WSTRUCT));
	ws->data_only_engl = wstr_dup(L"Hello Friend");
	ws->data_from_prog = convert_to_wstr(str);

	FILE *input = fopen("text.txt", "r");
    char buff[128];
    memset(buff, '\0', 128);
    fgets(buff, 128, input);
    
	ws->data_from_file = convert_to_wstr(buff);
	return ws;
}

void ws_print(WSTRUCT* ws)
{
	fprintf(stdout,"------------------------------|\n");
	const char FMT[] = "%30ls| %2ld\n";
	fprintf(stdout, FMT, ws->data_only_engl, wcslen(ws->data_only_engl) );
	fprintf(stdout, FMT, ws->data_from_prog, wcslen(ws->data_from_prog) );
	fprintf(stdout, FMT, ws->data_from_file, wcslen(ws->data_from_file) );
	fprintf(stdout,"123456789012345678901234567890|\n");
}

void ws_free(WSTRUCT** ws)
{
	free( (*ws)->data_from_prog);
	free( (*ws)->data_from_file);
	free( (*ws)->data_only_engl);
	free(*ws);
}



int main(int argc, char const *argv[])
{
	
#if defined (_WIN32)
	setlocale(LC_ALL, "ru_RU");
#else
	setlocale(LC_ALL, "ru_RU.utf8");
#endif

	printf("sizeof(char)    = %ld\n", sizeof(char) );
	printf("sizeof(char16_t)= %ld\n", sizeof(char16_t) );
	printf("sizeof(wchar_t) = %ld\n", sizeof(wchar_t) );
	printf("\n");


	CSTRUCT* cs = cs_create("Hello! Друг");
	cs_print(cs);
	cs_free(&cs);

	WSTRUCT* ws = ws_create("Hello! Друг");
	ws_print(ws);
	ws_free(&ws);


	return EXIT_SUCCESS;
}

Кратко: есть две структуры одна хранит строки в char*, другая - wchar_t*.

Обе хранят три строки: с чисто англ. символами, со строкой инициализируемой в программе и строкой считываемой из файла:

text.txt

Привет friend из файла!

В итоге под Linux получаю

sizeof(char)    = 1
sizeof(char16_t)= 2
sizeof(wchar_t) = 4

------------------------------|
                  Hello Friend| 12
               Hello! Друг| 15
Привет friend из файла!
| 37
123456789012345678901234567890|
------------------------------|
                  Hello Friend| 12
               Hello! Друг| 11
Привет friend из файла!
| 24
123456789012345678901234567890|

Видно, что выравнивание сработало для строки только с англ. символами.

А хотелось бы:

------------------------------|
                  Hello Friend| 12
                   Hello! Друг| 11
       Привет friend из файла!| 23
123456789012345678901234567890|

Соответственно под Windows имею:

sizeof(char)    = 1
sizeof(char16_t)= 2
sizeof(wchar_t) = 2

------------------------------|
                  Hello Friend| 12
               Hello! Друг| 15
Привет friend из файла!
| 37
123456789012345678901234567890|
------------------------------|
                  Hello Friend| 12
               Hello! Друг| 15
Привет friend из файла!
| 37
123456789012345678901234567890|

Как сделать кроссплатф. вывод на экран с правильным выравниваем?



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

У ICU еще таблицы которые много весят и постоянно обновляются. Обновляют ли их в таких библиотечках? Догадаться легко: либо никогда либо нечасто. И конечно Go-библиотека не будет синхронизированна с другими приложениями которые используют ICU.

MOPKOBKA ★★★★★
()
Последнее исправление: MOPKOBKA (всего исправлений: 2)

а с диакритикой что планируете делать, которая может быть как отдельно от модифицируемой буквы, так и в составе буквы? Че-то я тут в треде не вижу ни одного примера с нормализацией юникода. Без нормализации количество символов самому считать бессмысленно.

Языки сложная штука, юникод соответственно тоже, поэтому и пользуют монстров типа icu.

shimshimshim
()

еще кстати есть символы нулевой ширины, но которые в отличие от диакритики не модифицируют предыдущий символ и не нормализуются, типа zero-width non-breaking space, и символ табуляции переменной ширины. И то и другое периодически встречается в живой природе.

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

Это же всё, раз в год встречается и то не факт. В 99% случаев для людей спрашивающих на этом форуме, будет достаточно кириллицы и латиницы. А если на пару символов разъедется раз в год из-за смайла залетного, так и начихать. А то тред читаешь, так каждый второй перфекционист и каждый день с арабской вязью работает.

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

На самом деле достаточно часто встречается, просто никто не замечает. Вылезает только когда пытаешься токенизировать текст. А если добавить корейский язык, эмодзи, ()⟮⟯ скобки разной ширины и ⅒ vulgar fraction дроби, то все становится еще интереснее.

Вот еще вспомнил, вот эти дроби можно делать не только стандартные, но и вообще любые с помощью Superscript FRACTION SLASH Subscript ⁹⁹⁹⁹⁹⁄₂₂. И делают же! А если в числителе 1, есть отдельный символ ⅟

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

shimshimshim
()
Последнее исправление: shimshimshim (всего исправлений: 7)
Ответ на: комментарий от MOPKOBKA

Активно над этим сижу.

С помощью ф-ций mbstowcs, mbsrtowcs не удалось. Литералы пожалуйста, но если этими ф-циями , то иероглифы и проч.

Я др. идею хочу попробовать. Читать символы и если два байта соотв. русскому символу, то взять его из таблицы. Тогда можно большой switch для русских букв применить.

case {0xd0,0xae} : return L'Ю'; // это псевдокод

Для англ. и др. символов просто копируется один в один.

Т.е. это получается поиск в большой строке 2-байтовых подстрок. Задача поиска подстроки?

Думаю как бы строку читать как массив символов (2-хбайтовых). В этом загвоздка.

Ведь нужно всего то обычные наши ANSI-шные англ + символы умещающиеся в char. Плюс наши русские буквы, 2-xбайтовые.

Народ прям про какие-то иероглифы, араб. вязь пишет.. Для всего что не попадает в то, что вышеописано - return L'*';.

Как-то над этим сейчас думаю.

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

С помощью ф-ций mbstowcs, mbsrtowcs не удалось.

У меня они тоже в Windows не работают. Почему не хочешь использовать то API для Windows что я предложил в последнем примере? Для Linux ты можешь использовать стандартные функции преобразования wchar_t.

Но если ты хочешь по альфавиту преобразовать буквы, то можешь вот так:

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

int to_wchar(const char *s, int n, wchar_t *wch)
{
    int i;
    static const char *ru_table[] = {
        "а", "б", "в", "г", "д", "е", "ж", "з", "и", "й", "к", "л", "м",
        "н", "о", "п", "р", "с", "т", "у", "ф", "х", "ц", "ч", "ш", "щ",
        "ъ", "ы", "ь", "э", "ю", "я", "ё",
        "А", "Б", "В", "Г", "Д", "Е", "Ж", "З", "И", "Й", "К", "Л", "М",
        "Н", "О", "П", "Р", "С", "Т", "У", "Ф", "Х", "Ц", "Ч", "Ш", "Щ",
        "Ъ", "Ы", "Ь", "Э", "Ю", "Я", "Ё"
    };
    static const wchar_t *ru_table_w[] = {
        L"а", L"б", L"в", L"г", L"д", L"е", L"ж", L"з", L"и", L"й", L"к",
        L"л", L"м", L"н", L"о", L"п", L"р", L"с", L"т", L"у", L"ф", L"х",
        L"ц", L"ч", L"ш", L"щ", L"ъ", L"ы", L"ь", L"э", L"ю", L"я", L"ё",
        L"А", L"Б", L"В", L"Г", L"Д", L"Е", L"Ж", L"З", L"И", L"Й", L"К",
        L"Л", L"М", L"Н", L"О", L"П", L"Р", L"С", L"Т", L"У", L"Ф", L"Х",
        L"Ц", L"Ч", L"Ш", L"Щ", L"Ъ", L"Ы", L"Ь", L"Э", L"Ю", L"Я", L"Ё"
    };

    if (n > 1) {
        for (i = 0; i < sizeof(ru_table)/sizeof(const char*); i++) {
            if (s[0] == ru_table[i][0] && s[1] == ru_table[i][1]) {
                *wch = *ru_table_w[i];
                return 2;
            }
        }
    } 

    *wch = s[0];
    return 1;
}

void print_as_wchar(const char *s)
{
    wchar_t wch;
    int i, s_len = strlen(s);

    for (i = 0; i < s_len;) {
        i += to_wchar(&s[i], s_len - i, &wch);
        putwc(wch, stdout);
    }
}

int main(int argc, char **argv)
{
    setlocale(LC_ALL, "");
    print_as_wchar("Привет world!");
    return 0;
}

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

0xd0,0xae

вот это вот 0xD1, 0x91 это русское ё, и вот это 0xD0,0xB5,0xCC,0x88 то же самое русское ё. Оба варианта равнозначны.

Аналогично с й и с их прописными/строчными вариантами. Аналогично с любой европейской диакритикой, а ее там много.

Для хоть как-то работающей реализации сначала надо добиться, чтобы выполнялось str(0xD1, 0x91) == str(0xD0,0xB5,0xCC,0x88) = true, то есть делать нормализацию юникода.

попытки работать с юникодом на полшишечки ничем хорошим не заканчиваются.

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

Гениальный код! Спасибо! Беру.

Но его должен был написать я.

Вы из готовых наработок взяли? Или так быстро написали.

Я топтался на месте, потому что у меня была строка

const char ALPHABET[]="АБВГДЕЖЗИКЛМНОПРСТУФХЦЧШЩЬЫЪЭЮЯабвгдежзиклмнопрстуфхцчшщььыъэюя";

а надо было как массив представлять.

Плюс еще не знал как по массиву бежать, ведь в нем одно- и двух- символы вперемешку. Мне все время казалось, что надо читать текущий + следующий..

Функции mbstowcs, mbsrtowcs работают, но криво, как будто добавляют что-то лишнее (например, символ переноса на нов. строку) + не дают выравнивание. И в Linux, и в Windows они не выравнивают и выводятся только с пом printf, а не wprintf.

Вот так выводит по русски

const char FMT[] = "%30ls|\n";
printf(FMT, str);

Но отказаться от них полностью тоже нельзя.

В Windows сконвертир. функциями mbstowcs или mbsrtowcs рус. строки выводятся, но printf-ом с «%ls». Так что они работают под Windows.

До WinAPI-шной ф-ции еще не дошел, я с этими воевал.

Ф-ция

wchar_t* convert_to_wstr(const char* cstr)
{
    mbstate_t state;
    memset(&state, 0, sizeof(state) );
    size_t out_sz = 1 + mbsrtowcs(NULL, &cstr, 0, &state);
    
    wchar_t* ws = malloc(out_sz*sizeof(wchar_t) );
    mbstowcs(ws, cstr, out_sz);              //(1)
    //mbsrtowcs(ws, &cstr, out_sz, &state);  //(2)
    return ws;
}

работает. Хоть с раскомментир. строкой (1) или (2) (взаимоисключ.)

Не понимаю о какой ошибке вы имеете в виду

В твоем самом первом примере, у тебя неправильно идет конвертация, а потом неправильно идет вывод.

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

Вы из готовых наработок взяли? Или так быстро написали.

Больше практики, и такие «задачи» будут решаться на уровне спинного мозга.

Функции mbstowcs, mbsrtowcs работают, но криво

Да, они работают только под Linux, только с правильно установленной локалью.

mbstowcs, mbsrtowcs ... не дают выравнивание.

Ну, они никак на выравнивание не влияют, выравнивание дает wprintf(), который был показан в самом первом моем пример.

Не понимаю о какой ошибке вы имеете в виду

В твоем самом первом примере, у тебя неправильно идет конвертация, а потом неправильно идет вывод.

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

1. Неправильная установка локали через setlocale, в твоем первом примере что то не то, из за твоей установки даже wprintf правильно не работает.

2. Неправильно идет конвертация между char и wchar_t, как мы уже выяснили mb* функции вообще не работают на Windows, а на Linux они могут работать только если правильно установлена локаль.

3. Неправильно идет выравнивание в wprintf. Как объяснили выше, printf вообще неспособен к правильному выравниванию, wprintf способен, надо использовать только его, для строки форматирования вида «%30ls».

По итогу, что бы получить правильное форматирование:

1. Надо правильно установить локаль в самом начале программы.

2. Надо правильно конвертировать строки из char в wchar_t и обратно.

3. Надо использовать wprintf вместо printf для правильного выравнивания.

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

Прикладываю пример который должен работать везде, и использует две рабочие функции конвертации:

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

#ifdef _WIN32
#include <windows.h>
#endif

// Шаг 2, правильная конвертация
#ifdef _WIN32
wchar_t* cstr2wstr(const char* s)
{
  size_t s_len, ws_len;
  wchar_t *ws;

  s_len = strlen(s);
  ws_len = MultiByteToWideChar(CP_UTF8, 0, s, s_len, NULL, 0);
  if ((ws = malloc(ws_len + 1)) == NULL) {
    wprintf(L"cstr2wstr: low memory\n");
    exit(1);
  }
  MultiByteToWideChar(CP_UTF8, 0, s, s_len, ws, ws_len);
  ws[ws_len] = 0;
  return ws;
}
#else
wchar_t* cstr2wstr(const char* cstr)
{
    size_t out_sz = mbstowcs(NULL, cstr, 0);
    wchar_t* ws = malloc((out_sz + 1) * sizeof(wchar_t));
    mbstowcs(ws, cstr, out_sz);
    ws[out_sz] = 0;
    return ws;
}
#endif

void right_pad(const char *text, int pad_size)
{
    wchar_t *wtext = cstr2wstr(text);

    // Шаг 3, правильный вывод
    wprintf(L"%*ls |\n", pad_size, wtext);
    free(wtext);
}

int main()
{
    setlocale(LC_ALL, ""); // Шаг 1 правильная установка локали
    right_pad("Привет мир!", 30);
    right_pad("Hello World!", 30);
    right_pad("Довольно длинная строка ...", 30);
    return 0;
}

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

А почему у меня вот это не работает

int main(int argc, char const *argv[])
{

#if defined(_WIN32)
	//setlocale(LC_ALL, "ru_RU");
	setlocale(LC_ALL, "");
	printf("Windows\n");
#else
	//setlocale(LC_ALL, "ru_RU.utf8");
	setlocale(LC_ALL, "");
	printf("Linux\n");
#endif
        ...

Не устанавливает локаль! Хотя в какой операционке пишет правильно.

А вот так

int main(int argc, char const *argv[])
{
	/*

#if defined(_WIN32)
	//setlocale(LC_ALL, "ru_RU");
	setlocale(LC_ALL, "");
	printf("Windows\n");
#else
	//setlocale(LC_ALL, "ru_RU.utf8");
	setlocale(LC_ALL, "");
	printf("Linux\n");
#endif
*/
	setlocale(LC_ALL, "");

устанавливает.

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

Локаль устанавливается нормально в обоих случаях. Но заметь что нельзя смешивать printf или wprintf.

Протестируй этот код под Linux, и попробуй поменять printf и wprintf местами:

setlocale(LC_ALL, "");
printf("printf\n");
wprintf(L"wprintf\n");

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

Да, потому что после первого вызова printf нельзя использовать wprintf, и наоборот. Делай так, и все будет хорошо с wprintf:

#if defined(_WIN32)
	setlocale(LC_ALL, "");
	wprintf("Windows\n");
#else
	setlocale(LC_ALL, "");
	wprintf("Linux\n");
#endif

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

Да, со строками не особо удобно работать из чистого С.

А ведь об этом нигде не пишут в книжках по хеллоувордам

Может кто то подскажет источники для пополнения знаний лучше, но я предложу почитать вот это:

1. https://en.cppreference.com/w/c

2. https://sourceware.org/glibc/manual/latest/html_mono/libc.html

3. https://learn.microsoft.com/en-us/cpp/c-runtime-library/c-run-time-library-re...

Можно прямо все читать от начала до конца, там текста не особо много на самом деле.

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

Спасибо! Почитаю, хотя

https://en.cppreference.com/w/c

у меня все время открыто, одна из вкладок

PS Теперь наверное мне придется переправить все свои файлы - заменить printf на wprintf, в т.ч. sprintf на swprintf.

Хотя у меня вся диагностическая инфа идет на англ. Стоит ли?

А вот из файла как читать? в char[] буфер, а потом конвертировать.

Или сразу исп. wide-функции в wchar_t[] буфер?

Тоже дилемма.

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

А вот из файла как читать? в char[] буфер, а потом конвертировать.

С конвертацией, wchar_t непереносим между платформами. На Windows он 2 байта, на Linux он 4 байта. Файл может быть в UTF-16 BE/LE, или UTF-8. Так что только конвертация.

Или сразу исп. wide-функции в wchar_t[] буфер?

А fwread и не существует.

PS Теперь наверное мне придется переправить все свои файлы - заменить printf на wprintf, в т.ч. sprintf на swprintf.

Тут тебе виднее, не знаю что у тебя за программа.

Я бы просто отказался от поддержки старых версий Windows и Wine, если уж запускается и комплиируется нативно. Новые версии поддерживают UTF-8, и можно использовать пример с GNU libunistring, и не пользоваться wchar_t вовсе.

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

Спасибо Большое!

Туман рассеивается.

Я вот что подумал. У меня есть словарь с выбором

#define WIDECHAR_SUPPORT

#if defined (WIDECHAR_SUPPORT)
   #define symbol wchar_t
   #define STRLEN wcslen
   #define STRCMP wcscmp
   #define STRDUP wstr_dup
   #define FPRINTF fwprintf
#else
   #undef symbol
   #undef STRLEN
   #undef STRCMP
   #undef STRDUP
   #undef FPRINTF
   #define symbol char
   #define STRLEN strlen
   #define STRCMP strcmp
   #define STRDUP str_dup
   #define FPRINTF fprintf
#endif

typedef struct UDICTIONARY
{
    symbol **keys;
    void **values;
    size_t capacity;
    size_t size;
    unsigned char num_realloc;
} UDICTIONARY;

И в ф-ция вывода (на экран в т.ч.) содержимого словаря соответсвенно. Так вот она русские буквы выводила(!), а выравнивания не было.

Я то из-за этого панику всю поднял.

void udict_print(FILE *output, UDICTIONARY *dict)
{
  ...
const char FMT2[]="|%30s|%15p|\n";
  ...
#if defined (WIDECHAR_SUPPORT)
				cstr = convert_to_cstr(dict->keys[k]);
				fprintf(output, FMT2, cstr, dict->values[k]);
				free(cstr);
#else
				fprintf(output, FMT2, dict->keys[k], dict->values[k]);
#endif
  ...
}

Но имеет ли смысл так делать? Может сделать один вариант с symbol=wchar_t*

Т.к. если нужны русские буквы, то надо вкл. setlocale, чтобы работали ф-ции преобр. из char* в wchar_t* и обратно.

Это ф-ции mbsrtowcs, mbstowcs, wcsrtombs, wcstombs.

Но если мы вкл. локаль, то нам уже нужно исп. wprintf (и чтобы было ВЫРАВНИВАНИЕ правильное), а значит уже printf работать не будет.

Получается, русские буквы выводятся хорошо, а вся диагностика на англ. уже не будет выводиться printf-ом. Т.е. нужно полностью везде переходить на wprintf.

Это я к тому что я хотел исп. printf для диагностич. сообщений, а когда надо русские выводить, то исп. wprintf. Теперь понимаю, что

  • либо все на англ и printf;
  • англ с рус. но везде wptintf;

Смешать не получиться.

Можно было бы так еще подумать, что допустим буду выводить русс. символы только в файлы, а на экран выводить их не буду, и буду спокойно исп. в программе printf. Но для вывода в файл русский текст придется конвертировать, а значит надо вкл. локаль. И пользоваться только printf, то русс текст будет выводиться (в файл), НО без правильного выравнивания.

Правильно я рассуждаю?

Было бы интересно ваши мысли по этому поводу услышать.

PS. Зря я грешил на эти вышеуказанные ф-ции. Все они норм. делают. А перенос на нов. строку (я ошибочно думал что это одна из них неверно конв. и добавляет что-то) это у меня fgets(buff, 128, input); читал символ ‘\n’, и потом он оказывался в строке с пом. convert_to_wstr(buff).

Так что все работает и выравнивание тоже.

Только в wine нужно исп. setlocale(LC_ALL, «ru_RU»); а в Windows setlocale(LC_ALL, "");

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

Я считаю что вариант где везде один wchar_t намного лучше. Отлаживать, писать, поддерживать два варианта с char и wchar_t это плохой вариант.

Но если рассмотреть «простой» вариант с char, то: Вывод в файл от локали никак не зависит, что fwrite дали то он и запишет. Можно выводить UTF-8 на любых версиях Windows, и у пользователя будет файл с корректным текстом UTF-8. Получается можно установить правильную локаль, использовать printf везде, кроме старых версий Windows и Wine, для них выводить в stdout.txt который пользователь может открыть блокнотом.

Как в случае с printf вывести правильно %30s? Ну в таком синтаксисе никак, ведь внутренний алгоритм считает выравнивание основываясь на char, тогда как русская буква это 2 char, но в терминале русская буква это одна буква. Просто нужно считать размер строки через перевод в wchar_t и самому говорить printf сколько ему нужно сделать отступов.

Проблема %30s не в локали, проблема в том что там не учитываются многобайтовые символы. Но ты их можешь учесть. А wprintf работает только потому что русская буква это 1 wchar_t и алгоритм не нарушается.

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

Мне не нужна ширина широкой строки. Нужна ширина многобайтовой:

int mbwidth(const char* in)
{
    mbstate_t state;
    memset(&state, 0, sizeof state);
    size_t len = mbsrtowcs(NULL, &in, 0, &state);
    return (int)len;
}
Gyros
() автор топика
Последнее исправление: Gyros (всего исправлений: 1)
Ответ на: комментарий от MOPKOBKA
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <wchar.h>
#include <locale.h>

int mbwidth(const char* in)
{
    mbstate_t state;
    memset(&state, 0, sizeof state);
    size_t len =  mbsrtowcs(NULL, &in, 0, &state);
    return (int)len;
}


int main()
{
	setlocale(LC_ALL, "");
	char s1[] = "Hello";
	char s2[] = "Привет Friend";

	size_t l1 = strlen(s1);
	size_t l2 = strlen(s2);
	int w1 = mbwidth(s1);
	int w2 = mbwidth(s2);

	printf("%*s| %2d %2ld\n", 30, s1, w1, l1); //работает; или так
        printf("%30s| %2d %2ld\n", s1, w1, l1); // тоже работает
	printf("%*c%s| %2d %2ld\n", 30-w2, ' ' , s2, w2, l2); // работает

	printf("%30s|\n",  s2); // так выравнивание не работает

	return 0;
}

Соотв. надо проверять если mbwidth(s)!= strlen(s), то выводим с заполнением пробельным символом «%*c%s\n», иначе - обычным способом «%30s\n»

                         Hello|  5  5
                         Hello|  5  5
                 Привет Friend| 13 19
           Привет Friend|
Gyros
() автор топика
Последнее исправление: Gyros (всего исправлений: 2)
Ответ на: комментарий от Gyros

Соотв. надо проверять если mbwidth(s)!= strlen(s), то выводим с заполнением пробельным символом «%*c%s\n», иначе - обычным способом «%30s\n»

Проще мерить через mbwidth и выводить с помощью «%*.c%s\n», вообще забыв о %30s.

Кстати, в отличие от ("%*s", 0, ""), сообщение вида ("%*c", 0, ' ') всегда будет выводить лишний символ.

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

Проверил, действит. выводит лишний пробел при при пустой строке.

Кстати, у вас какая-то точка затесалась перед с, это я понял опечатка.

Т.е. лучше так

printf("%*s%s|\n", 30-mbwidth(str), "" , str);

А если допустим в словаре будут только англ. строки (для к-рых уже известно, что strlen(s) == mbwidth(s) и проще делать «%30s»), а я буду всегда вызывать mbwidth в printf-е. Не будет ли (без проверки mbwidth(s)!= strlen(s) это давать небольшую задержку при форматировании вывода?

Проще написать, чем словами объяснять

if (mbwidth(str)!= strlen(str))
  printf("%*s%s|\n", 30-mbwidth(str), "" , str);
else
  printf("%30s|\n", str);

Просто у меня четко есть словари где одни англ. строки и где англ с рус. смешаны. Так зачем для англ. строк вызывать напрасно mbwidth?

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

Просто посчитай количество вызовов mbwidth

Твой вариант с условием

size_t width = mbwidth(...); // 1x mbwidth
if (width != strlen(...))  // 1x strlen
  printf("%*s%s|\n", 30-width, ...);
else 
  printf("%30s"); 

Мой предлагаемый вариант

size_t width = mbwidth(...); // 1x mbwidth
printf("%*s%s|\n", 30-width, ...);
У меня на один strlen меньше, в остальном так же, только еще сократил количество кода, и веток с условиями которые нужно отлаживать.

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

Согласен, но с точки зрения логики..

Я имел еще в виду, что вот это

printf("%*s%s|\n", 30-width, ...);

будет критическим.

Таймером надо мерить с большим кол-вом строк.

Ладно это уже мелочи.

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

Я имел еще в виду, что вот это
printf(«%*s%s|\n», 30-width, ...);
будет критическим.

Не вижу тут сложных вычислений.

Таймером надо мерить с большим кол-вом строк.

А у тебя в программе будет выводится большое кол-во строк? Если нет, то можно не увлекаться таким.

MOPKOBKA ★★★★★
()