Анализ файла .csv целых чисел в двумерный массив в C

Я пытаюсь проанализировать файл .csv, содержащий целые числа, в двумерный массив в C, но моя программа не работает, и я не знаю, почему.

2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2,
2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2,
2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2,
2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2,
2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2,
2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2,
2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2,
2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2,
2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2

(Выше представлен файл .csv, который я пытаюсь проанализировать, это просто целые числа 1 и 2.)

Моя программа:

#include <stdio.h>
#define ROWS 12
#define COLUMNS 12

int main () {

int array [LEVEL_ROWS] [LEVEL_COLUMNS];

FILE *file;

file = fopen("data.csv","r");

if (file == NULL)
{
    printf("error opening file");
    return 1;
}

for (int i = 0; i < COLUMNS; i++)
{
    for(int j = 0; j < ROWS; j++)
    {
        if (fscanf(file,"%d,",&array[i][j] )!=1)
        {
            fprintf(stderr,"error reading from file\n");
            fclose(file);
            return 1;
        }
    }
}

fclose(file);

for(int i = 0; i < COLUMNS; i++)
{
    for (int j =0; j < ROWS; j++)
    {
        printf("%d ", array[i][j]);
    }
    printf("\n");
}

return 0;

}

Однако моя программа совершенно неправильно печатает содержимое файла .csv, и я не могу понять, почему.

распечатанный вывод (не совпадает с фактическим содержимым файла .csv):

2 2 2 2 2 2 2 2 2 2 2 
2 2 2 2 2 2 2 2 2 2 1
1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 2 2 1 1 1
1 1 1 1 1 1 1 1 1 1 1
1 1 1 1 2 2 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1
1 1 2 2 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 1 1
2 2 1 1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1 1 2 2

Любая помощь и объяснение того, что не так? Спасибо!

🤔 А знаете ли вы, что...
C стандартизирован ISO и ANSI, что обеспечивает переносимость кода между различными компиляторами и платформами.


1
128
2

Ответы:

Решено
  • #define COLUMNS 12 не соответствует количеству столбцов в файле. В файле 20 столбцов.
  • LEVEL_ROWS и LEVEL_COLUMNS вообще не определены.
  • Если вы хотите воспользоваться преимуществами внутреннего строкового порядка хранилища в C, не считывайте значения столбцов в естественный размер строк вашего массива. Вы определили массив int array [LEVEL_ROWS][LEVEL_COLUMNS];, но считываете значения столбцов в строки и наоборот.
  • «Недостающий» , в конце последней строки сейчас не является проблемой, но выглядит незавершенным. Лучше быть последовательным. Я бы удалил последнюю запятую в каждой строке (или, возможно, добавил бы ее ко всем).

Если вы затем определите COLUMNS равным 20 и поменяете местами чтение строк и столбцов (и печать), все будет работать нормально. Пример:

#include <stdio.h>

#define ROWS (12)
#define COLUMNS (20)

int main(void) {
    const char* filename = "data.csv";

    FILE* file = fopen(filename, "r");

    if (file == NULL) {
        perror(filename);
        return 1;
    }

    int array[ROWS][COLUMNS];

    for(int row = 0; row < ROWS; row++) {
        for(int col = 0; col < COLUMNS; col++) {
            if (fscanf(file, "%d,", &array[row][col]) != 1) {
                fprintf(stderr, "error reading from file\n");
                fclose(file);
                return 1;
            }
        }
    }
    fclose(file);

    for(int row = 0; row < ROWS; row++) {
        for(int col = 0; col < COLUMNS; col++) {
            printf("%d ", array[row][col]);
        }
        putchar('\n');
    }
}

Этот

2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2,
2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2,
2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2,
2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2,
2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2,
2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2,
2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2,
2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2,
2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2

не является допустимым файлом CSV. CSV-файлы должны иметь одинаковое количество полей в каждой записи. Поскольку разделители определяют поля, каждая строка должна иметь одинаковое количество разделителей, даже если для некоторых полей нет данных. Поля являются необязательными, но их позиции — нет.

Это действительный файл CSV:

, , , , 
1,2,3,4,5
,6,7,8,

Это также справедливо

    , , ,
    , , ,
    , , ,

дополнительные определения формата csv

  • первая строка файла csv может иметь так называемую строку заголовка и содержать имя каждого поля. Он использовался для создания почтовых этикеток в 90-х годах, он используется для предоставления имен столбцов для таблицы базы данных, импортируемой в SQL. Это пример со строкой заголовка:
