Search code examples
cparsingscanf

Parser only reads two fields out of the intended 7


Source code:

#include <stdio.h>
#include <stdbool.h>
#include <unistd.h>
#include <conio.h>
#include <windows.h>
#include "addon.h"

static book* archivelib;
static book* noticelib;
const int MAX_LINE_LENGTH = (255);

void fileCheck(file sourcefile) { //Checks if a file exists
    if (access(sourcefile.filename, F_OK) != false) {
        textcolour(RED);
        fprintf(stderr, "Error: %s does not exist.\n", sourcefile.filename);
        exit(EXIT_FAILURE);
    };
};

size_t recordcount(file source) { //Finds how many records are in a file
    source.fp = fopen(source.filename, "r");
    char ch;
    size_t records = 0;
    fseek(source.fp, START_OF_LINE, SEEK_SET);
    while ((ch = fgetc(source.fp)) != EOF)
        if (ch == '\n')
            records++;
    fclose(source.fp);
    return records;
};

void textcolour(int colour) {
    static int __BACKGROUND;
    HANDLE h = GetStdHandle(STD_OUTPUT_HANDLE);
    CONSOLE_SCREEN_BUFFER_INFO csbiInfo;

    GetConsoleScreenBufferInfo(h, &csbiInfo);

    SetConsoleTextAttribute (GetStdHandle (STD_OUTPUT_HANDLE),
                             colour + (__BACKGROUND << 4));
}

void cls(HANDLE hConsole) {
    CONSOLE_SCREEN_BUFFER_INFO csbi;
    SMALL_RECT scrollRect;
    COORD scrollTarget;
    CHAR_INFO fill;

    // Get the number of character cells in the current buffer.
    if (!GetConsoleScreenBufferInfo(hConsole, &csbi)) {
        return;
    }

    // Scroll the rectangle of the entire buffer.
    scrollRect.Left = 0;
    scrollRect.Top = 0;
    scrollRect.Right = csbi.dwSize.X;
    scrollRect.Bottom = csbi.dwSize.Y;

    // Scroll it upwards off the top of the buffer with a magnitude of the entire height.
    scrollTarget.X = 0;
    scrollTarget.Y = (SHORT)(0 - csbi.dwSize.Y);

    // Fill with empty spaces with the buffer's default text attribute.
    fill.Char.UnicodeChar = TEXT(' ');
    fill.Attributes = csbi.wAttributes;

    // Do the scroll
    ScrollConsoleScreenBuffer(hConsole, &scrollRect, NULL, scrollTarget, &fill);

    // Move the cursor to the top left corner too.
    csbi.dwCursorPosition.X = 0;
    csbi.dwCursorPosition.Y = 0;

    SetConsoleCursorPosition(hConsole, csbi.dwCursorPosition);
}

void clearscreen()  {
    HANDLE hStdout;
    hStdout = GetStdHandle(STD_OUTPUT_HANDLE);
    cls(hStdout);
}

const book emptybook = {
    .checkdigit = (0),
    .ISBN = "empty",
    .title = "empty",
    .DOR = "empty",
    .PersonDetails = { .firstname = "empty", .lastname = "empty", .fullname = "empty"},
    .status = "empty",
};

static file notice = {
    .filename = "NOTICE.txt",
    .sizeofline = MAX_LINE_LENGTH,
    .field = { .format = "%17[^|]|%55s|%10s|%10s|%21s|%10[^|]|%26[^\n]\n", .num = (7)},
};

static file archive = {
    .filename = "ARCHIVE.txt",
    .sizeofline = MAX_LINE_LENGTH,
    .field = { .format = "%17[^|]|%55s|%10s|%10s|%21s|%10[^|]|%26[^\n]\n", .num = (7)},
};

void printBook(book bk) { //Print information on book bk
    if (strcmp(bk.title, bk.PersonDetails.firstname) == 0) {
        textcolour(RED);
        fprintf(stderr, "Error: The book could not be printed\n");
        sleep(2);
        clearscreen();
        return;
    };

    printf("|ISBN:            %s\n", bk.ISBN);
    printf("|Book:            %s\n", bk.title);
    printf("|Authour:         %s\n", bk.PersonDetails.fullname);
    printf("|Date of Release: %s\n", bk.DOR);
};

