C File Handling
ngiacomelli

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
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");

ngiacomelli

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

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
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.

ngiacomelli

Still not having much luck with it :(

Tomasz Grajewski

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.

ngiacomelli

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

Where do we actually hook the section?

Tomasz Grajewski

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.

ngiacomelli

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

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 ;)

Arthur Kalliokoski

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}

Thread #589124. Printed from Allegro.cc