C разбирает запятые-значения с переносами строк

У меня есть CSV файл данных, содержащий следующие данные:

H1,H2,H3
a,"b
c
d",e

Когда я открываю через Excel как файл CSV, он может показать лист с заголовками столбцов как H1, H2, H3 и значения столбца как: a for H1,

multi line value as
b
c
d
for H2 

а также c for H3Мне нужно проанализировать этот файл с помощью программы на Си и получить значения, как это. Но мой следующий фрагмент кода не будет работать, так как у меня есть многострочные значения для столбца:

char buff[200];
char tokens[10][30];
fgets(buff, 200, stdin);
char *ptok = buff; // for iterating
char *pch; 
int i = 0;
while ((pch = strchr(ptok, ',')) != NULL) {
  *pch = 0; 
  strcpy(tokens[i++], ptok);
  ptok = pch+1;
}
strcpy(tokens[i++], ptok);

Как изменить этот фрагмент кода для размещения многострочных значений столбцов? Пожалуйста, не беспокойтесь о жестко запрограммированных значениях для строковых буферов, это тестовый код POC. Вместо какой-либо сторонней библиотеки я бы хотел сделать это трудным путем из первого принципа. Пожалуйста помоги.

1 ответ

Решение

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

Без этих осложнений разбор действительно довольно прост:

(Непроверенные)

/* Appends a non-quoted field to s and returns the delimiter */
int readSimpleField(struct String* s) {
  for (;;) {
    int ch = getc();
    if (ch == ',' || ch == '\n' || ch == EOF) return ch;
    stringAppend(s, ch);
  }
}

/* Appends a quoted field to s and returns the delimiter.
 * Assumes the open quote has already been read.
 * If the field is not terminated, returns ERROR, which
 * should be a value different from any character or EOF.
 * The delimiter returned is the character after the closing quote
 * (or EOF), which may not be a valid delimiter. Caller should check.
 */
int readQuotedField(struct String* s) {
  for (;;) {
    int ch;
    for (;;) {
      ch = getc();
      if (ch == EOF) return ERROR;
      if (ch == '"') {
        ch = getc();
        if (ch != '"') break;
      }
      stringAppend(s, ch);
    }
  }
}

/* Reads a single field into s and returns the following delimiter,
 * which might be invalid.
 */
int readField(struct String* s) {
  stringClear(s);
  int ch = getc();
  if (ch == '"') return readQuotedField(s);
  if (ch == '\n' || ch == EOF) return ch;
  stringAppend(s, ch);
  return readSimpleField(s);
}

/* Reads a single row into row and returns the following delimiter,
 * which might be invalid.
 */
int readRow(struct Row* row) {
  struct String field = {0};
  rowClear(row);
  /* Make sure there is at least one field */
  int ch = getc();
  if (ch != '\n' && ch != EOF) {
    ungetc(ch, stdin);
    do {
      ch = readField(s);
      rowAppend(row, s);
    } while (ch == ',');
  }
  return ch;
}

/* Reads an entire CSV file into table.
 * Returns true if the parse was successful.
 * If an error is encountered, returns false. If the end-of-file
 * indicator is set, the error was an unterminated quoted field; 
 * otherwise, the next character read will be the one which
 * triggered the error.
 */
bool readCSV(struct Table* table) {
  tableClear(table);
  struct Row row = {0};
  /* Make sure there is at least one row */
  int ch = getc();
  if (ch != EOF) {
    ungetc(ch, stdin);
    do {
      ch = readRow(row);
      tableAppend(table, row);
    } while (ch == '\n');
  }
  return ch == EOF;
}

Выше "из первых принципов" - он даже не использует стандартные строковые функции библиотеки C. Но это требует определенных усилий, чтобы понять и проверить. Лично я бы использовал (f) lex и, возможно, даже yacc / bison (хотя это немного излишне), чтобы упростить код и сделать ожидаемый синтаксис более очевидным. Но обработка структур переменной длины в C все еще должна быть первым шагом.

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