Программа на C для преобразования текстового файла в файл CSV

#c #csv #text

#c #csv #текст

Вопрос:

Вопрос в том, как преобразовать текстовый файл в файл CSV с помощью программирования на C. Входной текстовый файл отформатирован следующим образом:
JACK Maria Stephan Nora
20 34 45 28
London NewYork Toronto Berlin

Выходной файл CSV должен выглядеть следующим образом:

 Jack,20,London
Maria,34,NewYork
Stephan,45,Toronto
Nora,28,Berlin
  

Следующий код — это то, что я пробовал до сих пор:

 void  load_and_convert(const char* filename){
    FILE *fp1, *fp2;
    char ch;

    fp1=fopen(filename,"r");
    fp2=fopen("output.csv","w");

    for(int i=0;i<1000;i  ){
         ch=fgetc(fp1);
         fprintf(fp2,"%c",ch);    
         if(ch==' '|| ch=='n')
              fprintf(fp2,"%c,n",ch);
}
    fclose(fp1);
    fclose(fp2);

}
  

Результат моего кода выглядит следующим образом:

 Jack,
Maria,
Stephan,
Nora,
20,
34,
45,
28,
London,
NewYork,
Toronto,
Berlin,
  

Как я должен изменить свой код, чтобы он работал правильно?

В чем идея решения этого вопроса?

Комментарии:

1. О боже… fgetc возвращает int , и вы будете знать, что дочитали файл до конца, по тому факту, что возвращаемое значение равно EOF .

2. В остальном вы даже не близки к решению, поскольку вам нужно транспонировать выходные данные; либо вам нужно прочитать содержимое в 2D-массив, либо использовать 3 FILE * секунды или что-то столь же запутанное.

3. Если он действительно такой маленький, вы можете прочитать 3 строки с помощью fgets ; и использовать strtok_r для обозначения их по отдельности…

4. Иногда решением является язык более высокого уровня…

5. OT: относительно: fp1=fopen(filename,"r"); и fp2=fopen("output.csv","w"); всегда проверяйте (!=NULL) возвращаемое значение, чтобы убедиться, что операция прошла успешно. Если не удалось, то вызовите perror( "my error message" ); для вывода как вашего сообщения об ошибке, так и текстовой причины, по которой, по мнению системы, произошла ошибка stderr .

Ответ №1:

Поскольку у меня бывали случаи, вот рабочее решение для вас (я изо всех сил старался сделать решение как можно более элегантным):

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

#define MAX_STRING_LENGTH 50
#define MAX_NUMBER_OF_PEOPLE 50

typedef struct  
{  
  char name[MAX_STRING_LENGTH];
  int age;
  char city[MAX_STRING_LENGTH];
} Person;

void getName(char *src, char *delim, Person *people) {
  char *ptr = strtok(src, delim);
  int i = 0;
  while(ptr != NULL)
  {
    strncpy(people[i].name, ptr, MAX_STRING_LENGTH);
    ptr = strtok(NULL, delim);
    i  ;
  }
}

void getAge(char *src, char *delim, Person *people) {
  char *ptr = strtok(src, delim);
  int i = 0;
  while(ptr != NULL)
  {
    people[i].age = atoi(ptr);
    i  ;
    ptr = strtok(NULL, delim);
  }
}

void getCity(char *src, char *delim, Person *people) {
  char *ptr = strtok(src, delim);
  int i = 0;
  while(ptr != NULL)
  {
    strncpy(people[i].city, ptr, MAX_STRING_LENGTH);
    i  ;
    ptr = strtok(NULL, delim);
  }
}

int main(void)
{
  Person somebody[MAX_NUMBER_OF_PEOPLE];
  FILE *fp;
  char *line = NULL;
  size_t len = 0;
  ssize_t read;
  int ln = 0;

  fp = fopen("./test.txt", "r");
  if (fp == NULL)
      return -1;

  // Read every line, support first line is name, second line is age...
  while ((read = getline(amp;line, amp;len, fp)) != -1) {
    // remote trailing newline character
    line = strtok(line, "n");
    if (ln == 0) {
      getName(line, " ", somebody);
    } else if (ln == 1) {
      getAge(line, " ", somebody);
    } else {
      getCity(line, " ", somebody);
    }
    ln  ;
  }

  for (int j = 0; j < MAX_NUMBER_OF_PEOPLE; j  ) {
      if (somebody[j].age == 0) 
        break;
      printf("%s, %d, %sn", somebody[j].name, somebody[j].age, somebody[j].city);
  }

  fclose(fp);
  if (line)
      free(line);

  return 0;
}
  

Ответ №2:

