Hi all.
I've developed the INI parser like I said, with the good news that I just finished debugging and testing (not 100% the INI standard, but ini'ish :))
If you're interested and/or have any thoughts, below the source code.
It's not 100% bulletproof, but this is what it does for sure:
- both reading and parsing INI files as updating a specific attribute
- works with blocks and attributes, a block starts with [ ], followed by lines with attributes
- supports multiple blocks with the same name and same attributes, for example for ShaderPacks (see example below)
- it's lightweight and only needs std::string
Thanks for your thoughts on this topic and till next time.
Header:
/*** INI parser, Cristiaan Kop updated 25-10-2016 ***/
/*** ============================================================================== ***/
/*** NAMESPACE: Crealysm::COMMON ***/
/*** header file for the CIniParser class interface ***/
#ifndef CINI_PARSER_H
#define CINI_PARSER_H
#include <string>
#include <fstream>
#include <vector>
namespace Crealysm { namespace COMMON
{
/**************************************************************************************/
/*** CIniParser class ***/
/*** class for reading/updating INI files and retrieving data from them: parsing ***/
/**************************************************************************************/
class CIniParser
{
public:
CIniParser();
~CIniParser();
/***** START AND STOP READING *****/
bool StartReading(const std::string &pFilename);
bool StopReading();
/***** SET THE CURRENT FILENAME FOR WRITING *****/
void SetWriteFile(const std::string &pFilename);
// Reading: moves filestream to a requested or the next block
bool GetNextBlockType(uint *pNrAttributes, std::string *pBlockName);
bool GotoBlockByName(const std::string &pBlockName, const uint pBlockId, uint *pNrAttributes);
// Reading: retrieve next attribute's name
std::string GetNextAttributeName();
// Reading: retrieve attribute's value
int GetInt();
bool GetBool();
std::string GetString();
/***** WRITING *****/
bool UpdateInt(const std::string &pBlockName, const uint pBlockId, const std::string &pAttrib, const int pIntVal);
bool UpdateBool(const std::string &pBlockname, const uint pBlockId, const std::string &pAttrib, const bool pBoolVal);
bool UpdateString(const std::string &pBlockName, const uint pBlockId, const std::string &pAttrib, const std::string pStringVal);
private:
std::ifstream mReadIni;
std::ofstream mWriteIni;
std::vector<std::string> mTempReadLines;
std::string mWriteFile;
// Helper functions
bool GetBlockName(const std::string &pLine, std::string *pBlockName) const;
bool IsBlockLine(const std::string &pLine) const;
bool IsCommentLine(const std::string &pLine) const;
bool IsEmptyLine(const std::string &pLine) const;
int CountAttributes();
/***** WRITING *****/
bool PrepareUpdate(const std::string &pBlockName, const uint pBlockId, const std::string &pAttrib, uint *pStopLine);
bool FinishUpdate(const uint pContinueLine);
};
}
}
#endif
Implementation:
/*** INI parser, Cristiaan Kop updated 25-10-2016 ***/
/*** ============================================================================== ***/
/*** NAMESPACE: Crealysm::COMMON ***/
/*** cpp file with CIniPasser class implementation ***/
#include "ciniparser.h"
using namespace std;
namespace Crealysm { namespace COMMON
{
/**************************************************************************************/
/*** CONSTRUCTOR ***/
/*** ==> usage: when creating a CIniParser object ***/
/*** ==> sets all variables in the class object to initial/ passed parameter values ***/
/**************************************************************************************/
CIniParser::CIniParser()
{
// nothing yet
}
/**************************************************************************************/
/*** DESTRUCTOR ***/
/*** ==> usage: when CIniParser object is not needed anymore ***/
/*** ==> releases stuff, COM objects, memory etc. ***/
/**************************************************************************************/
CIniParser::~CIniParser()
{
if(mReadIni.is_open()) mReadIni.close();
if(mWriteIni.is_open()) mWriteIni.close();
}
/**************************************************************************************/
/*** START READING ***/
/*** ==> usage: to open the file stream for reading the INI file ***/
/*** ==> opens the fstream, based on input filename ***/
/**************************************************************************************/
bool CIniParser::StartReading(const string &pFilename)
{
if(mReadIni.is_open()) mReadIni.close();
mReadIni.open(pFilename);
if(!mReadIni) return false;
return true;
}
/**************************************************************************************/
/*** STOP READING ***/
/*** ==> usage: to close the file stream after reading the INI file ***/
/*** ==> closes the fstream, if open ***/
/**************************************************************************************/
bool CIniParser::StopReading()
{
if(mReadIni.is_open())
{
mReadIni.close();
return true;
}
return false;
}
/**************************************************************************************/
/*** SET WRITEFILE ***/
/*** ==> usage: to set the filename for writing ***/
/*** ==> only updates the member string with the writefile filename ***/
/**************************************************************************************/
void CIniParser::SetWriteFile(const string &pFilename)
{
mWriteFile = pFilename;
}
/**************************************************************************************/
/*** GET NEXT BLOCKTYPE ***/
/*** ==> usage: to retrieve the next blocktype and nr of attributes in the INI file ***/
/*** ==> returns the name and nr of attributes of the 1st following block ***/
/**************************************************************************************/
bool CIniParser::GetNextBlockType(uint *pNrAttributes, std::string *pBlockName)
{
if(!mReadIni.is_open()) return false;
// determine block type
string line = "";
while(!IsBlockLine(line)) // no comment or empty line
{
getline(mReadIni, line);
}
// valid block type found?
if(!GetBlockName(line, pBlockName)) return false;
// count the number of attributes
*pNrAttributes = CountAttributes();
return true;
}
/**************************************************************************************/
/*** GOTO BLOCK BY NAME ***/
/*** ==> usage: directly move stream to requested block, supports identical blocks ***/
/*** ==> moves the filestream if the block exists, otherwise keeps its position ***/
/**************************************************************************************/
bool CIniParser::GotoBlockByName(const std::string &pBlockName, const uint pBlockId, uint *pNrAttributes)
{
if(!mReadIni.is_open()) return false;
// store original position, return if requested block not found
int orgPos = (int)mReadIni.tellg();
// go to beginning of filestream
mReadIni.seekg(0, ios::beg);
string line;
int currBlockFound = -1;
while(!mReadIni.eof())
{
getline(mReadIni, line);
if(IsBlockLine(line))
{
string foundBlock;
GetBlockName(line, &foundBlock);
if(strcmp(foundBlock.c_str(), pBlockName.c_str()) == 0)
{
++currBlockFound;
if(currBlockFound == pBlockId)
{
*pNrAttributes = CountAttributes();
return true;
}
}
}
}
// restore stream position if not found
mReadIni.seekg(orgPos);
return false;
}
/**************************************************************************************/
/*** GET NEXT ATTRIBUTE NAME ***/
/*** ==> usage: to retrieve the label/ name of the next attribute ***/
/*** ==> returns a string with the attribute's name ***/
/**************************************************************************************/
string CIniParser::GetNextAttributeName()
{
if(!mReadIni.is_open()) return ""; // empty, file is not open
int currPos = 0;
string tempString;
while(true)
{
currPos = (int)mReadIni.tellg();
getline(mReadIni, tempString);
if(!IsCommentLine(tempString) && !IsEmptyLine(tempString) && !IsBlockLine(tempString)) break; // no comment or empty line
}
mReadIni.seekg(currPos); // reset position in filestream, before retrieving the value
string result, tseparator;
mReadIni >> result;
mReadIni >> tseparator; // skip the '='
return result;
}
/**************************************************************************************/
/*** GET INT ***/
/*** ==> usage: to retrieve the attribute´s value: INTEGER ***/
/*** ==> returns the read value from the filestream, as int ***/
/**************************************************************************************/
int CIniParser::GetInt()
{
if(!mReadIni.is_open()) return -1;
int result;
mReadIni >> result;
return result;
}
/**************************************************************************************/
/*** GET BOOL ***/
/*** ==> usage: to retrieve the attribute´s value: BOOLEAN ***/
/*** ==> returns the read value from the filestream, as bool ***/
/**************************************************************************************/
bool CIniParser::GetBool()
{
if(!mReadIni.is_open()) return false;
std::string tempResult;
mReadIni >> tempResult;
if(strcmp(tempResult.c_str(), "true") == 0) return true;
if(strcmp(tempResult.c_str(), "false") == 0) return false;
return false;
}
/**************************************************************************************/
/*** GET STRING ***/
/*** ==> usage: to retrieve the attribute´s value: STRING ***/
/*** ==> returns the read value from the filestream, as std::string ***/
/**************************************************************************************/
string CIniParser::GetString()
{
if(!mReadIni.is_open()) return "";
string result;
mReadIni >> result;
return result;
}
/**************************************************************************************/
/*** COUNT ATTRIBUTES ***/
/*** ==> usage: count number of attributes from current pos till next block ***/
/*** ==> stores stream pos, counts till next block and restores stream pos ***/
/**************************************************************************************/
int CIniParser::CountAttributes()
{
if(!mReadIni.is_open()) return -1;
string line;
bool nextBlock = false;
int nrAttributes = 0;
uint currPos = (int)mReadIni.tellg(); // store position in stream before counting
while(!nextBlock)
{
getline(mReadIni, line);
if(!IsEmptyLine(line) && !IsCommentLine(line)) // ignore empty lines and comments
{
if(IsBlockLine(line))
{
nextBlock = true;
break;
}
else ++nrAttributes;
}
}
// restore position in the file
mReadIni.seekg(currPos);
return nrAttributes;
}
/**************************************************************************************/
/*** UPDATE INT ***/
/*** ==> usage: to update a value of an attribute within a given block: INTEGER ***/
/*** ==> seeks the attribute's value in the stream and updates it ***/
/**************************************************************************************/
bool CIniParser::UpdateInt(const std::string &pBlockName, const uint pBlockId, const std::string &pAttrib, const int pIntVal)
{
uint attribLine = 0;
if(!PrepareUpdate(pBlockName, pBlockId, pAttrib, &attribLine)) return false;
mWriteIni << pAttrib << " = " << pIntVal << endl;
if(!FinishUpdate(attribLine+1)) return false;
return true;
}
/**************************************************************************************/
/*** UPDATE BOOL ***/
/*** ==> usage: to update a value of an attribute within a given block: BOOL ***/
/*** ==> seeks the attribute's value in the stream and updates it ***/
/**************************************************************************************/
bool CIniParser::UpdateBool(const std::string &pBlockName, const uint pBlockId, const std::string &pAttrib, const bool pBoolVal)
{
uint attribLine = 0;
if(!PrepareUpdate(pBlockName, pBlockId, pAttrib, &attribLine)) return false;
mWriteIni << pAttrib << " = ";
if(pBoolVal) mWriteIni << "true" << endl;
else mWriteIni << "false" << endl;
if(!FinishUpdate(attribLine+1)) return false;
return true;
}
/**************************************************************************************/
/*** UPDATE STRING ***/
/*** ==> usage: to update a value of an attribute within a given block: STRING ***/
/*** ==> seeks the attribute's value in the stream and updates it ***/
/**************************************************************************************/
bool CIniParser::UpdateString(const std::string &pBlockName, const uint pBlockId, const std::string &pAttrib, const std::string pStringVal)
{
uint attribLine = 0;
if(!PrepareUpdate(pBlockName, pBlockId, pAttrib, &attribLine)) return false;
mWriteIni << pAttrib << " = " << pStringVal << endl;
if(!FinishUpdate(attribLine+1)) return false;
return true;
}
/**************************************************************************************/
/*** PREPARE UPDATE ***/
/*** ==> usage: to prepare the updating of an attribute value ***/
/*** ==> stores the file in a vector of strings and seeks the to be updated attr ***/
/**************************************************************************************/
bool CIniParser::PrepareUpdate(const std::string &pBlockName, const uint pBlockId, const std::string &pAttrib, uint *pStopLine)
{
if(mWriteIni.is_open()) return false;
ifstream tempRead(mWriteFile);
if(!tempRead) return false;
// First store whole file as strings (per line)
mTempReadLines.resize(0);
while(!tempRead.eof())
{
string tempLine;
getline(tempRead, tempLine);
mTempReadLines.push_back(tempLine);
}
tempRead.close();
// Find the the correct block
int blockIdFound = -1;
int blockLine = 0;
for(uint bc=0;bc<mTempReadLines.size();++bc)
{
if(IsBlockLine(mTempReadLines[bc]))
{
string tblock;
if(!GetBlockName(mTempReadLines[bc], &tblock)) return false;
if(strcmp(tblock.c_str(), pBlockName.c_str()) == 0) ++blockIdFound;
}
if(blockIdFound == pBlockId)
{
blockLine = bc; // right block found
break;
}
}
// Find the correct attribute
int attribLine = 0;
for(uint lc=blockLine+1;lc<mTempReadLines.size();++lc)
{
if(IsBlockLine(mTempReadLines[lc])) break;
if(!IsCommentLine(mTempReadLines[lc]) && !IsEmptyLine(mTempReadLines[lc]))
{
if(mTempReadLines[lc].find(pAttrib.c_str()) != string::npos)
{
attribLine = lc; // attribute found
break;
}
}
}
// Found it at all?
if(blockLine == 0 || attribLine == 0) return false;
// Rewrite the file, including the changed attribute
mWriteIni.open(mWriteFile);
for(int w=0;w<attribLine;++w) mWriteIni << mTempReadLines[w] << endl;
*pStopLine = attribLine;
return true;
}
/**************************************************************************************/
/*** FINISH UPDATE ***/
/*** ==> usage: to finish the updating of an attribute value ***/
/*** ==> saves the file from after the updated attribute ***/
/**************************************************************************************/
bool CIniParser::FinishUpdate(const uint pContinueLine)
{
if(!mWriteIni.is_open()) return false;
for(uint w=pContinueLine;w<mTempReadLines.size();++w)
{
mWriteIni << mTempReadLines[w] << endl;
}
mWriteIni.close();
return true;
}
/**************************************************************************************/
/*** GET BLOCKNAME CONST ***/
/*** ==> usage: to retrieve the name of a block, from a blockline string ***/
/*** ==> returns the part of the string between [ ] ***/
/**************************************************************************************/
bool CIniParser::GetBlockName(const std::string &pLine, std::string *pBlockName) const
{
if(!IsBlockLine(pLine)) return false;
*pBlockName = pLine.substr(1, pLine.length()-2);
return true;
}
/**************************************************************************************/
/*** IS BLOCKLINE CONST ***/
/*** ==> usage: to check if a given line is defined as a 'block' definition ***/
/*** ==> returns true if it's a block line (starts and ends with [ ]) ***/
/**************************************************************************************/
bool CIniParser::IsBlockLine(const string &pLine) const
{
if(IsEmptyLine(pLine)) return false;
if(pLine.at(0) == '[' && pLine.at(pLine.length()-1) == ']') return true;
return false;
}
/**************************************************************************************/
/*** IS COMMENTLINE CONST ***/
/*** ==> usage: to check if a given line is a comment line (starts with ; ) ***/
/*** ==> returns true if it's a comment line ***/
/**************************************************************************************/
bool CIniParser::IsCommentLine(const string &pLine) const
{
if(pLine.length() < 1) return false;
if(pLine.at(0) == ';') return true;
return false;
}
/**************************************************************************************/
/*** IS EMPTYLINE CONST ***/
/*** ==> usage: to check if a given line is a empty line ***/
/*** ==> returns true if the string length's 0 ***/
/**************************************************************************************/
bool CIniParser::IsEmptyLine(const string &pLine) const
{
if(pLine.length() < 1) return true;
return false;
}
}
}
Tests:
// TESTING
int twidth = 0;
int theight = 0;
bool twindowed = false;
int tmsaa = 8;
bool tvsync = false;
bool taniso = false;
int tanisolevel = -1;
Crealysm::COMMON::CIniParser myParser;
/* READ TEST 1
std::string blockName;
uint nrAttrib = 0;
myParser.StartReading("data/settings.ini");
if(!myParser.GetNextBlockType(&nrAttrib, &blockName)) return false;
if(strcmp(blockName.c_str(), "d3dsettings") == 0)
{
// read attributes
}
*/
// READ TEST 2
myParser.StartReading("data/settings.ini");
uint nrAttrib = 0;
if(!myParser.GotoBlockByName("d3dsettings", 1, &nrAttrib)) return false;
for(uint attr=0;attr<nrAttrib;++attr)
{
std::string attribute = myParser.GetNextAttributeName();
if(attribute == "screen_width") twidth = myParser.GetInt();
if(attribute == "screen_height") theight = myParser.GetInt();
if(attribute == "windowed") twindowed = myParser.GetBool();
if(attribute == "nr_msaa") tmsaa = myParser.GetInt();
if(attribute == "v_sync") tvsync = myParser.GetBool();
if(attribute == "anisotropic") taniso = myParser.GetBool();
if(attribute == "anisotropic_level") tanisolevel = myParser.GetInt();
}
myParser.StopReading();
std::ofstream debugger("results.txt");
debugger << "width: " << twidth << std::endl;
debugger << "height: " << theight << std::endl;
debugger << "windowed: " << twindowed << std::endl;
debugger << "msaa: " << tmsaa << std::endl;
debugger << "vsync: " << tvsync << std::endl;
debugger << "aniso: " << taniso << std::endl;
debugger << "anisolevel: " << tanisolevel << std::endl;
debugger.close();
// WRITE TEST
//myParser.SetWriteFile("data/settings.ini");
//if(!myParser.UpdateInt("d3dsettings", 0, "msaa", 4)) return false;
//if(!myParser.UpdateBool("d3dsettings", 1, "windowed", true)) return false;
//if(!myParser.UpdateString("newblock", 0, "myname", "earl")) return false;
Tested INI files:
[the_other_block]
attrib1 = 230
attrib3 = false
[blocktesting]
; comments
[d3dsettings]
; d3d11 settings for the Crealysm11 3d engine
screen_width = 1280
screen_height = 720
windowed = true
msaa = 4
; more comments to mess up stuff
v_sync = true
anisotropic = true
anisotropic_level = 4
; even more comment
; ini file format for trying out
[newblock]
myname = cris
[multi:shaderpacks]
[ShaderPack]
vs_filename = VS_PS_basic.hlsl
vs_entrypoint = VS_main
ps_filename = VS_PS_basic.hlsl
ps_entrypoint = PS_main
gs_filename = no
gs_entrypoint = no
defines = MAX_PLIGHT_8;NORMALMAP
identifier = 8PTLIGHT-NORMALMAP
[ShaderPack]
vs_filename = VS_PS_basic.hlsl
vs_entrypoint = VS_main
ps_filename = VS_PS_basic.hlsl
ps_entrypoint = PS_main
gs_filename = no
gs_entrypoint = no
defines = MAX_PLIGHT_4
identifier = 4PTLIGHT
[ShaderPack]
vs_filename = VS_thingie.hlsl
vs_entrypoint = VS_main
ps_filename = no
ps_entrypoint = no
gs_filename = no
gs_entrypoint = no
defines = no
identifier = SPECIAL-VS-ONLY
[ShaderPack]
vs_filename = VSGS_special.hlsl
vs_entrypoint = VS_main
ps_filename = no
ps_entrypoint = no
gs_filename = VSGS_special.hlsl
gs_entrypoint = GS_dostuff
defines = no
identifier = SPECIAL-VSGS