Польские символы в C, используя файлы и списки - PullRequest
0 голосов
/ 15 января 2019

Мне нужно получить правильные польские символы "ąężźćśół". Я использовал некоторые решения, такие как setlocale, system chcp, wchar_t. Все идет хорошо, пока я не использую файлы / списки. wscanf, wprintf и wchar_t отлично работает.

Но если я пытаюсь что-то прочитать из файла и сохранить это в списке (даже в массиве), то при попытке отобразить это на экране я не могу получить правильные польские символы, и в случае списки, время от времени я получаю разные результаты, например, z` , A2 , как случайные символы из ниоткуда. Я пытался получить хорошие результаты, используя fscanf и fgets с w (широкими) вариациями, но это не работает. Неужели я что-то не так?

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

struct dyk{
    wchar_t line[200];                             
    struct dyk *next;                             
};

typedef struct dyk dyk;


void printdyk(char name[100]){
    dyk *wyp;
    wyp = malloc(sizeof(dyk));
    wchar_t yt[100];
    FILE *dyktando;
    dyktando = fopen(name, "r+");
    if(dyktando == NULL){
        wprintf(L"Błąd otwarcia pliku!\n");                 //Can't open file
    }else{
        fgets(&wyp->line, sizeof(dyk), dyktando);           //reading from file and send to the list
        wprintf(L"%s\n", wyp->line);                        //write text from the list on the screen
        wchar_t yt[100];
        wscanf(L"%s", &yt);                                 //testing strings comparing, so I have to put some variables
        int n=strcmp(yt, wyp->line);                        //str compare
        printf("%d", n);                                //result, it gives me -1 every time
    }
    fclose(dyktando);
}

Я протестировал функцию с txt-файлом, который содержит только один символ "ż". Не могу прочитать из файла правильно. В начале основной функции я поставил эти 2 строки:

system("chcp 852");
setlocale(LC_ALL, ".852");

Я использую кодовый блок, компилятор mingw32-gcc и никаких флагов.

Ответы [ 2 ]

0 голосов
/ 16 января 2019

Комментарий OP (Amatheon) указывает, что истинная основная проблема заключается в том, как правильно читать файлы, используя функции широких символов.

Чтобы обеспечить максимальную совместимость и переносимость, давайте ограничимся C99. Рассмотрим следующий пример программы:

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

#ifdef   USE_ERRNO_CONSTANTS
#define  SET_ERRNO(value)  (errno = (value))
#else
#define  SET_ERRNO(value)
#endif

ssize_t get_wide_delimited(wchar_t **lineptr, size_t *sizeptr, wint_t delim, FILE *stream)
{
    wchar_t  *line = NULL;
    size_t    size = 0;
    size_t    used = 0;
    wint_t    wc;

    if (!lineptr || !sizeptr || !stream) {
        /* Invalid function parameters. NULL pointers are not allowed. */
        SET_ERRNO(EINVAL);
        return -1;
    }
    if (ferror(stream)) {
        /* Stream is already in error state. */        
        SET_ERRNO(EIO);
        return -1;
    }

    if (*sizeptr > 0) {
        line = *lineptr;
        size = *sizeptr;
    } else {
        *lineptr = NULL;
    }

    while (1) {

        wc = fgetwc(stream);
        if (wc == WEOF || wc == delim)
            break;

        if (used + 1 > size) {
            /* Growth policy.  We wish to allocate a chunk of memory at once,
               so we don't need to do realloc() too often as it is a bit slow,
               relatively speaking.  On the other hand, we don't want to do
               too large allocations, because that would waste memory.
               Anything that makes 'size' larger than 'used' will work.
            */
            if (used < 254)
                size = 256;
            else
            if (used < 65536)
                size = 2 * used;
            else
                size = (used | 65535) + 65521;

            line = realloc(line, size * sizeof (wchar_t));
            if (!line) {
                /* Out of memory. */
                SET_ERRNO(ENOMEM);
                return -1;
            }

            *lineptr = line;
            *sizeptr = size;
        }

        line[used++] = wc;
    }

    if (wc == WEOF) {
        /* Verify that the WEOF did not indicate a read error. */
        if (ferror(stream)) {
            /* Read error. */
            SET_ERRNO(EIO);
            return -1;
        }
    }

    /* Ensure there is enough room for the delimiter and end-of-string mark. */
    if (used + 2 > size) {
        /* We could reuse the reallocation policy here,
           with the exception that the minimum is used + 2, not used + 1.
           For simplicity, we use the minimum reallocation instead.
        */
        size = used + 2;
        line = realloc(line, size * sizeof (wchar_t));
        if (!line) {
            /* Out of memory. */
            SET_ERRNO(ENOMEM);
            return -1;
        }
        *lineptr = line;
        *sizeptr = size;
    }

    /* Append the delimiter, unless end-of-stream mark. */
    if (wc != WEOF)
        line[used++] = wc;

    /* Append the end-of-string nul wide char,
       but do not include it in the returned length. */
    line[used] = L'\0';

    /* Success! */
    return (ssize_t)used;
}

ssize_t get_wide_line(wchar_t **lineptr, size_t *sizeptr, FILE *stream)
{
    return get_wide_delimited(lineptr, sizeptr, L'\n', stream);
}

int main(int argc, char *argv[])
{
    wchar_t       *line = NULL, *p;
    size_t         size = 0;
    unsigned long  linenum;
    FILE          *in;
    int            arg;

    if (!setlocale(LC_ALL, ""))
        fprintf(stderr, "Warning: Your C library does not support your current locale.\n");
    if (fwide(stdout, 1) < 1)
        fprintf(stderr, "Warning: Your C library does not support wide standard output.\n");

    if (argc < 2 || !strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) {
        fprintf(stderr, "\n");
        fprintf(stderr, "Usage: %s [ -h | --help ]\n", argv[0]);
        fprintf(stderr, "       %s FILENAME [ FILENAME ... ]\n", argv[0]);
        fprintf(stderr, "\n");
        fprintf(stderr, "This program will output the named files, using wide I/O.\n");
        fprintf(stderr, "\n");
        return EXIT_FAILURE;
    }

    for (arg = 1; arg < argc; arg++) {

        in = fopen(argv[arg], "r");
        if (!in) {
            fprintf(stderr, "%s: %s.\n", argv[arg], strerror(errno));
            return EXIT_FAILURE;
        }

        if (fwide(in, 1) < 1) {
            fprintf(stderr, "%s: Wide input is not supported from this file.\n", argv[arg]);
            fclose(in);
            return EXIT_FAILURE;
        }

        linenum = 0;

        while (get_wide_line(&line, &size, in) > 0) {
            linenum++;

            /* We use another pointer to the line for simplicity.
               We must not modify 'line' (except via 'free(line); line=NULL; size=0;'
               or a similar reallocation), because it points to dynamically allocated buffer. */
            p = line;

            /* Remove leading whitespace. */
            while (iswspace(*p))
                p++;

            /* Trim off the line at the first occurrence of newline or carriage return.
               (The line will also end at the first embedded nul wide character, L'\0',
                if the file contains any.) */
            p[wcscspn(p, L"\r\n")] = L'\0';

            wprintf(L"%s: Line %lu: '%ls', %zu characters.\n", argv[arg], linenum, p, wcslen(p));
        }

        if (ferror(in)) {
            fprintf(stderr, "%s: Read error.\n", argv[arg]);
            fclose(in);
            return EXIT_FAILURE;
        }
        if (fclose(in)) {
            fprintf(stderr, "%s: Delayed read error.\n", argv[arg]);
            return EXIT_FAILURE;
        }

        wprintf(L"%s: Total %lu lines read.\n", argv[arg], linenum);
        fflush(stdout);
    }

    free(line);
    line = NULL;
    size = 0;

    return EXIT_SUCCESS;
}

Поскольку константы errno EINVAL, EIO и ENOMEM не определены в стандартах C, get_wide_line() и get_wide_delimited() устанавливают errno только в случае определения значения препроцессора USE_ERRNO_CONSTANTS.

get_wide_line() и get_wide_delimited() являются повторными реализациями функций getwline() и getwdelim() из ISO / IEC TR 24731-2: 2010; эквиваленты широких символов функций POSIX.1 getline() и getdelim(). В отличие от fgets() или fgetws(), они используют динамически распределенный буфер для хранения строки, поэтому нет фиксированных ограничений длины строки, кроме доступной памяти.

Я явно пометил код для Лицензия Creative Commons Zero : Права не защищены . Это означает, что вы можете использовать его в своем собственном коде по любой лицензии.

Примечание. Я бы очень хотел, чтобы пользователи подталкивали своих поставщиков и членов комитета по стандарту C на включение их в стандартную часть библиотеки C в следующей версии стандарта C. Как видно из вышесказанного, они уже могут быть реализованы в стандарте C; просто библиотека C может сделать то же самое гораздо эффективнее. Библиотека GNU C является прекрасным примером этого (хотя даже они тормозят с реализацией из-за отсутствия стандартизации). Подумайте, сколько ошибок из-за переполнения буфера можно было бы избежать, если бы люди использовали getline() / getwdelim() вместо fgets() / fgetws()! И не думайте о том, какой будет максимальная длина строки в каждом случае. Беспроигрышная!

(На самом деле, мы могли бы переключить тип возврата на size_t и использовать 0 вместо -1 в качестве индикатора ошибки. Это ограничило бы изменения в тексте стандарта C добавлением четыре функции.Мне огорчает и раздражает меня из-за того, что столь значительную группу тривиальных функций так бесцельно и невежественно упускают из виду без видимой причины. Пожалуйста, покушайтесь на своих поставщиков и всех членов комитета по стандартам C, к которым у вас есть доступ, так непрестанно и беспощадно, как только можете. И вы, и они этого заслуживают.)

Основные части программы:

  • if (!setlocale(LC_ALL, ""))

    Это говорит библиотеке C использовать локаль, указанную пользователем.

    Пожалуйста, не кодируйте значение языка в ваших программах. В большинстве операционных систем все, что вам нужно сделать, это изменить переменную среды LANG или LC_ALL на локаль, которую вы хотите использовать, перед запуском вашей программы.

    Вы можете подумать, что "ну, я могу жестко закодировать его на этот раз, потому что это язык, используемый для этих данных" , но даже это может быть ошибкой, потому что новые локали могут быть созданы в любом время. Это особенно раздражает, когда часть набора символов жестко закодирована. Например, однобайтовый набор символов ISO 8859, используемый в Западной Европе, - это ISO 8859-15, а не ISO 8859-1, поскольку в ISO 8859-15 есть символ €, а в ISO 8859-1 - нет. Если в вашей программе жестко задан ISO 8859-1, он вообще не может правильно обрабатывать символ €.

  • if (fwide(stream, 1) < 1) как для stdout, так и для файловых дескрипторов

    Хотя внутренняя библиотека C выполняет эквивалент вызова fwide() в зависимости от того, какой тип функции ввода-вывода вы используете для дескриптора файла в самый первый раз, явная проверка намного лучше .

    В частности, если библиотека C не может поддерживать широкий ввод-вывод для файла или потока, представленного дескриптором, fwide() вернет отрицательное значение. (Если второй параметр также не равен нулю, он никогда не должен возвращать ноль; из-за проблем в стандартизации я рекомендую в этом случае использовать строгий подход к проверке возвращаемого значения, чтобы поймать поставщиков, которые решили сделать жизнь программистов как можно более сложной. пытаясь написать переносимый код, в то же время технически выполняя стандартный текст, как это делает Microsoft. Они даже наполнили комитет по стандарту C своими собственными представителями, чтобы они могли настроить C11 на функции, которые они не хотели поддерживать, плюс получить печать одобрения их собственных нестандартных расширений, которые раньше никто не использовал, чтобы помочь создать барьеры для разработчиков, пишущих переносимый код C. Да, я вообще не доверяю их поведению.)

  • ssize_t len = get_wide_line(&line, &size, handle);

    Если вы инициализируете wchar_t *line = NULL; и size_t size = 0; до первого вызова get_wide_line() или get_wide_delimited(), функция будет динамически изменять размер буфера по мере необходимости.

    Возвращаемое значение является отрицательным тогда и только тогда, когда возникает ошибка. (Функции никогда не должны возвращать ноль.)

    Если строка прочитана успешно, возвращаемое значение отражает количество широких символов в буфере, включая разделитель (новая строка, L'\n' для get_wide_delimited()), и всегда является положительным (больше нуля). Содержимое в буфере будет иметь завершающий символ конца строки, L'\0', но оно не учитывается в возвращаемом значении.

    Обратите внимание, что когда разделитель не равен L'\0', буфер может содержать встроенные широкие нулевые символы, L'\0'. В этом случае len > wcslen(line).

    Приведенные выше примеры программ пропускают все начальные пробелы в каждой строке ввода и отключают строку при первом переводе строки (L'\n'), возврате каретки (L'\r') или nul (L'\0'). Из-за этого возвращаемое значение len проверяется только на успех (положительное возвращаемое значение больше нуля).

  • free(line); line = NULL; size = 0;

    Можно отказаться от строки в любой точке, ее содержимое больше не требуется. Я рекомендую явно установить указатель строки в NULL, а размер в ноль, чтобы избежать ошибок использования после освобождения. Кроме того, это позволяет любому следующему get_wide_line() или get_wide_delimited() правильно динамически выделять новый буфер.

  • ferror(handle) после сбоя функции широкого ввода

    Как и в случае узких потоков и EOF, в двух случаях функции широкого ввода могут возвращать WEOF (или возвращать -1, в зависимости от функции): из-за отсутствия ввода или из-за ошибки чтения.

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


Допустим, у вас есть заурядный лектор, который не позволяет вам использовать вышеуказанные функции get_wide_line() или get_wide_delimited().

Не волнуйся. Мы можем реализовать ту же самую программу, используя fgetws(), если мы ограничим линию каким-либо фиксированным верхним пределом (широких символов). Строки длиннее этого будут читаться как две или более строк:

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

#ifndef  MAX_WIDE_LINE_LEN
#define  MAX_WIDE_LINE_LEN  1023
#endif

int main(int argc, char *argv[])
{
    wchar_t        line[MAX_WIDE_LINE_LEN + 1], *p;
    unsigned long  linenum;
    FILE          *in;
    int            arg;

    if (!setlocale(LC_ALL, ""))
        fprintf(stderr, "Warning: Your C library does not support your current locale.\n");
    if (fwide(stdout, 1) < 1)
        fprintf(stderr, "Warning: Your C library does not support wide standard output.\n");

    if (argc < 2 || !strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) {
        fprintf(stderr, "\n");
        fprintf(stderr, "Usage: %s [ -h | --help ]\n", argv[0]);
        fprintf(stderr, "       %s FILENAME [ FILENAME ... ]\n", argv[0]);
        fprintf(stderr, "\n");
        fprintf(stderr, "This program will output the named files, using wide I/O.\n");
        fprintf(stderr, "\n");
        return EXIT_FAILURE;
    }

    for (arg = 1; arg < argc; arg++) {

        in = fopen(argv[arg], "r");
        if (!in) {
            fprintf(stderr, "%s: %s.\n", argv[arg], strerror(errno));
            return EXIT_FAILURE;
        }

        if (fwide(in, 1) < 1) {
            fprintf(stderr, "%s: Wide input is not supported from this file.\n", argv[arg]);
            fclose(in);
            return EXIT_FAILURE;
        }

        linenum = 0;

        while (1) {

            /* If line is an array, (sizeof line / sizeof line[0]) evaluates to
               the number of elements in it.  This does not work if line is a pointer
               to dynamically allocated memory.  In that case, you need to remember
               number of wide characters you allocated for in a separate variable,
               and use that variable here instead. */
            p = fgetws(line, sizeof line / sizeof line[0], in);
            if (!p)
                break;

            /* Have a new line. */
            linenum++;

            /* Remove leading whitespace. */
            while (iswspace(*p))
                p++;

            /* Trim off the line at the first occurrence of newline or carriage return.
               (The line will also end at the first embedded nul wide character, L'\0',
                if the file contains any.) */
            p[wcscspn(p, L"\r\n")] = L'\0';

            wprintf(L"%s: Line %lu: '%ls', %zu characters.\n", argv[arg], linenum, p, wcslen(p));
        }

        if (ferror(in)) {
            fprintf(stderr, "%s: Read error.\n", argv[arg]);
            fclose(in);
            return EXIT_FAILURE;
        }
        if (fclose(in)) {
            fprintf(stderr, "%s: Delayed read error.\n", argv[arg]);
            return EXIT_FAILURE;
        }

        wprintf(L"%s: Total %lu lines read.\n", argv[arg], linenum);
        fflush(stdout);
    }

    return EXIT_SUCCESS;
}

Помимо функции, используемой для чтения каждой строки, разница в том, что вместо того, чтобы сохранять условие цикла while как while ((p = fgetws(line, ...))) { ... }, я перешел на форму while (1) { p = fgetws(line, ...); if (!p) break; ..., которая, на мой взгляд, более читаема.

Я намеренно показал сначала более длинную, более сложную на вид, а вторую - более простую, в надежде, что вы увидите, что у более сложной на самом деле проще main() - если мы не будем просто посчитайте строки кода или что-то столь же глупое, но посмотрите, сколько существует возможностей для ошибок.

Как пишет сам OP в комментарии, размер буфера, переданного в fgets() или fgetws(), является реальной проблемой. Существуют эмпирические правила, но все они страдают от хрупкости в отношении правок (особенно различий между массивами и указателями). При getline() / getdelim() / getwline() / getwdelim() / get_wide_line() / get_wide_delimited() эмпирическое правило равно wchar_t *line = NULL; size_t size = 0; ssize_t len; и len = get_wide_line(&line, &size, handle);. Нет вариаций, а просто запомнить и использовать. Плюс это избавляет от любых фиксированных ограничений.

0 голосов
/ 15 января 2019

Вы не используете wchar_t совместимые функции везде в вашем коде. В частности:

fgets(&wyp->line, sizeof(dyk), dyktando);           //reading from file and send to the list

wchar_t совместимая версия - fgetws. Кроме того, wyp->line (без оператора &) является правильным аргументом.

int n=strcmp(yt, wyp->line);                        //str compare

wcscmp следует использовать вместо.

Также обратите внимание, что sizeof в массиве wchar_t неверен, когда функция ожидает длину в символах , а не байтах (как это делает fgetws).

...