Как мне разрешить обходить функции DOS, которые использовали строки, содержащие символы с акцентом (от ASCII до UTF-8)?

Я писал SW, где хотел использовать старый код C, написанный в начале 80-х. Этот код сделал некоторое преобразование в строках. Также использовались акцентированные символы, которые в то время (DOS) были закодированы в таблице ASCII (коды больше 127).

Теперь новые системы используют кодировку UTF-8, поэтому старый код работает очень плохо. Я использую Linux (Ubuntu 17 / gcc gcc (Ubuntu 7.2.0-8ubuntu3) 7.2.0).

Я ищу обходной путь, позволяющий мне вносить как можно меньше изменений. Я начал делать некоторые тесты, чтобы проанализировать возникшие проблемы. Я сделал два main: один используетchar *струны иchar элементы, другое использование wchar_t * струны и wchar_t элементы. Оба не работают правильно.

Первый (используя char * а также char) требует, например, обходного пути, когда strchr распознает многобайтовый код, не печатает (printf) многобайтовый символ в правильном порядке, хотя Althoug печатает правильно char *, Кроме того, генерирует много предупреждений, связанных с использованием многобайтовых символов.

Второй (используя wchar_t * а также char *) работает, но не печатает правильно многобайтовые символы, они отображаются как '?' и когда они печатаются как wchar_t и как wchar_t * (строки).

MAIN1:

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

/* http://clc-wiki.net/wiki/strchr
 * standard C implementation
 */
char *_strchr(const char *s, int c);

char *_strchr(const char *s, int c)
{
    while (*s != (char)c)
        if (!*s++)
            return 0;
    return (char *)s;
}


int main()
{
    char          * p1 = NULL;
    const char    * t1 = "Sergio è un Italiano e andò via!";

    printf("Text --> %s\n\n",t1);

    for(size_t i=0;i<strlen(t1);i++) {
        printf("%02X %c|",(uint8_t)t1[i],t1[i]);
    }
    puts("\n");

    puts("Searching ò");
    /*warning: multi-character character constant [-Wmultichar]
                      p1 = strchr(t1,'ò');
                                     ^~~~
    */
    p1 = strchr(t1,'ò');
    printf("%s\n",p1-1); // -1 needs to correct the position

    /*warning: multi-character character constant [-Wmultichar]
                      p1 = _strchr(t1,'ò');
                                     ^~~~
    */
    p1 = _strchr(t1,'ò');
    printf("%s\n",p1-1);    // -1 needs to correct the position
    puts("");

    puts("Searching è");
    /*warning: multi-character character constant [-Wmultichar]
                      p1 = strchr(t1,'è');
                                     ^~~~
    */
    p1 = strchr(t1,'è');
    printf("%s\n",p1-1);    // -1 needs to correct the position

    /*warning: multi-character character constant [-Wmultichar]
                      p1 = _strchr(t1,'è');
                                     ^~~~
    */
    p1 = _strchr(t1,'è');
    printf("%s\n",p1-1);    // -1 needs to correct the position
    puts("");

    /*warning: multi-character character constant [-Wmultichar]
         printf("%c %c %08X %08X\n",'è','ò','è','ò');
                                    ^~~~
         printf("%c %c %08X %08X\n",'è','ò','è','ò');
                                        ^~~~
         printf("%c %c %08X %08X\n",'è','ò','è','ò');
                                            ^~~~
         printf("%c %c %08X %08X\n",'è','ò','è','ò');
                                                ^~~~
    */
    printf("%c %c %08X %08X\n",'è','ò','è','ò');

    /*multi-character character constant [-Wmultichar]
     printf("%c %c %08X %08X\n",'è','ò',(uint8_t)'è',(uint8_t)'ò');
                                ^~~~
     printf("%c %c %08X %08X\n",'è','ò',(uint8_t)'è',(uint8_t)'ò');
                                    ^~~~
     printf("%c %c %08X %08X\n",'è','ò',(uint8_t)'è',(uint8_t)'ò');
                                                 ^~~~
     printf("%c %c %08X %08X\n",'è','ò',(uint8_t)'è',(uint8_t)'ò');
                                                              ^~~~
    */
    printf("%c %c %08X %08X\n",'è','ò',(uint8_t)'è',(uint8_t)'ò');

    puts("");
    return 0;
}

Выход:

ГЛАВНЫЙ Выход

MAIN2:

#include <stdio.h>
#include <string.h>
#include <wchar.h>
#include <inttypes.h>

