Write In Formatted Txt File


JyCet

Member
Joined
Feb 23, 2004
Messages
469
Age
118
Location
France
Website
Visit site
hi all,

I never writed in a formatted text file, so i've a pb to write correctly in my text file :unsure:

my formatted file is like that:
Code:
# information
# blablabla
option1 0

# blablabla2
option2 0

# blablabla3
option3 0
...

here my idea for the C code:
Code:
if ((fp = fopen("rage2x.cfg", "r+")) != NULL){
	while(fgets(ligne,sizeof(ligne),fp) != NULL){
  sscanf(ligne, "%s %d", &arg1,&argd);
  if (strcmp(arg1,"#")!=0){
  	if (strcmp(arg1,"option1")==0){
    sprintf((char*)g_string, "option1 %d",option1);
    fputs(g_string,fp);
  	}
  	else if (strcmp(arg1,"option2")==0){
    sprintf((char*)g_string, "option2 %d",option2);
    fputs(g_string,fp);
  	}
  	else if (strcmp(arg1,"option3")==0){
    sprintf((char*)g_string, "option3 %d",option3);
    fputs(g_string,fp);
  	}
}}}

the pb is fputs write the correct information but not at the correct place, it's writed on the next line.
Any idea to return at the correct SEEK place to write at the correct place ? With fseek and ftell ?

thank
 
It's not clear to me what you expect to happen - you're reading some data, then writing new data. The file only has one position, used for both reading and writing, so the effect will be to write the new data just after the old data which was previously read.

At this point, you're then reading more data, and it's undefined what happens now. Strictly, after writing data, you should perform an explicit fseek before reading any more data, as otherwise your read may (or may not) return data which was already buffered before your write was performed (so you might, or might not, see old data).

I'm not really sure what you intended though - did you mean all your writes to append to the file? Or to replace the data on the fly? Generally speaking it's safer to either read the whole file into memory and then open it again for output, or to send your output to a different file and rename it afterwards. If you try to modify text files in-place you can run into nasty issues (e.g. if you write a line longer than what you read, you'll start overwriting the next line too).

If you do want to modify the file while still reading it, and you're sure you know what you're doing, then ftell and fseek are indeed the right functions to do that with. Note that fseek can do seeks relative to either the beginning of the file, the current position, or the end of the file - useful for different things.
 
thank for your quick answer gfoot :)

Here another test code:
Code:
if ((fp = fopen("rage2x.cfg", "r+")) != NULL){
	while(!Write){
  ptrFP=ftell(fp); // save current seek
  if (fgets(ligne,sizeof(ligne),fp) != NULL){
  	sscanf(ligne, "%s %d", &arg1,&argd);
  	if (strcmp(arg1,"#")!=0){
    ptrFP2=ftell(fp); // save current seek
    fseek (fp, ptrFP, SEEK_SET); // return to previous seek
    if (strcmp(arg1,"showfps")==0){
    	sprintf((char*)g_string, "showfps %d",options.fps);
    	fputs(g_string,fp);
    }
    else if (strcmp(arg1,"sound")==0){
    	sprintf((char*)g_string, "sound %d",options.sound);
    	fputs(g_string,fp);
    }
    else if (strcmp(arg1,"samplerate")==0){
    	sprintf((char*)g_string, "samplerate %d",options.rate);
    	fputs(g_string,fp);
    }
    else if (strcmp(arg1,"raster")==0){
    	sprintf((char*)g_string, "raster %d",options.raster);
    	fputs(g_string,fp);
    }
    else if (strcmp(arg1,"system")==0){
    	sprintf((char*)g_string, "system %d",options.system);
    	fputs(g_string,fp);
    }
    else if (strcmp(arg1,"country")==0){
    	sprintf((char*)g_string, "country %d",options.pays);
    	fputs(g_string,fp);
    }
    else if (strcmp(arg1,"cpuclock")==0){
    	sprintf((char*)g_string, "cpuclock %d",options.cpu);
    	fputs(g_string,fp);
    }
    else if (strcmp(arg1,"guispeed")==0){
    	sprintf((char*)g_string, "guispeed %d",options.guispeed);
    	fputs(g_string,fp);
    }
    else if (strcmp(arg1,"autoreloadgui")==0){
    	sprintf((char*)g_string, "autoreloadgui %d",options.reload);
    	fputs(g_string,fp);
    }
    else if (strcmp(arg1,"gameinfo")==0){
    	sprintf((char*)g_string, "gameinfo %d",options.gameinfo);
    	fputs(g_string,fp);
    }
    fseek (fp, ptrFP2, SEEK_SET); //return to current seek
  	}
  }else Write=1;
	}
}
fclose(fp);

