1

I'm working on a leave management system and I have written the staff details to a text file. But now I want to add the leave type to the end of a specific line by taking the staff ID as an input.

James720 | Name: James | Department: Administration | Leave Balance: 30
Kyle790 | Name: Kyle | Department: Administration | Leave Balance: 30

This is how I'm writing to the file (Above)

The target is:

James720 | Name: James | Department: Administration | Leave Balance: 30 | Type of Leave: Medical
Kyle790 | Name: Kyle | Department: Administration | Leave Balance: 30 | Type of Leave: Medical
void apply_leave()
{
    char line[100], newText[100],id[100];
    int i;
    FILE *file;
    file = fopen("staff.txt", "r+");

    // Get the new text to be added
    printf("Please confirm staff ID: ");
    scanf("%s",id);
    printf("Enter leave type:");
    scanf("%s",newText);

    // Iterate through each line of the file
    while (fgets(line, sizeof(line), file)) {
        if (strstr(line, id)) {
            i = strlen(line);
            fseek(file, -i, SEEK_CUR);
            fprintf(file,"%s",newText);
            break;
        }
    }
    fclose(file);
}

I tried this but the updated text file is:

JMedical0 | Name: James | Department: Administration | Leave Balance: 30
Kyle226 | Name: Tariq | Department: Management | Leave Balance: 30

As shown above, the word Medical is added to the beginning replacing the ID

Andreas Wenzel
  • 22,760
  • 4
  • 24
  • 39
  • In your question, you state that you only want to add information to a specific line. But when you state the desired output, you have added information to both lines, instead of only a specific line. Please [edit] the question to clarify this contradiction. – Andreas Wenzel Jan 24 '23 at 05:56

1 Answers1

1

Your approach will not work, because your approach only allows you to overwrite parts of the file, but not to insert anything.

Another issue is that in standard C, the line

fseek(file, -i, SEEK_CUR);

is not allowed, because you opened the file in text mode. When in text mode, the only legal offset for fseek is 0 or the return value of a previous call to ftell. See the documentation of fseek for further information. However, if you are using a POSIX platform (such as Linux), then these restrictions do not apply and this line is allowed.

Although some file systems do support inserting data into the middle of a file, this is not easy and generally requires the inserted data block to be a multiple of a certain size. Therefore, I do not recommend such a solution in your case.

It would be easier to write a new file with the desired data added. If you want, you can delete the old file afterwards and then rename the new file to the name of the old file.

I recommend that you open two files, one input file "staff.txt" and one output file "staff_new.txt". Open the input file in mode "r" and the output file in mode "w". Beware that this will overwrite the file "staff_new.txt", if it already exists. Do not use "r+" or "w+" for any of the files. You can then read the input file line by line using fgets (as you are already doing) and then copy that line to the output file. Afterwards, you can add all the information you want to the output file, before you start copying the next line.

Here is an example:

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

//forward declarations
void get_line_from_user( char prompt[], char buffer[], int buffer_size );
bool get_line_from_stream( char buffer[], int buffer_size, FILE *fp );

int main( void )
{
    char line[200], id[100], leave_type[100];
    FILE *input, *output;

    //attempt to open input file
    input = fopen( "staff.txt", "r" );
    if ( input == NULL )
    {
        fprintf( stderr, "Error opening input file!\n" );
        exit( EXIT_FAILURE );
    }

    //attempt to open output file
    output = fopen( "staff_new.txt", "w" );
    if ( output == NULL )
    {
        fprintf( stderr, "Error opening output file!\n" );
        exit( EXIT_FAILURE );
    }

    //get all requiredinput from the user
    get_line_from_user(
        "Please confirm staff ID: ",
        id, sizeof id
    );
    get_line_from_user(
        "Enter leave type: ",
        leave_type, sizeof leave_type
    );

    //iterate through each line of the file
    while ( get_line_from_stream( line, sizeof line, input ) )
    {
        //copy line to output file
        fputs( line, output );

        //add leave type, if appropriate
        if ( strstr( line, id ) != NULL )
        {
            fprintf( output, " | Type of Leave: %s", leave_type );
        }

        //write newline character to output file
        putc( '\n', output );
    }

    fclose( output );
    fclose( input );
}

