1
votes

I'm writing a program to read a map from a .csv file, and I want to read each section of the file and then write the information from it into a relevant struct in a 2D array. I use fscanf to get the number of rows and columns to malloc the array of structs, and I can read the first value up until the comma and deal with it. However, there can also be blank fields, that appear in a line such as 1 goat,,2 cat My program reads the 1 and goat and deals with that, but then the next thing it scans is just cat. I want it to read the 2 commas, recognize there's nothing in them, and then move on to the cat and read that before moving on to the next line

I don't know how to do the correct format specifications for the fscanf. Currently I've got fscanf(fp, "%[^,]", animal);

/*scans file until comma, writes the scanned text into array*/

              fscanf(fp, "%[^,]", animal);
              printf("Scanned in string for y (%d) and x %d = (%s)\n", j, k, anima;l);
              if (strcmp(animal, "") != 0)
              {   
                  /*if first character is lowercase, change to upper)*/
                  if(animal[0]>90)
                  {
                      animal[0]=animal[0]-32;
                  }
                  /*Checks if item is cat goat or mouse by checking 1st letter of the scanned in array is C G or M*/                     
                  if(strncmp(animal,"C", 1) == 0)             
                  {
                      /*copies name of the animal into the struct*/
                      strcpy(animalarray[j][k].name, "Cat");

                      /*write animal name into struct*/
                      token =strtok(animal, spaceDelimeter);
                      /* The 1 is already dealt with, can be moved past*/
                      token = strtok(NULL, commaDelimeter);
                      printf("token = %s\n", token);


                      animalarray[j][k].number= atoi(token); 


                      printf("Animal is %s, number is %d\n", animalarray[j][k].name, animalarray[j][k].number);
                  }  

The Input File is ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

2,3 1 goat,,2 cat 3 mouse, 5 goat,

The output when i run is

rows  2, columns  3 
Scanned in string for row 0 and column 0 = 1 goat
token = goat
Animal is goat, number is 1
Scanned in string for row 0 and column 1 = 2
token = (null)
Segmentation fault (core dumped)

it should be

...
Scanned in string for row 0 and column 1 = "blank"
Scanned in string for row 0 and column 1 = 2 cat
...
1

1 Answers

1
votes

fscanf cannot scan empty strings, but you can use the return value to detect whether there was anything scanned at all:

FILE *f = fopen(filename, "r");
char buf[80];
int row = 0;
int col = 0;

while (1) {
    int n = fscanf(f, "%79[^,\n]", buf);

    if (n == EOF) break;
    if (n == 0) *buf = '\0';

    printf("[%d, %d] '%s'\n", row, col, buf);

    if (fgetc(f) == ',') {
        col++;
    } else {
        col = 0;
        row++;
    }
}

If nothing was acanned, n is 0- In that case, the code explicitly clears the input string by writing the null terminator in the first char. Rows and columns are detected by whether an comma or a newline was read. (The next character can only be a comma, a newline or the end of the file.)

Another possibility is to read the input line-wise with fgets and then scan each line. The string scanning function sscanf has the same limitations as fscanf, so it might be better to use string functions, such as strchr. Here I have used strcspn from <string.h>, which counts the characters in a string until any of the given characters or the end of the string are found. That makes it very similar to the %[^n ...] format in fscanf:

FILE *f = fopen(filename, "r");
char line[80];
int row = 0;

while (fgets(line, sizeof(line), f)) {
    int col = 0;
    int offset = 0;

    while (line[offset]) {
        int next = strcspn(line + offset, ",\n");

        printf("[%d, %d] '%.*s'\n", row, col, next, line + offset);
        offset += next + 1;

        col++;
    }

    row++;
}

Again, column and line detection are by context.