// Library: SimpleIni
// File: SimpleIni.h
// Author: Brodie Thiesfield <code@jellycan.com>
// Source: http://code.jellycan.com/simpleini/
// Version: 2.8
//
// INTRODUCTION
// ============
// This component allows an INI-style configuration file to be used on both
// Windows and Linux/Unix. It is fast, simple and source code using this
// component will compile unchanged on either OS.
//
// 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)
// * loading and saving of INI-style configuration files
// * configuration files can have any newline format on all platforms
// * liberal acceptance of file format
// - key/values with no section
// - removal of whitespace around sections, keys and values
// * support for multi-line values (values with embedded newline characters)
// * optional support for multiple keys with the same name
// * optional case-insensitive sections and keys (for ASCII characters only)
// * supports both char or wchar_t programming interfaces
// * supports both MBCS (system locale) and UTF-8 file encodings
// * system locale does not need to be UTF-8 on Linux/Unix to load UTF-8 file
// * support for non-ASCII characters in section, keys, values and comments
// * support for non-standard character types or file encodings
// via user-written converter classes
// * support for adding/modifying values programmatically
// * compiles cleanly at warning level 4 (Windows/VC.NET 2003), warning level
// 3 (Windows/VC6) and -Wall (Linux/gcc)
//
// USAGE SUMMARY
// =============
// 1. 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.
//
// 2. Declare an instance 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
// --------- -------------- ---------- --------- ---------------
// SI_CONVERT_GENERIC
// char No Yes Yes *1 CSimpleIniA
// char Yes Yes Yes CSimpleIniCaseA
// wchar_t No Yes Yes CSimpleIniW
// wchar_t Yes Yes Yes CSimpleIniCaseW
// SI_CONVERT_WIN32
// char No No *2 Yes CSimpleIniA
// char Yes Yes Yes CSimpleIniCaseA
// wchar_t No Yes Yes CSimpleIniW
// wchar_t Yes Yes Yes CSimpleIniCaseW
// SI_CONVERT_ICU
// char No Yes Yes CSimpleIniA
// char Yes Yes Yes CSimpleIniCaseA
// UChar No Yes Yes CSimpleIniW
// UChar Yes Yes Yes CSimpleIniCaseW
//
// *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.
//
// 2. Call LoadFile() to load and parse the INI configuration file
//
// 3. Access and modify the data of the file using the following functions
//
// GetAllSections Return all section names
// GetAllKeys Return all key names for a section
// GetSection Return all key names and values in a section
// GetSectionSize Return the number of keys in a section
// GetValue Return a value for a section & key
// GetAllValues Return all values for a section & key
// SetValue Add or update a value for a section & key
// Delete Remove a section, or a key from a section
//
// 4. Call Save(), SaveFile() or SaveString() to save the INI configuration
// data (as necessary)
//
// MULTI-LINE VALUES
// =================
// Values that span multiple lines are created using the following format.
//
// key = <<<ENDTAG
// .... multiline value ....
// ENDTAG
//
// Note the following:
// * The text used for ENDTAG can be anything and is used to find
// where the multi-line text ends.
// * The newline after ENDTAG in the start tag, and the newline
// before ENDTAG in the end tag is not included in the data value.
// * The ending tag must be on it's own line with no whitespace before
// or after it.
// * The multi-line value is not modified at load or save. This means
// that the newline format (PC, Unix, Mac) is whatever the original
// file uses.
//
// NOTES
// =====
// * To compile using Microsoft Visual Studio 6 or Embedded Visual C++ 4,
// you need to modify this header file and remove all instances of the
// "typename" keyword where error C2899 occurs.
// * To load UTF-8 data on Windows 95, you need to use Microsoft Layer for
// Unicode, or SI_CONVERT_GENERIC, or SI_CONVERT_ICU.
// * When using SI_CONVERT_GENERIC, ConvertUTF.c must be compiled and linked.
// * When using SI_CONVERT_ICU, ICU header files must be on the include
// path and icuuc.lib must be linked in.
// * To load a UTF-8 file on Windows AND expose it with SI_CHAR == char,
// you should use SI_CONVERT_GENERIC.
// * The collation (sorting) order used for sections and keys returned from
// iterators is NOT DEFINED. If collation order of the text is important
// then it should be done yourself by either supplying a replacement
// SI_STRLESS class, or by sorting the strings external to this library.
// * Usage of the <mbstring.h> header on Windows can be disabled by defining
// SI_NO_MBCS. This is defined automatically on Windows CE platforms.
//
// MIT LICENCE
// ===========
// The licence text below is the boilerplate "MIT Licence" used from:
// http://www.opensource.org/licenses/mit-license.php
//
// Copyright (c) 2006, 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
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is furnished
// to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#ifndef INCLUDED_SimpleIni_h
#define INCLUDED_SimpleIni_h
// Disable these warnings in MSVC:
// 4127 "conditional expression is constant" as the conversion classes trigger
// it with the statement if (sizeof(SI_CHAR) == sizeof(char)). This test will
// be optimized away in a release build.
// 4702 "unreachable code" as the MS STL header causes it in release mode.
// Again, the code causing the warning will be cleaned up by the compiler.
#ifdef _MSC_VER
# pragma warning (disable: 4127 4702)
#endif
#include <string>
#include <map>
#include <list>
#include <assert.h>
enum SI_Error {
SI_OK = 0, //!< No error
SI_UPDATED = 1, //!< An existing value was updated
SI_INSERTED = 2, //!< A new value was inserted
// 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)
};
#define SI_BOM_UTF8 "\xEF\xBB\xBF"
#ifdef _WIN32
# 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"
#endif // _WIN32
#if defined(_WIN32) || defined(SI_CONVERT_ICU)
# define SI_HAS_WIDE_LOADFILE
#endif
#if defined(SI_CONVERT_ICU)
# include <unicode/ustring.h>
#endif
// ---------------------------------------------------------------------------
// MAIN TEMPLATE CLASS
// ---------------------------------------------------------------------------
/**
* Simple INI file reader. This can be instantiated with the choice of unicode
* or native characterset, and case sensitive or insensitive comparisons of
* section and key names. The supported combinations are pre-defined with the
* following typedefs:
*
* Interface Case-sensitive Typedef
* --------- -------------- ---------------
* char No CSimpleIniA
* char Yes CSimpleIniCaseA
* wchar_t No CSimpleIniW
* wchar_t Yes CSimpleIniCaseW
*
* Note that using other types for the SI_CHAR is supported. For instance,
* unsigned char, unsigned short, etc. Note that where the alternative type
* is a different size to char/wchar_t you may need to supply new helper
* classes for SI_STRLESS and SI_CONVERTER.
*/
template<class SI_CHAR, class SI_STRLESS, class SI_CONVERTER>
class CSimpleIniTempl
{
public:
/** map keys to values */
typedef std::multimap<const SI_CHAR *,const SI_CHAR *,SI_STRLESS> TKeyVal;
/** map sections to key/value map */
typedef std::map<const SI_CHAR *,TKeyVal,SI_STRLESS> TSection;
/** set of dependent string pointers. Note that these pointers are
* dependent on memory owned by CSimpleIni. */
typedef std::list<const SI_CHAR *> TNamesDepend;
/** 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;
};
/** 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);
}
};
/** 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);
}
};
/** 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 = 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<char*>(m_scratch.data()),
m_scratch.size());
}
const char * Data() { return m_scratch.data(); }
private:
std::string m_scratch;
};
public:
/**
* 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);
/**
* Destructor
*/
~CSimpleIniTempl();
/**
* Deallocate all memory stored by this object
*/
void Reset();
/**
* 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.
*
* If the file is not set to Unicode (UTF-8), then the data encoding is
* assumed to be the OS native encoding. This encoding is the system
* locale on Linux/Unix and the legacy MBCS encoding on Windows NT/2K/XP.
* If the storage format is set to Unicode then the file will be loaded
* as UTF-8 encoded data regardless of the native file encoding. If
* SI_CHAR == char then all of the char* parameters take and return UTF-8
* encoded data regardless of the system locale.
*/
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; }
/**
* 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:
*
* [section]
* test=value1
* test=value2
*
* Then with SetMultiKey(true), both of the values "value1" and "value2"
* will be returned for the key test. If SetMultiKey(false) is used, then
* the value for "test" will only be "value2". This value may be changed
* at any time.
*/
void SetMultiKey(bool a_bAllowMultiKey = true) {
m_bAllowMultiKey = a_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
* set to false then the multi-line construct <<<TAG as a value will be
* returned as is instead of loading the data. This value may be changed
* at any time.
*/
void SetMultiLine(bool a_bAllowMultiLine = true) {
m_bAllowMultiLine = a_bAllowMultiLine;
}
/**
* Query the status of multi-line data.
*/
bool IsMultiLine() const { return m_bAllowMultiLine; }
/**
* Load an INI file from disk into memory
*
* @param a_pszFile Path of the file to be loaded. This will be passed
* to fopen() and so must be a valid path for the
* current platform.
*
* @return SI_Error See error definitions
*/
SI_Error LoadFile(const char * a_pszFile);
#ifdef SI_HAS_WIDE_LOADFILE
/**
* Load an INI file from disk into memory
*
* @param a_pwszFile Path of the file to be loaded in UTF-16. This will
* be passed to _wfopen() on Windows. There is no
* wchar_t fopen function on Linux/Unix so this
* function is not supported there.
*
* @return SI_Error See error definitions
*/
SI_Error LoadFile(const wchar_t * a_pwszFile);
#endif // _WIN32
/**
* Load INI file data direct from memory
*
* @param a_pData Data to be loaded
* @param a_uDataLen Length of the data in bytes
*
* @return SI_Error See error definitions
*/
SI_Error LoadFile(
const char * a_pData,
size_t a_uDataLen);
/**
* Save the INI data. The data will be written to the output device
* in a format appropriate to the current data, selected by:
*
* SI_CHAR FORMAT
* ------- ------
* char same format as when loaded (MBCS or UTF-8)
* wchar_t UTF-8
* other UTF-8
*
* Note that comments, etc from the original data are not saved. Only the
* valid data contents stored in the file are written out. Any data
* prepended or appended to the output device should use the same format
* (MBCS or UTF-8) as this data, use the GetConverter() method to convert
* text to the correct format.
*
* To add a BOM to UTF-8 data, write it out manually at the very beginning
* like is done in SaveFile when a_bUseBOM is true.
*/
SI_Error Save(OutputWriter & a_oOutput) const;
/**
* Save the INI data to a file. See Save() for details. Do not set
* a_bUseBOM to true if any information has been written to the file
* prior to calling this method.
*
* @param a_pFile Handle to a file. File should be opened for
* binary output.
* @param a_bUseBOM Prepend the UTF-8 BOM if the output stream is
* in UTF-8 format.
*/
SI_Error SaveFile(FILE * a_pFile, bool a_bUseBOM = false) const {
FileWriter writer(a_pFile);
if (m_bStoreIsUtf8 && a_bUseBOM) {
writer.Write(SI_BOM_UTF8);
}
return Save(writer);
}
/**
* Save the INI data to a string. See Save() for details.
*
* @param a_sBuffer String to have the INI data appended to.
*/
SI_Error SaveString(std::string & a_sBuffer) const {
StringWriter writer(a_sBuffer);
return Save(writer);
}
/**
* 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.
*
* NOTE! The returned value is a pointer to string data stored in memory
* owned by CSimpleIni. Ensure that the CSimpleIni object is not destroyed
* or Reset while you are using this pointer!
*
* @param a_pSection Section to search
* @param a_pKey Key to search for
* @param a_pDefault Value to return if the key is not found
* @param a_pHasMultiple Optionally receive notification of if there are
* multiple entries for this key.
*
* @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 = 0,
bool * a_pHasMultiple = 0) const;
/**
* Retrieve all values for a specific key. This method can be used when
* multiple keys are both enabled and disabled.
*
* NOTE! The returned values are pointers to string data stored in memory
* owned by CSimpleIni. Ensure that the CSimpleIni object is not destroyed
* or Reset while you are using this pointer!
*
* @param a_pSection Section to search
* @param a_pKey Key to search for
* @param a_values List to return if the key is not found
* @param a_pHasMultiple Optionally receive notification of if there are
* multiple entries for this key.
*
* @return a_pDefault Key was not found in the section
* @return other Value of the key
*/
bool GetAllValues(
const SI_CHAR * a_pSection,
const SI_CHAR * a_pKey,
TNamesDepend & a_values) 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
* @param a_pKey Key to add or update. Set to NULL to
* create an empty section.
* @param a_pValue Value to set. Set to NULL to create an
* empty section.
*
* @return SI_Error See error definitions
* @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)
{
return AddEntry(a_pSection, a_pKey, a_pValue, true);
}
/**
* 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
* that name; there is no way to selectively delete individual key/values
* in this situation.
*
* @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_bRemoveEmpty If the section is empty after this key has
* been deleted, should the empty section be
* removed?
*
* @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);
/**
* 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.
*
* @param a_pSection Section to request data for
*
* @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;
/**
* 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.
*
* NOTE! This structure contains only pointers to strings. The actual
* string data is stored in memory owned by CSimpleIni. Ensure that the
* CSimpleIni object is not destroyed or Reset() while these strings
* are in use!
*
* @param a_pSection Name of the section to return
* @param a_pData Pointer to the section data.
* @return boolean Was a section matching the supplied
* name found.
*/
const TKeyVal * GetSection(
const SI_CHAR * a_pSection) const;
/**
* 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
* collation order of the returned strings is NOT DEFINED.
*
* NOTE! This structure contains only pointers to strings. The actual
* string data is stored in memory owned by CSimpleIni. Ensure that the
* CSimpleIni object is not destroyed or Reset() while these strings
* are in use!
*
* @param a_names Vector that will receive all of the section
* names. See note above!
*/
void GetAllSections(
TNamesDepend & a_names ) const;
/**
* Retrieve all unique key names in a section. The collation order of the
* returned strings is NOT DEFINED. Only unique key names are returned.
*
* NOTE! This structure contains only pointers to strings. The actual
* string data is stored in memory owned by CSimpleIni. Ensure that the
* CSimpleIni object is not destroyed or Reset() while these strings
* are in use!
*
* @param a_pSection Section to request data for
* @param a_names List that will receive all of the key
* names. See note above!
*/
void GetAllKeys(
const SI_CHAR * a_pSection,
TNamesDepend & a_names ) const;
/**
* 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);
}
private:
/** Load the file from a file pointer. */
SI_Error LoadFile(FILE * a_fpFile);
/** 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;
/** Add the section/key/value to our data. Strings will be copied only
* if a_bCopyStrings is set to true, otherwise 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,
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 == '#');
}
/** 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 IsEqual(const SI_CHAR * a_pLeft, const SI_CHAR * a_pRight) const {
static const SI_STRLESS strless = SI_STRLESS();
return !strless(a_pLeft, a_pRight) && !strless(a_pRight, a_pLeft);
}
bool IsMultiLineTag(const SI_CHAR * a_pData) const;
bool IsMultiLineData(const SI_CHAR * a_pData) const;
bool FindMultiLine(SI_CHAR *& a_pData, const SI_CHAR *& a_pVal) const;
bool IsNewLineChar(SI_CHAR a_c) const;
private:
/** 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;
/** 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;
/** Parsed INI data. Section -> (Key -> Value). */
TSection m_data;
/** 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;
/** 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 data values permitted to span multiple lines? */
bool m_bAllowMultiLine;
};
// ---------------------------------------------------------------------------
// IMPLEMENTATION
// ---------------------------------------------------------------------------
template<class SI_CHAR, class SI_STRLESS, class SI_CONVERTER>
CSimpleIniTempl<SI_CHAR,SI_STRLESS,SI_CONVERTER>::CSimpleIniTempl(
bool a_bIsUtf8, bool a_bAllowMultiKey, bool a_bAllowMultiLine)
: m_pData(0),
m_uDataLen(0),
m_bStoreIsUtf8(a_bIsUtf8),
m_bAllowMultiKey(a_bAllowMultiKey),
m_bAllowMultiLine(a_bAllowMultiLine)
{ }
template<class SI_CHAR, class SI_STRLESS, class SI_CONVERTER>
CSimpleIniTempl<SI_CHAR,SI_STRLESS,SI_CONVERTER>::~CSimpleIniTempl()
{
Reset();
}
template<class SI_CHAR, class SI_STRLESS, class SI_CONVERTER>
void
CSimpleIniTempl<SI_CHAR,SI_STRLESS,SI_CONVERTER>::Reset()
{
// remove all data
delete[] m_pData;
m_pData = 0;
m_uDataLen = 0;
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<SI_CHAR*>(*i);
}
m_strings.erase(m_strings.begin(), m_strings.end());
}
}
template<class SI_CHAR, class SI_STRLESS, class SI_CONVERTER>
SI_Error
CSimpleIniTempl<SI_CHAR,SI_STRLESS,SI_CONVERTER>::LoadFile(
const char * a_pszFile)
{
FILE * fp = fopen(a_pszFile, "rb");
if (!fp) {
return SI_FILE;
}
SI_Error rc = LoadFile(fp);
fclose(fp);
return rc;
}
#ifdef SI_HAS_WIDE_LOADFILE
template<class SI_CHAR, class SI_STRLESS, class SI_CONVERTER>
SI_Error
CSimpleIniTempl<SI_CHAR,SI_STRLESS,SI_CONVERTER>::LoadFile(
const wchar_t * a_pwszFile)
{
#ifdef _WIN32
FILE * fp;
errno_t err = _wfopen_s(&fp, a_pwszFile, L"rb");
if ((err!=0)||(!fp)) {
return SI_FILE;
}
SI_Error rc = LoadFile(fp);
fclose(fp);
return rc;
#else // SI_CONVERT_ICU
char szFile[256];
u_austrncpy(szFile, a_pwszFile, sizeof(szFile));
return LoadFile(szFile);
#endif
}
#endif // _WIN32
template<class SI_CHAR, class SI_STRLESS, class SI_CONVERTER>
SI_Error
CSimpleIniTempl<SI_CHAR,SI_STRLESS,SI_CONVERTER>::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;
}
char * pData = new char[lSize];
if (!pData) {
return SI_NOMEM;
}
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 = LoadFile(pData, uRead);
delete[] pData;
return rc;
}
template<class SI_CHAR, class SI_STRLESS, class SI_CONVERTER>
SI_Error
CSimpleIniTempl<SI_CHAR,SI_STRLESS,SI_CONVERTER>::LoadFile(
const char * a_pData,
size_t a_uDataLen)
{
SI_CONVERTER converter(m_bStoreIsUtf8);
// consume the UTF-8 BOM if it exists
if (m_bStoreIsUtf8 && a_uDataLen >= 3) {
if (memcmp(a_pData, SI_BOM_UTF8, 3) == 0) {
a_pData += 3;
a_uDataLen -= 3;
}
}
// determine the length of the converted data
size_t uLen = converter.SizeFromStore(a_pData, a_uDataLen);
if (uLen == (size_t)(-1)) {
return SI_FAIL;
}
// 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];
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 * pKey = 0;
const SI_CHAR * pVal = 0;
// add every entry in the file to the data table. We copy the strings if
// we are loading data into this class when we already have stored some
// because we only store a single block.
SI_Error rc;
bool bCopyStrings = (m_pData != 0);
while (FindEntry(pWork, pSection, pKey, pVal)) {
rc = AddEntry(pSection, pKey, pVal, bCopyStrings);
if (rc < 0) return rc;
}
// store these strings if we didn't copy them
if (bCopyStrings) {
delete[] pData;
}
else {
m_pData = pData;
m_uDataLen = uLen+1;
}
return SI_OK;
}
template<class SI_CHAR, class SI_STRLESS, class SI_CONVERTER>
bool
CSimpleIniTempl<SI_CHAR,SI_STRLESS,SI_CONVERTER>::FindEntry(
SI_CHAR *& a_pData,
const SI_CHAR *& a_pSection,
const SI_CHAR *& a_pKey,
const SI_CHAR *& a_pVal ) const
{
SI_CHAR * pTrail;
while (*a_pData) {
// skip spaces and empty lines
while (*a_pData && IsSpace(*a_pData)) {
++a_pData;
}
if (!*a_pData) {
break;
}
// skip comment lines
if (IsComment(*a_pData)) {
while (*a_pData && !IsNewLineChar(*a_pData)) {
++a_pData;
}
continue;
}
// process section names
if (*a_pData == '[') {
// skip leading spaces
++a_pData;
while (*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;
}
// 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;
}
a_pKey = 0;
a_pVal = 0;
return true;
}
// 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;
}
// if it's an invalid line, just skip it
if (*a_pData != '=') {
continue;
}
// empty keys are invalid
if (a_pKey == a_pData) {
while (*a_pData && !IsNewLineChar(*a_pData)) {
++a_pData;
}
continue;
}
// remove trailing spaces from the key
pTrail = a_pData - 1;
while (pTrail >= a_pKey && IsSpace(*pTrail)) {
--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;
}
// 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;
}
// remove trailing spaces from the value
pTrail = a_pData - 1;
if (*a_pData) { // prepare for the next round
++a_pData;
}
while (pTrail >= a_pVal && IsSpace(*pTrail)) {
--pTrail;
}
++pTrail;
*pTrail = 0;
// check for multi-line entries
if (m_bAllowMultiLine && IsMultiLineTag(a_pVal)) {
return FindMultiLine(a_pData, a_pVal);
}
// return the standard entry
return true;
}
return false;
}
template<class SI_CHAR, class SI_STRLESS, class SI_CONVERTER>
bool
CSimpleIniTempl<SI_CHAR,SI_STRLESS,SI_CONVERTER>::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<class SI_CHAR, class SI_STRLESS, class SI_CONVERTER>
bool
CSimpleIniTempl<SI_CHAR,SI_STRLESS,SI_CONVERTER>::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;
}
// check for prefix
if (IsSpace(*a_pData)) {
return true;
}
// embedded newlines
while (*a_pData) {
if (IsNewLineChar(*a_pData)) {
return true;
}
}
// check for suffix
if (IsSpace(*--a_pData)) {
return true;
}
return false;
}
template<class SI_CHAR, class SI_STRLESS, class SI_CONVERTER>
bool
CSimpleIniTempl<SI_CHAR,SI_STRLESS,SI_CONVERTER>::IsNewLineChar(
SI_CHAR a_c ) const
{
return a_c == '\n' || a_c == '\r';
}
template<class SI_CHAR, class SI_STRLESS, class SI_CONVERTER>
bool
CSimpleIniTempl<SI_CHAR,SI_STRLESS,SI_CONVERTER>::FindMultiLine(
SI_CHAR *& a_pData,
const SI_CHAR *& a_pVal
) const
{
// skip the "<<<" to get the tag that will end the multiline
const SI_CHAR * pTagName = a_pVal + 3;
a_pVal = a_pData; // real value starts on next line
// 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.
SI_CHAR *pLine;
SI_CHAR cRememberThis;
for(;;) {
// find the beginning and end of this line
while (IsNewLineChar(*a_pData)) ++a_pData;
pLine = a_pData;
while (*a_pData && !IsNewLineChar(*a_pData)) ++a_pData;
// end the line with a NULL
cRememberThis = *a_pData;
*a_pData = 0;
// see if we have found the tag
if (IsEqual(pLine, pTagName)) {
// null terminate the data before the newline of the previous line.
// If you want a new line at the end of the line then add an empty
// line before the tag.
--pLine;
if (*(pLine-1) == '\r') {
// handle Windows style newlines. This handles Unix newline files
// on Windows and Windows style newlines on Unix. \n\r
--pLine;
}
*pLine = 0;
if (cRememberThis) {
++a_pData;
}
return true;
}
// otherwise put the char back and continue checking
if (!cRememberThis) {
return false;
}
*a_pData++ = cRememberThis;
}
}
template<class SI_CHAR, class SI_STRLESS, class SI_CONVERTER>
SI_Error
CSimpleIniTempl<SI_CHAR,SI_STRLESS,SI_CONVERTER>::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 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<class SI_CHAR, class SI_STRLESS, class SI_CONVERTER>
SI_Error
CSimpleIniTempl<SI_CHAR,SI_STRLESS,SI_CONVERTER>::AddEntry(
const SI_CHAR * a_pSection,
const SI_CHAR * a_pKey,
const SI_CHAR * a_pValue,
bool a_bCopyStrings)
{
SI_Error rc;
bool bInserted = false;
// check for existence of the section first if we need string copies
typename TSection::iterator iSection = m_data.end();
if (a_bCopyStrings) {
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
// because we will be inserting the section next
rc = CopyString(a_pSection);
if (rc < 0) return rc;
}
}
// create the section entry
if (iSection == m_data.end()) {
std::pair<typename TSection::iterator,bool> i =
m_data.insert(
typename TSection::value_type( a_pSection, TKeyVal() ) );
iSection = i.first;
bInserted = true;
}
if (!a_pKey || !a_pValue) {
// section only entries are specified with pKey and pVal 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);
// make string copies if necessary
if (a_bCopyStrings) {
if (m_bAllowMultiKey || 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() || m_bAllowMultiKey) {
iKey = keyval.insert(typename TKeyVal::value_type(a_pKey, 0));
bInserted = true;
}
iKey->second = a_pValue;
return bInserted ? SI_INSERTED : SI_UPDATED;
}
template<class SI_CHAR, class SI_STRLESS, class SI_CONVERTER>
const SI_CHAR *
CSimpleIniTempl<SI_CHAR,SI_STRLESS,SI_CONVERTER>::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 (IsEqual(a_pKey, iTemp->first)) {
*a_pHasMultiple = true;
}
}
}
return iKeyVal->second;
}
template<class SI_CHAR, class SI_STRLESS, class SI_CONVERTER>
bool
CSimpleIniTempl<SI_CHAR,SI_STRLESS,SI_CONVERTER>::GetAllValues(
const SI_CHAR * a_pSection,
const SI_CHAR * a_pKey,
TNamesDepend & a_values) const
{
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(iKeyVal->second);
if (m_bAllowMultiKey) {
++iKeyVal;
while (iKeyVal != iSection->second.end() && IsEqual(a_pKey, iKeyVal->first)) {
a_values.push_back(iKeyVal->second);
++iKeyVal;
}
}
return true;
}
template<class SI_CHAR, class SI_STRLESS, class SI_CONVERTER>
int
CSimpleIniTempl<SI_CHAR,SI_STRLESS,SI_CONVERTER>::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 = 0;
typename TKeyVal::const_iterator iKeyVal = section.begin();
for (int n = 0; iKeyVal != section.end(); ++iKeyVal, ++n) {
if (!pLastKey || !IsEqual(pLastKey, iKeyVal->first)) {
++nCount;
pLastKey = iKeyVal->first;
}
}
return nCount;
}
template<class SI_CHAR, class SI_STRLESS, class SI_CONVERTER>
const typename CSimpleIniTempl<SI_CHAR,SI_STRLESS,SI_CONVERTER>::TKeyVal *
CSimpleIniTempl<SI_CHAR,SI_STRLESS,SI_CONVERTER>::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<class SI_CHAR, class SI_STRLESS, class SI_CONVERTER>
void
CSimpleIniTempl<SI_CHAR,SI_STRLESS,SI_CONVERTER>::GetAllSections(
TNamesDepend & a_names ) const
{
typename TSection::const_iterator i = m_data.begin();
for (int n = 0; i != m_data.end(); ++i, ++n ) {
a_names.push_back(i->first);
}
}
template<class SI_CHAR, class SI_STRLESS, class SI_CONVERTER>
void
CSimpleIniTempl<SI_CHAR,SI_STRLESS,SI_CONVERTER>::GetAllKeys(
const SI_CHAR * a_pSection,
TNamesDepend & a_names ) const
{
if (!a_pSection) {
return;
}
typename TSection::const_iterator iSection = m_data.find(a_pSection);
if (iSection == m_data.end()) {
return;
}
const TKeyVal & section = iSection->second;
const SI_CHAR * pLastKey = 0;
typename TKeyVal::const_iterator iKeyVal = section.begin();
for (int n = 0; iKeyVal != section.end(); ++iKeyVal, ++n ) {
if (!pLastKey || !IsEqual(pLastKey, iKeyVal->first)) {
a_names.push_back(iKeyVal->first);
pLastKey = iKeyVal->first;
}
}
}
template<class SI_CHAR, class SI_STRLESS, class SI_CONVERTER>
SI_Error
CSimpleIniTempl<SI_CHAR,SI_STRLESS,SI_CONVERTER>::Save(
OutputWriter & a_oOutput) const
{
Converter convert(m_bStoreIsUtf8);
// iterate through our sections and output the data
bool bFirstLine = true;
typename TSection::const_iterator iSection = m_data.begin();
for ( ; iSection != m_data.end(); ++iSection ) {
// write the section (unless there is no section name)
if (iSection->first[0]) {
if (!convert.ConvertToStore(iSection->first)) {
return SI_FAIL;
}
if (!bFirstLine) {
a_oOutput.Write(SI_NEWLINE_A);
}
a_oOutput.Write("[");
a_oOutput.Write(convert.Data());
a_oOutput.Write("]");
a_oOutput.Write(SI_NEWLINE_A);
}
// write all keys and values
typename TKeyVal::const_iterator iKeyVal = iSection->second.begin();
for ( ; iKeyVal != iSection->second.end(); ++iKeyVal) {
// write the key
if (!convert.ConvertToStore(iKeyVal->first)) {
return SI_FAIL;
}
a_oOutput.Write(convert.Data());
// write the value
if (!convert.ConvertToStore(iKeyVal->second)) {
return SI_FAIL;
}
a_oOutput.Write("=");
if (m_bAllowMultiLine && IsMultiLineData(iKeyVal->second)) {
a_oOutput.Write("<<<SI-END-OF-MULTILINE-TEXT" SI_NEWLINE_A);
a_oOutput.Write(convert.Data());
a_oOutput.Write(SI_NEWLINE_A "SI-END-OF-MULTILINE-TEXT");
}
else {
a_oOutput.Write(convert.Data());
}
a_oOutput.Write(SI_NEWLINE_A);
}
bFirstLine = false;
}
return SI_OK;
}
template<class SI_CHAR, class SI_STRLESS, class SI_CONVERTER>
bool
CSimpleIniTempl<SI_CHAR,SI_STRLESS,SI_CONVERTER>::Delete(
const SI_CHAR * a_pSection,
const SI_CHAR * a_pKey,
bool a_bRemoveEmpty)
{
if (!a_pSection) {
return false;
}
typename TSection::iterator iSection = m_data.find(a_pSection);
if (iSection == m_data.end()) {
return false;
}
// 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;
}
// remove any copied strings and then the key
typename TKeyVal::iterator iDelete;
do {
iDelete = iKeyVal++;
DeleteString(iDelete->first);
DeleteString(iDelete->second);
iSection->second.erase(iDelete);
}
while (iKeyVal != iSection->second.end()
&& IsEqual(a_pKey, iKeyVal->first));
// 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);
DeleteString(iKeyVal->second);
}
}
// delete the section itself
DeleteString(iSection->first);
m_data.erase(iSection);
return true;
}
template<class SI_CHAR, class SI_STRLESS, class SI_CONVERTER>
void
CSimpleIniTempl<SI_CHAR,SI_STRLESS,SI_CONVERTER>::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 (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) {
delete[] const_cast<SI_CHAR*>(*i);
m_strings.erase(i);
break;
}
}
}
}
// ---------------------------------------------------------------------------
// CONVERSION FUNCTIONS
// ---------------------------------------------------------------------------
// Defines the conversion classes for different libraries. Before including
// SimpleIni.h, set the converter that you wish you use by defining one of the
// following symbols.
//
// 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)
# ifdef _WIN32
# define SI_CONVERT_WIN32
# else
# define SI_CONVERT_GENERIC
# endif
#endif
/**
* Generic case-sensitive <less> comparison. This class returns numerically
* ordered ASCII case-sensitive text for all possible sizes and types of
* SI_CHAR.
*/
template<class SI_CHAR>
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;
}
};
/**
* Generic ASCII case-insensitive <less> comparison. This class returns
* numerically ordered ASCII case-insensitive text for all possible sizes
* 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<class SI_CHAR>
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_CHAR>
class SI_ConvertA {
bool m_bStoreIsUtf8;
protected:
SI_ConvertA() { }
public:
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;
}
/** 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.
* @param a_uInputDataLen Length of storage format data in bytes. This
* must be the actual length of the data, including
* NULL byte if NULL terminated string is required.
* @return Number of SI_CHAR required by the string when
* converted. If there are embedded NULL bytes in the
* input data, only the string up and not including
* 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)
{
assert(a_uInputDataLen != (size_t) -1);
// ASCII/MBCS/UTF-8 needs no conversion
return a_uInputDataLen;
}
/** 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.
* @param a_uInputDataLen Length of storage format data in bytes. This
* must be the actual length of the data, including
* NULL byte if NULL terminated string is required.
* @param a_pOutputData Pointer to the output buffer to received the
* converted data.
* @param a_uOutputDataSize Size of the output buffer in SI_CHAR.
* @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;
}
/** 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
* bytes required to be converted to storage format.
* @return Number of bytes required by the string when
* converted to storage format. This size always
* 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;
}
/** 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
* the data will be converted including the
* terminating NULL character.
* @param a_pOutputData Pointer to the buffer to receive the converted
* string.
* @param a_pOutputDataSize Size of the output buffer in char.
* @return true if all of the input data, including the
* 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;
}
};
// ---------------------------------------------------------------------------
// SI_CONVERT_GENERIC
// ---------------------------------------------------------------------------
#ifdef SI_CONVERT_GENERIC
#define SI_Case SI_GenericCase
#define SI_NoCase SI_GenericNoCase
#include <wchar.h>
#include "ConvertUTF.h"
/**
* Converts UTF-8 to a wchar_t (or equivalent) using the Unicode reference
* library functions. This can be used on all platforms.
*/
template<class SI_CHAR>
class SI_ConvertW {
bool m_bStoreIsUtf8;
protected:
SI_ConvertW() { }
public:
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;
}
/** 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.
* @param a_uInputDataLen Length of storage format data in bytes. This
* must be the actual length of the data, including
* NULL byte if NULL terminated string is required.
* @return Number of SI_CHAR required by the string when
* converted. If there are embedded NULL bytes in the
* input data, only the string up and not including
* 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)
{
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;
}
else {
return mbstowcs(NULL, a_pInputData, a_uInputDataLen);
}
}
/** 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.
* @param a_uInputDataLen Length of storage format data in bytes. This
* must be the actual length of the data, including
* NULL byte if NULL terminated string is required.
* @param a_pOutputData Pointer to the output buffer to received the
* converted data.
* @param a_uOutputDataSize Size of the output buffer in SI_CHAR.
* @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) {
// 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
// at http://www.unicode.org/Public/PROGRAMS/CVTUTF/
ConversionResult retval;
const UTF8 * pUtf8 = (const UTF8 *) a_pInputData;
if (sizeof(wchar_t) == sizeof(UTF32)) {
UTF32 * pUtf32 = (UTF32 *) a_pOutputData;
retval = ConvertUTF8toUTF32(
&pUtf8, pUtf8 + a_uInputDataLen,
&pUtf32, pUtf32 + a_uOutputDataSize,
lenientConversion);
}
else if (sizeof(wchar_t) == sizeof(UTF16)) {
UTF16 * pUtf16 = (UTF16 *) a_pOutputData;
retval = ConvertUTF8toUTF16(
&pUtf8, pUtf8 + a_uInputDataLen,
&pUtf16, pUtf16 + a_uOutputDataSize,
lenientConversion);
}
return retval == conversionOK;
}
else {
size_t retval = mbstowcs(a_pOutputData,
a_pInputData, a_uOutputDataSize);
return retval != (size_t)(-1);
}
}
/** 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
* bytes required to be converted to storage format.
* @return Number of bytes required by the string when
* converted to storage format. This size always
* 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
}
}
/** 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
* the data will be converted including the
* terminating NULL character.
* @param a_pOutputData Pointer to the buffer to receive the converted
* string.
* @param a_pOutputDataSize Size of the output buffer in char.
* @return true if all of the input data, including the
* terminating NULL character was successfully
* converted.
*/
bool ConvertToStore(
const SI_CHAR * a_pInputData,
char * a_pOutputData,
size_t a_uOutputDataSize)
{
if (m_bStoreIsUtf8) {
// calc input string length (SI_CHAR type and size independent)
size_t uInputLen = 0;
while (a_pInputData[uInputLen]) {
++uInputLen;
}
++uInputLen; // include the NULL char
// 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
// at http://www.unicode.org/Public/PROGRAMS/CVTUTF/
ConversionResult retval;
UTF8 * pUtf8 = (UTF8 *) a_pOutputData;
if (sizeof(wchar_t) == sizeof(UTF32)) {
const UTF32 * pUtf32 = (const UTF32 *) a_pInputData;
retval = ConvertUTF32toUTF8(
&pUtf32, pUtf32 + uInputLen + 1,
&pUtf8, pUtf8 + a_uOutputDataSize,
lenientConversion);
}
else if (sizeof(wchar_t) == sizeof(UTF16)) {
const UTF16 * pUtf16 = (const UTF16 *) a_pInputData;
retval = ConvertUTF16toUTF8(
&pUtf16, pUtf16 + uInputLen + 1,
&pUtf8, pUtf8 + a_uOutputDataSize,
lenientConversion);
}
return retval == conversionOK;
}
else {
size_t retval = wcstombs(a_pOutputData,
a_pInputData, a_uOutputDataSize);
return retval != (size_t) -1;
}
}
};
#endif // SI_CONVERT_GENERIC
// ---------------------------------------------------------------------------
// SI_CONVERT_ICU
// ---------------------------------------------------------------------------
#ifdef SI_CONVERT_ICU
#define SI_Case SI_GenericCase
#define SI_NoCase SI_GenericNoCase
#include <unicode/ucnv.h>
/**
* Converts MBCS/UTF-8 to UChar using ICU. This can be used on all platforms.
*/
template<class SI_CHAR>
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;
}
/* 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.
* @param a_uInputDataLen Length of storage format data in bytes. This
* must be the actual length of the data, including
* NULL byte if NULL terminated string is required.
* @return Number of UChar required by the string when
* converted. If there are embedded NULL bytes in the
* input data, only the string up and not including
* 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)
{
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;
}
}
nError = U_ZERO_ERROR;
ucnv_resetToUnicode(m_pConverter);
int32_t nLen = ucnv_toUChars(m_pConverter, NULL, 0,
a_pInputData, (int32_t) a_uInputDataLen, &nError);
if (nError != U_BUFFER_OVERFLOW_ERROR) {
return (size_t) -1;
}
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.
* @param a_uInputDataLen Length of storage format data in bytes. This
* must be the actual length of the data, including
* NULL byte if NULL terminated string is required.
* @param a_pOutputData Pointer to the output buffer to received the
* converted data.
* @param a_uOutputDataSize Size of the output buffer in UChar.
* @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_resetToUnicode(m_pConverter);
ucnv_toUChars(m_pConverter,
a_pOutputData, (int32_t) a_uOutputDataSize,
a_pInputData, (int32_t) a_uInputDataLen, &nError);
if (U_FAILURE(nError)) {
return false;
}
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
* bytes required to be converted to storage format.
* @return Number of bytes required by the string when
* converted to storage format. This size always
* 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;
}
}
nError = U_ZERO_ERROR;
ucnv_resetFromUnicode(m_pConverter);
int32_t nLen = ucnv_fromUChars(m_pConverter, NULL, 0,
a_pInputData, -1, &nError);
if (nError != U_BUFFER_OVERFLOW_ERROR) {
return (size_t) -1;
}
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
* the data will be converted including the
* terminating NULL character.
* @param a_pOutputData Pointer to the buffer to receive the converted
* string.
* @param a_pOutputDataSize Size of the output buffer in char.
* @return true if all of the input data, including the
* 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_resetFromUnicode(m_pConverter);
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
// Windows CE doesn't have errno or MBCS libraries
#ifdef _WIN32_WCE
# ifndef SI_NO_MBCS
# define SI_NO_MBCS
# endif
#endif
#include <windows.h>
#ifdef SI_NO_MBCS
# define SI_NoCase SI_GenericNoCase
#else // !SI_NO_MBCS
/**
* Case-insensitive comparison class using Win32 MBCS functions. This class
* returns a case-insensitive semi-collation order for MBCS text. It may not
* be safe for UTF-8 text returned in char format as we don't know what
* characters will be folded by the function! Therefore, if you are using
* SI_CHAR == char and SetUnicode(true), then you need to use the generic
* SI_NoCase class instead.
*/
#include <mbstring.h>
template<class SI_CHAR>
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<SI_CHAR>()(pLeft, pRight);
}
};
#endif // SI_NO_MBCS
/**
* Converts MBCS and UTF-8 to a wchar_t (or equivalent) on Windows. This uses
* only the Win32 functions and doesn't require the external Unicode UTF-8
* conversion library. It will not work on Windows 95 without using Microsoft
* Layer for Unicode in your application.
*/
template<class SI_CHAR>
class SI_ConvertW {
UINT m_uCodePage;
protected:
SI_ConvertW() { }
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.
* @param a_uInputDataLen Length of storage format data in bytes. This
* must be the actual length of the data, including
* NULL byte if NULL terminated string is required.
* @return Number of SI_CHAR required by the string when
* converted. If there are embedded NULL bytes in the
* input data, only the string up and not including
* 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)
{
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);
}
/** 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.
* @param a_uInputDataLen Length of storage format data in bytes. This
* must be the actual length of the data, including
* NULL byte if NULL terminated string is required.
* @param a_pOutputData Pointer to the output buffer to received the
* converted data.
* @param a_uOutputDataSize Size of the output buffer in SI_CHAR.
* @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
* data. The storage format is always UTF-8.
*
* @param a_pInputData NULL terminated string to calculate the number of
* bytes required to be converted to storage format.
* @return Number of bytes required by the string when
* converted to storage format. This size always
* 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);
}
/** 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
* the data will be converted including the
* terminating NULL character.
* @param a_pOutputData Pointer to the buffer to receive the converted
* string.
* @param a_pOutputDataSize Size of the output buffer in char.
* @return true if all of the input data, including the
* 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;
}
};
#endif // SI_CONVERT_WIN32
// ---------------------------------------------------------------------------
// TYPE DEFINITIONS
// ---------------------------------------------------------------------------
typedef CSimpleIniTempl<char,
SI_NoCase<char>,SI_ConvertA<char> > CSimpleIniA;
typedef CSimpleIniTempl<char,
SI_Case<char>,SI_ConvertA<char> > CSimpleIniCaseA;
#if defined(SI_CONVERT_ICU)
typedef CSimpleIniTempl<UChar,
SI_NoCase<UChar>,SI_ConvertW<UChar> > CSimpleIniW;
typedef CSimpleIniTempl<UChar,
SI_Case<UChar>,SI_ConvertW<UChar> > CSimpleIniCaseW;
#else
typedef CSimpleIniTempl<wchar_t,
SI_NoCase<wchar_t>,SI_ConvertW<wchar_t> > CSimpleIniW;
typedef CSimpleIniTempl<wchar_t,
SI_Case<wchar_t>,SI_ConvertW<wchar_t> > 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 // INCLUDED_SimpleIni_h