diff --git a/minipp.sln b/minipp.sln
new file mode 100644
index 0000000..ab22f0c
--- /dev/null
+++ b/minipp.sln
@@ -0,0 +1,28 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.12.35707.178 d17.12
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "minipp", "minipp\minipp.vcxproj", "{F0C52CC0-DB78-4E1A-982F-EE565764C5AF}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|x64 = Debug|x64
+ Debug|x86 = Debug|x86
+ Release|x64 = Release|x64
+ Release|x86 = Release|x86
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {F0C52CC0-DB78-4E1A-982F-EE565764C5AF}.Debug|x64.ActiveCfg = Debug|x64
+ {F0C52CC0-DB78-4E1A-982F-EE565764C5AF}.Debug|x64.Build.0 = Debug|x64
+ {F0C52CC0-DB78-4E1A-982F-EE565764C5AF}.Debug|x86.ActiveCfg = Debug|Win32
+ {F0C52CC0-DB78-4E1A-982F-EE565764C5AF}.Debug|x86.Build.0 = Debug|Win32
+ {F0C52CC0-DB78-4E1A-982F-EE565764C5AF}.Release|x64.ActiveCfg = Release|x64
+ {F0C52CC0-DB78-4E1A-982F-EE565764C5AF}.Release|x64.Build.0 = Release|x64
+ {F0C52CC0-DB78-4E1A-982F-EE565764C5AF}.Release|x86.ActiveCfg = Release|Win32
+ {F0C52CC0-DB78-4E1A-982F-EE565764C5AF}.Release|x86.Build.0 = Release|Win32
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+EndGlobal
diff --git a/minipp/minipp.vcxproj b/minipp/minipp.vcxproj
new file mode 100644
index 0000000..88359dd
--- /dev/null
+++ b/minipp/minipp.vcxproj
@@ -0,0 +1,139 @@
+
+
+
+
+ Debug
+ Win32
+
+
+ Release
+ Win32
+
+
+ Debug
+ x64
+
+
+ Release
+ x64
+
+
+
+ 17.0
+ Win32Proj
+ {f0c52cc0-db78-4e1a-982f-ee565764c5af}
+ minipp
+ 10.0
+
+
+
+ Application
+ true
+ v143
+ Unicode
+
+
+ Application
+ false
+ v143
+ true
+ Unicode
+
+
+ Application
+ true
+ v143
+ Unicode
+
+
+ Application
+ false
+ v143
+ true
+ Unicode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Level3
+ true
+ WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)
+ true
+
+
+ Console
+ true
+
+
+
+
+ Level3
+ true
+ true
+ true
+ WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)
+ true
+
+
+ Console
+ true
+ true
+ true
+
+
+
+
+ Level3
+ true
+ _DEBUG;_CONSOLE;%(PreprocessorDefinitions)
+ true
+
+
+ Console
+ true
+
+
+
+
+ Level3
+ true
+ true
+ true
+ NDEBUG;_CONSOLE;%(PreprocessorDefinitions)
+ true
+
+
+ Console
+ true
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/minipp/minipp.vcxproj.filters b/minipp/minipp.vcxproj.filters
new file mode 100644
index 0000000..aee9209
--- /dev/null
+++ b/minipp/minipp.vcxproj.filters
@@ -0,0 +1,30 @@
+
+
+
+
+ {4FC737F1-C7A5-4376-A066-2A32D752A2FF}
+ cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx
+
+
+ {93995380-89BD-4b04-88EB-625FBE52EBFB}
+ h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd
+
+
+ {67DA6AB6-F800-4c08-8B7A-83BB121AAD01}
+ rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms
+
+
+
+
+ Headerdateien
+
+
+
+
+ Quelldateien
+
+
+ Quelldateien
+
+
+
\ No newline at end of file
diff --git a/minipp/minipp/minipp.cpp b/minipp/minipp/minipp.cpp
new file mode 100644
index 0000000..f4ef2d0
--- /dev/null
+++ b/minipp/minipp/minipp.cpp
@@ -0,0 +1,436 @@
+#include "minipp.hpp"
+#include
+
+#define MINIPP_ENABLE_DEBUG_OUTPUT true
+
+#if MINIPP_ENABLE_DEBUG_OUTPUT
+#include
+#define COUT(msg) std::cout << msg << std::endl
+#else
+#define COUT(msg)
+#endif
+
+#define ASSERT(condition) if (!condition) abort();
+
+#define COUT_SYNTAX_ERROR(line, msg) COUT("Error in line " << line << ": " << msg)
+
+minipp::MiniPPFile::Section::~Section()
+{
+ for (auto& pair : m_values)
+ delete pair.second;
+ for (auto& pair : m_subSections)
+ delete pair.second;
+}
+
+minipp::MiniPPFile::Section* minipp::MiniPPFile::Section::GetSubSection(const std::string& key, EResult* result) const noexcept
+{
+ auto pathIndex = key.find('.');
+ std::string thisKey = key;
+ std::string rest;
+ if (pathIndex != std::string::npos)
+ {
+ thisKey = key.substr(0, pathIndex);
+ rest = key.substr(pathIndex + 1);
+ }
+
+ auto it = m_subSections.find(thisKey);
+ if (it == m_subSections.end())
+ {
+ COUT("Sub-Section not found: " << thisKey);
+ if (result != nullptr)
+ *result = EResult::SectionNotPresent;
+ return nullptr;
+ }
+ if (result != nullptr)
+ *result = EResult::Success;
+ if (rest.empty())
+ return it->second;
+ return it->second->GetSubSection(rest, result);
+}
+
+minipp::MiniPPFile::Section* minipp::MiniPPFile::Section::SetSubSection(const std::string& name, std::unique_ptr value, bool allowOverwrite, EResult* result) noexcept
+{
+ if (m_subSections.find(name) != m_subSections.end())
+ if (!allowOverwrite)
+ {
+ if (result != nullptr)
+ *result = EResult::SectionAlreadyPresent;
+ return nullptr;
+ }
+ else
+ delete m_subSections[name];
+
+ auto val = value.release();
+ m_subSections[name] = val;
+ if (result != nullptr)
+ *result = EResult::Success;
+ return val;
+}
+
+minipp::MiniPPFile::StringValue::StringValue(const std::string& str) : m_value(str) {}
+
+minipp::EResult minipp::MiniPPFile::StringValue::Parse(const std::string& str) noexcept
+{
+ m_value = "";
+ for (size_t i = 0; i < str.size(); ++i)
+ {
+ if (str[i] == '\\')
+ {
+ if (i + 1 >= str.size())
+ return EResult::FormatError;
+
+ switch (str[i + 1])
+ {
+ case '\"':
+ m_value.push_back('\"');
+ break;
+ case 'n':
+ m_value.push_back('\n');
+ break;
+ case 't':
+ m_value.push_back('\t');
+ break;
+ case 'r':
+ m_value.push_back('\r');
+ break;
+ case '\\':
+ m_value.push_back('\\');
+ break;
+ default:
+ return EResult::FormatError;
+ }
+ ++i;
+ }
+ else if (str[i] == '"')
+ return EResult::FormatError;
+ else
+ m_value.push_back(str[i]);
+ }
+ return EResult::Success;
+}
+
+minipp::EResult minipp::MiniPPFile::StringValue::ToString(std::string& destination) const noexcept
+{
+ std::string sanitizedValue = m_value;
+ for (size_t i = 0; i < sanitizedValue.size(); ++i)
+ {
+ switch (sanitizedValue[i])
+ {
+ case '\n':
+ sanitizedValue.replace(i, 1, "\\n");
+ i++;
+ break;
+ case '\t':
+ sanitizedValue.replace(i, 1, "\\t");
+ i++;
+ break;
+ case '\r':
+ sanitizedValue.replace(i, 1, "\\r");
+ i++;
+ break;
+ case '\\':
+ sanitizedValue.insert(i, 1, '\\');
+ i++;
+ break;
+ case '\"':
+ sanitizedValue.insert(i, 1, '\\');
+ i++;
+ break;
+ }
+ }
+ destination = "\"" + sanitizedValue + "\"";
+ return EResult::Success;
+}
+
+const std::string& minipp::MiniPPFile::StringValue::GetValue() const noexcept
+{
+ return m_value;
+}
+
+minipp::EResult minipp::MiniPPFile::Parse(const std::string& path) noexcept
+{
+ std::ifstream ifs;
+ ifs.open(path);
+ if (!ifs.is_open())
+ return EResult::FileIOError;
+
+ int64_t lineCounter = 0;
+
+ Section* currentSection = nullptr;
+
+ std::string currentLine;
+ while (std::getline(ifs, currentLine))
+ {
+ ++lineCounter;
+ Tools::StringTrim(currentLine);
+ if (currentLine.empty())
+ continue;
+ char firstChar = currentLine[0];
+ char lastChar = currentLine[currentLine.size() - 1];
+
+ if (firstChar == '#')
+ continue;
+
+ if (firstChar == '[')
+ {
+ if (lastChar != ']')
+ {
+ COUT_SYNTAX_ERROR(lineCounter, "Expected ']' at the end of the line.");
+ return EResult::FormatError;
+ }
+ std::string sectionName = currentLine.substr(1, currentLine.size() - 2);
+ Tools::StringTrim(sectionName);
+ if (sectionName.empty())
+ {
+ COUT_SYNTAX_ERROR(lineCounter, "Expected section path. Found empty section begin notation.");
+ return EResult::FormatError;
+ }
+ // Create section tree
+ Section* ubSection = &m_rootSection;
+
+ std::vector sectionPath = Tools::SplitByDelimiter(sectionName, '.');
+ for (size_t i = 0; i < sectionPath.size(); ++i)
+ {
+ const std::string& sectionName = sectionPath[i];
+ if (!Tools::IsNameValid(sectionName))
+ {
+ COUT_SYNTAX_ERROR(lineCounter, "Invalid section name. (\"" << sectionName << "\") May only contain [a - z][A - Z][0 - 9] and _.");
+ return EResult::FormatError;
+ }
+
+ EResult result;
+ ubSection->SetSubSection(sectionName, std::make_unique(), false, &result); // if this fails, the section is already present
+ if (result == EResult::SectionAlreadyPresent && i == sectionPath.size() - 1)
+ {
+ COUT_SYNTAX_ERROR(lineCounter, "All (sub-) sections may only be defined once.");
+ return EResult::FormatError;
+ }
+ ubSection = ubSection->GetSubSection(sectionName, &result);
+ ASSERT(Tools::IsResultOk(result)); // should never happen
+ }
+ currentSection = ubSection;
+ continue;
+ }
+ if (currentSection == nullptr)
+ {
+ COUT_SYNTAX_ERROR(lineCounter, "Expected section begin before key-value pair.");
+ return EResult::FormatError;
+ }
+
+ int64_t keyValueDelimiterIndex = Tools::FirstIndexOf(currentLine, '=');
+ if (keyValueDelimiterIndex == -1)
+ {
+ COUT_SYNTAX_ERROR(lineCounter, "Expected '=' in line.");
+ return EResult::FormatError;
+ }
+ auto keyValuePair = Tools::SplitInTwo(currentLine, keyValueDelimiterIndex);
+ Tools::StringTrim(keyValuePair.first);
+ Tools::StringTrim(keyValuePair.second);
+ if (keyValuePair.first.empty())
+ {
+ COUT_SYNTAX_ERROR(lineCounter, "Expected key in line.");
+ return EResult::FormatError;
+ }
+ if (keyValuePair.second.empty())
+ {
+ COUT_SYNTAX_ERROR(lineCounter, "Empty keys are not allowed");
+ return EResult::FormatError;
+ }
+ std::string& value = keyValuePair.second;
+ char valueFirstChar = value.front();
+ char valueLastChar = value.back();
+ if (valueFirstChar == '"')
+ {
+ if (valueLastChar != '"')
+ {
+ COUT_SYNTAX_ERROR(lineCounter, "Expected '\"' at the end of the value.");
+ return EResult::FormatError;
+ }
+ // is string
+ value = value.substr(1, value.size() - 2);
+ StringValue strValue;
+ auto parseResult = strValue.Parse(value);
+ if (!Tools::IsResultOk(parseResult))
+ {
+ COUT_SYNTAX_ERROR(lineCounter, "Invalid string value.");
+ return EResult::FormatError;
+ }
+ currentSection->SetValue(keyValuePair.first, strValue, false);
+ continue;
+ }
+ else
+ {
+ IntValue intValue;
+ auto parseResult = intValue.Parse(value);
+ if (!Tools::IsResultOk(parseResult))
+ {
+ COUT_SYNTAX_ERROR(lineCounter, "Invalid integer value.");
+ return EResult::FormatError;
+ }
+ currentSection->SetValue(keyValuePair.first, intValue, false);
+ continue;
+ }
+
+ }
+
+ return EResult::Success;
+}
+
+void minipp::MiniPPFile::Write(const std::string& path) noexcept
+{
+}
+
+bool minipp::MiniPPFile::Tools::StringStartsWith(const std::string& str, const std::string& beg)
+{
+ if (str.size() < beg.size())
+ return false;
+
+ for (size_t i = 0; i < beg.size(); ++i)
+ if (str[i] != beg[i])
+ return false;
+ return true;
+}
+
+bool minipp::MiniPPFile::Tools::StringEndsWith(const std::string& str, const std::string& end)
+{
+ if (str.size() < end.size())
+ return false;
+
+ for (size_t i = 0; i < end.size(); ++i)
+ if (str[str.size() - i - 1] != end[end.size() - i - 1])
+ return false;
+ return true;
+}
+
+void minipp::MiniPPFile::Tools::StringTrim(std::string& str)
+{
+ if (str.empty())
+ return;
+
+ size_t start = 0;
+ size_t end = str.size() - 1;
+ while (str[start] == ' ' || str[start] == '\t')
+ start++;
+ while (str[end] == ' ' || str[end] == '\t')
+ end--;
+
+ str = str.substr(start, end - start + 1);
+}
+
+bool minipp::MiniPPFile::Tools::IsNameValid(const std::string& name) noexcept
+{
+ for (const char c : name)
+ {
+ if (
+ (c >= 'a' && c <= 'z') ||
+ (c >= 'A' && c <= 'Z') ||
+ (c >= '0' && c <= '9') ||
+ (c == '_'))
+ continue;
+ return false;
+ }
+ return true;
+}
+
+int64_t minipp::MiniPPFile::Tools::FirstIndexOf(const std::string& str, char c) noexcept
+{
+ int64_t index = 0;
+ for (const char ch : str)
+ {
+ if (ch == c)
+ return index;
+ ++index;
+ }
+ return -1;
+}
+
+int64_t minipp::MiniPPFile::Tools::LastIndexOf(const std::string& str, char c) noexcept
+{
+ int64_t index = str.size() - 1;
+ for (int64_t i = str.size() - 1; i >= 0; --i)
+ {
+ if (str[i] == c)
+ return index;
+ --index;
+ }
+ return -1;
+}
+
+std::pair minipp::MiniPPFile::Tools::SplitInTwo(const std::string& str, int64_t firstLength) noexcept
+{
+ return std::make_pair(str.substr(0, firstLength), str.substr(firstLength + 1));
+}
+
+std::vector minipp::MiniPPFile::Tools::SplitByDelimiter(const std::string& str, char delimiter) noexcept
+{
+ std::vector elements;
+ std::string tmp;
+
+ for (const char c : str)
+ {
+ if (c == delimiter)
+ {
+ elements.push_back(tmp);
+ tmp.clear();
+ }
+ else
+ tmp.push_back(c);
+ }
+
+ if (!tmp.empty())
+ elements.push_back(tmp);
+ return elements;
+}
+
+bool minipp::MiniPPFile::Tools::IsResultOk(EResult result) noexcept
+{
+ return static_cast(result) > 0;
+}
+
+void minipp::MiniPPFile::Tools::RemoveAll(std::string& str, char old)
+{
+ str.erase(std::remove(str.begin(), str.end(), old), str.end());
+}
+
+bool minipp::MiniPPFile::Tools::IsIntegerDecimal(const std::string& str) noexcept
+{
+ for (size_t i = 0; i < str.size(); ++i)
+ if (str[i] < '0' || str[i] > '9')
+ return false;
+ return true;
+}
+
+minipp::MiniPPFile::IntValue::IntValue(int64_t value) : m_value(value) {}
+
+minipp::EResult minipp::MiniPPFile::IntValue::Parse(const std::string& str) noexcept
+{
+ std::string sanitizedValue = str;
+ Tools::RemoveAll(sanitizedValue, '_');
+ if (sanitizedValue.empty())
+ return EResult::FormatError;
+
+ char lastCharacter = str.back();
+ auto rest = str.substr(0, str.size() - 1);
+ if (lastCharacter == 'h')
+ m_value = std::stoll(rest, nullptr, 16);
+ else if (lastCharacter == 'b')
+ m_value = std::stoll(rest, nullptr, 2);
+ else
+ {
+ if (!Tools::IsIntegerDecimal(sanitizedValue))
+ return EResult::FormatError;
+ m_value = std::stoll(sanitizedValue);
+ }
+ return EResult::Success;
+}
+
+minipp::EResult minipp::MiniPPFile::IntValue::ToString(std::string& destination) const noexcept
+{
+ destination = std::to_string(m_value);
+ return EResult::Success;
+}
+
+int64_t minipp::MiniPPFile::IntValue::GetValue() const noexcept
+{
+ return m_value;
+}
diff --git a/minipp/minipp/minipp.hpp b/minipp/minipp/minipp.hpp
new file mode 100644
index 0000000..b5b08c9
--- /dev/null
+++ b/minipp/minipp/minipp.hpp
@@ -0,0 +1,141 @@
+#pragma once
+#include
+#include
+#include
+#include
+#include
+#include
+
+namespace minipp
+{
+ enum class EResult
+ {
+ /* Errors */
+ KeyNotPresent = -1,
+ KeyAlreadyPresent = -2,
+ SectionNotPresent = -3,
+ SectionAlreadyPresent = -4,
+ FileIOError = -5,
+ InvalidDataType = -6,
+ FormatError = -7,
+
+ /* OK Codes*/
+ Success = 1,
+ ValueOverwritten,
+ ValueOverwrittenAndDataTypeChanged
+ };
+
+ class MiniPPFile
+ {
+ private:
+ class Tools
+ {
+ public:
+ static bool StringStartsWith(const std::string& str, const std::string& beg);
+ static bool StringEndsWith(const std::string& str, const std::string& end);
+ static void StringTrim(std::string& str);
+ static bool IsNameValid(const std::string& name) noexcept;
+ static int64_t FirstIndexOf(const std::string& str, char c) noexcept;
+ static int64_t LastIndexOf(const std::string& str, char c) noexcept;
+ static std::pair SplitInTwo(const std::string& str, int64_t firstLength) noexcept;
+ static std::vector SplitByDelimiter(const std::string& str, char delimiter) noexcept;
+ static bool IsResultOk(EResult result) noexcept;
+ static void RemoveAll(std::string& str, char old);
+ static bool IsIntegerDecimal(const std::string& str) noexcept;
+ };
+
+ public:
+ class Value
+ {
+ public:
+ virtual EResult Parse(const std::string& str) noexcept = 0;
+ virtual EResult ToString(std::string& destination) const noexcept = 0;
+ };
+
+ class StringValue : public Value
+ {
+ private:
+ std::string m_value;
+
+ public:
+ StringValue() = default;
+ StringValue(const std::string& str);
+ EResult Parse(const std::string& str) noexcept override;
+ EResult ToString(std::string& destination) const noexcept override;
+ const std::string& GetValue() const noexcept;
+ };
+
+ class IntValue : public Value
+ {
+ private:
+ int64_t m_value = 0;
+
+ public:
+ IntValue() = default;
+ IntValue(int64_t value);
+ EResult Parse(const std::string& str) noexcept override;
+ EResult ToString(std::string& destination) const noexcept override;
+ int64_t GetValue() const noexcept;
+ };
+
+ class Section
+ {
+ private:
+ std::unordered_map m_values;
+ std::unordered_map m_subSections;
+
+ public:
+ Section() = default;
+ ~Section();
+ Section(const Section&) = delete;
+ Section& operator=(const Section&) = delete;
+
+ public:
+ template
+ EResult GetValue(T** target, const std::string& key) noexcept
+ {
+ auto it = m_values.find(key);
+ if (it == m_values.end())
+ return EResult::KeyNotPresent;
+
+ auto val = dynamic_cast(it->second);
+ if (val == nullptr)
+ return EResult::InvalidDataType;
+
+ *target = val;
+ return EResult::Success;
+ }
+
+ template
+ EResult SetValue(const std::string& name, const T& value, bool allowOverwrite = false) noexcept
+ {
+ if (m_values.find(name) != m_values.end())
+ if (!allowOverwrite)
+ return EResult::KeyAlreadyPresent;
+ else
+ delete m_values[name];
+
+ m_values[name] = new T(value);
+ return EResult::Success;
+ }
+
+ public:
+ Section* GetSubSection(const std::string& key, EResult* result = nullptr) const noexcept;
+ Section* SetSubSection(const std::string& name, std::unique_ptr value, bool allowOverwrite = false, EResult* result = nullptr) noexcept;
+ };
+
+ public:
+ MiniPPFile() = default;
+
+ private:
+ Section m_rootSection{};
+
+ public:
+ EResult Parse(const std::string& path) noexcept;
+ void Write(const std::string& path) noexcept;
+
+ public:
+ const Section& GetRoot() const noexcept { return m_rootSection; }
+ Section& GetRoot() noexcept { return m_rootSection; }
+ };
+}
\ No newline at end of file
diff --git a/minipp/test.cpp b/minipp/test.cpp
new file mode 100644
index 0000000..7fbda9c
--- /dev/null
+++ b/minipp/test.cpp
@@ -0,0 +1,22 @@
+#include "minipp/minipp.hpp"
+
+int main()
+{
+ minipp::MiniPPFile file;
+ auto result = file.Parse("test.mini");
+
+ auto& section = file.GetRoot();
+
+ minipp::MiniPPFile::Section* profileSection = section.GetSubSection("profile", &result);
+
+ minipp::MiniPPFile::IntValue* value;
+ auto result1 = profileSection->GetValue(&value, "window_width");
+
+ auto result2 = value->GetValue();
+
+ auto marianProfile = section.GetSubSection("profile.marian", &result);
+ minipp::MiniPPFile::StringValue* dockOutlinerType;
+ marianProfile->GetValue(&dockOutlinerType, "dock_outliner");
+
+ return 0;
+}
\ No newline at end of file
diff --git a/minipp/test.mini b/minipp/test.mini
new file mode 100644
index 0000000..f03c0d5
--- /dev/null
+++ b/minipp/test.mini
@@ -0,0 +1,10 @@
+[profile]
+dock_outliner="left"
+window_width=1000000b
+window_height=fh
+
+[profile.marian]
+dock_outliner="ri\\ght\\"
+
+[profile.toyb]
+dock_outliner="top"
\ No newline at end of file