Moving from plain "ascii" to XML?

Started by
17 comments, last by cozzie 7 years, 6 months ago

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

Crealysm game & engine development: http://www.crealysm.com

Looking for a passionate, disciplined and structured producer? PM me

This topic is closed to new replies.

Advertisement