How to read from INI files

From SA-MP Wiki

Jump to: navigation, search


This tutorial is about how to read from INI files. This is mostly used to read message texts (to ensure the capability of scripts to be multi-lingual), game preferences, but also user properties.

Contents

INI fileformat

In order to read from INI files, we firstly have to understand its fileformat. Fortunately it's easy to understand and human-readable.

An INI file is structured by sections. Each section must be started by writing its name between square brackets. Then, inside these sections, INI items are used to store the data itself. Let's see how this looks like:

[Messages]
JoinMsg=*** %s joined the server!
PartMsg=*** %s left the server!

We will also use this snippet for our test script, so save it as "example.ini" to the scriptfiles directory.

Function to read from INI files

Approach

So that we understood (hopefully) the fileformat, we have to choose how to read from an INI file. We have to write a function which needs some input parameters, namely: filename, section and item. These will uniquely define which item to get from the file.

At first we have to go through the file until we find the proper section. This is easily done by comparing every read line with "[WANTED_SECTION_NAME]". When found, we'll switchover to itemmode. In this mode, we'll compare the beginning of every line if we found the right item. If that happened, we'll return the text behind the equal character =.

When we're in itemmode and hit another section, we'll immediately stop, because the wanted item wasn't found in the wanted section.

Code

So let's write code:

public getINIString( filename[], section[], item[], result[] ) {
  new File:inifile;
  new line[512];
  new sectionstr[64], itemstr[64];
  new sectionfound = 0;
 
  inifile = fopen( filename, io_read );
  if( !inifile ) {
    printf( "FATAL: Couldn't open \"%s\"!", hdpath );
    return 0;
  }
 
  format( sectionstr, 64, "[%s]", section );
  format( itemstr, 64, "%s=", item );
 
  while( fread( inifile, line )) {
    line[strlen( line )-2] = 0; /* Remove \r\n */
 
    if( line[0] == 0 ) {
      continue;
    }
 
    /* If !sectionfound is true, we're looking for the proper section. */
    if( !sectionfound ) {
      /* Check if wanted section is being opened. */
      if( !strcmp( line, sectionstr, true, strlen( sectionstr ))) {
        sectionfound = 1;
      }
    }
    else { /* Itemmode from here. */
      /* We're leaving the wanted section and didn't find the value yet.
       * So we'll never reach it. */
      if( line[0] == '[' ) {
        return 0;
       }
 
      /* Have we reached our wanted INI item? */
      if( !strcmp( line, itemstr, true, strlen( itemstr ))) {
        format( result, sizeof( line ), "%s", line[strlen( itemstr )] );
        return 1;
      }
    }
  }
 
  fclose( inifile );
  return 0;
}

Well, that was actually a lot of code. :-) The most of it should be self-explanatory or is good described by comments. But there's one part which probably must be explained better; namely the part where the actual item text is copied. Let's have a look at it:

/* Have we reached our wanted INI item? */
if( !strcmp( line, itemstr, true, strlen( itemstr ))) {
  format( result, sizeof( line ), "%s", line[strlen( itemstr )] );
  return 1;
}

So at first, we just compare the read line with the wanted itemstr, that's nothing special. But in the next line, we format the result paramater with line[strlen( itemstr )]. Hu? Wouldn't be line[strlen( itemstr )] one character only? No!

PAWN has some very basic string slicing functionality. That means you can define a starting point over the array index component. Example:

public getPart( orgstr[], newstr[] ) {
  format( newstr, 10, orgstr[3] );
}
 
new orgstr[] = "Hi there!", newstr[10];
getPart( orgstr, newstr );
print( newstr );

This will printout "there!".

Usage

To make this complete, a short usage description of getINIString.

getINIString( filename[], section[], item[], result[] )

  • filename[]: Filename to read from.
  • section[]: Section where the item is in.
  • item[]: Name of the item we want the text to be retrieved from.
  • result[]: String which will hold the resulting item text. Must be large enough!
  • Return value: 1 when found, else 0.

Enhancement: Default value

What if you're trying to get a value from an INI file, but it doesn't exist? In this case, getINIString() returns 0, so we can react to this case. One idea could be to provide a default value which is used when an item can't be found in an INI file. This is fairly simple, we'll just add the following function to our tutorial:

public getINIStringDefault( filename[], section[], item[], defval[], result[] ) {
  new retval;
 
  retval = getINIString( filename, section, item, result );
  if( !retval ) {
    format( result, strlen( defval ), defval );
  }
 
  return retval;
}

In getINIStringDefault(), one parameter has been added: defval[]. This holds a default value which will be copied to result[] when the wanted item couldn't be found in the INI file.

Usage

getINIStringDefault( filename[], section[], item[], defval[], result[] )

  • filename[]: Filename to read from.
  • section[]: Section where the item is in.
  • item[]: Name of the item we want the text to be retrieved from.
  • defval[]: String that'll be copied to result[] when item[] can't be found in INI.
  • result[]: String which will hold the resulting item text. Must be large enough!
  • Return value: 1 when found, else 0.

Examples using our new functions

Here're some examples that use our new INI functions.

public OnPlayerConnect( playerid ) {
  new inimsg[256], joinmsg[256], playername[MAX_PLAYER_NAME];
 
  getINIStringDefault( "example.ini", "Messages", "JoinMsg", "*** %s joined the server!", inimsg );
  GetPlayerName( playerid, playername, MAX_PLAYER_NAME );
  format( joinmsg, inimsg, playername );
  SendClientMessage( playerid, 0xFFFF00FF, joinmsg );
}
 
public OnPlayerDisconnect( playerid ) {
  new inimsg[256], partmsg[256], playername[MAX_PLAYER_NAME];
 
  getINIStringDefault( "example.ini", "Messages", "PartMsg", "*** %s left the server!", inimsg );
  GetPlayerName( playerid, playername, MAX_PLAYER_NAME );
  format( partmsg, inimsg, playername );
  SendClientMessage( playerid, 0xFFFF00FF, partmsg );
}

These examples will display configurable join and part messages. You can edit them in scriptfiles/example.ini on-the-fly without the need to restart/reload the server.

Conclusion

I hope you liked this little tutorial/code snippet guide. ;-) If you have question or suggestions, feel free to contact me (stefan at-sign boxbox.org for e-mail, 290747149 for ICQ) or just edit this article.