field1,field2,field3,field4,quantity
,,,, 
1,2,3,4,5
,6,7,8,

Посмотрите этот файл, импортированный в Таблицы из Google.

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

  • последняя запись может иметь или не иметь новую строку в конце
  • пространства считаются частью поля
  • поля могут быть «заключены в кавычки», и это означает, что вы можете использовать некоторый разделитель, чтобы заключить в него поле, внутри которого есть разделитель, например запятые в CSV-файле, разделенном запятыми. Пример ниже:
field1,"Unit Price",field3,field4,quantity
,,,, 
1,2,3,4,5
,6,7,8,

Посмотрите это, импортированное в Excel из Microsoft

Обратите внимание, что пробелы в B1 и B3 сохраняются благодаря кавычкам.

Этот формат существует примерно с 70-х годов и присутствует во многих областях. Поскольку это формат обмена, во многих случаях у вас есть только файл для чтения, в других случаях вам нужно написать файл, чтобы отправить его кому-то, и вы не можете контролировать ни одну сторону, ни другую. csv-файлы импортируются в любую базу данных, в программы MS-Office, в продукты Google Cloud, программное обеспечение для печати этикеток из 90-х годов часто поставлялось с программой на 5,25-дюймовой дискете для создания этикеток на основе csv-файла.
А в последние годы CSV получил еще большую популярность благодаря ИИ, поскольку почти весь набор данных можно загрузить в виде файла CSV, а анализ CSV для подготовки данных перед обучением модели очень удобен в C. В Python также есть модуль csv.

Вот пример импорта CSV-файла в базу данных MySQL из документации.

LOAD DATA INFILE 'file.csv' 
INTO TABLE discounts 
FIELDS TERMINATED BY ',' 
ENCLOSED BY '"'
LINES TERMINATED BY '\n'
IGNORE 1 ROWS;

Последняя строка — это инструкция игнорировать строку заголовка. Это тот случай, когда имена столбцов в базе данных уже заданы. Остальное говорит само за себя.
Мы видим те же детали, о которых я писал ранее, о разделителе, признаке конца строки, строке заголовка и кавычках полей.

Формального определения формата csv не существует, поскольку на момент его создания не существовало таких понятий, как IETF или W3C. IETF подготовил что-то вроде RFC, RFC4180, который пытается дать некоторые определения, но уже слишком поздно, поскольку эти файлы повсюду с 70-х годов, и с тех пор программы импортируют эти файлы.
И с тех пор файлы такие, как я написал выше: возможно, гигантская таблица MxN. M записей, в которых ровно N полей отмечены N-1 одинаковыми разделителями, по одному в строке.

импорт файла csv с любым количеством полей и записей

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

В этом примере читается файл csv с любым количеством записей и любым количеством полей. Можно легко изменить. Память выделяется блоками настолько большого размера, насколько хочет пользователь (и позволяет память машины). Размер памяти может увеличиваться по требованию и в данном примере происходит автоматически. Полный код и вывод приведены в конце.

Обычный подход, используемый здесь, заключается в сканировании первой строки файла и просто подсчете количества полей.

Эта функция делает именно это:

size_t csv_get_n_fields(const char* file, const char delim)
{
    FILE* fl = fopen(file, "r");
    if (fl == NULL) return 0;
    char   line[250];
    size_t n_fields = 0;
    char*  p        = fgets(line, sizeof(line), fl);
    if (p == NULL)
    {
        fclose(fl);
        return 0;
    }
    while (*p != 0)
        if (delim == *p++) ++n_fields;
    fclose(fl);
    return 1 + n_fields;
}

Зная количество полей, мы можем приступить к чтению всех записей. Но нам нужна абстракция, модель.

абстракция для CSV-файла значений int


typedef struct
{
    size_t n_fields;     // columns
    size_t n_records;    // rows
    size_t max_records;  // limit
    int**  array;
} CSV_i;

Каждая строка массива содержит действительную запись файла csv. Строка 0 файла — это array[0] и представляет собой массив int с полями, что неудивительно. Количество полей было определено функцией выше, поэтому мы можем просто отсканировать файл и загрузить данные.

4 операции на CSV_i

CSV_i* csv_create(
    const char* file, size_t block, const char delim);
CSV_i* csv_destroy(CSV_i* del);
CSV_i* csv_read(
    const char* file, size_t block, const char delim);
