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);
}