Возможно ли "заставить" UTF-8 в программе на C?

58
8

Обычно, когда я хочу, чтобы моя программа использовала кодировку UTF-8, я пишу setlocale (LC_ALL, ""); , Но сегодня я обнаружил, что он просто устанавливает локализацию по умолчанию для локали среды, и я не знаю, использует ли среда UTF-8 по умолчанию.

Интересно, есть ли способ заставить кодировку символов быть UTF-8? Кроме того, есть ли способ проверить, использует ли моя программа UTF-8?

спросил(а) 2016-03-23T19:45:00+03:00 4 года, 6 месяцев назад
1
Решение
57

Это возможно, но это совершенно неправильно.

Прежде всего, текущий язык предназначен для пользователя. Это не только набор символов, но и язык, форматы даты и времени и т.д. У вашей программы абсолютно нет "права", чтобы с ней справиться.

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

На самом деле, вы не должны полагаться на то, что UTF-8 является текущей кодировкой, но использует широкую поддержку символов, включая такие функции, как wctype(), mbstowcs() и т.д. Системы POSIXy также предоставляют iconv_open() и iconv() в своих библиотеках C для преобразования между кодировками (которые всегда должны включать преобразование в и из wchar_t); в Windows вам потребуется отдельная библиотека libiconv версии. Так, например, компилятор GCC обрабатывает разные наборы символов. (Внутри он использует Unicode/UTF-8, но если вы его попросите, он может сделать необходимые преобразования для работы с другими наборами символов.)

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

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

Если ОП изменяет свой вопрос, чтобы точно указать, какая проблема, переопределяющая набор символов, должна решаться, я хочу показать, как правильно использовать вышеупомянутые утилиты и объекты POSIX (или эквивалентные свободно доступные библиотеки в Windows).

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

Пример 1. Локализованный с Новым годом!

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

int main(void)
{
/* We wish to use the user current locale. */
setlocale(LC_ALL, "");

/* We intend to use wide functions on standard output. */
fwide(stdout, 1);

/* For Windows compatibility, print out a Byte Order Mark.
* If you save the output to a file, this helps tell Windows
* applications that the file is Unicode.
* Other systems don't need it nor use it.
*/
fputwc(L'\uFEFF', stdout);

wprintf(L"Happy New Year!\n");
wprintf(L"С новым годом!\n");
wprintf(L"新年好!\n");
wprintf(L"賀正!\n");
wprintf(L"¡Feliz año nuevo!\n");
wprintf(L"Hyvää uutta vuotta!\n");

return EXIT_SUCCESS;
}

Заметим, что wprintf() принимает широкую строку (широкие строковые константы имеют форму L"", широкие символьные константы L'', в отличие от обычных/узких копий "" и ''). Форматы все те же; %s печатает нормальную/узкую строку, а %ls - широкую строку.

Пример 2. Чтение строк ввода со стандартного ввода и, при необходимости, сохранение их в файл. Имя файла предоставляется в командной строке.

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

typedef enum {
TRIM_LEFT = 1, /* Remove leading whitespace and control characters */
TRIM_RIGHT = 2, /* Remove trailing whitespace and control characters */
TRIM_NEWLINE = 4, /* Remove newline at end of line */
TRIM = 7, /* Remove leading and trailing whitespace and control characters */
OMIT_NUL = 8, /* Skip NUL characters (embedded binary zeros, L'\0') */
OMIT_CONTROLS = 16, /* Skip control characters */
CLEANUP = 31, /* All of the above. */
COMBINE_LWS = 32, /* Combine all whitespace into a single space */
} trim_opts;