int csv_show(CSV_i* csv, const char* msg);

Здесь нет ничего удивительного: только самый минимум. In csv_create()block — единица распределения в байтах. В примере используется 64 КБ. Конечно, может быть в командной строке.

код main в примере

#define BLK_SIZE (size_t)(64 * 1024)
int main(int argc,char** argv)
{
    const char* def_file = "input5.csv";
    char        f_name[200] = {0};
    if (argc < 2)
        strcpy(f_name, def_file);
    else
        strcpy(f_name, argv[1]);
    fprintf(stderr, "File is '%s'\n", f_name);
    CSV_i* my_csv =
        csv_read(f_name, BLK_SIZE, DELIM_COMMA);
    char msg[80] = {0};
    sprintf(msg, "test_file for %zu bytes:   ", BLK_SIZE);
    csv_show(my_csv, msg);
    csv_destroy(my_csv);
    return 0;
}

Таким образом можно прочитать любой файл csv. Пользователь может ввести имя файла в командной строке. Структура выделяется и расширяется по мере необходимости.

запуск примера с несколькими файлами


Stack Overflow > type input5.csv
, , , ,
1,2,3,4,5
,6,7,8,

Stack Overflow > p input5.csv
File is 'input5.csv'
csv has 5 fields
test_file for 65536 bytes:   array [3276,5] (in use: 3/3276 records)

0 0 0 0 0
1 2 3 4 5
0 6 7 8 0

Stack Overflow > type input4.csv
,999,,
15,16,17,18
1,2,3,4
10,,,11

Stack Overflow > p input4.csv
File is 'input4.csv'
csv has 4 fields
test_file for 65536 bytes:   array [4096,4] (in use: 4/4096 records)

0 999 0 0
15 16 17 18
1 2 3 4
10 0 0 11

[end]

Stack Overflow >

Stack Overflow > type input.csv
2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2
2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2
2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2
2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2
2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2
2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2
2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2
2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2
2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2
2,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2
2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2
2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2
2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2
Stack Overflow > p input.csv
File is 'input.csv'
csv has 20 fields
test_file for 65536 bytes:   array [819,20] (in use: 13/819 records)

2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2
2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2
2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2
2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2
2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2
2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2
2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2
2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2
2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2

[end]

Stack Overflow >

Строки ввода, в которых нет необходимого количества полей, просто игнорируются.

о коде

читать записи

Основная логика находится в csv_read, как показано ниже.

CSV_i* csv_read(
    const char* file, size_t block, const char delim)
{
    CSV_i* one = csv_create(file, block, delim);
    if (one == NULL) return NULL;
    FILE* fl = fopen(file, "r");
    if (fl == NULL)
    {
        csv_destroy(one);
        return NULL;
    }
    // read all records
    do {
        // read all fields
        char  line[MAX_LINE_SIZE] = {0};
        char* p_in = fgets(line, (int)sizeof(line), fl);
        if (p_in == NULL) break;  // read line by line
        size_t res = csv_get_line(line, one, delim);
        if (res == 0) ++one->n_records;
        if (one->n_records == one->max_records)
        {
            if (csv_extend(one) != 0)
            {
                fprintf(stderr, "no more space on array\n");
                fclose(fl);
                break;
            }
        }
    } while (1);
    fclose(fl);
    ++one->n_records;
    return one;
}
  • Файл используется вызовами fgets
  • Представление CSV в памяти создается с размером, основанным на размере блока, который является параметром функции. Затем функция вычисляет максимальное количество записей, которые могут поместиться в такой размер.
  • структура линейно расширяется по мере необходимости, в этом примере блоками по 64 КБ. Его можно изменить на меньшее значение, чтобы увидеть, как работает расширение: сообщения отправляются на stderr в этих точках расширения.

чтение полей

size_t csv_get_line(
    const char* line, CSV_i* one, const char delim)

эта функция получает строку и анализирует ее в CSV. Можно было бы использовать sscanf, но вместо этого используется очень простой двухсостоятельный автомат FSM. Легче тестировать и изменять, например. если мы решим добавить кавычки для полей.

Поскольку это отдельная функция, ее легче тестировать с помощью специальных строк ввода, помимо CSV.

Полный C код для примера

// 64K buffer at start
#define BLK_SIZE (size_t)(64 * 1024)
// standard delimiter
#define DELIM_COMMA ','
// max 4-digits fields, for testing
#define MAX_FIELD_SIZE 4
#define MAX_LINE_SIZE 250

#include <limits.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct
{
    size_t n_fields;     // columns
    size_t n_records;    // rows
    size_t max_records;  // limit
    int**  array;
} CSV_i;

CSV_i* csv_create(
    const char* file, size_t block, const char delim);
CSV_i* csv_destroy(CSV_i* del);
CSV_i* csv_read(
    const char* file, size_t block, const char delim);
int csv_show(CSV_i* csv, const char* msg);

// helpers
int    csv_extend(CSV_i* csv);
size_t csv_get_n_fields(const char* file, const char delim);
size_t csv_get_line(
    const char* line, CSV_i* csv, const char delim);

int main(int argc,char** argv)
{
    const char* def_file = "input5.csv";
    char        f_name[200] = {0};
    if (argc < 2)
        strcpy(f_name, def_file);
    else
        strcpy(f_name, argv[1]);
    fprintf(stderr, "File is '%s'\n", f_name);
    CSV_i* my_csv =
        csv_read(f_name, BLK_SIZE, DELIM_COMMA);
    char msg[80] = {0};
    sprintf(msg, "test_file for %zu bytes:   ", BLK_SIZE);
    csv_show(my_csv, msg);
    csv_destroy(my_csv);
    return 0;
}

CSV_i* csv_create(
    const char* file, size_t block, char delim)
{
    if (file == NULL) return NULL;
    size_t n_fields = csv_get_n_fields(file, delim);
    if (n_fields == 0) return NULL;
    printf("csv has %zu fields\n", n_fields);
    CSV_i* one = (CSV_i*)malloc(sizeof(CSV_i));
    if (one == NULL) return NULL;
    one->n_fields    = n_fields;
    one->n_records   = 0;  // empty
    one->max_records = block / n_fields / sizeof(int);
    if (one->max_records < 1) one->max_records = 1;
    // malloc the lines
    one->array = malloc(sizeof(int*) * one->max_records);
    if (one->array == NULL)
    {
        free(one);
        return NULL;
    }
    // build the columns inside lines
    size_t row = 0;
    for (; row < one->max_records; row += 1)
    {
        one->array[row] = malloc(sizeof(int) * n_fields);
        if (one->array[row] == NULL) break;
    }
    if (row == one->max_records) return one;
    // error: undo everything
    for (size_t i = 0; i < row; i += 1) free(one->array[i]);
    free(one->array);
    free(one);
    return NULL;
}

CSV_i* csv_destroy(CSV_i* del)
{
    if (del == NULL) return NULL;
    for (size_t row = 0; row < del->max_records; row += 1)
        free(del->array[row]);
    free(del->array);
    free(del);
    return NULL;
}

int csv_extend(CSV_i* csv)
{
    if (csv == NULL) return -1;
    size_t new_size = csv->max_records + csv->max_records;
    int**  new_block =
        realloc(csv->array, sizeof(int*) * new_size);
    if (new_block == NULL)
    {
        fprintf(
            stderr,
            "ERROR: Could not extend size from %zu to "
            "%zu\n",
            csv->max_records, new_size);
        return -2;
    }
    // build the columns inside lines
    csv->array = new_block;
    size_t row = csv->max_records;
    for (; row < new_size; row += 1)
    {
        csv->array[row] =
            malloc(sizeof(int) * csv->n_fields);
        if (csv->array[row] == NULL) break;
    }
    if (row == new_size)
    {
        fprintf(
            stderr,
            "EXTEND: size went from %zu to "
            "%zu\n",
            csv->max_records, new_size);
        csv->max_records = new_size;
        csv->array       = new_block;
        return 0;  // ok
    }
    // error: undo everything in reverse
    for (size_t i = csv->max_records; i < row; i += 1)
        free(csv->array[i]);
    new_size = csv->max_records / 2;
    new_block =
        realloc(csv->array, sizeof(int*) * new_size);
    if (new_block == NULL)
    {  // this should never fail
        fprintf(
            stderr,
            "ERROR: Could not trim size from %zu to "
            "%zu\n",
            new_size, csv->max_records);
        return -3;
    }
    csv->array = new_block;  // reset
    fprintf(
        stderr,
        "EXTEND: failed: size reset from %zu to "
        "%zu\n",
        csv->max_records, new_size);
    csv->max_records = new_size;
    return 0;
}

