Как мне разрешить обходить функции 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;
}
Выход:
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
; 0
(с errno
установить), если возникает ошибка.
Из-за универсальной поддержки новой строки, 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. Затем для вызова старых функций из нового материала вам необходимо:
- Убедитесь, что вы можете безопасно выполнить преобразование (входные данные могут содержать символы, которые не могут быть представлены в желаемой форме кодовой страницы Windows)...
- Преобразование из UTF-8 в желаемое представление кодовой страницы Windows. Это должно привести к созданию нового буфера / строки в совместимом представлении (копии).
- Вызовите старый код с недавно преобразованным представлением исходного аргумента
- Получите вывод в некотором буфере, он будет в представлении кодовой страницы Windows.
- Так что конвертируйте этот вывод в копию UTF-8.
- Очистите временную копию ввода, исходный буфер вывода от старого кода.
- Верните преобразованную выходную копию UTF-8 в новый код.
И вам нужно будет выполнить обратный танец, если вы хотите вызвать новый код UTF-8 из старого материала.
РЕДАКТИРОВАТЬ: Обратите внимание, что ваша старая система не могла ожидать чисто ASCII, потому что ASCII является 7-битной кодировкой, а UTF-8 явно обратно совместим с этим. Итак, ваша первая задача - исправить ваше понимание того, что именно используется в кодировке.