book* alloclib(file libfile) {
    fileCheck(libfile); //check the file
    libfile.records = recordcount(libfile); //find the number of records in file

    book* lib = malloc(libfile.records * sizeof *lib);
    if (lib == NULL) {
        textcolour(RED);
        fprintf(stderr, "Memory allocation failed!\n");
        exit(EXIT_FAILURE);
    };
    return(lib);
}

int CmpBookRecs(const void* recA, const void* recB) { //Compare book records alphabetically
    int result = 0;
    book* bkRecA = (book*)recA;
    book* bkRecB = (book*)recB;

    //Compare the titles
    result = strcmp(bkRecA->title,
                    bkRecB->title);

    //If (titles match) compare the names
    if (!result)
        result = strcmp(bkRecA->PersonDetails.lastname,
                        bkRecB->PersonDetails.lastname);

    //If (names match) compare the ISBN
    if (!result)
        result = strcmp(bkRecA->ISBN,
                        bkRecB->ISBN);
    return (result); //Return the the result
};

book* loadlibrary(file libsource) { //Loads all the books in "libarchive"
    printf("Loading library...\n");
    sleep(2);

    libsource.fp = fopen(libsource.filename, "r");
    if (libsource.fp == NULL) {
        textcolour(RED);
        fprintf(stderr, "Error opening file.\n"); //Return an error if the file cannot be opened
        exit(EXIT_FAILURE);
    };

    int read = 0; //read will be used to ensure each line/record is read correctly
    int records = 0; //records will keep track of the number of Student records read from the file

    char* token; //smaller char* broken from line
    const char* del = "-"; //the delimiter the line is broken into a series of tokens by

    //Read all records from the file and store them into the lib
    book* lib = alloclib(libsource);
    do {
        //Check how many fields are in the file
        read = fscanf(libsource.fp, libsource.field.format, lib[records].ISBN,
                                                            lib[records].title,
                                                            lib[records].PersonDetails.firstname,
                                                            lib[records].PersonDetails.lastname,
                                                            lib[records].PersonDetails.fullname,
                                                            lib[records].DOR);
        //If fscanf read values from the file, assign values then successfully add an another record
        if (read == libsource.field.num)
            records++;

        //If read is not equal the field size and it is not the end of file
        if (read != libsource.field.num && !feof(libsource.fp)) {
            textcolour(RED);
            fprintf(stderr, "File format incorrect. %d fields read instead of %d\n", read, libsource.field.num);
            sleep(2);
            exit(EXIT_FAILURE);
        };

        //If there was an error reading from the file exit with an error message and status
        if (ferror(libsource.fp)) {
            textcolour(RED);
            fprintf(stderr, "Error in reading file.\n");
            exit(EXIT_FAILURE);
        };
        lib[records].checkdigit = atoi(strrchr(lib[records].ISBN, '-'));
    } while (!feof(libsource.fp) && records < libsource.records);

    textcolour(LIGHTGREEN);
    printf("Sorting Library...\n");
    sleep(2);
    qsort(lib, records, sizeof(*lib), CmpBookRecs);
    textcolour(GREEN);
    printf("Library was sorted!\n\n");

    //free(line);
    fclose(libsource.fp);
    printf("Library was successfully loaded...\n");
    sleep(2);
    return (lib);
};

int CheckISBN(char* ISBN) { //Calculates the check digit for 13 digit International Standard Book Number
    int x[13];
    int count = 0;
    int i = 0;

    while (count < strlen(ISBN) && ISBN[i] != '\0') {
        if (isdigit(ISBN[i])) {
            x[count] = ISBN[i] - '0'; // Convert character to integer
            count++;
        } i++;
    }

    int s = 0,
        t = 0;

    //Calculation
    for (i = 0; i < 12; ++i) {
        if (i % 2 == 0) {
            t += (x[i] * 3);
        } else {
            t += x[i];
        } s += t;
    };

    int r = s % 10;
    return (10 - r);
};

