Ini Parser
Posted: Mon Jan 10, 2011 4:18 pm
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:
Example config file - config.ini
Some example code.
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.
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.
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
Code: Select all
[WIDTH] = 1024;
[HEIGHT] = 768;
[FULLSCREEN] = false;
[FPSLIMIT] = 60.0;
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";
Feel free to leave a comment/feedback. I hope someone finds it useful.