LearnerC
LearnerC

Reputation: 51

File scan with specific format and retrieve the data on following numbers in C

So I'm new to C programming and have some trouble figuring out how to file scan and input each different data into each variable. I already think file scan with fscanf, fgets or fread, but i don't know how to do it. This is the code that i already write:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct testfile
    {
        char name[20];
        int number;
        int height;
        float weight;
    }file[100];

int main ()
{
    char buffer[100];
    char choose='T';
    int x,y,z;
    FILE *fp;
    fp=fopen("File.txt","r");

    /* Fscanf, but fail
    for(x=0;x<100;x++)
    {
        fscanf(fp,"%d %[^\n] %d %f",&file[x].number,file[x].name,&file[x].height,&file[x].weight);
        printf("%d %s %d %f\n",file[x].number,file[x].name,file[x].height,file[x].weight);
    }*/

    /* fgets, same fail too
    while(!feof(fp))
    {
        fgets(buffer,sizeof buffer,fp);
        sscanf(buffer,"%d",&file[x].number);
        fgets(buffer,sizeof buffer,fp);
        sscanf(buffer,"%[^\n]",file[x].name);
        fgets(buffer,sizeof buffer,fp);
        sscanf(buffer,"%d",&file[x].height);
        fgets(buffer,sizeof buffer,fp);
        sscanf(buffer,"%f",&file[x].weight);
        printf("%d %s %d %f\n",file[x].number,file[x].name,file[x].name,file[x].height,file[x].weight);
        x++;
    }
    */
    
    /*After the file is success scanned and save into each variable, the user can choose what number from the data and retrieve the following data on that number
    while (choose !='Y')
    {
    printf("Choose Data: ");
    scanf("%d",&file[x].number);
    printf("Data number : %d\nName : %s\nHeight : %d\nWeight : %.2f\n",file[x].number,file[x].name,file[x].name,file[x].height,file[x].weight);
    x++;
    printf("Get Another Data? (Y/N) : ");
    scanf("%c",&choose);
    }*/
    fclose(fp);
    return 0;
}

File.txt content:

No  Name                        Height (CM)     Weight (KG)             
1   Josh Broclin                    175             83 
2   Ryan Andreas                    184             98
3   Tom Norton                      162             111.6
4   Harry Syd                       190             68
5   Hayu Beurang                    181             75
6   Jeff Rick                       169             108
7   Asley Thomas                    179             104

This is the attempt using fscanf (not mixed with fgets)

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct testfile
    {
        char name[20];
        int number;
        int height;
        float weight;
    }file[100];

int main ()
{
    char buffer[100];
    char choose='T';
    int x,y,z;
    FILE *fp;
    fp=fopen("File.txt","r");

     //Fscanf, but fail
    for(x=0;x<10;x++)
    {
        fscanf(fp,"%d %[^\n] %d %f",&file[x].number,file[x].name,&file[x].height,&file[x].weight);
        printf("%d %s %d %f\n",file[x].number,file[x].name,file[x].name,file[x].height,file[x].weight);
    }

    fclose(fp);
    return 0;

}

And this is the output:

0  0 0.000000
0  0 0.000000
0  0 0.000000
0  0 0.000000
0  0 0.000000
0  0 0.000000
0  0 0.000000
0  0 0.000000
0  0 0.000000
0  0 0.000000

Process returned 0 (0x0)   execution time : 0.020 s
Press any key to continue.

This one is using fgets (not mixed with fscanf)

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct testfile
    {
        char name[20];
        int number;
        int height;
        float weight;
    }file[100];

int main ()
{
    char buffer[100];
    char choose='T';
    int x,y,z;
    FILE *fp;
    fp=fopen("File.txt","r");

    while(!feof(fp))
    {
        fgets(buffer,sizeof buffer,fp);
        sscanf(buffer,"%d",&file[x].number);
        fgets(buffer,sizeof buffer,fp);
        sscanf(buffer,"%[^\n]",&file[x].name);
        fgets(buffer,sizeof buffer,fp);
        sscanf(buffer,"%d",&file[x].height);
        fgets(buffer,sizeof buffer,fp);
        sscanf(buffer,"%f",&file[x].weight);
        printf("%d %s %d %f\n",file[x].number,file[x].name,file[x].name,file[x].height,file[x].weight);
        x++;
    }
    fclose(fp);
    return 0;

}

The output for fgets:

538976309 1     Josh Broclin                            175   ☻ 4225408 0.000000
153100320 5     Hayu Beurang                    ♠ 4225440 0.000000

Process returned 0 (0x0)   execution time : 0.105 s
Press any key to continue.

Both method that i currently using give a wrong output. After the file is scanned, it can retrieve data information depend on what number the user choose, for example:

Choose Data: 3
Data number :3
Name : Tom Norton
Height : 162
Weight : 111.6
Get Another Data? (Y/N) : 

So how to solve this? I've been thinking about this problem and its solution, but can't find the solution. It really disturbs me whenever I want to sleep this problem comes again and figure out how to solve it. Thank you.

Upvotes: 0

Views: 91

Answers (1)

Oka
Oka

Reputation: 26355

Reiterating and building upon the comments, the primary problem here is allowing spaces in a field but also using spaces as delimiters between fields.

