Simple ini file parser

C, C++, Visual C++, C++.Net Topics
Post Reply
User avatar
SemiconductorCat
Major
Major
Posts: 455
Joined: Mon Aug 22, 2011 8:42 pm
Location: currently in hyperspace

Simple ini file parser

Post by SemiconductorCat » Thu Jan 16, 2014 8:44 pm

Hi all, I originally wrote this as a part of my final year project.
This code demonstrate how you could use state machines to parse ini file and read it's content into memory structure.

Code: Select all


/* LICENSE GNU GPL 
 * 
 * FILE: config_file.cpp 
        implementation of the simple ini file parser.
 *       Feel free to use this in your commercial or non-commercial software.
 * 
 * 
 * 
 * 
 * Written By: [email protected]
 * 
 */

#include "config_file.h"
#include "common.h"
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

static FILE* fd=0; /* file descriptor for the open file */

typedef struct tag_key_value 
{
  char* key ;
  char* value ;
  tag_key_value * next;
} key_value;

typedef struct tag_session{
  char* session_name ;
  key_value *head_key_value;
  struct tag_session* next; 
} session;

session * head;
session * current_session;

static session* get_next_session(session *current_session)
{
  if ( current_session == NULL)
    std::cerr << "ConfigFile: current session is NULL.\n";
  
  return current_session->next;
}

int config_file_session_into(const char * session_name)
{
  /* check for preconditions */
  if(session_name == NULL)
  {
   std::cerr << "ConfigFile:config_file_session_into could not enter \
                \n          into NULL session.\n";
   exit(0);
  }
  if( strcmp( session_name ,"")==0){
   std::cerr <<"ConfigFile:config_file_session_into could not enter into\
               \n\"\"session.\n";
   exit(0);
  }

  if(head ==NULL)
  {
    std::cerr << "ConfigFile:You need to call config_file_parse() before config_file_session_into(). \n";
    exit(0);
  }
  
  session* previous_session = current_session;
  current_session = head;
  while(TRUE)
  {
    if( strcmp( session_name , current_session->session_name ) != 0)
    {
      current_session = current_session->next;
      if(current_session == NULL){
	/* we didn't find the session that we looking for 
	 * This is the termination condition.
	 */
	current_session =previous_session;
	return FALSE;
      }
    }else{
      ::current_session = current_session;
     return TRUE;  
    }
  }
}

const char* config_file_get_current_session()
{
  if(current_session == NULL)
    return NULL;
  else
    return current_session->session_name;
}

const char* config_file_get_key_value(const char* keyword)
{
  if( current_session == NULL)
  {
    std::cerr << "ConfigFile: Please step into a session before calling \
                 \n           config_file_get_key_value().\n";
    exit(0);
  }
  
  key_value* current_key_value = current_session->head_key_value;
  while(TRUE)
  {
   if( current_key_value == NULL)
   {
     std::cerr << "ConfigFile: Required Keyword '" << keyword <<"' could not be\nfound\n";
     return NULL;
   }
   if ( strcmp( current_key_value->key,keyword) != 0)
   {
     current_key_value = current_key_value->next;
     continue;
   }else{
     char * ret_ = strdup(current_key_value->value);
     return ret_;     
   }
  }  
}

/* .some utility macros. */

#define IS_WHITE_SPACE(X)     (strchr(" \t",X)?TRUE:FALSE) 
#define IS_NEW_LINE(X)        (strchr("\n\r",X)?TRUE:FALSE)
#define IS_UPPER_ALPHA(X)     ((X <= 'Z' && X >= 'A')?TRUE:FALSE)
#define IS_LOWER_ALPHA(X)     ((X <='z' && X  >= 'a' )?TRUE:FALSE)
#define IS_ALPHA(X)           ((IS_UPPER_ALPHA(X) || IS_LOWER_ALPHA(X) )?TRUE:FALSE)
#define IS_NUM(X)             (strchr("0123456789",X)?TRUE:FALSE)
#define IS_ALPHANUM(X)        ((IS_ALPHA(X) || IS_NUM(X) )?TRUE:FALSE)

/* some utility functions */
static char* appendchar(char *str, char ch)
{
  if(str==NULL)
    std::cerr << "ConfigFile: Could not b appened into a NULL string.\n";
  int length = strlen(str);
  if(length == BUF_SIZE -1 ){
    std::cerr << "ConfigFile:appendchar could not append more.Buffer full.\n";
    exit(0);
  }
  str[length]=ch;
  str[length+1]=0; // append the null-termination character. //
}