book booksearch(file libsource, book* lib) { //Allows the user to search for a specify book based on its title or ISBN
    book foundbook; //book to be found
    char* search = malloc((MAX_LINE_LENGTH + 1) * sizeof *search); //search title
    char information[MAX_LINE_LENGTH]; //book title in record

    char* option;
    bool flag = true;
    do { //Infinite loop until input
        printf("\n");
        textcolour(BLUE);
        printf(" |     _____   ___   ____  ____      __  __ __             |\n");
        printf(" |    / ___/  /  _] /    ||    `    /  ]|  |  |            |\n");
        printf(" |   (   |_  /  [_ |  o  ||  D  )  /  / |  |  |            |\n");
        printf(" |    |__  ||    _]|     ||    /  /  /  |  _  |            |\n");
        printf(" |    /  ` ||   [_ |  _  ||    ` /   |_ |  |  |            |\n");
        printf(" |    `    ||     ||  |  ||  .  ``     ||  |  |            |\n");
        printf(" |     |___||_____||__|__||__||_| |____||__|__|            |\n");
        printf(" |     ___   ____   ______  ____   ___   ____    _____     |\n");
        printf(" |    /   ` |    ` |      ||    | /   ` |    `  / ___/     |\n");
        printf(" |   |     ||  o  )|      | |  | |     ||  _  |(   |_      |\n");
        printf(" |   |  O  ||   _/ |_|  |_| |  | |  O  ||  |  | |__  |     |\n");
        printf(" |   |     ||  |     |  |   |  | |     ||  |  | /  ` |     |\n");
        printf(" |   |     ||  |     |  |   |  | |     ||  |  | `    |     |\n");
        printf(" |    |___/ |__|     |__|  |____| |___/ |__|__|  |___|     |\n");
        printf(" |                                                         |\n");

        printf(" |- [");

        textcolour(LIGHTBLUE);
        printf("1");

        textcolour(BLUE);
        printf("] ISBN                                               |\n");
        printf(" |- [");

        textcolour(LIGHTBLUE);
        printf("2");

        textcolour(BLUE);
        printf("] Title                                              |\n");
        printf(" |- [");

        textcolour(LIGHTBLUE);
        printf("3");

        textcolour(BLUE);
        printf("] Exit                                               |\n\n");

        //User option
        switch (getc(stdin)) {
            case '1': option = "ISBN";
                      flag = false;
                      break;
            case '2': option = "Title";
                      flag = false;
                      break;
            case '3': return(emptybook); //Exit
                      break;
            default : textcolour(RED); //Red
                      fprintf(stderr, "\nInvalid option. Try again.");
                      clearscreen();
                      break;
        };
    } while (flag == true);

    //Prompt user for the book they want to search
    textcolour(YELLOW);
    clearscreen();
    sleep(2);
    printf("\nPlease enter the %s of the book you are searching for:\n", option);
    scanf("%s", search);
    //fgets(search, strlen(search), stdin);

    //Remove newline character if exists
    if ((strlen(search) > 0) && (search[strlen(search) - 1] == '\n'))
        search[strlen(search) - 1] = '\0';

    //Check if title is proper
    if (strcmp(option, "Title") != 0) {
        //Convert to search to all caps
        for (int i = 0; search[i] != '\0'; i++) {
            //If any character in the char* is not a character or char* is empty
            if (isalpha(search[i]) == 0 || search == NULL)
                goto impromperformat;
            search[i] = toupper(search[i]);
        };
    };

    //Check if ISBN is correct
    if (strcmp(option, "ISBN") == 0) {
        int q[5];
        int read = sscanf(search, "%3d-%2d-%4d-%3d-%1d", q[0],q[1],q[2],q[3],q[4]);
        if (read != 5) {
            impromperformat:
                textcolour(RED);
                fprintf(stderr, "This not a %s. Returning...\n", option);
                return(emptybook);
        }

        if (read == 5 && q[4] != CheckISBN(search)) {
            textcolour(RED);
            fprintf(stderr, "This not a proper ISBN. Returning...\n", option);
            return(emptybook);
        }
    };

    int records = 0; //the number of lines in the record
    bool found = false; //if the the book was found
    int linenumber = 0;
    const char* del = "|"; //character that seperates each field

    //While there are book records that have not been read
    while (fgets(information, libsource.sizeofline, libsource.fp) != NULL && linenumber != notice.records) {
        for (int i = 0; information[i] != '\n'; i++)
            information[i] = toupper(information[i]);

        //If the an occurrence of the substring, (search) in the string, (line) is found
        if (strstr(information, search) != NULL) {
            int read = sscanf(information, libsource.field.format, lib[records].ISBN,
                                                                   lib[records].title,
                                                                   lib[records].PersonDetails.firstname,
                                                                   lib[records].PersonDetails.lastname,
                                                                   lib[records].PersonDetails.fullname,
                                                                   lib[records].DOR);
            if (read != 5)
                goto notfound;

            foundbook.checkdigit = atoi(strrchr(foundbook.ISBN, '-'));
            if (foundbook.checkdigit != CheckISBN(foundbook.ISBN))
                goto notfound;

            if (strcmp(foundbook.status, "Available") != 0) {
                textcolour(RED);
                fprintf(stderr, "This book is currently unavailable.\n");
                return(emptybook);
            };

            textcolour(LIGHTGREEN);
            printf("A match was found for %s!\n", search); //Book is found
            found = true;
        } linenumber++;
    };

    notfound:
        //free(token); //Free up book title
        free(search); //Free up search

    if (found == false) {
        textcolour(LIGHTRED);
        printf("Sorry, %s is currently not available. Better add one to the bookshelf!\n", search);
        sleep(2);
        return(emptybook); //return an empty book if found is still false
    };
};

