Ini Parser

Whether you're a newbie or an experienced programmer, any questions, help, or just talk of any language will be welcomed here.

Moderator: Coders of Rage

Post Reply
techboy123
Chaos Rift Newbie
Chaos Rift Newbie
Posts: 12
Joined: Sat Feb 20, 2010 11:37 am
Favorite Gaming Platforms: PC, PS3, Dreamcast, DS, Wii
Programming Language of Choice: C++
Location: UK

Ini Parser

Post by techboy123 »

I was slightly bored of writing code to read a file to get settings, etc. So I wrote a class or two to make it a bit more painless for myself. I haven't tested what happens when the config file format is incorrect (my bad) but I would bet it ends badly.

This config (or ini, never looked up any differences) parser reads in data, allows you to have a fiddle, and save it. It supports ints, floats, bools and strings (using std::string as the template parameter). Using it is as simple as:
  • Create a Config object (Is instantiate the better term?)
  • Call Config::parseFile(filename)
  • Use Config::getVar< type_it_is >(varname) and Config::setVar<type_it_is>(varname, some_value) to
  • Config::saveFile() will save the current settings inside the object. You can also use Config::saveFile(filename)
  • Config::clear()... just clears the std::map. No more settings. Config::parseFile() does not call this internally.
  • Finally. Config::addVar and Config::delVar, add and delete (yes, respectively) new variables into the Config object. Using an existing variable name for
    addVar will delete the object and replace it with the new one.
Config.h

Code: Select all

#pragma once
#ifndef CONFIG_H
#define CONFIG_H 1

#include <ctype.h>
#include <string>
#include <algorithm>
#include <sstream>
#include <map>
#include <fstream>
#include <iomanip>
#include <exception>

typedef std::string stringtype;

class BaseConfigType {
protected:
	BaseConfigType() {}

public:
	virtual ~BaseConfigType() {}
	virtual void writeVar(std::ofstream& fileHandle) = 0;
};



template <typename T>
class ConfigType;



template <typename T> 
class ConfigWriteData {
	friend class ConfigType<T>;
	static void writeVar(std::ofstream& fileHandle, ConfigType<T>* configVar) {
		T* p = configVar->data;
		fileHandle << *p;
	}
};




template <typename T>
class ConfigType : public BaseConfigType {
protected:	
	T* data;	
	friend class ConfigWriteData<T>;
	ConfigWriteData<T> writeData;

public:	
	ConfigType(T const& a) : data( new T(a) ) {}	
	virtual ~ConfigType() { delete data; }
	
	inline T get() const {
		T* p = data;
		return *p;
	}

	inline void set(T const& v) {
		T* p = data;
		*p = v;
	}	

	//Leave writing the variable into the file to this class, since
	//knows what type the variable is.
	void writeVar(std::ofstream& fileHandle) {
		writeData.writeVar(fileHandle, this);
	}
	
};




template <>
class ConfigWriteData<std::string> {
	friend class ConfigType<std::string>;
	static void writeVar(std::ofstream& fileHandle, ConfigType<std::string>* configVar) {
		std::string* p = configVar->data;
		fileHandle <<'"'<< *p <<'"';
	}

};

template<>
class ConfigWriteData<float> {
	friend class ConfigType<float>;
	static void writeVar(std::ofstream& fileHandle, ConfigType<float>* configVar) {
		float* p = configVar->data;
		fileHandle << std::fixed << std::setprecision(7) << *p;
	}
};




class ConfigException : public std::logic_error {
public:
	enum ErrorCodes {
		UNKNOWN_ERROR = 0,
		VARIABLE_DOES_NOT_EXIST,
		FAILED_TO_OPEN_FILE,
		PARSE_ERROR,
	};

	ConfigException(std::string const& message, ErrorCodes error = UNKNOWN_ERROR) :
		std::logic_error(message), errorcode(error) {}

	ErrorCodes code() const{
		return errorcode;
	}

protected:
	enum ErrorCodes errorcode;

};




class Config {
protected:
	std::map< std::string, BaseConfigType* > vars;
	std::string curFile;