/* Read an unlimited-length line from a wide input stream.
*
* This function takes a pointer to a wide string pointer,
* pointer to the number of wide characters dynamically allocated for it,
* the stream to read from, and a set of options on how to treat the line.
*
* If an error occurs, this will return 0 with errno set to nonzero error number.
* Use strerror(errno) to obtain the error description (as a narrow string).
*
* If there is no more data to read from the stream,
* this will return 0 with errno 0, and feof(stream) will return true.
*
* If an empty line is read,
* this will return 0 with errno 0, but feof(stream) will return false.
*
* Typically, you initialize variables like
* wchar_t *line = NULL;
* size_t size = 0;
* before calling this function, so that subsequent calls the same, dynamically
* allocated buffer for the line, and it is automatically grown if necessary.
* There are no built-in limits to line lengths this way.
*/
size_t getwline(wchar_t **const lineptr,
size_t *const sizeptr,
FILE *const in,
trim_opts const trimming)
{
wchar_t *line;
size_t size;
size_t used = 0;
wint_t wc;
fpos_t startpos;
int seekable;

if (lineptr == NULL || sizeptr == NULL || in == NULL) {
errno = EINVAL;
return 0;
}

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

/* In error cases, we can try and get back to this position
* in the input stream, as we cannot really return the data
* read thus far. However, some streams like pipes are not seekable,
* so in those cases we should not even try.
* Use (seekable) as a flag to remember if we should try.
*/
if (fgetpos(in, &startpos) == 0)
seekable = 1;
else
seekable = 0;

while (1) {

/* When we read a wide character from a wide stream,
* fgetwc() will return WEOF with errno set if an error occurs.
* However, fgetwc() will return WEOF with errno *unchanged*
* if there is no more input in the stream.
* To detect which of the two happened, we need to clear errno
* first.
*/
errno = 0;
wc = fgetwc(in);
if (wc == WEOF) {
if (errno) {
const int saved_errno = errno;
if (seekable)
fsetpos(in, &startpos);
errno = saved_errno;
return 0;
}
if (ferror(in)) {
if (seekable)
fsetpos(in, &startpos);
errno = EIO;
return 0;
}
break;
}

/* Dynamically grow line buffer if necessary.
* We need room for the current wide character,
* plus at least the end-of-string mark, L'\0'.
*/
if (used + 2 > size) {
/* Size policy. This can be anything you see fit,
* as long as it yields size >= used + 2.
*
* This one increments size to next multiple of
* 1024 (minus 16). It works well in practice,
* but do not think of it as the "best" way.
* It is just a robust choice.
*/
size = (used | 1023) + 1009;
line = realloc(line, size * sizeof line[0]);
if (!line) {
/* Memory allocation failed. */
if (seekable)
fsetpos(in, &startpos);
errno = ENOMEM;
return 0;
}
*lineptr = line;
*sizeptr = size;
}

/* Append character to buffer. */
if (!trimming)
line[used++] = wc;
else {
/* Check if we have reasons to NOT add the character to buffer. */
do {
/* Omit NUL if asked to. */
if (trimming & OMIT_NUL)
if (wc == L'\0')
break;

/* Omit controls if asked to. */
if (trimming & OMIT_CONTROLS)
if (iswcntrl(wc))
break;

/* If we are at start of line, and we are left-trimming,
* only graphs (printable non-whitespace characters) are added. */
if (trimming & TRIM_LEFT)
if (wc == L'\0' || !iswgraph(wc))
break;

/* Combine whitespaces if asked to. */
if (trimming & COMBINE_LWS)
if (iswspace(wc)) {
if (used > 0 && line[used-1] == L' ')
break;
else
wc = L' ';
}

/* Okay, add the character to buffer. */
line[used++] = wc;

} while (0);
}

/* End of the line? */
if (wc == L'\n')
break;
}

/* The above loop will only end (break out)
* if end of line or end of input was found,
* and no error occurred.
*/

/* Trim right if asked to. */
if (trimming & TRIM_RIGHT)
while (used > 0 && iswspace(line[used-1]))
--used;
else
if (trimming & TRIM_NEWLINE)
while (used > 0 && (line[used-1] == L'\r' || line[used-1] == L'\n'))
--used;

/* Ensure we have room for end-of-string L'\0'. */
if (used >= size) {
size = used + 1;
line = realloc(line, size * sizeof line[0]);
if (!line) {
if (seekable)
fsetpos(in, &startpos);
errno = ENOMEM;
return 0;
}
*lineptr = line;
*sizeptr = size;
}

/* Add end of string mark. */
line[used] = L'\0';

/* Successful return. */
errno = 0;
return used;
}

/* Counts the number of wide characters in 'alpha' class.
*/
size_t count_letters(const wchar_t *ws)
{
size_t count = 0;
if (ws)
while (*ws != L'\0')
if (iswalpha(*(ws++)))
count++;
return count;
}