void titleview(book* lib, size_t libsize) {
    lib = alloclib(notice);
    lib = loadlibrary(notice);

    textcolour(CYAN);
    printf("| These are all records of the book currently at the library: \n");
    for (int records = 0; records < libsize; records++) {
        while (lib[records].checkdigit != CheckISBN(lib[records].ISBN))
            records++; //Go to next record skipping the incorrect record
        textcolour(BLUE);
        printf("%d) ", records);

        textcolour(CYAN);
        if (records % 2 == 0)
            textcolour(LIGHTBLUE);
        printBook(lib[records]);
        sleep(2);
        printf("\n");
    };
};

void menu(void) { //Program Menu
    book found;
    do { //Infinite loop until input
        printf("\n");
        textcolour(YELLOW);
        printf(" ___________________________________________________________________________________________________________________________________\n");
        printf(" |-     __  ___                __                            __            _____                                  _   __            -|\n");
        printf(" |-    /  |/  / ___  ___ _ ___/ / ___  _    __ _  __ ___ _  / / ___       / ___/ ___   __ _   __ _  __ __  ___   (_) / /_  __ __    -|\n");
        printf(" |-   / /|_/ / / -_)/ _ `// _  / / _ `| |/|/ /| |/ // _ `/ / / / -_)     / /__  / _ ` /  ' ` /  ' `/ // / / _ ` / / / __/ / // /    -|\n");
        printf(" |-  /_/  /_/  |__/ |_,_/ |_,_/  |___/|__,__/ |___/ |_,_/ /_/  |__/      |___/  |___//_/_/_//_/_/_/|_,_/ /_//_//_/  |__/  |_, /     -|\n");
        printf(" |-                                                                                                                      /___/      -|\n");
        printf(" |-     __    _    __                                  ____              __                                                         -|\n");
        printf(" |-    / /   (_)  / /   ____ ___ _  ____  __ __       / __/  __ __  ___ / /_ ___   __ _                                             -|\n");
        printf(" |-   / /__ / /  / _ ` / __// _ `/ / __/ / // /      _` `   / // / (_-</ __// -_) /  ' `                                            -|\n");
        printf(" |-  /____//_/  /_.__//_/   |_,_/ /_/    |_, /      /___/   |_, / /___/|__/ |__/ /_/_/_/                                            -|\n");
        printf(" |-                                     /___/              /___/                                                                    -|\n");
        printf(" |-                                                                                                                                 -|\n");
        printf(" |   [1] View Upcoming Titles                                                                                                        |\n");
        printf(" |   [2] Book Search                                                                                                                 |\n");
        printf(" |   [3] Exit                                                                                                                        |\n");

        //User option
        switch (getc(stdin)) {
            case '1': clearscreen();
                      sleep(2);
                      titleview(noticelib, notice.records);
                      break;
            case '2': clearscreen();
                      sleep(2);
                      found = booksearch(archive, archivelib); //Allows user to search a book
                      printBook(found);
                      break;
            case '3': exit(0); //Exit
                      break;
            default : textcolour(RED);
                      fprintf(stderr, "Invalid option. Try again.");
                      sleep(2);
                      clearscreen();
        };
    } while(1);
};

