Monday, June 3, 2024

Part 3: Library management in C| Read Comma Separated Values (CSV) file and Student Login System

Previous Page                                                                                                                           Next Page

In this application we have two scenarios where we read CSV files. First to read Students data and second to read books data. Students need to provide student id to login application. We read the students data from csv file and compare the student id. Also, we read large set of books data from csv file.

Sample student csv data:



Sample books csv data:


The header file loadCsvData.h is written to load csv files student data and books data. There are four main functions which reads the files.


static int getStudentID(char stuId[MAX_ID], struct stuProfile *st);
static int  loadStuValues(char *line, char *sFields[STU_COL_NUM]);
int readBooks();
static int  loadBookValues(char *line, char *lFields[BK_COL_NUM]);
int getRowCount(FILE *fpl);

First two functions static int getStudentID(char stuId[MAX_ID], struct stuProfile *st) and static int  loadStuValues(char *line, char *sFields[STU_COL_NUM]) loads student data. Let's breakdown these functions.

Function getStudentID(char stuId[MAX_ID], struct stuProfile *st): This function compares the student id passed in the paraments with the student data read and if student id found, it will initialize the stuProfile structure pointer by student information. 

  1. Line 49: fps = fopen(STU_DATA_FILE, "r") reads the csv file in read mode. 
  2. Line 58: while (!feof(fps)) loop reads the file line by line until the end of the file is reached.
  3. Line 61: if(fgets(inBuffer, BUFFER_SZ-1, fps)==NULL) break; reads the line from the file into the buffer. The loop will break if end of the file is reached.
  4. Line 65 and 69: if(++rowNum==1) continue; and if(strlen(inBuffer)==0) continue; skips the first header data and empty lines.
  5. Line 72: if(loadStuValues(inBuffer,sFields)==FAIL){This call function loadStuValues() to read each columns values from the line. The parameter sFields is character pointer to array of string equal to number of columns. The size of the array must be equal to number of columns in csv file. After reading columns value, function will assign it to sFields pointer variable. 
  6. Line 78: if(strcmp(sFields[ID],stuId)==0) it compares the student id passed in the parameter to file data. If student id exits, followings line will initialize structure stuProfile elements.
Source code of the function:

static int getStudentID(char stuId[MAX_ID], struct stuProfile *st){

    FILE *fps;

    // char* array will point to fields
    char *sFields[STU_COL_NUM];

    int err_count = 0;

    fps = fopen(STU_DATA_FILE, "r");
    if(fps == NULL) {
        //printf("Error opening file: %d\n",errno);
        return(FAIL);
    }

    char inBuffer [BUFFER_SZ];
    long rowNum = 0L;

    while (!feof(fps)) {

        // load line into static buffer
        if(fgets(inBuffer, BUFFER_SZ-1, fps)==NULL)
            break;

        // skip first line (headers)
        if(++rowNum==1)
            continue;

        // jump over empty lines
        if(strlen(inBuffer)==0)
            continue;
        // set pFields array pointers to null-terminated 
        //string fields in inBuffer
        if(loadStuValues(inBuffer,sFields)==FAIL){
           err_count++;
            if(err_count > MAX_ERROR_NUM)
                break;
        } else {
            //Compare Student ID
            if(strcmp(sFields[ID],stuId)==0){

                strcpy(st->stuID,sFields[ID]);
                strcpy(st->fName,sFields[fNAME]);
                strcpy(st->lName,sFields[lNAME]);
                strcpy(st->faculty,sFields[FACULTY]);

                fclose(fps);
                return PASS;
            }
        }
    }
    fclose(fps);

    return FAIL;
}
Function loadStuValues(char *line, char *sFields[STU_COL_NUM]): This function reads the column value separated by csv. To include the comma value inside the column we will user enclose the column value by double inverted quotation. 
  1. Line 97: char delim=','; Initialize delimiter value by comma (,). We can user delimiter other than comma.
  2. Line 98: if(line == NULL) return FAIL; If the input character buffer value is null, it will return fail flag.
  3. Line 103: if(*(line + strlen(line)-1) == '\r' || *(line + strlen(line)-1) == '\n') *(line + strlen(line)-1) = '\0'; It chop of last char of input if it is a CR or LF (e.g.Windows file loading in Unix env.). It can be removed if sure fgets has removed both CR and LF from end of line.
  4. Line 106: char *cptr = line; This declares a character pointer and initializes it to point to the start of the line.
  5. Line 111: sFields[fld]=cptr; This declares a character pointer and initializes it to point to the start of cptr variable. sFields[fld] is a string which represent the column values. At this line we can consider sFields[0] has value of whole line. 
  6. Line 112 to 126: while((ch=*cptr) != '\0' && fld < STU_COL_NUM) It loop through each character of string cptr until it reaches end of line. Inside while loop, we check opening quote ("), closing quote ("), and comma (,) value in each character and if found will split string by assigning null character ('\0'). Each time adding null character we will increment the index of array sFields and continue loop. At the end array sFields[0] is equal to first column, sFields[1] is equal to second column and so on.
  7. Line 128 to 131: It will verify the number of columns found in the file and the required number of columns.
  8. After reading line successfully, the function ends by returning true flag.
The source code of the above function:

static int  loadStuValues(char *line, char *sFields[STU_COL_NUM]){
    char delim=',';
    if(line == NULL)
        return FAIL;

    // chop of last char of input if it is a CR or LF 
    //(e.g.Windows file loading in Unix env.)
    // can be removed if sure fgets has removed both CR 
    //and LF from end of line
    if(*(line + strlen(line)-1) == '\r' || *(line + strlen(line)-1) == '\n')
        *(line + strlen(line)-1) = '\0';

    char *cptr = line;
    int fld = 0;
    int inquote = FALSE;
    char ch;

    sFields[fld]=cptr;
    while((ch=*cptr) != '\0' && fld < STU_COL_NUM){

        if(ch == '"') {
            if(! inquote){
                sFields[fld]=cptr+1;
            }else {
                *cptr = '\0';// zero out quote and jump over it
            }
            inquote = ! inquote;
        } else if(ch == delim && ! inquote){
            *cptr = '\0';// end of field, null terminate it
            sFields[++fld]=cptr+1;
        }

        cptr++;
    }
    if(fld > STU_COL_NUM-1){
        return FAIL;
    } else if (fld < STU_COL_NUM-1){
        return FAIL;
    }
    return PASS;
}
Second two functions int readBooks(); and static int  loadBookValues(char *line, char *lFields[BK_COL_NUM]); very much similar to above two functions. To understand the code further please follow my youtube video provided below. LoadBookValues and LoadStuValues is exactly same. However, getStudentID function we read only specific student data and assign it to stuProfile structure variable, and readBooks() function read all books data and assign it to array of bk (Book) structure variable. The size of Array of book structure 'bk' is allocated dynamically. We will discuss about in next part.

Student login system:
Student should choose the login in student mode at the start of an application. To login student should provide only student Id. The provided student Id is verified by reading student csv data in getStudentID function. If student Id found in the csv file, application let user to continue. Here, I choose not to use password for students because they will use application basically in read mode to view books details and view books issued to them. Student can't modify the application data.

No comments:

Post a Comment