int main(int argc, char *argv[])
{
FILE *out;

wchar_t *line = NULL;
size_t size = 0;
size_t len;

setlocale(LC_ALL, "");

/* Standard input and output should use wide characters. */
fwide(stdin, 1);
fwide(stdout, 1);

/* Check if the user asked for help. */
if (argc < 2 || argc > 3 || strcmp(argv[1], "-h") == 0 || strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "/?") == 0) {
fprintf(stderr, "\n");
fprintf(stderr, "Usage: %s [ -h | --help | /? ]\n", argv[0]);
fprintf(stderr, " %s FILENAME [ PROMPT ]\n", argv[0]);
fprintf(stderr, "\n");
fprintf(stderr, "The program will read input lines until an only '.' is supplied.\n");
fprintf(stderr, "If you do not want to save the output to a file,\n");
fprintf(stderr, "use '-' as the FILENAME.\n");
fprintf(stderr, "\n");
return EXIT_SUCCESS;
}

/* Open file for output, unless it is "-". */
if (strcmp(argv[1], "-") == 0)
out = NULL; /* No output to file */
else {
out = fopen(argv[1], "w");
if (out == NULL) {
fprintf(stderr, "%s: %s.\n", argv[1], strerror(errno));
return EXIT_FAILURE;
}

/* The output file is used with wide strings. */
fwide(out, 1);
}

while (1) {

/* Prompt? Note: our prompt string is narrow, but stdout is wide. */
if (argc > 2) {
wprintf(L"%s\n", argv[2]);
fflush(stdout);
}

len = getwline(&line, &size, stdin, CLEANUP);
if (len == 0) {
if (errno) {
fprintf(stderr, "Error reading standard input: %s.\n", strerror(errno));
break;
}
if (feof(stdin))
break;
}

/* The user does not wish to supply more lines? */
if (wcscmp(line, L".") == 0)
break;

/* Print the line to the file. */
if (out != NULL) {
fputws(line, out);
fputwc(L'\n', out);
}

/* Tell the user what we read. */
wprintf(L"Received %lu wide characters, %lu of which were letterlike.\n",
(unsigned long)len, (unsigned long)count_letters(line));
fflush(stdout);
}

/* The line buffer is no longer needed, so we can discard it.
* Note that free(NULL) is safe, so we do not need to check.
*/
free(line);

/* I personally also like to reset the variables.
* It helps with debugging, and to avoid reuse-after-free() errors. */
line = NULL;
size = 0;

return EXIT_SUCCESS;
}

Функция getwline() выше в значительной степени находится в самом сложном конце функций, которые вам могут понадобиться при работе с локализованной широкой поддержкой символов. Он позволяет вам читать локализованные строки ввода без ограничений по длине и, возможно, обрезать и очистить (удалить управляющие коды и внедренные двоичные нули) возвращенную строку. Он также отлично работает с кодировками LF и CR-LF (\n и \r\n).

ответил(а) 2016-03-23T23:26:00+03:00 4 года, 6 месяцев назад
70

Пытаться:

setlocale(LC_ALL, "en_US.UTF-8");

Вы можете запустить locale -a в терминале, чтобы получить полный список локалей, поддерживаемых вашей системой ("en_US.UTF-8" должен поддерживаться большинством/всеми поддерживающими системами UTF-8).

EDIT 1 (альтернативное правописание)

В комментариях Ли указывает, что некоторые системы имеют альтернативное правописание "en_US.utf8" (что меня удивило, но каждый день мы изучаем новые вещи).

Поскольку setlocale возвращает NULL, когда он терпит неудачу, вы можете связать эти вызовы:

if(!setlocale(LC_ALL, "en_US.UTF-8") && !setlocale(LC_ALL, "en_US.utf8"))
printf("failed to set locale to UTF-8");

EDIT 2 (выяснение, используем ли мы UTF-8)

Чтобы узнать, установлен ли языковой стандарт на UFT-8 (после попытки его установки), вы можете либо проверить возвращаемое значение (NULL означает, что вызов был неудачным), либо проверить используемый языковой стандарт.

Опция 1:

char * result;
if((result = setlocale (LC_ALL, "en_US.UTF-8")) == NULL)
printf("failed to set locale to UTF-8");

Вариант 2:

setlocale (LC_ALL, "en_US.UTF-8"); // set
char * result = setlocale (LC_ALL, NULL); // review
if(!strstr(result, "UTF-8"))
printf("failed to set locale to UTF-8");

ответил(а) 2016-03-23T19:51:00+03:00 4 года, 6 месяцев назад
57

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

В этом примере показано, как читать и обрабатывать CSV файлы (формат RFC-4180, при необходимости с ограниченной поддержкой обратного слэша) с использованием широких строк.

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

Однако логика кода надежна. В частности, он поддерживает универсальные символы новой строки, все четыре общих типа новой строки: Unix-подобный LF (\n), старый CR LF (\r\n), старый Mac CR (\r) и случайно встречающийся странный LF CR (\n\r). Нет встроенных ограничений. длина поля, количество полей в записи или количество записей в файле. Он работает очень хорошо, если вам нужно преобразовать CSV или обработать CSV-поток, похожий на поток (по полю или по записи), без необходимости иметь более одного в памяти в одной точке. Если вы хотите построить структуры для описания записей и полей в памяти, вам нужно будет добавить для них некоторый код леса.

Из-за универсальной поддержки новой строки при чтении ввода в интерактивном режиме для этой программы могут потребоваться два последовательных конечных входа ( Ctrl + Z в Windows и MS-DOS, Ctrl + D повсюду), поскольку первая из них обычно "потребляется" csv_next_field() или csv_skip_field(), а функция csv_next_record() нуждается в повторном чтении ее для ее фактического обнаружения. Однако вы обычно не запрашиваете у пользователя ввод данных CSV в интерактивном режиме, поэтому это должно быть приемлемым причудом.

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

/* RFC-4180 -format CSV file processing using wide input streams.
*
* #define BACKSLASH_ESCAPES if you additionally wish to have
* \\, \a, \b, \t, \n, \v, \f, \r, \", and \, de-escaped to their
* C string equivalents when reading CSV fields.
*/

typedef enum {
CSV_OK = 0,
CSV_END = 1,
CSV_INVALID_PARAMETERS = -1,
CSV_FORMAT_ERROR = -2,
CSV_CHARSET_ERROR = -3,
CSV_READ_ERROR = -4,
CSV_OUT_OF_MEMORY = -5,
} csv_status;

const char *csv_error(const csv_status code)
{
switch (code) {
case CSV_OK: return "No error";
case CSV_END: return "At end";
case CSV_INVALID_PARAMETERS: return "Invalid parameters";
case CSV_FORMAT_ERROR: return "Bad CSV format";
case CSV_CHARSET_ERROR: return "Illegal character in CSV file (incorrect locale?)";
case CSV_READ_ERROR: return "Read error";
case CSV_OUT_OF_MEMORY: return "Out of memory";
default: return "Unknown csv_status code";
}
}

/* Start the next record. Automatically skips any remaining fields in current record.
* Returns CSV_OK if successful, CSV_END if no more records, or a negative CSV_ error code. */
csv_status csv_next_record (FILE *const in);

/* Skip the next field. Returns CSV_OK if successful, CSV_END if no more fields in current record,
* or a negative CSV_ error code. */
csv_status csv_skip_field (FILE *const in);

/* Read the next field. Returns CSV_OK if successful, CSV_END if no more fields in current record,
* or a negative CSV_ error code.
* If this returns CSV_OK, then *dataptr is a dynamically allocated wide string to the field
* contents, space allocated for *sizeptr wide characters; and if lengthptr is not NULL, then
* *lengthptr is the number of wide characters in said wide string. */
csv_status csv_next_field (FILE *const in, wchar_t **const dataptr,
size_t *const sizeptr,
size_t *const lengthptr);