int main() {
    noticelib = alloclib(notice);
    archivelib = alloclib(archive);
    menu();
    free(archivelib); //free the archivelib
    free(noticelib); //free the noticelib
    return 0;
};

And my custom header:

#include <stdio.h>
#include <stdbool.h>
#include <unistd.h>
#include <conio.h>
#include <windows.h>
#include <stdio.h>

#ifndef __ADDON_H__
#define __ADDON_H__

#define MAX_PATH_LENGTH (255)
const int FIRST_LINE = 1;
const int START_OF_LINE = 0;

typedef struct {
    size_t records;
    size_t sizeofline;
    char* filename;
    FILE* fp;
    struct {
        int num;
        char* format;
    } field;
} file;

typedef struct {
    int checkdigit; //permits mathematical checking for accuracy
    char ISBN[18]; //ISBN number
    char title[76]; //Title of material
    char DOR[11]; //Date of release for the item
    struct {
        char firstname[11]; //first name
        char lastname[11]; //last name
        char fullname[22]; //First + Middle + Last name of Authour
    } PersonDetails; //details about a person
    char status[27];
} book; //structure for a book

enum {
    BLACK = 0,
    BLUE,
    GREEN,
    CYAN,
    RED,
    MAGENTA,
    BROWN,
    LIGHTGRAY,
    DARKGRAY,
    LIGHTBLUE,
    LIGHTGREEN,
    LIGHTCYAN,
    LIGHTRED,
    LIGHTMAGENTA,
    YELLOW,
    WHITE
};

#endif //__ADDON_H__

I've been trying to get the number of fields that could successfully be scanned in my file as read in the function loadlibrary. However, it only reads two fields out of the intended 7.

Any corrections on how to fix the error or improve my code are welcome. Thank you!

Here is the data I am currently testing it on: ARCHIVE.txt

ISBN|Title|First Name|Last Name|Full Name|DOR|Status
978-51-7947-772-3|"Bleed Magic"|Orville|Baker|Orville Baker|15/04/2014|Available
978-05-0191-800-4|"Solar Waltz"|Alejandra|Bauer|Alejandra Bauer|30/07/1985|Unavailable - Rented
978-43-8687-725-8|"From Halsey to Hal's Island"|Jacklyn|Bruce|Jacklyn Bruce|09/05/1996|Available
978-36-2955-426-0|"Witch With Honor"|Miriam|Bryant|Miriam Bryant|14/07/1992|Unavailable - Damaged
978-95-2736-473-4|"Point A to Z"|Sang|Cherry|Sang Cherry|28/07/1987|Unavailable - Library use only
978-45-0742-523-8|"Priest named Sins"|Teddy|Decker|Teddy Decker|15/09/1989|Unavailable - Missing
978-27-1385-463-7|"Success of the Solstice"|Sandra|Duncan|Sandra Duncan|24/04/1989|Unavailable - In Process
978-31-8143-556-4|"What’s Over There?"|Leonel|Espinoza|Leonel Espinoza|25/03/2003|Available
978-95-5238-240-6|"The Amazing Adventures of Ice-Boy: Robots of Everest"|Ingrid|Guerra|Ingrid Guerra|24/06/2004|Available
978-85-7786-850-6|"One Boy And The World!"|Ezequiel|Hall|Ezequiel Hall|01/11/1994|Unavailable - Rented
978-28-5593-358-0|"Origin Of The Fog"|Nicky|Hancock|Nicky Hancock|23/08/2013|Available
978-33-2798-076-7|"Signs of the Past"|Lynwood|Hardin|Lynwood Hardin|27/02/1992|Available
978-23-8499-409-0|"Bleeding At The Past"|Renee|Johnston|Renee Johnston|07/05/1993|Available
978-32-9910-416-1|"Vision of Evil"|Jon|Kirby|Jon Kirby|16/02/2007|Unavailable - Rented

NOTICE.txt

ISBN|Title|First Name|Last Name|Full Name|DOR|Status
978-89-8818-596-4|"London Titans: As We Still Exist"|Kieth|Kirk|Kieth Kirk|08/08/1996|Unavailable - Coming Soon
978-30-7350-796-6|"Linger Longer"|Ester|Li|Ester Li|05/07/2022|Unavailable - Coming Soon
978-87-6109-805-4|"Till You, My Wallflower, Are Blooming"|Penny|Lucero|Penny Lucero|06/08/2018|Unavailable - Coming Soon
978-44-5008-783-5|"Fin and Tail, Claw and Tooth"|Bernadine|Mckinney|Bernadine Mckinney|18/10/2023|Unavailable - Coming Soon
978-60-3893-332-9|"Yes... Maybe? No!"|Vicky|Mills|Vicky Mills|05/02/2008|Unavailable - Coming Soon
978-55-4205-071-3|"Mermaids and Sirens"|Genevieve|Ochoa|Genevieve Ochoa|01/07/1987|Unavailable - Coming Soon
978-66-8758-265-7|"O Death, Where Is Thy Sting?"|Mohamed|Oconnell|Mohamed Oconnell|16/02/2021|Unavailable - Coming Soon
978-68-4196-147-2|"Behind the Door"|Hunter|Payne|Hunter Payne|19/05/2010|Unavailable - Coming Soon
978-60-6519-219-5|"Stay Hidden"|Saul|Powers|Saul Powers|22/12/2004|Unavailable - Coming Soon
978-90-3065-807-8|"The Things that I Never Will Learn"|Eva|Robertson|Eva Robertson|18/06/2007|Unavailable - Coming Soon
978-74-8013-934-5|"Humans With Us Untold Gods"|Suzette|Rosales|Suzette Rosales|25/02/1987|Unavailable - Coming Soon
978-87-4206-938-7|"Whispers of a Ghost"|Diego|Russo|Diego Russo|03/06/1996|Unavailable - Coming Soon
978-66-2724-397-6|"When Love Lasts"|Barbra|Sanders|Barbra Sanders|29/11/2018|Unavailable - Coming Soon
978-78-4619-100-0|"Pelicans We"|Sheila|Whitehead|Sheila Whitehead|18/11/1982|Unavailable - Coming Soon
978-09-4189-874-4|"Obsidian Leviathan"|Elwood|Wise|Elwood Wise|10/05/2023|Unavailable - Coming Soon
978-20-7104-860-0|"Of Fire and Silk"|Reyes|Wolf|Reyes Wolf|27/02/2001|Unavailable - Coming Soon

Solution

  • Your program fails for me `loadlibrary() for me with:

    Number of fields read: 0
    File format incorrect.
    

    because argscheck() is called with the pointers lib[records].ISBN ... lib[records].status. As lib is allocated with malloc() those pointers are uninitialized and it cause undefined behavior in vfscanf() which a pointer to "a character array that is long enough to hold the input sequence and the terminating null byte". On my system those pointers happen to be NULL and vfscanf() returns 0. I suspect they are non-NULL on your system which will will likely cause memory corruption.

    Here is the minimal example you should have posted:

    #include <stdio.h>
    #include <stdlib.h>
    
    #define LEN 99
    #define str(s) str2(s)
    #define str2(s) #s
    
    int main() {
        FILE *f = fopen("ARCHIVE.txt", "r");
        char *a[1]; // uninitialized
        // a[0] = malloc(LEN+1);
        int read = fscanf(f, "%" str(LEN) "s, ", a[0]);
        printf("%d\n", read);
        fclose(f);
    }
    

    Always specify a maximum field width when reading strings into an array that you allocate with scanf() family otherwise long input will cause buffer overflows.

    If your scanf() support the 'm' character then scanf() can allocate those arrays for you, i.e. scanf("%ms, ", &a[0]). I would still specify a maximum field width to avoid a huge variable from being allocated if your input is untrusted. This will, btw, read a word (i.e. up to the first space) including the comma not what you expect.

    I also suggest you separate I/O and parsing code. Both are tricky and mixing them makes things way harder. Every I/O call may fail (forever; like when you read EOF). CSV files may contain embedded newlines so I suggest you just read the whole file into a buffer (either realloc() the array as needed to assume a max size of the file).