1   Josh Broclin                    175             83
-^^^----*-------^^^^^^^^^^^^^^^^^^^^---^^^^^^^^^^^^^--

(In this figure: - is data, ^ are delimiters, and * wants to be both.)

With fscanf, the %s specifier works by reading and ignoring leading whitespace characters, then reading as many non-whitespace characters as it can, until stopping when it encounters a trailing whitespace character.

After %s reads the J in the example above, the next whitespace character encountered will end the conversion. There is no special distinction between the spaces marked ^ and the space marked *.

As %[^\n], the specifier %[ will read and store any character, stopping when new line character is encountered.

Note that using these specifiers without a field-width is as unsafe as gets, as they will not limit how many characters are read into the buffer. This is an easy way to overflow a buffer. A field-width is specified in the form %19s, and should be at most the size of the buffer minus one - always leaving room for the null-terminating byte.

A secondary problem is the use of fgets four times per iteration. This will attempt to read four separate lines of input, when really you want to read one line at a time.

Additionally, you do not check the return values of fgets or fscanf, meaning you may be operating on indeterminate values if these functions failed.

See here why while(!feof(fp)) is always wrong.


A quick and simple solution is to just treat the first and last names as separate fields in the file, and concatenate them in memory. snprintf can be used to safely determine if concatenating the two names resulted in truncation of the resulting string.

This will work for the data file you have shown.

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

#define MAX_PEOPLE 100

struct person {
    char name[20];
    int number;
    int height;
    float weight;
};

int parse(struct person *p, char *input)
{
    char first[20];
    char last[20];

    int cons = sscanf(input, "%d%19s%19s%d%f",
            &p->number, first, last, &p->height, &p->weight);

    return 5 == cons &&
        snprintf(p->name, sizeof p->name, "%s %s", first, last) < sizeof p->name;
}

int main(int argc, char **argv)
{
    if (argc < 2) {
        fprintf(stderr, "usage: %s FILENAME\n", *argv);
        return EXIT_FAILURE;
    }

    FILE *stream = fopen(argv[1], "r");

    if (!stream) {
        perror("fopen");
        return EXIT_FAILURE;
    }

    size_t n = 0;
    struct person people[MAX_PEOPLE] = { 0 };
    char buffer[512];

    /* consume the header */
    (void) fgets(buffer, sizeof buffer, stream);

    while (n < MAX_PEOPLE && fgets(buffer, sizeof buffer, stream)) {
        if (parse(people + n, buffer))
            n++;
    }

    fclose(stream);

    for (size_t i = 0; i < n; i++)
        printf("%d - <<%s>> H:%d W:%f\n",
                people[i].number,
                people[i].name,
                people[i].height,
                people[i].weight);

}

While simple, the solution above does impose the requirement that people have exactly two names - something that does not hold true for everyone. Using comma-separated values, would be a more robust solution.

For the file:

1,Josh Broclin,175,83
2,Ryan Andreas,184,98
3,Tom Norton,162,111.6
4,Harry Syd,190,68       
5,Hayu Beurang,181,75    
6,Jeff Rick,169,108      
7,Asley Thomas,179,104
8,This Person Has Many Names,200,100

Here is a cursory CSV parser, using strtok:

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

#define MAX_PEOPLE 100
#define EXPECTED_FIELDS 4
#define DEL ",\r\n"

struct person {
    char name[64];
    int number;
    int height;
    float weight;
};

typedef int (*parser_action)(struct person *, char *);

int parse_int(int *d, char *s)
{
    return 1 == sscanf(s, "%d", d);
}

int parse_number(struct person *p, char *field)
{
    return parse_int(&p->number, field);
}

int parse_name(struct person *p, char *field)
{
    return snprintf(p->name, sizeof p->name, "%s", field) < sizeof p->name;
}

int parse_height(struct person *p, char *field)
{
    return parse_int(&p->height, field);
}

int parse_weight(struct person *p, char *field)
{
    return 1 == sscanf(field, "%f", &p->weight);
}

int parse(struct person *p, char *input)
{
    parser_action field_actions[EXPECTED_FIELDS] = { parse_number, parse_name, parse_height, parse_weight };

    size_t i = 0;
    char *tok = strtok(input, DEL);

    while (i < EXPECTED_FIELDS && tok) {
        if (!field_actions[i++](p, tok))
            return 0;

        tok = strtok(NULL, DEL);
    }

    return EXPECTED_FIELDS == i;
}

int main(int argc, char **argv)
{
    if (argc < 2) {
        fprintf(stderr, "usage: %s FILENAME\n", *argv);
        return EXIT_FAILURE;
    }

    FILE *stream = fopen(argv[1], "r");

    if (!stream) {
        perror("fopen");
        return EXIT_FAILURE;
    }

    size_t n = 0;
    struct person people[MAX_PEOPLE] = { 0 };
    char buffer[512];

    while (n < MAX_PEOPLE && fgets(buffer, sizeof buffer, stream)) {
        if (parse(people + n, buffer))
            n++;
    }

    fclose(stream);

    for (size_t i = 0; i < n; i++)
        printf("%d - <<%s>> H:%d W:%f\n",
                people[i].number,
                people[i].name,
                people[i].height,
                people[i].weight);

}

Upvotes: 1

Related Questions