static csv_status internal_skip_quoted(FILE *const in)
{
while (1) {
wint_t wc;

errno = 0;
wc = fgetwc(in);

if (wc == WEOF) {
if (errno == EILSEQ)
return CSV_CHARSET_ERROR;
if (errno)
return CSV_READ_ERROR;
if (ferror(in)) {
errno = EIO;
return CSV_READ_ERROR;
}
errno = 0;
return CSV_FORMAT_ERROR;
}

if (wc == L'"') {
errno = 0;
wc = fgetwc(in);

if (wc == L'"')
continue;

while (wc != WEOF && wc != L'\n' && wc != L'\r' && iswspace(wc)) {
errno = 0;
wc = fgetwc(in);
}

if (wc == WEOF) {
if (errno == EILSEQ)
return CSV_CHARSET_ERROR;
if (errno)
return CSV_READ_ERROR;
if (ferror(in)) {
errno = EIO;
return CSV_READ_ERROR;
}
errno = 0;
return CSV_END;
}

if (wc == L',') {
errno = 0;
return CSV_OK;
}

if (wc == L'\n' || wc == L'\r') {
ungetwc(wc, in);
errno = 0;
return CSV_END;
}

ungetwc(wc, in);
errno = 0;
return CSV_FORMAT_ERROR;
}

#ifdef BACKSLASH_ESCAPES
if (wc == L'\\') {
errno = 0;
wc = fgetwc(in);

if (wc == L'"')
continue;

if (wc == WEOF) {
if (errno == EILSEQ)
return CSV_CHARSET_ERROR;
if (errno)
return CSV_READ_ERROR;
if (ferror(in)) {
errno = EIO;
return CSV_READ_ERROR;
}
errno = 0;
return CSV_END;
}
}
#endif
}
}

static csv_status internal_skip_unquoted(FILE *const in, wint_t wc)
{
while (1) {

if (wc == WEOF) {
if (errno == EILSEQ)
return CSV_CHARSET_ERROR;
if (errno)
return CSV_READ_ERROR;
if (ferror(in)) {
errno = EIO;
return CSV_READ_ERROR;
}
errno = 0;
return CSV_END;
}

if (wc == L',') {
errno = 0;
return CSV_OK;
}

if (wc == L'\n' || wc == L'\r') {
ungetwc(wc, in);
errno = 0;
return CSV_END;
}

#ifdef BACKSLASH_ESCAPES
if (wc == L'\\') {
errno = 0;
wc = fgetwc(in);
if (wc == WEOF) {
if (errno == EILSEQ)
return CSV_CHARSET_ERROR;
if (errno)
return CSV_READ_ERROR;
if (ferror(in)) {
errno = EIO;
return CSV_READ_ERROR;
}
errno = 0;
return CSV_END;
}
}
#endif

errno = 0;
wc = fgetwc(in);
}
}

csv_status csv_next_record(FILE *const in)
{
while (1) {
wint_t wc;
csv_status status;

do {
errno = 0;
wc = fgetwc(in);
} while (wc != WEOF && wc != L'\n' && wc != L'\r' && iswspace(wc));

if (wc == WEOF) {
if (errno == EILSEQ)
return CSV_CHARSET_ERROR;
if (errno)
return CSV_READ_ERROR;
if (ferror(in)) {
errno = EIO;
return CSV_READ_ERROR;
}
errno = 0;
return CSV_END;
}

if (wc == L'\n' || wc == L'\r') {
wint_t next_wc;

errno = 0;
next_wc = fgetwc(in);

if (next_wc == WEOF) {
if (errno == EILSEQ)
return CSV_CHARSET_ERROR;
if (errno)
return CSV_READ_ERROR;
if (ferror(in)) {
errno = EIO;
return CSV_READ_ERROR;
}
errno = 0;
return CSV_END;
}

if ((wc == L'\n' && next_wc == L'\r') ||
(wc == L'\r' && next_wc == L'\n')) {
errno = 0;
return CSV_OK;
}

ungetwc(next_wc, in);
errno = 0;
return CSV_OK;
}

if (wc == L'"')
status = internal_skip_quoted(in);
else
status = internal_skip_unquoted(in, wc);

if (status < 0)
return status;
}
}

csv_status csv_skip_field(FILE *const in)
{
wint_t wc;

if (!in) {
errno = EINVAL;
return CSV_INVALID_PARAMETERS;
} else
if (ferror(in)) {
errno = EIO;
return CSV_READ_ERROR;
}

/* Skip leading whitespace. */
do {
errno = 0;
wc = fgetwc(in);
} while (wc != WEOF && wc != L'\n' && wc != L'\r' && iswspace(wc));

if (wc == L'"')
return internal_skip_quoted(in);
else
return internal_skip_unquoted(in, wc);

}