#define wputs(s) wprintf(s"\n")

/* https://opensource.apple.com/source/Libc/Libc-498.1.1/string/wcschr-fbsd.c
 * FBSD C implementation
 */
wchar_t * _wcschr(const wchar_t *s, wchar_t c);

wchar_t * _wcschr(const wchar_t *s, wchar_t c)
{
    while (*s != c && *s != L'\0')
        s++;
    if (*s == c)
        return ((wchar_t *)s);
    return (NULL);
}

int main()
{
    wchar_t       * p1 = NULL;
    const wchar_t * t1 = L"Sergio è un Italiano e andò via!";
    const wchar_t * f0 = L"%02X %c|";
    const wchar_t * f1 = L"Text --> %ls\n\n";
    const wchar_t * f2 = L"%ls\n";

    uint8_t * p = (uint8_t *)t1;

    wprintf(f1,t1);

    for(size_t i=0;;i++) {
        uint8_t c=*(p+i);

        wprintf(f0,c,(c<' ')?'.':(c>127)?'*':c);
        if ( c=='!' )
            break;
    }
    wputs(L"\n");

    wputs(L"Searching ò");

    p1 = wcschr(t1,L'ò');
    wprintf(f2,p1);

    p1 = _wcschr(t1,L'ò');
    wprintf(f2,p1);
    wputs(L"---");

    wputs(L"Searching è");

    p1 = wcschr(t1,L'è');
    wprintf(f2,p1);

    p1 = _wcschr(t1,L'è');
    wprintf(f2,p1);
    wputs(L"");

    wprintf(L"%lc %lc %08X %08X\n",L'è',L'ò',L'è',L'ò');
    wprintf(L"%lc %lc %08X %08X\n",L'è',L'ò',(uint8_t)L'è',(uint8_t)L'ò');

    wputs(L"");

    return 0;
}

Выход:

MAIN2 выход

2 ответа

Решение

Вам нужно локализовать вашу программу, если вы хотите использовать широкополосный ввод / вывод. Это не сложно, просто setlocale() звонок, плюс опционально fwide() чтобы увидеть, поддерживает ли пользовательский языковой стандарт широкий ввод-вывод для нужного потока.

В вашем main() перед любым вводом / выводом запустите

    if (!setlocale(LC_ALL, "")) {
        /* Current locale is not supported
           by the C library; abort. */
    }

Как говорится в комментарии, это говорит вашей C-библиотеке, что эта программа ориентирована на локаль, и что она должна выполнить настройку и подготовку, необходимые для соблюдения правил локали, установленных пользователем. Смотрите man 7 locale для дальнейшей информации. По сути, библиотека C не автоматически выбирает текущую локаль, которую установил пользователь, а использует локаль C/POSIX по умолчанию. Эта команда указывает библиотеке C попытаться соответствовать текущей настроенной локали.

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

    if (fwide(stdout, 1) <= 0) {
        /* The C library does not support wide-character
           orientation for standard output in this locale.
           Abort.
        */
    }

после настройки локали означает, что вы можете определить, не поддерживает ли библиотека C локаль пользователя или вообще не поддерживает широкие символы для этого конкретного потока; и прервать программу. (Всегда лучше сказать пользователю, что результаты будут мусором, чем молча попытаться сделать все возможное и, возможно, исказить пользовательские данные. В конце концов, пользователь может всегда использовать другой инструмент; но молча искажать пользовательские данные означает, что этот конкретный инструмент просто не заслуживает доверия: ничего не стоит.)

Вы не должны смешивать wprintf() а также printf(); ни fwprintf() а также fprintf() в тот же поток. Он либо дает сбой (ничего не печатает), сбивает с толку библиотеку C или выдает искаженные результаты. Точно так же вы не должны смешивать fgetc() а также fgetwc() в том же потоке. Проще говоря, вы не должны смешивать байтовые или широко-ориентированные функции в одном потоке.

Это не означает, что вы не можете напечатать байтово-ориентированную (или многобайтовую) строку в широко-ориентированном потоке или наоборот; наоборот. Это работает очень логично, %s а также %c всегда ссылаются на байтовую строку или символ, и %ls а также %lc широкая строка или символ. Например, если у вас есть

const wchar_t *ws = L"Hello";
const char     *s = "world!";

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

printf("%ls, %s\n", ws, s);

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

wprintf(L"%ls, %s\n", ws, s);

