Fwscanf не может правильно прочитать CSV-файл UTF-8 в C

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

Я пытаюсь прочитать CSV-файл в кодировке UTF-8 на языке C, используя fwscanf, но у меня возникают проблемы с процессом чтения. Файл содержит строки со строкой и значением с плавающей запятой, разделенными запятой. Вот минимальный пример, демонстрирующий проблему:

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

#define MAX_STRING_LENGTH 31

int main() {
    setlocale(LC_ALL, "en_US.UTF-8");
    FILE *file = fopen("input.csv", "r, ccs=UTF-8");
    if (file == NULL) {
        fwprintf(stderr, L"Error opening file.\n");
        return 1;
    }

    wchar_t string[MAX_STRING_LENGTH];
    float frequency;
    int row = 0;

    while (!feof(file)) {
        row++;
        int result = fwscanf(file, L"%30[^,],%f,", string, &frequency);
        
        if (result == 2) {
            wprintf(L"Row %d: String = '%ls', Frequency = %.4f\n", row, string, frequency);
        } else if (result == 1) {
            wprintf(L"Row %d: String = '%ls', Frequency not read\n", row, string);
        } else if (result == EOF) {
            break;
        } else {
            wprintf(L"Error reading row %d\n", row);
            wchar_t c;
            // Skip the rest of the line
            while ((c = fgetwc(file)) != L'\n' && c != WEOF);
        }
    }

    fclose(file);
    return 0;
}

Пример входного файла.csv:

hello,1.0000
world,0.5000
how,0.7500
are,0.2500
you,1.0000
?,0.5000

Ожидаемый результат:

Row 1: String = 'hello', Frequency = 1.0000
Row 2: String = 'world', Frequency = 0.5000
Row 3: String = 'how', Frequency = 0.7500
Row 4: String = 'are', Frequency = 0.2500
Row 5: String = 'you', Frequency = 1.0000
Row 6: String = '?', Frequency = 0.5000

Проблема, с которой я столкнулся, заключается в том, что fwscanf неправильно читает файл. Он либо считывает неверные значения, либо вообще не читает. Я пробовал использовать разные настройки локали и режимы открытия файлов, но проблема не устранена.

while (!feof(file)): stackoverflow.com/questions/5431941/…
chqrlie 03.07.2024 19:44

Прочтите это: Почему « while( !feof(file))» всегда неверно? Кроме того, смешивание [f][w]scanf() с [f]get[w]c() может затруднить кодирование. Просто читайте одну строку за раз, а затем анализируйте ее.

Andrew Henle 03.07.2024 19:47

@chqrlie Здесь другая проблема с функцией: fwscaf генерирует неопределенные значения значений, поскольку они могут быть китайскими или просто значениями NULL.

iPc 03.07.2024 19:50

@iPc: мы согласны.

chqrlie 03.07.2024 19:51

Между , и ccs= не должно быть пробела.

Ian Abbott 04.07.2024 17:44
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
3
5
70
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Аргумент string не соответствует строке формата L"%30[^,],%f,". %[ ожидает указатель на массив char, который получит преобразование широких символов, считанных из потока, в их многобайтовое представление.

Вы хотите выполнить противоположную задачу: преобразовать входной поток байтов в кодировке UTF-8 в широкую строку, то есть: массив wchar_t. Вместо этого вам следует использовать fscanf("%30l[^,],%f,", string, &frequency).

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

Широко ориентированные функции ввода-вывода, такие как fwscanf(), не подходят для вашего случая использования. Они ожидают ввода в виде последовательности расширенных символов (где реализации имеют некоторую свободу определения того, что это означает), но ввод UTF-8 — это не так. Реализации могут различаться, но вполне вероятно, что ваши вызовы fwscanf пытаются прочитать файл, как если бы он был закодирован в UCS-2 (который для этой цели функционально эквивалентен UTF-16). Точно так же ваши вызовы wprintf, вероятно, не выдают выходные данные, закодированные в соответствии с конфигурацией вашего терминала.

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

Ваш ввод UTF-8 лучше всего соответствует первому, а байт-ориентированные функции ввода-вывода лучше всего подходят для их чтения и записи. (И терминал или другое устройство отображения отвечает за интерпретацию кодовых последовательностей для представления соответствующих графических представлений.) В качестве примечания: в C есть литералы UTF-8, начиная с C11, и они соответствуют массивам char.

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


Кроме того,

  • подумайте о том, чтобы не использовать fscanffwscanf), так как их обманчиво сложно использовать правильно. Среди возможных альтернатив — читать построчно с помощью fgets(), а затем анализировать каждую строку с помощью sscanf().

  • while(!feof(file)) всегда неправильный.

Кстати, в C есть литералы UTF-8, начиная с C11, и они соответствуют массивам char. Не совсем: константы и строки с префиксом u8 представляют собой экземпляры и массивы типа char8_t, которые могут отличаться от типа char. Однако строковые литералы в исходных файлах в кодировке UTF-8 поддерживаются всеми компиляторами C как массивы char, если они принимают 8-битный ввод.

chqrlie 03.07.2024 21:43

@chqrlie, в C11, где они были представлены, и в C17, все еще текущей версии стандарта, литералы UTF-8 соответствуют массивам char. char8_t является новым в C2X (и будет типом элементов в литералах UTF-8 в этой версии), но, насколько мне известно, он еще не выпущен. В C2X char8_t имеет тот же тип, что и unsigned char, поэтому определенно отличается от char, даже если char не имеет знака.

John Bollinger 03.07.2024 22:45

Хорошая точка зрения. Это изменение делает эти литералы совершенно бесполезными, поскольку их больше нельзя передавать строковым функциям без приведения. Разрешить потенциальное подписание char было ужасной ошибкой, которая совершенно не соответствовала семантике strcmp() и getchar(). Слишком поздно для предварительного исправления с помощью нового типа char8_t.

chqrlie 03.07.2024 23:21

Другие вопросы по теме