csv_status csv_next_field(FILE *const in, wchar_t **const dataptr,
size_t *const sizeptr,
size_t *const lengthptr)
{
wchar_t *data;
size_t size;
size_t used = 0; /* length */
wint_t wc;

if (lengthptr)
*lengthptr = 0;

if (!in || !dataptr || !sizeptr) {
errno = EINVAL;
return CSV_INVALID_PARAMETERS;
} else
if (ferror(in)) {
errno = EIO;
return CSV_READ_ERROR;
}

if (*dataptr) {
data = *dataptr;
size = *sizeptr;
} else {
data = NULL;
size = 0;
*sizeptr = 0;
}

/* Skip leading whitespace. */
do {
errno = 0;
wc = fgetwc(in);
} while (wc != WEOF && wc != L'\n' && wc != L'\r' && iswspace(wc));

if (wc == WEOF) {
if (errno == EILSEQ)
return CSV_CHARSET_ERROR;
if (errno)
return CSV_READ_ERROR;
if (ferror(in)) {
errno = EIO;
return CSV_READ_ERROR;
}
errno = 0;
return CSV_END;
}

if (wc == L'\n' || wc == L'\r') {
ungetwc(wc, in);
errno = 0;
return CSV_END;
}

if (wc == L'"')
while (1) {

errno = 0;
wc = getwc(in);

if (wc == WEOF) {
if (errno == EILSEQ)
return CSV_CHARSET_ERROR;
if (errno)
return CSV_READ_ERROR;
if (ferror(in)) {
errno = EIO;
return CSV_READ_ERROR;
}
errno = 0;
return CSV_FORMAT_ERROR;

} else
if (wc == L'"') {
errno = 0;
wc = getwc(in);

if (wc != L'"') {
/* Not an escaped doublequote. */

while (wc != WEOF && wc != L'\n' && wc != L'\r' && iswspace(wc)) {
errno = 0;
wc = getwc(in);
}

if (wc == WEOF) {
if (errno == EILSEQ)
return CSV_CHARSET_ERROR;
if (errno)
return CSV_READ_ERROR;
if (ferror(in)) {
errno = EIO;
return CSV_READ_ERROR;
}
} else
if (wc == L'\n' || wc == L'\r') {
ungetwc(wc, in);
} else
if (wc != L',') {
errno = 0;
return CSV_FORMAT_ERROR;
}
break;
}

#ifdef BACKSLASH_ESCAPES
} else
if (wc == L'\\') {
errno = 0;
wc = getwc(in);

if (wc == L'\0')
continue;
else
if (wc == WEOF) {
if (errno == EILSEQ)
return CSV_CHARSET_ERROR;
if (errno)
return CSV_READ_ERROR;
if (ferror(in)) {
errno = EIO;
return CSV_READ_ERROR;
}
break;
} else
switch (wc) {
case L'a': wc = L'\a'; break;
case L'b': wc = L'\b'; break;
case L't': wc = L'\t'; break;
case L'n': wc = L'\n'; break;
case L'v': wc = L'\v'; break;
case L'f': wc = L'\f'; break;
case L'r': wc = L'\r'; break;
case L'\\': wc = L'\\'; break;
case L'"': wc = L'"'; break;
case L',': wc = L','; break;
default:
ungetwc(wc, in);
wc = L'\\';
}
#endif
}

if (used + 2 > size) {
/* Allocation policy.
* Anything that yields size >= used + 2 is acceptable.
* This one allocates in roughly 1024 byte chunks,
* and is known to be robust (but not optimal) in practice. */
size = (used | 1023) + 1009;
data = realloc(data, size * sizeof data[0]);
if (!data) {
errno = ENOMEM;
return CSV_OUT_OF_MEMORY;
}
*dataptr = data;
*sizeptr = size;
}

data[used++] = wc;
}
else
while (1) {

if (wc == L',')
break;

if (wc == L'\n' || wc == L'\r') {
ungetwc(wc, in);
break;
}

#ifdef BACKSLASH_ESCAPES
if (wc == L'\\') {
errno = 0;
wc = fgetwc(in);
if (wc == WEOF) {
if (errno == EILSEQ)
return CSV_CHARSET_ERROR;
if (errno)
return CSV_READ_ERROR;
if (ferror(in)) {
errno = EIO;
return CSV_READ_ERROR;
}
wc = L'\\';
} else
switch (wc) {
case L'a': wc = L'\a'; break;
case L'b': wc = L'\b'; break;
case L't': wc = L'\t'; break;
case L'n': wc = L'\n'; break;
case L'v': wc = L'\v'; break;
case L'f': wc = L'\f'; break;
case L'r': wc = L'\r'; break;
case L'"': wc = L'"'; break;
case L',': wc = L','; break;
case L'\\': wc = L'\\'; break;
default:
ungetwc(wc, in);
wc = L'\\';
}
}
#endif

if (used + 2 > size) {
/* Allocation policy.
* Anything that yields size >= used + 2 is acceptable.
* This one allocates in roughly 1024 byte chunks,
* and is known to be robust (but not optimal) in practice. */
size = (used | 1023) + 1009;
data = realloc(data, size * sizeof data[0]);
if (!data) {
errno = ENOMEM;
return CSV_OUT_OF_MEMORY;
}
*dataptr = data;
*sizeptr = size;
}

data[used++] = wc;

errno = 0;
wc = getwc(in);

if (wc == WEOF) {
if (errno == EILSEQ)
return CSV_CHARSET_ERROR;
if (errno)
return CSV_READ_ERROR;
if (ferror(in)) {
errno = EIO;
return CSV_READ_ERROR;
}
break;
}
}

/* Ensure there is room for the end-of-string mark. */
if (used >= size) {
size = used + 1;
data = realloc(data, size * sizeof data[0]);
if (!data) {
errno = ENOMEM;
return CSV_OUT_OF_MEMORY;
}
*dataptr = data;
*sizeptr = size;
}

data[used] = L'\0';

if (lengthptr)
*lengthptr = used;

errno = 0;
return CSV_OK;
}