CSV_i* csv_read(
    const char* file, size_t block, const char delim)
{
    CSV_i* one = csv_create(file, block, delim);
    if (one == NULL) return NULL;
    FILE* fl = fopen(file, "r");
    if (fl == NULL)
    {
        csv_destroy(one);
        return NULL;
    }
    // read all records
    do {
        // read all fields
        char  line[MAX_LINE_SIZE] = {0};
        char* p_in = fgets(line, (int)sizeof(line), fl);
        if (p_in == NULL) break;  // read line by line
        size_t res = csv_get_line(line, one, delim);
        if (res == 0) ++one->n_records;
        if (one->n_records == one->max_records)
        {
            if (csv_extend(one) != 0)
            {
                fprintf(stderr, "no more space on array\n");
                fclose(fl);
                break;
            }
        }
    } while (1);
    fclose(fl);
    ++one->n_records;
    return one;
}

int csv_show(CSV_i* csv, const char* msg)
{
    if (csv == NULL) return -1;
    if (msg != NULL) printf("%s", msg);
    printf(
        "array [%zu,%zu] (in use: %zu/%zu records)\n\n",
        csv->max_records, csv->n_fields, csv->n_records - 1,
        csv->max_records);
    for (size_t row = 0; row < csv->n_records - 1; row += 1)
    {
        for (size_t col = 0; col < csv->n_fields; col += 1)
            printf("%d ", csv->array[row][col]);
        printf("\n");
    }
    printf("\n[end]\n");
    return 0;
}

size_t csv_get_n_fields(const char* file, const char delim)
{
    FILE* fl = fopen(file, "r");
    if (fl == NULL) return 0;
    char   line[250];
    size_t n_fields = 0;
    char*  p        = fgets(line, sizeof(line), fl);
    if (p == NULL)
    {
        fclose(fl);
        return 0;
    }
    while (*p != 0)
        if (delim == *p++) ++n_fields;
    fclose(fl);
    return 1 + n_fields;
}

#define ST_INFIELD 0
#define ST_OVERFLOW 1

size_t csv_get_line(
    const char* line, CSV_i* one, const char delim)
{
    char        field[MAX_LINE_SIZE] = {0};
    const char* p_in                 = line;
    char*       p_out = field;  // pointer to field
    size_t      col   = 0;
    for (; col < one->n_fields; col += 1)
    {
        char state    = ST_INFIELD;
        char read_one = 0;
        while (!read_one)
        {
            switch (state)
            {
                case ST_INFIELD:
                    if (*p_in == 0)
                    {  // eol
                        *p_out = 0;
                        one->array[one->n_records][col] =
                            atoi(field);
                        read_one = 1;
                        break;
                    }
                    if (*p_in == delim)
                    {
                        *p_out = 0;
                        one->array[one->n_records][col] =
                            atoi(field);
                        p_out = field;  // reset field ptr
                        ++p_in;
                        read_one = 1;
                        break;
                    }
                    if (*p_in == '\n')
                    {  // must be the last field
                        *p_out = 0;
                        one->array[one->n_records][col] =
                            atoi(field);
                        p_out = field;  // reset field ptr
                        ++p_in;
                        read_one = 1;
                        break;
                    }
                    if ((p_out - field) == MAX_FIELD_SIZE)
                    {
                        state = ST_OVERFLOW;
                        ++p_in;
                        break;
                    }
                    *p_out++ = *p_in++;  // copy this one
                    break;
                case ST_OVERFLOW:
                    if (*p_in == 0)
                    {  // eol
                        *p_out = 0;
                        one->array[one->n_records][col] =
                            atoi(field);
                        p_out = field;  // reset field ptr
                        read_one = 1;
                        state    = ST_INFIELD;
                        break;
                    }
                    if (*p_in == delim)
                    {
                        *p_out = 0;
                        one->array[one->n_records][col] =
                            atoi(field);
                        p_out = field;  // reset field ptr
                        state = ST_INFIELD;
                        ++p_in;
                        read_one = 1;
                        break;
                    }
                    if (*p_in == '\n')
                    {
                        *p_out = 0;
                        one->array[one->n_records][col] =
                            atoi(field);
                        p_out = field;  // reset field ptr
                        state = ST_INFIELD;
                        read_one = 1;
                        break;
                    }
                    ++p_in;  // ignore
                default:
                    break;
            }
        }  // while()
        if (*p_in == 0) break;
    }  // for
    if (col == one->n_fields - 1) return 0;
    return col;
}