Это в основном ограничение в библиотеке POSIX C: вы должны использовать байтово-ориентированные функции для байтово-ориентированных потоков и широко-ориентированные функции для широко-ориентированных потоков. Сначала это может показаться странным, но если подумать, это очень ясное и простое правило.


Давайте рассмотрим пример программы, примерно похожей на вашу; расширен для чтения строк (неограниченной длины) строка за строкой из стандартного ввода с использованием любого соглашения новой строки (CR, LF, CRLF, LFCR):

#define _POSIX_C_SOURCE 200809L
#include <stdlib.h>
#include <locale.h>
#include <wchar.h>
#include <string.h>
#include <errno.h>
#include <stdio.h>

/* Function to read a wide-character line,
   using any newline convention, skipping embedded NUL bytes (L'\0'),
   and dynamically reallocating the buffer as needed.
   If *lineptr==NULL and *sizeptr==0, the buffer is dynamically allocated.
   Returns the number of wide characters read.
   If an error occurs, returns zero, with errno set.
   At end of input, returns zero, with errno zero.
*/
size_t wide_line(wchar_t **lineptr, size_t *sizeptr, FILE *in)
{
    wchar_t *line;
    size_t   size, used = 0;
    wint_t   wc;

    if (!lineptr || !sizeptr) {
        errno = EINVAL;
        return 0;
    }
    if (ferror(in)) {
        errno = EIO;
        return 0;
    }

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

    while (1) {

        if (used + 3 >= size) {
            /* Conservative dynamic memory reallocation policy. */
            if (used < 126)
                size = 128;
            else
            if (used < 2097152)
                size = (used * 3) / 2;
            else
                size = (used | 1048575) + 1048579;

            /* Check for size overflow. */
            if (used + 2 >= size) {
                errno = ENOMEM;
                return 0;
            }

            line = realloc(line, size * sizeof line[0]);
            if (!line) {
                errno = ENOMEM;
                return 0;
            }

            *lineptr = line;
            *sizeptr = size;
        }

        wc = fgetwc(in);
        if (wc == WEOF) {
            line[used] = L'\0';
            errno = 0;
            return used;

        } else
        if (wc == L'\n') {
            line[used++] = L'\n';

            wc = fgetwc(in);
            if (wc == L'\r')
                line[used++] = L'\r';
            else
            if (wc != WEOF)
                ungetwc(wc, in);

            line[used] = L'\0';
            errno = 0;
            return used;

        } else
        if (wc == L'\r') {
            line[used++] = L'\r';

            wc = fgetwc(in);
            if (wc == L'\n')
                line[used++] = L'\n';
            else
            if (wc != WEOF)
                ungetwc(wc, in);

            line[used] = L'\0';
            errno = 0;
            return used;
        } else
        if (wc != L'\0')
            line[used++] = wc;
    }
}

/* Returns a dynamically allocated wide string,
   with contents from a multibyte string. */
wchar_t *dup_mbstowcs(const char *src)
{
    if (src && *src) {
        wchar_t *dst;
        size_t   len, check;

        len = mbstowcs(NULL, src, 0);
        if (len == (size_t)-1) {
            errno = EILSEQ;
            return NULL;
        }

        dst = malloc((len + 1) * sizeof *dst);
        if (!dst) {
            errno = ENOMEM;
            return NULL;
        }

        check = mbstowcs(dst, src, len + 1);
        if (check != len) {
            free(dst);
            errno = EILSEQ;
            return NULL;
        }

        /* Be paranoid, and ensure the string is terminated. */
        dst[len] = L'\0';
        return dst;

    } else {
        wchar_t *empty;

        empty = malloc(sizeof *empty);
        if (!empty) {
            errno = ENOMEM;
            return NULL;
        }

        *empty = L'\0';
        return empty;
    }
}

