Jump to content
  • Advertisement
Sign in to follow this  
F-Kop

ini reader (c++, any platform)

This topic is 2473 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

a while back, i made a working ini loader with no bugs in 3 days. yesterday, i made a new one with no bugs in 1 day, with about half as many lines of code. this is proof that you can never master a program at the first attempt. anyway, the source code is below. is there anything i should have done differently? [Edited by - F-Kop on July 25, 2006 12:23:01 PM]

Share this post


Link to post
Share on other sites
Advertisement


// ini.h
// Ini loader

#ifndef _INI_H_
#define _INI_H_

typedef union
{
int i;
char *s;
} _IniVal_t;

// CIni class

class CIni
{
public:
// Creation
CIni( );
~CIni( );

// Files
virtual bool LoadFile( const char *fileName );
virtual bool SaveFile( const char *fileName );

// Key values
virtual int GetIntVal( const char *groupName, const char *keyName, int def );
virtual char* GetStrVal( const char *groupName, const char *keyName, char **buf, int max, const char *def );
virtual int SetIntVal( const char *groupName, const char *keyName, int value );
virtual char* SetStrVal( const char *groupName, const char *keyName, char *value );

/*// Enumerator
bool GetFirstGroupName( char **buf, int max );
bool GetNextGroupName( char **buf, int max );
bool GetFirstKeyName( const char *groupName, char **buf, int max );
bool GetNextKeyName( const char *groupName, char **buf, int max );*/


private:
// Private
enum { INVALID, GROUP, NULVAL, INTVAL, STRVAL };
int CountKeys( const char *groupName );
void ExtendGroups( );
void ExtendKeys( );
char* FindGroup( const char *groupName );
int FindKey( const char *groupName, const char *keyName );
int IdentifyLine( const char *buf );
static bool ReadId( const char *buf, int *pos, char *dst );
static int ReadInt( const char *buf, int *pos );
static char* ReadStr( const char *buf, int *pos );
static void SkipWhite( const char *buf, int *pos );
static bool ValidId( char chr );
static bool ValidIdInit( char chr );

// Values
int m_groupx;
int m_groupc;
char **m_groupName;

int m_keyx;
int m_keyc;
char **m_keyGroup;
char **m_keyName;

enum { NUL, INT, STR };
int *m_keyType;
_IniVal_t *m_keyVal;
};

#endif//end





// ini.cpp
// Ini loader

#include <memory.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "ini.h"
#include "misc.h"

#define GROUPX_INTERVAL 10
#define KEYX_INTERVAL 100
#define MAX_LINE 128
#define MAX_STRING 64

////////////////
// CIni class //
////////////////

//----------//
// Creation //

// Constructor

CIni::CIni( )
{
int i;

// Groups
m_groupx = GROUPX_INTERVAL;
m_groupc = 1;
m_groupName = new char*[GROUPX_INTERVAL];
m_groupName[0] = NULL;

// Keys
m_keyx = KEYX_INTERVAL;
m_keyc = 0;
m_keyGroup = new char*[KEYX_INTERVAL];
m_keyName = new char*[KEYX_INTERVAL];

// Values
m_keyType = new int[KEYX_INTERVAL];
m_keyVal = new _IniVal_t[KEYX_INTERVAL];

for( i = 0; i < m_keyx; i++ )
m_keyType = 0;
}

// Destructor

CIni::~CIni( )
{
int i;

for( i = 0; i < m_groupc; i++ )
{
delete [] m_groupName;
}

for( i = 0; i < m_keyc; i++ )
{
delete [] m_keyName;

if( m_keyType == STR )
delete [] m_keyVal.s;
}

delete [] m_groupName;
delete [] m_keyGroup;
delete [] m_keyName;
delete [] m_keyType;
delete [] m_keyVal;
}

//-------//
// Files //

// LoadFile
// Load ini from file

