Reading XML Config files with C++ and TinyXML in Linux

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

Leave a Reply

Your email address will not be published. Required fields are marked *