Allegro.cc - Online Community

Allegro.cc Forums » Programming Questions » C File Handling

This thread is locked; no one can reply to it. rss feed Print
C File Handling
ngiacomelli
Member #5,114
October 2004

File handling and string manipulation has always been a blindspot for me, as far as C is concerned. Basically, I've got a textfile that contains information on items within my game. I want to store this information as a plain textfile so that other people can edit it easily.

Here's some messy code that does work, but I feel there are a few problems with it. My first question: when using fgets, is it safe to just have a global buffer size (1000), like the one I'm using already? Or should I be specifying dependant on the string I'm grabbing (name, desc, etc). Is fgets even the best solution?

Also: the strings copy with a strange character at the end (^). I'm assuming this is is because the string isn't null terminated. But as I stated earlier, I really want to create and edit this file via a text editor (read: Notepad). What can I do?

An example file layout:

The item name.
Some fantastic description!
1

Some code:

1 
2typedef struct {
3
4 int stackable;
5 char name[256], desc[512];
6
7} Item;
8 
9Item items[max_item_num];
10 
11int setup_objects( char *filename ) {
12
13 FILE *f = fopen(filename,"r");
14 char i_string[1000];
15 int item_num = 0;
16 
17 if (!f) return 0;
18
19 while ( !feof(f) ) {
20
21 if( fgets(i_string,1000,f) != NULL ) {
22 sprintf(items[item_num].name, i_string);
23 }
24 if( fgets(i_string,1000,f) != NULL ) {
25 sprintf(items[item_num].desc, i_string);
26 }
27 if( fgets(i_string,1000,f) != NULL ) {
28 items[item_num].stackable = atoi(i_string);
29 }
30
31 item_num++;
32
33 }
34 fclose(f);
35
36 return 1;
37
38}

gnolam
Member #2,030
March 2002
avatar

Quote:

My first question: when using fgets, is it safe to just have a global buffer size (1000), like the one I'm using already?