bool CIni::LoadFile( const char *fileName )
{
FILE *file;
char *buf;
char *key;
char *str;
char *group;
int pos;

if( !( file = fopen( fileName, "r" ) ) )
return false;

buf = new char[MAX_LINE];
key = new char[MAX_STRING];
group = NULL;

while( !feof( file ) )
{
int lineType;

MyFgets( buf, MAX_LINE, file );
lineType = IdentifyLine( buf );
pos = 0;

switch( lineType )
{
// Group
case GROUP:
SkipWhite( buf, &pos );
pos++;
SkipWhite( buf, &pos );
ReadId( buf, &pos, key );
group = FindGroup( key );
break;

// Null
case NULVAL:
SkipWhite( buf, &pos );
ReadId( buf, &pos, key );
SetIntVal( group, key, 0 );
break;

// Integer
case INTVAL:
SkipWhite( buf, &pos );
ReadId( buf, &pos, key );
SkipWhite( buf, &pos );
pos++;
SkipWhite( buf, &pos );
SetIntVal( group, key, ReadInt( buf, &pos ) );
break;

// String
case STRVAL:
SkipWhite( buf, &pos );
ReadId( buf, &pos, key );
SkipWhite( buf, &pos );
pos++;
SkipWhite( buf, &pos );
SetStrVal( group, key, str = ReadStr( buf, &pos ) );
delete [] str;
break;
}
}

fclose( file );
delete [] buf;
delete [] key;
return true;
}

// SaveFile
// Save ini to file

bool CIni::SaveFile( const char *fileName )
{
int i;
FILE *file;
char *group;
char *oldGroup;
char *buf;

oldGroup = (char*) 1;
buf = new char[MAX_STRING];

if( !( file = fopen( fileName, "w+" ) ) )
return false;

for( i = 0; i < m_keyc; i++ )
{
group = m_keyGroup;

// Group
if( group != oldGroup )
{
if( !group ) fwrite( "\n", 1, 1, file );
else
{
fwrite( "\n[", 1, 2, file );
fwrite( group, 1, strlen( group ), file );
fwrite( "]\n", 1, 2, file );
}
}

oldGroup = group;

// Key value
fwrite( m_keyName, 1, strlen( m_keyName ), file );
fwrite( "=", 1, 1, file );

switch( m_keyType )
{
case INT:
{
int len;

itoa( m_keyVal.i, buf, 10 );
len = strlen( buf );
fwrite( buf, 1, len, file );
break;
}
case STR:
{
int len;

EncodeString( buf, m_keyVal.s, MAX_STRING );
len = strlen( buf );
fwrite( "\"", 1, 1, file );
fwrite( buf, 1, strlen( buf ), file );
fwrite( "\"", 1, 1, file );
break;
}
}

fwrite( "\n", 1, 1, file );
}

fclose( file );
delete [] buf;
return true;
}

//------------//
// Key values //

// GetIntVal
// Get integer value

int CIni::GetIntVal( const char *groupName, const char *keyName, int def )
{
int oldKeyc;
int key;

oldKeyc = m_keyc;
key = FindKey( groupName, keyName );

// Use default
if( ( m_keyc != oldKeyc ) || ( m_keyType[key] != INT ) )
{
if( m_keyType[key] == STR ) delete [] m_keyVal[key].s;

m_keyType[key] = INT;
m_keyVal[key].i = def;
}

return m_keyVal[key].i;
}

// GetStrVal
// Get string value
// NOTE: Allocates string if max is -1, otherwise copy

char* CIni::GetStrVal( const char *groupName, const char *keyName, char **buf, int max, const char *def )
{
int oldKeyc;
int key;

oldKeyc = m_keyc;
key = FindKey( groupName, keyName );

// Use default
if( ( m_keyc != oldKeyc ) || ( m_keyType[key] != STR ) )
{
if( m_keyType[key] == STR ) delete [] m_keyVal[key].s;

m_keyType[key] = STR;
m_keyVal[key].s = new char[ strlen( def ) + 1 ];
strcpy( m_keyVal[key].s, def );
}

// Copy string
if( max == -1 )
{
if( *buf ) delete [] *buf;
*buf = new char[ strlen( m_keyVal[key].s ) + 1 ];
strcpy( *buf, m_keyVal[key].s );
}
else if( max == 0 ) return 0;
else
{
int len;

len = Mini( max - 1, strlen( m_keyVal[key].s ) );
memcpy( *buf, m_keyVal[key].s, len );
*buf[len] = 0;
}

return *buf;
}

// SetIntVal
// Set integer value

int CIni::SetIntVal( const char *groupName, const char *keyName, int value )
{
int key;

key = FindKey( groupName, keyName );

if( m_keyType[key] == STR ) delete [] m_keyVal[key].s;
m_keyType[key] = INT;
m_keyVal[key].i = value;

return value;
}

// SetStrVal
// Set string value

