I’m currently working on a C++ FOSS project that requires to read configuration settings from file. In .NET this is pretty straightforward to do via ConfigurationManager class, so I had to write my own using TinyXML Source code as shown below:
Config.xml
<?xml version="1.0" encoding="utf-8"?> <config> <connectionStrings> <connectionString server="localhost" userName="myDevUser" password="p@ssw0rd" isEnabled="true"/> </connectionStrings> <commands> <command name="CreateDB" statement="CREATE DATABASE nixMetaFS"/> <command name="DropMetadataTable" statement="DROP TABLE IF EXISTS Metadata"/> <command name="CreateMetadataTable" statement="CREATE TABLE Metadata (Pk INT AUTO_INCREMENT PRIMARY KEY, fileId INT, fileName VARCHAR(255), filePath VARCHAR(2048), description VARCHAR(1024), comments VARCHAR(1024))"/> </commands> <appSettings> <add key="Somekey" value="Somevalue"/> </appSettings> </config>
Setting.h
#include "../Common.h" #include "../models/AppSetting.h" #include "../models/Command.h" #include "../models/ConnectionString.h" #include "tinyxml.h" using namespace nixMetaFS::Models; namespace nixMetaFS { namespace Core { template<class T> class Setting { public: vector<T> RehydrateFromXml(TiXmlHandle &hRoot, string elementName) { throw "Not implemented function"; } }; template<> class Setting<AppSetting> { public: vector<AppSetting> RehydrateFromXml(TiXmlHandle &hRoot, string elementName) { vector<AppSetting> retval; auto pElem = hRoot.FirstChild(elementName).FirstChild().Element(); if (pElem) { for (pElem; pElem; pElem = pElem->NextSiblingElement()) { retval.push_back(AppSetting(pElem->Attribute("key"), pElem->Attribute("value"))); } } return retval; } }; template<> class Setting<ConnectionString> { public: vector<ConnectionString> RehydrateFromXml(TiXmlHandle &hRoot, string elementName) { vector<ConnectionString> retval; auto pElem = hRoot.FirstChild(elementName).FirstChild().Element(); if (pElem) { for (pElem; pElem; pElem = pElem->NextSiblingElement()) { string disableStr(pElem->Attribute("isEnabled")); std::transform(disableStr.begin(), disableStr.end(), disableStr.begin(), ::tolower); auto isDisabled = disableStr.compare("true") == 0; ConnectionString connStr(pElem->Attribute("server"), pElem->Attribute("userName"), pElem->Attribute("password"), isDisabled); retval.push_back(connStr); } } return retval; } }; template<> class Setting<Command> { public: vector<Command> RehydrateFromXml(TiXmlHandle &hRoot, string elementName) { vector<Command> retval; auto pElem = hRoot.FirstChild(elementName).FirstChild().Element(); if (pElem) { for (pElem; pElem; pElem = pElem->NextSiblingElement()) { retval.push_back(Command(pElem->Attribute("name"), pElem->Attribute("statement"))); } } return retval; } }; } }
ConfigReader.h
#include "../Common.h" #include "../models/Config.h" #include "tinyxml.h" using namespace nixMetaFS::Models; namespace nixMetaFS { namespace Core { class ConfigReader { private: string m_filePath; bool Initialize(); static ConfigReader *m_Self; Config &m_Config = const_cast<Config &>(Config::Current_get()); public: ConfigReader(string filePath); }; } }
ConfigReader.cpp
#include "../core/Setting.h" #include "ConfigReader.h" using namespace nixMetaFS::Core; namespace fs = std::experimental::filesystem; ConfigReader *ConfigReader::m_Self; ConfigReader::ConfigReader(string filePath) { if (m_Self == nullptr) { m_filePath = filePath; m_Self = this; auto result = std::async(std::launch::async, &ConfigReader::Initialize, this); if (!result.get()) throw "Unable to load and parse config file."; } } bool ConfigReader::Initialize() { auto retval = false; auto configFile = m_filePath.substr(0, m_filePath.find_last_of("\/") + 1); configFile = configFile.append(ConfigFile); if (fs::exists(fs::path(configFile))) { try { TiXmlDocument config(configFile); if (config.LoadFile()) { TiXmlElement *pElem; Setting<Command> cmd; TiXmlHandle hDoc(&config); Setting<AppSetting> appSettings; Setting<ConnectionString> connectionStrings; pElem = hDoc.FirstChildElement().Element(); if (pElem) { auto hRoot = TiXmlHandle(pElem); std::async(std::launch::async, [&] { m_Config.Commands_set(cmd.RehydrateFromXml(hRoot, "commands")); }); std::async(std::launch::async, [&] { m_Config.AppSettings_set(appSettings.RehydrateFromXml(hRoot, "appSettings")); }); std::async(std::launch::async, [&] { m_Config.ConnectionStrings_set(connectionStrings.RehydrateFromXml(hRoot, "connectionStrings")); }); retval = true; } } } catch (std::exception &e) { } } return retval; }
The elements in XML file are treated as models that are read from disk and stored in ConfigReader. Their implementation is similar to the following ones
Command.h
#include "../Common.h" namespace nixMetaFS { namespace Models { class Command { private: string m_name; string m_statement; public: Command(); Command(const char *name, const char *statement); const string &Name_get(); const string &Statement_get(); void Name_set(string name); void Statement_set(string statement); }; } }
Command.cpp
#include "Command.h" using namespace nixMetaFS::Models; const string &Command::Name_get() { return m_name; } const string &Command::Statement_get() { return m_statement; } void Command::Name_set(string name) { m_name = name; } void Command::Statement_set(string statement) { m_statement = statement; } Command::Command() { } Command::Command(const char *name, const char *statement) : Command() { m_name.append(name); m_statement.append(statement); }