//This function will read exactly one line of input from the
//user. If the line is too long to fit in the buffer, then the
//function will automatically reprompt the user for input. On
//failure, the function will never return, but will print an
//error message and call "exit" instead.
void get_line_from_user( char prompt[], char buffer[], int buffer_size )
{
    for (;;)
    {
        char *p;

        //prompt user for input
        fputs( prompt, stdout );

        //attempt to read one line of input
        if ( fgets( buffer, buffer_size, stdin ) == NULL )
        {
            printf( "Error reading from input!\n" );
            exit( EXIT_FAILURE );
        }

        //attempt to find newline character
        p = strchr( buffer, '\n' );

        //make sure that entire line was read in (i.e. that
        //the buffer was not too small to store the entire line)
        if ( p == NULL )
        {
            int c;

            //a missing newline character is ok if the next
            //character is a newline character or if we have
            //reached end-of-file (for example if the input is
            //being piped from a file or if the user enters
            //end-of-file in the terminal itself)
            if ( !feof(stdin) && (c=getchar()) != '\n' )
            {
                printf( "Input was too long to fit in buffer!\n" );

                //discard remainder of line
                do
                {
                    if ( c == EOF )
                    {
                        printf( "Error reading from input!\n" );
                        exit( EXIT_FAILURE );
                    }

                    c = getchar();

                } while ( c != '\n' );

                continue;
            }
        }
        else
        {
            //remove newline character by overwriting it with
            //null character
            *p = '\0';
        }

        //input was ok, so break out of loop
        break;
    }
}

//This function will read exactly one line of input and remove the
//newline character, if it exists. On success, it will return true.
//If this function is unable to read any further lines due to
//end-of-file, it returns false. If it fails for any other reason, it
//will not return, but will print an error message and call "exit"
//instead.
bool get_line_from_stream( char buffer[], int buffer_size, FILE *fp )
{
    char *p;

    //attempt to read one line from the stream
    if ( fgets( buffer, buffer_size, fp ) == NULL )
    {
        if ( ferror( fp ) )
        {
            fprintf( stderr, "Input error!\n" );
            exit( EXIT_FAILURE );
        }

        return false;
    }

    //make sure that line was not too long for input buffer
    p = strchr( buffer, '\n' );
    if ( p == NULL )
    {
        //a missing newline character is ok if the next
        //character is a newline character or if we have
        //reached end-of-file (for example if the input is
        //being piped from a file or if the user enters
        //end-of-file in the terminal itself)
        if ( !feof(fp) && getc(fp) != '\n' )
        {
            printf( "Line input was too long!\n" );
            exit( EXIT_FAILURE );
        }
    }
    else
    {
        //remove newline character by overwriting it with a null
        //character
        *p = '\0';
    }

    return true;
}

Note that in the program above, I am not using scanf with the %s format specifier as you did in the question, because that will only read a single word, not a whole line. Also, using scanf for user input is generally not recommended. In my experience, it is generally better to use fgets for this. I have created the function get_line_from_user for this, which automatically removes the newline character and performs additional input validation, and reprompts the user if necessary. I also created a similar function get_line_from_stream, which also automatically removes the newline character, but does not reprompt if there is an input error. That second function is intended for reading from a file, not from a user.

This program has the following behavior:

When the user inputs

Please confirm staff ID: James720
Enter leave type: Medical

and the content of staff.txt is

James720 | Name: James | Department: Administration | Leave Balance: 30
Kyle790 | Name: Kyle | Department: Administration | Leave Balance: 30

then the following output file is written to staff_new.txt:

James720 | Name: James | Department: Administration | Leave Balance: 30 | Type of Leave: Medical
Kyle790 | Name: Kyle | Department: Administration | Leave Balance: 30
Andreas Wenzel
  • 22,760
  • 4
  • 24
  • 39
  • ...then, after the new file is written and closed, rename `"staff.txt"` to `"staff.txt.bak"` and rename `"staff_new.txt"` to `"staff.txt"`. – Dúthomhas Jan 24 '23 at 05:13