Storing input from a text file to struct elements

Viewed 30

I try to store input from a text file to an array of struct's elements

For example:

#001 Bulbasaur Grass Poison
#002 Ivysaur Grass Poison
#003 Venusaur Grass Poison
#004 Charmander Fire
#005 Charmeleon Fire
#006 Charizard Fire Flying

You can see that pokemons may have 2 types and some have only 1 type and the code went well until a pokemon that has only 1 type.

This a part of the output when I try to print the array of struct's elements:

#001 Bulbasaur Grass Poison
#002 Ivysaur Grass Poison
#003 Venusaur Grass Poison
#004 Charmander Fire #005
Charmeleon Fire #006 Charizard
Fire Flying #007 Squirtle
Water #008 Wartortle Water
#009 Blastoise Water #010

I tried a lot of things from checking a '\n' in the type1 buffer until searching web but I can't find a solution, please help me.

And BTW if I am doing something wrong with the dynamic memory allocation I would love to get suggestions.

These are the structs and the function:

typedef struct pokemon
{
    int ID;
    char *number;
    char *name;
    char *type1;
    char *type2;
} Pokemon;
struct Trainer
{
    char *firstName;
    char *lastName;
    Pokemon *pokemonsInBank;
    Pokemon pokemonHeldByTheTrainer[6];
} Trainer;
struct pokemonDataBase
{
    Pokemon *pokemonsDB;
    int numOfPokemon;
} pokemonDataBase;

Storing Function

void loadPokemonsToDB()
{
    char numBuffer[maxSize];
    char nameBuffer[maxSize];
    char typeBuffer[maxSize];
    char type2Buffer[maxSize];
    int i = 0;
    char check[maxSize];
    FILE *fp = fopen("Pokemons.txt", "r");
    if (fp == NULL)
    {
        printf("Error opening the file\n");
        exit(4);
    }
    pokemonDataBase.pokemonsDB = (Pokemon *)malloc((countPok()) * sizeof(Pokemon));
    if (pokemonDataBase.pokemonsDB == NULL)
    {
        printf("Memory Error\n");
        exit(4);
    }
    for (int i = 0; i < countPok(); i++)
    {
        fscanf(fp, "%s", numBuffer);
        pokemonDataBase.pokemonsDB[i].number = (char *)malloc((strlen(numBuffer) + 1) * sizeof(char));
        if (pokemonDataBase.pokemonsDB[i].number == NULL)
        {
            for (int j = 0; j < i; j++)
            {
                free(pokemonDataBase.pokemonsDB[j].number);
            }
            printf("Memory Error at index %d", i);
            exit(4);
        }
        strcpy(pokemonDataBase.pokemonsDB[i].number, numBuffer);
        fscanf(fp, "%s", nameBuffer);
        pokemonDataBase.pokemonsDB[i].name = (char *)malloc((strlen(nameBuffer) + 1) * sizeof(char));
        if (pokemonDataBase.pokemonsDB[i].name == NULL)
        {
            for (int j = 0; j < i; j++)
            {
                free(pokemonDataBase.pokemonsDB[j].name);
            }
            printf("Memory Error at index %d", i);
            exit(4);
        }
        strcpy(pokemonDataBase.pokemonsDB[i].name, nameBuffer);
        fscanf(fp, "%s", typeBuffer);
        pokemonDataBase.pokemonsDB[i].type1 = (char *)malloc((strlen(typeBuffer) + 1) * sizeof(char));
        if (pokemonDataBase.pokemonsDB[i].type1 == NULL)
        {
            for (int j = 0; j < i; j++)
            {
                free(pokemonDataBase.pokemonsDB[j].type1);
            }
            printf("Memory Error at index %d", i);
            exit(4);
        }
        strcpy(pokemonDataBase.pokemonsDB[i].type1, typeBuffer);
        fscanf(fp, "%s", type2Buffer);
        pokemonDataBase.pokemonsDB[i].type2 = (char *)malloc((strlen(type2Buffer) + 1) * sizeof(char));
        if (pokemonDataBase.pokemonsDB[i].type2 == NULL)
        {
            for (int j = 0; j < i; j++)
            {
                free(pokemonDataBase.pokemonsDB[j].type2);
            }
            printf("Memory Error at index %d", i);
            exit(4);
        }
        strcpy(pokemonDataBase.pokemonsDB[i].type2, type2Buffer);
    }
}
1 Answers

scanf is not your friend. A whitespace in the format string does not distinguish between space and newline. You can make scanf work, but it's really not worth the effort. I recommend you not do this, but you could try something like:

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

struct pokemon
{
    int ID;
    char name[128];
    char type1[128];
    char type2[128];
};
int
main(int argc, char **argv)
{
    FILE *ifp = argc > 1 ? fopen(argv[1], "r") : stdin;
    if( ifp == NULL ){
        perror(argv[1]);
        return 1;
    }
    struct pokemon poke[128];
    struct pokemon *p = poke;
    struct pokemon *e = poke + sizeof poke / sizeof *poke;;
    int i = 0;
    int rv;

    while( p < e &&
        2 < (rv = fscanf(ifp, " #%d %127s %127s %127[^\n]",
                &p->ID,
                p->name,
                p->type1,
                p->type2
            )
        )
    ){
        assert( rv == 3 || rv == 4 );
        if( rv == 3 ){
            p->type2[0] = '\0';
        }
        p += 1;
    }
    e = p;
    p = poke;

    for( ; p < e; p++ ){
        printf("%d  %s  %s %s\n", p->ID, p->name, p->type1, p->type2);
    }

}

/* sample of expected input
#001 Bulbasaur Grass Poison

#002 Ivysaur Grass Poison

#003 Venusaur Grass Poison

#004 Charmander Fire
*/

Note that this is extremely fragile. If a row contains 10 "words", the remainder will all go into the type2 field. Also, as soon as you have a name that exceeds the 127 character length, parsing will just stop and not give any indication as to why. Handling unruly input is just not easy to do with scanf, and it's generally not worth the effort. Maybe a simple:

if( ! feof(ifp) ){
    fprintf(stderr, "invalid input\n");
    exit(1);
}

after the input loop would be sufficient, but it would be better to simply stop trying to use scanf. In the end, you will waste a lot of time trying to figure its behavior, and that time would be better spent learning how to use other tools to parse the input.

If you want to minimize changes to your existing code, you could try adding something like the following before you attempt to scan into the second type:

int c;
while( ( c = getc(ifp) ) == ' ' ){
    ;
}
if( c != '\n' ){
    ungetc(c, ifp);
    /* Scan the second word */
} else {
    /* There is no second word */
}

Note that mixing scanf with getc is a potential source of confusion, and if you don't know exactly what you're doing this can cause great frustration. The basic idea is to consume spaces until you see a non-space (note that the above code will need to be modified if you want to handle tabs or other non-newline whitespace). If the first character you see is a newline, simply move on to the next line. If it is not a newline, put it back on the stream so scanf can see it and read the second type. Again, this cannot be stated often enough, you should abandon scanf now. It will cause you far more headaches than it is worth.

Related