То, что вам нужно сделать, нетривиально, если вы хотите решить проблему сохранения всех значений в памяти при преобразовании 3 строк с 4 полями в каждой строке в формат из 4 строк с 3 полями в строке. Итак, когда у вас есть файл данных, содержащий:

Пример входного файла

 $ cat dat/col2csv3x4.txt
JACK Maria Stephan Nora
20 34 45 28
London NewYork Toronto Berlin
  

Вы хотите прочитать каждую из трех строк, а затем преобразовать столбцы в строки для .csv вывода. Это означает, что в итоге вы получите 4 строки по 3 csv-поля в каждой, например

Ожидаемый результат программы

 $ ./bin/transpose2csv < dat/col2csv3x4.txt
JACK,20,London
Maria,34,NewYork
Stephan,45,Toronto
Nora,28,Berlin
  

В этом нет ничего сложного, но требуется тщательное внимание к обработке памяти объекта и распределению / перераспределению для обработки преобразования из 3 строк с 4 частями данных в 4 строки с 3 частями данных.

Один из подходов заключается в чтении всех исходных строк в типичную настройку типа указатель на указатель на символ. Затем преобразуйте / транспонируйте столбцы в строки. Поскольку, предположительно, в следующий раз может быть 100 строк с 500 полями, вам захочется подойти к преобразованию с использованием индексов и счетчиков для отслеживания ваших требований к распределению и перераспределению, чтобы ваш готовый код мог обрабатывать перенос общего количества строк и полей в поля — количество строк с таким количеством значений в строке, сколько у вас было исходных строк.

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

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

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

#define NPTR 2
#define NWRD 128
#define MAXC 1024

/** getlines allocates all storage required to read all lines from file.
 *  the pointers are doubled each time reallocation is needed and then
 *  realloc'ed a final time to exactly size to the number of lines. all
 *  lines are stored with the exact memory required.
 */
char **getlines (size_t *n, FILE *fp)
{
    size_t nptr = NPTR;     /* tracks number of allocated pointers */
    char buf[MAXC];         /* tmp buffer sufficient to hold each line */
    char **lines = calloc (nptr, sizeof *lines);

    if (!lines) {   /* validate EVERY allocaiton */
        perror ("calloc-lines");
        return NULL;
    }

    *n = 0;         /* pointer tracks no. of lines read */
    rewind (fp);    /* clears stream error state if set */

    while (fgets (buf, MAXC, fp)) { /* read each line o finput */
        size_t len;

        if (*n == nptr) {   /* check/realloc ptrs if required */
            void *tmp = realloc (lines, 2 * nptr * sizeof *lines);
            if (!tmp) {     /* validate reallocation */
                perror ("realloc-tmp");
                break;
            }
            lines = tmp;    /* assign new block, (opt, zero new mem below) */
            memset (lines   nptr, 0, nptr * sizeof *lines);
            nptr *= 2;      /* increment allocated pointer count */
        }

        buf[(len = strcspn(buf, "rn"))] = 0;  /* get line, remove 'n' */
        lines[*n] = malloc (len   1);           /* allocate for line */
        if (!lines[*n]) {                       /* validate */
            perror ("malloc-lines[*n]");
            break;
        }
        memcpy (lines[(*n)  ], buf, len   1);   /* copy to line[*n] */
    }

    if (!*n) {          /* if no lines read */
        free (lines);   /* free pointers */
        return NULL;
    }

    /* optional final realloc to free unused pointers */
    void *tmp = realloc (lines, *n * sizeof *lines);
    if (!tmp) {
        perror ("final-realloc");
        return lines;
    }

    return (lines = tmp);   /* return ptr to exact no. of required ptrs */
}

/** free all pointers and n alocated arrays */
void freep2p (void *p2p, size_t n)
{
    for (size_t i = 0; i < n; i  )
        free (((char **)p2p)[i]);
    free (p2p);
}

/** transpose a file of n rows and a varying number of fields to an
 *  allocated pointer-to-pointer t0 char structure with a fields number 
 *  of rows and n csv values per row.
 */