char* CIni::SetStrVal( const char *groupName, const char *keyName, char *value )
{
int key;

key = FindKey( groupName, keyName );

if( m_keyType[key] == STR ) delete [] m_keyVal[key].s;
m_keyType[key] = STR;
m_keyVal[key].s = new char[ strlen( value ) + 1 ];
strcpy( m_keyVal[key].s, value );

return value;
}

//---------//
// Private //

// CountKeys
// Count keys in a group

int CIni::CountKeys( const char *groupName )
{
int i;
int ret;
char *group;

ret = 0;
group = FindGroup( groupName );

for( i = 0; i < m_keyc; i++ )
{
if( group == m_keyGroup ) ret++;
}

return ret;
}

// ExtendGroups
// Extend group name array

void CIni::ExtendGroups( )
{
char **oldGroupName;

oldGroupName = m_groupName;
m_groupName = new char*[m_groupx + GROUPX_INTERVAL];
memcpy( m_groupName, oldGroupName, sizeof(char*) * m_groupx );
delete [] oldGroupName;

m_groupx += GROUPX_INTERVAL;
}

// ExtendKeys
// Extend key arrays

void CIni::ExtendKeys( )
{
char **oldKeyGroup;
char **oldKeyName;
int *oldKeyType;
_IniVal_t *oldKeyVal;

oldKeyGroup = m_keyGroup;
oldKeyName = m_keyName;
oldKeyType = m_keyType;
oldKeyVal = m_keyVal;

m_keyGroup = new char*[m_keyx + KEYX_INTERVAL];
m_keyName = new char*[m_keyx + KEYX_INTERVAL];
m_keyType = new int[m_keyx + KEYX_INTERVAL];
m_keyVal = new _IniVal_t[m_keyx + KEYX_INTERVAL];

memcpy( m_keyGroup, oldKeyGroup, sizeof(char*) * m_keyx );
memcpy( m_keyName, oldKeyName, sizeof(char*) * m_keyx );
memcpy( m_keyType, oldKeyType, sizeof(int) * m_keyx );
memcpy( m_keyVal, oldKeyVal, sizeof(_IniVal_t) * m_keyx );

delete [] oldKeyGroup;
delete [] oldKeyName;
delete [] oldKeyType;
delete [] oldKeyVal;

m_keyx += KEYX_INTERVAL;
}

// FindGroup
// Find group name

char* CIni::FindGroup( const char *groupName )
{
int i;

// Blank group
if( !groupName ) return NULL;

// Compare groups
for( i = 0; i < m_groupc; i++ )
{
if( m_groupName )
if( strcmp( groupName, m_groupName ) == 0 )
break;
}

// Create group
if( i == m_groupc )
{
if( i >= m_groupx ) ExtendGroups( );

m_groupName = new char[ strlen( groupName ) + 1 ];
strcpy( m_groupName, groupName );

m_groupc++;
}

return m_groupName;
}

// FindKey
// Find key name

int CIni::FindKey( const char *groupName, const char *keyName )
{
int i;
char *group;

group = FindGroup( groupName );

// Compare keys
for( i = 0; i < m_keyc; i++ )
{
if( group == m_keyGroup )
if( strcmp( keyName, m_keyName ) == 0 ) break;
}

// Create key
if( i == m_keyc )
{
if( i >= m_keyx ) ExtendKeys( );

m_keyGroup = group;
m_keyName = new char[ strlen( keyName ) + 1 ];
strcpy( m_keyName, keyName );

m_keyc++;
}

return i;
}

// IdentifyLine
// Identify purpose of line in .INI file

int CIni::IdentifyLine( const char *buf )
{
int pos;

pos = 0;
SkipWhite( buf, &pos );

// Group
if( buf[pos] == '[' )
{
pos++;
SkipWhite( buf, &pos );
if( !ReadId( buf, &pos, NULL ) ) return INVALID;
SkipWhite( buf, &pos );
if( buf[pos] != ']' ) return INVALID;
return GROUP;
}

// Key
if( ReadId( buf, &pos, NULL ) )
{
SkipWhite( buf, &pos );
if( buf[pos] != '=' ) return INVALID;
pos++;
SkipWhite( buf, &pos );

// Integer
if( ( buf[pos] >= '0' ) && ( buf[pos] <= '9' ) ) return INTVAL;
if( ( buf[pos] == '-' ) && ( ( buf[pos + 1] >= '0' ) && ( buf[pos + 1] <= '9' ) ) ) return INTVAL;

// String
if( buf[pos] == '\"' ) return STRVAL;

return NULVAL;
}

return INVALID;
}