/*  printsessions() */
static void print_sessions()
{
  if(head== NULL) 
  {
    printf("There are no sessions to printed\n");
    return;
  }
  
  session * current_session ;
  current_session= head;
  while( current_session != NULL)
  {
    printf("Session :%s\n" , current_session->session_name);
    current_session = current_session->next; 
  }  
}

/* add new session */
static int add_session(const char* session_name )
{
  // if the current session is NULL //
  // then allocate some memory for that //
  session* current_session ;
  if( head == NULL)
  {
    head =(session*) malloc( sizeof( session));
    head->next =NULL;
    current_session = head;
  }else{
    current_session = head;
    while( current_session->next != NULL)
    {
      current_session = current_session->next;  
    }    
    session * new_session = (session*) malloc(sizeof(session));
    current_session->next = new_session;
    current_session = new_session;
    new_session->next = NULL;
  }
  
  /*
  session* current_session = head;
  
  while( current_session->next != NULL)
  {
    current_session = current_session->next; 
  }
  // :TODO: some fix is needed here // 
  if( current_session != head )
  {
    current_session->next = (session*) malloc( sizeof(session));
    current_session = current_session->next;
  }
  */
  current_session->session_name = strdup( session_name);
  current_session->next=NULL;
  current_session->head_key_value = NULL;
  ::current_session = current_session;
  /* default return value */
  return 0;
}

/* put new keyword value couple into session */
static int put_into_session(session *current_session, \
const char *keyword , const char * value )
{
   key_value * current_key_value = current_session->head_key_value;
   if(current_key_value !=  NULL){
     while(current_key_value->next != NULL)
     {
      current_key_value = current_key_value->next; 
     }
   
     current_key_value->next =(key_value*) malloc( sizeof(key_value));
      current_key_value = current_key_value->next;
   }
   else{
    current_session->head_key_value = (key_value*) malloc( sizeof(key_value));
    current_key_value = current_session->head_key_value;     
   }
   
   /* allocate memory to hold string keyword 
      and copy it here.
    */
   int size = strlen(keyword)+1;
   current_key_value->key = (char*) malloc(size);
   strcpy(current_key_value->key, keyword);
   
   /* allocate memory to hold string value 
    * and copy it here.
    */
   size = strlen(value)+1;
   current_key_value->value = (char*) malloc(size);
   strcpy(current_key_value->value, value);
   current_key_value->next = NULL;
}

/* forward declaration */
static int free_key_value(key_value* /* ptr_key_value */);

/* free memory allocated by the session data
 * structure*/
static int free_sessions(session * ptr_session){
  // if there are next sessions in the link list 
  // then
  if(ptr_session->next != NULL)
  {
   free_sessions(ptr_session->next);
  }
  
  /* fre the current session */
  free( ptr_session->session_name);
  free_key_value(ptr_session->head_key_value);
  free(ptr_session);
}

/*
 * free memory allocated by the key_value pair
 * data structure 
 */
static int free_key_value ( key_value * ptr_key_value)
{
  if( ptr_key_value->next != NULL )
  {
    /* recursive function would be called */
    free_key_value( ptr_key_value->next);
  }
  
  /* free the current allocated memory in 
   * current structure 
   */
  free( ptr_key_value->key);
  free( ptr_key_value->value);
  free (ptr_key_value);
}

static char input_buffer[ BUF_SIZE ];

int config_file_open(const char* file_name){
  /* check for the prerequisites */
  if(!file_name)
    {
      std::cerr << "ConfigFile: file_name should not be NULL \n";
      exit(0);
    }
  fd=fopen(file_name , "r");
  if (fd==NULL)
    { 
      char * error_msg = (char*)malloc(BUF_SIZE);
      sprintf( error_msg,"ConfigFile: could not open the file :%s\n",file_name);
      std::cerr << error_msg ;
      free(error_msg);
      exit(0);      
    } 
  /* default return */
  head = NULL;
  current_session = NULL;
  return 0;
}

/* de-initialize and free memories associated with.
 * 
 */
int config_file_dinit()
{
  if(head != NULL)
  {
    // clean up the memory //
    free_sessions(head);
  }
  head = NULL;
  current_session =NULL;
  fd = NULL;
  
  /* default return value */
  return 0;  
}

/* parse the configuration file.  */
int config_file_parse()
{
  if( fd == NULL)
    {
      std::cerr << "ConfigFile: please call config_file_open() before \
                   \n            calling config_file_parse(). "<< std::endl;
      exit(0);		   
    }
    int count ;
    int state = CONFIG_STATE_START;
    int last_state = CONFIG_STATE_START; /* :TODO: update the current state 
                                          *         in the next increment
                                          *        */
    char * current_string = (char*) malloc(BUF_SIZE);
    char * current_value = (char*) malloc(BUF_SIZE);
    char * current_key =(char*) malloc(BUF_SIZE);
    
    while(true){
      count = fread(input_buffer,1,BUF_SIZE,fd );
      if( count == 0)
	break;
      
      int i =0;
      while (count > i){
	char current_char = input_buffer[i];
	i++;
	
	switch (state )
	{
	  case CONFIG_STATE_START:
	    if(current_char == '[')
	    {
	      state = CONFIG_STATE_1;
	      break;
	    }
	    // ignore the white spaces //
	    if( IS_WHITE_SPACE(current_char) )  
	    {
	      state=CONFIG_STATE_START;
	      break;
	    }
	    // ignore new lines as well //
	    if( IS_NEW_LINE(current_char))
	    {
	      break;
	    }
	    
	    if( IS_ALPHANUM(current_char))
	    {
	      state=CONFIG_STATE_3;
	      i--;
	      break;
	    }
	    if( current_char == 0 )
	    {
	      break; 
	    }
	    
	    else{
	      std::cerr << "ConfigFile: parsing error have occurrend\
	                   \nLast known state is CONFIG_STATE_START.\n" ;
	      exit(0);
	    }
	    	    
	    break;
	  case CONFIG_STATE_1:
	    // ignore white spaces //
	    if ( IS_WHITE_SPACE(current_char) )
	    {
	      break; 
	    }
	    
	    if( IS_ALPHANUM(current_char)) 
	    {
	       state = CONFIG_STATE_1;
	       appendchar(current_string,current_char); 
	       state= CONFIG_STATE_1; // state would not be changed.//
	       break;
	    }
	    
	    if( current_char == '\\' )
	    {
	      state = CONFIG_STATE_2;
	      break;
	    }
	    if( current_char == ']')
	    {
	      add_session(current_string); 
	      current_string[0] = 0; // again null terminate //
	      state =  CONFIG_STATE_START ;
	      break;
	    }
	    else{
	      std::cerr <<"ConfigFile: Expected alphanum or / or ]. \n";
	      exit(0);
	    }
	    // ::NEVER REACH::
	    break;
	  case CONFIG_STATE_2:
	    if( IS_WHITE_SPACE(current_char)) 
	    {
	       std::cerr <<"ConfigFile: Not Expected a whitespace after \'\\\'.\n"<<std::endl;
	       exit(0);
	    }
	    if( current_char == 't')
	    {
	      appendchar(current_string,'\t');
	      state = CONFIG_STATE_1;
	      break;
	    }
	    if ( current_char == 's' )
	    {
	      appendchar(current_string,' ');
	      state = CONFIG_STATE_1;
	      break;
	    }
	    
	    if( current_char == 'n')
	    {
	      appendchar(current_string, '\n');
	      state=CONFIG_STATE_1;
	      break;
	    }
	    else{
	      std::cerr <<"ConfigFile: Expected \\t \\n or \\t.\n" ;
	      exit(0);
	    }
	    // ::NEVER REACH::    
	    break;
	  case CONFIG_STATE_3:
	    if(IS_ALPHANUM(current_char) )
	    {
	      appendchar(current_string,current_char);
	      break;
	    }
	    if(current_char == '\\')
	    {
	      state = CONFIG_STATE_4;
	      break;
	    }
	    if( current_char == '=')
	    {
	      strcpy(current_key,current_string);
	      current_string[0] =0; // bring null termination back to zero position. //
	      state = CONFIG_STATE_5;
	      break;
	    }
	    else{
	      std::cerr << "ConfigFile: expected / = or alphanum.\n";
	      exit(0);
	    }
	    // ::NEVER REACH::
	    break;
	  case CONFIG_STATE_4:
	    if( IS_WHITE_SPACE(current_char)) 
	    {
	       std::cerr <<"ConfigFile: Not Expected a whitespace after \'\\\'.\n"<<std::endl;
	       exit(0);
	    }
	    if( current_char == 't')
	    {
	      appendchar(current_string,'\t');
	      state = CONFIG_STATE_3;
	      break;
	    }
	    if ( current_char == 's' )
	    {
	      appendchar(current_string,' ');
	      state = CONFIG_STATE_3;
	      break;
	    }
	    
	    if( current_char == 'n')
	    {
	      appendchar(current_string, '\n');
	      state=CONFIG_STATE_3;
	      break;
	    }
	    else{
	      std::cerr <<"ConfigFile: Expected \\t \\n or \\t.\n" ;
	      exit(0);
	    }
	    
	    // :NEVER_REACH:
	    break;
	  case CONFIG_STATE_5:
	    /* ignore white spaces */
	    if( IS_WHITE_SPACE(current_char) )
	      break;
	    
	    if(IS_ALPHANUM(current_char) )
	    {
	      appendchar(current_string,current_char);
	      state = CONFIG_STATE_5; // keep in the current state //
	      break;
	    }
	    
	    if( current_char == '\\')
	    {
	      state = CONFIG_STATE_6;
	      break;
	    }
	    
	    if( IS_NEW_LINE(current_char) )
	    {
	      state = CONFIG_STATE_START;
	      strcpy(current_value,current_string); 
	      if(current_session ==NULL)
	      {
		std::cerr << "ConfigFile: You need be inside a session before you have key=value pairs.\n";
		exit(0);
	      }
	      
	      put_into_session(current_session,current_key,current_value);
	      current_string[0]= 0; // bring the null termination back to zero position //
	      break;
	    }
	    
	    else{
	      std::cerr << "ConfigFile: expected a newline ,alphanum or '\\' operator.\n";
	      exit(0);
	    }
	    // ::NEVER REACHES:: //
	    break;
	  case CONFIG_STATE_6:
	    if( IS_WHITE_SPACE(current_char)) 
	    {
	       std::cerr <<"ConfigFile: Not Expected a whitespace after \'\\\'.\n"<<std::endl;
	       exit(0);
	    }
	    if( current_char == 't')
	    {
	      appendchar(current_string,'\t');
	      state = CONFIG_STATE_5;
	      break;
	    }
	    if ( current_char == 's' )
	    {
	      appendchar(current_string,' ');
	      state = CONFIG_STATE_5;
	      break;
	    }
	    
	    if( current_char == 'n')
	    {
	      appendchar(current_string, '\n');
	      state=CONFIG_STATE_5;
	      break;
	    }
	    else{
	      std::cerr <<"ConfigFile: Expected \\t \\n or \\t.\n" ;
	      exit(0);
	    }
	    // :NEVER REACHES: //
	    break;	    
	  default:
	    std::cerr <<"ConfigFile: Parsing error occurred ! exiting...\n";
	    exit(0);
	}
      }
    }
    // we no longer need these memory //
    free(current_string);
    free(current_value);
    free(current_key);
}


interface.

Code: Select all


/* FILE: config_file.h
             

*/
#ifndef __CONFIG_FILE_H__
#define __CONFIG_FILE_H__

#include <iostream>
#include <stdio.h>
#include <stdlib.h>

/* Internal states that used in state machine */
#define CONFIG_STATE_1         1 /*  state1 just after '[' operator from the start state*/
#define CONFIG_STATE_2         2 /*  state for the first argument there.                   */
#define CONFIG_STATE_3         3
#define CONFIG_STATE_4         4
#define CONFIG_STATE_5         5
#define CONFIG_STATE_6         6
#define CONFIG_STATE_7         7
#define CONFIG_STATE_ERROR   100
#define CONFIG_STATE_START   101

/* functions */
int config_file_open(const char *  /*file_name*/ );
int config_file_parse();
int config_file_session_into( const char * /* session_name */);
const char* config_file_get_current_session ();
const char* config_file_get_key_value(const char*  /*keyword*/ );
int config_file_dinit();

#endif /* ENDIF __CONFIG_FILE_H__ */


I'm going to release the code to github so you guys just could download the tarball or use this as a static library.






To use the code follow this example.

Code: Select all

/*
  FILE: example.cpp
  demonstrates the usage of config_file 
*/
#include "../common.h"
#include "../config_file.h"
#include <stdio.h>
#include <iostream>

const char* file_name ="./config_file_test.ini";

int main(int argc,char** argv)
{
  config_file_open(file_name);
  config_file_parse();
  // session into the main //
  if( config_file_session_into("server"))
   std::cout << " config_file_session_into success\n";
  printf(config_file_get_key_value("keyword1\t\n"));
  config_file_get_key_value("keyword3");
  return 0; 
}


along with this file ,
config_file_test.ini

Code: Select all

[main]
keyword1=value1
keyword2=value2
keyword3=value3

[server]
keyword1\t\n=value11\t\t
keyword2=value12
keyword3=value13



To compile you may invoke these commands like bellow.

Code: Select all


g++ -o config_file.o config_file.cpp 
g++ -o example.o example.cpp
g++ -o example config_file.o example.o 
./example

User avatar
Neo
Site Admin
Site Admin
Posts: 2642
Joined: Wed Jul 15, 2009 2:07 am
Location: Colombo

Re: Simple ini file parser

Post by Neo » Tue Jan 21, 2014 9:34 pm

Nicely written code. Well done Semi!
Post Reply

Return to “C/C++ Programming”