I want to read a csv file and load it into an array of struct. I used the code that I found on youtube and github (https://github.com/portfoliocourses/c-example-code/blob/main/csv_to_struct_array.c). Now I want to change all the members of the struct into an array of character (or a string). This works if I change sequentially each struct (member type
and age
at least) but if I change the member average
into an array of character, I get the error message File format incorrect
. I am suspecting thatit si because there is a problem with the character EOF
in the file during the while (!feof(file));
loop. How could I solve the problem?
Here is the original code:
/*******************************************************************************
*
* Program: Read CSV File Data To An Array Of Structs
*
* Description: Example of reading CSV file data into an array of structs in C.
*
* YouTube Lesson: https://www.youtube.com/watch?v=rbVt5v8NNe8
*
* Author: Kevin Browne @ https://portfoliocourses.com
*
*******************************************************************************/
#include <stdio.h>
// A struct for representing student data in a file formatted like this:
//
// U,Virat Kohli,23,95.6
// U,Serena Williams,22,83.2
// G,Wayne Gretzky,19,84.2
//
// with a "student type" (e.g. undergraduate, graduate) single character,
// followed by the student's name, age and then average.
//
typedef struct
{
// members for the student's type, name, age and average
char type;
char name[50];
int age;
double average;
} Student;
int main(void)
{
// file pointer variable for accessing the file
FILE *file;
// attempt to open file.txt in read mode to read the file contents
file = fopen("file.txt", "r");
// if the file failed to open, exit with an error message and status
if (file == NULL)
{
printf("Error opening file.\n");
return 1;
}
// array of structs for storing the Student data from the file
Student students[100];
// read will be used to ensure each line/record is read correctly
int read = 0;
// records will keep track of the number of Student records read from the file
int records = 0;
// read all records from the file and store them into the students array
do
{
// Read a line/record from the file with the above format, notice in
// particular how we read in the student's name with %49[^,] which matches
// up to 49 characters NOT including the comma (so it will stop matching
// at the next comma). The name member can store 50 characters, so
// factoring in the NULL terminator this is the maximum amount of characters
// we can read in for a number. fscanf() will return the number of values
// it was able to read successfully which we expect to be 4, and we store
// that into read.
//
read = fscanf(file,
"%c,%49[^,],%d,%lf\n",
&students[records].type,
students[records].name,
&students[records].age,
&students[records].average);
// if fscanf read 4 values from the file then we've successfully read
// in another record
if (read == 4) records++;
// The only time that fscanf should NOT read 4 values from the file is
// when we've reached the end of the file, so if fscanf did not read in
// exactly 4 values and we're not at the end of the file, there has been
// an error (likely due to an incorrect file format) and so we exit with
// an error message and status.
if (read != 4 && !feof(file))
{
printf("File format incorrect.\n");
return 1;
}
// if there was an error reading from the file exit with an error message
// and status
if (ferror(file))
{
printf("Error reading file.\n");
return 1;
}
} while (!feof(file));
// close the file as we are done working with it
fclose(file);
// print out the number of records read
printf("\n%d records read.\n\n", records);
// print out each of the records that was read
for (int i = 0; i < records; i++)
printf("%c %s %d %.2f\n",
students[i].type,
students[i].name,
students[i].age,
students[i].average);
printf("\n");
return 0;
}
Here is the modified code:
#include <stdio.h>
typedef struct
{
//char type; \\original commented code
//char name[50]; \\original commented code
//int age; \\original commented code
//double average; \\original commented code
char type[50];
char name[50];
char age[50];
char average[50];
} Student;
int main(void)
{
FILE *file;
file = fopen("file.txt", "r");
if (file == NULL)
{
printf("Error opening file.\n");
return 1;
}
Student students[100];
int read = 0;
int records = 0;
do
{
read = fscanf(file,
//"%c,%49[^,],%d,%lf\n" \\original commented code
"%49[^,],%49[^,],%49[^,],%49[^,]\n",
students[records].type,
students[records].name,
students[records].age,
students[records].average);
//students[records].average);
if (read == 4) records++;
if (read != 4 && !feof(file))
{
printf("File format incorrect.\n");
return 1;
}
if (ferror(file))
{
printf("Error reading file.\n");
return 1;
}
} while (!feof(file));
fclose(file);
printf("\n%d records read.\n\n", records);
for (int i = 0; i < records; i++)
//printf("%c %s %d %.2f\n", \\original commented code
printf("%s %s %s %.s\n",
students[i].type,
students[i].name,
students[i].age,
students[i].average);
printf("\n");
return 0;
}
EDIT 1
I changed the
" %49[^,],%49[^,],%49[^,],%49[^\n]",
and got this output:
3 records read.
U Virat Kohli 23
U Serena Williams 22
G Wayne Gretzky 19
There is an improvement in the sense that the file can be parsed but the last column (or item after the last comma of each line) disappears as it should read:
3 records read.
U Virat Kohli 23 95.60
U Serena Williams 22 83.20
G Wayne Gretzky 19 84.20
Here is the source code of the modified version
#include <stdio.h>
typedef struct
{
//char type; \\original commented code
//char name[50]; \\original commented code
//int age; \\original commented code
//double average; \\original commented code
char type[50];
char name[50];
char age[50];
char average[50];
} Student;
int main(void)
{
FILE *file;
file = fopen("file.txt", "r");
if (file == NULL)
{
printf("Error opening file.\n");
return 1;
}
Student students[100];
int read = 0;
int records = 0;
do
{
read = fscanf(file,
//"%c,%49[^,],%d,%lf\n" \\original commented code
//"%49[^,],%49[^,],%49[^,],%49[^,\n]",
" %49[^,],%49[^,],%49[^,],%49[^\n]",
students[records].type,
students[records].name,
students[records].age,
students[records].average);
//students[records].average);
if (read == 4) records++;
if (read != 4 && !feof(file))
{
printf("File format incorrect.\n");
return 1;
}
if (ferror(file))
{
printf("Error reading file.\n");
return 1;
}
} while (!feof(file));
fclose(file);
printf("\n%d records read.\n\n", records);
for (int i = 0; i < records; i++)
//printf("%c %s %d %.2f\n", \\original commented code
printf("%s %s %s %.s\n",
students[i].type,
students[i].name,
students[i].age,
students[i].average);
printf("\n");
return 0;
}
with the source record file.txt
(copied from the original code):
U,Virat Kohli,23,95.6
U,Serena Williams,22,83.2
G,Wayne Gretzky,19,84.2
Rewrite the format string like
" %49[^,],%49[^,],%49[^,],%49[^\n]",
Pay attention to the leading space in the format string. It allows to skip white space characters.
I assume that whole records are not ended with a comma.
Another approach is to declare a character array large enough to store a record from the file and to use fgets
instead of scanf
. Then you can parse an obtained record using either strtok
or sscanf
.
Also in the call of printf
printf("%s %s %s %.s\n",
students[i].type,
students[i].name,
students[i].age,
students[i].average);
there is incorrect conversion specifier %.s
. Instead just write %s
.