The fgets is safe, but the sprintf copying isn't. If you're just going to copy a string, use strncpy instead of sprintf (and if you actually do need sprintf's functionality, use snprintf)!
But why not just fgets (with the proper length) directly to the name and desc arrays instead of to a temporary buffer that you copy?

Quote:

Also: the strings copy with a strange character at the end (^).

That's an LF, as in a CRLF line ending. Open your file in text mode and it should go away. I.e.:FILE *f = fopen(filename, "rt");

--
Move to the Democratic People's Republic of Vivendi Universal (formerly known as Sweden) - officially democracy- and privacy-free since 2008-06-18!

ngiacomelli
Member #5,114
October 2004

Some good points, some updated code. Changing to "rt" hasn't solved the character problem, though.

1int setup_objects( char *filename ) {
2
3 FILE *f = fopen(filename,"rt");
4 char i_string[100];
5 int item_num = 0;
6 
7 if (!f) return 0;
8
9 while ( !feof(f) ) {
10
11 fgets(items[item_num].name,256,f);
12 fgets(items[item_num].desc,512,f);
13
14 if( fgets(i_string,1,f) != NULL ) {
15 items[item_num].stackable = atoi(i_string);
16 }
17
18 item_num++;
19
20 }
21 fclose(f);
22
23 return 1;
24
25}

Matt Smith
Member #783
November 2000

mingw fgets() acts just like in Unix. It only recognises \n as EOL, and so when you read text files with \r\n endings, you get a stray \r at the end.

The following code will strip these.

  fgets(buffer,BUFFER_MAX,f);
  int len = strlen(buffer);
  if (buffer[len-1] == '\r') buffer[len-1]= '\0';

This code is safe with ASCII and with UTF-8, afaics

Kitty Cat
Member #2,815
October 2002
avatar

Quote:

mingw fgets() acts just like in Unix. It only recognises \n as EOL, and so when you read text files with \r\n endings, you get a stray \r at the end.

Actually, they read up to and including the line-ending. If the line-ends in the file are \r\n, then you'll have \r\n at the end of the string. If it's just \n, you'll have \n at the end. Opening the file in non-b mode should automatically change \r\n to just \n, though.

--
"Do not meddle in the affairs of cats, for they are subtle and will pee on your computer." -- Bruce Graham

ngiacomelli
Member #5,114
October 2004

Still not having much luck with it :(

Tomasz Grajewski
Member #4,284
February 2004

Maybe you could try the Allegro config routines, here is an example how it could look like (should compile as C++, don't know about C):

1typedef struct {
2 int stackable;
3 char name[256], desc[512];
4} Item;
5 
6 
7Item items[max_item_num];
8 
9 
10int setup_objects(char *filename)
11{
12 if (!exists(filename))
13 return 0;
14
15 push_config_state();
16 set_config_file(filename);
17 
18 /*
19 ** This guarantees, that non-ASCII section names will be loaded correctly.
20 ** You probably want to edit this if you're using only ASCII files and characters.
21 */
22 const size_t section_size = uwidth_max(U_CURRENT) * 16;
23 char section[section_size];
24 
25 bool at_least_one_loaded = false;
26 int item_num = 0;
27 uszprintf(section, section_size, "ITEM_%i", item_num);
28 
29 while ((item_num < max_item_num) && config_is_hooked(section))
30 {
31 ustrzncpy(items[item_num].name, sizeof(items[item_num].name), get_config_string(section, "name", empty_string), 255);
32 ustrzncpy(items[item_num].desc, sizeof(items[item_num].desc), get_config_string(section, "desc", empty_string), 511);
33 items[item_num].stackable = get_config_int(section, "stackable", 0);
34 
35 at_least_one_loaded = true;
36 item_num++;
37 uszprintf(section, section_size, "ITEM_%i", item_num);
38 }
39 
40 pop_config_state();
41 
42 /*
43 ** Returns 1 if at least one item has been loaded, else return 0.
44 ** Maybe a better way would be to return the number of items loaded?
45 **
46 ** return at_least_one_loaded ? item_num + 1 : 0;
47 */
48 return at_least_one_loaded;
49}

Now your text file should be something like that:

[ITEM_0]
name = The item name.
desc = Some fantastic description!
stackable = 1

[ITEM_1]
name = Some other item.
desc = Some another fantastic description!
stackable = 0

Sorry, but code I've posted isn't tested, but I hope that it will be useful somehow :)

EDITED few times.

___________________________________________________________________________________________
mBlocks - a Tetris clone written in JavaScript | Merix Studio - website design & development | Zeitgeist

ngiacomelli
Member #5,114
October 2004

The code doesn't work for me. I have a question, though:

Where do we actually hook the section?

Tomasz Grajewski
Member #4,284
February 2004

Ups, sorry Nial. I've used wrong function. I wanted to check if a section exists in a file. It seems that Allegro doesn't have such a function, but you may try the

int list_config_sections(const char ***names);

function, but you will need of course to change the source code I've posted greatly.

___________________________________________________________________________________________
mBlocks - a Tetris clone written in JavaScript | Merix Studio - website design & development | Zeitgeist

ngiacomelli
Member #5,114
October 2004

Tomasz, not a problem! I've used the Allegro config routines once before (long ago). So I'll go RTFM. Thanks for taking the time to type all that up!

Edit: Tomasz, you're right! How the heck can I check (reliably) if a section is not found? I could compare the item name with empty_string, I suppose.

Tomasz Grajewski
Member #4,284
February 2004

I've wrote a fixed version, this time using the int list_config_sections(const char ***names); function (it's introduced in the version 4.2.1 of Allegro). Also this time it compiles as C++, so you will need to change it in few places to use it as C.

1typedef struct
2{
3 int stackable;
4 char name[256], desc[512];
5} Item;
6 
7 
8Item items[max_item_num];
9 
10 
11int setup_objects(char *filename)
12{
13 if (!exists(filename))
14 return 0;
15 
16 push_config_state();
17 set_config_file(filename);
18 
19 char const **sections = 0;
20 int sections_num = list_config_sections(&sections);
21 int i = 0;
22 
23 const char * const item_section_name = "ITEM_";
24 const int item_section_name_length = ustrlen(item_section_name);
25
26 for (int s = 0; (s < sections_num) && (i < max_item_num); s++)
27 if (!ustrnicmp(sections[s], item_section_name, item_section_name_length))
28 {
29 ustrzncpy(items<i>.name, sizeof(items<i>.name), get_config_string(sections[s], "name", empty_string), 255);
30 ustrzncpy(items<i>.desc, sizeof(items<i>.desc), get_config_string(sections[s], "desc", empty_string), 511);
31 items<i>.stackable = get_config_int(sections[s], "stackable", 0);
32 i++;
33 }
34 
35 free_config_entries(&sections);
36 pop_config_state();
37 return i;
38}

Now your text file could look like this:

[ITEM_SWORD]
name = Magic Sword
desc = This is some text about the Magic Sword.
stackable = 1

[ITEM_GUN]
name = Big F*cking Gun
desc = This is some text about BFG.
stackable = 0

I hope that this time it will work for you. You could try to tweak this function, maybe even change it, so it will allocate the items[] array dynamically for you ;)

___________________________________________________________________________________________
mBlocks - a Tetris clone written in JavaScript | Merix Studio - website design & development | Zeitgeist

Arthur Kalliokoski
Second in Command
February 2005
avatar

When I use fgets, I make the buffer size and the bytes requested slightly larger than a MAX_STRING_SIZE #define, which is largest allowed string. Try a 1000 char line in a C file and try to compile it!

1#include <stdio.h>
2#include <string.h>
3 
4#define MAX_STRING_SIZE 255
5#define MAX_BUFF_SIZE MAX_STRING_SIZE+1
6 
7int main(void)
8{
9 FILE *fp;
10 int linenum = 0;
11 char text[MAX_BUFF_SIZE];
12 
13 fp = fopen("mytext.txt","rb"); //never had prob with rb myself
14 if(!fp){ fprintf(stderr,"can't open 'mytext.txt'"); return -1;
15 while(fgets(text,MAX_BUFF_SIZE,fp)) {
16 linenum++;
17 if(MAX_STRING_SIZE < strlen(text)) {
18 fprintf(stderr,"Max string length exceeded on line %d",linenum);
19 return -1;
20 }
21 do_something_with_line();
22 }
23 fclose(fp);
24 return 0;
25}

They all watch too much MSNBC... they get ideas.

Go to: