Чтение символов Unicode из файла в C - PullRequest
0 голосов
/ 14 октября 2018

Я пытаюсь прочитать строку UTF-8 из файла .csv, а затем записать ее в консоли.

a.csv содержимое:

Gijón

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

int main(int argc, char *argv[])
{
    char *locale = setlocale(LC_ALL, "");
    printf("locale: %s\n", locale);

    const int MAX_LINE_SIZE = 1024;
    char line[MAX_LINE_SIZE];
    wchar_t wline[MAX_LINE_SIZE];

    // Attempt 0: no special handling
    FILE* stream = fopen("a.csv", "r");
    fgets(line, MAX_LINE_SIZE, stream);
    printf("%s\n", line); // Expected to print "Gijón", prints "Gijón"
    fclose(stream);

    // Attempt 1: mbstowcs
    mbstowcs(wline, line, MAX_LINE_SIZE);
    wprintf(L"%ls\n", wline); // Expected to print "Gijón", prints "Gijón"

    // Attempt 2: fgetws
    stream = fopen("a.csv", "r");
    fgetws(wline, MAX_LINE_SIZE, stream);
    wprintf(L"%ls\n", wline); // Expected to print "Gijón", prints "Gijón"
    fclose(stream);

    // Attempt 3: _wfopen
    stream = _wfopen(L"a.csv", L"rb");
    fgetws(wline, MAX_LINE_SIZE, stream);
    wprintf(L"%ls\n", wline); // Expected to print "Gijón", prints ""
    fclose(stream);

    // Printing command line parameter
    mbstowcs(wline, argv[1], MAX_LINE_SIZE);
    wprintf(L"%ls\n", wline); // Properly prints "Gijón"
}

Но запуск этой программы приводит к:

.\myprogram.exe Gijón
locale: Spanish_Spain.1252
Gijón
Gijón

Gijón

Не думаю, что это проблема самой консоли, поскольку argv[1] преобразование работает нормально.

Чего мне не хватает?

Ответы [ 2 ]

0 голосов
/ 14 октября 2018

wchar_t и функции широких символов (wfopen и т. Д.) Используются главным образом в Windows для обработки Unicode в кодировке UTF16.

UTF8 использует char и те же функции C, совместимые с ASCII (* 1006)* и т. д.) Для чтения UTF8 вы можете использовать те же функции Си для ASCII.

Windows не имеет полной поддержки для чтения и отображения UTF8, поэтому вам необходимо выполнить конвертацию между UTF8 и UTF16, чтобы правильно отображать текст.Windows 10 имеет поддержку UTF8 для консольной Windows, см. Соответствующие темы.

#include <stdio.h>
#include <windows.h>

int main(void)
{
    const char* filename = "a.csv";
    FILE* fp = fopen(filename, "r");
    char buf[1000];
    fgets(buf, sizeof(buf), fp);

    if(strlen(buf) > 2)
        if(strncmp(buf, "\xFF\xFE", 2) == 0)
        {
            printf("UTF16-LE\n");
            fclose(fp);
            fp = fopen(filename, "rb");
            wchar_t wbuf[1000] = { 0 };
            fgets((char*)wbuf, sizeof(buf), fp);
            MessageBoxW(0, wbuf, L"UTF16-LE", 0);
            return 0;
        }

    if(strlen(buf) > 3)
        if(strncmp(buf, "\xEF\xBB\xBF", 3) == 0)
            printf("UTF8 with BOM\n");

    //assume UTF8 and convert to UTF16:
    int size = MultiByteToWideChar(CP_UTF8, 0, buf, -1, NULL, 0);
    wchar_t *utf16 = malloc((size + 1) * sizeof(wchar_t));
    MultiByteToWideChar(CP_UTF8, 0, buf, -1, utf16, size);

    MessageBoxA(0, buf, "ANSI", 0);
    MessageBoxW(0, utf16, L"UTF8 converted", 0);
    return 0;
}

Если исходный файл имеет формат UTF8, то в основном вы рассматриваете его как ASCII.Просто будьте осторожны с такими функциями, как strtok, которые не могут обрабатывать вводимые символы вне диапазона ASCII.Единственное другое осложнение - когда вы пытаетесь распечатать его в Windows.Используйте приведенный ниже пример с пользовательской функцией printf:

void printf_utf8(const char* format, ...)
{
    va_list args;
    va_start(args, format);
    int len = _vscprintf(format, args) + 1; 
    char *buf = malloc(len);
    vsprintf(buf, format, args);

    //convert to UTF16 and print
    int wbuf_size = MultiByteToWideChar(CP_UTF8, 0, buf, -1, NULL, 0);
    wchar_t *wbuf = malloc((wbuf_size + 1) * sizeof(wchar_t));
    MultiByteToWideChar(CP_UTF8, 0, buf, -1, wbuf, wbuf_size);

    DWORD temp;
    HANDLE h = GetStdHandle(STD_OUTPUT_HANDLE);
    WriteConsoleW(h, wbuf, wcslen(wbuf), &temp, 0);

    free(wbuf);
    free(buf);
}

int main(void)
{
    FILE* fp = fopen("a.csv", "r");
    if(!fp)
        return 0;
    char buf[1000];
    fgets(buf, sizeof(buf), fp);
    printf_utf8("Test %s %d\n", buf, 123);
    return 0;
}
0 голосов
/ 14 октября 2018

Я думаю, вам нужно преобразовать широкие символы в кодировку 1252.Кодировка 1252 является кодировкой 8 бит / символ и будет поддерживать только небольшое подмножество символов Юникода.Возможно, есть функции преобразования / библиотеки.Но, кажется, это не слишком сложно, чтобы написать это самостоятельно (большое предложение switch / case).

...