	void parseStringVariable(std::string const& varname, std::string const& vardata) {
		std::string::const_iterator it, beg, end;
	
		
		//Get the string contents
		bool inString = false;
		for(beg = it = vardata.begin(); it != vardata.end(); ++it) { 
			if(*it == '"' || *it == '\'') {
				if(!inString) beg = it+1;
				else{
					end = it;
					break;
				}

				inString = !inString;
				continue;
			}
		}

		//Add it to the variables
		addVar<std::string>( varname, std::string(beg,end) );
	}


	void parseFloatVariable(std::string const& varname, std::string const& vardata) {
		float f = 0.0f; 
		std::stringstream ss;

		//Get the float as a string
		std::string::const_iterator it = vardata.begin();
		std::string fstr;
		for(; it != vardata.end(); it++) {
			if(isdigit(*it) || *it == '.') fstr += *it;
		}

		//Convert into float
		ss.clear();
		ss << std::fixed << std::setprecision(7) << fstr;
		ss >> std::fixed >> std::setprecision(7) >> f;

		addVar<float>(varname, f);

	}	

	void parseIntVariable(std::string const& varname, std::string const& vardata) {
		int i = 0; 
		std::stringstream ss;

		//Get the float as a string
		std::string::const_iterator it = vardata.begin();
		std::string istr;
		for(; it != vardata.end(); it++) {
			if(isdigit(*it)) istr += *it;
		}

		//Convert into float
		ss.clear();
		ss << istr;
		ss >> i;	

		addVar<int>(varname, i);
	}

	void parseVariable(std::string const& varname, std::string const& line) {
		//Get the variable data
		std::string::const_iterator it = line.begin();

		//Get to the point after the =
		for(; it != line.end(); ++it) {
			if(*it == '=') {
				++it;
				break;
			}
		}

		//Read the variable data
		std::string vardata(it, line.end() );
		
		//Parse the variable
		bool endloop = false;
		for(it = vardata.begin(); it != vardata.end() && !endloop; it++) {
			switch(toupper(*it)) {
			case '"':
				parseStringVariable(varname, vardata);
				endloop = true;
				break;
			case'F':
				addVar<bool>(varname, false);
				endloop = true;
				break;
			case'T':
				addVar<bool>(varname, true);
				endloop = true;
				break;

			default:
				if(isdigit(*it) ) {
					if(vardata.find('.',0) != vardata.npos) {
						parseFloatVariable(varname, vardata);
						endloop = true;
					}
					else {
						parseIntVariable(varname, vardata);
						endloop = true;
					}
				}
				break;
				

			}
		}

	}

	void parseLine(std::string const& line, std::string &parseErrors) {
		std::string varname;

		//Get the variable name
		parseVariableName(varname, line.begin(), line.end(), &parseErrors);
		parseVariable(varname, line);
	}

	void parseVariableName(std::string &varname, std::string::const_iterator beg, std::string::const_iterator end, std::string* errors = 0) {
		std::string::const_iterator it = beg;

		bool hitspace = false;
		bool endloop  = false;
		bool failed   = false;


		for(; it != end && !endloop; ++it) {
			char const c = toupper(*it);
			switch(c) {
			case '[':
				hitspace = false;
				break;

			case ']':
				hitspace = false;
				endloop  = true;
				break;

			case ' ':
				hitspace = true;
				break;

			default:
				if(isalnum(c)) {
					if(hitspace) {
						endloop = true;
						failed  = true;

						if(errors) {
							*errors += "Unexpected character " +c;
						}
					}

					varname += c;
				}

				else if(c == '_') {
					if(hitspace) {
						endloop = true;
						failed  = true;

						if(errors) {
							*errors += "Unexpected character " +c;
						}
					}

                                        varname += c;


				}

				break;

			}

		}
	}


public:
	Config() {}
	~Config(void){
		clear();
	}

