From ad40c514b6b12f728cb8e9a8f7a5fa8ef1806cdb Mon Sep 17 00:00:00 2001 From: "Marian W." Date: Sat, 1 Feb 2025 00:28:38 +0100 Subject: [PATCH] =?UTF-8?q?Projektdateien=20hinzuf=C3=BCgen.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- minipp.sln | 28 +++ minipp/minipp.vcxproj | 139 +++++++++++ minipp/minipp.vcxproj.filters | 30 +++ minipp/minipp/minipp.cpp | 436 ++++++++++++++++++++++++++++++++++ minipp/minipp/minipp.hpp | 141 +++++++++++ minipp/test.cpp | 22 ++ minipp/test.mini | 10 + 7 files changed, 806 insertions(+) create mode 100644 minipp.sln create mode 100644 minipp/minipp.vcxproj create mode 100644 minipp/minipp.vcxproj.filters create mode 100644 minipp/minipp/minipp.cpp create mode 100644 minipp/minipp/minipp.hpp create mode 100644 minipp/test.cpp create mode 100644 minipp/test.mini 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