/* Helper function: print a wide string as if in quotes, but backslash-escape special characters.
*/
static void wquoted(FILE *const out, const wchar_t *ws, const size_t len)
{
if (out) {
size_t i;

for (i = 0; i < len; i++)
if (ws[i] == L'\0')
fputws(L"\\0", out);
else
if (ws[i] == L'\a')
fputws(L"\\a", out);
else
if (ws[i] == L'\b')
fputws(L"\\b", out);
else
if (ws[i] == L'\t')
fputws(L"\\t", out);
else
if (ws[i] == L'\n')
fputws(L"\\n", out);
else
if (ws[i] == L'\v')
fputws(L"\\v", out);
else
if (ws[i] == L'\f')
fputws(L"\\f", out);
else
if (ws[i] == L'\r')
fputws(L"\\r", out);
else
if (ws[i] == L'"')
fputws(L"\\\"", out);
else
if (ws[i] == L'\\')
fputws(L"\\\\", out);
else
if (iswprint(ws[i]))
fputwc(ws[i], out);
else
if (ws[i] < 65535)
fwprintf(out, L"\\x%04x", (unsigned int)ws[i]);
else
fwprintf(out, L"\\x%08x", (unsigned long)ws[i]);
}
}

static int show_csv(FILE *const in, const char *const filename)
{
wchar_t *field_contents = NULL;
size_t field_allocated = 0;
size_t field_length = 0;
unsigned long record = 0UL;
unsigned long field;
csv_status status;

while (1) {

/* First field in this record. */
field = 0UL;
record++;

while (1) {

status = csv_next_field(in, &field_contents, &field_allocated, &field_length);

if (status == CSV_END)
break;

if (status < 0) {
fprintf(stderr, "%s: %s.\n", filename, csv_error(status));
free(field_contents);
return -1;
}

field++;

wprintf(L"Record %lu, field %lu is \"", record, field);
wquoted(stdout, field_contents, field_length);
wprintf(L"\", %lu characters.\n", (unsigned long)field_length);
}

status = csv_next_record(in);

if (status == CSV_END) {
free(field_contents);
return 0;
}

if (status < 0) {
fprintf(stderr, "%s: %s.\n", filename, csv_error(status));
free(field_contents);
return -1;
}
}
}

static int usage(const char *argv0)
{
fprintf(stderr, "\n");
fprintf(stderr, "Usage: %s [ -h | --help | /? ]\n", argv0);
fprintf(stderr, " %s CSV-FILE [ ... ]\n", argv0);
fprintf(stderr, "\n");
fprintf(stderr, "Use special file name '-' to read from standard input.\n");
fprintf(stderr, "\n");
return EXIT_SUCCESS;
}