	template<typename T>
	T getVar(std::string const &varname) {
		std::string var(varname);	//Convert to uppercase
		std::transform(var.begin(), var.end(), var.begin(), toupper);
		

		if(vars.find(var) == vars.end()) {
			throw ConfigException(varname + " does not exist", 
				                  ConfigException::VARIABLE_DOES_NOT_EXIST
								 );
		}

		ConfigType<T>* p =  reinterpret_cast< ConfigType<T>* >(vars[var]);
		return p->get();
	}

	template<typename T>
	void setVar(std::string const&varname, T const& v) {
		std::string var(varname);	//Convert to uppercase
		std::transform(var.begin(), var.end(), var.begin(), toupper);

		
		if(vars.find(varname) == vars.end() ) {
			vars[varname] = new ConfigType<T>(v);			
		} 

		else {
			ConfigType<T>* p =  reinterpret_cast< ConfigType<T>* >(vars[var]);
			p->set(v);
		}
	}

	template<typename T>
	void addVar(std::string const& varname, T const& v) {
		if(vars.find(varname) != vars.end() ) {
			delete vars[varname];
		}

		vars[varname] = new ConfigType<T>(v);
	}

	void delVar(std::string const& varname) {
		if(vars.find(varname) != vars.end() ) {
			delete vars[varname];
		}

		vars.erase(varname);
	}





	void parseFile(std::string const& filename) {
		std::fstream file(filename, std::ios::in);
		if(!file.is_open() ) {
			throw ConfigException("Failed to open " + curFile, ConfigException::FAILED_TO_OPEN_FILE);
		}

		std::string data( (std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>() ); //Get the file data
		std::string line, errors;
	
		for(std::string::iterator it = data.begin(); it != data.end(); it++) {
			switch(*it) {
			case ';':
				parseLine(line, errors);
				line.clear();
				break;

			case '\n':	//Ignore it
				break;

			default:
				line += *it;
				break;

			}

		}

		curFile = filename;
	}

	

	void saveFile() {
		std::ofstream file(curFile, std::ios::out);		
		file.clear();

		//Is file open
		if(!file.is_open()) {
			throw ConfigException("Failed to open " + curFile, ConfigException::FAILED_TO_OPEN_FILE);
		}		

		for(std::map< std::string, BaseConfigType* >::iterator it = vars.begin(); it != vars.end(); it++) {
			file <<"[" << it->first <<"] = ";
			it->second->writeVar(file);
			file << ";" << std::endl;
		}

		file.close();
	}



	void saveFile(std::string const& filename) {
		std::ofstream file(filename, std::ios::out);		
		file.clear();

		//Is file open
		if(!file.is_open()) {
			throw ConfigException("Failed to open " + curFile, ConfigException::FAILED_TO_OPEN_FILE);
		}		

		for(std::map< std::string, BaseConfigType* >::iterator it = vars.begin(); it != vars.end(); it++) {
			file <<"[" << it->first <<"] = ";
			it->second->writeVar(file);
			file << ";" << std::endl;
		}

		file.close();
	}

	void clear() {
		for(std::map< std::string, BaseConfigType* >::iterator it = vars.begin(); it != vars.end(); it++) {
			delete it->second;
		}
		vars.clear();
	}
};

#endif
Example config file - config.ini

Code: Select all

[WIDTH] = 1024;
[HEIGHT] = 768;
[FULLSCREEN] = false;
[FPSLIMIT] = 60.0;
Some example code.

Code: Select all

//Example
#include <iostream>

int main(int argc, char** argv) {
    Config config;
    config.parseFile("config.ini");
 
    try {
        int lc = config.getVar<int>("LOOPCOUNT");         
        std::string msg( config.getVar<std::string>("MESSAGE") );

    } catch ( ConfigException const& e) {
          std::cout<< e.what()<<" "<<e.code()<<std::endl;
    }

    for(int i = 0; i < lc; i++) {
        std::cout<<Message<<std::endl;
    }


    return 0;
}

Code: Select all

[LOOPCOUNT] = 5;
[MESSAGE] = "Hello World";
If anyone does have any concerns about using it, I don't have any issues with you using it. Though your tutors/lecturers (if any) might think otherwise..
Feel free to leave a comment/feedback. I hope someone finds it useful.
Post Reply