int main(int argc, char *argv[])
{
    wchar_t **argw;
    wchar_t  *line = NULL;
    size_t    size = 0;
    size_t    len;
    int       arg;

    if (!setlocale(LC_ALL, "")) {
        fprintf(stderr, "Current locale is unsupported.\n");
        return EXIT_FAILURE;
    }

    if (fwide(stdin, 1) <= 0) {
        fprintf(stderr, "Standard input does not support wide characters.\n");
        return EXIT_FAILURE;
    }

    if (fwide(stdout, 1) <= 0) {
        fprintf(stderr, "Standard output does not support wide characters.\n");
        return EXIT_FAILURE;
    }

    if (argc < 2) {
        fprintf(stderr, "\n");
        fprintf(stderr, "Usage: %s WIDE-CHARACTER [ WIDE-CHARACTER ... ]\n", argv[0]);
        fprintf(stderr, "\n");
        fprintf(stderr, "This program will look for the first instance of each wide character\n");
        fprintf(stderr, "in each line of input.\n");
        return EXIT_SUCCESS;
    }

    /* Convert command-line arguments to wide character strings. */
    argw = malloc((size_t)(argc + 1) * sizeof *argw);
    if (!argw) {
        fprintf(stderr, "Out of memory.\n");
        return EXIT_FAILURE;
    }
    for (arg = 0; arg < argc; arg++) {
        argw[arg] = dup_mbstowcs(argv[arg]);
        if (!argw[arg]) {
            fprintf(stderr, "Error converting argv[%d]: %s.\n", arg, strerror(errno));
            return EXIT_FAILURE;
        }
    }
    argw[argc] = NULL;

    while (1) {

        len = wide_line(&line, &size, stdin);
        if (!len) {
            if (errno) {
                fprintf(stderr, "Error reading standard input: %s.\n", strerror(errno));
                return EXIT_FAILURE;
            } else
            if (ferror(stdin)) {
                fprintf(stderr, "Error reading standard input.\n");
                return EXIT_FAILURE;
            }
            /* It was just an end of file, no error. */
            break;
        }

        for (arg = 1; arg < argc; arg++)
            if (argw[arg][0] != L'\0') {
                wchar_t  *pos = wcschr(line, argw[arg][0]);
                if (pos) {
                    size_t  i = (size_t)(pos - line);

                    fputws(line, stdout);
                    wprintf(L"%*lc\n", (int)(i + 1), argw[arg][0]);
                }
            }

    }

    /* Because we are exiting the program,
       we don't *need* to free the line buffer we used.
       However, this is completely safe,
       and this is the way you should free the buffer. */
    free(line);
    line = NULL;
    size = 0;

    return EXIT_SUCCESS;
}

Потому что POSIX не стандартизировал широкоформатную версию getline() Мы реализуем наш собственный вариант как wide_line(), Он поддерживает все четыре соглашения новой строки и возвращает size_t; 0errno установить), если возникает ошибка.

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

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

dup_mbstowcs() Функция наиболее полезна, когда параметры командной строки необходимы как строки широких символов. Он просто выполняет преобразование в динамически распределяемый буфер. По существу, argw[] это широкоформатная копия argv[] массив.

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

Если вы сохраните выше, как example.c, вы можете скомпилировать его, например,

gcc -Wall -O2 example.c -o example

Если вы бежите, например,

printf 'Sergio è un Italiano e andò via!\n' | ./example 'o' 'ò' 'è'

выход будет

Sergio è un Italiano e andò via!
     o
Sergio è un Italiano e andò via!
                          ò
Sergio è un Italiano e andò via!
       è

Отступ "трюк" в том, что если i это позиция, в которой вы хотите печатать широкий символ, тогда (i+1) ширина этого логического поля. Когда мы используем * как поле ширины в спецификации печати, ширина читается из int параметр, предшествующий фактическому параметру, который будет напечатан.

Вам нужно конвертировать в и из ожидаемых кодировок символов. Скажем, старая система ожидает некоторую кодовую страницу Windows, а новый код ожидает UTF-8. Затем для вызова старых функций из нового материала вам необходимо:

  1. Убедитесь, что вы можете безопасно выполнить преобразование (входные данные могут содержать символы, которые не могут быть представлены в желаемой форме кодовой страницы Windows)...
  2. Преобразование из UTF-8 в желаемое представление кодовой страницы Windows. Это должно привести к созданию нового буфера / строки в совместимом представлении (копии).
  3. Вызовите старый код с недавно преобразованным представлением исходного аргумента
  4. Получите вывод в некотором буфере, он будет в представлении кодовой страницы Windows.
  5. Так что конвертируйте этот вывод в копию UTF-8.
  6. Очистите временную копию ввода, исходный буфер вывода от старого кода.
  7. Верните преобразованную выходную копию UTF-8 в новый код.

И вам нужно будет выполнить обратный танец, если вы хотите вызвать новый код UTF-8 из старого материала.

РЕДАКТИРОВАТЬ: Обратите внимание, что ваша старая система не могла ожидать чисто ASCII, потому что ASCII является 7-битной кодировкой, а UTF-8 явно обратно совместим с этим. Итак, ваша первая задача - исправить ваше понимание того, что именно используется в кодировке.

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