#pragma region CPL License /* Nuclex Native Framework Copyright (C) 2002-2023 Nuclex Development Labs This library is free software; you can redistribute it and/or modify it under the terms of the IBM Common Public License as published by the IBM Corporation; either version 1.0 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the IBM Common Public License for more details. You should have received a copy of the IBM Common Public License along with this library */ #pragma endregion // CPL License #ifndef NUCLEX_SUPPORT_SETTINGS_INIDOCUMENTMODEL_H #define NUCLEX_SUPPORT_SETTINGS_INIDOCUMENTMODEL_H #include "Nuclex/Support/Config.h" #include "Nuclex/Support/Text/StringMatcher.h" #include // for std::vector #include // for std::string #include // for std::uint8_t #include // for std::optional #include // for std::unordered_map #include // for std::unordered_set // IDEA: Provide second constructor with unique_ptr that transfers memory ownership // This could, perhaps, save on a few allocations // Downside is that the parser would have to support two different allocation models namespace Nuclex { namespace Support { namespace Settings { // ------------------------------------------------------------------------------------------- // /// /// Document model storing the contents of an .ini file in an easily traversable format /// /// /// /// This is the same concept as you might find in a DOM (document object model) style /// XML parser, a representation of the .ini file's contents as a set of objects /// allowing easy manipulation and search through all nodes/elements. /// /// /// This document model takes great care to preserve the original lines and merely /// memorize where each lines' important characters are. Meaningless lines (comments /// and un-parseable ones) are preserved as well, allowing the reconstruction of /// the whole .ini file in its original format, even if values are modified. /// /// /// Allocation is done in chunks, reducing memory fragmentation and improving /// cache locality. /// /// class IniDocumentModel { /// Initializes a new empty .ini file document model public: IniDocumentModel(); /// /// Initializes a new .ini file document model parsing the specified file contents /// /// The whole contents of an .ini file /// Lenght of the .ini file in bytes public: IniDocumentModel(const std::uint8_t *fileContents, std::size_t byteCount); /// Frees all memory owned by the instance public: ~IniDocumentModel(); /// Serializes the entire document model into a memory block /// Vector holding the entire .ini file contents public: std::vector Serialize() const; /// Serializes the entire document model back into an .ini file /// Path of the .ini file in to save the document model public: void Serialize( void *context, void write(void *context, const std::uint8_t *, std::size_t) ) const; /// Retrieves a list of all sections that exist in the .ini file /// A list of all sections contained in the .ini file public: std::vector GetAllSections() const; /// Retrieves a list of all properties defined within a section /// Name of the section whose properties will be liste /// A list of all properties defined in the specified section public: std::vector GetAllProperties(const std::string §ionName) const; /// Looks up the value of a property /// Name of the section in which the property exists /// Name of the property that will be looked up /// The value of the property if the property exists public: std::optional GetPropertyValue( const std::string §ionName, const std::string &propertyName ) const; /// Creates a property or updates an existing property's value /// Name of the section in which the property will be set /// Name of the property that will be set /// Value that will be assigned to the property public: void SetPropertyValue( const std::string §ionName, const std::string &propertyName, const std::string &propertyValue ); /// Deletes a property if it exists /// Name of the section in which the property exists /// Name of the property that will be deleted /// True if the property existed and was deleted, false otherwise public: bool DeleteProperty( const std::string §ionName, const std::string &propertyName ); /// Deletes an entire section from the document if it exists /// Name of the section that will be deleted /// True if the section existed and was deleted, false otherwise public: bool DeleteSection(const std::string §ionName); #pragma region struct Line /// An arbitrary line from an .ini file protected: struct Line { /// Pointer to the previous line public: Line *Previous; /// Pointer to the next line public: Line *Next; /// The text contained in this line, including CR or CR-LF public: std::uint8_t *Contents; /// Length of the line in bytes public: std::size_t Length; }; #pragma endregion // struct Line #pragma region struct SectionLine /// A line in an .ini file declaring a section protected: struct SectionLine : public Line { /// Byte index at which the section name begins public: std::size_t NameStartIndex; /// Length of the section name in bytes public: std::size_t NameLength; }; #pragma endregion // struct SectionLine #pragma region struct PropertyLine /// A line in an .ini file containing a property assignment protected: struct PropertyLine : public Line { /// Byte index at which the property name begins public: std::size_t NameStartIndex; /// Length of the property name in bytes public: std::size_t NameLength; /// Byte index at which the property's value begins public: std::size_t ValueStartIndex; /// Length of the property's value in bytes public: std::size_t ValueLength; }; #pragma endregion // struct PropertyLine #pragma region class IndexedSection /// Index for quick lookup of all properties in a section by name protected: class IndexedSection { /// Map from (case-insensitive) property name to property line public: typedef std::unordered_map< std::string, PropertyLine *, Text::CaseInsensitiveUtf8Hash, Text::CaseInsensitiveUtf8EqualTo > PropertyMap; /// Line in which this section is declared. Can be a nullptr. /// /// Section contents immediately follow this line. The section line is only /// a nullptr if this section it the implicit default section, in which case /// it covers all properties from the beginning of the file until the first /// explicit section declaration (important for the DeleteSection() method). /// public: SectionLine *DeclarationLine; /// Index of property lines in this section by their property name public: PropertyMap Properties; /// Last line in this section. Can be a nullptr. /// /// Used when appending properties to the section so they appear at the very end. /// Is only a nullptr when constructing a new .ini file or loading an empty one, /// in which case properties will be appended as the last line in the file. /// public: Line *LastLine; }; #pragma endregion // class IndexedSection // Internal helper that parses an existing .ini file into the document model private: class FileParser; //private: class LineBuilder; /// Retrieves or creates the section with the specified name /// Name of the section that will be retrieved or created /// The new or existing section of the specified name private: IndexedSection *getOrCreateSection(const std::string §ionName); /// Creates a new line to declare the specified property /// Name of the property the line will declare /// Value that will be assigned to the property /// The new property declaration line private: PropertyLine *createPropertyLine( const std::string &propertyName, const std::string &propertyValue ); /// Integrates a line into the linked list of lines /// Line after which the new line will appear /// New line that will be integrated /// Whether generate an extra blank line first private: void integrateLine(Line *previous, Line *newLine, bool blankLineBefore = false); /// Parses the contents of an existing .ini file /// Buffer holding the entire .ini file in memory /// Size of the .ini file in bytes /// /// Amount of memory allocated in /// private: void parseFileContents( const std::uint8_t *fileContents, std::size_t byteCount ); /// Changes the value stored in an existing line /// Existing line containing the old value /// New property value that will be stored in the line /// Whether quotes will be added around the property line private: static void updateExistingPropertyLine( PropertyLine *existingPropertyLine, const std::string &newValue, bool addQuotes ); /// Checks whether the specified property's value has quotes around it /// Property line that will be checked /// True if the property line uses quotes, false if not private: static bool hasQuotes(PropertyLine *propertyLine); /// Checks whether the specified property value requires quotes /// Value that will be checked for requiring quotes /// True if the property value has to be surrounded with quotes private: static bool requiresQuotes(const std::string &propertyValue); /// Allocates memory for a single line /// Type of line that will be allocated /// The bytes this line consists of, including CR / CR-LF /// Length of the line in bytes /// The new line private: template TLine *allocateLine(const std::uint8_t *contents, std::size_t byteCount); /// Allocates memory for the specified type /// Type for which memory will be allocated /// Extra bytes to make available after the type /// The memory address of the newly allocated type /// /// An uninitialized instance is returned. No constructors will be called. /// This is for internal use and should only ever be used with POD types. /// private: template T *allocate(std::size_t extraByteCount = 0); /// Frees the memory allocated for a line type /// Type of line whose memory will be freed /// The line instance that will be freed private: template void freeLine(TLine *line); /// Map from property name to the lines containing a property private: typedef IndexedSection::PropertyMap PropertyMap; /// Map from section name to a type holding the properties in the section private: typedef std::unordered_map< std::string, IndexedSection *, Text::CaseInsensitiveUtf8Hash, Text::CaseInsensitiveUtf8EqualTo > SectionMap; /// Memory holding all Line instances from when the .ini file was loaded /// /// Instead of allocating lines individually, this document model allocates a big memory /// chunk that holds all line instances and their respective text, too. This avoids /// memory fragmentation and is fairly efficient as usually, .ini files aren't completely /// restructured during an application run. /// private: std::vector loadedLinesMemory; /// Memory for all Line instances that were created after loading private: std::unordered_set createdLinesMemory; /// Pointer to the first line, useful to reconstruct the file private: Line *firstLine; /// Map allowing quick access to all the sections in the .ini file /// /// The global section (containing all properties before the first section declaration) /// is nameless and always present. /// private: SectionMap sections; /// Should there be spaces before and after the equals sign? private: bool hasSpacesAroundAssignment; /// Should property assignments be padded with empty lines between them? private: bool usesPaddingLines; /// Whether the configuration file uses weird Windows line breaks private: bool usesCrLf; }; // ------------------------------------------------------------------------------------------- // }}} // namespace Nuclex::Support::Settings #endif // NUCLEX_SUPPORT_SETTINGS_INIDOCUMENTMODEL_H