0

I'm having trouble figuring out how to get my program to stop reading a file once the character string endOfFileMarker "***" is read given a file called "studentRecords.txt" with sample input as shown:

23456770,Mina,Porter,3,ENEE,114,CMSC,412,ENME,515  
23456790,Alex,Simpson,1,CMSC,412  
***

I'm reading the file using a while loop indicating that as long the file is not equal to the end of the file and if the first input from which I read is not equivalent to the endOfFileMarker. Right now the output doesn't stop reading at the endOfFileMarker and takes it as a new record in the structure with the given output of a display function (I realize the error with the 2nd record but that appears to be a problem with the display function and not the way I'm storing it):

23456770 Mina Porter    3        ENEE 114  CMSC 412  ENME 515

23456Alex Alex Simpson  1        CMSC 412

*** Alex Simpson        1        CMSC 412

I've tried using fgets earlier and creating an input buffer to read each line. But since there will be variable number of course names and course codes for each student, I found fscanf and using a while loop with control condition of !feof to work better. Kind of at a loss right now of how to stop storing into the structure once I hit the endOfFileMarker. If someone can please help me out with this, that would be very appreciated. My full code is written below.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define filename "studentRecords.txt"

typedef struct courseInfo
{//structure defining courseInfo elements
    int courseID;
    char courseName[30];
}crsInfo;

typedef struct studentInfo
{//structure defining studentInfo elements
    char studentID[9];
    char firstName[20];
    char lastName[25];
    int coursesAttended;
    crsInfo cInfo[10];
    struct studentInfo * next;
}stdInfo;

stdInfo * firstStdNodePointer = NULL;

stdInfo * currentStdNodePointer = NULL;

void addStudentInfo(stdInfo newStd)
{   
    if (firstStdNodePointer == NULL) //Create the first course node
    {
        firstStdNodePointer = (stdInfo *) malloc(sizeof(stdInfo));
        strcpy(firstStdNodePointer->studentID, newStd.studentID);
        strcpy(firstStdNodePointer->firstName, newStd.firstName);
        strcpy(firstStdNodePointer->lastName, newStd.lastName);
        firstStdNodePointer->coursesAttended = newStd.coursesAttended;

        for(int i = 0; i < newStd.coursesAttended; i++)
        {
            firstStdNodePointer->cInfo[i].courseID = newStd.cInfo[i].courseID;
            strcpy(firstStdNodePointer->cInfo[i].courseName, newStd.cInfo[i].courseName);
        }

        firstStdNodePointer->next = NULL;
        currentStdNodePointer = firstStdNodePointer;
    }
    else // add next course to the end of the course linked list.
    {

        // Go to the last Course in the list to get the course ID

        stdInfo * newStdNodePointer = (stdInfo *) malloc(sizeof(stdInfo));

        strcpy(newStdNodePointer->studentID, newStd.studentID);
        strcpy(newStdNodePointer->firstName, newStd.firstName);
        strcpy(newStdNodePointer->lastName, newStd.lastName);
        newStdNodePointer->coursesAttended = newStd.coursesAttended;

        for(int j = 0; j < newStd.coursesAttended; j++)
        {
            newStdNodePointer->cInfo[j].courseID = newStd.cInfo[j].courseID;
            strcpy(newStdNodePointer->cInfo[j].courseName, newStd.cInfo[j].courseName);
        }

        newStdNodePointer->next = NULL;
        currentStdNodePointer->next = newStdNodePointer;         // Link previous node with newNode
        currentStdNodePointer = currentStdNodePointer->next; // Make current node as previous node
    }
}

void loadStudentInfo()
{
    FILE * fptr = NULL;
    fptr = fopen(filename, "r+");

    const char endOfFileMarker[] = "***"; //marks the end of the student record list

    if(fptr == NULL)
    {
        printf("File can not be opened\n");
    }

    stdInfo newStd;//defining a new struct studentInfo variable so I can pass to the addStudent function
    //char line[100] = "";
    //char * strPtr;
    while (!feof(fptr) && strcmp(newStd.studentID, endOfFileMarker) != 0 )
    {
        fscanf(fptr, "%[^,],", newStd.studentID); 
        printf("%s\n", newStd.studentID);

        fscanf(fptr, "%[^,],", newStd.firstName);
        printf("%s\n", newStd.firstName);

        fscanf(fptr, "%[^,],", newStd.lastName);

        fscanf(fptr, "%i,", &newStd.coursesAttended);

        for(int j = 0; j < newStd.coursesAttended; j++)
        {//To read each courseName and ID, you need to go according to how many courses they entered
        //because the amount of records in cInfo should correspond with how many pairs of courseName
        //are entered into the file
            fscanf(fptr, "%[^,],", newStd.cInfo[j].courseName);
            fscanf(fptr, "%i,", &newStd.cInfo[j].courseID);
        }

        addStudentInfo(newStd);
    }
    fclose(fptr);
}

void displayCourseInfo()
{
    printf("------------------------------------------------\n");

    stdInfo * stdListPointer = firstStdNodePointer;

    //start from the beginning
    while(stdListPointer != NULL) {
        printf("%s %s %s\t%i\t", stdListPointer->studentID, stdListPointer->firstName, stdListPointer->lastName, stdListPointer->coursesAttended);
        for(int i = 0; i < stdListPointer->coursesAttended; i++)
        {
            printf(" %s %i ", stdListPointer->cInfo[i].courseName, stdListPointer->cInfo[i].courseID);
        }
        printf("\n");
        stdListPointer = stdListPointer->next;
    }
    printf("------------------------------------------------\n");
}
void switchCaseMenu()
{
    int selection;
    int menuActive = 1;
    while(menuActive)
    {

        printf("60-141 Bonus Assignment - Ben John\n");
        printf("------------\n");
        printf("1. Add a new student\n");
        printf("2. Delete a student\n");
        printf("3. Search for a student\n");
        printf("4. Display current students\n");
        printf("5. Save student information to file\n");
        printf("6. Exit\n");

        printf("Please enter a selection: ");
        scanf("%i", &selection);

        switch(selection)
        {
            case 1:
                printf("~Selected - Add a new student~\n");
                break;
            case 2:
                printf("~Selected - Delete a student~\n");
                break;
            case 3:
                printf("~Selected - Search for s student~\n");
                break;
            case 4:
                printf("~Selected - Display current students~\n");
                displayCourseInfo();
                break;
            case 5:
                printf("~Selected - Save student information to file~\n");
                break;
            case 6:
                printf("~Selected - Exit~\n");
                menuActive = 0;
                break;
            default:
                printf("Invalid Input!\n");
        }
    }
    printf("Goodbye!\n");
}

int main(void)
{
    loadStudentInfo();

    switchCaseMenu();

    return 0;
}
ggorlen
  • 44,755
  • 7
  • 76
  • 106
Benjamin John
  • 69
  • 1
  • 8
  • 1
    Consider using a loop with fgets() + sscanf() instead of fscanf(). It help parsing line by line and no more need fot feof(). – ulix Aug 22 '18 at 04:15
  • Undefined behavior at `strcmp(newStd.studentID, endOfFileMarker)`; `newStd.studentID` is uninitialized. – melpomene Aug 22 '18 at 04:19
  • 1
    You're using `feof` wrong; see https://stackoverflow.com/questions/5431941/why-is-while-feof-file-always-wrong. – melpomene Aug 22 '18 at 04:20

1 Answers1

2

I'll suggest that you read the file line by line using fgets and use sscanf to do the scanning. Then you can use strcmp to break the loop. Something like:

while(fgets(buffer, SIZE_OF_BUFFER, fileptr))
{
    size_t len = strlen(buffer);
    if (len > 0 && buffer[len-1] == '\n') buffer[len - 1] = '\0'; // Strip \n if present
    if (strcmp(buffer, "***") == 0) break;  // Stop reading

    // use sscanf on buffer to find the individual fields in the line
}

Note that fgets also stores the \n character (aka newline) into the buffer so before doing the string compare, the \n is stripped off (if present).

For your use case you don't really need to test whether the last character in the string is actually a \n. Just make the buffer sufficiently large and always strip off the last character. In this way the code can be simplified to:

while(fgets(buffer, SIZE_OF_BUFFER, fileptr))
{
    size_t len = strlen(buffer);
    if (len) buffer[len - 1] = '\0';         // Strip last character
    if (strcmp(buffer, "***") == 0) break;   // Stop reading

    // use sscanf on buffer to find the individual fields in the line
}

or an even more compact way (thanks to @melpomene):

while(fgets(buffer, SIZE_OF_BUFFER, fileptr))
{
    buffer[strcspn(buffer, "\n")] = '\0';    // Strip \n character if present
    if (strcmp(buffer, "***") == 0) break;   // Stop reading

    // use sscanf on buffer to find the individual fields in the line
}
Support Ukraine
  • 42,271
  • 4
  • 38
  • 63
  • You could also do `buffer[strcspn(buffer, "\n")] = '\0';` which is always safe. – melpomene Aug 22 '18 at 05:03
  • so I've tried using the second variation of the while(fgets) you used. I guess I'm not understanding the nature of how sscanf works because I first used one sscanf line to read each line up until the coursesAttended line because I need a way to read a variable amount of courseNames and courseIDs, so I use the value of of coursesAttended to read in the courseNames and courseIDs of each line. The file is read correctly however only up to and including the coursesAttended variable. It begins reading the studentID repetitively in the courseName and coursed variables. Would I need fseek here? – Benjamin John Aug 22 '18 at 18:29