int main(int argc, char *argv[])
{
FILE *in;
int arg;

setlocale(LC_ALL, "");

fwide(stdin, 1);
fwide(stdout, 1);

if (argc < 1)
return usage(argv[0]);

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

if (!strcmp(argv[arg], "-h") || !strcmp(argv[arg], "--help") || !strcmp(argv[arg], "/?"))
return usage(argv[0]);

if (!strcmp(argv[arg], "-")) {

if (show_csv(stdin, "(standard input)"))
return EXIT_FAILURE;

} else {

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

if (show_csv(in, argv[arg]))
return EXIT_FAILURE;
if (ferror(in)) {
fprintf(stderr, "%s: %s.\n", argv[arg], strerror(EIO));
fclose(in);
return EXIT_FAILURE;
}
if (fclose(in)) {
fprintf(stderr, "%s: %s.\n", argv[arg], strerror(EIO));
return EXIT_FAILURE;
}
}
}

return EXIT_SUCCESS;
}

Использование вышеперечисленных csv_next_field(), csv_skip_field() и csv_next_record() довольно просто.

Обычно fwide(stream, 1) файл CSV, затем вызывайте fwide(stream, 1) чтобы сообщить библиотеке C, что вы собираетесь использовать широкие варианты строк вместо стандартных функций ввода-вывода с узкой строкой.

Создайте четыре переменные и выполните инициализацию первых двух:

 wchar_t   *field = NULL;
size_t allocated = 0;
size_t length;
csv_status status;

field является указателем на динамически распределенное содержимое каждого прочитанного поля. Он выделяется автоматически; по сути, вам не нужно об этом беспокоиться. allocated содержит текущий распределенный размер (в широких символах, включая завершение L'\0'), и мы будем использовать length и status позже.

На этом этапе вы готовы прочитать или пропустить первое поле в первой записи.

Вы не хотите называть csv_next_record() в этот момент, если вы не хотите пропустить первую запись целиком в файле.

status = csv_skip_field(stream); вызова status = csv_skip_field(stream); пропустить следующее поле или status = csv_next_field(stream, &field, &allocated, &length); чтобы прочитать его.

Если status == CSV_OK, то есть содержимое поля в мудром струнном field. Она имеет length широких символов в нем.

Если status == CSV_END, в текущей записи больше не было полей. (field не изменилось, и вы не должны его изучать).

В противном случае, status < 0, и он описывает код ошибки. Вы можете использовать csv_error(status) чтобы получить (узкую) строку, описывающую его.

В любой момент вы можете переместить (пропустить) к началу следующей записи, вызвав status = csv_next_record(stream); ,

Если он вернет CSV_OK, возможно, появится новая запись. (Мы знаем только, когда вы пытаетесь прочитать или пропустить первое поле. Это похоже на то, как стандартная функция библиотеки C feof() только сообщает вам, пытались ли вы прочесть за конец ввода, но не указывает, есть ли еще доступные данные или нет).

Если он возвращает CSV_END, вы уже обработали последнюю запись, и записей больше нет.

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

После того, как вы закончите, отбросьте буфер поля:

 free(field);
field = NULL;
allocated = 0;

Вам действительно не нужно переустанавливать переменные в NULL и ноль, но я рекомендую это. Фактически вы можете сделать это в любой момент (когда вас больше не интересует содержимое текущего поля), так как csv_next_field() будет автоматически выделять новый буфер по мере необходимости.

Обратите внимание, что free(NULL); всегда безопасен и ничего не делает. Вам не нужно проверять, является ли field NULL или нет, прежде чем освобождать его. Это также является причиной, по которой я рекомендую инициализировать переменные сразу после их объявления. Это просто упрощает управление.

Скомпилированная примерная программа принимает одно или несколько имен файлов CSV в качестве параметров командной строки, затем считывает файлы и сообщает содержимое каждого поля в файле. Если у вас есть особенно дьявольски сложный CSV файл, это оптимально для проверки того, правильно ли этот подход правильно считывает все поля.

ответил(а) 2016-03-24T13:56:00+03:00 4 года, 6 месяцев назад
Ваш ответ
Введите минимум 50 символов
Чтобы , пожалуйста,
Выберите тему жалобы:

Другая проблема