my original text file is:
Code:
# GNGEO LAUNCH OPTIONS

# roms path
# romspath1 is the first scanned folder
romspath1 roms/
romspath2 /mnt/sd/roms/neogeomvs/

# show framerate
# 1 enable
# 0 disable (default)
showfps 1

# sound
# 0 disable (very fast emulation)
# 1 enable (slow down)
sound 1

# sample rate
# 0 for 22050Hz (default)
# 1 for 11025Hz (faster than 22050Hz)
samplerate 1

# set the cpu clock for gngeo2x (only available in the new gngeo)
# from 200MHz to 300MHz
cpuclock 266

# automatic reload of RAGE2X (only available in the new gngeo)
# 0 disable (default)
# 1 enable
autoreloadgui 1

# raster effect for side kick game
# 0 disable (default)
# 1 enable (slow down emulation)
raster 0

system 0
country 0


# RAGE2X GUI OPTIONS

# set GUI speed
# from 0 to 2 (0 is the faster)
guispeed 0

# display game information
# 0 disable (default)
# 1 enable
gameinfo 0

and the result after a saving :(
Code:
# GNGEO LAUNCH OPTIONS

# roms path
# romspath1 is the first scanned folder
romspath1 roms/
romspath2 /mnt/sd/roms/neogeomvs/

# show framerate
# 1 enable
# 0 disable (default)
showfps 1
showfps 1
# 0 disable (very fast emulation)
# 1 enable (slow down)
sound 1
sound 1ple rate
# 0 for 22050Hz (default)
# 1 for 11025Hz (faster than 22050Hz)
samplerate 0
samplerate 0cpu clock for gngeo2x (only available in the new gngeo)
# from 200MHz to 300MHz
cpuclock 266
cpuclock 266c reload of RAGE2X (only available in the new gngeo)
# 0 disable (default)
# 1 enable
autoreloadgui 1
autoreloadgui 1ct for side kick game
# 0 disable (default)
# 1 enable (slow down emulation)
raster 0
raster 0 0
country 0
country 0E2X GUI OPTIONS
country 0UI speed
# from 0 to 2 (0 is the faster)
guispeed 0
guispeed 0y game information
# 0 disable (default)
# 1 enable
gameinfo 0
 
Well as I said, I'd strongly recommend not trying to write data into an existing text file. Writes overwrite, they don't insert, and the file is a linear 1D structure, not a 2D one, so changing line lengths etc is a no-no.

The first thing that's going wrong is that you're not checking whether sscanf actually read anything. The blank lines are presumably falling through, and your arg1 string is still set to whatever it was from the previous valid line. That's why a lot of things are getting doubled up.

Then, and this is the serious problem with trying to update text files like this, you're writing a longer string (e.g. "showfps 1") to the file where previously there was a blank line - so the 's' overwrites the old newline, the 'h' overwrites the first character of the following line, and so on.

I'd really recommend changing your approach - e.g.:

Code:
    // read the data first
    FILE *fp = fopen (filename, "rb");

    fseek (fp, 0, SEEK_END);
    int length = ftell(fp);

    char *data = malloc (length+1);
    fread (fp, data, 1, length);
    fclose(fp);

    // now write the data back with modifications
    fp = fopen (filename, "wb");

    // start at the beginning of the data
    char *pos = data;

    // loop over all the lines
    while (pos-data < length) {

        // find end of line
        char *end_of_line = strchr(pos, '\n');

        // special case if file ended without a newline character
        if (!end_of_line) end_of_line = data+length;

        // terminate the string here now
        *end_of_line = '\0'

        // Now parse the string (starting from 'pos') as before,
        // and write data to fp.
        ...

        // advance to next line
        pos = end_of_line+1;
    }
    fclose(fp);
    free(data);

It's untested of course, but should give the general idea - read the entire file into a buffer, then truncate the file and write lines back to it from the buffer, one at a time. You'll want to write out the comments and blank lines too, not just skip them.

Possible issues: This will only support Unix files. While the C library has support for platform-dependent file formats, it doesn't allow one platform to transparently accept text files from a different platform. It's pretty easy to make the above code also work for DOS format files (just check whether the last character of the line is '\r' and null it out if so), and it's not that hard to also support Mac format (when it fails to find a '\n', make it check also for '\r' before assuming the rest of the file is all one line).
 
Back
Top