diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..f5e3ec5 --- /dev/null +++ b/.clang-format @@ -0,0 +1,2 @@ +BasedOnStyle: LLVM +ReflowComments: false diff --git a/.editorconfig b/.editorconfig index e49d512..8ed230c 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,3 +1,7 @@ [*] end_of_line = lf +[*.{c,cpp,h,hpp}] +indent_style = space +indent_size = 4 + diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 3b72173..2dcaf4a 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -34,6 +34,12 @@ jobs: run: | sudo apt install libgtest-dev cmake + - name: Format check + if: matrix.runner == 'ubuntu-latest' + run: | + sudo apt install clang-format + make format-check + - name: Test with CMake (-DSIMPLEINI_USE_SYSTEM_GTEST=OFF) run: | cmake -S . -B build -DSIMPLEINI_USE_SYSTEM_GTEST=OFF diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..18f75f5 --- /dev/null +++ b/Makefile @@ -0,0 +1,51 @@ +# Convenience targets around CMake. The build system remains CMake; this Makefile +# only configures build/ and invokes cmake/ctest/clang-format from the tree root. + +BUILD_DIR := build +CMAKE := cmake +CLANG_FORMAT ?= clang-format +FORMAT_FILES := SimpleIni.h $(wildcard tests/*.cpp) + +CMAKE_FLAGS := -DCMAKE_BUILD_TYPE=Debug + +JOBS := $(shell nproc 2>/dev/null || sysctl -n hw.ncpu 2>/dev/null || echo 4) + +.PHONY: all help configure build test format format-check check clean + +all: build + +help: + @echo "SimpleIni developer targets:" + @echo " make configure Configure the CMake build in $(BUILD_DIR)/" + @echo " make build Build tests (warnings are errors)" + @echo " make test Run tests via ctest" + @echo " make format Apply clang-format to sources" + @echo " make format-check Verify formatting (no writes)" + @echo " make check format-check, build, and test" + @echo " make clean Remove $(BUILD_DIR)/" + +configure: $(BUILD_DIR)/CMakeCache.txt + +$(BUILD_DIR)/CMakeCache.txt: + $(CMAKE) -S . -B $(BUILD_DIR) $(CMAKE_FLAGS) + +build: configure + $(CMAKE) --build $(BUILD_DIR) -j $(JOBS) + +test: build + $(CMAKE) -E chdir $(BUILD_DIR) ctest --output-on-failure + +format: + @command -v $(CLANG_FORMAT) >/dev/null 2>&1 \ + || { echo "error: $(CLANG_FORMAT) not found; install clang-format" >&2; exit 1; } + $(CLANG_FORMAT) -i $(FORMAT_FILES) + +format-check: + @command -v $(CLANG_FORMAT) >/dev/null 2>&1 \ + || { echo "error: $(CLANG_FORMAT) not found; install clang-format" >&2; exit 1; } + $(CLANG_FORMAT) --dry-run --Werror $(FORMAT_FILES) + +check: format-check test + +clean: + rm -rf $(BUILD_DIR) diff --git a/README.md b/README.md index a62e68a..0ccb39d 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,22 @@ That's it! The library is ready to use. # Build and Test -While the library itself doesn't require building, you can build and run the test suite using CMake. +While the library itself doesn't require building, you can build and run the test suite using CMake. A top-level `Makefile` wraps the usual CMake workflow for local development: + +```bash +make help # list targets +make check # format-check, build, and test +make format # apply clang-format +make format-check # verify formatting +make test # run ctest +make clean # remove build/ +``` + +Requires `cmake`, a C++17 compiler, and `clang-format` for format targets. Test builds use `-Werror`. CMake remains the build system. + +CI (`.github/workflows/build-and-test.yml`) runs `make format-check` on Linux only; tests run on Linux (x64 and arm64), Windows, and macOS. + +Direct CMake usage: ```bash # Configure the project diff --git a/SimpleIni.h b/SimpleIni.h index 43ed60b..6a9adda 100644 --- a/SimpleIni.h +++ b/SimpleIni.h @@ -216,7 +216,7 @@ #define INCLUDED_SimpleIni_h #if defined(_MSC_VER) && (_MSC_VER >= 1020) -# pragma once +#pragma once #endif // Disable these warnings in MSVC: @@ -229,69 +229,68 @@ // 4786 "identifier truncated to 256 characters" as this is thrown hundreds // of times VC6 as soon as STL is used. #ifdef _MSC_VER -# pragma warning (push) -# pragma warning (disable: 4127 4503 4702 4786) +#pragma warning(push) +#pragma warning(disable : 4127 4503 4702 4786) #endif -#include +#include #include -#include -#include +#include #include -#include +#include #include +#include #ifdef SI_SUPPORT_IOSTREAMS -# include +#include #endif // SI_SUPPORT_IOSTREAMS #ifdef _DEBUG -# ifndef assert -# include -# endif -# define SI_ASSERT(x) assert(x) +#ifndef assert +#include +#endif +#define SI_ASSERT(x) assert(x) #else -# define SI_ASSERT(x) +#define SI_ASSERT(x) #endif using SI_Error = int; -constexpr int SI_OK = 0; //!< No error -constexpr int SI_UPDATED = 1; //!< An existing value was updated -constexpr int SI_INSERTED = 2; //!< A new value was inserted +constexpr int SI_OK = 0; //!< No error +constexpr int SI_UPDATED = 1; //!< An existing value was updated +constexpr int SI_INSERTED = 2; //!< A new value was inserted // note: test for any error with (retval < 0) -constexpr int SI_FAIL = -1; //!< Generic failure -constexpr int SI_NOMEM = -2; //!< Out of memory error -constexpr int SI_FILE = -3; //!< File error (see errno for detail error) +constexpr int SI_FAIL = -1; //!< Generic failure +constexpr int SI_NOMEM = -2; //!< Out of memory error +constexpr int SI_FILE = -3; //!< File error (see errno for detail error) //! Maximum supported file size (1 GiB). Files larger than this will be rejected //! to prevent excessive memory allocation and potential denial of service. constexpr size_t SI_MAX_FILE_SIZE = 1024ULL * 1024ULL * 1024ULL; -#define SI_UTF8_SIGNATURE "\xEF\xBB\xBF" +#define SI_UTF8_SIGNATURE "\xEF\xBB\xBF" #ifdef _WIN32 -# define SI_NEWLINE_A "\r\n" -# define SI_NEWLINE_W L"\r\n" +#define SI_NEWLINE_A "\r\n" +#define SI_NEWLINE_W L"\r\n" #else // !_WIN32 -# define SI_NEWLINE_A "\n" -# define SI_NEWLINE_W L"\n" +#define SI_NEWLINE_A "\n" +#define SI_NEWLINE_W L"\n" #endif // _WIN32 #if defined(SI_CONVERT_ICU) -# include +#include #endif #if defined(_WIN32) -# define SI_HAS_WIDE_FILE -# define SI_WCHAR_T wchar_t +#define SI_HAS_WIDE_FILE +#define SI_WCHAR_T wchar_t #elif defined(SI_CONVERT_ICU) -# define SI_HAS_WIDE_FILE -# define SI_WCHAR_T UChar +#define SI_HAS_WIDE_FILE +#define SI_WCHAR_T UChar #endif - // --------------------------------------------------------------------------- // MAIN TEMPLATE CLASS // --------------------------------------------------------------------------- @@ -315,185 +314,176 @@ constexpr size_t SI_MAX_FILE_SIZE = 1024ULL * 1024ULL * 1024ULL; is a different size to char/wchar_t you may need to supply new helper classes for SI_STRLESS and SI_CONVERTER. */ -template -class CSimpleIniTempl -{ +template +class CSimpleIniTempl { public: - typedef SI_CHAR SI_CHAR_T; - - /** key entry */ - struct Entry { - const SI_CHAR * pItem; - const SI_CHAR * pComment; - int nOrder; - - Entry(const SI_CHAR * a_pszItem = NULL, int a_nOrder = 0) - : pItem(a_pszItem) - , pComment(NULL) - , nOrder(a_nOrder) - { } - Entry(const SI_CHAR * a_pszItem, const SI_CHAR * a_pszComment, int a_nOrder) - : pItem(a_pszItem) - , pComment(a_pszComment) - , nOrder(a_nOrder) - { } - Entry(const Entry & rhs) { operator=(rhs); } - Entry & operator=(const Entry & rhs) { - pItem = rhs.pItem; - pComment = rhs.pComment; - nOrder = rhs.nOrder; - return *this; - } + typedef SI_CHAR SI_CHAR_T; + + /** key entry */ + struct Entry { + const SI_CHAR *pItem; + const SI_CHAR *pComment; + int nOrder; + + Entry(const SI_CHAR *a_pszItem = NULL, int a_nOrder = 0) + : pItem(a_pszItem), pComment(NULL), nOrder(a_nOrder) {} + Entry(const SI_CHAR *a_pszItem, const SI_CHAR *a_pszComment, int a_nOrder) + : pItem(a_pszItem), pComment(a_pszComment), nOrder(a_nOrder) {} + Entry(const Entry &rhs) { operator=(rhs); } + Entry &operator=(const Entry &rhs) { + pItem = rhs.pItem; + pComment = rhs.pComment; + nOrder = rhs.nOrder; + return *this; + } #if defined(_MSC_VER) && _MSC_VER <= 1200 - /** STL of VC6 doesn't allow me to specify my own comparator for list::sort() */ - bool operator<(const Entry & rhs) const { return LoadOrder()(*this, rhs); } - bool operator>(const Entry & rhs) const { return LoadOrder()(rhs, *this); } + /** STL of VC6 doesn't allow me to specify my own comparator for list::sort() */ + bool operator<(const Entry &rhs) const { return LoadOrder()(*this, rhs); } + bool operator>(const Entry &rhs) const { return LoadOrder()(rhs, *this); } #endif - /** Strict less ordering by name of key only */ - struct KeyOrder { - bool operator()(const Entry & lhs, const Entry & rhs) const { - const static SI_STRLESS isLess = SI_STRLESS(); - return isLess(lhs.pItem, rhs.pItem); - } - }; - - /** Strict less ordering by order, and then name of key */ - struct LoadOrder { - bool operator()(const Entry & lhs, const Entry & rhs) const { - if (lhs.nOrder != rhs.nOrder) { - return lhs.nOrder < rhs.nOrder; - } - return KeyOrder()(lhs, rhs); - } - }; + /** Strict less ordering by name of key only */ + struct KeyOrder { + bool operator()(const Entry &lhs, const Entry &rhs) const { + const static SI_STRLESS isLess = SI_STRLESS(); + return isLess(lhs.pItem, rhs.pItem); + } + }; + + /** Strict less ordering by order, and then name of key */ + struct LoadOrder { + bool operator()(const Entry &lhs, const Entry &rhs) const { + if (lhs.nOrder != rhs.nOrder) { + return lhs.nOrder < rhs.nOrder; + } + return KeyOrder()(lhs, rhs); + } }; + }; - /** map keys to values */ - typedef std::multimap TKeyVal; + /** map keys to values */ + typedef std::multimap + TKeyVal; - /** map sections to key/value map */ - typedef std::map TSection; + /** map sections to key/value map */ + typedef std::map TSection; - /** set of dependent string pointers. Note that these pointers are + /** set of dependent string pointers. Note that these pointers are dependent on memory owned by CSimpleIni. */ - typedef std::list TNamesDepend; + typedef std::list TNamesDepend; - /** interface definition for the OutputWriter object to pass to Save() + /** interface definition for the OutputWriter object to pass to Save() in order to output the INI file data. */ - class OutputWriter { - public: - OutputWriter() { } - virtual ~OutputWriter() { } - virtual void Write(const char * a_pBuf) = 0; - private: - OutputWriter(const OutputWriter &); // disable - OutputWriter & operator=(const OutputWriter &); // disable - }; - - /** OutputWriter class to write the INI data to a file */ - class FileWriter : public OutputWriter { - FILE * m_file; - public: - FileWriter(FILE * a_file) : m_file(a_file) { } - void Write(const char * a_pBuf) { - fputs(a_pBuf, m_file); - } - private: - FileWriter(const FileWriter &); // disable - FileWriter & operator=(const FileWriter &); // disable - }; - - /** OutputWriter class to write the INI data to a string */ - class StringWriter : public OutputWriter { - std::string & m_string; - public: - StringWriter(std::string & a_string) : m_string(a_string) { } - void Write(const char * a_pBuf) { - m_string.append(a_pBuf); - } - private: - StringWriter(const StringWriter &); // disable - StringWriter & operator=(const StringWriter &); // disable - }; + class OutputWriter { + public: + OutputWriter() {} + virtual ~OutputWriter() {} + virtual void Write(const char *a_pBuf) = 0; + + private: + OutputWriter(const OutputWriter &); // disable + OutputWriter &operator=(const OutputWriter &); // disable + }; + + /** OutputWriter class to write the INI data to a file */ + class FileWriter : public OutputWriter { + FILE *m_file; + + public: + FileWriter(FILE *a_file) : m_file(a_file) {} + void Write(const char *a_pBuf) { fputs(a_pBuf, m_file); } + + private: + FileWriter(const FileWriter &); // disable + FileWriter &operator=(const FileWriter &); // disable + }; + + /** OutputWriter class to write the INI data to a string */ + class StringWriter : public OutputWriter { + std::string &m_string; + + public: + StringWriter(std::string &a_string) : m_string(a_string) {} + void Write(const char *a_pBuf) { m_string.append(a_pBuf); } + + private: + StringWriter(const StringWriter &); // disable + StringWriter &operator=(const StringWriter &); // disable + }; #ifdef SI_SUPPORT_IOSTREAMS - /** OutputWriter class to write the INI data to an ostream */ - class StreamWriter : public OutputWriter { - std::ostream & m_ostream; - public: - StreamWriter(std::ostream & a_ostream) : m_ostream(a_ostream) { } - void Write(const char * a_pBuf) { - m_ostream << a_pBuf; - } - private: - StreamWriter(const StreamWriter &); // disable - StreamWriter & operator=(const StreamWriter &); // disable - }; + /** OutputWriter class to write the INI data to an ostream */ + class StreamWriter : public OutputWriter { + std::ostream &m_ostream; + + public: + StreamWriter(std::ostream &a_ostream) : m_ostream(a_ostream) {} + void Write(const char *a_pBuf) { m_ostream << a_pBuf; } + + private: + StreamWriter(const StreamWriter &); // disable + StreamWriter &operator=(const StreamWriter &); // disable + }; #endif // SI_SUPPORT_IOSTREAMS - /** Characterset conversion utility class to convert strings to the + /** Characterset conversion utility class to convert strings to the same format as is used for the storage. */ - class Converter : private SI_CONVERTER { - public: - Converter(bool a_bStoreIsUtf8) : SI_CONVERTER(a_bStoreIsUtf8) { - m_scratch.resize(1024); - } - Converter(const Converter & rhs) { operator=(rhs); } - Converter & operator=(const Converter & rhs) { - m_scratch = rhs.m_scratch; - return *this; - } - bool ConvertToStore(const SI_CHAR * a_pszString) { - size_t uLen = SI_CONVERTER::SizeToStore(a_pszString); - if (uLen == (size_t)(-1)) { - return false; - } - while (uLen > m_scratch.size()) { - m_scratch.resize(m_scratch.size() * 2); - } - return SI_CONVERTER::ConvertToStore( - a_pszString, - const_cast(m_scratch.data()), - m_scratch.size()); - } - const char * Data() { return m_scratch.data(); } - private: - std::string m_scratch; - }; + class Converter : private SI_CONVERTER { + public: + Converter(bool a_bStoreIsUtf8) : SI_CONVERTER(a_bStoreIsUtf8) { + m_scratch.resize(1024); + } + Converter(const Converter &rhs) { operator=(rhs); } + Converter &operator=(const Converter &rhs) { + m_scratch = rhs.m_scratch; + return *this; + } + bool ConvertToStore(const SI_CHAR *a_pszString) { + size_t uLen = SI_CONVERTER::SizeToStore(a_pszString); + if (uLen == (size_t)(-1)) { + return false; + } + while (uLen > m_scratch.size()) { + m_scratch.resize(m_scratch.size() * 2); + } + return SI_CONVERTER::ConvertToStore( + a_pszString, const_cast(m_scratch.data()), m_scratch.size()); + } + const char *Data() { return m_scratch.data(); } + + private: + std::string m_scratch; + }; public: - /*-----------------------------------------------------------------------*/ + /*-----------------------------------------------------------------------*/ - /** Default constructor. + /** Default constructor. @param a_bIsUtf8 See the method SetUnicode() for details. @param a_bMultiKey See the method SetMultiKey() for details. @param a_bMultiLine See the method SetMultiLine() for details. */ - CSimpleIniTempl( - bool a_bIsUtf8 = false, - bool a_bMultiKey = false, - bool a_bMultiLine = false - ); + CSimpleIniTempl(bool a_bIsUtf8 = false, bool a_bMultiKey = false, + bool a_bMultiLine = false); - /** Destructor */ - ~CSimpleIniTempl(); + /** Destructor */ + ~CSimpleIniTempl(); - /** Deallocate all memory stored by this object */ - void Reset(); + /** Deallocate all memory stored by this object */ + void Reset(); - /** Has any data been loaded */ - bool IsEmpty() const { return m_data.empty(); } + /** Has any data been loaded */ + bool IsEmpty() const { return m_data.empty(); } - /*-----------------------------------------------------------------------*/ - /** @{ @name Settings */ + /*-----------------------------------------------------------------------*/ + /** @{ @name Settings */ - /** Set the storage format of the INI data. This affects both the loading + /** Set the storage format of the INI data. This affects both the loading and saving of the INI data using all of the Load/Save API functions. This value cannot be changed after any INI data has been loaded. @@ -507,14 +497,15 @@ class CSimpleIniTempl \param a_bIsUtf8 Assume UTF-8 encoding for the source? */ - void SetUnicode(bool a_bIsUtf8 = true) { - if (!m_pData) m_bStoreIsUtf8 = a_bIsUtf8; - } + void SetUnicode(bool a_bIsUtf8 = true) { + if (!m_pData) + m_bStoreIsUtf8 = a_bIsUtf8; + } - /** Get the storage format of the INI data. */ - bool IsUnicode() const { return m_bStoreIsUtf8; } + /** Get the storage format of the INI data. */ + bool IsUnicode() const { return m_bStoreIsUtf8; } - /** Should multiple identical keys be permitted in the file. If set to false + /** Should multiple identical keys be permitted in the file. If set to false then the last value encountered will be used as the value of the key. If set to true, then all values will be available to be queried. For example, with the following input: @@ -532,53 +523,50 @@ class CSimpleIniTempl \param a_bAllowMultiKey Allow multi-keys in the source? */ - void SetMultiKey(bool a_bAllowMultiKey = true) { - m_bAllowMultiKey = a_bAllowMultiKey; - } + void SetMultiKey(bool a_bAllowMultiKey = true) { + m_bAllowMultiKey = a_bAllowMultiKey; + } - /** Get the storage format of the INI data. */ - bool IsMultiKey() const { return m_bAllowMultiKey; } + /** Get the storage format of the INI data. */ + bool IsMultiKey() const { return m_bAllowMultiKey; } - /** Should data values be permitted to span multiple lines in the file. If + /** Should data values be permitted to span multiple lines in the file. If set to false then the multi-line construct << @@ -754,13 +721,10 @@ class CSimpleIniTempl @return SI_Error See error definitions */ - SI_Error Save( - OutputWriter & a_oOutput, - bool a_bAddSignature = false - ) const; + SI_Error Save(OutputWriter &a_oOutput, bool a_bAddSignature = false) const; #ifdef SI_SUPPORT_IOSTREAMS - /** Save the INI data to an ostream. See Save() for details. + /** Save the INI data to an ostream. See Save() for details. @param a_ostream String to have the INI data appended to. @@ -771,17 +735,13 @@ class CSimpleIniTempl @return SI_Error See error definitions */ - SI_Error Save( - std::ostream & a_ostream, - bool a_bAddSignature = false - ) const - { - StreamWriter writer(a_ostream); - return Save(writer, a_bAddSignature); - } + SI_Error Save(std::ostream &a_ostream, bool a_bAddSignature = false) const { + StreamWriter writer(a_ostream); + return Save(writer, a_bAddSignature); + } #endif // SI_SUPPORT_IOSTREAMS - /** Append the INI data to a string. See Save() for details. + /** Append the INI data to a string. See Save() for details. @param a_sBuffer String to have the INI data appended to. @@ -792,20 +752,16 @@ class CSimpleIniTempl @return SI_Error See error definitions */ - SI_Error Save( - std::string & a_sBuffer, - bool a_bAddSignature = false - ) const - { - StringWriter writer(a_sBuffer); - return Save(writer, a_bAddSignature); - } + SI_Error Save(std::string &a_sBuffer, bool a_bAddSignature = false) const { + StringWriter writer(a_sBuffer); + return Save(writer, a_bAddSignature); + } - /*-----------------------------------------------------------------------*/ - /** @} + /*-----------------------------------------------------------------------*/ + /** @} @{ @name Accessing INI Data */ - /** Retrieve all section names. The list is returned as an STL vector of + /** Retrieve all section names. The list is returned as an STL vector of names and can be iterated or searched as necessary. Note that the sort order of the returned strings is NOT DEFINED. You can sort the names into the load order if desired. Search this file for ".sort" @@ -819,11 +775,9 @@ class CSimpleIniTempl @param a_names Vector that will receive all of the section names. See note above! */ - void GetAllSections( - TNamesDepend & a_names - ) const; + void GetAllSections(TNamesDepend &a_names) const; - /** Retrieve all unique key names in a section. The sort order of the + /** Retrieve all unique key names in a section. The sort order of the returned strings is NOT DEFINED. You can sort the names into the load order if desired. Search this file for ".sort" for an example. Only unique key names are returned. @@ -840,12 +794,9 @@ class CSimpleIniTempl @return true Section was found. @return false Matching section was not found. */ - bool GetAllKeys( - const SI_CHAR * a_pSection, - TNamesDepend & a_names - ) const; + bool GetAllKeys(const SI_CHAR *a_pSection, TNamesDepend &a_names) const; - /** Retrieve all values for a specific key. This method can be used when + /** Retrieve all values for a specific key. This method can be used when multiple keys are both enabled and disabled. Note that the sort order of the returned strings is NOT DEFINED. You can sort the names into the load order if desired. Search this file for ".sort" for an example. @@ -861,13 +812,10 @@ class CSimpleIniTempl @return true Key was found. @return false Matching section/key was not found. */ - bool GetAllValues( - const SI_CHAR * a_pSection, - const SI_CHAR * a_pKey, - TNamesDepend & a_values - ) const; + bool GetAllValues(const SI_CHAR *a_pSection, const SI_CHAR *a_pKey, + TNamesDepend &a_values) const; - /** Query the number of keys in a specific section. Note that if multiple + /** Query the number of keys in a specific section. Note that if multiple keys are enabled, then this value may be different to the number of keys returned by GetAllKeys. @@ -876,11 +824,9 @@ class CSimpleIniTempl @return -1 Section does not exist in the file @return >=0 Number of keys in the section */ - int GetSectionSize( - const SI_CHAR * a_pSection - ) const; + int GetSectionSize(const SI_CHAR *a_pSection) const; - /** Retrieve all key and value pairs for a section. The data is returned + /** Retrieve all key and value pairs for a section. The data is returned as a pointer to an STL map and can be iterated or searched as desired. Note that multiple entries for the same key may exist when multiple keys have been enabled. @@ -893,26 +839,20 @@ class CSimpleIniTempl @param a_pSection Name of the section to return @return Section data */ - const TKeyVal * GetSection( - const SI_CHAR * a_pSection - ) const; + const TKeyVal *GetSection(const SI_CHAR *a_pSection) const; - /** Test if a section exists. Convenience function */ - inline bool SectionExists( - const SI_CHAR * a_pSection - ) const { - return GetSection(a_pSection) != NULL; - } + /** Test if a section exists. Convenience function */ + inline bool SectionExists(const SI_CHAR *a_pSection) const { + return GetSection(a_pSection) != NULL; + } - /** Test if the key exists in a section. Convenience function. */ - inline bool KeyExists( - const SI_CHAR * a_pSection, - const SI_CHAR * a_pKey - ) const { - return GetValue(a_pSection, a_pKey) != NULL; - } + /** Test if the key exists in a section. Convenience function. */ + inline bool KeyExists(const SI_CHAR *a_pSection, + const SI_CHAR *a_pKey) const { + return GetValue(a_pSection, a_pKey) != NULL; + } - /** Retrieve the value for a specific key. If multiple keys are enabled + /** Retrieve the value for a specific key. If multiple keys are enabled (see SetMultiKey) then only the first value associated with that key will be returned, see GetAllValues for getting all values with multikey. @@ -929,14 +869,11 @@ class CSimpleIniTempl @return a_pDefault Key was not found in the section @return other Value of the key */ - const SI_CHAR * GetValue( - const SI_CHAR * a_pSection, - const SI_CHAR * a_pKey, - const SI_CHAR * a_pDefault = NULL, - bool * a_pHasMultiple = NULL - ) const; - - /** Retrieve a numeric value for a specific key. If multiple keys are enabled + const SI_CHAR *GetValue(const SI_CHAR *a_pSection, const SI_CHAR *a_pKey, + const SI_CHAR *a_pDefault = NULL, + bool *a_pHasMultiple = NULL) const; + + /** Retrieve a numeric value for a specific key. If multiple keys are enabled (see SetMultiKey) then only the first value associated with that key will be returned, see GetAllValues for getting all values with multikey. @@ -949,14 +886,10 @@ class CSimpleIniTempl @return a_nDefault Key was not found in the section @return other Value of the key */ - long GetLongValue( - const SI_CHAR * a_pSection, - const SI_CHAR * a_pKey, - long a_nDefault = 0, - bool * a_pHasMultiple = NULL - ) const; - - /** Retrieve a numeric value for a specific key. If multiple keys are enabled + long GetLongValue(const SI_CHAR *a_pSection, const SI_CHAR *a_pKey, + long a_nDefault = 0, bool *a_pHasMultiple = NULL) const; + + /** Retrieve a numeric value for a specific key. If multiple keys are enabled (see SetMultiKey) then only the first value associated with that key will be returned, see GetAllValues for getting all values with multikey. @@ -969,14 +902,11 @@ class CSimpleIniTempl @return a_nDefault Key was not found in the section @return other Value of the key */ - double GetDoubleValue( - const SI_CHAR * a_pSection, - const SI_CHAR * a_pKey, - double a_nDefault = 0, - bool * a_pHasMultiple = NULL - ) const; - - /** Retrieve a boolean value for a specific key. If multiple keys are enabled + double GetDoubleValue(const SI_CHAR *a_pSection, const SI_CHAR *a_pKey, + double a_nDefault = 0, + bool *a_pHasMultiple = NULL) const; + + /** Retrieve a boolean value for a specific key. If multiple keys are enabled (see SetMultiKey) then only the first value associated with that key will be returned, see GetAllValues for getting all values with multikey. @@ -994,14 +924,10 @@ class CSimpleIniTempl @return a_nDefault Key was not found in the section @return other Value of the key */ - bool GetBoolValue( - const SI_CHAR * a_pSection, - const SI_CHAR * a_pKey, - bool a_bDefault = false, - bool * a_pHasMultiple = NULL - ) const; - - /** Add or update a section or value. This will always insert + bool GetBoolValue(const SI_CHAR *a_pSection, const SI_CHAR *a_pKey, + bool a_bDefault = false, bool *a_pHasMultiple = NULL) const; + + /** Add or update a section or value. This will always insert when multiple keys are enabled. @param a_pSection Section to add or update @@ -1030,18 +956,14 @@ class CSimpleIniTempl @return SI_UPDATED Value was updated @return SI_INSERTED Value was inserted */ - SI_Error SetValue( - const SI_CHAR * a_pSection, - const SI_CHAR * a_pKey, - const SI_CHAR * a_pValue, - const SI_CHAR * a_pComment = NULL, - bool a_bForceReplace = false - ) - { - return AddEntry(a_pSection, a_pKey, a_pValue, a_pComment, a_bForceReplace, true); - } - - /** Add or update a numeric value. This will always insert + SI_Error SetValue(const SI_CHAR *a_pSection, const SI_CHAR *a_pKey, + const SI_CHAR *a_pValue, const SI_CHAR *a_pComment = NULL, + bool a_bForceReplace = false) { + return AddEntry(a_pSection, a_pKey, a_pValue, a_pComment, a_bForceReplace, + true); + } + + /** Add or update a numeric value. This will always insert when multiple keys are enabled. @param a_pSection Section to add or update @@ -1064,16 +986,11 @@ class CSimpleIniTempl @return SI_UPDATED Value was updated @return SI_INSERTED Value was inserted */ - SI_Error SetLongValue( - const SI_CHAR * a_pSection, - const SI_CHAR * a_pKey, - long a_nValue, - const SI_CHAR * a_pComment = NULL, - bool a_bUseHex = false, - bool a_bForceReplace = false - ); - - /** Add or update a double value. This will always insert + SI_Error SetLongValue(const SI_CHAR *a_pSection, const SI_CHAR *a_pKey, + long a_nValue, const SI_CHAR *a_pComment = NULL, + bool a_bUseHex = false, bool a_bForceReplace = false); + + /** Add or update a double value. This will always insert when multiple keys are enabled. @param a_pSection Section to add or update @@ -1093,15 +1010,11 @@ class CSimpleIniTempl @return SI_UPDATED Value was updated @return SI_INSERTED Value was inserted */ - SI_Error SetDoubleValue( - const SI_CHAR * a_pSection, - const SI_CHAR * a_pKey, - double a_nValue, - const SI_CHAR * a_pComment = NULL, - bool a_bForceReplace = false - ); - - /** Add or update a boolean value. This will always insert + SI_Error SetDoubleValue(const SI_CHAR *a_pSection, const SI_CHAR *a_pKey, + double a_nValue, const SI_CHAR *a_pComment = NULL, + bool a_bForceReplace = false); + + /** Add or update a boolean value. This will always insert when multiple keys are enabled. @param a_pSection Section to add or update @@ -1121,15 +1034,11 @@ class CSimpleIniTempl @return SI_UPDATED Value was updated @return SI_INSERTED Value was inserted */ - SI_Error SetBoolValue( - const SI_CHAR * a_pSection, - const SI_CHAR * a_pKey, - bool a_bValue, - const SI_CHAR * a_pComment = NULL, - bool a_bForceReplace = false - ); - - /** Delete an entire section, or a key from a section. Note that the + SI_Error SetBoolValue(const SI_CHAR *a_pSection, const SI_CHAR *a_pKey, + bool a_bValue, const SI_CHAR *a_pComment = NULL, + bool a_bForceReplace = false); + + /** Delete an entire section, or a key from a section. Note that the data returned by GetSection is invalid and must not be used after anything has been deleted from that section using this method. Note when multiple keys is enabled, this will delete all keys with @@ -1147,13 +1056,10 @@ class CSimpleIniTempl @return true Key or section was deleted. @return false Key or section was not found. */ - bool Delete( - const SI_CHAR * a_pSection, - const SI_CHAR * a_pKey, - bool a_bRemoveEmpty = false - ); + bool Delete(const SI_CHAR *a_pSection, const SI_CHAR *a_pKey, + bool a_bRemoveEmpty = false); - /** Delete an entire section, or a key from a section. If value is + /** Delete an entire section, or a key from a section. If value is provided, only remove keys with the value. Note that the data returned by GetSection is invalid and must not be used after anything has been deleted from that section using this method. @@ -1173,54 +1079,41 @@ class CSimpleIniTempl @return true Key/value or section was deleted. @return false Key/value or section was not found. */ - bool DeleteValue( - const SI_CHAR * a_pSection, - const SI_CHAR * a_pKey, - const SI_CHAR * a_pValue, - bool a_bRemoveEmpty = false - ); - - /*-----------------------------------------------------------------------*/ - /** @} + bool DeleteValue(const SI_CHAR *a_pSection, const SI_CHAR *a_pKey, + const SI_CHAR *a_pValue, bool a_bRemoveEmpty = false); + + /*-----------------------------------------------------------------------*/ + /** @} @{ @name Converter */ - /** Return a conversion object to convert text to the same encoding + /** Return a conversion object to convert text to the same encoding as is used by the Save(), SaveFile() and SaveString() functions. Use this to prepare the strings that you wish to append or prepend to the output INI data. */ - Converter GetConverter() const { - return Converter(m_bStoreIsUtf8); - } + Converter GetConverter() const { return Converter(m_bStoreIsUtf8); } - /*-----------------------------------------------------------------------*/ - /** @} */ + /*-----------------------------------------------------------------------*/ + /** @} */ private: - // copying is not permitted - CSimpleIniTempl(const CSimpleIniTempl &); // disabled - CSimpleIniTempl & operator=(const CSimpleIniTempl &); // disabled + // copying is not permitted + CSimpleIniTempl(const CSimpleIniTempl &); // disabled + CSimpleIniTempl &operator=(const CSimpleIniTempl &); // disabled - /** Parse the data looking for a file comment and store it if found. + /** Parse the data looking for a file comment and store it if found. */ - SI_Error FindFileComment( - SI_CHAR *& a_pData, - bool a_bCopyStrings - ); + SI_Error FindFileComment(SI_CHAR *&a_pData, bool a_bCopyStrings); - /** Parse the data looking for the next valid entry. The memory pointed to + /** Parse the data looking for the next valid entry. The memory pointed to by a_pData is modified by inserting NULL characters. The pointer is updated to the current location in the block of text. */ - bool FindEntry( - SI_CHAR *& a_pData, - const SI_CHAR *& a_pSection, - const SI_CHAR *& a_pKey, - const SI_CHAR *& a_pVal, - const SI_CHAR *& a_pComment - ) const; + bool FindEntry(SI_CHAR *&a_pData, const SI_CHAR *&a_pSection, + const SI_CHAR *&a_pKey, const SI_CHAR *&a_pVal, + const SI_CHAR *&a_pComment) const; - /** Add the section/key/value to our data. + /** Add the section/key/value to our data. @param a_pSection Section name. Sections will be created if they don't already exist. @@ -1242,1608 +1135,1467 @@ class CSimpleIniTempl @param a_bCopyStrings Should copies of the strings be made or not. If false then the pointers will be used as is. */ - SI_Error AddEntry( - const SI_CHAR * a_pSection, - const SI_CHAR * a_pKey, - const SI_CHAR * a_pValue, - const SI_CHAR * a_pComment, - bool a_bForceReplace, - bool a_bCopyStrings - ); - - /** Is the supplied character a whitespace character? */ - inline bool IsSpace(SI_CHAR ch) const { - return (ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n'); - } - - /** Does the supplied character start a comment line? */ - inline bool IsComment(SI_CHAR ch) const { - return (ch == ';' || ch == '#'); - } - - - /** Skip over a newline character (or characters) for either DOS or UNIX */ - inline void SkipNewLine(SI_CHAR *& a_pData) const { - a_pData += (*a_pData == '\r' && *(a_pData+1) == '\n') ? 2 : 1; - } - - /** Make a copy of the supplied string, replacing the original pointer */ - SI_Error CopyString(const SI_CHAR *& a_pString); - - /** Delete a string from the copied strings buffer if necessary */ - void DeleteString(const SI_CHAR * a_pString); - - /** Internal use of our string comparison function */ - bool IsLess(const SI_CHAR * a_pLeft, const SI_CHAR * a_pRight) const { - const static SI_STRLESS isLess = SI_STRLESS(); - return isLess(a_pLeft, a_pRight); - } - - bool IsMultiLineTag(const SI_CHAR * a_pData) const; - bool IsMultiLineData(const SI_CHAR * a_pData) const; - bool IsSingleLineQuotedValue(const SI_CHAR* a_pData) const; - bool LoadMultiLineText( - SI_CHAR *& a_pData, - const SI_CHAR *& a_pVal, - const SI_CHAR * a_pTagName, - bool a_bAllowBlankLinesInComment = false - ) const; - bool IsNewLineChar(SI_CHAR a_c) const; - - bool OutputMultiLineText( - OutputWriter & a_oOutput, - Converter & a_oConverter, - const SI_CHAR * a_pText - ) const; + SI_Error AddEntry(const SI_CHAR *a_pSection, const SI_CHAR *a_pKey, + const SI_CHAR *a_pValue, const SI_CHAR *a_pComment, + bool a_bForceReplace, bool a_bCopyStrings); + + /** Is the supplied character a whitespace character? */ + inline bool IsSpace(SI_CHAR ch) const { + return (ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n'); + } + + /** Does the supplied character start a comment line? */ + inline bool IsComment(SI_CHAR ch) const { return (ch == ';' || ch == '#'); } + + /** Skip over a newline character (or characters) for either DOS or UNIX */ + inline void SkipNewLine(SI_CHAR *&a_pData) const { + a_pData += (*a_pData == '\r' && *(a_pData + 1) == '\n') ? 2 : 1; + } + + /** Make a copy of the supplied string, replacing the original pointer */ + SI_Error CopyString(const SI_CHAR *&a_pString); + + /** Delete a string from the copied strings buffer if necessary */ + void DeleteString(const SI_CHAR *a_pString); + + /** Internal use of our string comparison function */ + bool IsLess(const SI_CHAR *a_pLeft, const SI_CHAR *a_pRight) const { + const static SI_STRLESS isLess = SI_STRLESS(); + return isLess(a_pLeft, a_pRight); + } + + bool IsMultiLineTag(const SI_CHAR *a_pData) const; + bool IsMultiLineData(const SI_CHAR *a_pData) const; + bool IsSingleLineQuotedValue(const SI_CHAR *a_pData) const; + bool LoadMultiLineText(SI_CHAR *&a_pData, const SI_CHAR *&a_pVal, + const SI_CHAR *a_pTagName, + bool a_bAllowBlankLinesInComment = false) const; + bool IsNewLineChar(SI_CHAR a_c) const; + + bool OutputMultiLineText(OutputWriter &a_oOutput, Converter &a_oConverter, + const SI_CHAR *a_pText) const; private: - /** Copy of the INI file data in our character format. This will be + /** Copy of the INI file data in our character format. This will be modified when parsed to have NULL characters added after all interesting string entries. All of the string pointers to sections, keys and values point into this block of memory. */ - SI_CHAR * m_pData; + SI_CHAR *m_pData; - /** Length of the data that we have stored. Used when deleting strings + /** Length of the data that we have stored. Used when deleting strings to determine if the string is stored here or in the allocated string buffer. */ - size_t m_uDataLen; + size_t m_uDataLen; - /** File comment for this data, if one exists. */ - const SI_CHAR * m_pFileComment; + /** File comment for this data, if one exists. */ + const SI_CHAR *m_pFileComment; - /** constant empty string */ - const SI_CHAR m_cEmptyString; + /** constant empty string */ + const SI_CHAR m_cEmptyString; - /** Parsed INI data. Section -> (Key -> Value). */ - TSection m_data; + /** Parsed INI data. Section -> (Key -> Value). */ + TSection m_data; - /** This vector stores allocated memory for copies of strings that have + /** This vector stores allocated memory for copies of strings that have been supplied after the file load. It will be empty unless SetValue() has been called. */ - TNamesDepend m_strings; + TNamesDepend m_strings; - /** Is the format of our datafile UTF-8 or MBCS? */ - bool m_bStoreIsUtf8; + /** Is the format of our datafile UTF-8 or MBCS? */ + bool m_bStoreIsUtf8; - /** Are multiple values permitted for the same key? */ - bool m_bAllowMultiKey; + /** Are multiple values permitted for the same key? */ + bool m_bAllowMultiKey; - /** Are data values permitted to span multiple lines? */ - bool m_bAllowMultiLine; + /** Are data values permitted to span multiple lines? */ + bool m_bAllowMultiLine; - /** Should spaces be written out surrounding the equals sign? */ - bool m_bSpaces; - - /** Should quoted data in values be recognized and parsed? */ - bool m_bParseQuotes; + /** Should spaces be written out surrounding the equals sign? */ + bool m_bSpaces; + + /** Should quoted data in values be recognized and parsed? */ + bool m_bParseQuotes; - /** Do keys always need to have an equals sign when reading/writing? */ - bool m_bAllowKeyOnly; + /** Do keys always need to have an equals sign when reading/writing? */ + bool m_bAllowKeyOnly; - /** Next order value, used to ensure sections and keys are output in the + /** Next order value, used to ensure sections and keys are output in the same order that they are loaded/added. */ - int m_nOrder; + int m_nOrder; }; // --------------------------------------------------------------------------- // IMPLEMENTATION // --------------------------------------------------------------------------- -template -CSimpleIniTempl::CSimpleIniTempl( - bool a_bIsUtf8, - bool a_bAllowMultiKey, - bool a_bAllowMultiLine - ) - : m_pData(0) - , m_uDataLen(0) - , m_pFileComment(NULL) - , m_cEmptyString(0) - , m_bStoreIsUtf8(a_bIsUtf8) - , m_bAllowMultiKey(a_bAllowMultiKey) - , m_bAllowMultiLine(a_bAllowMultiLine) - , m_bSpaces(true) - , m_bParseQuotes(false) - , m_bAllowKeyOnly(false) - , m_nOrder(0) -{ } - -template -CSimpleIniTempl::~CSimpleIniTempl() -{ - Reset(); +template +CSimpleIniTempl::CSimpleIniTempl( + bool a_bIsUtf8, bool a_bAllowMultiKey, bool a_bAllowMultiLine) + : m_pData(0), m_uDataLen(0), m_pFileComment(NULL), m_cEmptyString(0), + m_bStoreIsUtf8(a_bIsUtf8), m_bAllowMultiKey(a_bAllowMultiKey), + m_bAllowMultiLine(a_bAllowMultiLine), m_bSpaces(true), + m_bParseQuotes(false), m_bAllowKeyOnly(false), m_nOrder(0) {} + +template +CSimpleIniTempl::~CSimpleIniTempl() { + Reset(); } -template -void -CSimpleIniTempl::Reset() -{ - // remove all data - delete[] m_pData; - m_pData = NULL; - m_uDataLen = 0; - m_pFileComment = NULL; - if (!m_data.empty()) { - m_data.erase(m_data.begin(), m_data.end()); - } - - // remove all strings - if (!m_strings.empty()) { - typename TNamesDepend::iterator i = m_strings.begin(); - for (; i != m_strings.end(); ++i) { - delete[] const_cast(i->pItem); - } - m_strings.erase(m_strings.begin(), m_strings.end()); - } +template +void CSimpleIniTempl::Reset() { + // remove all data + delete[] m_pData; + m_pData = NULL; + m_uDataLen = 0; + m_pFileComment = NULL; + if (!m_data.empty()) { + m_data.erase(m_data.begin(), m_data.end()); + } + + // remove all strings + if (!m_strings.empty()) { + typename TNamesDepend::iterator i = m_strings.begin(); + for (; i != m_strings.end(); ++i) { + delete[] const_cast(i->pItem); + } + m_strings.erase(m_strings.begin(), m_strings.end()); + } } -template -SI_Error -CSimpleIniTempl::LoadFile( - const char * a_pszFile - ) -{ - FILE * fp = NULL; +template +SI_Error CSimpleIniTempl::LoadFile( + const char *a_pszFile) { + FILE *fp = NULL; #if __STDC_WANT_SECURE_LIB__ && !_WIN32_WCE - fopen_s(&fp, a_pszFile, "rb"); -#else // !__STDC_WANT_SECURE_LIB__ - fp = fopen(a_pszFile, "rb"); + fopen_s(&fp, a_pszFile, "rb"); +#else // !__STDC_WANT_SECURE_LIB__ + fp = fopen(a_pszFile, "rb"); #endif // __STDC_WANT_SECURE_LIB__ - if (!fp) { - return SI_FILE; - } - SI_Error rc = LoadFile(fp); - fclose(fp); - return rc; + if (!fp) { + return SI_FILE; + } + SI_Error rc = LoadFile(fp); + fclose(fp); + return rc; } #ifdef SI_HAS_WIDE_FILE -template -SI_Error -CSimpleIniTempl::LoadFile( - const SI_WCHAR_T * a_pwszFile - ) -{ +template +SI_Error CSimpleIniTempl::LoadFile( + const SI_WCHAR_T *a_pwszFile) { #ifdef _WIN32 - FILE * fp = NULL; + FILE *fp = NULL; #if __STDC_WANT_SECURE_LIB__ && !_WIN32_WCE - _wfopen_s(&fp, a_pwszFile, L"rb"); -#else // !__STDC_WANT_SECURE_LIB__ - fp = _wfopen(a_pwszFile, L"rb"); + _wfopen_s(&fp, a_pwszFile, L"rb"); +#else // !__STDC_WANT_SECURE_LIB__ + fp = _wfopen(a_pwszFile, L"rb"); #endif // __STDC_WANT_SECURE_LIB__ - if (!fp) return SI_FILE; - SI_Error rc = LoadFile(fp); - fclose(fp); - return rc; -#else // !_WIN32 (therefore SI_CONVERT_ICU) - char szFile[256]; - u_austrncpy(szFile, a_pwszFile, sizeof(szFile)); - return LoadFile(szFile); + if (!fp) + return SI_FILE; + SI_Error rc = LoadFile(fp); + fclose(fp); + return rc; +#else // !_WIN32 (therefore SI_CONVERT_ICU) + char szFile[256]; + u_austrncpy(szFile, a_pwszFile, sizeof(szFile)); + return LoadFile(szFile); #endif // _WIN32 } #endif // SI_HAS_WIDE_FILE -template +template SI_Error -CSimpleIniTempl::LoadFile( - FILE * a_fpFile - ) -{ - // load the raw file data - int retval = fseek(a_fpFile, 0, SEEK_END); - if (retval != 0) { - return SI_FILE; - } - long lSize = ftell(a_fpFile); - if (lSize < 0) { - return SI_FILE; - } - if (lSize == 0) { - return SI_OK; - } - - // check file size is within supported limits (SI_MAX_FILE_SIZE) - if (static_cast(lSize) > SI_MAX_FILE_SIZE) { - return SI_FILE; - } +CSimpleIniTempl::LoadFile(FILE *a_fpFile) { + // load the raw file data + int retval = fseek(a_fpFile, 0, SEEK_END); + if (retval != 0) { + return SI_FILE; + } + long lSize = ftell(a_fpFile); + if (lSize < 0) { + return SI_FILE; + } + if (lSize == 0) { + return SI_OK; + } + + // check file size is within supported limits (SI_MAX_FILE_SIZE) + if (static_cast(lSize) > SI_MAX_FILE_SIZE) { + return SI_FILE; + } + + // allocate and ensure NULL terminated + char *pData = new (std::nothrow) char[static_cast(lSize) + 1]; + if (!pData) { + return SI_NOMEM; + } + pData[lSize] = 0; + + // load data into buffer + fseek(a_fpFile, 0, SEEK_SET); + size_t uRead = fread(pData, sizeof(char), lSize, a_fpFile); + if (uRead != (size_t)lSize) { + delete[] pData; + return SI_FILE; + } - // allocate and ensure NULL terminated - char * pData = new(std::nothrow) char[static_cast(lSize) + 1]; - if (!pData) { - return SI_NOMEM; - } - pData[lSize] = 0; - - // load data into buffer - fseek(a_fpFile, 0, SEEK_SET); - size_t uRead = fread(pData, sizeof(char), lSize, a_fpFile); - if (uRead != (size_t) lSize) { - delete[] pData; - return SI_FILE; - } + // convert the raw data to unicode + SI_Error rc = LoadData(pData, uRead); + delete[] pData; + return rc; +} - // convert the raw data to unicode - SI_Error rc = LoadData(pData, uRead); +template +SI_Error CSimpleIniTempl::LoadData( + const char *a_pData, size_t a_uDataLen) { + if (!a_pData) { + return SI_OK; + } + + // if the UTF-8 BOM exists, consume it and set mode to unicode, if we have + // already loaded data and try to change mode half-way through then this will + // be ignored and we will assert in debug versions + if (a_uDataLen >= 3 && memcmp(a_pData, SI_UTF8_SIGNATURE, 3) == 0) { + a_pData += 3; + a_uDataLen -= 3; + SI_ASSERT(m_bStoreIsUtf8 || !m_pData); // we don't expect mixed mode data + SetUnicode(); + } + + if (a_uDataLen == 0) { + return SI_OK; + } + + // determine the length of the converted data + SI_CONVERTER converter(m_bStoreIsUtf8); + size_t uLen = converter.SizeFromStore(a_pData, a_uDataLen); + if (uLen == (size_t)(-1)) { + return SI_FAIL; + } + + // check converted data size is within supported limits (SI_MAX_FILE_SIZE) + if (uLen >= (SI_MAX_FILE_SIZE / sizeof(SI_CHAR))) { + return SI_FILE; + } + + // allocate memory for the data, ensure that there is a NULL + // terminator wherever the converted data ends + SI_CHAR *pData = new (std::nothrow) SI_CHAR[uLen + 1]; + if (!pData) { + return SI_NOMEM; + } + memset(pData, 0, sizeof(SI_CHAR) * (uLen + 1)); + + // convert the data + if (!converter.ConvertFromStore(a_pData, a_uDataLen, pData, uLen)) { delete[] pData; + return SI_FAIL; + } + + // parse it + const static SI_CHAR empty = 0; + SI_CHAR *pWork = pData; + const SI_CHAR *pSection = ∅ + const SI_CHAR *pItem = NULL; + const SI_CHAR *pVal = NULL; + const SI_CHAR *pComment = NULL; + + // We copy the strings if we are loading data into this class when we + // already have stored some. + bool bCopyStrings = (m_pData != NULL); + + // find a file comment if it exists, this is a comment that starts at the + // beginning of the file and continues until the first blank line. + SI_Error rc = FindFileComment(pWork, bCopyStrings); + if (rc < 0) return rc; -} -template -SI_Error -CSimpleIniTempl::LoadData( - const char * a_pData, - size_t a_uDataLen - ) -{ - if (!a_pData) { - return SI_OK; - } - - // if the UTF-8 BOM exists, consume it and set mode to unicode, if we have - // already loaded data and try to change mode half-way through then this will - // be ignored and we will assert in debug versions - if (a_uDataLen >= 3 && memcmp(a_pData, SI_UTF8_SIGNATURE, 3) == 0) { - a_pData += 3; - a_uDataLen -= 3; - SI_ASSERT(m_bStoreIsUtf8 || !m_pData); // we don't expect mixed mode data - SetUnicode(); - } + // add every entry in the file to the data table + while (FindEntry(pWork, pSection, pItem, pVal, pComment)) { + rc = AddEntry(pSection, pItem, pVal, pComment, false, bCopyStrings); + if (rc < 0) + return rc; + } - if (a_uDataLen == 0) { - return SI_OK; - } + // store these strings if we didn't copy them + if (bCopyStrings) { + delete[] pData; + } else { + m_pData = pData; + m_uDataLen = uLen + 1; + } - // determine the length of the converted data - SI_CONVERTER converter(m_bStoreIsUtf8); - size_t uLen = converter.SizeFromStore(a_pData, a_uDataLen); - if (uLen == (size_t)(-1)) { - return SI_FAIL; - } + return SI_OK; +} - // check converted data size is within supported limits (SI_MAX_FILE_SIZE) - if (uLen >= (SI_MAX_FILE_SIZE / sizeof(SI_CHAR))) { - return SI_FILE; - } +#ifdef SI_SUPPORT_IOSTREAMS +template +SI_Error CSimpleIniTempl::LoadData( + std::istream &a_istream) { + std::string strData; + char szBuf[512]; + do { + a_istream.get(szBuf, sizeof(szBuf), '\0'); + strData.append(szBuf); + } while (a_istream.good()); + return LoadData(strData); +} +#endif // SI_SUPPORT_IOSTREAMS - // allocate memory for the data, ensure that there is a NULL - // terminator wherever the converted data ends - SI_CHAR * pData = new(std::nothrow) SI_CHAR[uLen + 1]; - if (!pData) { - return SI_NOMEM; - } - memset(pData, 0, sizeof(SI_CHAR) * (uLen + 1)); +template +SI_Error CSimpleIniTempl::FindFileComment( + SI_CHAR *&a_pData, bool a_bCopyStrings) { + // there can only be a single file comment + if (m_pFileComment) { + return SI_OK; + } - // convert the data - if (!converter.ConvertFromStore(a_pData, a_uDataLen, pData, uLen)) { - delete[] pData; - return SI_FAIL; - } + // Load the file comment as multi-line text, this will modify all of + // the newline characters to be single \n chars + if (!LoadMultiLineText(a_pData, m_pFileComment, NULL, false)) { + return SI_OK; + } - // parse it - const static SI_CHAR empty = 0; - SI_CHAR * pWork = pData; - const SI_CHAR * pSection = ∅ - const SI_CHAR * pItem = NULL; - const SI_CHAR * pVal = NULL; - const SI_CHAR * pComment = NULL; + // copy the string if necessary + if (a_bCopyStrings) { + SI_Error rc = CopyString(m_pFileComment); + if (rc < 0) + return rc; + } - // We copy the strings if we are loading data into this class when we - // already have stored some. - bool bCopyStrings = (m_pData != NULL); + return SI_OK; +} - // find a file comment if it exists, this is a comment that starts at the - // beginning of the file and continues until the first blank line. - SI_Error rc = FindFileComment(pWork, bCopyStrings); - if (rc < 0) return rc; +template +bool CSimpleIniTempl::FindEntry( + SI_CHAR *&a_pData, const SI_CHAR *&a_pSection, const SI_CHAR *&a_pKey, + const SI_CHAR *&a_pVal, const SI_CHAR *&a_pComment) const { + a_pComment = NULL; - // add every entry in the file to the data table - while (FindEntry(pWork, pSection, pItem, pVal, pComment)) { - rc = AddEntry(pSection, pItem, pVal, pComment, false, bCopyStrings); - if (rc < 0) return rc; + bool bHaveValue = false; + SI_CHAR *pTrail = NULL; + while (*a_pData) { + // skip spaces and empty lines + while (*a_pData && IsSpace(*a_pData)) { + ++a_pData; } - - // store these strings if we didn't copy them - if (bCopyStrings) { - delete[] pData; + if (!*a_pData) { + break; } - else { - m_pData = pData; - m_uDataLen = uLen+1; + + // skip processing of comment lines but keep a pointer to + // the start of the comment. + if (IsComment(*a_pData)) { + LoadMultiLineText(a_pData, a_pComment, NULL, true); + continue; } - return SI_OK; -} + // process section names + if (*a_pData == '[') { + // skip leading spaces + ++a_pData; + while (*a_pData && IsSpace(*a_pData)) { + ++a_pData; + } -#ifdef SI_SUPPORT_IOSTREAMS -template -SI_Error -CSimpleIniTempl::LoadData( - std::istream & a_istream - ) -{ - std::string strData; - char szBuf[512]; - do { - a_istream.get(szBuf, sizeof(szBuf), '\0'); - strData.append(szBuf); - } - while (a_istream.good()); - return LoadData(strData); -} -#endif // SI_SUPPORT_IOSTREAMS + // find the end of the section name (it may contain spaces) + // and convert it to lowercase as necessary + a_pSection = a_pData; + while (*a_pData && *a_pData != ']' && !IsNewLineChar(*a_pData)) { + ++a_pData; + } + + // if it's an invalid line, just skip it + if (*a_pData != ']') { + continue; + } + + // remove trailing spaces from the section + pTrail = a_pData - 1; + while (pTrail >= a_pSection && IsSpace(*pTrail)) { + --pTrail; + } + ++pTrail; + *pTrail = 0; + + // skip to the end of the line + ++a_pData; // safe as checked that it == ']' above + while (*a_pData && !IsNewLineChar(*a_pData)) { + ++a_pData; + } -template -SI_Error -CSimpleIniTempl::FindFileComment( - SI_CHAR *& a_pData, - bool a_bCopyStrings - ) -{ - // there can only be a single file comment - if (m_pFileComment) { - return SI_OK; + a_pKey = NULL; + a_pVal = NULL; + return true; } - // Load the file comment as multi-line text, this will modify all of - // the newline characters to be single \n chars - if (!LoadMultiLineText(a_pData, m_pFileComment, NULL, false)) { - return SI_OK; + // find the end of the key name (it may contain spaces) + a_pKey = a_pData; + while (*a_pData && *a_pData != '=' && !IsNewLineChar(*a_pData)) { + ++a_pData; } + // *a_pData is null, equals, or newline - // copy the string if necessary - if (a_bCopyStrings) { - SI_Error rc = CopyString(m_pFileComment); - if (rc < 0) return rc; + // if no value and we don't allow no value, then invalid + bHaveValue = (*a_pData == '='); + if (!bHaveValue && !m_bAllowKeyOnly) { + continue; } - return SI_OK; -} + // empty keys are invalid + if (bHaveValue && a_pKey == a_pData) { + while (*a_pData && !IsNewLineChar(*a_pData)) { + ++a_pData; + } + continue; + } -template -bool -CSimpleIniTempl::FindEntry( - SI_CHAR *& a_pData, - const SI_CHAR *& a_pSection, - const SI_CHAR *& a_pKey, - const SI_CHAR *& a_pVal, - const SI_CHAR *& a_pComment - ) const -{ - a_pComment = NULL; - - bool bHaveValue = false; - SI_CHAR * pTrail = NULL; - while (*a_pData) { - // skip spaces and empty lines - while (*a_pData && IsSpace(*a_pData)) { - ++a_pData; - } - if (!*a_pData) { - break; - } + // remove trailing spaces from the key + pTrail = a_pData - 1; + while (pTrail >= a_pKey && IsSpace(*pTrail)) { + --pTrail; + } + ++pTrail; - // skip processing of comment lines but keep a pointer to - // the start of the comment. - if (IsComment(*a_pData)) { - LoadMultiLineText(a_pData, a_pComment, NULL, true); - continue; - } + if (bHaveValue) { + // process the value + *pTrail = 0; - // process section names - if (*a_pData == '[') { - // skip leading spaces - ++a_pData; - while (*a_pData && IsSpace(*a_pData)) { - ++a_pData; - } + // skip leading whitespace on the value + ++a_pData; // safe as checked that it == '=' above + while (*a_pData && !IsNewLineChar(*a_pData) && IsSpace(*a_pData)) { + ++a_pData; + } - // find the end of the section name (it may contain spaces) - // and convert it to lowercase as necessary - a_pSection = a_pData; - while (*a_pData && *a_pData != ']' && !IsNewLineChar(*a_pData)) { - ++a_pData; - } + // find the end of the value which is the end of this line + a_pVal = a_pData; + while (*a_pData && !IsNewLineChar(*a_pData)) { + ++a_pData; + } - // if it's an invalid line, just skip it - if (*a_pData != ']') { - continue; - } + // remove trailing spaces from the value + pTrail = a_pData - 1; + if (*a_pData) { // prepare for the next round + SkipNewLine(a_pData); + } + while (pTrail >= a_pVal && IsSpace(*pTrail)) { + --pTrail; + } + ++pTrail; + *pTrail = 0; + + // check for multi-line entries + if (m_bAllowMultiLine && IsMultiLineTag(a_pVal)) { + // skip the "<<<" to get the tag that will end the multiline + const SI_CHAR *pTagName = a_pVal + 3; + return LoadMultiLineText(a_pData, a_pVal, pTagName); + } + + // check for quoted values, we are not supporting escapes in quoted values (yet) + if (m_bParseQuotes) { + --pTrail; + if (pTrail > a_pVal && *a_pVal == '"' && *pTrail == '"') { + ++a_pVal; + *pTrail = 0; + } + } + } else { + // no value to process, just prepare for the next + if (*a_pData) { + SkipNewLine(a_pData); + } + *pTrail = 0; + } - // remove trailing spaces from the section - pTrail = a_pData - 1; - while (pTrail >= a_pSection && IsSpace(*pTrail)) { - --pTrail; - } - ++pTrail; - *pTrail = 0; + // return the standard entry + return true; + } - // skip to the end of the line - ++a_pData; // safe as checked that it == ']' above - while (*a_pData && !IsNewLineChar(*a_pData)) { - ++a_pData; - } + return false; +} - a_pKey = NULL; - a_pVal = NULL; - return true; - } +template +bool CSimpleIniTempl::IsMultiLineTag( + const SI_CHAR *a_pVal) const { + // check for the "<<<" prefix for a multi-line entry + if (*a_pVal++ != '<') + return false; + if (*a_pVal++ != '<') + return false; + if (*a_pVal++ != '<') + return false; + return true; +} - // find the end of the key name (it may contain spaces) - a_pKey = a_pData; - while (*a_pData && *a_pData != '=' && !IsNewLineChar(*a_pData)) { - ++a_pData; - } - // *a_pData is null, equals, or newline +template +bool CSimpleIniTempl::IsMultiLineData( + const SI_CHAR *a_pData) const { + // data is multi-line if it has any of the following features: + // * whitespace prefix + // * embedded newlines + // * whitespace suffix - // if no value and we don't allow no value, then invalid - bHaveValue = (*a_pData == '='); - if (!bHaveValue && !m_bAllowKeyOnly) { - continue; - } + // empty string + if (!*a_pData) { + return false; + } - // empty keys are invalid - if (bHaveValue && a_pKey == a_pData) { - while (*a_pData && !IsNewLineChar(*a_pData)) { - ++a_pData; - } - continue; - } + // check for prefix + if (IsSpace(*a_pData)) { + return true; + } - // remove trailing spaces from the key - pTrail = a_pData - 1; - while (pTrail >= a_pKey && IsSpace(*pTrail)) { - --pTrail; - } - ++pTrail; + // embedded newlines + const SI_CHAR *pStart = a_pData; + while (*a_pData) { + if (IsNewLineChar(*a_pData)) { + return true; + } + ++a_pData; + } - if (bHaveValue) { - // process the value - *pTrail = 0; + // check for suffix (ensure we don't go before start of string) + if (a_pData > pStart && IsSpace(*(a_pData - 1))) { + return true; + } - // skip leading whitespace on the value - ++a_pData; // safe as checked that it == '=' above - while (*a_pData && !IsNewLineChar(*a_pData) && IsSpace(*a_pData)) { - ++a_pData; - } + return false; +} - // find the end of the value which is the end of this line - a_pVal = a_pData; - while (*a_pData && !IsNewLineChar(*a_pData)) { - ++a_pData; - } +template +bool CSimpleIniTempl:: + IsSingleLineQuotedValue(const SI_CHAR *a_pData) const { + // data needs quoting if it starts or ends with whitespace + // and doesn't have embedded newlines - // remove trailing spaces from the value - pTrail = a_pData - 1; - if (*a_pData) { // prepare for the next round - SkipNewLine(a_pData); - } - while (pTrail >= a_pVal && IsSpace(*pTrail)) { - --pTrail; - } - ++pTrail; - *pTrail = 0; - - // check for multi-line entries - if (m_bAllowMultiLine && IsMultiLineTag(a_pVal)) { - // skip the "<<<" to get the tag that will end the multiline - const SI_CHAR* pTagName = a_pVal + 3; - return LoadMultiLineText(a_pData, a_pVal, pTagName); - } + // empty string + if (!*a_pData) { + return false; + } - // check for quoted values, we are not supporting escapes in quoted values (yet) - if (m_bParseQuotes) { - --pTrail; - if (pTrail > a_pVal && *a_pVal == '"' && *pTrail == '"') { - ++a_pVal; - *pTrail = 0; - } - } - } - else { - // no value to process, just prepare for the next - if (*a_pData) { - SkipNewLine(a_pData); - } - *pTrail = 0; - } + // check for prefix + if (IsSpace(*a_pData)) { + return true; + } - // return the standard entry - return true; + // embedded newlines + const SI_CHAR *pStart = a_pData; + while (*a_pData) { + if (IsNewLineChar(*a_pData)) { + return false; } + ++a_pData; + } - return false; + // check for suffix (ensure we don't go before start of string) + if (a_pData > pStart && IsSpace(*(a_pData - 1))) { + return true; + } + + return false; } -template -bool -CSimpleIniTempl::IsMultiLineTag( - const SI_CHAR * a_pVal - ) const -{ - // check for the "<<<" prefix for a multi-line entry - if (*a_pVal++ != '<') return false; - if (*a_pVal++ != '<') return false; - if (*a_pVal++ != '<') return false; - return true; +template +bool CSimpleIniTempl::IsNewLineChar( + SI_CHAR a_c) const { + return (a_c == '\n' || a_c == '\r'); } -template -bool -CSimpleIniTempl::IsMultiLineData( - const SI_CHAR * a_pData - ) const -{ - // data is multi-line if it has any of the following features: - // * whitespace prefix - // * embedded newlines - // * whitespace suffix - - // empty string - if (!*a_pData) { - return false; - } +template +bool CSimpleIniTempl::LoadMultiLineText( + SI_CHAR *&a_pData, const SI_CHAR *&a_pVal, const SI_CHAR *a_pTagName, + bool a_bAllowBlankLinesInComment) const { + // we modify this data to strip all newlines down to a single '\n' + // character. This means that on Windows we need to strip out some + // characters which will make the data shorter. + // i.e. LINE1-LINE1\r\nLINE2-LINE2\0 will become + // LINE1-LINE1\nLINE2-LINE2\0 + // The pDataLine entry is the pointer to the location in memory that + // the current line needs to start to run following the existing one. + // This may be the same as pCurrLine in which case no move is needed. + SI_CHAR *pDataLine = a_pData; + SI_CHAR *pCurrLine; + + // value starts at the current line + a_pVal = a_pData; + + // find the end tag. This tag must start in column 1 and be + // followed by a newline. We ignore any whitespace after the end + // tag but not whitespace before it. + SI_CHAR cEndOfLineChar = *a_pData; + for (;;) { + // if we are loading comments then we need a comment character as + // the first character on every line + if (!a_pTagName && !IsComment(*a_pData)) { + // if we aren't allowing blank lines then we're done + if (!a_bAllowBlankLinesInComment) { + break; + } + + // if we are allowing blank lines then we only include them + // in this comment if another comment follows, so read ahead + // to find out. + SI_CHAR *pCurr = a_pData; + int nNewLines = 0; + while (IsSpace(*pCurr)) { + if (IsNewLineChar(*pCurr)) { + ++nNewLines; + SkipNewLine(pCurr); + } else { + ++pCurr; + } + } + + // we have a comment, add the blank lines to the output + // and continue processing from here + if (IsComment(*pCurr)) { + for (; nNewLines > 0; --nNewLines) + *pDataLine++ = '\n'; + a_pData = pCurr; + continue; + } + + // the comment ends here + break; + } + + // find the end of this line + pCurrLine = a_pData; + while (*a_pData && !IsNewLineChar(*a_pData)) + ++a_pData; + + // move this line down to the location that it should be if necessary + if (pDataLine < pCurrLine) { + size_t nLen = (size_t)(a_pData - pCurrLine); + memmove(pDataLine, pCurrLine, nLen * sizeof(SI_CHAR)); + pDataLine[nLen] = '\0'; + } + + // end the line with a NULL + cEndOfLineChar = *a_pData; + *a_pData = 0; + + // if are looking for a tag then do the check now. This is done before + // checking for end of the data, so that if we have the tag at the end + // of the data then the tag is removed correctly. + if (a_pTagName) { + // strip whitespace from the end of this tag + SI_CHAR *pc = a_pData - 1; + while (pc > pDataLine && IsSpace(*pc)) + --pc; + SI_CHAR ch = *++pc; + *pc = 0; + + if (!IsLess(pDataLine, a_pTagName) && !IsLess(a_pTagName, pDataLine)) { + break; + } - // check for prefix - if (IsSpace(*a_pData)) { - return true; + *pc = ch; } - // embedded newlines - const SI_CHAR * pStart = a_pData; - while (*a_pData) { - if (IsNewLineChar(*a_pData)) { - return true; - } - ++a_pData; + // if we are at the end of the data then we just automatically end + // this entry and return the current data. + if (!cEndOfLineChar) { + return true; } - // check for suffix (ensure we don't go before start of string) - if (a_pData > pStart && IsSpace(*(a_pData - 1))) { - return true; - } + // otherwise we need to process this newline to ensure that it consists + // of just a single \n character. + pDataLine += (a_pData - pCurrLine); + *a_pData = cEndOfLineChar; + SkipNewLine(a_pData); + *pDataLine++ = '\n'; + } + // if we didn't find a comment at all then return false + if (a_pVal == a_pData) { + a_pVal = NULL; return false; + } + + // the data (which ends at the end of the last line) needs to be + // null-terminated BEFORE before the newline character(s). If the + // user wants a new line in the multi-line data then they need to + // add an empty line before the tag. + *--pDataLine = '\0'; + + // if looking for a tag and if we aren't at the end of the data, + // then move a_pData to the start of the next line. + if (a_pTagName && cEndOfLineChar) { + SI_ASSERT(IsNewLineChar(cEndOfLineChar)); + *a_pData = cEndOfLineChar; + SkipNewLine(a_pData); + } + + return true; } -template -bool -CSimpleIniTempl::IsSingleLineQuotedValue( - const SI_CHAR* a_pData -) const -{ - // data needs quoting if it starts or ends with whitespace - // and doesn't have embedded newlines +template +SI_Error CSimpleIniTempl::CopyString( + const SI_CHAR *&a_pString) { + size_t uLen = 0; + if (sizeof(SI_CHAR) == sizeof(char)) { + uLen = strlen((const char *)a_pString); + } else if (sizeof(SI_CHAR) == sizeof(wchar_t)) { + uLen = wcslen((const wchar_t *)a_pString); + } else { + for (; a_pString[uLen]; ++uLen) /*loop*/ + ; + } + ++uLen; // NULL character + SI_CHAR *pCopy = new (std::nothrow) SI_CHAR[uLen]; + if (!pCopy) { + return SI_NOMEM; + } + memcpy(pCopy, a_pString, sizeof(SI_CHAR) * uLen); + m_strings.push_back(pCopy); + a_pString = pCopy; + return SI_OK; +} - // empty string - if (!*a_pData) { - return false; +template +SI_Error CSimpleIniTempl::AddEntry( + const SI_CHAR *a_pSection, const SI_CHAR *a_pKey, const SI_CHAR *a_pValue, + const SI_CHAR *a_pComment, bool a_bForceReplace, bool a_bCopyStrings) { + SI_Error rc; + bool bInserted = false; + + SI_ASSERT(!a_pComment || IsComment(*a_pComment)); + + // if we are copying strings then make a copy of the comment now + // because we will need it when we add the entry. + if (a_bCopyStrings && a_pComment) { + rc = CopyString(a_pComment); + if (rc < 0) + return rc; + } + + // create the section entry if necessary + typename TSection::iterator iSection = m_data.find(a_pSection); + if (iSection == m_data.end()) { + // if the section doesn't exist then we need a copy as the + // string needs to last beyond the end of this function + if (a_bCopyStrings) { + rc = CopyString(a_pSection); + if (rc < 0) + return rc; } - // check for prefix - if (IsSpace(*a_pData)) { - return true; + // only set the comment if this is a section only entry + Entry oSection(a_pSection, ++m_nOrder); + if (a_pComment && !a_pKey) { + oSection.pComment = a_pComment; } - // embedded newlines - const SI_CHAR * pStart = a_pData; - while (*a_pData) { - if (IsNewLineChar(*a_pData)) { - return false; - } - ++a_pData; - } + typename TSection::value_type oEntry(oSection, TKeyVal()); + typedef typename TSection::iterator SectionIterator; + std::pair i = m_data.insert(oEntry); + iSection = i.first; + bInserted = true; + } + if (!a_pKey) { + // section only entries are specified with pItem as NULL + return bInserted ? SI_INSERTED : SI_UPDATED; + } + + // check for existence of the key + TKeyVal &keyval = iSection->second; + typename TKeyVal::iterator iKey = keyval.find(a_pKey); + bInserted = iKey == keyval.end(); + + // remove all existing entries but save the load order and + // comment of the first entry + int nLoadOrder = ++m_nOrder; + if (iKey != keyval.end() && m_bAllowMultiKey && a_bForceReplace) { + const SI_CHAR *pComment = NULL; + while (iKey != keyval.end() && !IsLess(a_pKey, iKey->first.pItem)) { + if (iKey->first.nOrder < nLoadOrder) { + nLoadOrder = iKey->first.nOrder; + pComment = iKey->first.pComment; + } + ++iKey; + } + if (pComment) { + DeleteString(a_pComment); + a_pComment = pComment; + rc = CopyString(a_pComment); + if (rc < 0) + return rc; + } + Delete(a_pSection, a_pKey); + iKey = keyval.end(); + } + + // values need to be a valid string, even if they are an empty string + if (!a_pValue) { + a_pValue = &m_cEmptyString; + } + + // make string copies if necessary + bool bForceCreateNewKey = m_bAllowMultiKey && !a_bForceReplace; + if (a_bCopyStrings) { + if (bForceCreateNewKey || iKey == keyval.end()) { + // if the key doesn't exist then we need a copy as the + // string needs to last beyond the end of this function + // because we will be inserting the key next + rc = CopyString(a_pKey); + if (rc < 0) + return rc; + } + + // we always need a copy of the value + rc = CopyString(a_pValue); + if (rc < 0) + return rc; + } + + // create the key entry + if (iKey == keyval.end() || bForceCreateNewKey) { + Entry oKey(a_pKey, nLoadOrder); + if (a_pComment) { + oKey.pComment = a_pComment; + } + typename TKeyVal::value_type oEntry(oKey, + static_cast(NULL)); + iKey = keyval.insert(oEntry); + } + + iKey->second = a_pValue; + return bInserted ? SI_INSERTED : SI_UPDATED; +} - // check for suffix (ensure we don't go before start of string) - if (a_pData > pStart && IsSpace(*(a_pData - 1))) { - return true; - } +template +const SI_CHAR *CSimpleIniTempl::GetValue( + const SI_CHAR *a_pSection, const SI_CHAR *a_pKey, const SI_CHAR *a_pDefault, + bool *a_pHasMultiple) const { + if (a_pHasMultiple) { + *a_pHasMultiple = false; + } + if (!a_pSection || !a_pKey) { + return a_pDefault; + } + typename TSection::const_iterator iSection = m_data.find(a_pSection); + if (iSection == m_data.end()) { + return a_pDefault; + } + typename TKeyVal::const_iterator iKeyVal = iSection->second.find(a_pKey); + if (iKeyVal == iSection->second.end()) { + return a_pDefault; + } + + // check for multiple entries with the same key + if (m_bAllowMultiKey && a_pHasMultiple) { + typename TKeyVal::const_iterator iTemp = iKeyVal; + if (++iTemp != iSection->second.end()) { + if (!IsLess(a_pKey, iTemp->first.pItem)) { + *a_pHasMultiple = true; + } + } + } + + return iKeyVal->second; +} - return false; +template +long CSimpleIniTempl::GetLongValue( + const SI_CHAR *a_pSection, const SI_CHAR *a_pKey, long a_nDefault, + bool *a_pHasMultiple) const { + // return the default if we don't have a value + const SI_CHAR *pszValue = GetValue(a_pSection, a_pKey, NULL, a_pHasMultiple); + if (!pszValue || !*pszValue) + return a_nDefault; + + // convert to UTF-8/MBCS which for a numeric value will be the same as ASCII + char szValue[64] = {0}; + SI_CONVERTER c(m_bStoreIsUtf8); + if (!c.ConvertToStore(pszValue, szValue, sizeof(szValue))) { + return a_nDefault; + } + + // handle the value as hex if prefaced with "0x" + long nValue = a_nDefault; + char *pszSuffix = szValue; + if (szValue[0] == '0' && (szValue[1] == 'x' || szValue[1] == 'X')) { + if (!szValue[2]) + return a_nDefault; + nValue = strtol(&szValue[2], &pszSuffix, 16); + } else { + nValue = strtol(szValue, &pszSuffix, 10); + } + + // any invalid strings will return the default value + if (*pszSuffix) { + return a_nDefault; + } + + return nValue; } -template -bool -CSimpleIniTempl::IsNewLineChar( - SI_CHAR a_c - ) const -{ - return (a_c == '\n' || a_c == '\r'); +template +SI_Error CSimpleIniTempl::SetLongValue( + const SI_CHAR *a_pSection, const SI_CHAR *a_pKey, long a_nValue, + const SI_CHAR *a_pComment, bool a_bUseHex, bool a_bForceReplace) { + // use SetValue to create sections + if (!a_pSection || !a_pKey) + return SI_FAIL; + + // convert to an ASCII string + char szInput[64]; +#if __STDC_WANT_SECURE_LIB__ && !_WIN32_WCE + sprintf_s(szInput, a_bUseHex ? "0x%lx" : "%ld", a_nValue); +#else // !__STDC_WANT_SECURE_LIB__ + snprintf(szInput, sizeof(szInput), a_bUseHex ? "0x%lx" : "%ld", a_nValue); +#endif // __STDC_WANT_SECURE_LIB__ + + // convert to output text + SI_CHAR szOutput[64]; + SI_CONVERTER c(m_bStoreIsUtf8); + c.ConvertFromStore(szInput, strlen(szInput) + 1, szOutput, + sizeof(szOutput) / sizeof(SI_CHAR)); + + // actually add it + return AddEntry(a_pSection, a_pKey, szOutput, a_pComment, a_bForceReplace, + true); } -template -bool -CSimpleIniTempl::LoadMultiLineText( - SI_CHAR *& a_pData, - const SI_CHAR *& a_pVal, - const SI_CHAR * a_pTagName, - bool a_bAllowBlankLinesInComment - ) const -{ - // we modify this data to strip all newlines down to a single '\n' - // character. This means that on Windows we need to strip out some - // characters which will make the data shorter. - // i.e. LINE1-LINE1\r\nLINE2-LINE2\0 will become - // LINE1-LINE1\nLINE2-LINE2\0 - // The pDataLine entry is the pointer to the location in memory that - // the current line needs to start to run following the existing one. - // This may be the same as pCurrLine in which case no move is needed. - SI_CHAR * pDataLine = a_pData; - SI_CHAR * pCurrLine; - - // value starts at the current line - a_pVal = a_pData; - - // find the end tag. This tag must start in column 1 and be - // followed by a newline. We ignore any whitespace after the end - // tag but not whitespace before it. - SI_CHAR cEndOfLineChar = *a_pData; - for(;;) { - // if we are loading comments then we need a comment character as - // the first character on every line - if (!a_pTagName && !IsComment(*a_pData)) { - // if we aren't allowing blank lines then we're done - if (!a_bAllowBlankLinesInComment) { - break; - } +template +double CSimpleIniTempl::GetDoubleValue( + const SI_CHAR *a_pSection, const SI_CHAR *a_pKey, double a_nDefault, + bool *a_pHasMultiple) const { + // return the default if we don't have a value + const SI_CHAR *pszValue = GetValue(a_pSection, a_pKey, NULL, a_pHasMultiple); + if (!pszValue || !*pszValue) + return a_nDefault; + + // convert to UTF-8/MBCS which for a numeric value will be the same as ASCII + char szValue[64] = {0}; + SI_CONVERTER c(m_bStoreIsUtf8); + if (!c.ConvertToStore(pszValue, szValue, sizeof(szValue))) { + return a_nDefault; + } + + char *pszSuffix = szValue; + double nValue = strtod(szValue, &pszSuffix); + + // any invalid strings will return the default value + // check if no conversion was performed or if there are trailing characters + if (pszSuffix == szValue || *pszSuffix) { + return a_nDefault; + } + + return nValue; +} - // if we are allowing blank lines then we only include them - // in this comment if another comment follows, so read ahead - // to find out. - SI_CHAR * pCurr = a_pData; - int nNewLines = 0; - while (IsSpace(*pCurr)) { - if (IsNewLineChar(*pCurr)) { - ++nNewLines; - SkipNewLine(pCurr); - } - else { - ++pCurr; - } - } +template +SI_Error CSimpleIniTempl::SetDoubleValue( + const SI_CHAR *a_pSection, const SI_CHAR *a_pKey, double a_nValue, + const SI_CHAR *a_pComment, bool a_bForceReplace) { + // use SetValue to create sections + if (!a_pSection || !a_pKey) + return SI_FAIL; - // we have a comment, add the blank lines to the output - // and continue processing from here - if (IsComment(*pCurr)) { - for (; nNewLines > 0; --nNewLines) *pDataLine++ = '\n'; - a_pData = pCurr; - continue; - } + // convert to an ASCII string + char szInput[64]; +#if __STDC_WANT_SECURE_LIB__ && !_WIN32_WCE + sprintf_s(szInput, "%f", a_nValue); +#else // !__STDC_WANT_SECURE_LIB__ + snprintf(szInput, sizeof(szInput), "%f", a_nValue); +#endif // __STDC_WANT_SECURE_LIB__ - // the comment ends here - break; - } + // convert to output text + SI_CHAR szOutput[64]; + SI_CONVERTER c(m_bStoreIsUtf8); + c.ConvertFromStore(szInput, strlen(szInput) + 1, szOutput, + sizeof(szOutput) / sizeof(SI_CHAR)); - // find the end of this line - pCurrLine = a_pData; - while (*a_pData && !IsNewLineChar(*a_pData)) ++a_pData; + // actually add it + return AddEntry(a_pSection, a_pKey, szOutput, a_pComment, a_bForceReplace, + true); +} - // move this line down to the location that it should be if necessary - if (pDataLine < pCurrLine) { - size_t nLen = (size_t) (a_pData - pCurrLine); - memmove(pDataLine, pCurrLine, nLen * sizeof(SI_CHAR)); - pDataLine[nLen] = '\0'; - } +template +bool CSimpleIniTempl::GetBoolValue( + const SI_CHAR *a_pSection, const SI_CHAR *a_pKey, bool a_bDefault, + bool *a_pHasMultiple) const { + // return the default if we don't have a value + const SI_CHAR *pszValue = GetValue(a_pSection, a_pKey, NULL, a_pHasMultiple); + if (!pszValue || !*pszValue) + return a_bDefault; - // end the line with a NULL - cEndOfLineChar = *a_pData; - *a_pData = 0; - - // if are looking for a tag then do the check now. This is done before - // checking for end of the data, so that if we have the tag at the end - // of the data then the tag is removed correctly. - if (a_pTagName) { - // strip whitespace from the end of this tag - SI_CHAR* pc = a_pData - 1; - while (pc > pDataLine && IsSpace(*pc)) --pc; - SI_CHAR ch = *++pc; - *pc = 0; - - if (!IsLess(pDataLine, a_pTagName) && !IsLess(a_pTagName, pDataLine)) { - break; - } + // we only look at the minimum number of characters + switch (pszValue[0]) { + case 't': + case 'T': // true + case 'y': + case 'Y': // yes + case '1': // 1 (one) + return true; - *pc = ch; - } + case 'f': + case 'F': // false + case 'n': + case 'N': // no + case '0': // 0 (zero) + return false; - // if we are at the end of the data then we just automatically end - // this entry and return the current data. - if (!cEndOfLineChar) { - return true; - } + case 'o': + case 'O': + if (pszValue[1] == 'n' || pszValue[1] == 'N') + return true; // on + if (pszValue[1] == 'f' || pszValue[1] == 'F') + return false; // off + break; + } + + // no recognized value, return the default + return a_bDefault; +} - // otherwise we need to process this newline to ensure that it consists - // of just a single \n character. - pDataLine += (a_pData - pCurrLine); - *a_pData = cEndOfLineChar; - SkipNewLine(a_pData); - *pDataLine++ = '\n'; - } +template +SI_Error CSimpleIniTempl::SetBoolValue( + const SI_CHAR *a_pSection, const SI_CHAR *a_pKey, bool a_bValue, + const SI_CHAR *a_pComment, bool a_bForceReplace) { + // use SetValue to create sections + if (!a_pSection || !a_pKey) + return SI_FAIL; + + // convert to an ASCII string + const char *pszInput = a_bValue ? "true" : "false"; + + // convert to output text + SI_CHAR szOutput[64]; + SI_CONVERTER c(m_bStoreIsUtf8); + c.ConvertFromStore(pszInput, strlen(pszInput) + 1, szOutput, + sizeof(szOutput) / sizeof(SI_CHAR)); + + // actually add it + return AddEntry(a_pSection, a_pKey, szOutput, a_pComment, a_bForceReplace, + true); +} - // if we didn't find a comment at all then return false - if (a_pVal == a_pData) { - a_pVal = NULL; - return false; - } +template +bool CSimpleIniTempl::GetAllValues( + const SI_CHAR *a_pSection, const SI_CHAR *a_pKey, + TNamesDepend &a_values) const { + a_values.clear(); - // the data (which ends at the end of the last line) needs to be - // null-terminated BEFORE before the newline character(s). If the - // user wants a new line in the multi-line data then they need to - // add an empty line before the tag. - *--pDataLine = '\0'; + if (!a_pSection || !a_pKey) { + return false; + } + typename TSection::const_iterator iSection = m_data.find(a_pSection); + if (iSection == m_data.end()) { + return false; + } + typename TKeyVal::const_iterator iKeyVal = iSection->second.find(a_pKey); + if (iKeyVal == iSection->second.end()) { + return false; + } + + // insert all values for this key + a_values.push_back( + Entry(iKeyVal->second, iKeyVal->first.pComment, iKeyVal->first.nOrder)); + if (m_bAllowMultiKey) { + ++iKeyVal; + while (iKeyVal != iSection->second.end() && + !IsLess(a_pKey, iKeyVal->first.pItem)) { + a_values.push_back(Entry(iKeyVal->second, iKeyVal->first.pComment, + iKeyVal->first.nOrder)); + ++iKeyVal; + } + } + + return true; +} - // if looking for a tag and if we aren't at the end of the data, - // then move a_pData to the start of the next line. - if (a_pTagName && cEndOfLineChar) { - SI_ASSERT(IsNewLineChar(cEndOfLineChar)); - *a_pData = cEndOfLineChar; - SkipNewLine(a_pData); - } +template +int CSimpleIniTempl::GetSectionSize( + const SI_CHAR *a_pSection) const { + if (!a_pSection) { + return -1; + } + + typename TSection::const_iterator iSection = m_data.find(a_pSection); + if (iSection == m_data.end()) { + return -1; + } + const TKeyVal §ion = iSection->second; + + // if multi-key isn't permitted then the section size is + // the number of keys that we have. + if (!m_bAllowMultiKey || section.empty()) { + return (int)section.size(); + } + + // otherwise we need to count them + int nCount = 0; + const SI_CHAR *pLastKey = NULL; + typename TKeyVal::const_iterator iKeyVal = section.begin(); + for (; iKeyVal != section.end(); ++iKeyVal) { + if (!pLastKey || IsLess(pLastKey, iKeyVal->first.pItem)) { + ++nCount; + pLastKey = iKeyVal->first.pItem; + } + } + return nCount; +} - return true; +template +const typename CSimpleIniTempl::TKeyVal * +CSimpleIniTempl::GetSection( + const SI_CHAR *a_pSection) const { + if (a_pSection) { + typename TSection::const_iterator i = m_data.find(a_pSection); + if (i != m_data.end()) { + return &(i->second); + } + } + return 0; } -template -SI_Error -CSimpleIniTempl::CopyString( - const SI_CHAR *& a_pString - ) -{ - size_t uLen = 0; - if (sizeof(SI_CHAR) == sizeof(char)) { - uLen = strlen((const char *)a_pString); - } - else if (sizeof(SI_CHAR) == sizeof(wchar_t)) { - uLen = wcslen((const wchar_t *)a_pString); - } - else { - for ( ; a_pString[uLen]; ++uLen) /*loop*/ ; - } - ++uLen; // NULL character - SI_CHAR * pCopy = new(std::nothrow) SI_CHAR[uLen]; - if (!pCopy) { - return SI_NOMEM; - } - memcpy(pCopy, a_pString, sizeof(SI_CHAR)*uLen); - m_strings.push_back(pCopy); - a_pString = pCopy; - return SI_OK; +template +void CSimpleIniTempl::GetAllSections( + TNamesDepend &a_names) const { + a_names.clear(); + typename TSection::const_iterator i = m_data.begin(); + for (; i != m_data.end(); ++i) { + a_names.push_back(i->first); + } } -template -SI_Error -CSimpleIniTempl::AddEntry( - const SI_CHAR * a_pSection, - const SI_CHAR * a_pKey, - const SI_CHAR * a_pValue, - const SI_CHAR * a_pComment, - bool a_bForceReplace, - bool a_bCopyStrings - ) -{ - SI_Error rc; - bool bInserted = false; - - SI_ASSERT(!a_pComment || IsComment(*a_pComment)); - - // if we are copying strings then make a copy of the comment now - // because we will need it when we add the entry. - if (a_bCopyStrings && a_pComment) { - rc = CopyString(a_pComment); - if (rc < 0) return rc; - } - - // create the section entry if necessary - typename TSection::iterator iSection = m_data.find(a_pSection); - if (iSection == m_data.end()) { - // if the section doesn't exist then we need a copy as the - // string needs to last beyond the end of this function - if (a_bCopyStrings) { - rc = CopyString(a_pSection); - if (rc < 0) return rc; - } +template +bool CSimpleIniTempl::GetAllKeys( + const SI_CHAR *a_pSection, TNamesDepend &a_names) const { + a_names.clear(); - // only set the comment if this is a section only entry - Entry oSection(a_pSection, ++m_nOrder); - if (a_pComment && !a_pKey) { - oSection.pComment = a_pComment; - } + if (!a_pSection) { + return false; + } - typename TSection::value_type oEntry(oSection, TKeyVal()); - typedef typename TSection::iterator SectionIterator; - std::pair i = m_data.insert(oEntry); - iSection = i.first; - bInserted = true; - } - if (!a_pKey) { - // section only entries are specified with pItem as NULL - return bInserted ? SI_INSERTED : SI_UPDATED; - } - - // check for existence of the key - TKeyVal & keyval = iSection->second; - typename TKeyVal::iterator iKey = keyval.find(a_pKey); - bInserted = iKey == keyval.end(); - - // remove all existing entries but save the load order and - // comment of the first entry - int nLoadOrder = ++m_nOrder; - if (iKey != keyval.end() && m_bAllowMultiKey && a_bForceReplace) { - const SI_CHAR * pComment = NULL; - while (iKey != keyval.end() && !IsLess(a_pKey, iKey->first.pItem)) { - if (iKey->first.nOrder < nLoadOrder) { - nLoadOrder = iKey->first.nOrder; - pComment = iKey->first.pComment; - } - ++iKey; - } - if (pComment) { - DeleteString(a_pComment); - a_pComment = pComment; - rc = CopyString(a_pComment); - if (rc < 0) return rc; - } - Delete(a_pSection, a_pKey); - iKey = keyval.end(); - } + typename TSection::const_iterator iSection = m_data.find(a_pSection); + if (iSection == m_data.end()) { + return false; + } - // values need to be a valid string, even if they are an empty string - if (!a_pValue) { - a_pValue = &m_cEmptyString; + const TKeyVal §ion = iSection->second; + const SI_CHAR *pLastKey = NULL; + typename TKeyVal::const_iterator iKeyVal = section.begin(); + for (; iKeyVal != section.end(); ++iKeyVal) { + if (!pLastKey || IsLess(pLastKey, iKeyVal->first.pItem)) { + a_names.push_back(iKeyVal->first); + pLastKey = iKeyVal->first.pItem; } + } - // make string copies if necessary - bool bForceCreateNewKey = m_bAllowMultiKey && !a_bForceReplace; - if (a_bCopyStrings) { - if (bForceCreateNewKey || iKey == keyval.end()) { - // if the key doesn't exist then we need a copy as the - // string needs to last beyond the end of this function - // because we will be inserting the key next - rc = CopyString(a_pKey); - if (rc < 0) return rc; - } - - // we always need a copy of the value - rc = CopyString(a_pValue); - if (rc < 0) return rc; - } - - // create the key entry - if (iKey == keyval.end() || bForceCreateNewKey) { - Entry oKey(a_pKey, nLoadOrder); - if (a_pComment) { - oKey.pComment = a_pComment; - } - typename TKeyVal::value_type oEntry(oKey, static_cast(NULL)); - iKey = keyval.insert(oEntry); - } - - iKey->second = a_pValue; - return bInserted ? SI_INSERTED : SI_UPDATED; -} - -template -const SI_CHAR * -CSimpleIniTempl::GetValue( - const SI_CHAR * a_pSection, - const SI_CHAR * a_pKey, - const SI_CHAR * a_pDefault, - bool * a_pHasMultiple - ) const -{ - if (a_pHasMultiple) { - *a_pHasMultiple = false; - } - if (!a_pSection || !a_pKey) { - return a_pDefault; - } - typename TSection::const_iterator iSection = m_data.find(a_pSection); - if (iSection == m_data.end()) { - return a_pDefault; - } - typename TKeyVal::const_iterator iKeyVal = iSection->second.find(a_pKey); - if (iKeyVal == iSection->second.end()) { - return a_pDefault; - } - - // check for multiple entries with the same key - if (m_bAllowMultiKey && a_pHasMultiple) { - typename TKeyVal::const_iterator iTemp = iKeyVal; - if (++iTemp != iSection->second.end()) { - if (!IsLess(a_pKey, iTemp->first.pItem)) { - *a_pHasMultiple = true; - } - } - } - - return iKeyVal->second; -} - -template -long -CSimpleIniTempl::GetLongValue( - const SI_CHAR * a_pSection, - const SI_CHAR * a_pKey, - long a_nDefault, - bool * a_pHasMultiple - ) const -{ - // return the default if we don't have a value - const SI_CHAR * pszValue = GetValue(a_pSection, a_pKey, NULL, a_pHasMultiple); - if (!pszValue || !*pszValue) return a_nDefault; - - // convert to UTF-8/MBCS which for a numeric value will be the same as ASCII - char szValue[64] = { 0 }; - SI_CONVERTER c(m_bStoreIsUtf8); - if (!c.ConvertToStore(pszValue, szValue, sizeof(szValue))) { - return a_nDefault; - } - - // handle the value as hex if prefaced with "0x" - long nValue = a_nDefault; - char * pszSuffix = szValue; - if (szValue[0] == '0' && (szValue[1] == 'x' || szValue[1] == 'X')) { - if (!szValue[2]) return a_nDefault; - nValue = strtol(&szValue[2], &pszSuffix, 16); - } - else { - nValue = strtol(szValue, &pszSuffix, 10); - } - - // any invalid strings will return the default value - if (*pszSuffix) { - return a_nDefault; - } - - return nValue; + return true; } -template -SI_Error -CSimpleIniTempl::SetLongValue( - const SI_CHAR * a_pSection, - const SI_CHAR * a_pKey, - long a_nValue, - const SI_CHAR * a_pComment, - bool a_bUseHex, - bool a_bForceReplace - ) -{ - // use SetValue to create sections - if (!a_pSection || !a_pKey) return SI_FAIL; - - // convert to an ASCII string - char szInput[64]; +template +SI_Error CSimpleIniTempl::SaveFile( + const char *a_pszFile, bool a_bAddSignature) const { + FILE *fp = NULL; #if __STDC_WANT_SECURE_LIB__ && !_WIN32_WCE - sprintf_s(szInput, a_bUseHex ? "0x%lx" : "%ld", a_nValue); -#else // !__STDC_WANT_SECURE_LIB__ - snprintf(szInput, sizeof(szInput), a_bUseHex ? "0x%lx" : "%ld", a_nValue); + fopen_s(&fp, a_pszFile, "wb"); +#else // !__STDC_WANT_SECURE_LIB__ + fp = fopen(a_pszFile, "wb"); #endif // __STDC_WANT_SECURE_LIB__ - - // convert to output text - SI_CHAR szOutput[64]; - SI_CONVERTER c(m_bStoreIsUtf8); - c.ConvertFromStore(szInput, strlen(szInput) + 1, - szOutput, sizeof(szOutput) / sizeof(SI_CHAR)); - - // actually add it - return AddEntry(a_pSection, a_pKey, szOutput, a_pComment, a_bForceReplace, true); -} - -template -double -CSimpleIniTempl::GetDoubleValue( - const SI_CHAR * a_pSection, - const SI_CHAR * a_pKey, - double a_nDefault, - bool * a_pHasMultiple - ) const -{ - // return the default if we don't have a value - const SI_CHAR * pszValue = GetValue(a_pSection, a_pKey, NULL, a_pHasMultiple); - if (!pszValue || !*pszValue) return a_nDefault; - - // convert to UTF-8/MBCS which for a numeric value will be the same as ASCII - char szValue[64] = { 0 }; - SI_CONVERTER c(m_bStoreIsUtf8); - if (!c.ConvertToStore(pszValue, szValue, sizeof(szValue))) { - return a_nDefault; - } - - char * pszSuffix = szValue; - double nValue = strtod(szValue, &pszSuffix); - - // any invalid strings will return the default value - // check if no conversion was performed or if there are trailing characters - if (pszSuffix == szValue || *pszSuffix) { - return a_nDefault; - } - - return nValue; -} - -template -SI_Error -CSimpleIniTempl::SetDoubleValue( - const SI_CHAR * a_pSection, - const SI_CHAR * a_pKey, - double a_nValue, - const SI_CHAR * a_pComment, - bool a_bForceReplace - ) -{ - // use SetValue to create sections - if (!a_pSection || !a_pKey) return SI_FAIL; - - // convert to an ASCII string - char szInput[64]; -#if __STDC_WANT_SECURE_LIB__ && !_WIN32_WCE - sprintf_s(szInput, "%f", a_nValue); -#else // !__STDC_WANT_SECURE_LIB__ - snprintf(szInput, sizeof(szInput), "%f", a_nValue); -#endif // __STDC_WANT_SECURE_LIB__ - - // convert to output text - SI_CHAR szOutput[64]; - SI_CONVERTER c(m_bStoreIsUtf8); - c.ConvertFromStore(szInput, strlen(szInput) + 1, - szOutput, sizeof(szOutput) / sizeof(SI_CHAR)); - - // actually add it - return AddEntry(a_pSection, a_pKey, szOutput, a_pComment, a_bForceReplace, true); -} - -template -bool -CSimpleIniTempl::GetBoolValue( - const SI_CHAR * a_pSection, - const SI_CHAR * a_pKey, - bool a_bDefault, - bool * a_pHasMultiple - ) const -{ - // return the default if we don't have a value - const SI_CHAR * pszValue = GetValue(a_pSection, a_pKey, NULL, a_pHasMultiple); - if (!pszValue || !*pszValue) return a_bDefault; - - // we only look at the minimum number of characters - switch (pszValue[0]) { - case 't': case 'T': // true - case 'y': case 'Y': // yes - case '1': // 1 (one) - return true; - - case 'f': case 'F': // false - case 'n': case 'N': // no - case '0': // 0 (zero) - return false; - - case 'o': case 'O': - if (pszValue[1] == 'n' || pszValue[1] == 'N') return true; // on - if (pszValue[1] == 'f' || pszValue[1] == 'F') return false; // off - break; - } - - // no recognized value, return the default - return a_bDefault; -} - -template -SI_Error -CSimpleIniTempl::SetBoolValue( - const SI_CHAR * a_pSection, - const SI_CHAR * a_pKey, - bool a_bValue, - const SI_CHAR * a_pComment, - bool a_bForceReplace - ) -{ - // use SetValue to create sections - if (!a_pSection || !a_pKey) return SI_FAIL; - - // convert to an ASCII string - const char * pszInput = a_bValue ? "true" : "false"; - - // convert to output text - SI_CHAR szOutput[64]; - SI_CONVERTER c(m_bStoreIsUtf8); - c.ConvertFromStore(pszInput, strlen(pszInput) + 1, - szOutput, sizeof(szOutput) / sizeof(SI_CHAR)); - - // actually add it - return AddEntry(a_pSection, a_pKey, szOutput, a_pComment, a_bForceReplace, true); -} - -template -bool -CSimpleIniTempl::GetAllValues( - const SI_CHAR * a_pSection, - const SI_CHAR * a_pKey, - TNamesDepend & a_values - ) const -{ - a_values.clear(); - - if (!a_pSection || !a_pKey) { - return false; - } - typename TSection::const_iterator iSection = m_data.find(a_pSection); - if (iSection == m_data.end()) { - return false; - } - typename TKeyVal::const_iterator iKeyVal = iSection->second.find(a_pKey); - if (iKeyVal == iSection->second.end()) { - return false; - } - - // insert all values for this key - a_values.push_back(Entry(iKeyVal->second, iKeyVal->first.pComment, iKeyVal->first.nOrder)); - if (m_bAllowMultiKey) { - ++iKeyVal; - while (iKeyVal != iSection->second.end() && !IsLess(a_pKey, iKeyVal->first.pItem)) { - a_values.push_back(Entry(iKeyVal->second, iKeyVal->first.pComment, iKeyVal->first.nOrder)); - ++iKeyVal; - } - } - - return true; -} - -template -int -CSimpleIniTempl::GetSectionSize( - const SI_CHAR * a_pSection - ) const -{ - if (!a_pSection) { - return -1; - } - - typename TSection::const_iterator iSection = m_data.find(a_pSection); - if (iSection == m_data.end()) { - return -1; - } - const TKeyVal & section = iSection->second; - - // if multi-key isn't permitted then the section size is - // the number of keys that we have. - if (!m_bAllowMultiKey || section.empty()) { - return (int) section.size(); - } - - // otherwise we need to count them - int nCount = 0; - const SI_CHAR * pLastKey = NULL; - typename TKeyVal::const_iterator iKeyVal = section.begin(); - for (; iKeyVal != section.end(); ++iKeyVal) { - if (!pLastKey || IsLess(pLastKey, iKeyVal->first.pItem)) { - ++nCount; - pLastKey = iKeyVal->first.pItem; - } - } - return nCount; -} - -template -const typename CSimpleIniTempl::TKeyVal * -CSimpleIniTempl::GetSection( - const SI_CHAR * a_pSection - ) const -{ - if (a_pSection) { - typename TSection::const_iterator i = m_data.find(a_pSection); - if (i != m_data.end()) { - return &(i->second); - } - } - return 0; -} - -template -void -CSimpleIniTempl::GetAllSections( - TNamesDepend & a_names - ) const -{ - a_names.clear(); - typename TSection::const_iterator i = m_data.begin(); - for (; i != m_data.end(); ++i) { - a_names.push_back(i->first); - } -} - -template -bool -CSimpleIniTempl::GetAllKeys( - const SI_CHAR * a_pSection, - TNamesDepend & a_names - ) const -{ - a_names.clear(); - - if (!a_pSection) { - return false; - } - - typename TSection::const_iterator iSection = m_data.find(a_pSection); - if (iSection == m_data.end()) { - return false; - } - - const TKeyVal & section = iSection->second; - const SI_CHAR * pLastKey = NULL; - typename TKeyVal::const_iterator iKeyVal = section.begin(); - for (; iKeyVal != section.end(); ++iKeyVal) { - if (!pLastKey || IsLess(pLastKey, iKeyVal->first.pItem)) { - a_names.push_back(iKeyVal->first); - pLastKey = iKeyVal->first.pItem; - } - } - - return true; -} - -template -SI_Error -CSimpleIniTempl::SaveFile( - const char * a_pszFile, - bool a_bAddSignature - ) const -{ - FILE * fp = NULL; -#if __STDC_WANT_SECURE_LIB__ && !_WIN32_WCE - fopen_s(&fp, a_pszFile, "wb"); -#else // !__STDC_WANT_SECURE_LIB__ - fp = fopen(a_pszFile, "wb"); -#endif // __STDC_WANT_SECURE_LIB__ - if (!fp) return SI_FILE; - SI_Error rc = SaveFile(fp, a_bAddSignature); - fclose(fp); - return rc; + if (!fp) + return SI_FILE; + SI_Error rc = SaveFile(fp, a_bAddSignature); + fclose(fp); + return rc; } #ifdef SI_HAS_WIDE_FILE -template -SI_Error -CSimpleIniTempl::SaveFile( - const SI_WCHAR_T * a_pwszFile, - bool a_bAddSignature - ) const -{ +template +SI_Error CSimpleIniTempl::SaveFile( + const SI_WCHAR_T *a_pwszFile, bool a_bAddSignature) const { #ifdef _WIN32 - FILE * fp = NULL; + FILE *fp = NULL; #if __STDC_WANT_SECURE_LIB__ && !_WIN32_WCE - _wfopen_s(&fp, a_pwszFile, L"wb"); -#else // !__STDC_WANT_SECURE_LIB__ - fp = _wfopen(a_pwszFile, L"wb"); + _wfopen_s(&fp, a_pwszFile, L"wb"); +#else // !__STDC_WANT_SECURE_LIB__ + fp = _wfopen(a_pwszFile, L"wb"); #endif // __STDC_WANT_SECURE_LIB__ - if (!fp) return SI_FILE; - SI_Error rc = SaveFile(fp, a_bAddSignature); - fclose(fp); - return rc; -#else // !_WIN32 (therefore SI_CONVERT_ICU) - char szFile[256]; - u_austrncpy(szFile, a_pwszFile, sizeof(szFile)); - return SaveFile(szFile, a_bAddSignature); + if (!fp) + return SI_FILE; + SI_Error rc = SaveFile(fp, a_bAddSignature); + fclose(fp); + return rc; +#else // !_WIN32 (therefore SI_CONVERT_ICU) + char szFile[256]; + u_austrncpy(szFile, a_pwszFile, sizeof(szFile)); + return SaveFile(szFile, a_bAddSignature); #endif // _WIN32 } #endif // SI_HAS_WIDE_FILE -template -SI_Error -CSimpleIniTempl::SaveFile( - FILE * a_pFile, - bool a_bAddSignature - ) const -{ - FileWriter writer(a_pFile); - return Save(writer, a_bAddSignature); +template +SI_Error CSimpleIniTempl::SaveFile( + FILE *a_pFile, bool a_bAddSignature) const { + FileWriter writer(a_pFile); + return Save(writer, a_bAddSignature); } -template -SI_Error -CSimpleIniTempl::Save( - OutputWriter & a_oOutput, - bool a_bAddSignature - ) const -{ - Converter convert(m_bStoreIsUtf8); +template +SI_Error CSimpleIniTempl::Save( + OutputWriter &a_oOutput, bool a_bAddSignature) const { + Converter convert(m_bStoreIsUtf8); - // add the UTF-8 signature if it is desired - if (m_bStoreIsUtf8 && a_bAddSignature) { - a_oOutput.Write(SI_UTF8_SIGNATURE); - } + // add the UTF-8 signature if it is desired + if (m_bStoreIsUtf8 && a_bAddSignature) { + a_oOutput.Write(SI_UTF8_SIGNATURE); + } - // get all of the sections sorted in load order - TNamesDepend oSections; - GetAllSections(oSections); + // get all of the sections sorted in load order + TNamesDepend oSections; + GetAllSections(oSections); #if defined(_MSC_VER) && _MSC_VER <= 1200 - oSections.sort(); + oSections.sort(); #elif defined(__BORLANDC__) - oSections.sort(Entry::LoadOrder()); + oSections.sort(Entry::LoadOrder()); #else - oSections.sort(typename Entry::LoadOrder()); + oSections.sort(typename Entry::LoadOrder()); #endif - // if there is an empty section name, then it must be written out first - // regardless of the load order - typename TNamesDepend::iterator is = oSections.begin(); - for (; is != oSections.end(); ++is) { - if (!*is->pItem) { - // move the empty section name to the front of the section list - if (is != oSections.begin()) { - oSections.splice(oSections.begin(), oSections, is, std::next(is)); - } - break; - } + // if there is an empty section name, then it must be written out first + // regardless of the load order + typename TNamesDepend::iterator is = oSections.begin(); + for (; is != oSections.end(); ++is) { + if (!*is->pItem) { + // move the empty section name to the front of the section list + if (is != oSections.begin()) { + oSections.splice(oSections.begin(), oSections, is, std::next(is)); + } + break; + } + } + + // write the file comment if we have one + bool bNeedNewLine = false; + if (m_pFileComment) { + if (!OutputMultiLineText(a_oOutput, convert, m_pFileComment)) { + return SI_FAIL; + } + bNeedNewLine = true; + } + + // iterate through our sections and output the data + typename TNamesDepend::const_iterator iSection = oSections.begin(); + for (; iSection != oSections.end(); ++iSection) { + // write out the comment if there is one + if (iSection->pComment) { + if (bNeedNewLine) { + a_oOutput.Write(SI_NEWLINE_A); + a_oOutput.Write(SI_NEWLINE_A); + } + if (!OutputMultiLineText(a_oOutput, convert, iSection->pComment)) { + return SI_FAIL; + } + bNeedNewLine = false; } - // write the file comment if we have one - bool bNeedNewLine = false; - if (m_pFileComment) { - if (!OutputMultiLineText(a_oOutput, convert, m_pFileComment)) { - return SI_FAIL; - } - bNeedNewLine = true; + if (bNeedNewLine) { + a_oOutput.Write(SI_NEWLINE_A); + a_oOutput.Write(SI_NEWLINE_A); + bNeedNewLine = false; } - // iterate through our sections and output the data - typename TNamesDepend::const_iterator iSection = oSections.begin(); - for ( ; iSection != oSections.end(); ++iSection ) { - // write out the comment if there is one - if (iSection->pComment) { - if (bNeedNewLine) { - a_oOutput.Write(SI_NEWLINE_A); - a_oOutput.Write(SI_NEWLINE_A); - } - if (!OutputMultiLineText(a_oOutput, convert, iSection->pComment)) { - return SI_FAIL; - } - bNeedNewLine = false; - } - - if (bNeedNewLine) { - a_oOutput.Write(SI_NEWLINE_A); - a_oOutput.Write(SI_NEWLINE_A); - bNeedNewLine = false; - } - - // write the section (unless there is no section name) - if (*iSection->pItem) { - if (!convert.ConvertToStore(iSection->pItem)) { - return SI_FAIL; - } - a_oOutput.Write("["); - a_oOutput.Write(convert.Data()); - a_oOutput.Write("]"); - a_oOutput.Write(SI_NEWLINE_A); - } + // write the section (unless there is no section name) + if (*iSection->pItem) { + if (!convert.ConvertToStore(iSection->pItem)) { + return SI_FAIL; + } + a_oOutput.Write("["); + a_oOutput.Write(convert.Data()); + a_oOutput.Write("]"); + a_oOutput.Write(SI_NEWLINE_A); + } - // get all of the keys sorted in load order - TNamesDepend oKeys; - GetAllKeys(iSection->pItem, oKeys); + // get all of the keys sorted in load order + TNamesDepend oKeys; + GetAllKeys(iSection->pItem, oKeys); #if defined(_MSC_VER) && _MSC_VER <= 1200 - oKeys.sort(); + oKeys.sort(); #elif defined(__BORLANDC__) - oKeys.sort(Entry::LoadOrder()); + oKeys.sort(Entry::LoadOrder()); #else - oKeys.sort(typename Entry::LoadOrder()); + oKeys.sort(typename Entry::LoadOrder()); #endif - // write all keys and values - typename TNamesDepend::const_iterator iKey = oKeys.begin(); - for ( ; iKey != oKeys.end(); ++iKey) { - // get all values for this key - TNamesDepend oValues; - GetAllValues(iSection->pItem, iKey->pItem, oValues); - - typename TNamesDepend::const_iterator iValue = oValues.begin(); - for ( ; iValue != oValues.end(); ++iValue) { - // write out the comment if there is one - if (iValue->pComment) { - a_oOutput.Write(SI_NEWLINE_A); - if (!OutputMultiLineText(a_oOutput, convert, iValue->pComment)) { - return SI_FAIL; - } - } - - // write the key - if (!convert.ConvertToStore(iKey->pItem)) { - return SI_FAIL; - } - a_oOutput.Write(convert.Data()); - - // write the value as long - if (*iValue->pItem || !m_bAllowKeyOnly) { - if (!convert.ConvertToStore(iValue->pItem)) { - return SI_FAIL; - } - a_oOutput.Write(m_bSpaces ? " = " : "="); - if (m_bParseQuotes && IsSingleLineQuotedValue(iValue->pItem)) { - // the only way to preserve external whitespace on a value (i.e. before or after) - // is to quote it. This is simple quoting, we don't escape quotes within the data. - a_oOutput.Write("\""); - a_oOutput.Write(convert.Data()); - a_oOutput.Write("\""); - } - else if (m_bAllowMultiLine && IsMultiLineData(iValue->pItem)) { - // multi-line data needs to be processed specially to ensure - // that we use the correct newline format for the current system - a_oOutput.Write("<<pItem)) { - return SI_FAIL; - } - a_oOutput.Write("END_OF_TEXT"); - } - else { - a_oOutput.Write(convert.Data()); - } - } - a_oOutput.Write(SI_NEWLINE_A); - } - } + // write all keys and values + typename TNamesDepend::const_iterator iKey = oKeys.begin(); + for (; iKey != oKeys.end(); ++iKey) { + // get all values for this key + TNamesDepend oValues; + GetAllValues(iSection->pItem, iKey->pItem, oValues); - bNeedNewLine = true; - } + typename TNamesDepend::const_iterator iValue = oValues.begin(); + for (; iValue != oValues.end(); ++iValue) { + // write out the comment if there is one + if (iValue->pComment) { + a_oOutput.Write(SI_NEWLINE_A); + if (!OutputMultiLineText(a_oOutput, convert, iValue->pComment)) { + return SI_FAIL; + } + } - return SI_OK; -} + // write the key + if (!convert.ConvertToStore(iKey->pItem)) { + return SI_FAIL; + } + a_oOutput.Write(convert.Data()); -template -bool -CSimpleIniTempl::OutputMultiLineText( - OutputWriter & a_oOutput, - Converter & a_oConverter, - const SI_CHAR * a_pText - ) const -{ - const SI_CHAR * pEndOfLine; - SI_CHAR cEndOfLineChar = *a_pText; - while (cEndOfLineChar) { - // find the end of this line - pEndOfLine = a_pText; - for (; *pEndOfLine && *pEndOfLine != '\n'; ++pEndOfLine) /*loop*/ ; - cEndOfLineChar = *pEndOfLine; - - // temporarily null terminate, convert and output the line - *const_cast(pEndOfLine) = 0; - if (!a_oConverter.ConvertToStore(a_pText)) { - return false; + // write the value as long + if (*iValue->pItem || !m_bAllowKeyOnly) { + if (!convert.ConvertToStore(iValue->pItem)) { + return SI_FAIL; + } + a_oOutput.Write(m_bSpaces ? " = " : "="); + if (m_bParseQuotes && IsSingleLineQuotedValue(iValue->pItem)) { + // the only way to preserve external whitespace on a value (i.e. before or after) + // is to quote it. This is simple quoting, we don't escape quotes within the data. + a_oOutput.Write("\""); + a_oOutput.Write(convert.Data()); + a_oOutput.Write("\""); + } else if (m_bAllowMultiLine && IsMultiLineData(iValue->pItem)) { + // multi-line data needs to be processed specially to ensure + // that we use the correct newline format for the current system + a_oOutput.Write("<<pItem)) { + return SI_FAIL; + } + a_oOutput.Write("END_OF_TEXT"); + } else { + a_oOutput.Write(convert.Data()); + } } - *const_cast(pEndOfLine) = cEndOfLineChar; - a_pText += (pEndOfLine - a_pText) + 1; - a_oOutput.Write(a_oConverter.Data()); a_oOutput.Write(SI_NEWLINE_A); + } } - return true; -} -template -bool -CSimpleIniTempl::Delete( - const SI_CHAR * a_pSection, - const SI_CHAR * a_pKey, - bool a_bRemoveEmpty - ) -{ - return DeleteValue(a_pSection, a_pKey, NULL, a_bRemoveEmpty); -} + bNeedNewLine = true; + } -template -bool -CSimpleIniTempl::DeleteValue( - const SI_CHAR * a_pSection, - const SI_CHAR * a_pKey, - const SI_CHAR * a_pValue, - bool a_bRemoveEmpty - ) -{ - if (!a_pSection) { - return false; - } - - typename TSection::iterator iSection = m_data.find(a_pSection); - if (iSection == m_data.end()) { - return false; - } + return SI_OK; +} - // remove a single key if we have a keyname - if (a_pKey) { - typename TKeyVal::iterator iKeyVal = iSection->second.find(a_pKey); - if (iKeyVal == iSection->second.end()) { - return false; - } +template +bool CSimpleIniTempl::OutputMultiLineText( + OutputWriter &a_oOutput, Converter &a_oConverter, + const SI_CHAR *a_pText) const { + const SI_CHAR *pEndOfLine; + SI_CHAR cEndOfLineChar = *a_pText; + while (cEndOfLineChar) { + // find the end of this line + pEndOfLine = a_pText; + for (; *pEndOfLine && *pEndOfLine != '\n'; ++pEndOfLine) /*loop*/ + ; + cEndOfLineChar = *pEndOfLine; + + // temporarily null terminate, convert and output the line + *const_cast(pEndOfLine) = 0; + if (!a_oConverter.ConvertToStore(a_pText)) { + return false; + } + *const_cast(pEndOfLine) = cEndOfLineChar; + a_pText += (pEndOfLine - a_pText) + 1; + a_oOutput.Write(a_oConverter.Data()); + a_oOutput.Write(SI_NEWLINE_A); + } + return true; +} - const static SI_STRLESS isLess = SI_STRLESS(); +template +bool CSimpleIniTempl::Delete( + const SI_CHAR *a_pSection, const SI_CHAR *a_pKey, bool a_bRemoveEmpty) { + return DeleteValue(a_pSection, a_pKey, NULL, a_bRemoveEmpty); +} - // remove any copied strings and then the key - typename TKeyVal::iterator iDelete; - bool bDeleted = false; - do { - iDelete = iKeyVal++; - - if(a_pValue == NULL || - (isLess(a_pValue, iDelete->second) == false && - isLess(iDelete->second, a_pValue) == false)) { - DeleteString(iDelete->first.pItem); - DeleteString(iDelete->second); - iSection->second.erase(iDelete); - bDeleted = true; - } - } - while (iKeyVal != iSection->second.end() - && !IsLess(a_pKey, iKeyVal->first.pItem)); +template +bool CSimpleIniTempl::DeleteValue( + const SI_CHAR *a_pSection, const SI_CHAR *a_pKey, const SI_CHAR *a_pValue, + bool a_bRemoveEmpty) { + if (!a_pSection) { + return false; + } - if(!bDeleted) { - return false; - } + typename TSection::iterator iSection = m_data.find(a_pSection); + if (iSection == m_data.end()) { + return false; + } - // done now if the section is not empty or we are not pruning away - // the empty sections. Otherwise let it fall through into the section - // deletion code - if (!a_bRemoveEmpty || !iSection->second.empty()) { - return true; - } - } - else { - // delete all copied strings from this section. The actual - // entries will be removed when the section is removed. - typename TKeyVal::iterator iKeyVal = iSection->second.begin(); - for ( ; iKeyVal != iSection->second.end(); ++iKeyVal) { - DeleteString(iKeyVal->first.pItem); - DeleteString(iKeyVal->second); - } + // remove a single key if we have a keyname + if (a_pKey) { + typename TKeyVal::iterator iKeyVal = iSection->second.find(a_pKey); + if (iKeyVal == iSection->second.end()) { + return false; } - // delete the section itself - DeleteString(iSection->first.pItem); - m_data.erase(iSection); + const static SI_STRLESS isLess = SI_STRLESS(); - return true; + // remove any copied strings and then the key + typename TKeyVal::iterator iDelete; + bool bDeleted = false; + do { + iDelete = iKeyVal++; + + if (a_pValue == NULL || (isLess(a_pValue, iDelete->second) == false && + isLess(iDelete->second, a_pValue) == false)) { + DeleteString(iDelete->first.pItem); + DeleteString(iDelete->second); + iSection->second.erase(iDelete); + bDeleted = true; + } + } while (iKeyVal != iSection->second.end() && + !IsLess(a_pKey, iKeyVal->first.pItem)); + + if (!bDeleted) { + return false; + } + + // done now if the section is not empty or we are not pruning away + // the empty sections. Otherwise let it fall through into the section + // deletion code + if (!a_bRemoveEmpty || !iSection->second.empty()) { + return true; + } + } else { + // delete all copied strings from this section. The actual + // entries will be removed when the section is removed. + typename TKeyVal::iterator iKeyVal = iSection->second.begin(); + for (; iKeyVal != iSection->second.end(); ++iKeyVal) { + DeleteString(iKeyVal->first.pItem); + DeleteString(iKeyVal->second); + } + } + + // delete the section itself + DeleteString(iSection->first.pItem); + m_data.erase(iSection); + + return true; } -template -void -CSimpleIniTempl::DeleteString( - const SI_CHAR * a_pString - ) -{ - // strings may exist either inside the data block, or they will be - // individually allocated and stored in m_strings. We only physically - // delete those stored in m_strings. - if (!m_pData || a_pString < m_pData || a_pString >= m_pData + m_uDataLen) { - typename TNamesDepend::iterator i = m_strings.begin(); - for (;i != m_strings.end(); ++i) { - if (a_pString == i->pItem) { - delete[] const_cast(i->pItem); - m_strings.erase(i); - break; - } - } +template +void CSimpleIniTempl::DeleteString( + const SI_CHAR *a_pString) { + // strings may exist either inside the data block, or they will be + // individually allocated and stored in m_strings. We only physically + // delete those stored in m_strings. + if (!m_pData || a_pString < m_pData || a_pString >= m_pData + m_uDataLen) { + typename TNamesDepend::iterator i = m_strings.begin(); + for (; i != m_strings.end(); ++i) { + if (a_pString == i->pItem) { + delete[] const_cast(i->pItem); + m_strings.erase(i); + break; + } } + } } // --------------------------------------------------------------------------- @@ -2854,7 +2606,7 @@ CSimpleIniTempl::DeleteString( // SimpleIni.h, set the converter that you wish you use by defining one of the // following symbols. // -// SI_NO_CONVERSION Do not make the "W" wide character version of the +// SI_NO_CONVERSION Do not make the "W" wide character version of the // library available. Only CSimpleIniA etc is defined. // Default on Linux/MacOS/etc. // SI_CONVERT_WIN32 Use the Win32 API functions for conversion. @@ -2864,12 +2616,13 @@ CSimpleIniTempl::DeleteString( // SI_CONVERT_ICU Use the IBM ICU conversion library. Requires // ICU headers on include path and icuuc.lib -#if !defined(SI_NO_CONVERSION) && !defined(SI_CONVERT_GENERIC) && !defined(SI_CONVERT_WIN32) && !defined(SI_CONVERT_ICU) -# ifdef _WIN32 -# define SI_CONVERT_WIN32 -# else -# define SI_NO_CONVERSION -# endif +#if !defined(SI_NO_CONVERSION) && !defined(SI_CONVERT_GENERIC) && \ + !defined(SI_CONVERT_WIN32) && !defined(SI_CONVERT_ICU) +#ifdef _WIN32 +#define SI_CONVERT_WIN32 +#else +#define SI_NO_CONVERSION +#endif #endif /** @@ -2877,18 +2630,17 @@ CSimpleIniTempl::DeleteString( * ordered ASCII case-sensitive text for all possible sizes and types of * SI_CHAR. */ -template -struct SI_GenericCase { - bool operator()(const SI_CHAR * pLeft, const SI_CHAR * pRight) const { - long cmp; - for ( ;*pLeft && *pRight; ++pLeft, ++pRight) { - cmp = (long) *pLeft - (long) *pRight; - if (cmp != 0) { - return cmp < 0; - } - } - return *pRight != 0; - } +template struct SI_GenericCase { + bool operator()(const SI_CHAR *pLeft, const SI_CHAR *pRight) const { + long cmp; + for (; *pLeft && *pRight; ++pLeft, ++pRight) { + cmp = (long)*pLeft - (long)*pRight; + if (cmp != 0) { + return cmp < 0; + } + } + return *pRight != 0; + } }; /** @@ -2897,42 +2649,42 @@ struct SI_GenericCase { * and types of SI_CHAR. It is not safe for MBCS text comparison where * ASCII A-Z characters are used in the encoding of multi-byte characters. */ -template -struct SI_GenericNoCase { - inline SI_CHAR locase(SI_CHAR ch) const { - return (ch < 'A' || ch > 'Z') ? ch : (ch - 'A' + 'a'); - } - bool operator()(const SI_CHAR * pLeft, const SI_CHAR * pRight) const { - long cmp; - for ( ;*pLeft && *pRight; ++pLeft, ++pRight) { - cmp = (long) locase(*pLeft) - (long) locase(*pRight); - if (cmp != 0) { - return cmp < 0; - } - } - return *pRight != 0; - } +template struct SI_GenericNoCase { + inline SI_CHAR locase(SI_CHAR ch) const { + return (ch < 'A' || ch > 'Z') ? ch : (ch - 'A' + 'a'); + } + bool operator()(const SI_CHAR *pLeft, const SI_CHAR *pRight) const { + long cmp; + for (; *pLeft && *pRight; ++pLeft, ++pRight) { + cmp = (long)locase(*pLeft) - (long)locase(*pRight); + if (cmp != 0) { + return cmp < 0; + } + } + return *pRight != 0; + } }; /** * Null conversion class for MBCS/UTF-8 to char (or equivalent). */ -template -class SI_ConvertA { - bool m_bStoreIsUtf8; +template class SI_ConvertA { + bool m_bStoreIsUtf8; + protected: - SI_ConvertA() { } + SI_ConvertA() {} + public: - SI_ConvertA(bool a_bStoreIsUtf8) : m_bStoreIsUtf8(a_bStoreIsUtf8) { } + SI_ConvertA(bool a_bStoreIsUtf8) : m_bStoreIsUtf8(a_bStoreIsUtf8) {} - /* copy and assignment */ - SI_ConvertA(const SI_ConvertA & rhs) { operator=(rhs); } - SI_ConvertA & operator=(const SI_ConvertA & rhs) { - m_bStoreIsUtf8 = rhs.m_bStoreIsUtf8; - return *this; - } + /* copy and assignment */ + SI_ConvertA(const SI_ConvertA &rhs) { operator=(rhs); } + SI_ConvertA &operator=(const SI_ConvertA &rhs) { + m_bStoreIsUtf8 = rhs.m_bStoreIsUtf8; + return *this; + } - /** Calculate the number of SI_CHAR required for converting the input + /** Calculate the number of SI_CHAR required for converting the input * from the storage format. The storage format is always UTF-8 or MBCS. * * @param a_pInputData Data in storage format to be converted to SI_CHAR. @@ -2945,18 +2697,15 @@ class SI_ConvertA { * the NULL byte will be converted. * @return -1 cast to size_t on a conversion error. */ - size_t SizeFromStore( - const char * a_pInputData, - size_t a_uInputDataLen) - { - (void)a_pInputData; - SI_ASSERT(a_uInputDataLen != (size_t) -1); + size_t SizeFromStore(const char *a_pInputData, size_t a_uInputDataLen) { + (void)a_pInputData; + SI_ASSERT(a_uInputDataLen != (size_t)-1); - // ASCII/MBCS/UTF-8 needs no conversion - return a_uInputDataLen; - } + // ASCII/MBCS/UTF-8 needs no conversion + return a_uInputDataLen; + } - /** Convert the input string from the storage format to SI_CHAR. + /** Convert the input string from the storage format to SI_CHAR. * The storage format is always UTF-8 or MBCS. * * @param a_pInputData Data in storage format to be converted to SI_CHAR. @@ -2969,21 +2718,17 @@ class SI_ConvertA { * @return true if all of the input data was successfully * converted. */ - bool ConvertFromStore( - const char * a_pInputData, - size_t a_uInputDataLen, - SI_CHAR * a_pOutputData, - size_t a_uOutputDataSize) - { - // ASCII/MBCS/UTF-8 needs no conversion - if (a_uInputDataLen > a_uOutputDataSize) { - return false; - } - memcpy(a_pOutputData, a_pInputData, a_uInputDataLen); - return true; + bool ConvertFromStore(const char *a_pInputData, size_t a_uInputDataLen, + SI_CHAR *a_pOutputData, size_t a_uOutputDataSize) { + // ASCII/MBCS/UTF-8 needs no conversion + if (a_uInputDataLen > a_uOutputDataSize) { + return false; } + memcpy(a_pOutputData, a_pInputData, a_uInputDataLen); + return true; + } - /** Calculate the number of char required by the storage format of this + /** Calculate the number of char required by the storage format of this * data. The storage format is always UTF-8 or MBCS. * * @param a_pInputData NULL terminated string to calculate the number of @@ -2993,14 +2738,12 @@ class SI_ConvertA { * includes space for the terminating NULL character. * @return -1 cast to size_t on a conversion error. */ - size_t SizeToStore( - const SI_CHAR * a_pInputData) - { - // ASCII/MBCS/UTF-8 needs no conversion - return strlen((const char *)a_pInputData) + 1; - } + size_t SizeToStore(const SI_CHAR *a_pInputData) { + // ASCII/MBCS/UTF-8 needs no conversion + return strlen((const char *)a_pInputData) + 1; + } - /** Convert the input string to the storage format of this data. + /** Convert the input string to the storage format of this data. * The storage format is always UTF-8 or MBCS. * * @param a_pInputData NULL terminated source string to convert. All of @@ -3013,31 +2756,27 @@ class SI_ConvertA { * terminating NULL character was successfully * converted. */ - bool ConvertToStore( - const SI_CHAR * a_pInputData, - char * a_pOutputData, - size_t a_uOutputDataSize) - { - // calc input string length (SI_CHAR type and size independent) - size_t uInputLen = strlen((const char *)a_pInputData) + 1; - if (uInputLen > a_uOutputDataSize) { - return false; - } - - // ascii/UTF-8 needs no conversion - memcpy(a_pOutputData, a_pInputData, uInputLen); - return true; + bool ConvertToStore(const SI_CHAR *a_pInputData, char *a_pOutputData, + size_t a_uOutputDataSize) { + // calc input string length (SI_CHAR type and size independent) + size_t uInputLen = strlen((const char *)a_pInputData) + 1; + if (uInputLen > a_uOutputDataSize) { + return false; } -}; + // ascii/UTF-8 needs no conversion + memcpy(a_pOutputData, a_pInputData, uInputLen); + return true; + } +}; // --------------------------------------------------------------------------- // SI_CONVERT_GENERIC // --------------------------------------------------------------------------- #ifdef SI_CONVERT_GENERIC -#define SI_Case SI_GenericCase -#define SI_NoCase SI_GenericNoCase +#define SI_Case SI_GenericCase +#define SI_NoCase SI_GenericNoCase #include @@ -3046,143 +2785,139 @@ namespace SI_UTF8 { constexpr char32_t REPLACEMENT = 0xFFFD; /** Decode one UTF-8 code point. Returns false on illegal or truncated input. */ -inline bool Decode(const char *& a_src, const char * a_end, char32_t & a_cp) -{ - if (a_src >= a_end) { - return false; - } - const unsigned char c0 = (unsigned char) *a_src++; - if (c0 < 0x80) { - a_cp = c0; - return true; - } - if (c0 < 0xC2) { - return false; - } - - int extra = 0; - char32_t ch = 0; - if (c0 < 0xE0) { - extra = 1; - ch = c0 & 0x1F; - } - else if (c0 < 0xF0) { - extra = 2; - ch = c0 & 0x0F; - } - else if (c0 < 0xF5) { - extra = 3; - ch = c0 & 0x07; - } - else { - return false; - } - if (a_src + extra > a_end) { - a_src -= 1; - return false; - } +inline bool Decode(const char *&a_src, const char *a_end, char32_t &a_cp) { + if (a_src >= a_end) { + return false; + } + const unsigned char c0 = (unsigned char)*a_src++; + if (c0 < 0x80) { + a_cp = c0; + return true; + } + if (c0 < 0xC2) { + return false; + } + + int extra = 0; + char32_t ch = 0; + if (c0 < 0xE0) { + extra = 1; + ch = c0 & 0x1F; + } else if (c0 < 0xF0) { + extra = 2; + ch = c0 & 0x0F; + } else if (c0 < 0xF5) { + extra = 3; + ch = c0 & 0x07; + } else { + return false; + } + if (a_src + extra > a_end) { + a_src -= 1; + return false; + } - for (int i = 0; i < extra; ++i) { - const unsigned char cx = (unsigned char) *a_src++; - if ((cx & 0xC0) != 0x80) { - a_src -= (i + 2); - return false; - } - ch = (ch << 6) | (cx & 0x3F); + for (int i = 0; i < extra; ++i) { + const unsigned char cx = (unsigned char)*a_src++; + if ((cx & 0xC0) != 0x80) { + a_src -= (i + 2); + return false; } + ch = (ch << 6) | (cx & 0x3F); + } - if ((extra == 1 && ch < 0x80) - || (extra == 2 && ch < 0x800) - || (extra == 3 && ch < 0x10000) - || ch > 0x10FFFF - || (ch >= 0xD800 && ch <= 0xDFFF)) { - a_src -= (extra + 1); - return false; - } + if ((extra == 1 && ch < 0x80) || (extra == 2 && ch < 0x800) || + (extra == 3 && ch < 0x10000) || ch > 0x10FFFF || + (ch >= 0xD800 && ch <= 0xDFFF)) { + a_src -= (extra + 1); + return false; + } - a_cp = ch; - return true; + a_cp = ch; + return true; } /** Encode one code point as UTF-8. Returns bytes written, or -1 on error. * Surrogate code points (U+D800..U+DFFF) and values above U+10FFFF are * encoded as U+FFFD so output is always well-formed UTF-8. */ -inline int Encode(char32_t a_cp, char * a_dst, size_t a_cap) -{ - if (a_cp >= 0x110000 - || (a_cp >= 0xD800 && a_cp <= 0xDFFF)) { - a_cp = REPLACEMENT; - } - - if (a_cp < 0x80) { - if (a_cap < 1) return -1; - a_dst[0] = (char) a_cp; - return 1; - } - if (a_cp < 0x800) { - if (a_cap < 2) return -1; - a_dst[0] = (char) (0xC0 | (a_cp >> 6)); - a_dst[1] = (char) (0x80 | (a_cp & 0x3F)); - return 2; - } - if (a_cp < 0x10000) { - if (a_cap < 3) return -1; - a_dst[0] = (char) (0xE0 | (a_cp >> 12)); - a_dst[1] = (char) (0x80 | ((a_cp >> 6) & 0x3F)); - a_dst[2] = (char) (0x80 | (a_cp & 0x3F)); - return 3; - } - if (a_cap < 4) return -1; - a_dst[0] = (char) (0xF0 | (a_cp >> 18)); - a_dst[1] = (char) (0x80 | ((a_cp >> 12) & 0x3F)); - a_dst[2] = (char) (0x80 | ((a_cp >> 6) & 0x3F)); - a_dst[3] = (char) (0x80 | (a_cp & 0x3F)); - return 4; +inline int Encode(char32_t a_cp, char *a_dst, size_t a_cap) { + if (a_cp >= 0x110000 || (a_cp >= 0xD800 && a_cp <= 0xDFFF)) { + a_cp = REPLACEMENT; + } + + if (a_cp < 0x80) { + if (a_cap < 1) + return -1; + a_dst[0] = (char)a_cp; + return 1; + } + if (a_cp < 0x800) { + if (a_cap < 2) + return -1; + a_dst[0] = (char)(0xC0 | (a_cp >> 6)); + a_dst[1] = (char)(0x80 | (a_cp & 0x3F)); + return 2; + } + if (a_cp < 0x10000) { + if (a_cap < 3) + return -1; + a_dst[0] = (char)(0xE0 | (a_cp >> 12)); + a_dst[1] = (char)(0x80 | ((a_cp >> 6) & 0x3F)); + a_dst[2] = (char)(0x80 | (a_cp & 0x3F)); + return 3; + } + if (a_cap < 4) + return -1; + a_dst[0] = (char)(0xF0 | (a_cp >> 18)); + a_dst[1] = (char)(0x80 | ((a_cp >> 12) & 0x3F)); + a_dst[2] = (char)(0x80 | ((a_cp >> 6) & 0x3F)); + a_dst[3] = (char)(0x80 | (a_cp & 0x3F)); + return 4; } -template -inline bool AppendCodePoint(char32_t a_cp, SI_CHAR *& a_dst, SI_CHAR * a_dstEnd) -{ - if (sizeof(SI_CHAR) >= sizeof(char32_t)) { - if (a_dst >= a_dstEnd) return false; - *a_dst++ = (SI_CHAR) a_cp; - return true; - } - if (sizeof(SI_CHAR) == 2) { - if (a_cp < 0x10000) { - if (a_dst >= a_dstEnd) return false; - *a_dst++ = (SI_CHAR) a_cp; - return true; - } - if (a_dst + 1 >= a_dstEnd) return false; - a_cp -= 0x10000; - *a_dst++ = (SI_CHAR) (0xD800 + (a_cp >> 10)); - *a_dst++ = (SI_CHAR) (0xDC00 + (a_cp & 0x3FF)); - return true; - } - return false; +template +inline bool AppendCodePoint(char32_t a_cp, SI_CHAR *&a_dst, SI_CHAR *a_dstEnd) { + if (sizeof(SI_CHAR) >= sizeof(char32_t)) { + if (a_dst >= a_dstEnd) + return false; + *a_dst++ = (SI_CHAR)a_cp; + return true; + } + if (sizeof(SI_CHAR) == 2) { + if (a_cp < 0x10000) { + if (a_dst >= a_dstEnd) + return false; + *a_dst++ = (SI_CHAR)a_cp; + return true; + } + if (a_dst + 1 >= a_dstEnd) + return false; + a_cp -= 0x10000; + *a_dst++ = (SI_CHAR)(0xD800 + (a_cp >> 10)); + *a_dst++ = (SI_CHAR)(0xDC00 + (a_cp & 0x3FF)); + return true; + } + return false; } -template -inline bool ReadCodePoint(const SI_CHAR *& a_src, char32_t & a_cp) -{ - if (sizeof(SI_CHAR) >= sizeof(char32_t)) { - a_cp = (char32_t) *a_src++; - return true; - } - if (sizeof(SI_CHAR) == 2) { - const char32_t w = (char32_t) *a_src++; - if (w >= 0xD800 && w <= 0xDBFF) { - if (*a_src >= 0xDC00 && *a_src <= 0xDFFF) { - a_cp = 0x10000 + ((w - 0xD800) << 10) - + ((char32_t) *a_src++ - 0xDC00); - return true; - } - } - a_cp = w; +template +inline bool ReadCodePoint(const SI_CHAR *&a_src, char32_t &a_cp) { + if (sizeof(SI_CHAR) >= sizeof(char32_t)) { + a_cp = (char32_t)*a_src++; + return true; + } + if (sizeof(SI_CHAR) == 2) { + const char32_t w = (char32_t)*a_src++; + if (w >= 0xD800 && w <= 0xDBFF) { + if (*a_src >= 0xDC00 && *a_src <= 0xDFFF) { + a_cp = 0x10000 + ((w - 0xD800) << 10) + ((char32_t)*a_src++ - 0xDC00); return true; + } } - return false; + a_cp = w; + return true; + } + return false; } } // namespace SI_UTF8 @@ -3191,22 +2926,23 @@ inline bool ReadCodePoint(const SI_CHAR *& a_src, char32_t & a_cp) * Converts UTF-8 to a wchar_t (or equivalent) using built-in UTF-8 conversion. * This can be used on all platforms without locale configuration. */ -template -class SI_ConvertW { - bool m_bStoreIsUtf8; +template class SI_ConvertW { + bool m_bStoreIsUtf8; + protected: - SI_ConvertW() { } + SI_ConvertW() {} + public: - SI_ConvertW(bool a_bStoreIsUtf8) : m_bStoreIsUtf8(a_bStoreIsUtf8) { } + SI_ConvertW(bool a_bStoreIsUtf8) : m_bStoreIsUtf8(a_bStoreIsUtf8) {} - /* copy and assignment */ - SI_ConvertW(const SI_ConvertW & rhs) { operator=(rhs); } - SI_ConvertW & operator=(const SI_ConvertW & rhs) { - m_bStoreIsUtf8 = rhs.m_bStoreIsUtf8; - return *this; - } + /* copy and assignment */ + SI_ConvertW(const SI_ConvertW &rhs) { operator=(rhs); } + SI_ConvertW &operator=(const SI_ConvertW &rhs) { + m_bStoreIsUtf8 = rhs.m_bStoreIsUtf8; + return *this; + } - /** Calculate the number of SI_CHAR required for converting the input + /** Calculate the number of SI_CHAR required for converting the input * from the storage format. The storage format is always UTF-8 or MBCS. * * @param a_pInputData Data in storage format to be converted to SI_CHAR. @@ -3219,35 +2955,32 @@ class SI_ConvertW { * the NULL byte will be converted. * @return -1 cast to size_t on a conversion error. */ - size_t SizeFromStore( - const char * a_pInputData, - size_t a_uInputDataLen) - { - SI_ASSERT(a_uInputDataLen != (size_t) -1); - - if (m_bStoreIsUtf8) { - // worst case scenario for UTF-8 to wchar_t is 1 char -> 1 wchar_t - // so we just return the same number of characters required as for - // the source text. - return a_uInputDataLen; - } + size_t SizeFromStore(const char *a_pInputData, size_t a_uInputDataLen) { + SI_ASSERT(a_uInputDataLen != (size_t)-1); + + if (m_bStoreIsUtf8) { + // worst case scenario for UTF-8 to wchar_t is 1 char -> 1 wchar_t + // so we just return the same number of characters required as for + // the source text. + return a_uInputDataLen; + } - // get the required buffer size + // get the required buffer size #if defined(_MSC_VER) - size_t uBufSiz; - errno_t e = mbstowcs_s(&uBufSiz, NULL, 0, a_pInputData, a_uInputDataLen); - return (e == 0) ? uBufSiz : (size_t) -1; + size_t uBufSiz; + errno_t e = mbstowcs_s(&uBufSiz, NULL, 0, a_pInputData, a_uInputDataLen); + return (e == 0) ? uBufSiz : (size_t)-1; #elif !defined(SI_NO_MBSTOWCS_NULL) - return mbstowcs(NULL, a_pInputData, a_uInputDataLen); + return mbstowcs(NULL, a_pInputData, a_uInputDataLen); #else - // fall back processing for platforms that don't support a NULL dest to mbstowcs - // worst case scenario is 1:1, this will be a sufficient buffer size - (void)a_pInputData; - return a_uInputDataLen; + // fall back processing for platforms that don't support a NULL dest to mbstowcs + // worst case scenario is 1:1, this will be a sufficient buffer size + (void)a_pInputData; + return a_uInputDataLen; #endif - } + } - /** Convert the input string from the storage format to SI_CHAR. + /** Convert the input string from the storage format to SI_CHAR. * The storage format is always UTF-8 or MBCS. * * @param a_pInputData Data in storage format to be converted to SI_CHAR. @@ -3260,48 +2993,42 @@ class SI_ConvertW { * @return true if all of the input data was successfully * converted. */ - bool ConvertFromStore( - const char * a_pInputData, - size_t a_uInputDataLen, - SI_CHAR * a_pOutputData, - size_t a_uOutputDataSize) - { - if (m_bStoreIsUtf8) { - const char * src = a_pInputData; - const char * srcEnd = a_pInputData + a_uInputDataLen; - SI_CHAR * dst = a_pOutputData; - SI_CHAR * dstEnd = a_pOutputData + a_uOutputDataSize; - if (sizeof(SI_CHAR) != 2 && sizeof(SI_CHAR) < sizeof(char32_t)) { - return false; - } - while (src < srcEnd) { - char32_t cp; - if (!SI_UTF8::Decode(src, srcEnd, cp)) { - return false; - } - if (!SI_UTF8::AppendCodePoint(cp, dst, dstEnd)) { - return false; - } - } - return true; + bool ConvertFromStore(const char *a_pInputData, size_t a_uInputDataLen, + SI_CHAR *a_pOutputData, size_t a_uOutputDataSize) { + if (m_bStoreIsUtf8) { + const char *src = a_pInputData; + const char *srcEnd = a_pInputData + a_uInputDataLen; + SI_CHAR *dst = a_pOutputData; + SI_CHAR *dstEnd = a_pOutputData + a_uOutputDataSize; + if (sizeof(SI_CHAR) != 2 && sizeof(SI_CHAR) < sizeof(char32_t)) { + return false; + } + while (src < srcEnd) { + char32_t cp; + if (!SI_UTF8::Decode(src, srcEnd, cp)) { + return false; + } + if (!SI_UTF8::AppendCodePoint(cp, dst, dstEnd)) { + return false; } + } + return true; + } - // convert to wchar_t + // convert to wchar_t #if defined(_MSC_VER) - size_t uBufSiz; - errno_t e = mbstowcs_s(&uBufSiz, - a_pOutputData, a_uOutputDataSize, - a_pInputData, a_uInputDataLen); - (void)uBufSiz; - return (e == 0); + size_t uBufSiz; + errno_t e = mbstowcs_s(&uBufSiz, a_pOutputData, a_uOutputDataSize, + a_pInputData, a_uInputDataLen); + (void)uBufSiz; + return (e == 0); #else - size_t retval = mbstowcs(a_pOutputData, - a_pInputData, a_uOutputDataSize); - return retval != (size_t)(-1); + size_t retval = mbstowcs(a_pOutputData, a_pInputData, a_uOutputDataSize); + return retval != (size_t)(-1); #endif - } + } - /** Calculate the number of char required by the storage format of this + /** Calculate the number of char required by the storage format of this * data. The storage format is always UTF-8 or MBCS. * * @param a_pInputData NULL terminated string to calculate the number of @@ -3311,27 +3038,33 @@ class SI_ConvertW { * includes space for the terminating NULL character. * @return -1 cast to size_t on a conversion error. */ - size_t SizeToStore( - const SI_CHAR * a_pInputData) - { - if (m_bStoreIsUtf8) { - // worst case scenario for wchar_t to UTF-8 is 1 wchar_t -> 6 char - size_t uLen = 0; - while (a_pInputData[uLen]) { - ++uLen; - } - return (6 * uLen) + 1; - } - else { - size_t uLen = wcstombs(NULL, a_pInputData, 0); - if (uLen == (size_t)(-1)) { - return uLen; - } - return uLen + 1; // include NULL terminator - } + size_t SizeToStore(const SI_CHAR *a_pInputData) { + if (m_bStoreIsUtf8) { + // worst case scenario for wchar_t to UTF-8 is 1 wchar_t -> 6 char + size_t uLen = 0; + while (a_pInputData[uLen]) { + ++uLen; + } + return (6 * uLen) + 1; + } else { +#if defined(_MSC_VER) + size_t uLen = 0; + errno_t e = wcstombs_s(&uLen, NULL, 0, a_pInputData, 0); + if (e != 0) { + return (size_t)-1; + } + return uLen + 1; // include NULL terminator +#else + size_t uLen = wcstombs(NULL, a_pInputData, 0); + if (uLen == (size_t)(-1)) { + return uLen; + } + return uLen + 1; // include NULL terminator +#endif } + } - /** Convert the input string to the storage format of this data. + /** Convert the input string to the storage format of this data. * The storage format is always UTF-8 or MBCS. * * @param a_pInputData NULL terminated source string to convert. All of @@ -3344,83 +3077,87 @@ class SI_ConvertW { * terminating NULL character was successfully * converted. */ - bool ConvertToStore( - const SI_CHAR * a_pInputData, - char * a_pOutputData, - size_t a_uOutputDataSize - ) - { - if (m_bStoreIsUtf8) { - const SI_CHAR * src = a_pInputData; - char * dst = a_pOutputData; - char * dstEnd = a_pOutputData + a_uOutputDataSize; - if (sizeof(SI_CHAR) != 2 && sizeof(SI_CHAR) < sizeof(char32_t)) { - return false; - } - while (*src) { - char32_t cp; - if (!SI_UTF8::ReadCodePoint(src, cp)) { - return false; - } - char buf[4]; - const int n = SI_UTF8::Encode(cp, buf, sizeof(buf)); - if (n < 0 || (size_t) (dstEnd - dst) < (size_t) n) { - return false; - } - memcpy(dst, buf, (size_t) n); - dst += n; - } - if (dst >= dstEnd) { - return false; - } - *dst = '\0'; - return true; - } - else { - size_t retval = wcstombs(a_pOutputData, - a_pInputData, a_uOutputDataSize); - return retval != (size_t) -1; - } + bool ConvertToStore(const SI_CHAR *a_pInputData, char *a_pOutputData, + size_t a_uOutputDataSize) { + if (m_bStoreIsUtf8) { + const SI_CHAR *src = a_pInputData; + char *dst = a_pOutputData; + char *dstEnd = a_pOutputData + a_uOutputDataSize; + if (sizeof(SI_CHAR) != 2 && sizeof(SI_CHAR) < sizeof(char32_t)) { + return false; + } + while (*src) { + char32_t cp; + if (!SI_UTF8::ReadCodePoint(src, cp)) { + return false; + } + char buf[4]; + const int n = SI_UTF8::Encode(cp, buf, sizeof(buf)); + if (n < 0 || (size_t)(dstEnd - dst) < (size_t)n) { + return false; + } + memcpy(dst, buf, (size_t)n); + dst += n; + } + if (dst >= dstEnd) { + return false; + } + *dst = '\0'; + return true; + } else { +#if defined(_MSC_VER) + size_t converted = 0; + errno_t e = wcstombs_s(&converted, a_pOutputData, a_uOutputDataSize, + a_pInputData, 0); + return (e == 0); +#else + size_t retval = wcstombs(a_pOutputData, a_pInputData, a_uOutputDataSize); + return retval != (size_t)-1; +#endif } + } }; #endif // SI_CONVERT_GENERIC - // --------------------------------------------------------------------------- // SI_CONVERT_ICU // --------------------------------------------------------------------------- #ifdef SI_CONVERT_ICU -#define SI_Case SI_GenericCase -#define SI_NoCase SI_GenericNoCase +#define SI_Case SI_GenericCase +#define SI_NoCase SI_GenericNoCase #include /** * Converts MBCS/UTF-8 to UChar using ICU. This can be used on all platforms. */ -template -class SI_ConvertW { - const char * m_pEncoding; - UConverter * m_pConverter; -protected: - SI_ConvertW() : m_pEncoding(NULL), m_pConverter(NULL) { } -public: - SI_ConvertW(bool a_bStoreIsUtf8) : m_pConverter(NULL) { - m_pEncoding = a_bStoreIsUtf8 ? "UTF-8" : NULL; - } +template class SI_ConvertW { + const char *m_pEncoding; + UConverter *m_pConverter; - /* copy and assignment */ - SI_ConvertW(const SI_ConvertW & rhs) { operator=(rhs); } - SI_ConvertW & operator=(const SI_ConvertW & rhs) { - m_pEncoding = rhs.m_pEncoding; - m_pConverter = NULL; - return *this; - } - ~SI_ConvertW() { if (m_pConverter) ucnv_close(m_pConverter); } +protected: + SI_ConvertW() : m_pEncoding(NULL), m_pConverter(NULL) {} - /** Calculate the number of UChar required for converting the input +public: + SI_ConvertW(bool a_bStoreIsUtf8) : m_pConverter(NULL) { + m_pEncoding = a_bStoreIsUtf8 ? "UTF-8" : NULL; + } + + /* copy and assignment */ + SI_ConvertW(const SI_ConvertW &rhs) { operator=(rhs); } + SI_ConvertW &operator=(const SI_ConvertW &rhs) { + m_pEncoding = rhs.m_pEncoding; + m_pConverter = NULL; + return *this; + } + ~SI_ConvertW() { + if (m_pConverter) + ucnv_close(m_pConverter); + } + + /** Calculate the number of UChar required for converting the input * from the storage format. The storage format is always UTF-8 or MBCS. * * @param a_pInputData Data in storage format to be converted to UChar. @@ -3433,33 +3170,30 @@ class SI_ConvertW { * the NULL byte will be converted. * @return -1 cast to size_t on a conversion error. */ - size_t SizeFromStore( - const char * a_pInputData, - size_t a_uInputDataLen) - { - SI_ASSERT(a_uInputDataLen != (size_t) -1); - - UErrorCode nError; - - if (!m_pConverter) { - nError = U_ZERO_ERROR; - m_pConverter = ucnv_open(m_pEncoding, &nError); - if (U_FAILURE(nError)) { - return (size_t) -1; - } - } + size_t SizeFromStore(const char *a_pInputData, size_t a_uInputDataLen) { + SI_ASSERT(a_uInputDataLen != (size_t)-1); - nError = U_ZERO_ERROR; - int32_t nLen = ucnv_toUChars(m_pConverter, NULL, 0, - a_pInputData, (int32_t) a_uInputDataLen, &nError); - if (U_FAILURE(nError) && nError != U_BUFFER_OVERFLOW_ERROR) { - return (size_t) -1; - } + UErrorCode nError; + + if (!m_pConverter) { + nError = U_ZERO_ERROR; + m_pConverter = ucnv_open(m_pEncoding, &nError); + if (U_FAILURE(nError)) { + return (size_t)-1; + } + } - return (size_t) nLen; + nError = U_ZERO_ERROR; + int32_t nLen = ucnv_toUChars(m_pConverter, NULL, 0, a_pInputData, + (int32_t)a_uInputDataLen, &nError); + if (U_FAILURE(nError) && nError != U_BUFFER_OVERFLOW_ERROR) { + return (size_t)-1; } - /** Convert the input string from the storage format to UChar. + return (size_t)nLen; + } + + /** Convert the input string from the storage format to UChar. * The storage format is always UTF-8 or MBCS. * * @param a_pInputData Data in storage format to be converted to UChar. @@ -3472,34 +3206,29 @@ class SI_ConvertW { * @return true if all of the input data was successfully * converted. */ - bool ConvertFromStore( - const char * a_pInputData, - size_t a_uInputDataLen, - UChar * a_pOutputData, - size_t a_uOutputDataSize) - { - UErrorCode nError; - - if (!m_pConverter) { - nError = U_ZERO_ERROR; - m_pConverter = ucnv_open(m_pEncoding, &nError); - if (U_FAILURE(nError)) { - return false; - } - } - - nError = U_ZERO_ERROR; - ucnv_toUChars(m_pConverter, - a_pOutputData, (int32_t) a_uOutputDataSize, - a_pInputData, (int32_t) a_uInputDataLen, &nError); - if (U_FAILURE(nError)) { - return false; - } + bool ConvertFromStore(const char *a_pInputData, size_t a_uInputDataLen, + UChar *a_pOutputData, size_t a_uOutputDataSize) { + UErrorCode nError; + + if (!m_pConverter) { + nError = U_ZERO_ERROR; + m_pConverter = ucnv_open(m_pEncoding, &nError); + if (U_FAILURE(nError)) { + return false; + } + } - return true; + nError = U_ZERO_ERROR; + ucnv_toUChars(m_pConverter, a_pOutputData, (int32_t)a_uOutputDataSize, + a_pInputData, (int32_t)a_uInputDataLen, &nError); + if (U_FAILURE(nError)) { + return false; } - /** Calculate the number of char required by the storage format of this + return true; + } + + /** Calculate the number of char required by the storage format of this * data. The storage format is always UTF-8 or MBCS. * * @param a_pInputData NULL terminated string to calculate the number of @@ -3509,30 +3238,28 @@ class SI_ConvertW { * includes space for the terminating NULL character. * @return -1 cast to size_t on a conversion error. */ - size_t SizeToStore( - const UChar * a_pInputData) - { - UErrorCode nError; - - if (!m_pConverter) { - nError = U_ZERO_ERROR; - m_pConverter = ucnv_open(m_pEncoding, &nError); - if (U_FAILURE(nError)) { - return (size_t) -1; - } - } + size_t SizeToStore(const UChar *a_pInputData) { + UErrorCode nError; - nError = U_ZERO_ERROR; - int32_t nLen = ucnv_fromUChars(m_pConverter, NULL, 0, - a_pInputData, -1, &nError); - if (U_FAILURE(nError) && nError != U_BUFFER_OVERFLOW_ERROR) { - return (size_t) -1; - } + if (!m_pConverter) { + nError = U_ZERO_ERROR; + m_pConverter = ucnv_open(m_pEncoding, &nError); + if (U_FAILURE(nError)) { + return (size_t)-1; + } + } - return (size_t) nLen + 1; + nError = U_ZERO_ERROR; + int32_t nLen = + ucnv_fromUChars(m_pConverter, NULL, 0, a_pInputData, -1, &nError); + if (U_FAILURE(nError) && nError != U_BUFFER_OVERFLOW_ERROR) { + return (size_t)-1; } - /** Convert the input string to the storage format of this data. + return (size_t)nLen + 1; + } + + /** Convert the input string to the storage format of this data. * The storage format is always UTF-8 or MBCS. * * @param a_pInputData NULL terminated source string to convert. All of @@ -3545,53 +3272,48 @@ class SI_ConvertW { * terminating NULL character was successfully * converted. */ - bool ConvertToStore( - const UChar * a_pInputData, - char * a_pOutputData, - size_t a_uOutputDataSize) - { - UErrorCode nError; - - if (!m_pConverter) { - nError = U_ZERO_ERROR; - m_pConverter = ucnv_open(m_pEncoding, &nError); - if (U_FAILURE(nError)) { - return false; - } - } - - nError = U_ZERO_ERROR; - ucnv_fromUChars(m_pConverter, - a_pOutputData, (int32_t) a_uOutputDataSize, - a_pInputData, -1, &nError); - if (U_FAILURE(nError)) { - return false; - } + bool ConvertToStore(const UChar *a_pInputData, char *a_pOutputData, + size_t a_uOutputDataSize) { + UErrorCode nError; + + if (!m_pConverter) { + nError = U_ZERO_ERROR; + m_pConverter = ucnv_open(m_pEncoding, &nError); + if (U_FAILURE(nError)) { + return false; + } + } - return true; + nError = U_ZERO_ERROR; + ucnv_fromUChars(m_pConverter, a_pOutputData, (int32_t)a_uOutputDataSize, + a_pInputData, -1, &nError); + if (U_FAILURE(nError)) { + return false; } + + return true; + } }; #endif // SI_CONVERT_ICU - // --------------------------------------------------------------------------- // SI_CONVERT_WIN32 // --------------------------------------------------------------------------- #ifdef SI_CONVERT_WIN32 -#define SI_Case SI_GenericCase +#define SI_Case SI_GenericCase // Windows CE doesn't have errno or MBCS libraries #ifdef _WIN32_WCE -# ifndef SI_NO_MBCS -# define SI_NO_MBCS -# endif +#ifndef SI_NO_MBCS +#define SI_NO_MBCS +#endif #endif #include #ifdef SI_NO_MBCS -# define SI_NoCase SI_GenericNoCase +#define SI_NoCase SI_GenericNoCase #else // !SI_NO_MBCS /** * Case-insensitive comparison class using Win32 MBCS functions. This class @@ -3602,19 +3324,17 @@ class SI_ConvertW { * SI_NoCase class instead. */ #include -template -struct SI_NoCase { - bool operator()(const SI_CHAR * pLeft, const SI_CHAR * pRight) const { - if (sizeof(SI_CHAR) == sizeof(char)) { - return _mbsicmp((const unsigned char *)pLeft, - (const unsigned char *)pRight) < 0; - } - if (sizeof(SI_CHAR) == sizeof(wchar_t)) { - return _wcsicmp((const wchar_t *)pLeft, - (const wchar_t *)pRight) < 0; - } - return SI_GenericNoCase()(pLeft, pRight); +template struct SI_NoCase { + bool operator()(const SI_CHAR *pLeft, const SI_CHAR *pRight) const { + if (sizeof(SI_CHAR) == sizeof(char)) { + return _mbsicmp((const unsigned char *)pLeft, + (const unsigned char *)pRight) < 0; + } + if (sizeof(SI_CHAR) == sizeof(wchar_t)) { + return _wcsicmp((const wchar_t *)pLeft, (const wchar_t *)pRight) < 0; } + return SI_GenericNoCase()(pLeft, pRight); + } }; #endif // SI_NO_MBCS @@ -3624,24 +3344,25 @@ struct SI_NoCase { * conversion library. It will not work on Windows 95 without using Microsoft * Layer for Unicode in your application. */ -template -class SI_ConvertW { - UINT m_uCodePage; -protected: - SI_ConvertW() { } -public: - SI_ConvertW(bool a_bStoreIsUtf8) { - m_uCodePage = a_bStoreIsUtf8 ? CP_UTF8 : CP_ACP; - } +template class SI_ConvertW { + UINT m_uCodePage; - /* copy and assignment */ - SI_ConvertW(const SI_ConvertW & rhs) { operator=(rhs); } - SI_ConvertW & operator=(const SI_ConvertW & rhs) { - m_uCodePage = rhs.m_uCodePage; - return *this; - } +protected: + SI_ConvertW() {} - /** Calculate the number of SI_CHAR required for converting the input +public: + SI_ConvertW(bool a_bStoreIsUtf8) { + m_uCodePage = a_bStoreIsUtf8 ? CP_UTF8 : CP_ACP; + } + + /* copy and assignment */ + SI_ConvertW(const SI_ConvertW &rhs) { operator=(rhs); } + SI_ConvertW &operator=(const SI_ConvertW &rhs) { + m_uCodePage = rhs.m_uCodePage; + return *this; + } + + /** Calculate the number of SI_CHAR required for converting the input * from the storage format. The storage format is always UTF-8 or MBCS. * * @param a_pInputData Data in storage format to be converted to SI_CHAR. @@ -3654,20 +3375,15 @@ class SI_ConvertW { * the NULL byte will be converted. * @return -1 cast to size_t on a conversion error. */ - size_t SizeFromStore( - const char * a_pInputData, - size_t a_uInputDataLen) - { - SI_ASSERT(a_uInputDataLen != (size_t) -1); + size_t SizeFromStore(const char *a_pInputData, size_t a_uInputDataLen) { + SI_ASSERT(a_uInputDataLen != (size_t)-1); - int retval = MultiByteToWideChar( - m_uCodePage, 0, - a_pInputData, (int) a_uInputDataLen, - 0, 0); - return (size_t)(retval > 0 ? retval : -1); - } + int retval = MultiByteToWideChar(m_uCodePage, 0, a_pInputData, + (int)a_uInputDataLen, 0, 0); + return (size_t)(retval > 0 ? retval : -1); + } - /** Convert the input string from the storage format to SI_CHAR. + /** Convert the input string from the storage format to SI_CHAR. * The storage format is always UTF-8 or MBCS. * * @param a_pInputData Data in storage format to be converted to SI_CHAR. @@ -3680,20 +3396,15 @@ class SI_ConvertW { * @return true if all of the input data was successfully * converted. */ - bool ConvertFromStore( - const char * a_pInputData, - size_t a_uInputDataLen, - SI_CHAR * a_pOutputData, - size_t a_uOutputDataSize) - { - int nSize = MultiByteToWideChar( - m_uCodePage, 0, - a_pInputData, (int) a_uInputDataLen, - (wchar_t *) a_pOutputData, (int) a_uOutputDataSize); - return (nSize > 0); - } - - /** Calculate the number of char required by the storage format of this + bool ConvertFromStore(const char *a_pInputData, size_t a_uInputDataLen, + SI_CHAR *a_pOutputData, size_t a_uOutputDataSize) { + int nSize = + MultiByteToWideChar(m_uCodePage, 0, a_pInputData, (int)a_uInputDataLen, + (wchar_t *)a_pOutputData, (int)a_uOutputDataSize); + return (nSize > 0); + } + + /** Calculate the number of char required by the storage format of this * data. The storage format is always UTF-8. * * @param a_pInputData NULL terminated string to calculate the number of @@ -3703,17 +3414,13 @@ class SI_ConvertW { * includes space for the terminating NULL character. * @return -1 cast to size_t on a conversion error. */ - size_t SizeToStore( - const SI_CHAR * a_pInputData) - { - int retval = WideCharToMultiByte( - m_uCodePage, 0, - (const wchar_t *) a_pInputData, -1, - 0, 0, 0, 0); - return (size_t) (retval > 0 ? retval : -1); - } + size_t SizeToStore(const SI_CHAR *a_pInputData) { + int retval = WideCharToMultiByte( + m_uCodePage, 0, (const wchar_t *)a_pInputData, -1, 0, 0, 0, 0); + return (size_t)(retval > 0 ? retval : -1); + } - /** Convert the input string to the storage format of this data. + /** Convert the input string to the storage format of this data. * The storage format is always UTF-8 or MBCS. * * @param a_pInputData NULL terminated source string to convert. All of @@ -3726,77 +3433,66 @@ class SI_ConvertW { * terminating NULL character was successfully * converted. */ - bool ConvertToStore( - const SI_CHAR * a_pInputData, - char * a_pOutputData, - size_t a_uOutputDataSize) - { - int retval = WideCharToMultiByte( - m_uCodePage, 0, - (const wchar_t *) a_pInputData, -1, - a_pOutputData, (int) a_uOutputDataSize, 0, 0); - return retval > 0; - } + bool ConvertToStore(const SI_CHAR *a_pInputData, char *a_pOutputData, + size_t a_uOutputDataSize) { + int retval = + WideCharToMultiByte(m_uCodePage, 0, (const wchar_t *)a_pInputData, -1, + a_pOutputData, (int)a_uOutputDataSize, 0, 0); + return retval > 0; + } }; #endif // SI_CONVERT_WIN32 - - // --------------------------------------------------------------------------- // SI_NO_CONVERSION // --------------------------------------------------------------------------- #ifdef SI_NO_CONVERSION -#define SI_Case SI_GenericCase -#define SI_NoCase SI_GenericNoCase +#define SI_Case SI_GenericCase +#define SI_NoCase SI_GenericNoCase #endif // SI_NO_CONVERSION - - // --------------------------------------------------------------------------- // TYPE DEFINITIONS // --------------------------------------------------------------------------- -typedef CSimpleIniTempl,SI_ConvertA > CSimpleIniA; -typedef CSimpleIniTempl,SI_ConvertA > CSimpleIniCaseA; +typedef CSimpleIniTempl, SI_ConvertA> CSimpleIniA; +typedef CSimpleIniTempl, SI_ConvertA> CSimpleIniCaseA; #if defined(SI_NO_CONVERSION) -// if there is no wide char conversion then we don't need to define the +// if there is no wide char conversion then we don't need to define the // widechar "W" versions of CSimpleIni -# define CSimpleIni CSimpleIniA -# define CSimpleIniCase CSimpleIniCaseA -# define SI_NEWLINE SI_NEWLINE_A +#define CSimpleIni CSimpleIniA +#define CSimpleIniCase CSimpleIniCaseA +#define SI_NEWLINE SI_NEWLINE_A #else -# if defined(SI_CONVERT_ICU) -typedef CSimpleIniTempl,SI_ConvertW > CSimpleIniW; -typedef CSimpleIniTempl,SI_ConvertW > CSimpleIniCaseW; -# else -typedef CSimpleIniTempl,SI_ConvertW > CSimpleIniW; -typedef CSimpleIniTempl,SI_ConvertW > CSimpleIniCaseW; -# endif - -# ifdef _UNICODE -# define CSimpleIni CSimpleIniW -# define CSimpleIniCase CSimpleIniCaseW -# define SI_NEWLINE SI_NEWLINE_W -# else // !_UNICODE -# define CSimpleIni CSimpleIniA -# define CSimpleIniCase CSimpleIniCaseA -# define SI_NEWLINE SI_NEWLINE_A -# endif // _UNICODE +#if defined(SI_CONVERT_ICU) +typedef CSimpleIniTempl, SI_ConvertW> + CSimpleIniW; +typedef CSimpleIniTempl, SI_ConvertW> + CSimpleIniCaseW; +#else +typedef CSimpleIniTempl, SI_ConvertW> + CSimpleIniW; +typedef CSimpleIniTempl, SI_ConvertW> + CSimpleIniCaseW; +#endif + +#ifdef _UNICODE +#define CSimpleIni CSimpleIniW +#define CSimpleIniCase CSimpleIniCaseW +#define SI_NEWLINE SI_NEWLINE_W +#else // !_UNICODE +#define CSimpleIni CSimpleIniA +#define CSimpleIniCase CSimpleIniCaseA +#define SI_NEWLINE SI_NEWLINE_A +#endif // _UNICODE #endif #ifdef _MSC_VER -# pragma warning (pop) +#pragma warning(pop) #endif #endif // INCLUDED_SimpleIni_h - diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 46f6868..2d43770 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -54,7 +54,9 @@ set_target_properties(tests PROPERTIES CXX_EXTENSIONS OFF ) if(MSVC) - target_compile_options(tests PRIVATE /utf-8) + target_compile_options(tests PRIVATE /utf-8 /W4 /WX) +else() + target_compile_options(tests PRIVATE -Wall -Wextra -Wpedantic -Werror) endif() add_test(NAME tests COMMAND tests WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) diff --git a/tests/ts-boolean.cpp b/tests/ts-boolean.cpp index bf30ff7..80a8294 100644 --- a/tests/ts-boolean.cpp +++ b/tests/ts-boolean.cpp @@ -3,250 +3,241 @@ class TestBoolean : public ::testing::Test { protected: - void SetUp() override; + void SetUp() override; + protected: - CSimpleIniA ini; + CSimpleIniA ini; }; -void TestBoolean::SetUp() { - ini.SetUnicode(); -} +void TestBoolean::SetUp() { ini.SetUnicode(); } // Test GetBoolValue with all recognized TRUE values TEST_F(TestBoolean, TestGetBoolValueTrue) { - std::string input = - "[bools]\n" - "true1 = true\n" - "true2 = t\n" - "true3 = yes\n" - "true4 = y\n" - "true5 = 1\n" - "true6 = on\n"; - - SI_Error rc = ini.LoadData(input); - ASSERT_EQ(rc, SI_OK); - - ASSERT_TRUE(ini.GetBoolValue("bools", "true1", false)); - ASSERT_TRUE(ini.GetBoolValue("bools", "true2", false)); - ASSERT_TRUE(ini.GetBoolValue("bools", "true3", false)); - ASSERT_TRUE(ini.GetBoolValue("bools", "true4", false)); - ASSERT_TRUE(ini.GetBoolValue("bools", "true5", false)); - ASSERT_TRUE(ini.GetBoolValue("bools", "true6", false)); + std::string input = "[bools]\n" + "true1 = true\n" + "true2 = t\n" + "true3 = yes\n" + "true4 = y\n" + "true5 = 1\n" + "true6 = on\n"; + + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); + + ASSERT_TRUE(ini.GetBoolValue("bools", "true1", false)); + ASSERT_TRUE(ini.GetBoolValue("bools", "true2", false)); + ASSERT_TRUE(ini.GetBoolValue("bools", "true3", false)); + ASSERT_TRUE(ini.GetBoolValue("bools", "true4", false)); + ASSERT_TRUE(ini.GetBoolValue("bools", "true5", false)); + ASSERT_TRUE(ini.GetBoolValue("bools", "true6", false)); } // Test GetBoolValue with all recognized FALSE values TEST_F(TestBoolean, TestGetBoolValueFalse) { - std::string input = - "[bools]\n" - "false1 = false\n" - "false2 = f\n" - "false3 = no\n" - "false4 = n\n" - "false5 = 0\n" - "false6 = off\n"; - - SI_Error rc = ini.LoadData(input); - ASSERT_EQ(rc, SI_OK); - - ASSERT_FALSE(ini.GetBoolValue("bools", "false1", true)); - ASSERT_FALSE(ini.GetBoolValue("bools", "false2", true)); - ASSERT_FALSE(ini.GetBoolValue("bools", "false3", true)); - ASSERT_FALSE(ini.GetBoolValue("bools", "false4", true)); - ASSERT_FALSE(ini.GetBoolValue("bools", "false5", true)); - ASSERT_FALSE(ini.GetBoolValue("bools", "false6", true)); + std::string input = "[bools]\n" + "false1 = false\n" + "false2 = f\n" + "false3 = no\n" + "false4 = n\n" + "false5 = 0\n" + "false6 = off\n"; + + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); + + ASSERT_FALSE(ini.GetBoolValue("bools", "false1", true)); + ASSERT_FALSE(ini.GetBoolValue("bools", "false2", true)); + ASSERT_FALSE(ini.GetBoolValue("bools", "false3", true)); + ASSERT_FALSE(ini.GetBoolValue("bools", "false4", true)); + ASSERT_FALSE(ini.GetBoolValue("bools", "false5", true)); + ASSERT_FALSE(ini.GetBoolValue("bools", "false6", true)); } // Test GetBoolValue with case variations TEST_F(TestBoolean, TestGetBoolValueCaseInsensitive) { - std::string input = - "[bools]\n" - "upper = TRUE\n" - "mixed = YeS\n" - "lower = false\n" - "caps = NO\n"; - - SI_Error rc = ini.LoadData(input); - ASSERT_EQ(rc, SI_OK); - - ASSERT_TRUE(ini.GetBoolValue("bools", "upper", false)); - ASSERT_TRUE(ini.GetBoolValue("bools", "mixed", false)); - ASSERT_FALSE(ini.GetBoolValue("bools", "lower", true)); - ASSERT_FALSE(ini.GetBoolValue("bools", "caps", true)); + std::string input = "[bools]\n" + "upper = TRUE\n" + "mixed = YeS\n" + "lower = false\n" + "caps = NO\n"; + + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); + + ASSERT_TRUE(ini.GetBoolValue("bools", "upper", false)); + ASSERT_TRUE(ini.GetBoolValue("bools", "mixed", false)); + ASSERT_FALSE(ini.GetBoolValue("bools", "lower", true)); + ASSERT_FALSE(ini.GetBoolValue("bools", "caps", true)); } // Test GetBoolValue with unrecognized values (should return default) TEST_F(TestBoolean, TestGetBoolValueUnrecognized) { - std::string input = - "[bools]\n" - "invalid1 = maybe\n" - "invalid2 = 2\n" - "invalid3 = \n" - "invalid4 = enabled\n"; + std::string input = "[bools]\n" + "invalid1 = maybe\n" + "invalid2 = 2\n" + "invalid3 = \n" + "invalid4 = enabled\n"; - SI_Error rc = ini.LoadData(input); - ASSERT_EQ(rc, SI_OK); + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); - // Unrecognized values should return default - ASSERT_TRUE(ini.GetBoolValue("bools", "invalid1", true)); - ASSERT_FALSE(ini.GetBoolValue("bools", "invalid1", false)); + // Unrecognized values should return default + ASSERT_TRUE(ini.GetBoolValue("bools", "invalid1", true)); + ASSERT_FALSE(ini.GetBoolValue("bools", "invalid1", false)); - ASSERT_TRUE(ini.GetBoolValue("bools", "invalid2", true)); - ASSERT_FALSE(ini.GetBoolValue("bools", "invalid2", false)); + ASSERT_TRUE(ini.GetBoolValue("bools", "invalid2", true)); + ASSERT_FALSE(ini.GetBoolValue("bools", "invalid2", false)); - ASSERT_TRUE(ini.GetBoolValue("bools", "invalid3", true)); - ASSERT_FALSE(ini.GetBoolValue("bools", "invalid3", false)); + ASSERT_TRUE(ini.GetBoolValue("bools", "invalid3", true)); + ASSERT_FALSE(ini.GetBoolValue("bools", "invalid3", false)); - ASSERT_TRUE(ini.GetBoolValue("bools", "invalid4", true)); - ASSERT_FALSE(ini.GetBoolValue("bools", "invalid4", false)); + ASSERT_TRUE(ini.GetBoolValue("bools", "invalid4", true)); + ASSERT_FALSE(ini.GetBoolValue("bools", "invalid4", false)); } // Test GetBoolValue with missing key TEST_F(TestBoolean, TestGetBoolValueMissing) { - std::string input = "[bools]\n"; + std::string input = "[bools]\n"; - SI_Error rc = ini.LoadData(input); - ASSERT_EQ(rc, SI_OK); + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); - ASSERT_TRUE(ini.GetBoolValue("bools", "missing", true)); - ASSERT_FALSE(ini.GetBoolValue("bools", "missing", false)); + ASSERT_TRUE(ini.GetBoolValue("bools", "missing", true)); + ASSERT_FALSE(ini.GetBoolValue("bools", "missing", false)); - ASSERT_TRUE(ini.GetBoolValue("missing_section", "key", true)); - ASSERT_FALSE(ini.GetBoolValue("missing_section", "key", false)); + ASSERT_TRUE(ini.GetBoolValue("missing_section", "key", true)); + ASSERT_FALSE(ini.GetBoolValue("missing_section", "key", false)); } // Test SetBoolValue TEST_F(TestBoolean, TestSetBoolValue) { - SI_Error rc = ini.SetBoolValue("bools", "value1", true); - ASSERT_EQ(rc, SI_INSERTED); + SI_Error rc = ini.SetBoolValue("bools", "value1", true); + ASSERT_EQ(rc, SI_INSERTED); - bool result = ini.GetBoolValue("bools", "value1", false); - ASSERT_TRUE(result); + bool result = ini.GetBoolValue("bools", "value1", false); + ASSERT_TRUE(result); - rc = ini.SetBoolValue("bools", "value2", false); - ASSERT_EQ(rc, SI_INSERTED); + rc = ini.SetBoolValue("bools", "value2", false); + ASSERT_EQ(rc, SI_INSERTED); - result = ini.GetBoolValue("bools", "value2", true); - ASSERT_FALSE(result); + result = ini.GetBoolValue("bools", "value2", true); + ASSERT_FALSE(result); } // Test SetBoolValue updates existing value TEST_F(TestBoolean, TestSetBoolValueUpdate) { - SI_Error rc = ini.SetBoolValue("bools", "toggle", true); - ASSERT_EQ(rc, SI_INSERTED); + SI_Error rc = ini.SetBoolValue("bools", "toggle", true); + ASSERT_EQ(rc, SI_INSERTED); - ASSERT_TRUE(ini.GetBoolValue("bools", "toggle", false)); + ASSERT_TRUE(ini.GetBoolValue("bools", "toggle", false)); - rc = ini.SetBoolValue("bools", "toggle", false); - ASSERT_EQ(rc, SI_UPDATED); + rc = ini.SetBoolValue("bools", "toggle", false); + ASSERT_EQ(rc, SI_UPDATED); - ASSERT_FALSE(ini.GetBoolValue("bools", "toggle", true)); + ASSERT_FALSE(ini.GetBoolValue("bools", "toggle", true)); } // Test SetBoolValue output format TEST_F(TestBoolean, TestSetBoolValueFormat) { - ini.SetBoolValue("bools", "enabled", true); - ini.SetBoolValue("bools", "disabled", false); + ini.SetBoolValue("bools", "enabled", true); + ini.SetBoolValue("bools", "disabled", false); - std::string output; - SI_Error rc = ini.Save(output); - ASSERT_EQ(rc, SI_OK); + std::string output; + SI_Error rc = ini.Save(output); + ASSERT_EQ(rc, SI_OK); - // Should write as "true" and "false" - EXPECT_NE(output.find("enabled = true"), std::string::npos); - EXPECT_NE(output.find("disabled = false"), std::string::npos); + // Should write as "true" and "false" + EXPECT_NE(output.find("enabled = true"), std::string::npos); + EXPECT_NE(output.find("disabled = false"), std::string::npos); } // Test GetBoolValue with whitespace TEST_F(TestBoolean, TestGetBoolValueWhitespace) { - std::string input = - "[bools]\n" - "padded = true \n" - "tabs =\tfalse\t\n"; + std::string input = "[bools]\n" + "padded = true \n" + "tabs =\tfalse\t\n"; - SI_Error rc = ini.LoadData(input); - ASSERT_EQ(rc, SI_OK); + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); - ASSERT_TRUE(ini.GetBoolValue("bools", "padded", false)); - ASSERT_FALSE(ini.GetBoolValue("bools", "tabs", true)); + ASSERT_TRUE(ini.GetBoolValue("bools", "padded", false)); + ASSERT_FALSE(ini.GetBoolValue("bools", "tabs", true)); } // Test boolean with multikey TEST_F(TestBoolean, TestBooleanMultikey) { - ini.SetMultiKey(true); - - std::string input = - "[bools]\n" - "flag = true\n" - "flag = false\n" - "flag = yes\n"; - - SI_Error rc = ini.LoadData(input); - ASSERT_EQ(rc, SI_OK); - - bool hasMultiple = false; - bool result = ini.GetBoolValue("bools", "flag", false, &hasMultiple); - ASSERT_TRUE(result); // First value is true - ASSERT_TRUE(hasMultiple); - - // Get all values - CSimpleIniA::TNamesDepend values; - ini.GetAllValues("bools", "flag", values); - ASSERT_EQ(values.size(), 3); + ini.SetMultiKey(true); + + std::string input = "[bools]\n" + "flag = true\n" + "flag = false\n" + "flag = yes\n"; + + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); + + bool hasMultiple = false; + bool result = ini.GetBoolValue("bools", "flag", false, &hasMultiple); + ASSERT_TRUE(result); // First value is true + ASSERT_TRUE(hasMultiple); + + // Get all values + CSimpleIniA::TNamesDepend values; + ini.GetAllValues("bools", "flag", values); + ASSERT_EQ(values.size(), 3); } // Test SetBoolValue with force replace TEST_F(TestBoolean, TestSetBoolValueForceReplace) { - ini.SetMultiKey(true); + ini.SetMultiKey(true); - std::string input = - "[bools]\n" - "value = true\n" - "value = false\n"; + std::string input = "[bools]\n" + "value = true\n" + "value = false\n"; - SI_Error rc = ini.LoadData(input); - ASSERT_EQ(rc, SI_OK); + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); - // Replace all values - rc = ini.SetBoolValue("bools", "value", true, nullptr, true); - ASSERT_EQ(rc, SI_UPDATED); + // Replace all values + rc = ini.SetBoolValue("bools", "value", true, nullptr, true); + ASSERT_EQ(rc, SI_UPDATED); - // Should only have one value now - bool hasMultiple = false; - bool result = ini.GetBoolValue("bools", "value", false, &hasMultiple); - ASSERT_TRUE(result); - ASSERT_FALSE(hasMultiple); + // Should only have one value now + bool hasMultiple = false; + bool result = ini.GetBoolValue("bools", "value", false, &hasMultiple); + ASSERT_TRUE(result); + ASSERT_FALSE(hasMultiple); } // Test boolean roundtrip TEST_F(TestBoolean, TestBooleanRoundtrip) { - ini.SetBoolValue("test", "bool1", true); - ini.SetBoolValue("test", "bool2", false); - ini.SetBoolValue("test", "bool3", true); - - std::string output; - SI_Error rc = ini.Save(output); - ASSERT_EQ(rc, SI_OK); - - // Load it back - CSimpleIniA ini2; - ini2.SetUnicode(); - rc = ini2.LoadData(output); - ASSERT_EQ(rc, SI_OK); - - ASSERT_TRUE(ini2.GetBoolValue("test", "bool1", false)); - ASSERT_FALSE(ini2.GetBoolValue("test", "bool2", true)); - ASSERT_TRUE(ini2.GetBoolValue("test", "bool3", false)); + ini.SetBoolValue("test", "bool1", true); + ini.SetBoolValue("test", "bool2", false); + ini.SetBoolValue("test", "bool3", true); + + std::string output; + SI_Error rc = ini.Save(output); + ASSERT_EQ(rc, SI_OK); + + // Load it back + CSimpleIniA ini2; + ini2.SetUnicode(); + rc = ini2.LoadData(output); + ASSERT_EQ(rc, SI_OK); + + ASSERT_TRUE(ini2.GetBoolValue("test", "bool1", false)); + ASSERT_FALSE(ini2.GetBoolValue("test", "bool2", true)); + ASSERT_TRUE(ini2.GetBoolValue("test", "bool3", false)); } // Test "of" typo for "off" (documented quirk) TEST_F(TestBoolean, TestBoolValueOfTypo) { - std::string input = - "[bools]\n" - "typo = of\n"; + std::string input = "[bools]\n" + "typo = of\n"; - SI_Error rc = ini.LoadData(input); - ASSERT_EQ(rc, SI_OK); + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); - // "of" is recognized as false (documented quirk) - ASSERT_FALSE(ini.GetBoolValue("bools", "typo", true)); + // "of" is recognized as false (documented quirk) + ASSERT_FALSE(ini.GetBoolValue("bools", "typo", true)); } diff --git a/tests/ts-bugfix.cpp b/tests/ts-bugfix.cpp index dbcf039..fc05fd5 100644 --- a/tests/ts-bugfix.cpp +++ b/tests/ts-bugfix.cpp @@ -2,136 +2,129 @@ #include "gtest/gtest.h" TEST(TestBugFix, TestEmptySection) { - CSimpleIniA ini; - ini.SetValue("foo", "skey", "sval"); - ini.SetValue("", "rkey", "rval"); - ini.SetValue("bar", "skey", "sval"); - - std::string output; - ini.Save(output); - - std::string expected = - "rkey = rval\n" - "\n" - "\n" - "[foo]\n" - "skey = sval\n" - "\n" - "\n" - "[bar]\n" - "skey = sval\n"; - - output.erase(std::remove(output.begin(), output.end(), '\r'), output.end()); - ASSERT_STREQ(expected.c_str(), output.c_str()); + CSimpleIniA ini; + ini.SetValue("foo", "skey", "sval"); + ini.SetValue("", "rkey", "rval"); + ini.SetValue("bar", "skey", "sval"); + + std::string output; + ini.Save(output); + + std::string expected = "rkey = rval\n" + "\n" + "\n" + "[foo]\n" + "skey = sval\n" + "\n" + "\n" + "[bar]\n" + "skey = sval\n"; + + output.erase(std::remove(output.begin(), output.end(), '\r'), output.end()); + ASSERT_STREQ(expected.c_str(), output.c_str()); } TEST(TestBugFix, TestMultiLineIgnoreTrailSpace0) { - std::string input = - "; multiline values\n" - "key = <<pItem, "value2") == 0) { - found = true; - break; - } - } - ASSERT_FALSE(found); + ini.SetMultiKey(true); + + std::string input = "[section]\n" + "key = value1\n" + "key = value2\n" + "key = value3\n"; + + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); + + CSimpleIniA::TNamesDepend values; + ini.GetAllValues("section", "key", values); + ASSERT_EQ(values.size(), 3); + + // Delete specific value + bool result = ini.DeleteValue("section", "key", "value2"); + ASSERT_TRUE(result); + + // Should have 2 values left + values.clear(); + ini.GetAllValues("section", "key", values); + ASSERT_EQ(values.size(), 2); + + // Verify value2 is gone + bool found = false; + for (auto it = values.begin(); it != values.end(); ++it) { + if (strcmp(it->pItem, "value2") == 0) { + found = true; + break; + } + } + ASSERT_FALSE(found); } // Test DeleteValue removes all when value is NULL TEST_F(TestDeletion, TestDeleteValueNull) { - ini.SetMultiKey(true); + ini.SetMultiKey(true); - std::string input = - "[section]\n" - "key = value1\n" - "key = value2\n" - "key = value3\n"; + std::string input = "[section]\n" + "key = value1\n" + "key = value2\n" + "key = value3\n"; - SI_Error rc = ini.LoadData(input); - ASSERT_EQ(rc, SI_OK); + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); - // Delete all values (NULL means all) - bool result = ini.DeleteValue("section", "key", nullptr); - ASSERT_TRUE(result); + // Delete all values (NULL means all) + bool result = ini.DeleteValue("section", "key", nullptr); + ASSERT_TRUE(result); - ASSERT_FALSE(ini.KeyExists("section", "key")); + ASSERT_FALSE(ini.KeyExists("section", "key")); } // Test DeleteValue with removeEmpty TEST_F(TestDeletion, TestDeleteValueRemoveEmpty) { - ini.SetMultiKey(true); + ini.SetMultiKey(true); - std::string input = - "[section]\n" - "key = value1\n"; + std::string input = "[section]\n" + "key = value1\n"; - SI_Error rc = ini.LoadData(input); - ASSERT_EQ(rc, SI_OK); + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); - // Delete value with removeEmpty = true - bool result = ini.DeleteValue("section", "key", "value1", true); - ASSERT_TRUE(result); + // Delete value with removeEmpty = true + bool result = ini.DeleteValue("section", "key", "value1", true); + ASSERT_TRUE(result); - // Section should be removed - ASSERT_FALSE(ini.SectionExists("section")); + // Section should be removed + ASSERT_FALSE(ini.SectionExists("section")); } // Test DeleteValue non-existent value TEST_F(TestDeletion, TestDeleteValueMissing) { - ini.SetMultiKey(true); + ini.SetMultiKey(true); - std::string input = - "[section]\n" - "key = value1\n" - "key = value2\n"; + std::string input = "[section]\n" + "key = value1\n" + "key = value2\n"; - SI_Error rc = ini.LoadData(input); - ASSERT_EQ(rc, SI_OK); + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); - bool result = ini.DeleteValue("section", "key", "value3"); - ASSERT_FALSE(result); + bool result = ini.DeleteValue("section", "key", "value3"); + ASSERT_FALSE(result); - // Values should still be there - CSimpleIniA::TNamesDepend values; - ini.GetAllValues("section", "key", values); - ASSERT_EQ(values.size(), 2); + // Values should still be there + CSimpleIniA::TNamesDepend values; + ini.GetAllValues("section", "key", values); + ASSERT_EQ(values.size(), 2); } // Test Delete all keys one by one TEST_F(TestDeletion, TestDeleteAllKeys) { - std::string input = - "[section]\n" - "key1 = value1\n" - "key2 = value2\n" - "key3 = value3\n"; + std::string input = "[section]\n" + "key1 = value1\n" + "key2 = value2\n" + "key3 = value3\n"; - SI_Error rc = ini.LoadData(input); - ASSERT_EQ(rc, SI_OK); + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); - ASSERT_EQ(ini.GetSectionSize("section"), 3); + ASSERT_EQ(ini.GetSectionSize("section"), 3); - ini.Delete("section", "key1"); - ASSERT_EQ(ini.GetSectionSize("section"), 2); + ini.Delete("section", "key1"); + ASSERT_EQ(ini.GetSectionSize("section"), 2); - ini.Delete("section", "key2"); - ASSERT_EQ(ini.GetSectionSize("section"), 1); + ini.Delete("section", "key2"); + ASSERT_EQ(ini.GetSectionSize("section"), 1); - ini.Delete("section", "key3"); - ASSERT_EQ(ini.GetSectionSize("section"), 0); + ini.Delete("section", "key3"); + ASSERT_EQ(ini.GetSectionSize("section"), 0); - // Section should still exist - ASSERT_TRUE(ini.SectionExists("section")); + // Section should still exist + ASSERT_TRUE(ini.SectionExists("section")); } // Test deletion preserves other data TEST_F(TestDeletion, TestDeletePreservesOthers) { - std::string input = - "[section1]\n" - "key1 = value1\n" - "key2 = value2\n" - "\n" - "[section2]\n" - "key3 = value3\n" - "key4 = value4\n"; - - SI_Error rc = ini.LoadData(input); - ASSERT_EQ(rc, SI_OK); - - ini.Delete("section1", "key1"); - - // Verify section1 still has key2 - ASSERT_TRUE(ini.KeyExists("section1", "key2")); - const char* value = ini.GetValue("section1", "key2"); - ASSERT_STREQ(value, "value2"); - - // Verify section2 is untouched - ASSERT_TRUE(ini.KeyExists("section2", "key3")); - ASSERT_TRUE(ini.KeyExists("section2", "key4")); - value = ini.GetValue("section2", "key3"); - ASSERT_STREQ(value, "value3"); + std::string input = "[section1]\n" + "key1 = value1\n" + "key2 = value2\n" + "\n" + "[section2]\n" + "key3 = value3\n" + "key4 = value4\n"; + + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); + + ini.Delete("section1", "key1"); + + // Verify section1 still has key2 + ASSERT_TRUE(ini.KeyExists("section1", "key2")); + const char *value = ini.GetValue("section1", "key2"); + ASSERT_STREQ(value, "value2"); + + // Verify section2 is untouched + ASSERT_TRUE(ini.KeyExists("section2", "key3")); + ASSERT_TRUE(ini.KeyExists("section2", "key4")); + value = ini.GetValue("section2", "key3"); + ASSERT_STREQ(value, "value3"); } // Test Delete and Save roundtrip TEST_F(TestDeletion, TestDeleteRoundtrip) { - std::string input = - "[section1]\n" - "key1 = value1\n" - "key2 = value2\n" - "\n" - "[section2]\n" - "key3 = value3\n"; - - SI_Error rc = ini.LoadData(input); - ASSERT_EQ(rc, SI_OK); - - ini.Delete("section1", "key1"); - - std::string output; - rc = ini.Save(output); - ASSERT_EQ(rc, SI_OK); - - // Load it back - CSimpleIniA ini2; - ini2.SetUnicode(); - rc = ini2.LoadData(output); - ASSERT_EQ(rc, SI_OK); - - ASSERT_FALSE(ini2.KeyExists("section1", "key1")); - ASSERT_TRUE(ini2.KeyExists("section1", "key2")); - ASSERT_TRUE(ini2.KeyExists("section2", "key3")); + std::string input = "[section1]\n" + "key1 = value1\n" + "key2 = value2\n" + "\n" + "[section2]\n" + "key3 = value3\n"; + + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); + + ini.Delete("section1", "key1"); + + std::string output; + rc = ini.Save(output); + ASSERT_EQ(rc, SI_OK); + + // Load it back + CSimpleIniA ini2; + ini2.SetUnicode(); + rc = ini2.LoadData(output); + ASSERT_EQ(rc, SI_OK); + + ASSERT_FALSE(ini2.KeyExists("section1", "key1")); + ASSERT_TRUE(ini2.KeyExists("section1", "key2")); + ASSERT_TRUE(ini2.KeyExists("section2", "key3")); } // Test DeleteValue only deletes exact match TEST_F(TestDeletion, TestDeleteValueExactMatch) { - ini.SetMultiKey(true); - - std::string input = - "[section]\n" - "key = value\n" - "key = value123\n" - "key = 123value\n"; - - SI_Error rc = ini.LoadData(input); - ASSERT_EQ(rc, SI_OK); - - bool result = ini.DeleteValue("section", "key", "value"); - ASSERT_TRUE(result); - - CSimpleIniA::TNamesDepend values; - ini.GetAllValues("section", "key", values); - ASSERT_EQ(values.size(), 2); - - // Verify "value" is gone but others remain - bool found_value123 = false, found_123value = false, found_value = false; - for (auto it = values.begin(); it != values.end(); ++it) { - if (strcmp(it->pItem, "value123") == 0) found_value123 = true; - if (strcmp(it->pItem, "123value") == 0) found_123value = true; - if (strcmp(it->pItem, "value") == 0) found_value = true; - } - ASSERT_TRUE(found_value123); - ASSERT_TRUE(found_123value); - ASSERT_FALSE(found_value); + ini.SetMultiKey(true); + + std::string input = "[section]\n" + "key = value\n" + "key = value123\n" + "key = 123value\n"; + + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); + + bool result = ini.DeleteValue("section", "key", "value"); + ASSERT_TRUE(result); + + CSimpleIniA::TNamesDepend values; + ini.GetAllValues("section", "key", values); + ASSERT_EQ(values.size(), 2); + + // Verify "value" is gone but others remain + bool found_value123 = false, found_123value = false, found_value = false; + for (auto it = values.begin(); it != values.end(); ++it) { + if (strcmp(it->pItem, "value123") == 0) + found_value123 = true; + if (strcmp(it->pItem, "123value") == 0) + found_123value = true; + if (strcmp(it->pItem, "value") == 0) + found_value = true; + } + ASSERT_TRUE(found_value123); + ASSERT_TRUE(found_123value); + ASSERT_FALSE(found_value); } diff --git a/tests/ts-edgecases.cpp b/tests/ts-edgecases.cpp index 8f6bcc6..2bf560e 100644 --- a/tests/ts-edgecases.cpp +++ b/tests/ts-edgecases.cpp @@ -4,393 +4,375 @@ class TestEdgeCases : public ::testing::Test { protected: - void SetUp() override; + void SetUp() override; + protected: - CSimpleIniA ini; + CSimpleIniA ini; }; -void TestEdgeCases::SetUp() { - ini.SetUnicode(); -} +void TestEdgeCases::SetUp() { ini.SetUnicode(); } // Test special characters in section names TEST_F(TestEdgeCases, TestSpecialCharsSectionNames) { - std::string input = - "[section-with-dashes]\n" - "key = value1\n" - "\n" - "[section_with_underscores]\n" - "key = value2\n" - "\n" - "[section.with.dots]\n" - "key = value3\n" - "\n" - "[section:with:colons]\n" - "key = value4\n"; - - SI_Error rc = ini.LoadData(input); - ASSERT_EQ(rc, SI_OK); - - ASSERT_STREQ(ini.GetValue("section-with-dashes", "key"), "value1"); - ASSERT_STREQ(ini.GetValue("section_with_underscores", "key"), "value2"); - ASSERT_STREQ(ini.GetValue("section.with.dots", "key"), "value3"); - ASSERT_STREQ(ini.GetValue("section:with:colons", "key"), "value4"); + std::string input = "[section-with-dashes]\n" + "key = value1\n" + "\n" + "[section_with_underscores]\n" + "key = value2\n" + "\n" + "[section.with.dots]\n" + "key = value3\n" + "\n" + "[section:with:colons]\n" + "key = value4\n"; + + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); + + ASSERT_STREQ(ini.GetValue("section-with-dashes", "key"), "value1"); + ASSERT_STREQ(ini.GetValue("section_with_underscores", "key"), "value2"); + ASSERT_STREQ(ini.GetValue("section.with.dots", "key"), "value3"); + ASSERT_STREQ(ini.GetValue("section:with:colons", "key"), "value4"); } // Test special characters in key names TEST_F(TestEdgeCases, TestSpecialCharsKeyNames) { - std::string input = - "[section]\n" - "key-with-dashes = value1\n" - "key_with_underscores = value2\n" - "key.with.dots = value3\n"; - - SI_Error rc = ini.LoadData(input); - ASSERT_EQ(rc, SI_OK); - - ASSERT_STREQ(ini.GetValue("section", "key-with-dashes"), "value1"); - ASSERT_STREQ(ini.GetValue("section", "key_with_underscores"), "value2"); - ASSERT_STREQ(ini.GetValue("section", "key.with.dots"), "value3"); + std::string input = "[section]\n" + "key-with-dashes = value1\n" + "key_with_underscores = value2\n" + "key.with.dots = value3\n"; + + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); + + ASSERT_STREQ(ini.GetValue("section", "key-with-dashes"), "value1"); + ASSERT_STREQ(ini.GetValue("section", "key_with_underscores"), "value2"); + ASSERT_STREQ(ini.GetValue("section", "key.with.dots"), "value3"); } // Test equals sign in value TEST_F(TestEdgeCases, TestEqualsInValue) { - std::string input = - "[section]\n" - "key1 = value=with=equals\n" - "key2 = a=b\n" - "key3 = ===\n"; - - SI_Error rc = ini.LoadData(input); - ASSERT_EQ(rc, SI_OK); - - ASSERT_STREQ(ini.GetValue("section", "key1"), "value=with=equals"); - ASSERT_STREQ(ini.GetValue("section", "key2"), "a=b"); - ASSERT_STREQ(ini.GetValue("section", "key3"), "==="); + std::string input = "[section]\n" + "key1 = value=with=equals\n" + "key2 = a=b\n" + "key3 = ===\n"; + + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); + + ASSERT_STREQ(ini.GetValue("section", "key1"), "value=with=equals"); + ASSERT_STREQ(ini.GetValue("section", "key2"), "a=b"); + ASSERT_STREQ(ini.GetValue("section", "key3"), "==="); } // Test semicolon in value (comment character) TEST_F(TestEdgeCases, TestSemicolonInValue) { - std::string input = - "[section]\n" - "key = value ; this is not a comment\n"; + std::string input = "[section]\n" + "key = value ; this is not a comment\n"; - SI_Error rc = ini.LoadData(input); - ASSERT_EQ(rc, SI_OK); + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); - // The semicolon and text after should be part of value (trimmed) - const char* value = ini.GetValue("section", "key"); - ASSERT_NE(value, nullptr); - // Value might be "value ; this is not a comment" or just "value" depending on implementation + // The semicolon and text after should be part of value (trimmed) + const char *value = ini.GetValue("section", "key"); + ASSERT_NE(value, nullptr); + // Value might be "value ; this is not a comment" or just "value" depending on implementation } // Test hash in value (comment character) TEST_F(TestEdgeCases, TestHashInValue) { - std::string input = - "[section]\n" - "key = value # this is not a comment\n"; + std::string input = "[section]\n" + "key = value # this is not a comment\n"; - SI_Error rc = ini.LoadData(input); - ASSERT_EQ(rc, SI_OK); + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); - const char* value = ini.GetValue("section", "key"); - ASSERT_NE(value, nullptr); + const char *value = ini.GetValue("section", "key"); + ASSERT_NE(value, nullptr); } // Test bracket in value TEST_F(TestEdgeCases, TestBracketInValue) { - std::string input = - "[section]\n" - "key1 = [value]\n" - "key2 = ]value[\n" - "key3 = [[nested]]\n"; - - SI_Error rc = ini.LoadData(input); - ASSERT_EQ(rc, SI_OK); - - ASSERT_STREQ(ini.GetValue("section", "key1"), "[value]"); - ASSERT_STREQ(ini.GetValue("section", "key2"), "]value["); - ASSERT_STREQ(ini.GetValue("section", "key3"), "[[nested]]"); + std::string input = "[section]\n" + "key1 = [value]\n" + "key2 = ]value[\n" + "key3 = [[nested]]\n"; + + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); + + ASSERT_STREQ(ini.GetValue("section", "key1"), "[value]"); + ASSERT_STREQ(ini.GetValue("section", "key2"), "]value["); + ASSERT_STREQ(ini.GetValue("section", "key3"), "[[nested]]"); } // Test very long section name TEST_F(TestEdgeCases, TestLongSectionName) { - std::string longName(1000, 'a'); - std::string input = "[" + longName + "]\nkey=value\n"; + std::string longName(1000, 'a'); + std::string input = "[" + longName + "]\nkey=value\n"; - SI_Error rc = ini.LoadData(input); - ASSERT_EQ(rc, SI_OK); + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); - ASSERT_TRUE(ini.SectionExists(longName.c_str())); - ASSERT_STREQ(ini.GetValue(longName.c_str(), "key"), "value"); + ASSERT_TRUE(ini.SectionExists(longName.c_str())); + ASSERT_STREQ(ini.GetValue(longName.c_str(), "key"), "value"); } // Test very long key name TEST_F(TestEdgeCases, TestLongKeyName) { - std::string longKey(1000, 'b'); - std::string input = "[section]\n" + longKey + "=value\n"; + std::string longKey(1000, 'b'); + std::string input = "[section]\n" + longKey + "=value\n"; - SI_Error rc = ini.LoadData(input); - ASSERT_EQ(rc, SI_OK); + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); - ASSERT_STREQ(ini.GetValue("section", longKey.c_str()), "value"); + ASSERT_STREQ(ini.GetValue("section", longKey.c_str()), "value"); } // Test very long value TEST_F(TestEdgeCases, TestLongValue) { - std::string longValue(10000, 'c'); - ini.SetValue("section", "key", longValue.c_str()); + std::string longValue(10000, 'c'); + ini.SetValue("section", "key", longValue.c_str()); - const char* value = ini.GetValue("section", "key"); - ASSERT_STREQ(value, longValue.c_str()); + const char *value = ini.GetValue("section", "key"); + ASSERT_STREQ(value, longValue.c_str()); } // Test leading whitespace in section name TEST_F(TestEdgeCases, TestLeadingWhitespaceSection) { - std::string input = - "[ section ]\n" - "key = value\n"; + std::string input = "[ section ]\n" + "key = value\n"; - SI_Error rc = ini.LoadData(input); - ASSERT_EQ(rc, SI_OK); + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); - // Whitespace should be trimmed - ASSERT_TRUE(ini.SectionExists("section")); - ASSERT_STREQ(ini.GetValue("section", "key"), "value"); + // Whitespace should be trimmed + ASSERT_TRUE(ini.SectionExists("section")); + ASSERT_STREQ(ini.GetValue("section", "key"), "value"); } // Test leading/trailing whitespace in key name TEST_F(TestEdgeCases, TestWhitespaceKeyName) { - std::string input = - "[section]\n" - " key = value\n"; + std::string input = "[section]\n" + " key = value\n"; - SI_Error rc = ini.LoadData(input); - ASSERT_EQ(rc, SI_OK); + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); - // Whitespace should be trimmed - ASSERT_TRUE(ini.KeyExists("section", "key")); - ASSERT_STREQ(ini.GetValue("section", "key"), "value"); + // Whitespace should be trimmed + ASSERT_TRUE(ini.KeyExists("section", "key")); + ASSERT_STREQ(ini.GetValue("section", "key"), "value"); } // Test leading/trailing whitespace in value TEST_F(TestEdgeCases, TestWhitespaceValue) { - std::string input = - "[section]\n" - "key = value \n"; + std::string input = "[section]\n" + "key = value \n"; - SI_Error rc = ini.LoadData(input); - ASSERT_EQ(rc, SI_OK); + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); - // Whitespace should be trimmed - const char* value = ini.GetValue("section", "key"); - ASSERT_STREQ(value, "value"); + // Whitespace should be trimmed + const char *value = ini.GetValue("section", "key"); + ASSERT_STREQ(value, "value"); } // Test tabs as whitespace TEST_F(TestEdgeCases, TestTabWhitespace) { - std::string input = - "[\tsection\t]\n" - "\tkey\t=\tvalue\t\n"; + std::string input = "[\tsection\t]\n" + "\tkey\t=\tvalue\t\n"; - SI_Error rc = ini.LoadData(input); - ASSERT_EQ(rc, SI_OK); + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); - ASSERT_TRUE(ini.SectionExists("section")); - ASSERT_STREQ(ini.GetValue("section", "key"), "value"); + ASSERT_TRUE(ini.SectionExists("section")); + ASSERT_STREQ(ini.GetValue("section", "key"), "value"); } // Test empty lines and multiple blank lines TEST_F(TestEdgeCases, TestEmptyLines) { - std::string input = - "\n\n\n" - "[section1]\n" - "\n\n" - "key1 = value1\n" - "\n\n\n" - "[section2]\n" - "\n" - "key2 = value2\n" - "\n\n"; - - SI_Error rc = ini.LoadData(input); - ASSERT_EQ(rc, SI_OK); - - ASSERT_STREQ(ini.GetValue("section1", "key1"), "value1"); - ASSERT_STREQ(ini.GetValue("section2", "key2"), "value2"); + std::string input = "\n\n\n" + "[section1]\n" + "\n\n" + "key1 = value1\n" + "\n\n\n" + "[section2]\n" + "\n" + "key2 = value2\n" + "\n\n"; + + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); + + ASSERT_STREQ(ini.GetValue("section1", "key1"), "value1"); + ASSERT_STREQ(ini.GetValue("section2", "key2"), "value2"); } // Test different newline formats TEST_F(TestEdgeCases, TestMixedNewlines) { - // Mix of \r\n, \n, and \r - std::string input = - "[section1]\r\n" - "key1 = value1\n" - "[section2]\r" - "key2 = value2\r\n"; - - SI_Error rc = ini.LoadData(input); - ASSERT_EQ(rc, SI_OK); - - ASSERT_STREQ(ini.GetValue("section1", "key1"), "value1"); - ASSERT_STREQ(ini.GetValue("section2", "key2"), "value2"); + // Mix of \r\n, \n, and \r + std::string input = "[section1]\r\n" + "key1 = value1\n" + "[section2]\r" + "key2 = value2\r\n"; + + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); + + ASSERT_STREQ(ini.GetValue("section1", "key1"), "value1"); + ASSERT_STREQ(ini.GetValue("section2", "key2"), "value2"); } // Test malformed section (no closing bracket) TEST_F(TestEdgeCases, TestMalformedSection) { - std::string input = - "[section\n" - "key = value\n"; + std::string input = "[section\n" + "key = value\n"; - SI_Error rc = ini.LoadData(input); - // Should either handle gracefully or accept as-is - ASSERT_EQ(rc, SI_OK); + SI_Error rc = ini.LoadData(input); + // Should either handle gracefully or accept as-is + ASSERT_EQ(rc, SI_OK); } // Test multiple equals signs on line TEST_F(TestEdgeCases, TestMultipleEquals) { - std::string input = - "[section]\n" - "key = value = more = data\n"; + std::string input = "[section]\n" + "key = value = more = data\n"; - SI_Error rc = ini.LoadData(input); - ASSERT_EQ(rc, SI_OK); + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); - // Everything after first = should be the value - ASSERT_STREQ(ini.GetValue("section", "key"), "value = more = data"); + // Everything after first = should be the value + ASSERT_STREQ(ini.GetValue("section", "key"), "value = more = data"); } // Test empty value vs no equals TEST_F(TestEdgeCases, TestEmptyVsNoEquals) { - ini.SetAllowKeyOnly(true); + ini.SetAllowKeyOnly(true); - std::string input = - "[section]\n" - "key1 = \n" - "key2\n"; + std::string input = "[section]\n" + "key1 = \n" + "key2\n"; - SI_Error rc = ini.LoadData(input); - ASSERT_EQ(rc, SI_OK); + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); - const char* val1 = ini.GetValue("section", "key1"); - const char* val2 = ini.GetValue("section", "key2"); + const char *val1 = ini.GetValue("section", "key1"); + const char *val2 = ini.GetValue("section", "key2"); - // Both should exist - ASSERT_TRUE(ini.KeyExists("section", "key1")); - ASSERT_TRUE(ini.KeyExists("section", "key2")); + // Both should exist + ASSERT_TRUE(ini.KeyExists("section", "key1")); + ASSERT_TRUE(ini.KeyExists("section", "key2")); - // key1 should have empty string, key2 should be NULL or empty - ASSERT_NE(val1, nullptr); - ASSERT_STREQ(val1, ""); + // key1 should have empty string, key2 should be NULL or empty + ASSERT_NE(val1, nullptr); + ASSERT_STREQ(val1, ""); + ASSERT_TRUE(val2 == nullptr || val2[0] == '\0'); } // Test Unicode section/key/value names TEST_F(TestEdgeCases, TestUnicode) { - const char tesuto[] = u8"テスト"; - const char kensa[] = u8"検査"; - const char value[] = u8"値"; + const char tesuto[] = u8"テスト"; + const char kensa[] = u8"検査"; + const char value[] = u8"値"; - ini.SetValue(tesuto, kensa, value); + ini.SetValue(tesuto, kensa, value); - const char* result = ini.GetValue(tesuto, kensa); - ASSERT_STREQ(result, value); + const char *result = ini.GetValue(tesuto, kensa); + ASSERT_STREQ(result, value); } // Test many sections TEST_F(TestEdgeCases, TestManySections) { - for (int i = 0; i < 1000; i++) { - std::string section = "section" + std::to_string(i); - ini.SetValue(section.c_str(), "key", "value"); - } - - CSimpleIniA::TNamesDepend sections; - ini.GetAllSections(sections); - ASSERT_EQ(sections.size(), 1000); - - // Verify a few - ASSERT_STREQ(ini.GetValue("section0", "key"), "value"); - ASSERT_STREQ(ini.GetValue("section500", "key"), "value"); - ASSERT_STREQ(ini.GetValue("section999", "key"), "value"); + for (int i = 0; i < 1000; i++) { + std::string section = "section" + std::to_string(i); + ini.SetValue(section.c_str(), "key", "value"); + } + + CSimpleIniA::TNamesDepend sections; + ini.GetAllSections(sections); + ASSERT_EQ(sections.size(), 1000); + + // Verify a few + ASSERT_STREQ(ini.GetValue("section0", "key"), "value"); + ASSERT_STREQ(ini.GetValue("section500", "key"), "value"); + ASSERT_STREQ(ini.GetValue("section999", "key"), "value"); } // Test many keys in one section TEST_F(TestEdgeCases, TestManyKeys) { - for (int i = 0; i < 1000; i++) { - std::string key = "key" + std::to_string(i); - ini.SetValue("section", key.c_str(), "value"); - } + for (int i = 0; i < 1000; i++) { + std::string key = "key" + std::to_string(i); + ini.SetValue("section", key.c_str(), "value"); + } - ASSERT_EQ(ini.GetSectionSize("section"), 1000); + ASSERT_EQ(ini.GetSectionSize("section"), 1000); - // Verify a few - ASSERT_STREQ(ini.GetValue("section", "key0"), "value"); - ASSERT_STREQ(ini.GetValue("section", "key500"), "value"); - ASSERT_STREQ(ini.GetValue("section", "key999"), "value"); + // Verify a few + ASSERT_STREQ(ini.GetValue("section", "key0"), "value"); + ASSERT_STREQ(ini.GetValue("section", "key500"), "value"); + ASSERT_STREQ(ini.GetValue("section", "key999"), "value"); } // Test SetValue with comment TEST_F(TestEdgeCases, TestSetValueWithComment) { - SI_Error rc = ini.SetValue("section", "key", "value", "; This is a comment"); - ASSERT_EQ(rc, SI_INSERTED); + SI_Error rc = ini.SetValue("section", "key", "value", "; This is a comment"); + ASSERT_EQ(rc, SI_INSERTED); - ASSERT_STREQ(ini.GetValue("section", "key"), "value"); + ASSERT_STREQ(ini.GetValue("section", "key"), "value"); - std::string output; - ini.Save(output); + std::string output; + ini.Save(output); - // Comment should be in output - EXPECT_NE(output.find("; This is a comment"), std::string::npos); + // Comment should be in output + EXPECT_NE(output.find("; This is a comment"), std::string::npos); } // Test key without section (should go to empty section) TEST_F(TestEdgeCases, TestKeyWithoutSection) { - std::string input = - "key1 = value1\n" - "\n" - "[section]\n" - "key2 = value2\n"; - - SI_Error rc = ini.LoadData(input); - ASSERT_EQ(rc, SI_OK); - - // key1 should be in empty section - ASSERT_STREQ(ini.GetValue("", "key1"), "value1"); - ASSERT_STREQ(ini.GetValue("section", "key2"), "value2"); + std::string input = "key1 = value1\n" + "\n" + "[section]\n" + "key2 = value2\n"; + + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); + + // key1 should be in empty section + ASSERT_STREQ(ini.GetValue("", "key1"), "value1"); + ASSERT_STREQ(ini.GetValue("section", "key2"), "value2"); } // Test consecutive sections with same name (should merge or override) TEST_F(TestEdgeCases, TestDuplicateSections) { - std::string input = - "[section]\n" - "key1 = value1\n" - "\n" - "[section]\n" - "key2 = value2\n"; - - SI_Error rc = ini.LoadData(input); - ASSERT_EQ(rc, SI_OK); - - // Both keys should be in the section - ASSERT_STREQ(ini.GetValue("section", "key1"), "value1"); - ASSERT_STREQ(ini.GetValue("section", "key2"), "value2"); - ASSERT_EQ(ini.GetSectionSize("section"), 2); + std::string input = "[section]\n" + "key1 = value1\n" + "\n" + "[section]\n" + "key2 = value2\n"; + + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); + + // Both keys should be in the section + ASSERT_STREQ(ini.GetValue("section", "key1"), "value1"); + ASSERT_STREQ(ini.GetValue("section", "key2"), "value2"); + ASSERT_EQ(ini.GetSectionSize("section"), 2); } // Test value with only whitespace TEST_F(TestEdgeCases, TestWhitespaceOnlyValue) { - std::string input = - "[section]\n" - "key1 = \n" - "key2 = \t\t\t\n"; - - SI_Error rc = ini.LoadData(input); - ASSERT_EQ(rc, SI_OK); - - // Whitespace-only should be trimmed to empty - const char* val1 = ini.GetValue("section", "key1"); - const char* val2 = ini.GetValue("section", "key2"); - ASSERT_NE(val1, nullptr); - ASSERT_NE(val2, nullptr); - ASSERT_STREQ(val1, ""); - ASSERT_STREQ(val2, ""); + std::string input = "[section]\n" + "key1 = \n" + "key2 = \t\t\t\n"; + + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); + + // Whitespace-only should be trimmed to empty + const char *val1 = ini.GetValue("section", "key1"); + const char *val2 = ini.GetValue("section", "key2"); + ASSERT_NE(val1, nullptr); + ASSERT_NE(val2, nullptr); + ASSERT_STREQ(val1, ""); + ASSERT_STREQ(val2, ""); } diff --git a/tests/ts-generic.cpp b/tests/ts-generic.cpp index ecdf7cc..af774cd 100644 --- a/tests/ts-generic.cpp +++ b/tests/ts-generic.cpp @@ -5,67 +5,68 @@ // Test SI_CONVERT_GENERIC with char interface class TestGenericChar : public ::testing::Test { protected: - void SetUp() override; + void SetUp() override; + protected: - CSimpleIniA ini; + CSimpleIniA ini; }; void TestGenericChar::SetUp() { - ini.SetUnicode(); - SI_Error err = ini.LoadFile("tests.ini"); - ASSERT_EQ(err, SI_OK); + ini.SetUnicode(); + SI_Error err = ini.LoadFile("tests.ini"); + ASSERT_EQ(err, SI_OK); } TEST_F(TestGenericChar, TestSectionAKeyAValA) { - const char* result = ini.GetValue("section1", "key1"); - ASSERT_STREQ(result, "value1"); + const char *result = ini.GetValue("section1", "key1"); + ASSERT_STREQ(result, "value1"); } TEST_F(TestGenericChar, TestSectionAKeyAValU) { - const char tesuto2[] = u8"テスト2"; - const char* result = ini.GetValue("section2", "test2"); - ASSERT_STREQ(result, tesuto2); + const char tesuto2[] = u8"テスト2"; + const char *result = ini.GetValue("section2", "test2"); + ASSERT_STREQ(result, tesuto2); } TEST_F(TestGenericChar, TestSectionAKeyUValA) { - const char tesuto[] = u8"テスト"; - const char* result = ini.GetValue("section2", tesuto); - ASSERT_STREQ(result, "test"); + const char tesuto[] = u8"テスト"; + const char *result = ini.GetValue("section2", tesuto); + ASSERT_STREQ(result, "test"); } TEST_F(TestGenericChar, TestSectionAKeyUValU) { - const char tesuto2[] = u8"テスト2"; - const char tesutoni[] = u8"テスト二"; - const char* result = ini.GetValue("section2", tesuto2); - ASSERT_STREQ(result, tesutoni); + const char tesuto2[] = u8"テスト2"; + const char tesutoni[] = u8"テスト二"; + const char *result = ini.GetValue("section2", tesuto2); + ASSERT_STREQ(result, tesutoni); } TEST_F(TestGenericChar, TestSectionUKeyAValA) { - const char kensa[] = u8"検査"; - const char* result = ini.GetValue(kensa, "key2"); - ASSERT_STREQ(result, "value2"); + const char kensa[] = u8"検査"; + const char *result = ini.GetValue(kensa, "key2"); + ASSERT_STREQ(result, "value2"); } TEST_F(TestGenericChar, TestSectionUKeyAValU) { - const char kensa[] = u8"検査"; - const char tesuto2[] = u8"テスト2"; - const char* result = ini.GetValue(kensa, "test2"); - ASSERT_STREQ(result, tesuto2); + const char kensa[] = u8"検査"; + const char tesuto2[] = u8"テスト2"; + const char *result = ini.GetValue(kensa, "test2"); + ASSERT_STREQ(result, tesuto2); } TEST_F(TestGenericChar, TestSectionUKeyUValA) { - const char kensa[] = u8"検査"; - const char tesuto[] = u8"テスト"; - const char* result = ini.GetValue(kensa, tesuto); - ASSERT_STREQ(result, "test"); + const char kensa[] = u8"検査"; + const char tesuto[] = u8"テスト"; + const char *result = ini.GetValue(kensa, tesuto); + ASSERT_STREQ(result, "test"); } TEST_F(TestGenericChar, TestSectionUKeyUValU) { - const char kensa[] = u8"検査"; - const char tesuto2[] = u8"テスト2"; - const char tesutoni[] = u8"テスト二"; - const char* result = ini.GetValue(kensa, tesuto2); - ASSERT_STREQ(result, tesutoni); + const char kensa[] = u8"検査"; + const char tesuto2[] = u8"テスト2"; + const char tesutoni[] = u8"テスト二"; + const char *result = ini.GetValue(kensa, tesuto2); + ASSERT_STREQ(result, tesutoni); } #ifdef _WIN32 @@ -73,66 +74,67 @@ TEST_F(TestGenericChar, TestSectionUKeyUValU) { // On non-Windows platforms, wchar_t support with SI_CONVERT_GENERIC doesn't work the same way class TestGenericWide : public ::testing::Test { protected: - void SetUp() override; + void SetUp() override; + protected: - CSimpleIniW ini; + CSimpleIniW ini; }; void TestGenericWide::SetUp() { - ini.SetUnicode(); - SI_Error err = ini.LoadFile("tests.ini"); - ASSERT_EQ(err, SI_OK); + ini.SetUnicode(); + SI_Error err = ini.LoadFile("tests.ini"); + ASSERT_EQ(err, SI_OK); } TEST_F(TestGenericWide, TestSectionAKeyAValA) { - const wchar_t* result = ini.GetValue(L"section1", L"key1"); - ASSERT_STREQ(result, L"value1"); + const wchar_t *result = ini.GetValue(L"section1", L"key1"); + ASSERT_STREQ(result, L"value1"); } TEST_F(TestGenericWide, TestSectionAKeyAValU) { - const wchar_t tesuto2[] = L"テスト2"; - const wchar_t* result = ini.GetValue(L"section2", L"test2"); - ASSERT_STREQ(result, tesuto2); + const wchar_t tesuto2[] = L"テスト2"; + const wchar_t *result = ini.GetValue(L"section2", L"test2"); + ASSERT_STREQ(result, tesuto2); } TEST_F(TestGenericWide, TestSectionAKeyUValA) { - const wchar_t tesuto[] = L"テスト"; - const wchar_t* result = ini.GetValue(L"section2", tesuto); - ASSERT_STREQ(result, L"test"); + const wchar_t tesuto[] = L"テスト"; + const wchar_t *result = ini.GetValue(L"section2", tesuto); + ASSERT_STREQ(result, L"test"); } TEST_F(TestGenericWide, TestSectionAKeyUValU) { - const wchar_t tesuto2[] = L"テスト2"; - const wchar_t tesutoni[] = L"テスト二"; - const wchar_t* result = ini.GetValue(L"section2", tesuto2); - ASSERT_STREQ(result, tesutoni); + const wchar_t tesuto2[] = L"テスト2"; + const wchar_t tesutoni[] = L"テスト二"; + const wchar_t *result = ini.GetValue(L"section2", tesuto2); + ASSERT_STREQ(result, tesutoni); } TEST_F(TestGenericWide, TestSectionUKeyAValA) { - const wchar_t kensa[] = L"検査"; - const wchar_t* result = ini.GetValue(kensa, L"key2"); - ASSERT_STREQ(result, L"value2"); + const wchar_t kensa[] = L"検査"; + const wchar_t *result = ini.GetValue(kensa, L"key2"); + ASSERT_STREQ(result, L"value2"); } TEST_F(TestGenericWide, TestSectionUKeyAValU) { - const wchar_t kensa[] = L"検査"; - const wchar_t tesuto2[] = L"テスト2"; - const wchar_t* result = ini.GetValue(kensa, L"test2"); - ASSERT_STREQ(result, tesuto2); + const wchar_t kensa[] = L"検査"; + const wchar_t tesuto2[] = L"テスト2"; + const wchar_t *result = ini.GetValue(kensa, L"test2"); + ASSERT_STREQ(result, tesuto2); } TEST_F(TestGenericWide, TestSectionUKeyUValA) { - const wchar_t kensa[] = L"検査"; - const wchar_t tesuto[] = L"テスト"; - const wchar_t* result = ini.GetValue(kensa, tesuto); - ASSERT_STREQ(result, L"test"); + const wchar_t kensa[] = L"検査"; + const wchar_t tesuto[] = L"テスト"; + const wchar_t *result = ini.GetValue(kensa, tesuto); + ASSERT_STREQ(result, L"test"); } TEST_F(TestGenericWide, TestSectionUKeyUValU) { - const wchar_t kensa[] = L"検査"; - const wchar_t tesuto2[] = L"テスト2"; - const wchar_t tesutoni[] = L"テスト二"; - const wchar_t* result = ini.GetValue(kensa, tesuto2); - ASSERT_STREQ(result, tesutoni); + const wchar_t kensa[] = L"検査"; + const wchar_t tesuto2[] = L"テスト2"; + const wchar_t tesutoni[] = L"テスト二"; + const wchar_t *result = ini.GetValue(kensa, tesuto2); + ASSERT_STREQ(result, tesutoni); } #endif // _WIN32 diff --git a/tests/ts-multiline.cpp b/tests/ts-multiline.cpp index c6ee50c..73a3bcf 100644 --- a/tests/ts-multiline.cpp +++ b/tests/ts-multiline.cpp @@ -4,361 +4,345 @@ class TestMultiline : public ::testing::Test { protected: - void SetUp() override; + void SetUp() override; + protected: - CSimpleIniA ini; + CSimpleIniA ini; }; void TestMultiline::SetUp() { - ini.SetUnicode(); - ini.SetMultiLine(true); + ini.SetUnicode(); + ini.SetMultiLine(true); } // Test basic multiline value TEST_F(TestMultiline, TestBasicMultiline) { - std::string input = - "[section]\n" - "key = << #include #include +#include class TestNumeric : public ::testing::Test { protected: - void SetUp() override; + void SetUp() override; + protected: - CSimpleIniA ini; + CSimpleIniA ini; }; -void TestNumeric::SetUp() { - ini.SetUnicode(); -} +void TestNumeric::SetUp() { ini.SetUnicode(); } // Test GetLongValue with valid integers TEST_F(TestNumeric, TestGetLongValuePositive) { - std::string input = - "[numbers]\n" - "positive = 42\n" - "zero = 0\n" - "negative = -123\n"; + std::string input = "[numbers]\n" + "positive = 42\n" + "zero = 0\n" + "negative = -123\n"; - SI_Error rc = ini.LoadData(input); - ASSERT_EQ(rc, SI_OK); + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); - long result = ini.GetLongValue("numbers", "positive", 0); - ASSERT_EQ(result, 42); + long result = ini.GetLongValue("numbers", "positive", 0); + ASSERT_EQ(result, 42); - result = ini.GetLongValue("numbers", "zero", -1); - ASSERT_EQ(result, 0); + result = ini.GetLongValue("numbers", "zero", -1); + ASSERT_EQ(result, 0); - result = ini.GetLongValue("numbers", "negative", 0); - ASSERT_EQ(result, -123); + result = ini.GetLongValue("numbers", "negative", 0); + ASSERT_EQ(result, -123); } // Test GetLongValue with hex values TEST_F(TestNumeric, TestGetLongValueHex) { - std::string input = - "[numbers]\n" - "hex1 = 0xFF\n" - "hex2 = 0x10\n" - "hex3 = 0x12345678\n"; + std::string input = "[numbers]\n" + "hex1 = 0xFF\n" + "hex2 = 0x10\n" + "hex3 = 0x12345678\n"; - SI_Error rc = ini.LoadData(input); - ASSERT_EQ(rc, SI_OK); + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); - long result = ini.GetLongValue("numbers", "hex1", 0); - ASSERT_EQ(result, 0xFF); + long result = ini.GetLongValue("numbers", "hex1", 0); + ASSERT_EQ(result, 0xFF); - result = ini.GetLongValue("numbers", "hex2", 0); - ASSERT_EQ(result, 0x10); + result = ini.GetLongValue("numbers", "hex2", 0); + ASSERT_EQ(result, 0x10); - result = ini.GetLongValue("numbers", "hex3", 0); - ASSERT_EQ(result, 0x12345678); + result = ini.GetLongValue("numbers", "hex3", 0); + ASSERT_EQ(result, 0x12345678); } // Test GetLongValue with invalid values (should return default) TEST_F(TestNumeric, TestGetLongValueInvalid) { - std::string input = - "[numbers]\n" - "text = hello\n" - "empty = \n" - "partial = 123abc\n"; + std::string input = "[numbers]\n" + "text = hello\n" + "empty = \n" + "partial = 123abc\n"; - SI_Error rc = ini.LoadData(input); - ASSERT_EQ(rc, SI_OK); + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); - long result = ini.GetLongValue("numbers", "text", 999); - ASSERT_EQ(result, 999); + long result = ini.GetLongValue("numbers", "text", 999); + ASSERT_EQ(result, 999); - // Empty string returns default (SimpleIni behavior, not raw strtol) - result = ini.GetLongValue("numbers", "empty", 999); - ASSERT_EQ(result, 999); + // Empty string returns default (SimpleIni behavior, not raw strtol) + result = ini.GetLongValue("numbers", "empty", 999); + ASSERT_EQ(result, 999); - // "123abc" returns default - SimpleIni validates the full string - result = ini.GetLongValue("numbers", "partial", 999); - ASSERT_EQ(result, 999); + // "123abc" returns default - SimpleIni validates the full string + result = ini.GetLongValue("numbers", "partial", 999); + ASSERT_EQ(result, 999); } // Test GetLongValue with non-existent key TEST_F(TestNumeric, TestGetLongValueMissing) { - std::string input = "[numbers]\n"; + std::string input = "[numbers]\n"; - SI_Error rc = ini.LoadData(input); - ASSERT_EQ(rc, SI_OK); + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); - long result = ini.GetLongValue("numbers", "missing", 777); - ASSERT_EQ(result, 777); + long result = ini.GetLongValue("numbers", "missing", 777); + ASSERT_EQ(result, 777); - result = ini.GetLongValue("missing_section", "key", 888); - ASSERT_EQ(result, 888); + result = ini.GetLongValue("missing_section", "key", 888); + ASSERT_EQ(result, 888); } // Test SetLongValue TEST_F(TestNumeric, TestSetLongValue) { - SI_Error rc = ini.SetLongValue("numbers", "value1", 12345); - ASSERT_EQ(rc, SI_INSERTED); + SI_Error rc = ini.SetLongValue("numbers", "value1", 12345); + ASSERT_EQ(rc, SI_INSERTED); - long result = ini.GetLongValue("numbers", "value1", 0); - ASSERT_EQ(result, 12345); + long result = ini.GetLongValue("numbers", "value1", 0); + ASSERT_EQ(result, 12345); - // Update existing value - rc = ini.SetLongValue("numbers", "value1", 67890); - ASSERT_EQ(rc, SI_UPDATED); + // Update existing value + rc = ini.SetLongValue("numbers", "value1", 67890); + ASSERT_EQ(rc, SI_UPDATED); - result = ini.GetLongValue("numbers", "value1", 0); - ASSERT_EQ(result, 67890); + result = ini.GetLongValue("numbers", "value1", 0); + ASSERT_EQ(result, 67890); } // Test SetLongValue with hex format TEST_F(TestNumeric, TestSetLongValueHex) { - SI_Error rc = ini.SetLongValue("numbers", "hexval", 255, nullptr, true); - ASSERT_EQ(rc, SI_INSERTED); + SI_Error rc = ini.SetLongValue("numbers", "hexval", 255, nullptr, true); + ASSERT_EQ(rc, SI_INSERTED); - std::string output; - rc = ini.Save(output); - ASSERT_EQ(rc, SI_OK); + std::string output; + rc = ini.Save(output); + ASSERT_EQ(rc, SI_OK); - // Should be written as hex - EXPECT_NE(output.find("0xff"), std::string::npos); + // Should be written as hex + EXPECT_NE(output.find("0xff"), std::string::npos); - // Should read back correctly - long result = ini.GetLongValue("numbers", "hexval", 0); - ASSERT_EQ(result, 255); + // Should read back correctly + long result = ini.GetLongValue("numbers", "hexval", 0); + ASSERT_EQ(result, 255); } // Test SetLongValue with negative values TEST_F(TestNumeric, TestSetLongValueNegative) { - SI_Error rc = ini.SetLongValue("numbers", "negative", -9999); - ASSERT_EQ(rc, SI_INSERTED); + SI_Error rc = ini.SetLongValue("numbers", "negative", -9999); + ASSERT_EQ(rc, SI_INSERTED); - long result = ini.GetLongValue("numbers", "negative", 0); - ASSERT_EQ(result, -9999); + long result = ini.GetLongValue("numbers", "negative", 0); + ASSERT_EQ(result, -9999); } // Test GetDoubleValue with valid doubles TEST_F(TestNumeric, TestGetDoubleValue) { - std::string input = - "[floats]\n" - "pi = 3.14159\n" - "negative = -2.5\n" - "integer = 42.0\n" - "scientific = 1.23e-4\n" - "zero = 0.0\n"; + std::string input = "[floats]\n" + "pi = 3.14159\n" + "negative = -2.5\n" + "integer = 42.0\n" + "scientific = 1.23e-4\n" + "zero = 0.0\n"; - SI_Error rc = ini.LoadData(input); - ASSERT_EQ(rc, SI_OK); + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); - double result = ini.GetDoubleValue("floats", "pi", 0.0); - ASSERT_NEAR(result, 3.14159, 0.00001); + double result = ini.GetDoubleValue("floats", "pi", 0.0); + ASSERT_NEAR(result, 3.14159, 0.00001); - result = ini.GetDoubleValue("floats", "negative", 0.0); - ASSERT_NEAR(result, -2.5, 0.00001); + result = ini.GetDoubleValue("floats", "negative", 0.0); + ASSERT_NEAR(result, -2.5, 0.00001); - result = ini.GetDoubleValue("floats", "integer", 0.0); - ASSERT_NEAR(result, 42.0, 0.00001); + result = ini.GetDoubleValue("floats", "integer", 0.0); + ASSERT_NEAR(result, 42.0, 0.00001); - result = ini.GetDoubleValue("floats", "scientific", 0.0); - ASSERT_NEAR(result, 1.23e-4, 0.000001); + result = ini.GetDoubleValue("floats", "scientific", 0.0); + ASSERT_NEAR(result, 1.23e-4, 0.000001); - result = ini.GetDoubleValue("floats", "zero", 1.0); - ASSERT_NEAR(result, 0.0, 0.00001); + result = ini.GetDoubleValue("floats", "zero", 1.0); + ASSERT_NEAR(result, 0.0, 0.00001); } // Test GetDoubleValue with invalid values TEST_F(TestNumeric, TestGetDoubleValueInvalid) { - std::string input = - "[floats]\n" - "text = not_a_number\n" - "empty = \n"; + std::string input = "[floats]\n" + "text = not_a_number\n" + "empty = \n"; - SI_Error rc = ini.LoadData(input); - ASSERT_EQ(rc, SI_OK); + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); - double result = ini.GetDoubleValue("floats", "text", 99.9); - ASSERT_NEAR(result, 99.9, 0.00001); + double result = ini.GetDoubleValue("floats", "text", 99.9); + ASSERT_NEAR(result, 99.9, 0.00001); - result = ini.GetDoubleValue("floats", "empty", 88.8); - ASSERT_NEAR(result, 88.8, 0.00001); + result = ini.GetDoubleValue("floats", "empty", 88.8); + ASSERT_NEAR(result, 88.8, 0.00001); } // Test SetDoubleValue TEST_F(TestNumeric, TestSetDoubleValue) { - SI_Error rc = ini.SetDoubleValue("floats", "value1", 3.14159); - ASSERT_EQ(rc, SI_INSERTED); + SI_Error rc = ini.SetDoubleValue("floats", "value1", 3.14159); + ASSERT_EQ(rc, SI_INSERTED); - double result = ini.GetDoubleValue("floats", "value1", 0.0); - ASSERT_NEAR(result, 3.14159, 0.00001); + double result = ini.GetDoubleValue("floats", "value1", 0.0); + ASSERT_NEAR(result, 3.14159, 0.00001); - // Update existing value - rc = ini.SetDoubleValue("floats", "value1", 2.71828); - ASSERT_EQ(rc, SI_UPDATED); + // Update existing value + rc = ini.SetDoubleValue("floats", "value1", 2.71828); + ASSERT_EQ(rc, SI_UPDATED); - result = ini.GetDoubleValue("floats", "value1", 0.0); - ASSERT_NEAR(result, 2.71828, 0.00001); + result = ini.GetDoubleValue("floats", "value1", 0.0); + ASSERT_NEAR(result, 2.71828, 0.00001); } // Test SetDoubleValue with negative and scientific notation TEST_F(TestNumeric, TestSetDoubleValueFormats) { - SI_Error rc = ini.SetDoubleValue("floats", "negative", -123.456); - ASSERT_EQ(rc, SI_INSERTED); + SI_Error rc = ini.SetDoubleValue("floats", "negative", -123.456); + ASSERT_EQ(rc, SI_INSERTED); - double result = ini.GetDoubleValue("floats", "negative", 0.0); - ASSERT_NEAR(result, -123.456, 0.0001); + double result = ini.GetDoubleValue("floats", "negative", 0.0); + ASSERT_NEAR(result, -123.456, 0.0001); - rc = ini.SetDoubleValue("floats", "tiny", 0.000001); - ASSERT_EQ(rc, SI_INSERTED); + rc = ini.SetDoubleValue("floats", "tiny", 0.000001); + ASSERT_EQ(rc, SI_INSERTED); - result = ini.GetDoubleValue("floats", "tiny", 0.0); - ASSERT_NEAR(result, 0.000001, 0.0000001); + result = ini.GetDoubleValue("floats", "tiny", 0.0); + ASSERT_NEAR(result, 0.000001, 0.0000001); } // Test multikey with numeric values TEST_F(TestNumeric, TestMultikeyNumeric) { - ini.SetMultiKey(true); - - std::string input = - "[numbers]\n" - "value = 10\n" - "value = 20\n" - "value = 30\n"; - - SI_Error rc = ini.LoadData(input); - ASSERT_EQ(rc, SI_OK); - - bool hasMultiple = false; - long result = ini.GetLongValue("numbers", "value", 0, &hasMultiple); - ASSERT_EQ(result, 10); - ASSERT_TRUE(hasMultiple); - - // Get all values - CSimpleIniA::TNamesDepend values; - ini.GetAllValues("numbers", "value", values); - ASSERT_EQ(values.size(), 3); + ini.SetMultiKey(true); + + std::string input = "[numbers]\n" + "value = 10\n" + "value = 20\n" + "value = 30\n"; + + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); + + bool hasMultiple = false; + long result = ini.GetLongValue("numbers", "value", 0, &hasMultiple); + ASSERT_EQ(result, 10); + ASSERT_TRUE(hasMultiple); + + // Get all values + CSimpleIniA::TNamesDepend values; + ini.GetAllValues("numbers", "value", values); + ASSERT_EQ(values.size(), 3); } // Test SetLongValue with force replace in multikey mode TEST_F(TestNumeric, TestSetLongValueForceReplace) { - ini.SetMultiKey(true); + ini.SetMultiKey(true); - std::string input = - "[numbers]\n" - "value = 10\n" - "value = 20\n"; + std::string input = "[numbers]\n" + "value = 10\n" + "value = 20\n"; - SI_Error rc = ini.LoadData(input); - ASSERT_EQ(rc, SI_OK); + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); - // Replace all values - rc = ini.SetLongValue("numbers", "value", 999, nullptr, false, true); - ASSERT_EQ(rc, SI_UPDATED); + // Replace all values + rc = ini.SetLongValue("numbers", "value", 999, nullptr, false, true); + ASSERT_EQ(rc, SI_UPDATED); - // Should only have one value now - bool hasMultiple = false; - long result = ini.GetLongValue("numbers", "value", 0, &hasMultiple); - ASSERT_EQ(result, 999); - ASSERT_FALSE(hasMultiple); + // Should only have one value now + bool hasMultiple = false; + long result = ini.GetLongValue("numbers", "value", 0, &hasMultiple); + ASSERT_EQ(result, 999); + ASSERT_FALSE(hasMultiple); } // Test extreme values TEST_F(TestNumeric, TestExtremeValues) { - SI_Error rc = ini.SetLongValue("numbers", "max", LONG_MAX); - ASSERT_EQ(rc, SI_INSERTED); + SI_Error rc = ini.SetLongValue("numbers", "max", LONG_MAX); + ASSERT_EQ(rc, SI_INSERTED); - rc = ini.SetLongValue("numbers", "min", LONG_MIN); - ASSERT_EQ(rc, SI_INSERTED); + rc = ini.SetLongValue("numbers", "min", LONG_MIN); + ASSERT_EQ(rc, SI_INSERTED); - long result = ini.GetLongValue("numbers", "max", 0); - ASSERT_EQ(result, LONG_MAX); + long result = ini.GetLongValue("numbers", "max", 0); + ASSERT_EQ(result, LONG_MAX); - result = ini.GetLongValue("numbers", "min", 0); - ASSERT_EQ(result, LONG_MIN); + result = ini.GetLongValue("numbers", "min", 0); + ASSERT_EQ(result, LONG_MIN); } // Test numeric values with whitespace TEST_F(TestNumeric, TestNumericWhitespace) { - std::string input = - "[numbers]\n" - "padded = 42 \n" - "tabs =\t123\t\n"; + std::string input = "[numbers]\n" + "padded = 42 \n" + "tabs =\t123\t\n"; - SI_Error rc = ini.LoadData(input); - ASSERT_EQ(rc, SI_OK); + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); - long result = ini.GetLongValue("numbers", "padded", 0); - ASSERT_EQ(result, 42); + long result = ini.GetLongValue("numbers", "padded", 0); + ASSERT_EQ(result, 42); - result = ini.GetLongValue("numbers", "tabs", 0); - ASSERT_EQ(result, 123); + result = ini.GetLongValue("numbers", "tabs", 0); + ASSERT_EQ(result, 123); } // Test numeric roundtrip TEST_F(TestNumeric, TestNumericRoundtrip) { - ini.SetLongValue("test", "long1", 12345); - ini.SetLongValue("test", "long2", -67890); - ini.SetDoubleValue("test", "double1", 3.14159); - ini.SetDoubleValue("test", "double2", -2.71828); - - std::string output; - SI_Error rc = ini.Save(output); - ASSERT_EQ(rc, SI_OK); - - // Load it back - CSimpleIniA ini2; - ini2.SetUnicode(); - rc = ini2.LoadData(output); - ASSERT_EQ(rc, SI_OK); - - ASSERT_EQ(ini2.GetLongValue("test", "long1", 0), 12345); - ASSERT_EQ(ini2.GetLongValue("test", "long2", 0), -67890); - ASSERT_NEAR(ini2.GetDoubleValue("test", "double1", 0.0), 3.14159, 0.00001); - ASSERT_NEAR(ini2.GetDoubleValue("test", "double2", 0.0), -2.71828, 0.00001); + ini.SetLongValue("test", "long1", 12345); + ini.SetLongValue("test", "long2", -67890); + ini.SetDoubleValue("test", "double1", 3.14159); + ini.SetDoubleValue("test", "double2", -2.71828); + + std::string output; + SI_Error rc = ini.Save(output); + ASSERT_EQ(rc, SI_OK); + + // Load it back + CSimpleIniA ini2; + ini2.SetUnicode(); + rc = ini2.LoadData(output); + ASSERT_EQ(rc, SI_OK); + + ASSERT_EQ(ini2.GetLongValue("test", "long1", 0), 12345); + ASSERT_EQ(ini2.GetLongValue("test", "long2", 0), -67890); + ASSERT_NEAR(ini2.GetDoubleValue("test", "double1", 0.0), 3.14159, 0.00001); + ASSERT_NEAR(ini2.GetDoubleValue("test", "double2", 0.0), -2.71828, 0.00001); } diff --git a/tests/ts-quotes.cpp b/tests/ts-quotes.cpp index 127e23e..950777e 100644 --- a/tests/ts-quotes.cpp +++ b/tests/ts-quotes.cpp @@ -1,202 +1,185 @@ -#include #include "../SimpleIni.h" #include "gtest/gtest.h" +#include class TestQuotes : public ::testing::Test { protected: - void SetUp() override; + void SetUp() override; protected: - CSimpleIniA ini; - std::string input; - std::string expect; - std::string output; + CSimpleIniA ini; + std::string input; + std::string expect; + std::string output; }; -void TestQuotes::SetUp() { - ini.SetUnicode(); -} +void TestQuotes::SetUp() { ini.SetUnicode(); } TEST_F(TestQuotes, TestEmpty) { - ini.SetQuotes(true); + ini.SetQuotes(true); - input = - "[section]\n" - "key1 = \"\"\n" - "key2 = \n" - ; + input = "[section]\n" + "key1 = \"\"\n" + "key2 = \n"; - // no need to preserve quotes for empty data - expect = - "[section]\n" - "key1 = \n" - "key2 = \n" - ; + // no need to preserve quotes for empty data + expect = "[section]\n" + "key1 = \n" + "key2 = \n"; - const char* result; - SI_Error rc = ini.LoadData(input); - ASSERT_EQ(rc, SI_OK); + const char *result; + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); - result = ini.GetValue("section", "key1"); - ASSERT_STREQ(result, ""); + result = ini.GetValue("section", "key1"); + ASSERT_STREQ(result, ""); - result = ini.GetValue("section", "key2"); - ASSERT_STREQ(result, ""); + result = ini.GetValue("section", "key2"); + ASSERT_STREQ(result, ""); - rc = ini.Save(output); - ASSERT_EQ(rc, SI_OK); + rc = ini.Save(output); + ASSERT_EQ(rc, SI_OK); - output.erase(std::remove(output.begin(), output.end(), '\r'), output.end()); - ASSERT_STREQ(expect.c_str(), output.c_str()); + output.erase(std::remove(output.begin(), output.end(), '\r'), output.end()); + ASSERT_STREQ(expect.c_str(), output.c_str()); } TEST_F(TestQuotes, TestEmptyDisabled) { - ini.SetQuotes(false); + ini.SetQuotes(false); - input = - "[section]\n" - "key1 = \"\"\n" - "key2 = \n" - ; + input = "[section]\n" + "key1 = \"\"\n" + "key2 = \n"; - const char* result; - SI_Error rc = ini.LoadData(input); - ASSERT_EQ(rc, SI_OK); + const char *result; + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); - result = ini.GetValue("section", "key1"); - ASSERT_STREQ(result, "\"\""); + result = ini.GetValue("section", "key1"); + ASSERT_STREQ(result, "\"\""); - result = ini.GetValue("section", "key2"); - ASSERT_STREQ(result, ""); + result = ini.GetValue("section", "key2"); + ASSERT_STREQ(result, ""); - rc = ini.Save(output); - ASSERT_EQ(rc, SI_OK); + rc = ini.Save(output); + ASSERT_EQ(rc, SI_OK); - output.erase(std::remove(output.begin(), output.end(), '\r'), output.end()); - ASSERT_STREQ(input.c_str(), output.c_str()); + output.erase(std::remove(output.begin(), output.end(), '\r'), output.end()); + ASSERT_STREQ(input.c_str(), output.c_str()); } TEST_F(TestQuotes, TestGeneral) { - ini.SetQuotes(true); - - input = - "[section]\n" - "key1 = foo\n" - "key2 = \"foo\"\n" - "key3 = foo \n" - "key4 = \" foo \"\n" - "key5 = \"foo\n" - "key6 = foo\"\n" - "key7 = foo \" foo \n" - "key8 = \" foo \" foo \" \n" - ; - - expect = - "[section]\n" - "key1 = foo\n" - "key2 = foo\n" - "key3 = foo\n" - "key4 = \" foo \"\n" - "key5 = \"foo\n" - "key6 = foo\"\n" - "key7 = foo \" foo\n" - "key8 = \" foo \" foo \"\n" - ; - - const char* result; - SI_Error rc = ini.LoadData(input); - ASSERT_EQ(rc, SI_OK); - - result = ini.GetValue("section", "key1"); - ASSERT_STREQ(result, "foo"); - - result = ini.GetValue("section", "key2"); - ASSERT_STREQ(result, "foo"); - - result = ini.GetValue("section", "key3"); - ASSERT_STREQ(result, "foo"); - - result = ini.GetValue("section", "key4"); - ASSERT_STREQ(result, " foo "); - - result = ini.GetValue("section", "key5"); - ASSERT_STREQ(result, "\"foo"); - - result = ini.GetValue("section", "key6"); - ASSERT_STREQ(result, "foo\""); - - result = ini.GetValue("section", "key7"); - ASSERT_STREQ(result, "foo \" foo"); - - result = ini.GetValue("section", "key8"); - ASSERT_STREQ(result, " foo \" foo "); - - rc = ini.Save(output); - ASSERT_EQ(rc, SI_OK); - - output.erase(std::remove(output.begin(), output.end(), '\r'), output.end()); - ASSERT_STREQ(expect.c_str(), output.c_str()); + ini.SetQuotes(true); + + input = "[section]\n" + "key1 = foo\n" + "key2 = \"foo\"\n" + "key3 = foo \n" + "key4 = \" foo \"\n" + "key5 = \"foo\n" + "key6 = foo\"\n" + "key7 = foo \" foo \n" + "key8 = \" foo \" foo \" \n"; + + expect = "[section]\n" + "key1 = foo\n" + "key2 = foo\n" + "key3 = foo\n" + "key4 = \" foo \"\n" + "key5 = \"foo\n" + "key6 = foo\"\n" + "key7 = foo \" foo\n" + "key8 = \" foo \" foo \"\n"; + + const char *result; + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); + + result = ini.GetValue("section", "key1"); + ASSERT_STREQ(result, "foo"); + + result = ini.GetValue("section", "key2"); + ASSERT_STREQ(result, "foo"); + + result = ini.GetValue("section", "key3"); + ASSERT_STREQ(result, "foo"); + + result = ini.GetValue("section", "key4"); + ASSERT_STREQ(result, " foo "); + + result = ini.GetValue("section", "key5"); + ASSERT_STREQ(result, "\"foo"); + + result = ini.GetValue("section", "key6"); + ASSERT_STREQ(result, "foo\""); + + result = ini.GetValue("section", "key7"); + ASSERT_STREQ(result, "foo \" foo"); + + result = ini.GetValue("section", "key8"); + ASSERT_STREQ(result, " foo \" foo "); + + rc = ini.Save(output); + ASSERT_EQ(rc, SI_OK); + + output.erase(std::remove(output.begin(), output.end(), '\r'), output.end()); + ASSERT_STREQ(expect.c_str(), output.c_str()); } TEST_F(TestQuotes, TestGeneralDisabled) { - ini.SetQuotes(false); - - input = - "[section]\n" - "key1 = foo\n" - "key2 = \"foo\"\n" - "key3 = foo \n" - "key4 = \" foo \"\n" - "key5 = \"foo\n" - "key6 = foo\"\n" - "key7 = foo \" foo \n" - "key8 = \" foo \" foo \" \n" - ; - - expect = - "[section]\n" - "key1 = foo\n" - "key2 = \"foo\"\n" - "key3 = foo\n" - "key4 = \" foo \"\n" - "key5 = \"foo\n" - "key6 = foo\"\n" - "key7 = foo \" foo\n" - "key8 = \" foo \" foo \"\n" - ; - - const char* result; - SI_Error rc = ini.LoadData(input); - ASSERT_EQ(rc, SI_OK); - - rc = ini.Save(output); - ASSERT_EQ(rc, SI_OK); - - result = ini.GetValue("section", "key1"); - ASSERT_STREQ(result, "foo"); - - result = ini.GetValue("section", "key2"); - ASSERT_STREQ(result, "\"foo\""); - - result = ini.GetValue("section", "key3"); - ASSERT_STREQ(result, "foo"); - - result = ini.GetValue("section", "key4"); - ASSERT_STREQ(result, "\" foo \""); - - result = ini.GetValue("section", "key5"); - ASSERT_STREQ(result, "\"foo"); - - result = ini.GetValue("section", "key6"); - ASSERT_STREQ(result, "foo\""); - - result = ini.GetValue("section", "key7"); - ASSERT_STREQ(result, "foo \" foo"); - - result = ini.GetValue("section", "key8"); - ASSERT_STREQ(result, "\" foo \" foo \""); - - output.erase(std::remove(output.begin(), output.end(), '\r'), output.end()); - ASSERT_STREQ(expect.c_str(), output.c_str()); -} + ini.SetQuotes(false); + + input = "[section]\n" + "key1 = foo\n" + "key2 = \"foo\"\n" + "key3 = foo \n" + "key4 = \" foo \"\n" + "key5 = \"foo\n" + "key6 = foo\"\n" + "key7 = foo \" foo \n" + "key8 = \" foo \" foo \" \n"; + + expect = "[section]\n" + "key1 = foo\n" + "key2 = \"foo\"\n" + "key3 = foo\n" + "key4 = \" foo \"\n" + "key5 = \"foo\n" + "key6 = foo\"\n" + "key7 = foo \" foo\n" + "key8 = \" foo \" foo \"\n"; + + const char *result; + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); + + rc = ini.Save(output); + ASSERT_EQ(rc, SI_OK); + + result = ini.GetValue("section", "key1"); + ASSERT_STREQ(result, "foo"); + + result = ini.GetValue("section", "key2"); + ASSERT_STREQ(result, "\"foo\""); + result = ini.GetValue("section", "key3"); + ASSERT_STREQ(result, "foo"); + + result = ini.GetValue("section", "key4"); + ASSERT_STREQ(result, "\" foo \""); + + result = ini.GetValue("section", "key5"); + ASSERT_STREQ(result, "\"foo"); + + result = ini.GetValue("section", "key6"); + ASSERT_STREQ(result, "foo\""); + + result = ini.GetValue("section", "key7"); + ASSERT_STREQ(result, "foo \" foo"); + + result = ini.GetValue("section", "key8"); + ASSERT_STREQ(result, "\" foo \" foo \""); + + output.erase(std::remove(output.begin(), output.end(), '\r'), output.end()); + ASSERT_STREQ(expect.c_str(), output.c_str()); +} diff --git a/tests/ts-roundtrip.cpp b/tests/ts-roundtrip.cpp index b2be9f7..82bd7be 100644 --- a/tests/ts-roundtrip.cpp +++ b/tests/ts-roundtrip.cpp @@ -1,246 +1,226 @@ -#include -#include "../SimpleIni.h" +#include "../SimpleIni.h" #include "gtest/gtest.h" +#include class TestRoundTrip : public ::testing::Test { protected: - void SetUp() override; - void TestMulti(); - void TestBOM(bool useBOM); + void SetUp() override; + void TestMulti(); + void TestBOM(bool useBOM); protected: - CSimpleIniA ini; - std::string input; - std::string output; + CSimpleIniA ini; + std::string input; + std::string output; }; -void TestRoundTrip::SetUp() { - ini.SetUnicode(); -} +void TestRoundTrip::SetUp() { ini.SetUnicode(); } TEST_F(TestRoundTrip, TestStandard) { - input = - "; File comment\n" - "\n" - "\n" - "; Section 1 comment\n" - "[section1]\n" - "\n" - "\n" - "; Section 2 comment\n" - "[section2]\n" - "\n" - "; key1 comment\n" - "key1 = string\n" - "\n" - "; key 2 comment\n" - "key2 = true\n" - "key3 = 3.1415\n" - ; - - SI_Error rc = ini.LoadData(input); - ASSERT_EQ(rc, SI_OK); - - const char* result = ini.GetValue("section2", "key1"); - ASSERT_STREQ(result, "string"); - - rc = ini.Save(output); - ASSERT_EQ(rc, SI_OK); - - output.erase(std::remove(output.begin(), output.end(), '\r'), output.end()); - ASSERT_STREQ(input.c_str(), output.c_str()); + input = "; File comment\n" + "\n" + "\n" + "; Section 1 comment\n" + "[section1]\n" + "\n" + "\n" + "; Section 2 comment\n" + "[section2]\n" + "\n" + "; key1 comment\n" + "key1 = string\n" + "\n" + "; key 2 comment\n" + "key2 = true\n" + "key3 = 3.1415\n"; + + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); + + const char *result = ini.GetValue("section2", "key1"); + ASSERT_STREQ(result, "string"); + + rc = ini.Save(output); + ASSERT_EQ(rc, SI_OK); + + output.erase(std::remove(output.begin(), output.end(), '\r'), output.end()); + ASSERT_STREQ(input.c_str(), output.c_str()); } void TestRoundTrip::TestMulti() { - input = - "[section]\n" - "key = string1\n" - "key = string2\n" - ; + input = "[section]\n" + "key = string1\n" + "key = string2\n"; - SI_Error rc = ini.LoadData(input); - ASSERT_EQ(rc, SI_OK); + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); - rc = ini.Save(output); - ASSERT_EQ(rc, SI_OK); + rc = ini.Save(output); + ASSERT_EQ(rc, SI_OK); - output.erase(std::remove(output.begin(), output.end(), '\r'), output.end()); + output.erase(std::remove(output.begin(), output.end(), '\r'), output.end()); } TEST_F(TestRoundTrip, TestMultiGood) { - ini.SetMultiKey(true); - TestMulti(); - ASSERT_STREQ(input.c_str(), output.c_str()); + ini.SetMultiKey(true); + TestMulti(); + ASSERT_STREQ(input.c_str(), output.c_str()); } TEST_F(TestRoundTrip, TestMultiBad) { - std::string expected = - "[section]\n" - "key = string2\n"; - - ini.SetMultiKey(false); - TestMulti(); - ASSERT_STRNE(input.c_str(), output.c_str()); - ASSERT_STREQ(expected.c_str(), output.c_str()); + std::string expected = "[section]\n" + "key = string2\n"; + + ini.SetMultiKey(false); + TestMulti(); + ASSERT_STRNE(input.c_str(), output.c_str()); + ASSERT_STREQ(expected.c_str(), output.c_str()); } TEST_F(TestRoundTrip, TestSpacesTrue) { - input = - "[section]\n" - "key = string1\n"; + input = "[section]\n" + "key = string1\n"; - SI_Error rc = ini.LoadData(input); - ASSERT_EQ(rc, SI_OK); + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); - ini.SetSpaces(true); - rc = ini.Save(output); - ASSERT_EQ(rc, SI_OK); + ini.SetSpaces(true); + rc = ini.Save(output); + ASSERT_EQ(rc, SI_OK); - output.erase(std::remove(output.begin(), output.end(), '\r'), output.end()); - - ASSERT_STREQ(input.c_str(), output.c_str()); + output.erase(std::remove(output.begin(), output.end(), '\r'), output.end()); + + ASSERT_STREQ(input.c_str(), output.c_str()); } TEST_F(TestRoundTrip, TestSpacesFalse) { - input = - "[section]\n" - "key = string1\n"; + input = "[section]\n" + "key = string1\n"; - SI_Error rc = ini.LoadData(input); - ASSERT_EQ(rc, SI_OK); + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); - ini.SetSpaces(false); - rc = ini.Save(output); - ASSERT_EQ(rc, SI_OK); + ini.SetSpaces(false); + rc = ini.Save(output); + ASSERT_EQ(rc, SI_OK); - output.erase(std::remove(output.begin(), output.end(), '\r'), output.end()); + output.erase(std::remove(output.begin(), output.end(), '\r'), output.end()); - ASSERT_STRNE(input.c_str(), output.c_str()); + ASSERT_STRNE(input.c_str(), output.c_str()); - std::string expected = - "[section]\n" - "key=string1\n"; + std::string expected = "[section]\n" + "key=string1\n"; - ASSERT_STREQ(expected.c_str(), output.c_str()); + ASSERT_STREQ(expected.c_str(), output.c_str()); } void TestRoundTrip::TestBOM(bool useBOM) { - const char bom[] = "\xEF\xBB\xBF"; - const char input8[] = - u8"[テスト1]\n" - u8"テスト2 = テスト3\n"; + const char bom[] = "\xEF\xBB\xBF"; + const char input8[] = u8"[テスト1]\n" + u8"テスト2 = テスト3\n"; - input = bom; - input += input8; + input = bom; + input += input8; - ini.Reset(); - ini.SetUnicode(false); - SI_Error rc = ini.LoadData(input); - ASSERT_EQ(rc, SI_OK); + ini.Reset(); + ini.SetUnicode(false); + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); - const char tesuto1[] = u8"テスト1"; - const char tesuto2[] = u8"テスト2"; - const char tesuto3[] = u8"テスト3"; + const char tesuto1[] = u8"テスト1"; + const char tesuto2[] = u8"テスト2"; + const char tesuto3[] = u8"テスト3"; - const char* result = ini.GetValue(tesuto1, tesuto2); - ASSERT_STREQ(result, tesuto3); + const char *result = ini.GetValue(tesuto1, tesuto2); + ASSERT_STREQ(result, tesuto3); - rc = ini.Save(output, useBOM); - ASSERT_EQ(rc, SI_OK); + rc = ini.Save(output, useBOM); + ASSERT_EQ(rc, SI_OK); - output.erase(std::remove(output.begin(), output.end(), '\r'), output.end()); + output.erase(std::remove(output.begin(), output.end(), '\r'), output.end()); } TEST_F(TestRoundTrip, TestWithBOM) { - TestBOM(true); + TestBOM(true); - ASSERT_STREQ(input.c_str(), output.c_str()); + ASSERT_STREQ(input.c_str(), output.c_str()); } TEST_F(TestRoundTrip, TestWithoutBOM) { - TestBOM(false); + TestBOM(false); - ASSERT_STRNE(input.c_str(), output.c_str()); + ASSERT_STRNE(input.c_str(), output.c_str()); - std::string expected(input, 3); - ASSERT_STREQ(expected.c_str(), output.c_str()); + std::string expected(input, 3); + ASSERT_STREQ(expected.c_str(), output.c_str()); } TEST_F(TestRoundTrip, TestAllowKeyOnly1) { - ini.SetAllowKeyOnly(false); - - input = - "[section1]\n" - "key1 = string\n" - "key2 = \n" - "key3= \n" - "key4=\n" - "key5\n" - "\n" - "Never going to give you up\n" - "Never going to let you down\n" - ; - - std::string expect = - "[section1]\n" - "key1 = string\n" - "key2 = \n" - "key3 = \n" - "key4 = \n" - ; - - SI_Error rc = ini.LoadData(input); - ASSERT_EQ(rc, SI_OK); - - rc = ini.Save(output); - ASSERT_EQ(rc, SI_OK); - - output.erase(std::remove(output.begin(), output.end(), '\r'), output.end()); - ASSERT_STREQ(expect.c_str(), output.c_str()); + ini.SetAllowKeyOnly(false); + + input = "[section1]\n" + "key1 = string\n" + "key2 = \n" + "key3= \n" + "key4=\n" + "key5\n" + "\n" + "Never going to give you up\n" + "Never going to let you down\n"; + + std::string expect = "[section1]\n" + "key1 = string\n" + "key2 = \n" + "key3 = \n" + "key4 = \n"; + + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); + + rc = ini.Save(output); + ASSERT_EQ(rc, SI_OK); + + output.erase(std::remove(output.begin(), output.end(), '\r'), output.end()); + ASSERT_STREQ(expect.c_str(), output.c_str()); } TEST_F(TestRoundTrip, TestAllowKeyOnly2) { - ini.SetAllowKeyOnly(true); - - input = - "[section1]\n" - "key1\n" - "key2\n" - "[section2]\n" - "key1 = string\n" - "key2 = \n" - "key3= \n" - "key4=\n" - "\n" - "key5\n" - "\n" - "Never going to give you up\n" - "\n" - "Never going to let you down\n" - ; - - std::string expect = - "[section1]\n" - "key1\n" - "key2\n" - "\n\n" - "[section2]\n" - "key1 = string\n" - "key2\n" - "key3\n" - "key4\n" - "key5\n" - "Never going to give you up\n" - "Never going to let you down\n" - ; - - SI_Error rc = ini.LoadData(input); - ASSERT_EQ(rc, SI_OK); - - rc = ini.Save(output); - ASSERT_EQ(rc, SI_OK); - - output.erase(std::remove(output.begin(), output.end(), '\r'), output.end()); - ASSERT_STREQ(expect.c_str(), output.c_str()); + ini.SetAllowKeyOnly(true); + + input = "[section1]\n" + "key1\n" + "key2\n" + "[section2]\n" + "key1 = string\n" + "key2 = \n" + "key3= \n" + "key4=\n" + "\n" + "key5\n" + "\n" + "Never going to give you up\n" + "\n" + "Never going to let you down\n"; + + std::string expect = "[section1]\n" + "key1\n" + "key2\n" + "\n\n" + "[section2]\n" + "key1 = string\n" + "key2\n" + "key3\n" + "key4\n" + "key5\n" + "Never going to give you up\n" + "Never going to let you down\n"; + + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); + + rc = ini.Save(output); + ASSERT_EQ(rc, SI_OK); + + output.erase(std::remove(output.begin(), output.end(), '\r'), output.end()); + ASSERT_STREQ(expect.c_str(), output.c_str()); } - diff --git a/tests/ts-sections.cpp b/tests/ts-sections.cpp index 7312a76..9ac181d 100644 --- a/tests/ts-sections.cpp +++ b/tests/ts-sections.cpp @@ -3,339 +3,332 @@ class TestSections : public ::testing::Test { protected: - void SetUp() override; + void SetUp() override; + protected: - CSimpleIniA ini; + CSimpleIniA ini; }; -void TestSections::SetUp() { - ini.SetUnicode(); -} +void TestSections::SetUp() { ini.SetUnicode(); } // Test GetSectionSize TEST_F(TestSections, TestGetSectionSize) { - std::string input = - "[section1]\n" - "key1 = value1\n" - "key2 = value2\n" - "key3 = value3\n" - "\n" - "[section2]\n" - "key1 = value1\n" - "\n" - "[empty]\n"; - - SI_Error rc = ini.LoadData(input); - ASSERT_EQ(rc, SI_OK); - - int size = ini.GetSectionSize("section1"); - ASSERT_EQ(size, 3); - - size = ini.GetSectionSize("section2"); - ASSERT_EQ(size, 1); - - size = ini.GetSectionSize("empty"); - ASSERT_EQ(size, 0); - - // Non-existent section - size = ini.GetSectionSize("missing"); - ASSERT_EQ(size, -1); + std::string input = "[section1]\n" + "key1 = value1\n" + "key2 = value2\n" + "key3 = value3\n" + "\n" + "[section2]\n" + "key1 = value1\n" + "\n" + "[empty]\n"; + + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); + + int size = ini.GetSectionSize("section1"); + ASSERT_EQ(size, 3); + + size = ini.GetSectionSize("section2"); + ASSERT_EQ(size, 1); + + size = ini.GetSectionSize("empty"); + ASSERT_EQ(size, 0); + + // Non-existent section + size = ini.GetSectionSize("missing"); + ASSERT_EQ(size, -1); } // Test GetSectionSize with multikey TEST_F(TestSections, TestGetSectionSizeMultikey) { - ini.SetMultiKey(true); + ini.SetMultiKey(true); - std::string input = - "[section]\n" - "key1 = value1\n" - "key1 = value2\n" - "key2 = value3\n"; + std::string input = "[section]\n" + "key1 = value1\n" + "key1 = value2\n" + "key2 = value3\n"; - SI_Error rc = ini.LoadData(input); - ASSERT_EQ(rc, SI_OK); + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); - // Should count unique keys, not total entries - int size = ini.GetSectionSize("section"); - ASSERT_EQ(size, 2); + // Should count unique keys, not total entries + int size = ini.GetSectionSize("section"); + ASSERT_EQ(size, 2); } // Test GetSection TEST_F(TestSections, TestGetSection) { - std::string input = - "[section1]\n" - "key1 = value1\n" - "key2 = value2\n" - "key3 = value3\n"; - - SI_Error rc = ini.LoadData(input); - ASSERT_EQ(rc, SI_OK); - - const CSimpleIniA::TKeyVal* section = ini.GetSection("section1"); - ASSERT_NE(section, nullptr); - ASSERT_EQ(section->size(), 3); - - // Verify we can iterate the section - int count = 0; - for (auto it = section->begin(); it != section->end(); ++it) { - count++; - ASSERT_NE(it->first.pItem, nullptr); - ASSERT_NE(it->second, nullptr); - } - ASSERT_EQ(count, 3); + std::string input = "[section1]\n" + "key1 = value1\n" + "key2 = value2\n" + "key3 = value3\n"; + + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); + + const CSimpleIniA::TKeyVal *section = ini.GetSection("section1"); + ASSERT_NE(section, nullptr); + ASSERT_EQ(section->size(), 3); + + // Verify we can iterate the section + int count = 0; + for (auto it = section->begin(); it != section->end(); ++it) { + count++; + ASSERT_NE(it->first.pItem, nullptr); + ASSERT_NE(it->second, nullptr); + } + ASSERT_EQ(count, 3); } // Test GetSection returns nullptr for missing section TEST_F(TestSections, TestGetSectionMissing) { - std::string input = "[section1]\nkey=value\n"; + std::string input = "[section1]\nkey=value\n"; - SI_Error rc = ini.LoadData(input); - ASSERT_EQ(rc, SI_OK); + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); - const CSimpleIniA::TKeyVal* section = ini.GetSection("missing"); - ASSERT_EQ(section, nullptr); + const CSimpleIniA::TKeyVal *section = ini.GetSection("missing"); + ASSERT_EQ(section, nullptr); } // Test SectionExists TEST_F(TestSections, TestSectionExists) { - std::string input = - "[section1]\n" - "key = value\n" - "\n" - "[section2]\n" - "\n" - "[empty]\n"; - - SI_Error rc = ini.LoadData(input); - ASSERT_EQ(rc, SI_OK); - - ASSERT_TRUE(ini.SectionExists("section1")); - ASSERT_TRUE(ini.SectionExists("section2")); - ASSERT_TRUE(ini.SectionExists("empty")); - ASSERT_FALSE(ini.SectionExists("missing")); - ASSERT_FALSE(ini.SectionExists("")); + std::string input = "[section1]\n" + "key = value\n" + "\n" + "[section2]\n" + "\n" + "[empty]\n"; + + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); + + ASSERT_TRUE(ini.SectionExists("section1")); + ASSERT_TRUE(ini.SectionExists("section2")); + ASSERT_TRUE(ini.SectionExists("empty")); + ASSERT_FALSE(ini.SectionExists("missing")); + ASSERT_FALSE(ini.SectionExists("")); } // Test KeyExists TEST_F(TestSections, TestKeyExists) { - std::string input = - "[section1]\n" - "key1 = value1\n" - "key2 = value2\n" - "\n" - "[section2]\n" - "key3 = value3\n"; - - SI_Error rc = ini.LoadData(input); - ASSERT_EQ(rc, SI_OK); - - ASSERT_TRUE(ini.KeyExists("section1", "key1")); - ASSERT_TRUE(ini.KeyExists("section1", "key2")); - ASSERT_FALSE(ini.KeyExists("section1", "key3")); - ASSERT_FALSE(ini.KeyExists("section1", "missing")); - - ASSERT_TRUE(ini.KeyExists("section2", "key3")); - ASSERT_FALSE(ini.KeyExists("section2", "key1")); - - ASSERT_FALSE(ini.KeyExists("missing", "key")); + std::string input = "[section1]\n" + "key1 = value1\n" + "key2 = value2\n" + "\n" + "[section2]\n" + "key3 = value3\n"; + + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); + + ASSERT_TRUE(ini.KeyExists("section1", "key1")); + ASSERT_TRUE(ini.KeyExists("section1", "key2")); + ASSERT_FALSE(ini.KeyExists("section1", "key3")); + ASSERT_FALSE(ini.KeyExists("section1", "missing")); + + ASSERT_TRUE(ini.KeyExists("section2", "key3")); + ASSERT_FALSE(ini.KeyExists("section2", "key1")); + + ASSERT_FALSE(ini.KeyExists("missing", "key")); } // Test KeyExists with empty section TEST_F(TestSections, TestKeyExistsEmptySection) { - ini.SetAllowKeyOnly(true); + ini.SetAllowKeyOnly(true); - std::string input = - "key1 = value1\n" - "[section]\n" - "key2 = value2\n"; + std::string input = "key1 = value1\n" + "[section]\n" + "key2 = value2\n"; - SI_Error rc = ini.LoadData(input); - ASSERT_EQ(rc, SI_OK); + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); - ASSERT_TRUE(ini.KeyExists("", "key1")); - ASSERT_FALSE(ini.KeyExists("", "key2")); - ASSERT_TRUE(ini.KeyExists("section", "key2")); + ASSERT_TRUE(ini.KeyExists("", "key1")); + ASSERT_FALSE(ini.KeyExists("", "key2")); + ASSERT_TRUE(ini.KeyExists("section", "key2")); } // Test GetAllSections TEST_F(TestSections, TestGetAllSections) { - std::string input = - "[section1]\n" - "key = value\n" - "\n" - "[section2]\n" - "key = value\n" - "\n" - "[section3]\n"; - - SI_Error rc = ini.LoadData(input); - ASSERT_EQ(rc, SI_OK); - - CSimpleIniA::TNamesDepend sections; - ini.GetAllSections(sections); - - ASSERT_EQ(sections.size(), 3); - - // Verify section names - bool found1 = false, found2 = false, found3 = false; - for (auto it = sections.begin(); it != sections.end(); ++it) { - if (strcmp(it->pItem, "section1") == 0) found1 = true; - if (strcmp(it->pItem, "section2") == 0) found2 = true; - if (strcmp(it->pItem, "section3") == 0) found3 = true; - } - ASSERT_TRUE(found1); - ASSERT_TRUE(found2); - ASSERT_TRUE(found3); + std::string input = "[section1]\n" + "key = value\n" + "\n" + "[section2]\n" + "key = value\n" + "\n" + "[section3]\n"; + + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); + + CSimpleIniA::TNamesDepend sections; + ini.GetAllSections(sections); + + ASSERT_EQ(sections.size(), 3); + + // Verify section names + bool found1 = false, found2 = false, found3 = false; + for (auto it = sections.begin(); it != sections.end(); ++it) { + if (strcmp(it->pItem, "section1") == 0) + found1 = true; + if (strcmp(it->pItem, "section2") == 0) + found2 = true; + if (strcmp(it->pItem, "section3") == 0) + found3 = true; + } + ASSERT_TRUE(found1); + ASSERT_TRUE(found2); + ASSERT_TRUE(found3); } // Test GetAllKeys TEST_F(TestSections, TestGetAllKeys) { - std::string input = - "[section1]\n" - "key1 = value1\n" - "key2 = value2\n" - "key3 = value3\n"; - - SI_Error rc = ini.LoadData(input); - ASSERT_EQ(rc, SI_OK); - - CSimpleIniA::TNamesDepend keys; - bool result = ini.GetAllKeys("section1", keys); - ASSERT_TRUE(result); - ASSERT_EQ(keys.size(), 3); - - // Verify key names - bool found1 = false, found2 = false, found3 = false; - for (auto it = keys.begin(); it != keys.end(); ++it) { - if (strcmp(it->pItem, "key1") == 0) found1 = true; - if (strcmp(it->pItem, "key2") == 0) found2 = true; - if (strcmp(it->pItem, "key3") == 0) found3 = true; - } - ASSERT_TRUE(found1); - ASSERT_TRUE(found2); - ASSERT_TRUE(found3); + std::string input = "[section1]\n" + "key1 = value1\n" + "key2 = value2\n" + "key3 = value3\n"; + + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); + + CSimpleIniA::TNamesDepend keys; + bool result = ini.GetAllKeys("section1", keys); + ASSERT_TRUE(result); + ASSERT_EQ(keys.size(), 3); + + // Verify key names + bool found1 = false, found2 = false, found3 = false; + for (auto it = keys.begin(); it != keys.end(); ++it) { + if (strcmp(it->pItem, "key1") == 0) + found1 = true; + if (strcmp(it->pItem, "key2") == 0) + found2 = true; + if (strcmp(it->pItem, "key3") == 0) + found3 = true; + } + ASSERT_TRUE(found1); + ASSERT_TRUE(found2); + ASSERT_TRUE(found3); } // Test GetAllKeys for missing section TEST_F(TestSections, TestGetAllKeysMissing) { - std::string input = "[section1]\nkey=value\n"; + std::string input = "[section1]\nkey=value\n"; - SI_Error rc = ini.LoadData(input); - ASSERT_EQ(rc, SI_OK); + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); - CSimpleIniA::TNamesDepend keys; - bool result = ini.GetAllKeys("missing", keys); - ASSERT_FALSE(result); - ASSERT_EQ(keys.size(), 0); + CSimpleIniA::TNamesDepend keys; + bool result = ini.GetAllKeys("missing", keys); + ASSERT_FALSE(result); + ASSERT_EQ(keys.size(), 0); } // Test GetAllKeys with multikey TEST_F(TestSections, TestGetAllKeysMultikey) { - ini.SetMultiKey(true); + ini.SetMultiKey(true); - std::string input = - "[section]\n" - "key1 = value1\n" - "key1 = value2\n" - "key2 = value3\n" - "key2 = value4\n"; + std::string input = "[section]\n" + "key1 = value1\n" + "key1 = value2\n" + "key2 = value3\n" + "key2 = value4\n"; - SI_Error rc = ini.LoadData(input); - ASSERT_EQ(rc, SI_OK); + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); - CSimpleIniA::TNamesDepend keys; - bool result = ini.GetAllKeys("section", keys); - ASSERT_TRUE(result); + CSimpleIniA::TNamesDepend keys; + bool result = ini.GetAllKeys("section", keys); + ASSERT_TRUE(result); - // Should return unique keys - ASSERT_EQ(keys.size(), 2); + // Should return unique keys + ASSERT_EQ(keys.size(), 2); } // Test creating empty section TEST_F(TestSections, TestCreateEmptySection) { - SI_Error rc = ini.SetValue("newsection", nullptr, nullptr); - ASSERT_EQ(rc, SI_INSERTED); + SI_Error rc = ini.SetValue("newsection", nullptr, nullptr); + ASSERT_EQ(rc, SI_INSERTED); - ASSERT_TRUE(ini.SectionExists("newsection")); - ASSERT_EQ(ini.GetSectionSize("newsection"), 0); + ASSERT_TRUE(ini.SectionExists("newsection")); + ASSERT_EQ(ini.GetSectionSize("newsection"), 0); - // Adding same section again should update, not insert - rc = ini.SetValue("newsection", nullptr, nullptr); - ASSERT_EQ(rc, SI_UPDATED); + // Adding same section again should update, not insert + rc = ini.SetValue("newsection", nullptr, nullptr); + ASSERT_EQ(rc, SI_UPDATED); } // Test section with only keys (no equals sign) TEST_F(TestSections, TestSectionWithKeyOnly) { - ini.SetAllowKeyOnly(true); + ini.SetAllowKeyOnly(true); - std::string input = - "[section]\n" - "key1\n" - "key2\n" - "key3\n"; + std::string input = "[section]\n" + "key1\n" + "key2\n" + "key3\n"; - SI_Error rc = ini.LoadData(input); - ASSERT_EQ(rc, SI_OK); + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); - int size = ini.GetSectionSize("section"); - ASSERT_EQ(size, 3); + int size = ini.GetSectionSize("section"); + ASSERT_EQ(size, 3); - ASSERT_TRUE(ini.KeyExists("section", "key1")); - ASSERT_TRUE(ini.KeyExists("section", "key2")); - ASSERT_TRUE(ini.KeyExists("section", "key3")); + ASSERT_TRUE(ini.KeyExists("section", "key1")); + ASSERT_TRUE(ini.KeyExists("section", "key2")); + ASSERT_TRUE(ini.KeyExists("section", "key3")); - // Values should be NULL or empty - const char* value = ini.GetValue("section", "key1"); - ASSERT_TRUE(value == nullptr || strlen(value) == 0); + // Values should be NULL or empty + const char *value = ini.GetValue("section", "key1"); + ASSERT_TRUE(value == nullptr || strlen(value) == 0); } // Test GetSection with multikey returns all entries TEST_F(TestSections, TestGetSectionMultikey) { - ini.SetMultiKey(true); + ini.SetMultiKey(true); - std::string input = - "[section]\n" - "key1 = value1\n" - "key1 = value2\n" - "key2 = value3\n"; + std::string input = "[section]\n" + "key1 = value1\n" + "key1 = value2\n" + "key2 = value3\n"; - SI_Error rc = ini.LoadData(input); - ASSERT_EQ(rc, SI_OK); + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); - const CSimpleIniA::TKeyVal* section = ini.GetSection("section"); - ASSERT_NE(section, nullptr); + const CSimpleIniA::TKeyVal *section = ini.GetSection("section"); + ASSERT_NE(section, nullptr); - // Should have 3 total entries (not 2) - ASSERT_EQ(section->size(), 3); + // Should have 3 total entries (not 2) + ASSERT_EQ(section->size(), 3); } // Test empty ini TEST_F(TestSections, TestEmptyIni) { - ASSERT_TRUE(ini.IsEmpty()); + ASSERT_TRUE(ini.IsEmpty()); - CSimpleIniA::TNamesDepend sections; - ini.GetAllSections(sections); - ASSERT_EQ(sections.size(), 0); + CSimpleIniA::TNamesDepend sections; + ini.GetAllSections(sections); + ASSERT_EQ(sections.size(), 0); - ASSERT_FALSE(ini.SectionExists("anything")); - ASSERT_EQ(ini.GetSectionSize("anything"), -1); - ASSERT_EQ(ini.GetSection("anything"), nullptr); + ASSERT_FALSE(ini.SectionExists("anything")); + ASSERT_EQ(ini.GetSectionSize("anything"), -1); + ASSERT_EQ(ini.GetSection("anything"), nullptr); } // Test Reset clears all data TEST_F(TestSections, TestReset) { - std::string input = - "[section1]\n" - "key = value\n"; + std::string input = "[section1]\n" + "key = value\n"; - SI_Error rc = ini.LoadData(input); - ASSERT_EQ(rc, SI_OK); + SI_Error rc = ini.LoadData(input); + ASSERT_EQ(rc, SI_OK); - ASSERT_FALSE(ini.IsEmpty()); - ASSERT_TRUE(ini.SectionExists("section1")); + ASSERT_FALSE(ini.IsEmpty()); + ASSERT_TRUE(ini.SectionExists("section1")); - ini.Reset(); + ini.Reset(); - ASSERT_TRUE(ini.IsEmpty()); - ASSERT_FALSE(ini.SectionExists("section1")); + ASSERT_TRUE(ini.IsEmpty()); + ASSERT_FALSE(ini.SectionExists("section1")); } diff --git a/tests/ts-snippets.cpp b/tests/ts-snippets.cpp index 9255c7e..a3272b3 100644 --- a/tests/ts-snippets.cpp +++ b/tests/ts-snippets.cpp @@ -1,303 +1,291 @@ #include "../SimpleIni.h" #include "gtest/gtest.h" - // ### SIMPLE USAGE TEST(TestSnippets, TestSimple) { - // simple demonstration + // simple demonstration - CSimpleIniA ini; - ini.SetUnicode(); + CSimpleIniA ini; + ini.SetUnicode(); - SI_Error rc = ini.LoadFile("example.ini"); - if (rc < 0) { /* handle error */ }; - ASSERT_EQ(rc, SI_OK); + SI_Error rc = ini.LoadFile("example.ini"); + if (rc < 0) { /* handle error */ + }; + ASSERT_EQ(rc, SI_OK); - const char* pv; - pv = ini.GetValue("section", "key", "default"); - ASSERT_STREQ(pv, "value"); + const char *pv; + pv = ini.GetValue("section", "key", "default"); + ASSERT_STREQ(pv, "value"); - ini.SetValue("section", "key", "newvalue"); + ini.SetValue("section", "key", "newvalue"); - pv = ini.GetValue("section", "key", "default"); - ASSERT_STREQ(pv, "newvalue"); + pv = ini.GetValue("section", "key", "default"); + ASSERT_STREQ(pv, "newvalue"); } - // ### LOADING DATA TEST(TestSnippets, TestLoadFile) { - // load from a data file - CSimpleIniA ini; - SI_Error rc = ini.LoadFile("example.ini"); - if (rc < 0) { /* handle error */ }; - ASSERT_EQ(rc, SI_OK); + // load from a data file + CSimpleIniA ini; + SI_Error rc = ini.LoadFile("example.ini"); + if (rc < 0) { /* handle error */ + }; + ASSERT_EQ(rc, SI_OK); } TEST(TestSnippets, TestLoadString) { - // load from a string - const std::string example = "[section]\nkey = value\n"; - CSimpleIniA ini; - SI_Error rc = ini.LoadData(example); - if (rc < 0) { /* handle error */ }; - ASSERT_EQ(rc, SI_OK); + // load from a string + const std::string example = "[section]\nkey = value\n"; + CSimpleIniA ini; + SI_Error rc = ini.LoadData(example); + if (rc < 0) { /* handle error */ + }; + ASSERT_EQ(rc, SI_OK); } - // ### GETTING SECTIONS AND KEYS TEST(TestSnippets, TestSectionsAndKeys) { - const std::string example = - "[section1]\n" - "key1 = value1\n" - "key2 = value2\n" - "\n" - "[section2]\n" - "[section3]\n"; - - CSimpleIniA ini; - SI_Error rc = ini.LoadData(example); - ASSERT_EQ(rc, SI_OK); - - - - // get all sections - CSimpleIniA::TNamesDepend sections; - ini.GetAllSections(sections); - - // get all keys in a section - CSimpleIniA::TNamesDepend keys; - ini.GetAllKeys("section1", keys); - - - - const char* expectedSections[] = { "section1", "section2", "section3", nullptr }; - const char* expectedKeys[] = { "key1", "key2", nullptr }; - - CSimpleIniA::TNamesDepend::const_iterator it; - int i; - - for (i = 0, it = sections.begin(); it != sections.end(); ++i, ++it) { - ASSERT_NE(expectedSections[i], nullptr); - ASSERT_STREQ(expectedSections[i], it->pItem); - } - ASSERT_EQ(expectedSections[i], nullptr); - - for (i = 0, it = keys.begin(); it != keys.end(); ++i, ++it) { - ASSERT_NE(expectedKeys[i], nullptr); - ASSERT_STREQ(expectedKeys[i], it->pItem); - } - ASSERT_EQ(expectedKeys[i], nullptr); + const std::string example = "[section1]\n" + "key1 = value1\n" + "key2 = value2\n" + "\n" + "[section2]\n" + "[section3]\n"; + + CSimpleIniA ini; + SI_Error rc = ini.LoadData(example); + ASSERT_EQ(rc, SI_OK); + + // get all sections + CSimpleIniA::TNamesDepend sections; + ini.GetAllSections(sections); + + // get all keys in a section + CSimpleIniA::TNamesDepend keys; + ini.GetAllKeys("section1", keys); + + const char *expectedSections[] = {"section1", "section2", "section3", + nullptr}; + const char *expectedKeys[] = {"key1", "key2", nullptr}; + + CSimpleIniA::TNamesDepend::const_iterator it; + int i; + + for (i = 0, it = sections.begin(); it != sections.end(); ++i, ++it) { + ASSERT_NE(expectedSections[i], nullptr); + ASSERT_STREQ(expectedSections[i], it->pItem); + } + ASSERT_EQ(expectedSections[i], nullptr); + + for (i = 0, it = keys.begin(); it != keys.end(); ++i, ++it) { + ASSERT_NE(expectedKeys[i], nullptr); + ASSERT_STREQ(expectedKeys[i], it->pItem); + } + ASSERT_EQ(expectedKeys[i], nullptr); } - // ### GETTING VALUES TEST(TestSnippets, TestGettingValues) { - const std::string example = - "[section1]\n" - "key1 = value1\n" - "key2 = value2.1\n" - "key2 = value2.2\n" - "\n" - "[section2]\n" - "[section3]\n"; - - bool utf8 = true; - bool multiKey = true; - CSimpleIniA ini(utf8, multiKey); - SI_Error rc = ini.LoadData(example); - ASSERT_EQ(rc, SI_OK); - - - // get the value of a key that doesn't exist - const char* pv; - pv = ini.GetValue("section1", "key99"); - ASSERT_EQ(pv, nullptr); - - // get the value of a key that does exist - pv = ini.GetValue("section1", "key1"); - ASSERT_STREQ(pv, "value1"); - - // get the value of a key which may have multiple - // values. If hasMultiple is true, then there are - // multiple values and just one value has been returned - bool hasMulti; - pv = ini.GetValue("section1", "key1", nullptr, &hasMulti); - ASSERT_STREQ(pv, "value1"); - ASSERT_EQ(hasMulti, false); - - pv = ini.GetValue("section1", "key2", nullptr, &hasMulti); - ASSERT_STREQ(pv, "value2.1"); - ASSERT_EQ(hasMulti, true); - - // get all values of a key with multiple values - CSimpleIniA::TNamesDepend values; - ini.GetAllValues("section1", "key2", values); - - // sort the values into a known order, in this case we want - // the original load order - values.sort(CSimpleIniA::Entry::LoadOrder()); - - // output all of the items - CSimpleIniA::TNamesDepend::const_iterator it; - for (it = values.begin(); it != values.end(); ++it) { - //printf("value = '%s'\n", it->pItem); - } - - - int i; - const char* expectedValues[] = { "value2.1", "value2.2", nullptr }; - for (i = 0, it = values.begin(); it != values.end(); ++it, ++i) { - ASSERT_NE(expectedValues[i], nullptr); - ASSERT_STREQ(expectedValues[i], it->pItem); - } - ASSERT_EQ(expectedValues[i], nullptr); + const std::string example = "[section1]\n" + "key1 = value1\n" + "key2 = value2.1\n" + "key2 = value2.2\n" + "\n" + "[section2]\n" + "[section3]\n"; + + bool utf8 = true; + bool multiKey = true; + CSimpleIniA ini(utf8, multiKey); + SI_Error rc = ini.LoadData(example); + ASSERT_EQ(rc, SI_OK); + + // get the value of a key that doesn't exist + const char *pv; + pv = ini.GetValue("section1", "key99"); + ASSERT_EQ(pv, nullptr); + + // get the value of a key that does exist + pv = ini.GetValue("section1", "key1"); + ASSERT_STREQ(pv, "value1"); + + // get the value of a key which may have multiple + // values. If hasMultiple is true, then there are + // multiple values and just one value has been returned + bool hasMulti; + pv = ini.GetValue("section1", "key1", nullptr, &hasMulti); + ASSERT_STREQ(pv, "value1"); + ASSERT_EQ(hasMulti, false); + + pv = ini.GetValue("section1", "key2", nullptr, &hasMulti); + ASSERT_STREQ(pv, "value2.1"); + ASSERT_EQ(hasMulti, true); + + // get all values of a key with multiple values + CSimpleIniA::TNamesDepend values; + ini.GetAllValues("section1", "key2", values); + + // sort the values into a known order, in this case we want + // the original load order + values.sort(CSimpleIniA::Entry::LoadOrder()); + + // output all of the items + CSimpleIniA::TNamesDepend::const_iterator it; + for (it = values.begin(); it != values.end(); ++it) { + //printf("value = '%s'\n", it->pItem); + } + + int i; + const char *expectedValues[] = {"value2.1", "value2.2", nullptr}; + for (i = 0, it = values.begin(); it != values.end(); ++it, ++i) { + ASSERT_NE(expectedValues[i], nullptr); + ASSERT_STREQ(expectedValues[i], it->pItem); + } + ASSERT_EQ(expectedValues[i], nullptr); } // ### VALUE EXISTS -TEST(TestSnippets, TestExists) -{ - const std::string example = - "[section1]\n" - "key1 = value1\n" - "key2 = value2.1\n" - "key2 = value2.2\n" - "\n" - "[section2]\n" - "key1\n" - "key2\n" - "[section3]\n"; - - CSimpleIniA ini; - ini.SetUnicode(); - ini.SetMultiKey(); - ini.SetAllowKeyOnly(); - - SI_Error rc = ini.LoadData(example); - ASSERT_EQ(rc, SI_OK); - - - // check for section doesn't exist - EXPECT_FALSE(ini.SectionExists("")); - EXPECT_FALSE(ini.SectionExists("section4")); - - // check for section does exist - EXPECT_TRUE(ini.SectionExists("section1")); - EXPECT_TRUE(ini.SectionExists("section2")); - EXPECT_TRUE(ini.SectionExists("section3")); - - // check for key doesn't exist - EXPECT_FALSE(ini.KeyExists("", "key")); - EXPECT_FALSE(ini.KeyExists("section1", "key")); - EXPECT_FALSE(ini.KeyExists("section2", "key")); - - // check for key does exist - EXPECT_TRUE(ini.KeyExists("section1", "key1")); - EXPECT_TRUE(ini.KeyExists("section1", "key2")); - EXPECT_TRUE(ini.KeyExists("section2", "key1")); - EXPECT_TRUE(ini.KeyExists("section2", "key2")); +TEST(TestSnippets, TestExists) { + const std::string example = "[section1]\n" + "key1 = value1\n" + "key2 = value2.1\n" + "key2 = value2.2\n" + "\n" + "[section2]\n" + "key1\n" + "key2\n" + "[section3]\n"; + + CSimpleIniA ini; + ini.SetUnicode(); + ini.SetMultiKey(); + ini.SetAllowKeyOnly(); + + SI_Error rc = ini.LoadData(example); + ASSERT_EQ(rc, SI_OK); + + // check for section doesn't exist + EXPECT_FALSE(ini.SectionExists("")); + EXPECT_FALSE(ini.SectionExists("section4")); + + // check for section does exist + EXPECT_TRUE(ini.SectionExists("section1")); + EXPECT_TRUE(ini.SectionExists("section2")); + EXPECT_TRUE(ini.SectionExists("section3")); + + // check for key doesn't exist + EXPECT_FALSE(ini.KeyExists("", "key")); + EXPECT_FALSE(ini.KeyExists("section1", "key")); + EXPECT_FALSE(ini.KeyExists("section2", "key")); + + // check for key does exist + EXPECT_TRUE(ini.KeyExists("section1", "key1")); + EXPECT_TRUE(ini.KeyExists("section1", "key2")); + EXPECT_TRUE(ini.KeyExists("section2", "key1")); + EXPECT_TRUE(ini.KeyExists("section2", "key2")); } // ### MODIFYING DATA TEST(TestSnippets, TestModifyingData) { - bool utf8 = true; - bool multiKey = false; - CSimpleIniA ini(utf8, multiKey); - SI_Error rc; - - - // add a new section - rc = ini.SetValue("section1", nullptr, nullptr); - if (rc < 0) { /* handle error */ }; - ASSERT_EQ(rc, SI_INSERTED); - - // not an error to add one that already exists - rc = ini.SetValue("section1", nullptr, nullptr); - if (rc < 0) { /* handle error */ }; - ASSERT_EQ(rc, SI_UPDATED); - - // get the value of a key that doesn't exist - const char* pv; - pv = ini.GetValue("section2", "key1", "default-value"); - ASSERT_STREQ(pv, "default-value"); - - // adding a key (the section will be added if needed) - rc = ini.SetValue("section2", "key1", "value1"); - if (rc < 0) { /* handle error */ }; - ASSERT_EQ(rc, SI_INSERTED); - - // ensure it is set to expected value - pv = ini.GetValue("section2", "key1", nullptr); - ASSERT_STREQ(pv, "value1"); - - // change the value of a key - rc = ini.SetValue("section2", "key1", "value2"); - if (rc < 0) { /* handle error */ }; - ASSERT_EQ(rc, SI_UPDATED); - - // ensure it is set to expected value - pv = ini.GetValue("section2", "key1", nullptr); - ASSERT_STREQ(pv, "value2"); + bool utf8 = true; + bool multiKey = false; + CSimpleIniA ini(utf8, multiKey); + SI_Error rc; + + // add a new section + rc = ini.SetValue("section1", nullptr, nullptr); + if (rc < 0) { /* handle error */ + }; + ASSERT_EQ(rc, SI_INSERTED); + + // not an error to add one that already exists + rc = ini.SetValue("section1", nullptr, nullptr); + if (rc < 0) { /* handle error */ + }; + ASSERT_EQ(rc, SI_UPDATED); + + // get the value of a key that doesn't exist + const char *pv; + pv = ini.GetValue("section2", "key1", "default-value"); + ASSERT_STREQ(pv, "default-value"); + + // adding a key (the section will be added if needed) + rc = ini.SetValue("section2", "key1", "value1"); + if (rc < 0) { /* handle error */ + }; + ASSERT_EQ(rc, SI_INSERTED); + + // ensure it is set to expected value + pv = ini.GetValue("section2", "key1", nullptr); + ASSERT_STREQ(pv, "value1"); + + // change the value of a key + rc = ini.SetValue("section2", "key1", "value2"); + if (rc < 0) { /* handle error */ + }; + ASSERT_EQ(rc, SI_UPDATED); + + // ensure it is set to expected value + pv = ini.GetValue("section2", "key1", nullptr); + ASSERT_STREQ(pv, "value2"); } - // ### DELETING DATA TEST(TestSnippets, TestDeletingData) { - const std::string example = - "[section1]\n" - "key1 = value1\n" - "key2 = value2\n" - "\n" - "[section2]\n" - "key1 = value1\n" - "key2 = value2\n" - "\n" - "[section3]\n"; - - bool utf8 = true; - CSimpleIniA ini(utf8); - SI_Error rc = ini.LoadData(example); - ASSERT_EQ(rc, SI_OK); - - - // deleting a key from a section. Optionally the entire - // section may be deleted if it is now empty. - bool done, deleteSectionIfEmpty = true; - done = ini.Delete("section1", "key1", deleteSectionIfEmpty); - ASSERT_EQ(done, true); - done = ini.Delete("section1", "key1"); - ASSERT_EQ(done, false); - - // deleting an entire section and all keys in it - done = ini.Delete("section2", nullptr); - ASSERT_EQ(done, true); - done = ini.Delete("section2", nullptr); - ASSERT_EQ(done, false); + const std::string example = "[section1]\n" + "key1 = value1\n" + "key2 = value2\n" + "\n" + "[section2]\n" + "key1 = value1\n" + "key2 = value2\n" + "\n" + "[section3]\n"; + + bool utf8 = true; + CSimpleIniA ini(utf8); + SI_Error rc = ini.LoadData(example); + ASSERT_EQ(rc, SI_OK); + + // deleting a key from a section. Optionally the entire + // section may be deleted if it is now empty. + bool done, deleteSectionIfEmpty = true; + done = ini.Delete("section1", "key1", deleteSectionIfEmpty); + ASSERT_EQ(done, true); + done = ini.Delete("section1", "key1"); + ASSERT_EQ(done, false); + + // deleting an entire section and all keys in it + done = ini.Delete("section2", nullptr); + ASSERT_EQ(done, true); + done = ini.Delete("section2", nullptr); + ASSERT_EQ(done, false); } - // ### SAVING DATA TEST(TestSnippets, TestSavingData) { - bool utf8 = true; - CSimpleIniA ini(utf8); - SI_Error rc; - - - // save the data to a string - std::string data; - rc = ini.Save(data); - if (rc < 0) { /* handle error */ }; - ASSERT_EQ(rc, SI_OK); - - // save the data back to the file - rc = ini.SaveFile("example2.ini"); - if (rc < 0) { /* handle error */ }; - ASSERT_EQ(rc, SI_OK); + bool utf8 = true; + CSimpleIniA ini(utf8); + SI_Error rc; + + // save the data to a string + std::string data; + rc = ini.Save(data); + if (rc < 0) { /* handle error */ + }; + ASSERT_EQ(rc, SI_OK); + + // save the data back to the file + rc = ini.SaveFile("example2.ini"); + if (rc < 0) { /* handle error */ + }; + ASSERT_EQ(rc, SI_OK); } - diff --git a/tests/ts-utf8-conversion.cpp b/tests/ts-utf8-conversion.cpp index 287cea3..f6595cb 100644 --- a/tests/ts-utf8-conversion.cpp +++ b/tests/ts-utf8-conversion.cpp @@ -13,375 +13,358 @@ #include #if defined(__APPLE__) -# define SI_TEST_UTF8_REF_ICONV 1 -# include +#define SI_TEST_UTF8_REF_ICONV 1 +#include #elif defined(__has_include) -# if __has_include() -# define SI_TEST_UTF8_REF_UCHAR 1 -# include -# endif +#if __has_include() +#define SI_TEST_UTF8_REF_UCHAR 1 +#include +#endif #endif namespace { bool g_systemUtf8Ready = false; -std::string TestDataPath(const char * a_name) -{ - return std::string("data/") + a_name; +std::string TestDataPath(const char *a_name) { + return std::string("data/") + a_name; } -[[noreturn]] void FailMissingTestData(const char * a_path) -{ - std::fprintf(stderr, - "error: test data not found at '%s'.\n" - "Run tests via ctest from the build directory, or run the tests binary\n" - "with its working directory set to the folder that contains data/.\n", - a_path); - std::exit(1); +[[noreturn]] void FailMissingTestData(const char *a_path) { + std::fprintf( + stderr, + "error: test data not found at '%s'.\n" + "Run tests via ctest from the build directory, or run the tests binary\n" + "with its working directory set to the folder that contains data/.\n", + a_path); + std::exit(1); } -void RequireTestData(const char * a_name) -{ - const std::string path = TestDataPath(a_name); - std::ifstream in(path); - if (!in.good()) { - FailMissingTestData(path.c_str()); - } +void RequireTestData(const char *a_name) { + const std::string path = TestDataPath(a_name); + std::ifstream in(path); + if (!in.good()) { + FailMissingTestData(path.c_str()); + } } -bool InitSystemUtf8Reference() -{ +bool InitSystemUtf8Reference() { #if defined(SI_TEST_UTF8_REF_ICONV) - return true; + return true; #elif defined(SI_TEST_UTF8_REF_UCHAR) - return setlocale(LC_CTYPE, "C.UTF-8") - || setlocale(LC_CTYPE, "en_US.UTF-8") - || setlocale(LC_CTYPE, ""); + return setlocale(LC_CTYPE, "C.UTF-8") || setlocale(LC_CTYPE, "en_US.UTF-8") || + setlocale(LC_CTYPE, ""); #else - return false; + return false; #endif } -bool IsAssignedScalar(char32_t cp) -{ - return cp <= 0x10FFFF && !(cp >= 0xD800 && cp <= 0xDFFF); +bool IsAssignedScalar(char32_t cp) { + return cp <= 0x10FFFF && !(cp >= 0xD800 && cp <= 0xDFFF); } #if defined(SI_TEST_UTF8_REF_ICONV) -bool SystemEncode(char32_t cp, char * buf, size_t cap, size_t & outLen) -{ - iconv_t cd = iconv_open("UTF-8", "UTF-32LE"); - if (cd == (iconv_t) -1) { - return false; - } - - unsigned char in[4] = { - (unsigned char) (cp & 0xFF), - (unsigned char) ((cp >> 8) & 0xFF), - (unsigned char) ((cp >> 16) & 0xFF), - (unsigned char) ((cp >> 24) & 0xFF), - }; - char * inbuf = (char *) in; - size_t inleft = sizeof(in); - char * outbuf = buf; - size_t outleft = cap; - - if (iconv(cd, &inbuf, &inleft, &outbuf, &outleft) == (size_t) -1) { - iconv_close(cd); - return false; - } - +bool SystemEncode(char32_t cp, char *buf, size_t cap, size_t &outLen) { + iconv_t cd = iconv_open("UTF-8", "UTF-32LE"); + if (cd == (iconv_t)-1) { + return false; + } + + unsigned char in[4] = { + (unsigned char)(cp & 0xFF), + (unsigned char)((cp >> 8) & 0xFF), + (unsigned char)((cp >> 16) & 0xFF), + (unsigned char)((cp >> 24) & 0xFF), + }; + char *inbuf = (char *)in; + size_t inleft = sizeof(in); + char *outbuf = buf; + size_t outleft = cap; + + if (iconv(cd, &inbuf, &inleft, &outbuf, &outleft) == (size_t)-1) { iconv_close(cd); - outLen = cap - outleft; - return true; + return false; + } + + iconv_close(cd); + outLen = cap - outleft; + return true; } -bool SystemDecode(const char * buf, size_t len, char32_t & cp, size_t & consumed) -{ - iconv_t cd = iconv_open("UTF-32LE", "UTF-8"); - if (cd == (iconv_t) -1) { - return false; - } +bool SystemDecode(const char *buf, size_t len, char32_t &cp, size_t &consumed) { + iconv_t cd = iconv_open("UTF-32LE", "UTF-8"); + if (cd == (iconv_t)-1) { + return false; + } - unsigned char out[4] = {}; - char * inbuf = (char *) buf; - size_t inleft = len; - char * outbuf = (char *) out; - size_t outleft = sizeof(out); - - if (iconv(cd, &inbuf, &inleft, &outbuf, &outleft) == (size_t) -1) { - iconv_close(cd); - return false; - } + unsigned char out[4] = {}; + char *inbuf = (char *)buf; + size_t inleft = len; + char *outbuf = (char *)out; + size_t outleft = sizeof(out); + if (iconv(cd, &inbuf, &inleft, &outbuf, &outleft) == (size_t)-1) { iconv_close(cd); - consumed = len - inleft; - cp = (char32_t) out[0] - | ((char32_t) out[1] << 8) - | ((char32_t) out[2] << 16) - | ((char32_t) out[3] << 24); - return true; + return false; + } + + iconv_close(cd); + consumed = len - inleft; + cp = (char32_t)out[0] | ((char32_t)out[1] << 8) | ((char32_t)out[2] << 16) | + ((char32_t)out[3] << 24); + return true; } #elif defined(SI_TEST_UTF8_REF_UCHAR) -bool SystemEncode(char32_t cp, char * buf, size_t cap, size_t & outLen) -{ - mbstate_t st{}; - memset(&st, 0, sizeof(st)); - const size_t n = c32rtomb(buf, cp, &st); - if (n == (size_t) -1) { - return false; - } - outLen = n; - (void) cap; - return true; +bool SystemEncode(char32_t cp, char *buf, size_t cap, size_t &outLen) { + mbstate_t st{}; + memset(&st, 0, sizeof(st)); + const size_t n = c32rtomb(buf, cp, &st); + if (n == (size_t)-1) { + return false; + } + outLen = n; + (void)cap; + return true; } -bool SystemDecode(const char * buf, size_t len, char32_t & cp, size_t & consumed) -{ - mbstate_t st{}; - memset(&st, 0, sizeof(st)); - const size_t n = mbrtoc32(&cp, buf, len, &st); - if (n == (size_t) -1 || n == (size_t) -2 || n == (size_t) -3) { - return false; - } - if (n == 0) { - if (len == 0 || buf[0] != '\0') { - return false; - } - cp = 0; - consumed = 1; - return true; +bool SystemDecode(const char *buf, size_t len, char32_t &cp, size_t &consumed) { + mbstate_t st{}; + memset(&st, 0, sizeof(st)); + const size_t n = mbrtoc32(&cp, buf, len, &st); + if (n == (size_t)-1 || n == (size_t)-2 || n == (size_t)-3) { + return false; + } + if (n == 0) { + if (len == 0 || buf[0] != '\0') { + return false; } - consumed = n; + cp = 0; + consumed = 1; return true; + } + consumed = n; + return true; } #endif -std::vector LoadHexLines(const char * a_path) -{ - std::ifstream in(a_path); - if (!in.is_open()) { - FailMissingTestData(a_path); +std::vector LoadHexLines(const char *a_path) { + std::ifstream in(a_path); + if (!in.is_open()) { + FailMissingTestData(a_path); + } + std::vector payloads; + std::string line; + while (std::getline(in, line)) { + if (line.empty() || line[0] == '#') { + continue; } - std::vector payloads; - std::string line; - while (std::getline(in, line)) { - if (line.empty() || line[0] == '#') { - continue; - } - if (line.size() % 2 != 0) { - continue; - } - std::string bytes; - bytes.reserve(line.size() / 2); - bool ok = true; - for (size_t i = 0; i < line.size(); i += 2) { - if (!std::isxdigit((unsigned char) line[i]) - || !std::isxdigit((unsigned char) line[i + 1])) { - ok = false; - break; - } - char hex[3] = { line[i], line[i + 1], '\0' }; - bytes.push_back((char) std::strtoul(hex, nullptr, 16)); - } - if (ok) { - payloads.push_back(bytes); - } + if (line.size() % 2 != 0) { + continue; } - return payloads; + std::string bytes; + bytes.reserve(line.size() / 2); + bool ok = true; + for (size_t i = 0; i < line.size(); i += 2) { + if (!std::isxdigit((unsigned char)line[i]) || + !std::isxdigit((unsigned char)line[i + 1])) { + ok = false; + break; + } + char hex[3] = {line[i], line[i + 1], '\0'}; + bytes.push_back((char)std::strtoul(hex, nullptr, 16)); + } + if (ok) { + payloads.push_back(bytes); + } + } + return payloads; } -bool DecodeUtf8Sequence(const std::string & a_utf8, std::vector & a_cps) -{ - const char * src = a_utf8.data(); - const char * end = a_utf8.data() + a_utf8.size(); - a_cps.clear(); - while (src < end) { - char32_t cp = 0; - if (!SI_UTF8::Decode(src, end, cp)) { - return false; - } - a_cps.push_back(cp); +bool DecodeUtf8Sequence(const std::string &a_utf8, + std::vector &a_cps) { + const char *src = a_utf8.data(); + const char *end = a_utf8.data() + a_utf8.size(); + a_cps.clear(); + while (src < end) { + char32_t cp = 0; + if (!SI_UTF8::Decode(src, end, cp)) { + return false; } - return true; + a_cps.push_back(cp); + } + return true; } -bool ConvertFromStoreUtf8(const std::string & a_utf8) -{ - SI_ConvertW conv(true); - const size_t uLen = conv.SizeFromStore(a_utf8.data(), a_utf8.size()); - if (uLen == (size_t) -1) { - return false; - } - std::vector out(uLen + 1, 0); - return conv.ConvertFromStore( - a_utf8.data(), a_utf8.size(), out.data(), uLen); +bool ConvertFromStoreUtf8(const std::string &a_utf8) { + SI_ConvertW conv(true); + const size_t uLen = conv.SizeFromStore(a_utf8.data(), a_utf8.size()); + if (uLen == (size_t)-1) { + return false; + } + std::vector out(uLen + 1, 0); + return conv.ConvertFromStore(a_utf8.data(), a_utf8.size(), out.data(), uLen); } -const std::vector & RejectCorpus() -{ - static const std::vector corpus = - LoadHexLines(TestDataPath("utf8-reject.hex").c_str()); - return corpus; +const std::vector &RejectCorpus() { + static const std::vector corpus = + LoadHexLines(TestDataPath("utf8-reject.hex").c_str()); + return corpus; } class SystemUtf8Environment : public ::testing::Environment { public: - void SetUp() override { - g_systemUtf8Ready = InitSystemUtf8Reference(); - } + void SetUp() override { g_systemUtf8Ready = InitSystemUtf8Reference(); } }; -const ::testing::Environment * const kSystemUtf8Environment = +const ::testing::Environment *const kSystemUtf8Environment = ::testing::AddGlobalTestEnvironment(new SystemUtf8Environment); -class Utf8RejectTest - : public ::testing::TestWithParam {}; +class Utf8RejectTest : public ::testing::TestWithParam {}; TEST_P(Utf8RejectTest, Decode_Rejects) { - std::vector cps; - EXPECT_FALSE(DecodeUtf8Sequence(GetParam(), cps)); + std::vector cps; + EXPECT_FALSE(DecodeUtf8Sequence(GetParam(), cps)); } TEST_P(Utf8RejectTest, ConvertFromStore_Rejects) { - EXPECT_FALSE(ConvertFromStoreUtf8(GetParam())); + EXPECT_FALSE(ConvertFromStoreUtf8(GetParam())); } TEST_P(Utf8RejectTest, LoadData_Rejects) { - const std::string data = std::string("[s]\nkey = value\nbad = ") + GetParam() + "\n"; - CSimpleIniW ini; - ini.SetUnicode(); - EXPECT_NE(ini.LoadData(data.c_str(), data.size()), SI_OK); + const std::string data = + std::string("[s]\nkey = value\nbad = ") + GetParam() + "\n"; + CSimpleIniW ini; + ini.SetUnicode(); + EXPECT_NE(ini.LoadData(data.c_str(), data.size()), SI_OK); } -INSTANTIATE_TEST_SUITE_P( - ExternalRejectCorpus, - Utf8RejectTest, - ::testing::ValuesIn(RejectCorpus())); +INSTANTIATE_TEST_SUITE_P(ExternalRejectCorpus, Utf8RejectTest, + ::testing::ValuesIn(RejectCorpus())); } // namespace -TEST(Utf8Conversion, EncodeAndDecodeMatchSystemLibraryForAllAssignedScalars) -{ +TEST(Utf8Conversion, EncodeAndDecodeMatchSystemLibraryForAllAssignedScalars) { #if !defined(SI_TEST_UTF8_REF_ICONV) && !defined(SI_TEST_UTF8_REF_UCHAR) - GTEST_SKIP() << "No system UTF-8 reference implementation available"; + GTEST_SKIP() << "No system UTF-8 reference implementation available"; #endif - if (!g_systemUtf8Ready) { - GTEST_SKIP() << "System UTF-8 reference unavailable"; - } + if (!g_systemUtf8Ready) { + GTEST_SKIP() << "System UTF-8 reference unavailable"; + } - for (char32_t cp = 0; cp <= 0x10FFFF; ++cp) { - if (!IsAssignedScalar(cp)) { - continue; - } - - char sysBuf[8] = {}; - char ourBuf[8] = {}; - size_t sysLen = 0; - ASSERT_TRUE(SystemEncode(cp, sysBuf, sizeof(sysBuf), sysLen)) << std::hex << cp; - - const int ourLen = SI_UTF8::Encode(cp, ourBuf, sizeof(ourBuf)); - ASSERT_GE(ourLen, 0) << std::hex << cp; - ASSERT_EQ((size_t) ourLen, sysLen) << std::hex << cp; - ASSERT_EQ(memcmp(sysBuf, ourBuf, sysLen), 0) << std::hex << cp; - - char32_t sysCp = 0; - char32_t ourCp = 0; - size_t sysConsumed = 0; - ASSERT_TRUE(SystemDecode(sysBuf, sysLen, sysCp, sysConsumed)) << std::hex << cp; - ASSERT_EQ(sysConsumed, (size_t) sysLen) << std::hex << cp; - - const char * ourSrc = ourBuf; - ASSERT_TRUE(SI_UTF8::Decode(ourSrc, ourBuf + ourLen, ourCp)) << std::hex << cp; - ASSERT_EQ((size_t) (ourSrc - ourBuf), (size_t) ourLen) << std::hex << cp; - - ASSERT_EQ(sysCp, cp) << std::hex << cp; - ASSERT_EQ(ourCp, cp) << std::hex << cp; - - const char * roundSrc = sysBuf; - char32_t roundCp = 0; - ASSERT_TRUE(SI_UTF8::Decode(roundSrc, sysBuf + sysLen, roundCp)) << std::hex << cp; - ASSERT_EQ(roundCp, cp) << std::hex << cp; + for (char32_t cp = 0; cp <= 0x10FFFF; ++cp) { + if (!IsAssignedScalar(cp)) { + continue; } -} -TEST(Utf8Conversion, OutOfRangeScalar_EncodesReplacement) -{ - char buf[4] = {}; - const int n = SI_UTF8::Encode(0x110000, buf, sizeof(buf)); - ASSERT_EQ(n, 3); - ASSERT_EQ((unsigned char) buf[0], 0xEF); - ASSERT_EQ((unsigned char) buf[1], 0xBF); - ASSERT_EQ((unsigned char) buf[2], 0xBD); - - const char * src = buf; - char32_t cp = 0; - ASSERT_TRUE(SI_UTF8::Decode(src, buf + n, cp)); - ASSERT_EQ(cp, SI_UTF8::REPLACEMENT); + char sysBuf[8] = {}; + char ourBuf[8] = {}; + size_t sysLen = 0; + ASSERT_TRUE(SystemEncode(cp, sysBuf, sizeof(sysBuf), sysLen)) + << std::hex << cp; + + const int ourLen = SI_UTF8::Encode(cp, ourBuf, sizeof(ourBuf)); + ASSERT_GE(ourLen, 0) << std::hex << cp; + ASSERT_EQ((size_t)ourLen, sysLen) << std::hex << cp; + ASSERT_EQ(memcmp(sysBuf, ourBuf, sysLen), 0) << std::hex << cp; + + char32_t sysCp = 0; + char32_t ourCp = 0; + size_t sysConsumed = 0; + ASSERT_TRUE(SystemDecode(sysBuf, sysLen, sysCp, sysConsumed)) + << std::hex << cp; + ASSERT_EQ(sysConsumed, (size_t)sysLen) << std::hex << cp; + + const char *ourSrc = ourBuf; + ASSERT_TRUE(SI_UTF8::Decode(ourSrc, ourBuf + ourLen, ourCp)) + << std::hex << cp; + ASSERT_EQ((size_t)(ourSrc - ourBuf), (size_t)ourLen) << std::hex << cp; + + ASSERT_EQ(sysCp, cp) << std::hex << cp; + ASSERT_EQ(ourCp, cp) << std::hex << cp; + + const char *roundSrc = sysBuf; + char32_t roundCp = 0; + ASSERT_TRUE(SI_UTF8::Decode(roundSrc, sysBuf + sysLen, roundCp)) + << std::hex << cp; + ASSERT_EQ(roundCp, cp) << std::hex << cp; + } } -TEST(Utf8Conversion, SurrogateScalar_EncodesReplacement) -{ - char buf[4] = {}; - const int n = SI_UTF8::Encode(0xD800, buf, sizeof(buf)); - ASSERT_EQ(n, 3); - ASSERT_EQ((unsigned char) buf[0], 0xEF); - ASSERT_EQ((unsigned char) buf[1], 0xBF); - ASSERT_EQ((unsigned char) buf[2], 0xBD); +TEST(Utf8Conversion, OutOfRangeScalar_EncodesReplacement) { + char buf[4] = {}; + const int n = SI_UTF8::Encode(0x110000, buf, sizeof(buf)); + ASSERT_EQ(n, 3); + ASSERT_EQ((unsigned char)buf[0], 0xEF); + ASSERT_EQ((unsigned char)buf[1], 0xBF); + ASSERT_EQ((unsigned char)buf[2], 0xBD); + + const char *src = buf; + char32_t cp = 0; + ASSERT_TRUE(SI_UTF8::Decode(src, buf + n, cp)); + ASSERT_EQ(cp, SI_UTF8::REPLACEMENT); +} - const char * src = buf; - char32_t cp = 0; - ASSERT_TRUE(SI_UTF8::Decode(src, buf + n, cp)); - ASSERT_EQ(cp, SI_UTF8::REPLACEMENT); +TEST(Utf8Conversion, SurrogateScalar_EncodesReplacement) { + char buf[4] = {}; + const int n = SI_UTF8::Encode(0xD800, buf, sizeof(buf)); + ASSERT_EQ(n, 3); + ASSERT_EQ((unsigned char)buf[0], 0xEF); + ASSERT_EQ((unsigned char)buf[1], 0xBF); + ASSERT_EQ((unsigned char)buf[2], 0xBD); + + const char *src = buf; + char32_t cp = 0; + ASSERT_TRUE(SI_UTF8::Decode(src, buf + n, cp)); + ASSERT_EQ(cp, SI_UTF8::REPLACEMENT); } class Utf8IniRoundtripTest : public ::testing::Test {}; TEST_F(Utf8IniRoundtripTest, LoadSave_PreservesUnicodeValues) { - RequireTestData("utf8-ini-roundtrip.ini"); + RequireTestData("utf8-ini-roundtrip.ini"); - // Use \x escapes so expected values are correct on MSVC without /utf-8. - constexpr wchar_t kGreek[] = L"\x03BA\x1F79\x03C3\x03BC\x03B5"; - constexpr wchar_t kJapanese[] = L"\x30C6\x30B9\x30C8\x4E8C"; - constexpr wchar_t kTest2[] = L"\x30C6\x30B9\x30C8\x0032"; - constexpr wchar_t kInspectionSection[] = L"\x691C\x67FB"; - constexpr wchar_t kMixed[] = L"Hello \x4E16\x754C"; + // Use \x escapes so expected values are correct on MSVC without /utf-8. + constexpr wchar_t kGreek[] = L"\x03BA\x1F79\x03C3\x03BC\x03B5"; + constexpr wchar_t kJapanese[] = L"\x30C6\x30B9\x30C8\x4E8C"; + constexpr wchar_t kTest2[] = L"\x30C6\x30B9\x30C8\x0032"; + constexpr wchar_t kInspectionSection[] = L"\x691C\x67FB"; + constexpr wchar_t kMixed[] = L"Hello \x4E16\x754C"; - CSimpleIniW ini; - ini.SetUnicode(); + CSimpleIniW ini; + ini.SetUnicode(); - ASSERT_EQ(ini.LoadFile(TestDataPath("utf8-ini-roundtrip.ini").c_str()), SI_OK); + ASSERT_EQ(ini.LoadFile(TestDataPath("utf8-ini-roundtrip.ini").c_str()), + SI_OK); - const wchar_t * greek = ini.GetValue(L"unicode", L"greek"); - ASSERT_NE(greek, nullptr); - EXPECT_STREQ(greek, kGreek); + const wchar_t *greek = ini.GetValue(L"unicode", L"greek"); + ASSERT_NE(greek, nullptr); + EXPECT_STREQ(greek, kGreek); - const wchar_t * japanese = ini.GetValue(L"unicode", L"japanese"); - ASSERT_NE(japanese, nullptr); - EXPECT_STREQ(japanese, kJapanese); + const wchar_t *japanese = ini.GetValue(L"unicode", L"japanese"); + ASSERT_NE(japanese, nullptr); + EXPECT_STREQ(japanese, kJapanese); - const wchar_t * mixed = ini.GetValue(L"unicode", L"mixed"); - ASSERT_NE(mixed, nullptr); - EXPECT_STREQ(mixed, kMixed); + const wchar_t *mixed = ini.GetValue(L"unicode", L"mixed"); + ASSERT_NE(mixed, nullptr); + EXPECT_STREQ(mixed, kMixed); - const wchar_t * fromSection = ini.GetValue(kInspectionSection, L"test2"); - ASSERT_NE(fromSection, nullptr); - EXPECT_STREQ(fromSection, kTest2); + const wchar_t *fromSection = ini.GetValue(kInspectionSection, L"test2"); + ASSERT_NE(fromSection, nullptr); + EXPECT_STREQ(fromSection, kTest2); - std::string saved; - ASSERT_EQ(ini.Save(saved), SI_OK); + std::string saved; + ASSERT_EQ(ini.Save(saved), SI_OK); - CSimpleIniW reloaded; - reloaded.SetUnicode(); - ASSERT_EQ(reloaded.LoadData(saved), SI_OK); + CSimpleIniW reloaded; + reloaded.SetUnicode(); + ASSERT_EQ(reloaded.LoadData(saved), SI_OK); - EXPECT_STREQ(reloaded.GetValue(L"unicode", L"greek"), kGreek); - EXPECT_STREQ(reloaded.GetValue(L"unicode", L"japanese"), kJapanese); - EXPECT_STREQ(reloaded.GetValue(L"unicode", L"mixed"), kMixed); - EXPECT_STREQ(reloaded.GetValue(kInspectionSection, L"test2"), kTest2); + EXPECT_STREQ(reloaded.GetValue(L"unicode", L"greek"), kGreek); + EXPECT_STREQ(reloaded.GetValue(L"unicode", L"japanese"), kJapanese); + EXPECT_STREQ(reloaded.GetValue(L"unicode", L"mixed"), kMixed); + EXPECT_STREQ(reloaded.GetValue(kInspectionSection, L"test2"), kTest2); } diff --git a/tests/ts-utf8.cpp b/tests/ts-utf8.cpp index 5507e5f..eb9b171 100644 --- a/tests/ts-utf8.cpp +++ b/tests/ts-utf8.cpp @@ -3,65 +3,66 @@ class TestUTF8 : public ::testing::Test { protected: - void SetUp() override; + void SetUp() override; + protected: - CSimpleIniA ini; + CSimpleIniA ini; }; void TestUTF8::SetUp() { - ini.SetUnicode(); - SI_Error err = ini.LoadFile("tests.ini"); - ASSERT_EQ(err, SI_OK); + ini.SetUnicode(); + SI_Error err = ini.LoadFile("tests.ini"); + ASSERT_EQ(err, SI_OK); } TEST_F(TestUTF8, TestSectionAKeyAValA) { - const char* result = ini.GetValue("section1", "key1"); - ASSERT_STREQ(result, "value1"); + const char *result = ini.GetValue("section1", "key1"); + ASSERT_STREQ(result, "value1"); } TEST_F(TestUTF8, TestSectionAKeyAValU) { - const char tesuto2[] = u8"テスト2"; - const char* result = ini.GetValue("section2", "test2"); - ASSERT_STREQ(result, tesuto2); + const char tesuto2[] = u8"テスト2"; + const char *result = ini.GetValue("section2", "test2"); + ASSERT_STREQ(result, tesuto2); } TEST_F(TestUTF8, TestSectionAKeyUValA) { - const char tesuto[] = u8"テスト"; - const char* result = ini.GetValue("section2", tesuto); - ASSERT_STREQ(result, "test"); + const char tesuto[] = u8"テスト"; + const char *result = ini.GetValue("section2", tesuto); + ASSERT_STREQ(result, "test"); } TEST_F(TestUTF8, TestSectionAKeyUValU) { - const char tesuto2[] = u8"テスト2"; - const char tesutoni[] = u8"テスト二"; - const char* result = ini.GetValue("section2", tesuto2); - ASSERT_STREQ(result, tesutoni); + const char tesuto2[] = u8"テスト2"; + const char tesutoni[] = u8"テスト二"; + const char *result = ini.GetValue("section2", tesuto2); + ASSERT_STREQ(result, tesutoni); } TEST_F(TestUTF8, TestSectionUKeyAValA) { - const char kensa[] = u8"検査"; - const char* result = ini.GetValue(kensa, "key2"); - ASSERT_STREQ(result, "value2"); + const char kensa[] = u8"検査"; + const char *result = ini.GetValue(kensa, "key2"); + ASSERT_STREQ(result, "value2"); } TEST_F(TestUTF8, TestSectionUKeyAValU) { - const char kensa[] = u8"検査"; - const char tesuto2[] = u8"テスト2"; - const char* result = ini.GetValue(kensa, "test2"); - ASSERT_STREQ(result, tesuto2); + const char kensa[] = u8"検査"; + const char tesuto2[] = u8"テスト2"; + const char *result = ini.GetValue(kensa, "test2"); + ASSERT_STREQ(result, tesuto2); } TEST_F(TestUTF8, TestSectionUKeyUValA) { - const char kensa[] = u8"検査"; - const char tesuto[] = u8"テスト"; - const char* result = ini.GetValue(kensa, tesuto); - ASSERT_STREQ(result, "test"); + const char kensa[] = u8"検査"; + const char tesuto[] = u8"テスト"; + const char *result = ini.GetValue(kensa, tesuto); + ASSERT_STREQ(result, "test"); } TEST_F(TestUTF8, TestSectionUKeyUValU) { - const char kensa[] = u8"検査"; - const char tesuto2[] = u8"テスト2"; - const char tesutoni[] = u8"テスト二"; - const char* result = ini.GetValue(kensa, tesuto2); - ASSERT_STREQ(result, tesutoni); + const char kensa[] = u8"検査"; + const char tesuto2[] = u8"テスト2"; + const char tesutoni[] = u8"テスト二"; + const char *result = ini.GetValue(kensa, tesuto2); + ASSERT_STREQ(result, tesutoni); } diff --git a/tests/ts-wchar.cpp b/tests/ts-wchar.cpp index 640cdf3..b4b69eb 100644 --- a/tests/ts-wchar.cpp +++ b/tests/ts-wchar.cpp @@ -3,65 +3,66 @@ class TestWide : public ::testing::Test { protected: - void TestWide::SetUp() override; + void TestWide::SetUp() override; + protected: - CSimpleIniW ini; + CSimpleIniW ini; }; void TestWide::SetUp() { - ini.SetUnicode(); - SI_Error err = ini.LoadFile(L"tests.ini"); - ASSERT_EQ(err, SI_OK); + ini.SetUnicode(); + SI_Error err = ini.LoadFile(L"tests.ini"); + ASSERT_EQ(err, SI_OK); } TEST_F(TestWide, TestSectionAKeyAValA) { - const wchar_t* result = ini.GetValue(L"section1", L"key1"); - ASSERT_STREQ(result, L"value1"); + const wchar_t *result = ini.GetValue(L"section1", L"key1"); + ASSERT_STREQ(result, L"value1"); } TEST_F(TestWide, TestSectionAKeyAValU) { - const wchar_t tesuto2[] = L"テスト2"; - const wchar_t* result = ini.GetValue(L"section2", L"test2"); - ASSERT_STREQ(result, tesuto2); + const wchar_t tesuto2[] = L"テスト2"; + const wchar_t *result = ini.GetValue(L"section2", L"test2"); + ASSERT_STREQ(result, tesuto2); } TEST_F(TestWide, TestSectionAKeyUValA) { - const wchar_t tesuto[] = L"テスト"; - const wchar_t* result = ini.GetValue(L"section2", tesuto); - ASSERT_STREQ(result, L"test"); + const wchar_t tesuto[] = L"テスト"; + const wchar_t *result = ini.GetValue(L"section2", tesuto); + ASSERT_STREQ(result, L"test"); } TEST_F(TestWide, TestSectionAKeyUValU) { - const wchar_t tesuto2[] = L"テスト2"; - const wchar_t tesutoni[] = L"テスト二"; - const wchar_t* result = ini.GetValue(L"section2", tesuto2); - ASSERT_STREQ(result, tesutoni); + const wchar_t tesuto2[] = L"テスト2"; + const wchar_t tesutoni[] = L"テスト二"; + const wchar_t *result = ini.GetValue(L"section2", tesuto2); + ASSERT_STREQ(result, tesutoni); } TEST_F(TestWide, TestSectionUKeyAValA) { - const wchar_t kensa[] = L"検査"; - const wchar_t* result = ini.GetValue(kensa, L"key2"); - ASSERT_STREQ(result, L"value2"); + const wchar_t kensa[] = L"検査"; + const wchar_t *result = ini.GetValue(kensa, L"key2"); + ASSERT_STREQ(result, L"value2"); } TEST_F(TestWide, TestSectionUKeyAValU) { - const wchar_t kensa[] = L"検査"; - const wchar_t tesuto2[] = L"テスト2"; - const wchar_t* result = ini.GetValue(kensa, L"test2"); - ASSERT_STREQ(result, tesuto2); + const wchar_t kensa[] = L"検査"; + const wchar_t tesuto2[] = L"テスト2"; + const wchar_t *result = ini.GetValue(kensa, L"test2"); + ASSERT_STREQ(result, tesuto2); } TEST_F(TestWide, TestSectionUKeyUValA) { - const wchar_t kensa[] = L"検査"; - const wchar_t tesuto[] = L"テスト"; - const wchar_t* result = ini.GetValue(kensa, tesuto); - ASSERT_STREQ(result, L"test"); + const wchar_t kensa[] = L"検査"; + const wchar_t tesuto[] = L"テスト"; + const wchar_t *result = ini.GetValue(kensa, tesuto); + ASSERT_STREQ(result, L"test"); } TEST_F(TestWide, TestSectionUKeyUValU) { - const wchar_t kensa[] = L"検査"; - const wchar_t tesuto2[] = L"テスト2"; - const wchar_t tesutoni[] = L"テスト二"; - const wchar_t* result = ini.GetValue(kensa, tesuto2); - ASSERT_STREQ(result, tesutoni); + const wchar_t kensa[] = L"検査"; + const wchar_t tesuto2[] = L"テスト2"; + const wchar_t tesutoni[] = L"テスト二"; + const wchar_t *result = ini.GetValue(kensa, tesuto2); + ASSERT_STREQ(result, tesutoni); }