Поскольку вы застряли в обработке пустых полей при токенизации каждой строки, давайте рассмотрим использование strsep
, чтобы справиться с этим для вас. Есть несколько предостережений по поводу использования strsep
. Сначала обратите внимание на тип первого параметра. Это char **
. Это означает, что вы не можете прочитать каждую строку в массив фиксированных символов и передать адрес фиксированного массива (это будет не char**
, а вместо char (*)[length]
). Далее, поскольку strsep
обновит указатель, предоставленный в качестве первого параметра, вы не можете просто дать ему адрес выделенного буфера, который вы используете для хранения каждой прочитанной строки (вы потеряете указатель на начало выделенного блока и быть не в состоянии free()
памяти или прочитать более одной строки.
Итак, нижняя строка, вам нужен выделенный буфер для хранения текста, который вы собираетесь передать strsep
, а затем вам нужно 2 указателя , один для захвата возврата из strsep
и один для передачи адреса на strsep
(чтобы сохранить исходный указатель буфера).
Имея это в виду, вы можете проанализировать CSV с пустыми полями, похожими на:
while (fgets (buf, MAXC, fp)) { /* read each line in file */
size_t i = 0; /* counter */
p = fields = buf; /* initialize pointers to use with strsep */
printf ("\nline %2zu:\n", n++ + 1); /* output heading */
while ((p = strsep (&fields, DELIM))) { /* call strsep */
p[strcspn(p, "\r\n")] = 0; /* trim '\n' (last) */
printf (" field %2zu: '%s'\n", i++ + 1, p); /* output field */
}
}
Собрав воедино полный пример, используя ваши данные, вы можете сделать что-то похожее на:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAXC 1024 /* if you need a constant, #define one (or more) */
#define DELIM "," /* (numeric or string) */
int main (int argc, char **argv) {
size_t n = 0, lines, nflds;
char *buf, *fields, *p; /* must use 2 pointers for strsep */
/* use filename provided as 1st argument (stdin by default) */
FILE *fp = argc > 1 ? fopen (argv[1], "r") : stdin;
if (!fp) { /* validate file open for reading */
perror ("file open failed");
return 1;
}
if (!(buf = malloc (MAXC))) { /* allocate storage for buffer */
perror ("malloc-buf"); /* cannot be array with strsep */
return 1;
}
if (!fgets (buf, MAXC, fp)) { /* read/validate 1st line */
fputs ("error: insufficient input line 1.\n", stderr);
return 1;
} /* convert to lines and no. of fields (lines not needed) */
if (sscanf (buf, "%zu,%zu", &lines, &nflds) != 2) {
fputs ("error: invalid format line 1.\n", stderr);
return 1;
}
while (fgets (buf, MAXC, fp)) { /* read each line in file */
size_t i = 0; /* counter */
p = fields = buf; /* initialize pointers to use with strsep */
printf ("\nline %2zu:\n", n++ + 1); /* output heading */
while ((p = strsep (&fields, DELIM))) { /* call strsep */
p[strcspn(p, "\r\n")] = 0; /* trim '\n' (last) */
printf (" field %2zu: '%s'\n", i++ + 1, p); /* output field */
}
}
if (fp != stdin) fclose (fp); /* close file if not stdin */
free (buf); /* free allocated memory */
return 0;
}
Пример входного файла
$ cat dat/emptyflds.csv
5,4
,,,C 200
,G Vibranium Shield:hands:990,,C 50
M Healing Potion:85,,M Defence Enchanment:360,
,,,
,,G Lighsaber:hands:850,5,4
Пример использования / Вывод
В примере просто выводится номер строки, а затем каждое отдельное поле в отдельной строке под ним, чтобы вы могли подтвердить разделение:
$ ./bin/strcspnsepcsv <dat/emptyflds.csv
line 1:
field 1: ''
field 2: ''
field 3: ''
field 4: 'C 200'
line 2:
field 1: ''
field 2: 'G Vibranium Shield:hands:990'
field 3: ''
field 4: 'C 50'
line 3:
field 1: 'M Healing Potion:85'
field 2: ''
field 3: 'M Defence Enchanment:360'
field 4: ''
line 4:
field 1: ''
field 2: ''
field 3: ''
field 4: ''
line 5:
field 1: ''
field 2: ''
field 3: 'G Lighsaber:hands:850'
field 4: '5'
field 5: '4'
( примечание: строка 5 содержит пятое поле, которое превышает ожидаемое количество полей)
Для дальнейшего разделения внутри полей в ':'
или в любом другом месте, вы можете позвонить strtok
по указателю p
в пределах поля токенизации while
loop.