char **transpose2csv (size_t *n, FILE *fp)
{
    char **l = NULL, **t = NULL;
    size_t  csvl = 0,       /* csv line count */
            ncsv = 0,       /* number of csv lines allocated */
            nchr = MAXC,    /* initial chars alloc for csv line */
            *offset,        /* array tracking read offsets in lines */
            *used;          /* array tracking write offset to csv lines */

    if (!(l = getlines (n, fp))) {  /* read all lines to l */
        fputs ("error: getlines failed.n", stderr);
        return NULL;
    }
    ncsv = *n;
#ifdef DEBUG
    for (size_t i = 0; i < *n; i  )
        puts (l[i]);
#endif

    if (!(t = malloc (ncsv * sizeof *t))) { /* alloc ncsv ptrs for csv */
        perror ("malloc-t");
        freep2p (l, *n);        /* free everything else on failure */
        return NULL;
    }

    for (size_t i = 0; i < ncsv; i  )   /* alloc MAXC chars to csv ptrs */
        if (!(t[i] = malloc (nchr * sizeof *t[i]))) {
            perror ("malloc-t[i]");
            while (i--)         /* free everything else on failure */
                free (t[i]);
            free (t);
            freep2p (l, *n);
            return NULL;
        }

    if (!(offset = calloc (*n, sizeof *offset))) {  /* alloc offsets array */
        perror ("calloc-offsets");
        free (t);
        freep2p (l, *n);
        return NULL;
    }

    if (!(used = calloc (ncsv, sizeof *used))) {    /* alloc used array */
        perror ("calloc-used");
        free (t);
        free (offset);
        freep2p (l, *n);
        return NULL;
    }

    for (;;) {  /* loop continually transposing cols to csv rows */
        for (size_t i = 0; i < *n; i  ) { /* read next word from each line */
            char word[NWRD];    /* tmp buffer for word */
            int off;            /* number of characters consumed in read */
            if (sscanf (l[i]   offset[i], "%s%n", word, amp;off) != 1)
                goto readdone;  /* break nested loops on read failure */
            size_t len = strlen (word);         /* get word length */
            offset[i]  = off;                   /* increment read offset */
            if (csvl == ncsv) { /* check/realloc new csv row as required */
                size_t newsz = ncsv   1;    /* allocate  1 row over *n */
                void *tmp = realloc (t, newsz * sizeof *t); /* realloc ptrs */
                if (!tmp) {
                    perror ("realloc-t");
                    freep2p (t, ncsv);
                    goto readdone;
                }
                t = tmp;
                t[ncsv] = NULL;     /* set new pointer NULL */

                /* allocate nchr chars to new pointer */
                if (!(t[ncsv] = malloc (nchr * sizeof *t[ncsv]))) {
                    perror ("malloc-t[i]");
                    while (ncsv--)   /* free everything else on failure */
                        free (t[ncsv]);
                    goto readdone;
                }

                tmp = realloc (used, newsz * sizeof *used); /* realloc used */
                if (!tmp) {
                    perror ("realloc-used");
                    freep2p (t, ncsv);
                    goto readdone;
                }
                used = tmp;
                used[ncsv] = 0;

                ncsv  ;
            }
            if (nchr - used[csvl] - 2 < len) {  /* check word fits in line */
                /* realloc t[i] if required (left for you) */
                fputs ("realloc t[i] required.n", stderr);
            }
            /* write word to csv line at end */
            sprintf (t[csvl]   used[csvl], used[csvl] ? ",%s" : "%s", word);
            t[csvl][used[csvl] ? used[csvl]   len   1 : len] = 0;
            used[csvl]  = used[csvl] ? len   1 : len;
        }
        csvl  ;
    }
    readdone:;

    freep2p (l, *n);
    free (offset);
    free (used);

    *n = csvl;

    return t;
}

int main (int argc, char **argv) {

    char **t;
    size_t n = 0;
    /* 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 (!(t = transpose2csv (amp;n, fp))) {
        fputs ("error: transpose2csv failed.n", stderr);
        return 1;
    }

    if (fp != stdin) fclose (fp);   /* close file if not stdin */

    for (size_t i = 0; i < n; i  )
        if (t[i])
        puts (t[i]);

    freep2p (t, n);

    return 0;
}
  

Пример использования / вывода

 $ ./bin/transpose2csv < dat/col2csv3x4.txt
JACK,20,London
Maria,34,NewYork
Stephan,45,Toronto
Nora,28,Berlin
  

Использование памяти / Проверка ошибок

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

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

Для Linux valgrind это обычный выбор. Для каждой платформы существуют аналогичные средства проверки памяти. Все они просты в использовании, просто запустите через них свою программу.

 $ valgrind ./bin/transpose2csv < dat/col2csv3x4.txt
==18604== Memcheck, a memory error detector
==18604== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==18604== Using Valgrind-3.12.0 and LibVEX; rerun with -h for copyright info
==18604== Command: ./bin/transpose2csv
==18604==
JACK,20,London
Maria,34,NewYork
Stephan,45,Toronto
Nora,28,Berlin
==18604==
==18604== HEAP SUMMARY:
==18604==     in use at exit: 0 bytes in 0 blocks
==18604==   total heap usage: 15 allocs, 15 frees, 4,371 bytes allocated
==18604==
==18604== All heap blocks were freed -- no leaks are possible
==18604==
==18604== For counts of detected and suppressed errors, rerun with: -v
==18604== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
  

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

Просмотрите все и дайте мне знать, если у вас возникнут дополнительные вопросы.