// ReadId
// Read identifier

bool CIni::ReadId( const char *buf, int *pos, char *dst )
{
int di;

di = 0;

if( ValidIdInit( buf[*pos] ) )
{
for(;;)
{
if( !ValidId( buf[*pos] ) )
{
if( dst ) dst[di] = 0;
return true;
}

if( dst )
{
dst[di] = buf[*pos];
di++;
}

(*pos)++;
}
}
return false;
}

// ReadInt
// Read integer
// Returns value

int CIni::ReadInt( const char *buf, int *pos )
{
int neg;
int val;

val = 0;

// Negative
if( buf[*pos] == '-' )
{
neg = -1;
*pos++;
}
else neg = 1;

// Count
while( ( buf[*pos] >= '0' ) && ( buf[*pos] <= '9' ) )
{
val *= 10;
val += buf[*pos] - '0';
(*pos)++;
}

return val * neg;
}

// ReadStr
// Read string
// Returns new string

char* CIni::ReadStr( const char *buf, int *pos )
{
char *enc;
char *ret;
int start;
int len;

(*pos)++;
start = *pos;
len = 0;

// Count
while( ( buf[*pos] != '\r' ) && ( buf[*pos] != '\n' ) &&
( buf[*pos] != '\"' ) && ( buf[*pos] != 0 ) )
{
if( buf[*pos] == '\\' )
{
len++;
(*pos)++;
}
len++;
(*pos)++;
}

// Read
enc = new char[len + 1];
memcpy( enc, &buf[start], len );
enc[len] = 0;

// Decode
ret = new char[len + 1];
DecodeString( ret, enc, len );
delete [] enc;

return ret;
}

// SkipWhite
// Skip white spaces

void CIni::SkipWhite( const char *buf, int *pos )
{
while( ( buf[*pos] == '\t' ) || ( buf[*pos] == ' ' ) ) (*pos)++;
}

// ValidId
// Valid identifier character
// A-Z, a-z, _, @, ., :, 0-9

bool CIni::ValidId( char chr )
{
return( ( ( chr >= 'A' ) && ( chr <= 'Z' ) ) ||
( ( chr >= 'a' ) && ( chr <= 'z' ) ) ||
( chr == '_' ) || ( chr == '@' ) ||
( chr == '.' ) || ( chr == ':' ) ||
( ( chr >= '0' ) && ( chr <= '9' ) ) );
}

// ValidIdInit
// Valid initial identifier character
// A-Z, a-z, _, @

bool CIni::ValidIdInit( char chr )
{
return( ( ( chr >= 'A' ) && ( chr <= 'Z' ) ) ||
( ( chr >= 'a' ) && ( chr <= 'z' ) ) ||
( chr == '_' ) || ( chr == '@' ) );
}

//end






// misc.h
// Misc

#ifndef _MISC_H_
#define _MISC_H_

#include <stdio.h>

#ifdef __cplusplus
#define MISC extern "C"
#else
#define MISC
#endif

//-----------//
// Constants //

// AlphabetCompare

#define MISC_SAME 0
#define MISC_BEFORE 1
#define MISC_AFTER 2

//-----------//
// Functions //

// Chars

MISC char Lcase( char chr );
MISC char Ucase( char chr );

// Files

MISC char* MyFgets( char *buf, int max, FILE *file );

// Integers

MISC int Mini( int l, int r );
MISC int Maxi( int l, int r );

// Strings

MISC void Alert( const char *text );
MISC int AplhabetCompare( const char *var, const char *ctl );
MISC char* DecodeString( char *dst, const char *src, int max );
MISC char* EncodeString( char *dst, const char *src, int max );
MISC void Error( const char *text );

#endif//end




// misc.c
// Misc

#ifdef WIN32
#include <windows.h>
#endif

#include <string.h>
#include "misc.h"

///////////////
// Functions //
///////////////

//-------//
// Chars //

// Lcase
// Lower case

char Lcase( char chr )
{
if( ( chr >= 'A' ) && ( chr <= 'Z' ) )
chr += 'a' - 'A';

return chr;
}

// Ucase
// Upper case

char Ucase( char chr )
{
if( ( chr >= 'a' ) && ( chr <= 'z' ) )
chr -= 'a' - 'A';

return chr;
}

