diff --git a/libs/simpleini/ConvertUTF.c b/libs/simpleini/ConvertUTF.c index 9b3deebd..9eaf933a 100644 --- a/libs/simpleini/ConvertUTF.c +++ b/libs/simpleini/ConvertUTF.c @@ -194,7 +194,7 @@ static const UTF32 offsetsFromUTF8[6] = { 0x00000000UL, 0x00003080UL, 0x000E2080 * Once the bits are split out into bytes of UTF-8, this is a mask OR-ed * into the first byte, depending on how many bytes follow. There are * as many entries in this table as there are UTF-8 sequence types. - * (I.e., one byte sequence, two byte... etc.). Remember that sequencs + * (I.e., one byte sequence, two byte... etc.). Remember that sequences * for *legal* UTF-8 will be 4 or fewer bytes total. */ static const UTF8 firstByteMark[7] = { 0x00, 0x00, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC }; diff --git a/libs/simpleini/ConvertUTF.h b/libs/simpleini/ConvertUTF.h index 14d7b70d..6ecc819c 100644 --- a/libs/simpleini/ConvertUTF.h +++ b/libs/simpleini/ConvertUTF.h @@ -24,7 +24,7 @@ Conversions between UTF32, UTF-16, and UTF-8. Header file. - Several funtions are included here, forming a complete set of + Several functions are included here, forming a complete set of conversions between the three formats. UTF-7 is not included here, but is handled in a separate source file. @@ -102,7 +102,7 @@ typedef unsigned char Boolean; /* 0 or 1 */ typedef enum { conversionOK, /* conversion successful */ sourceExhausted, /* partial character in source, but hit end */ - targetExhausted, /* insuff. room in target for conversion */ + targetExhausted, /* insufficient room in target for conversion */ sourceIllegal /* source sequence is illegal/malformed */ } ConversionResult; diff --git a/libs/simpleini/SimpleIni.h b/libs/simpleini/SimpleIni.h index 5db9a763..1f0c0ae3 100644 --- a/libs/simpleini/SimpleIni.h +++ b/libs/simpleini/SimpleIni.h @@ -3,9 +3,9 @@
Library SimpleIni
File SimpleIni.h -
Author Brodie Thiesfield [code at jellycan dot com] +
Author Brodie Thiesfield
Source https://github.com/brofield/simpleini -
Version 4.17 +
Version 4.22
Jump to the @link CSimpleIniTempl CSimpleIni @endlink interface documentation. @@ -16,11 +16,10 @@ Windows and Linux/Unix. It is fast, simple and source code using this component will compile unchanged on either OS. - @section features FEATURES - MIT Licence allows free use in all software (including GPL and commercial) - - multi-platform (Windows 95/98/ME/NT/2K/XP/2003, Windows CE, Linux, Unix) + - multi-platform (Windows CE/9x/NT..10/etc, Linux, MacOSX, Unix) - loading and saving of INI-style configuration files - configuration files can have any newline format on all platforms - liberal acceptance of file format @@ -38,26 +37,36 @@ - support for non-standard character types or file encodings via user-written converter classes - support for adding/modifying values programmatically - - compiles cleanly in the following compilers: + - should compile cleanly without warning usually at the strictest warning level + - it has been tested with the following compilers: - Windows/VC6 (warning level 3) - Windows/VC.NET 2003 (warning level 4) - Windows/VC 2005 (warning level 4) + - Windows/VC 2019 (warning level 4) - Linux/gcc (-Wall) - + - Mac OS/c++ (-Wall) @section usage USAGE SUMMARY + -# Decide if you will be using utf8 or MBCS files, and working with the + data in utf8, wchar_t or ICU chars. + -# If you will only be using straight utf8 files and access the data via the + char interface, then you do not need any conversion library and could define + SI_NO_CONVERSION. Note that no conversion also means no validation of the data. + If no converter is specified then the default converter is SI_CONVERT_GENERIC + on Mac/Linux and SI_CONVERT_WIN32 on Windows. If you need widechar support on + Mac/Linux then use either SI_CONVERT_GENERIC or SI_CONVERT_ICU. These are also + supported on all platforms. -# Define the appropriate symbol for the converter you wish to use and - include the SimpleIni.h header file. If no specific converter is defined - then the default converter is used. The default conversion mode uses - SI_CONVERT_WIN32 on Windows and SI_CONVERT_GENERIC on all other - platforms. If you are using ICU then SI_CONVERT_ICU is supported on all - platforms. - -# Declare an instance the appropriate class. Note that the following + include the SimpleIni.h header file. + -# Declare an instance of the appropriate class. Note that the following definitions are just shortcuts for commonly used types. Other types (PRUnichar, unsigned short, unsigned char) are also possible. -
Interface Case-sensitive Load UTF-8 Load MBCS Typedef +
Interface Case-sensitive Load UTF-8 Load MBCS Typedef +
SI_NO_CONVERSION +
char No Yes No CSimpleIniA +
char Yes Yes No CSimpleIniCaseA
SI_CONVERT_GENERIC
char No Yes Yes #1 CSimpleIniA
char Yes Yes Yes CSimpleIniCaseA @@ -77,6 +86,8 @@ #1 On Windows you are better to use CSimpleIniA with SI_CONVERT_WIN32.
#2 Only affects Windows. On Windows this uses MBCS functions and so may fold case incorrectly leading to uncertain results. + -# Set all the options that you require, see all the Set*() options below. + The SetUnicode() option is very common and can be specified in the constructor. -# Call LoadData() or LoadFile() to load and parse the INI configuration file -# Access and modify the data of the file using the following functions @@ -88,6 +99,8 @@
GetValue Return a value for a section & key
SetValue Add or update a value for a section & key
Delete Remove a section, or a key from a section +
SectionExists Does a section exist? +
KeyExists Does a key exist?
-# Call Save() or SaveFile() to save the INI configuration data @@ -161,17 +174,21 @@ SI_STRLESS class, or by sorting the strings external to this library. - Usage of the header on Windows can be disabled by defining SI_NO_MBCS. This is defined automatically on Windows CE platforms. + - Not thread-safe so manage your own locking @section contrib CONTRIBUTIONS + + Many thanks to the following contributors: - 2010/05/03: Tobias Gehrig: added GetDoubleValue() + - See list of many contributors in github @section licence MIT LICENCE The licence text below is the boilerplate "MIT Licence" used from: http://www.opensource.org/licenses/mit-license.php - Copyright (c) 2006-2012, Brodie Thiesfield + Copyright (c) 2006-2024, Brodie Thiesfield Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -213,6 +230,7 @@ #endif #include +#include #include #include #include @@ -232,16 +250,16 @@ # define SI_ASSERT(x) #endif -enum SI_Error { - SI_OK = 0, //!< No error - SI_UPDATED = 1, //!< An existing value was updated - SI_INSERTED = 2, //!< A new value was inserted +using SI_Error = int; - // note: test for any error with (retval < 0) - SI_FAIL = -1, //!< Generic failure - SI_NOMEM = -2, //!< Out of memory error - SI_FILE = -3 //!< File error (see errno for detail error) -}; +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) #define SI_UTF8_SIGNATURE "\xEF\xBB\xBF" @@ -326,7 +344,7 @@ class CSimpleIniTempl #endif /** Strict less ordering by name of key only */ - struct KeyOrder : std::binary_function { + struct KeyOrder { bool operator()(const Entry & lhs, const Entry & rhs) const { const static SI_STRLESS isLess = SI_STRLESS(); return isLess(lhs.pItem, rhs.pItem); @@ -334,7 +352,7 @@ class CSimpleIniTempl }; /** Strict less ordering by order, and then name of key */ - struct LoadOrder : std::binary_function { + struct LoadOrder { bool operator()(const Entry & lhs, const Entry & rhs) const { if (lhs.nOrder != rhs.nOrder) { return lhs.nOrder < rhs.nOrder; @@ -414,7 +432,6 @@ class CSimpleIniTempl */ class Converter : private SI_CONVERTER { public: - using SI_CONVERTER::SizeToStore; Converter(bool a_bStoreIsUtf8) : SI_CONVERTER(a_bStoreIsUtf8) { m_scratch.resize(1024); } @@ -424,7 +441,7 @@ class CSimpleIniTempl return *this; } bool ConvertToStore(const SI_CHAR * a_pszString) { - size_t uLen = SizeToStore(a_pszString); + size_t uLen = SI_CONVERTER::SizeToStore(a_pszString); if (uLen == (size_t)(-1)) { return false; } @@ -541,6 +558,35 @@ class CSimpleIniTempl /** Query the status of spaces output */ bool UsingSpaces() const { return m_bSpaces; } + + /** Should we recognise and parse quotes in single line values? + + \param a_bParseQuotes Parse quoted data in values? + */ + void SetQuotes(bool a_bParseQuotes = true) { + m_bParseQuotes = a_bParseQuotes; + } + + /** Are we permitting keys and values to be quoted? */ + bool UsingQuotes() const { return m_bParseQuotes; } + + /** When reading/writing an ini file, do we require every key to have an equals + sign to delineate a valid key value. If false, then every valid key must + have an equals sign and any lines without an equals sign is ignored. If + true then keys do not require an equals sign to be considered a key. Note + that this means that any non-commented line of text would become a key. + + \param a_bAllowKeyOnly Permit keys without an equals sign or value. + */ + void SetAllowKeyOnly(bool a_bAllowKeyOnly = true) { + m_bAllowKeyOnly = a_bAllowKeyOnly; + } + + /** Do we allow keys to exist without a value or equals sign? */ + bool GetAllowKeyOnly() const { return m_bAllowKeyOnly; } + + + /*-----------------------------------------------------------------------*/ /** @} @{ @name Loading INI Data */ @@ -837,13 +883,27 @@ class CSimpleIniTempl are in use! @param a_pSection Name of the section to return - @return boolean Was a section matching the supplied - name found. + @return Section data */ 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 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 (see SetMultiKey) then only the first value associated with that key will be returned, see GetAllValues for getting all values with multikey. @@ -1065,8 +1125,8 @@ class CSimpleIniTempl 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 - that name; there is no way to selectively delete individual key/values - in this situation. + that name; to selectively delete individual key/values, use + DeleteValue. @param a_pSection Section to delete key from, or if a_pKey is NULL, the section to remove. @@ -1085,6 +1145,33 @@ class CSimpleIniTempl bool a_bRemoveEmpty = false ); + /** 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. + Note when multiple keys is enabled, all keys with the value will + be deleted. + + @param a_pSection Section to delete key from, or if + a_pKey is NULL, the section to remove. + @param a_pKey Key to remove from the section. Set to + NULL to remove the entire section. + @param a_pValue Value of key to remove from the section. + Set to NULL to remove all keys. + @param a_bRemoveEmpty If the section is empty after this key has + been deleted, should the empty section be + removed? + + @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 + ); + /*-----------------------------------------------------------------------*/ /** @} @{ @name Converter */ @@ -1186,6 +1273,7 @@ class CSimpleIniTempl 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, @@ -1217,6 +1305,9 @@ class CSimpleIniTempl /** File comment for this data, if one exists. */ const SI_CHAR * m_pFileComment; + /** constant empty string */ + const SI_CHAR m_cEmptyString; + /** Parsed INI data. Section -> (Key -> Value). */ TSection m_data; @@ -1238,6 +1329,12 @@ class CSimpleIniTempl /** 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; + /** Next order value, used to ensure sections and keys are output in the same order that they are loaded/added. */ @@ -1257,10 +1354,13 @@ CSimpleIniTempl::CSimpleIniTempl( : 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) { } @@ -1359,7 +1459,7 @@ CSimpleIniTempl::LoadFile( } // allocate and ensure NULL terminated - char * pData = new char[lSize+1]; + char * pData = new(std::nothrow) char[lSize+static_cast(1)]; if (!pData) { return SI_NOMEM; } @@ -1386,21 +1486,26 @@ CSimpleIniTempl::LoadData( size_t a_uDataLen ) { - SI_CONVERTER converter(m_bStoreIsUtf8); - - if (a_uDataLen == 0) { + 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(); + } - // consume the UTF-8 BOM if it exists - if (m_bStoreIsUtf8 && a_uDataLen >= 3) { - if (memcmp(a_pData, SI_UTF8_SIGNATURE, 3) == 0) { - a_pData += 3; - a_uDataLen -= 3; - } + 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; @@ -1408,7 +1513,7 @@ CSimpleIniTempl::LoadData( // allocate memory for the data, ensure that there is a NULL // terminator wherever the converted data ends - SI_CHAR * pData = new SI_CHAR[uLen+1]; + SI_CHAR * pData = new(std::nothrow) SI_CHAR[uLen+1]; if (!pData) { return SI_NOMEM; } @@ -1512,6 +1617,7 @@ CSimpleIniTempl::FindEntry( { a_pComment = NULL; + bool bHaveValue = false; SI_CHAR * pTrail = NULL; while (*a_pData) { // skip spaces and empty lines @@ -1569,19 +1675,20 @@ CSimpleIniTempl::FindEntry( } // find the end of the key name (it may contain spaces) - // and convert it to lowercase as necessary a_pKey = a_pData; while (*a_pData && *a_pData != '=' && !IsNewLineChar(*a_pData)) { ++a_pData; } + // *a_pData is null, equals, or newline - // if it's an invalid line, just skip it - if (*a_pData != '=') { + // if no value and we don't allow no value, then invalid + bHaveValue = (*a_pData == '='); + if (!bHaveValue && !m_bAllowKeyOnly) { continue; } // empty keys are invalid - if (a_pKey == a_pData) { + if (bHaveValue && a_pKey == a_pData) { while (*a_pData && !IsNewLineChar(*a_pData)) { ++a_pData; } @@ -1594,36 +1701,56 @@ CSimpleIniTempl::FindEntry( --pTrail; } ++pTrail; - *pTrail = 0; - // 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; - } + if (bHaveValue) { + // process the value + *pTrail = 0; - // 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; - } + // 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; + } - // 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; + // 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; + } - // 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); + // 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; } // return the standard entry @@ -1683,6 +1810,41 @@ CSimpleIniTempl::IsMultiLineData( return false; } +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 + + // empty string + if (!*a_pData) { + return false; + } + + // check for prefix + if (IsSpace(*a_pData)) { + return true; + } + + // embedded newlines + while (*a_pData) { + if (IsNewLineChar(*a_pData)) { + return false; + } + ++a_pData; + } + + // check for suffix + if (IsSpace(*--a_pData)) { + return true; + } + + return false; +} + template bool CSimpleIniTempl::IsNewLineChar( @@ -1716,8 +1878,8 @@ CSimpleIniTempl::LoadMultiLineText( a_pVal = a_pData; // find the end tag. This tag must start in column 1 and be - // followed by a newline. No whitespace removal is done while - // searching for this tag. + // 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 @@ -1773,10 +1935,18 @@ CSimpleIniTempl::LoadMultiLineText( // 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 && - (!IsLess(pDataLine, a_pTagName) && !IsLess(a_pTagName, pDataLine))) - { - break; + 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; + } + + *pc = ch; } // if we are at the end of the data then we just automatically end @@ -1833,7 +2003,7 @@ CSimpleIniTempl::CopyString( for ( ; a_pString[uLen]; ++uLen) /*loop*/ ; } ++uLen; // NULL character - SI_CHAR * pCopy = new SI_CHAR[uLen]; + SI_CHAR * pCopy = new(std::nothrow) SI_CHAR[uLen]; if (!pCopy) { return SI_NOMEM; } @@ -1878,7 +2048,7 @@ CSimpleIniTempl::AddEntry( // only set the comment if this is a section only entry Entry oSection(a_pSection, ++m_nOrder); - if (a_pComment && (!a_pKey || !a_pValue)) { + if (a_pComment && !a_pKey) { oSection.pComment = a_pComment; } @@ -1888,14 +2058,15 @@ CSimpleIniTempl::AddEntry( iSection = i.first; bInserted = true; } - if (!a_pKey || !a_pValue) { - // section only entries are specified with pItem and pVal as NULL + 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 @@ -1918,6 +2089,11 @@ CSimpleIniTempl::AddEntry( 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) { @@ -1942,8 +2118,8 @@ CSimpleIniTempl::AddEntry( } typename TKeyVal::value_type oEntry(oKey, static_cast(NULL)); iKey = keyval.insert(oEntry); - bInserted = true; } + iKey->second = a_pValue; return bInserted ? SI_INSERTED : SI_UPDATED; } @@ -2009,7 +2185,7 @@ CSimpleIniTempl::GetLongValue( long nValue = a_nDefault; char * pszSuffix = szValue; if (szValue[0] == '0' && (szValue[1] == 'x' || szValue[1] == 'X')) { - if (!szValue[2]) return a_nDefault; + if (!szValue[2]) return a_nDefault; nValue = strtol(&szValue[2], &pszSuffix, 16); } else { @@ -2043,7 +2219,7 @@ CSimpleIniTempl::SetLongValue( #if __STDC_WANT_SECURE_LIB__ && !_WIN32_WCE sprintf_s(szInput, a_bUseHex ? "0x%lx" : "%ld", a_nValue); #else // !__STDC_WANT_SECURE_LIB__ - sprintf(szInput, a_bUseHex ? "0x%lx" : "%ld", a_nValue); + snprintf(szInput, sizeof(szInput), a_bUseHex ? "0x%lx" : "%ld", a_nValue); #endif // __STDC_WANT_SECURE_LIB__ // convert to output text @@ -2090,32 +2266,32 @@ CSimpleIniTempl::GetDoubleValue( 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 - ) + 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; + // use SetValue to create sections + if (!a_pSection || !a_pKey) return SI_FAIL; - // convert to an ASCII string - char szInput[64]; + // convert to an ASCII string + char szInput[64]; #if __STDC_WANT_SECURE_LIB__ && !_WIN32_WCE - sprintf_s(szInput, "%f", a_nValue); + sprintf_s(szInput, "%f", a_nValue); #else // !__STDC_WANT_SECURE_LIB__ - sprintf(szInput, "%f", a_nValue); + 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)); + // 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); + // actually add it + return AddEntry(a_pSection, a_pKey, szOutput, a_pComment, a_bForceReplace, true); } template @@ -2390,6 +2566,19 @@ CSimpleIniTempl::Save( 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; + } + } + // write the file comment if we have one bool bNeedNewLine = false; if (m_pFileComment) { @@ -2465,22 +2654,31 @@ CSimpleIniTempl::Save( } a_oOutput.Write(convert.Data()); - // write the value - if (!convert.ConvertToStore(iValue->pItem)) { - return SI_FAIL; - } - a_oOutput.Write(m_bSpaces ? " = " : "="); - 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)) { + // write the value as long + if (*iValue->pItem || !m_bAllowKeyOnly) { + if (!convert.ConvertToStore(iValue->pItem)) { return SI_FAIL; } - a_oOutput.Write("END_OF_TEXT"); - } - else { - a_oOutput.Write(convert.Data()); + 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); } @@ -2528,6 +2726,18 @@ CSimpleIniTempl::Delete( const SI_CHAR * a_pKey, bool a_bRemoveEmpty ) +{ + return DeleteValue(a_pSection, a_pKey, NULL, a_bRemoveEmpty); +} + +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; @@ -2545,18 +2755,30 @@ CSimpleIniTempl::Delete( return false; } + const static SI_STRLESS isLess = SI_STRLESS(); + // remove any copied strings and then the key typename TKeyVal::iterator iDelete; + bool bDeleted = false; do { iDelete = iKeyVal++; - DeleteString(iDelete->first.pItem); - DeleteString(iDelete->second); - iSection->second.erase(iDelete); + 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 @@ -2610,13 +2832,15 @@ 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 +// library available. Only CSimpleIniA etc is defined. // SI_CONVERT_GENERIC Use the Unicode reference conversion library in // the accompanying files ConvertUTF.h/c // SI_CONVERT_ICU Use the IBM ICU conversion library. Requires // ICU headers on include path and icuuc.lib // SI_CONVERT_WIN32 Use the Win32 API functions for conversion. -#if !defined(SI_CONVERT_GENERIC) && !defined(SI_CONVERT_WIN32) && !defined(SI_CONVERT_ICU) +#if !defined(SI_NO_CONVERSION) && !defined(SI_CONVERT_GENERIC) && !defined(SI_CONVERT_WIN32) && !defined(SI_CONVERT_ICU) # ifdef _WIN32 # define SI_CONVERT_WIN32 # else @@ -2873,7 +3097,7 @@ class SI_ConvertW { // This uses the Unicode reference implementation to do the // conversion from UTF-8 to wchar_t. The required files are // ConvertUTF.h and ConvertUTF.c which should be included in - // the distribution but are publically available from unicode.org + // the distribution but are publicly available from unicode.org // at http://www.unicode.org/Public/PROGRAMS/CVTUTF/ ConversionResult retval; const UTF8 * pUtf8 = (const UTF8 *) a_pInputData; @@ -2960,7 +3184,7 @@ class SI_ConvertW { // This uses the Unicode reference implementation to do the // conversion from wchar_t to UTF-8. The required files are // ConvertUTF.h and ConvertUTF.c which should be included in - // the distribution but are publically available from unicode.org + // the distribution but are publicly available from unicode.org // at http://www.unicode.org/Public/PROGRAMS/CVTUTF/ ConversionResult retval; UTF8 * pUtf8 = (UTF8 *) a_pOutputData; @@ -3346,6 +3570,19 @@ class SI_ConvertW { #endif // SI_CONVERT_WIN32 + +// --------------------------------------------------------------------------- +// SI_NO_CONVERSION +// --------------------------------------------------------------------------- +#ifdef SI_NO_CONVERSION + +#define SI_Case SI_GenericCase +#define SI_NoCase SI_GenericNoCase + +#endif // SI_NO_CONVERSION + + + // --------------------------------------------------------------------------- // TYPE DEFINITIONS // --------------------------------------------------------------------------- @@ -3355,27 +3592,35 @@ typedef CSimpleIniTempl,SI_ConvertA > CSimpleIniCaseA; -#if defined(SI_CONVERT_ICU) +#if defined(SI_NO_CONVERSION) +// 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 +#else +# if defined(SI_CONVERT_ICU) typedef CSimpleIniTempl,SI_ConvertW > CSimpleIniW; typedef CSimpleIniTempl,SI_ConvertW > CSimpleIniCaseW; -#else +# else typedef CSimpleIniTempl,SI_ConvertW > CSimpleIniW; typedef CSimpleIniTempl,SI_ConvertW > CSimpleIniCaseW; -#endif +# 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 +# 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)