Last active
November 16, 2022 16:00
-
-
Save thennequin/c604c8caa38048a1fcdc to your computer and use it in GitHub Desktop.
Ini Config Reader/Writer
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Ini::IniConfig& m_oIniConfig; | |
Ini::CategoryValueMap& mCategories = m_oIniConfig.GetCategories(); | |
if (ImGui::Button("Reload")) | |
{ | |
m_oIniConfig.Load("Config.ini", true, false); | |
} | |
ImGui::SameLine(); | |
if (ImGui::Button("Save")) | |
{ | |
m_oIniConfig.Save(); | |
} | |
if (ImGui::TreeNode("IniConfig")) | |
{ | |
for (Ini::CategoryValueMap::iterator itCat = mCategories.begin(); itCat != mCategories.end(); ++itCat) | |
{ | |
if (ImGui::TreeNode(itCat->first.c_str())) | |
{ | |
for (Ini::Values::iterator itVal = itCat->second.begin(); itVal != itCat->second.end(); ++itVal) | |
{ | |
if (itVal->m_bIsComment) | |
{ | |
ImGui::TextColored(ImVec4(0.f,0.7f,0.f,1.f), "%s", itVal->m_sName.c_str()); | |
} | |
else | |
{ | |
ImGui::Text( "%s = %s", itVal->m_sName.c_str(), itVal->m_sValue.c_str()); | |
} | |
} | |
ImGui::TreePop(); | |
} | |
} | |
ImGui::TreePop(); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include "IniConfig.h" | |
namespace Ini | |
{ | |
////////////////////////////////////////// | |
// Value | |
////////////////////////////////////////// | |
Value::Value(const String& sName, const String& sValue, bool bIsComment) | |
{ | |
m_sName = sName; | |
m_sValue = sValue; | |
m_bIsComment = bIsComment; | |
} | |
Value::Value(const Value& oCopy) | |
{ | |
m_sName = oCopy.m_sName; | |
m_sValue = oCopy.m_sValue; | |
m_bIsComment = oCopy.m_bIsComment; | |
} | |
////////////////////////////////////////// | |
// IniConfig | |
////////////////////////////////////////// | |
IniConfig::IniConfig() | |
{ | |
m_bHasChanges = false; | |
} | |
IniConfig::~IniConfig() | |
{ | |
} | |
int IniConfig::Load(const String& sFilename, bool bKeepComment, bool bKeepExisting) | |
{ | |
if (!sFilename.empty()) | |
{ | |
FILE* pFile = fopen(sFilename.c_str(), "r"); | |
if (NULL != pFile) | |
{ | |
EReadState eState = E_READ_STATE_NONE; | |
char cCurrentChar; | |
char cPreviousChar = ' '; | |
String sCurrentCategory; | |
String sCurrentName; | |
String sCurrentValue; | |
CategoryValueMap mCategories; | |
int iCurrentLine = 1; | |
bool bAddValue = false; | |
if (bKeepExisting) | |
{ | |
mCategories = m_mCategories; | |
} | |
size_t iReadSize; | |
while (true) | |
{ | |
iReadSize = fread(&cCurrentChar, sizeof(char), 1, pFile); | |
if (iReadSize > 0) | |
{ | |
if (cCurrentChar == '\r') | |
{ | |
continue; | |
} | |
if (cCurrentChar == '\n') | |
{ | |
++iCurrentLine; | |
} | |
switch (eState) | |
{ | |
case E_READ_STATE_NONE: | |
switch (cCurrentChar) | |
{ | |
case '#': | |
sCurrentName = ""; | |
eState = E_READ_STATE_COMMENT; | |
break; | |
case '[': | |
sCurrentCategory = ""; | |
eState = E_READ_STATE_CATEGORY; | |
break; | |
case ' ': | |
case '\t': | |
case '\r': | |
case '\n': | |
//Ignore char | |
break; | |
default: | |
eState = E_READ_STATE_PRE_NAME; | |
break; | |
} | |
break; | |
case E_READ_STATE_CATEGORY: | |
switch (cCurrentChar) | |
{ | |
case ']': | |
Trim(sCurrentCategory); | |
eState = E_READ_STATE_POST_CATEGORY; | |
break; | |
default: | |
sCurrentCategory += cCurrentChar; | |
break; | |
} | |
break; | |
case E_READ_STATE_POST_CATEGORY: | |
switch (cCurrentChar) | |
{ | |
case '\n': | |
eState = E_READ_STATE_NONE; | |
break; | |
case ' ': | |
case '\t': | |
//Ignore | |
break; | |
default: // Invalid Char | |
fclose(pFile); | |
return iCurrentLine; | |
} | |
break; | |
case E_READ_STATE_COMMENT: | |
switch (cCurrentChar) | |
{ | |
case '\n': | |
mCategories[sCurrentCategory].push_back(Value(sCurrentName, sCurrentValue, true)); | |
eState = E_READ_STATE_NONE; | |
break; | |
default: | |
sCurrentName += cCurrentChar; | |
break; | |
} | |
break; | |
case E_READ_STATE_PRE_NAME: | |
switch (cCurrentChar) | |
{ | |
case '\r': | |
case '\n': | |
eState = E_READ_STATE_NONE; | |
break; | |
case ' ': | |
case '\t': | |
//Ignore | |
break; | |
default: | |
eState = E_READ_STATE_NAME; | |
sCurrentName = ""; | |
sCurrentName += cPreviousChar; | |
sCurrentName += cCurrentChar; | |
break; | |
} | |
break; | |
case E_READ_STATE_NAME: | |
switch (cCurrentChar) | |
{ | |
case '\r': | |
case '\n': | |
eState = E_READ_STATE_NONE; | |
break; | |
case '=': | |
eState = E_READ_STATE_VALUE; | |
Trim(sCurrentName); | |
sCurrentValue = ""; | |
if (sCurrentName.empty()) | |
{ | |
fclose(pFile); | |
return iCurrentLine; | |
} | |
break; | |
default: | |
sCurrentName += cCurrentChar; | |
break; | |
} | |
break; | |
case E_READ_STATE_VALUE: | |
switch (cCurrentChar) | |
{ | |
default: | |
sCurrentValue += cCurrentChar; | |
break; | |
case '\r': | |
case '\n': | |
Trim(sCurrentValue); | |
bAddValue = true; | |
eState = E_READ_STATE_NONE; | |
break; | |
} | |
break; | |
} | |
cPreviousChar = cCurrentChar; | |
} | |
if (bAddValue || (iReadSize == 0 && !sCurrentName.empty())) | |
{ | |
bAddValue = false; | |
//Error if value name already exist | |
for (Values::const_iterator itValue = mCategories[sCurrentCategory].begin(); itValue != mCategories[sCurrentCategory].end(); ++itValue) | |
{ | |
if (!itValue->m_bIsComment && itValue->m_sName == sCurrentName) | |
{ | |
fclose(pFile); | |
return iCurrentLine; | |
} | |
} | |
mCategories[sCurrentCategory].push_back(Value(sCurrentName, sCurrentValue, eState == E_READ_STATE_COMMENT)); | |
sCurrentName = ""; | |
sCurrentValue = ""; | |
} | |
if (iReadSize == 0) | |
{ | |
break; | |
} | |
} | |
m_mCategories = mCategories; | |
m_sLastFilename = sFilename; | |
fclose(pFile); | |
return 0; | |
} | |
} | |
return -1; | |
} | |
bool IniConfig::Save(const String& sFilename, const String& sEOL ) | |
{ | |
FILE* pFile = NULL; | |
if (!sFilename.empty()) | |
{ | |
pFile = fopen(sFilename.c_str(), "w"); | |
} | |
else if (!m_sLastFilename.empty()) | |
{ | |
pFile = fopen(m_sLastFilename.c_str(), "w"); | |
} | |
if (NULL != pFile) | |
{ | |
for (CategoryValueMap::const_iterator it = m_mCategories.begin(); it != m_mCategories.end(); ++it) | |
{ | |
fprintf(pFile, "\n[%s]\n", it->first.c_str()); | |
for (Values::const_iterator itValue = it->second.begin(); itValue != it->second.end(); ++itValue) | |
{ | |
if (itValue->m_bIsComment) | |
{ | |
fprintf(pFile, "#%s\n", itValue->m_sName.c_str()); | |
} | |
else | |
{ | |
fprintf(pFile, "%s=%s\n", itValue->m_sName.c_str(), itValue->m_sValue.c_str()); | |
} | |
} | |
} | |
fclose(pFile); | |
return true; | |
} | |
return false; | |
} | |
bool IniConfig::HasChanges() | |
{ | |
return m_bHasChanges; | |
} | |
bool IniConfig::HasCategory(const String& sCategory) const | |
{ | |
return (m_mCategories.find(sCategory) != m_mCategories.end()); | |
} | |
bool IniConfig::HasValue(const String& sCategory, const String& sName) const | |
{ | |
CategoryValueMap::const_iterator itCategory = m_mCategories.find(sCategory); | |
if (itCategory != m_mCategories.end()) | |
{ | |
for (Values::const_iterator itValue = itCategory->second.begin(); itValue != itCategory->second.end(); ++itValue) | |
{ | |
if (!itValue->m_bIsComment && itValue->m_sValue == sName) | |
{ | |
return true; | |
} | |
} | |
} | |
return false; | |
} | |
CategoryValueMap& IniConfig::GetCategories() | |
{ | |
return m_mCategories; | |
} | |
String IniConfig::ReadStringValue(const String& sCategory, const String& sName, const String& sDefaultValue, bool bWriteDefaultValue) | |
{ | |
Values::const_iterator itValue; | |
if (GetValueConst(sCategory, sName, itValue)) | |
{ | |
return itValue->m_sValue; | |
} | |
if (bWriteDefaultValue) | |
{ | |
m_mCategories[sCategory].push_back(Value(sName, sDefaultValue, false)); | |
} | |
return sDefaultValue; | |
} | |
/*int IniConfig::ReadIntValue(const String& sCategory, const String& sName, int iDefault, bool bWriteDefaultValue) | |
{ | |
Values::const_iterator itValue; | |
if (GetValueConst(sCategory, sName, itValue)) | |
{ | |
//return itValue->m_sValue; | |
} | |
if (bWriteDefaultValue) | |
{ | |
//m_mCategories[sCategory].push_back(Value(sName, sDefaultValue, false)); | |
} | |
return iDefault; | |
} | |
float IniConfig::ReadFloatValue(const String& sCategory, const String& sName, float fDefault, bool bWriteDefaultValue) | |
{ | |
Values::const_iterator itValue; | |
if (GetValueConst(sCategory, sName, itValue)) | |
{ | |
//std::stringfor | |
//return itValue->m_sValue; | |
} | |
if (bWriteDefaultValue) | |
{ | |
//m_mCategories[sCategory].push_back(Value(sName, sDefaultValue, false)); | |
} | |
return fDefault; | |
}*/ | |
void IniConfig::WriteStringValue(const String& sCategory, const String& sName, const String& sValue) | |
{ | |
Values::iterator itValue; | |
if (GetValue(sCategory, sName, itValue)) | |
{ | |
itValue->m_sValue = sValue; | |
} | |
else | |
{ | |
m_mCategories[sCategory].push_back(Value(sName, sValue)); | |
} | |
} | |
/*void IniConfig::WriteIntValue(const String& sCategory, const String& sName, int iValue) | |
{ | |
//TODO | |
} | |
void IniConfig::WriteFloatValue(const String& sCategory, const String& sName, float fValue) | |
{ | |
//TODO | |
}*/ | |
bool IniConfig::GetValueConst(const String& sCategory, const String& sName, Values::const_iterator& itOutValue) const | |
{ | |
CategoryValueMap::const_iterator itCategory = m_mCategories.find(sCategory); | |
if (itCategory != m_mCategories.end()) | |
{ | |
for (Values::const_iterator itValue = itCategory->second.begin(); itValue != itCategory->second.end(); ++itValue) | |
{ | |
if (!itValue->m_bIsComment && itValue->m_sName == sName) | |
{ | |
itOutValue = itValue; | |
return true; | |
} | |
} | |
} | |
return false; | |
} | |
bool IniConfig::GetValue(const String& sCategory, const String& sName, Values::iterator& itOutValue) | |
{ | |
CategoryValueMap::iterator itCategory = m_mCategories.find(sCategory); | |
if (itCategory != m_mCategories.end()) | |
{ | |
for (Values::iterator itValue = itCategory->second.begin(); itValue != itCategory->second.end(); ++itValue) | |
{ | |
if (!itValue->m_bIsComment && itValue->m_sName == sName) | |
{ | |
itOutValue = itValue; | |
return true; | |
} | |
} | |
} | |
return false; | |
} | |
void IniConfig::Trim(String& sToTrim, char cToRemove) | |
{ | |
//Trim Right | |
int iNewLen = sToTrim.length(); | |
while (iNewLen > 0) | |
{ | |
if (sToTrim[iNewLen - 1] == cToRemove) | |
{ | |
--iNewLen; | |
} | |
else | |
{ | |
break; | |
} | |
} | |
//TrimLeft | |
int iStart = 0; | |
while (iStart < iNewLen) | |
{ | |
if (sToTrim[iStart] == cToRemove) | |
{ | |
++iStart; | |
} | |
else | |
{ | |
break; | |
} | |
} | |
sToTrim = sToTrim.substr(iStart, iNewLen - iStart); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#ifndef _INI_READER_H_ | |
#define _INI_READER_H_ | |
#include <vector> | |
#include <string> | |
#include <map> | |
namespace Ini | |
{ | |
using String = std::string; | |
struct Value | |
{ | |
public: | |
Value(const String& sName = "", const String& sValue = "", bool bIsComment = false); //{ Name = name; Value = value; IsCom = Com; } | |
Value(const Value& oCopy); | |
String m_sName; | |
String m_sValue; | |
bool m_bIsComment; | |
}; | |
typedef std::vector<Value> Values; | |
typedef std::map<String, Values> CategoryValueMap; | |
class IniConfig | |
{ | |
private: | |
enum EReadState | |
{ | |
E_READ_STATE_NONE, | |
E_READ_STATE_CATEGORY, | |
E_READ_STATE_POST_CATEGORY, | |
E_READ_STATE_COMMENT, | |
E_READ_STATE_PRE_NAME, | |
E_READ_STATE_NAME, | |
E_READ_STATE_VALUE, | |
E_READ_STATE_MAX | |
}; | |
public: | |
IniConfig(); | |
~IniConfig(); | |
/* Return value | |
0 = Ok | |
-1 = Can't open the file | |
>=1 = Error found on this line | |
*/ | |
int Load(const String& sFilename, bool bKeepComment = true, bool bKeepExisting = false); | |
bool Save(const String& sFilename = "", const String& sEOL = "\n"); | |
bool HasChanges(); | |
bool HasCategory(const String& sCategory) const; | |
bool HasValue(const String& sCategory, const String& sName) const; | |
CategoryValueMap& GetCategories(); | |
String ReadStringValue(const String& sCategory, const String& sName, const String& sDefaultValue = "", bool bWriteDefaultValue = true); | |
//int ReadIntValue(const String& sCategory, const String& sName, int iDefault = 0, bool bWriteDefaultValue = true); | |
//float ReadFloatValue(const String& sCategory, const String& sName, float fDefault = 0, bool bWriteDefaultValue = true); | |
void WriteStringValue(const String& sCategory, const String& sName, const String& sValue); | |
//void WriteIntValue(const String& sCategory, const String& sName, int iValue); | |
//void WriteFloatValue(const String& sCategory, const String& sName, float fValue); | |
protected: | |
bool GetValueConst(const String& sCategory, const String& sName, Values::const_iterator& itOutValue) const; | |
bool GetValue(const String& sCategory, const String& sName, Values::iterator& itOutValue); | |
void Trim(String& sToTrim, char cToRemove = ' '); | |
protected: | |
bool m_bHasChanges; | |
String m_sLastFilename; | |
CategoryValueMap m_mCategories; | |
}; | |
}; | |
#endif //_INI_READER_H_ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Oh sorry for the very late response, I didn't see your comment.
m_oIniConfig is here for the exemple, it should not compile because it's an undefined reference.
It's to the user to fill it with a static or something else.
Ini file is composed by Categories and Keys/Values.
The ImGuiIniDisplay juste allow to display the categories and their keys/values in TreeNodes.
If you want a hierarchy, Ini system is not relevant.
Prefer a system like Json, XML or YAML.