LINUX.ORG.RU

[Си] Как прочитать один Unicode-символ из файла?

 


0

1

Здрасьте :) Вопрос, есть такая функция как fgetwc(...) которая вроде должна считывать Unicode-символ из файла, но на самом деле нифига не считывает, точнее считывает как обычный однобайтовый символ, а потом его преобразует в четырёхбайтовый. При этом нашёл у себя 2 man страницы по теме:

man 3 fgetwc

It reads a wide character from stream and returns it.

man 3p fgetwc

The fgetwc() function shall obtain the next character (if present) from the input stream pointed to by stream, convert that to the corresponding wide-character code, and advance the associated file position indicator for the stream (if defined).

И как бы получается пишут они немного о разном...

И вот например, существует файл /home/maxim/unicode16.txt закодированный в UTF-16, и программа:

// main.c
#include <stdio.h>
#include <wchar.h>
#include <string.h>

int main(void)
{
	printf("Size of wchar_t = %d\n", sizeof(wchar_t)); // 4
	printf("Size of wint_t = %d\n\n", sizeof(wint_t)); // 4

	FILE* f_in = fopen("/home/maxim/unicode16.txt", "r");
	if (!f_in) {
		fprintf(stderr, "Невозможно открыть файл для чтения!\n");
		return -1;
	}

	wint_t wc = 0;

	clearerr(f_in);
	printf("Status: %s\n", strerror(ferror(f_in)));
	while (wc = fgetwc(f_in)) {
		if (ferror(f_in)) {
			fprintf(stderr, "Status: %s\n", strerror(ferror(f_in)));
			break;
		}
		printf("%08X : `%lc'\n", wc, wc);
	}

	fclose(f_in);
	return 0;
}

Результаты её работы:

$gcc -o main main.c && ./main
Size of wchar_t = 4
Size of wint_t = 4

Status: Success
00000001 : `'
00000004 : `'
0000000A : `
'

А вот содержимое unicode16.txt (Там записана буква Ё и перевод строки):

$hexdump -C /home/maxim/unicode16.txt
00000000  01 04 0a 00                                       |....|
00000004

Я ожидал что программа выдаст что-то вроде 00000104 00000A00, а выдаёт она не то. Где подвох, и как мне таки прочитать один символ _юникода_ из файла?

>Где подвох, и как мне таки прочитать один символ _юникода_ из файла?
тётя Сара мне таки подсказывает, что один символ из уникода можно кодировать разным числом байт и что если считать первый байт, то можно понять по нему является ли этот код символом уникода, а если нет, то нужно считать второй байт, опять неудача 3 байт и т.д.

мне казалось, что это так работает

dimon555 ★★★★★
()

Из мана:

> NOTES

> The behavior of fgetwc() depends on the LC_CTYPE category of the current locale.


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

setlocale(LC_CTYPE, "");

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

И это, смысл функции fgetwc в том, что она считывает символ в системной кодировке (не обязательно юникодной) и затем преобразовывает его в юникод. Так что если тебе нужно читать из файла, в котором _всегда_ текст закодирован юникодом независимо от системной кодировки, то придётся писать свои функции. В принципе перекодирования разных юникодных кодировок друг в друга - это очень простая задача, куски кода помоему даже на википедии есть.

Deleted
()

ну, во-первых,

> while (wc = fgetwc(f_in)) {…}

while ((wc = fgetwc(f_in)) != WEOF) {…}

fgetwc(f_in) вернёт нуль только если успешно прочитает '\0' из файла. (теперь «if (ferror(f_in))» можно выкидывать.)

и, во-вторых, юникодов много, а libc с libastral пока не линкуется, так что… fgetwc читает в той кодировке, которая указана в LC_CTYPE. установить её в utf16/utf32 под линуксом, ЕМНИП, нельзя.

могу порекомендовать использовать нечто вроде: uint16_t wc = 0; while (fread(&wc, 2, 1, f_in) == 1) {…}

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

Кажись понял :) Метод setlocale(LC_CTYPE, "ru_RU.UTF16") не канает, но зато setlocale(LC_CTYPE, "ru_RU.UTF8") канает (ну и файл соответственно в UTF-8). Впринципе мы не гордые, UTF-8 меня устроит, надеюсь это будет достаточно кроссплатформенным вариантом, если я захочу кросскомпилировать программу для Windows с использованием MinGW...

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

> Впринципе мы не гордые, UTF-8 меня устроит, надеюсь это будет достаточно кроссплатформенным вариантом

Оно и правильно. UTF-8 хорошо идёт для передачи текста наружу (файлы, сеть...): исключается геморрой с порядком байт и BOM, да и места поменьше жрёт.

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

> Впринципе мы не гордые, UTF-8 меня устроит, надеюсь это будет достаточно кроссплатформенным вариантом, если я захочу кросскомпилировать программу для Windows с использованием MinGW...

дык, если какой именно юникод — не принципиально, то конечно же ютф8. тем более, что ютф16 не все символы пятого юникода поддерживает, а ютф32 слишком «жирный» для хранения/передачи данных.

а по поводу кросс-компиляции под виндой MinGW… скорее всего, не получится. setlocale — часть gnu libc, а не компилятора. но это не так страшно, т.к. кроссплатформенных сырцов функций преобразования ютф8↔ютф32(wchar_t) в интернетах навалом, тысячи их :).

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

>по поводу кросс-компиляции под виндой MinGW… скорее всего, не получится. скорее всего, не получится. setlocale — часть gnu libc, а не компилятора.

может я что-то не понимаю, но man setlocale говорит

CONFORMING TO C89, C99, POSIX.1-2001.

значит это часть стандарта и проблем быть не должно по идее

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