//-------//
// Files //

// MyFgets
// Replacement for fgets

char* MyFgets( char *buf, int max, FILE *file )
{
int i;
char chr;

for( i = 0; i < max - 1; i++ )
{
fread( &chr, 1, 1, file );

if( ( chr == '\r' ) || ( chr == '\n' ) ||
( chr == 0 ) || feof( file ) )
break;

buf = chr;
}

buf = 0;
return buf;
}

//----------//
// Integers //

// Mini
// Minimum integer

int Mini( int l, int r )
{
if( l < r ) return l;
else return r;
}

// Maxi
// Maximum integer

int Maxi( int l, int r )
{
if( l > r ) return l;
else return r;
}

//---------//
// Strings //

// Alert
// Popup alert box

void Alert( const char *text )
{
// win32
#ifdef WIN32
MessageBox( NULL, text, "Alert", MB_OK | MB_ICONEXCLAMATION );
#endif
}

// AlphabetCompare
// Which string comes first alphabetically

int AlphabetCompare( const char *var, const char *ctl )
{
int i;
int len;

len = Maxi( strlen( var ), strlen( ctl ) );

for( i = 0; i <= len; i++ )
{
if( Lcase( var ) < Lcase( ctl ) ) return MISC_BEFORE;
if( Lcase( var ) > Lcase( ctl ) ) return MISC_AFTER;
}

return MISC_SAME;
}

// DecodeString
// Decode text string

char* DecodeString( char *dst, const char *src, int max )
{
int len;
int i;
int pos;

pos = 0;

len = strlen( src ) + 1;

for( i = 0; i < len; i++ )
{
// Escape chars
if( src == '\\' )
{
if( pos >= max ) break;

switch( src[i + 1] )
{
case 'r':
dst[pos] = '\r';
break;
case 'n':
dst[pos] = '\n';
break;
case 't':
dst[pos] = '\t';
break;
case '\\':
dst[pos] = '\\';
break;
case '\"':
dst[pos] = '\"';
break;
default:
pos--;
}

pos++;
i++;
}

// Normal chars
else
{
if( pos >= max ) break;

dst[pos] = src;
pos++;
}
}

dst[pos] = 0;
return dst;
}

// EncodeString
// Encode text string

char* EncodeString( char *dst, const char *src, int max )
{
int brk;
int i;
int pos;

brk = 0;
pos = 0;
max -= 2;

for( i = 0; src; i++ )
{
switch( src )
{
case '\r':
case '\n':
case '\t':
case '\\':
case '\"':
// Bound check
if( pos >= max - 1 )
{
brk = 1;
break;
}

// Backslash
dst[pos] = '\\';
pos++;
break;
}

if( ( pos >= max ) || ( brk ) ) break;

// Escape chars
switch( src )
{
case '\r':
dst[pos] = 'r';
break;
case '\n':
dst[pos] = 'n';
break;
case '\t':
dst[pos] = 't';
break;
case '\\':
dst[pos] = '\\';
break;
case '\"':
dst[pos] = '\"';
break;

// Normal chars
default:
dst[pos] = src;
}

pos++;
}

dst[pos] = 0;

return dst;
}

// Error
// Popup error box

void Error( const char *text )
{
// win32
#ifdef WIN32
MessageBox( NULL, text, "Error", MB_OK | MB_ICONERROR );
#endif
}

//end

Share this post


Link to post
Share on other sites
Heh, C with classes :). Why not use std::string, std::vector, toupper(), tolower(), and everything else that's already done for you?

Do you have an example of using it?

Share this post


Link to post
Share on other sites
EXAMPLE: if you had a file called test.ini, and it looked like this:


[Group1]
IntVal=100
StrVal="one hundred"

[Group2]
IntVal=33
StrVal="thirty-three"


// you would load it like this:


CIni ini;
ini.LoadFile( "test.ini" );


// and you could get the values like this:


int intVal1, intVal2;
char *strVal1, *strVal2;

strVal1 = new char[100];
strVal2 = new char[100];

// the last parameter in GetIntVal and GetStrVal
// is the value to use if the key wasn't found
// in the ini file
intVal1 = ini.GetIntVal( "Group1", "IntVal", 0 );
intVal2 = ini.GetIntVal( "Group2", "IntVal", 0 );
ini.GetStrVal( "Group1", "StrVal", &strVal1, 100, "n/a" );
ini.GetStrVal( "Group2", "StrVal", &strVal2, 100, "n/a" );





if the fourth parameter in GetStrVal is -1, it allocates the string for you of just the right size, but you have to delete it yourself. most people might not like that, but i find it pretty useful.

when you're done, you can ini.SaveFile( "test.ini" );.

Share this post


Link to post
Share on other sites
That isn't C++, that's an attempt at coding C with C++. How do you know there are no bugs? And what do you count a bug? What if one of the new-expressions throw an exception? You don't have a new_handler. What about file operations gone wrong? If you intend to use CIni polymorphically like this:

CIni* file = new SomeClassDerivedFromCIni();
delete file;

Then you need your destructor to be virtual. If you don't, then why do you have virtual functions? Do you know itoa doesn't exist in C++? The headers you use are all wrong, they should be memory, cstdio, cstdlib and cstring. Those are just the most obvious ones.

EDIT: You even have a bug in your lowercase/uppercase functions, 'b' is not required to be 'a'+1, it could easily be like this:
'A' = 5
'a' = 6
'b' = 61
'B' = 152
'5' = 7

Share this post


Link to post
Share on other sites
You can also cut down the code by properly using std::string, std::stringstream, std::map and some structures.

Like was said before, this is C with classes, not C++.

Share this post


Link to post
Share on other sites
As promised (finally).

My idea is to create a data structure that represents an INI file. It is a wrapper for a std::map (associative array) which maps section names to "sections", which in turn are data structures mapping property names to values in a similar way. The data structure initializes itself in the constructor by accepting a file name and attempting to read an INI file from it (throwing an exception if anything goes wrong). This is exception-safe because the wrapped data members are standard library containers held by value (not pointer). The destructor may attempt to write to the INI file again (if this behaviour was requested). It does not throw an exception on failure, because you shouldn't do that in destructors. Note that no file writing happens on a failed read/setup, because the object destructor is not called in such cases (only the destructors of members). The constructor takes a 'writeback' parameter (the initial value for that flag; it's left as a public member because it's really part of the object's interface) and an 'exists' parameter: if 'exists' is false, the file is not looked for - this allows us to create INI files from scratch.

To access the data, the natural operator[] syntax is provided. The intent is that when you want to write a key value, it should be created automatically if it doesn't exist; but if you read a key value and don't want to change it, there should be no such creation. The operator[] of std::map provides this automatic creation. Therefore, I provide const-overloaded versions of operator[]: the const version (which would be applied for a read-only access) checks for the key's presence first, and if not found, returns a const reference to a static "empty" instance (so readers will see blank keys). The non-const version directly uses the std::map operator[] to auto-create missing keys. This way, you can add items to a non-const INIFile: the operator[] default-constructs a new value associated with the specified key, and returns a non-const reference to it, through which you can assign the intended value.

That does require that the user start with a const INIFile when trying to do read-only access, or create a const reference to an existing non-const INIFile and call through that.


#include <string>
#include <map>
#include <iostream>
#include <fstream>
#include <exception>
#include <stdexcept>

template <typename Traits> class Dictionary;

struct KeyTraits {
typedef std::string key_t;
typedef std::string value_t;
typedef std::pair<key_t, value_t> item_t;
static const char* begin_text() { return ""; }
static const char* separator_text() { return " = "; }
static const char* end_text() { return "\n"; }
};

typedef Dictionary<KeyTraits> INISection;

struct SectionTraits {
typedef std::string key_t;
typedef INISection value_t;
typedef std::pair<key_t, value_t> item_t;
static const char* begin_text() { return "["; }
static const char* separator_text() { return "]\n"; }
static const char* end_text() { return "\n"; }
};

template <typename Traits>
class Dictionary {
public:
typedef typename Traits::key_t key_t;
typedef typename Traits::value_t value_t;
typedef std::map<key_t, value_t> storage_t;
typedef typename storage_t::iterator iterator;
typedef typename storage_t::const_iterator const_iterator;

private:
static value_t empty;
storage_t data;

public:
const value_t& operator[](const key_t& key) const {
const_iterator it = data.find(key);
const value_t& result = (it == data.end()) ? empty : it->second;
return result;
}

value_t& operator[](const key_t& key) {
value_t& result = data[key];
return data[key];
}

friend std::ostream& operator<<(std::ostream& os, const Dictionary<Traits>& d) {
for (const_iterator it = d.data.begin(); it != d.data.end(); ++it) {
os << Traits::begin_text() << it->first
<< Traits::separator_text() << it->second
<< Traits::end_text();
}
return os;
}
};

// There's got to be a less verbose way to do this initialization :(
template<typename Traits>
typename Dictionary<Traits>::value_t Dictionary<Traits>::empty = typename Dictionary<Traits>::value_t();

typedef Dictionary<SectionTraits> INIBase;

class INIFile : public INIBase {
std::string filename;

static void trim(std::string& to_trim) {
int lstrip = to_trim.find_first_not_of(" \t\n");
int rstrip = to_trim.find_last_not_of(" \t\n");
if (lstrip == std::string::npos || rstrip == std::string::npos) {
to_trim = "";
} else {
to_trim = to_trim.substr(lstrip, rstrip + 1);
}
}

public:
bool writeback;

INIFile(const std::string& filename, bool writeback = true, bool exists = true):
filename(filename), writeback(writeback), INIBase(exists ? createBase(filename) : INIBase()) {}

static INIBase createBase(const std::string& filename) {
// We construct with exists = false if we want to make a new INI file.
INIBase tmp;

std::string current_line;
std::string current_section = "";
std::ifstream input(filename.c_str());
if (!input.is_open()) {
throw std::runtime_error("INI file not found");
}

while (std::getline(input, current_line)) {
// Trim whitespace from line
trim(current_line);
// Skip empty lines
if (current_line.empty()) { continue; }
int last_index = current_line.length() - 1;
// Make sure this is clearly either a section title or key=value pair.
if (current_line[0] == '[' && current_line[last_index] == ']') {
if (current_line.find_first_of("[]", 1) != last_index) {
throw std::runtime_error("found brackets in mid-line");
}

current_section = current_line.substr(1, last_index - 1);
trim(current_section);
if (current_section == "") {
throw std::runtime_error("empty section name");
}
} else {
// This should be a key=value pair. Make sure some section is "open".
if (current_section == "") {
throw std::runtime_error("keys before the first section");
}
// Find the '=' and make sure there's only one.
int pos = current_line.find('=');
if (pos == std::string::npos) {
throw std::runtime_error("no = found in expected key=value pair");
}
if (pos != current_line.rfind('=')) {
throw std::runtime_error(">1 =s found in expected key=value pair");
}
// Split up key and value and do the insertion
std::string key(current_line, 0, pos); trim(key);
std::string value(current_line, pos + 1); trim(value);
tmp[current_section][key] = value;
}
}
return tmp;
}

~INIFile() {
if (writeback) {
write_to_file();
}
}

void write_to_file() {
std::ofstream output(filename.c_str());
output << *this;
}
};

// A few really basic tests. BTW, yes, this is all tested and works :)
int main() {
{
INIFile test("foo.ini", true, false);
std::cout << "TEST ADDING SOMETHING" << std::endl;
test["foo"]["bar"] = "baz";
std::cout << "TESTED ADDING SOMETHING" << std::endl;
}
{
std::cout << "REOPENING" << std::endl;
const INIFile test("foo.ini", false, true);
std::cout << "REOPENED" << std::endl;
std::cout << "FOOBAR:" << test["foo"]["bar"] << std::endl;
std::cout << "FOO:" << test["foo"] << std::endl;
std::cout << "FOOBAZ:" << test["foo"]["baz"] << std::endl;
}
{
std::cout << "TEST NO WRITEBACK" << std::endl;
INIFile test("foo.ini", false, true);
test["quux"]["spam"] = "ni";
}
}



[Edited by - Zahlman on August 2, 2006 1:43:17 AM]

Share this post


Link to post
Share on other sites
Hello,
I am working on the same code., but getting errors like this., will some one help to rectify these errors.

In file included from ../src/INI.cpp:17:
In constructor 'INIFile::INIFile(const std::string&, bool, bool)':
107: warning: 'INIFile::writeback' will be initialized after
110: warning: base 'Dictionary<SectionTraits>'
109: warning: when initialized here

Share this post


Link to post
Share on other sites
1) Those aren't errors, those are warnings. Some warnings are erros, but in this case I think they could be safely ignored.

2) This thread was from 2006.

Share this post


Link to post
Share on other sites

This topic is 2473 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Guest
This topic is now closed to further replies.
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

GameDev.net is your game development community. Create an account for your GameDev Portfolio and participate in the largest developer community in the games industry.

Sign me up!