tomwalters@268: /** @mainpage tomwalters@268: tomwalters@268: tomwalters@268:
Library SimpleIni tomwalters@268:
File SimpleIni.h tomwalters@268:
Author Brodie Thiesfield [code at jellycan dot com] tomwalters@268:
Source http://code.jellycan.com/simpleini/ tomwalters@330:
Version 4.13 tomwalters@268:
tomwalters@268: tomwalters@268: Jump to the @link CSimpleIniTempl CSimpleIni @endlink interface documentation. tomwalters@268: tomwalters@268: @section intro INTRODUCTION tomwalters@268: tomwalters@268: This component allows an INI-style configuration file to be used on both tomwalters@268: Windows and Linux/Unix. It is fast, simple and source code using this tomwalters@268: component will compile unchanged on either OS. tomwalters@268: tomwalters@268: tomwalters@268: @section features FEATURES tomwalters@268: tomwalters@268: - MIT Licence allows free use in all software (including GPL and commercial) tomwalters@268: - multi-platform (Windows 95/98/ME/NT/2K/XP/2003, Windows CE, Linux, Unix) tomwalters@268: - loading and saving of INI-style configuration files tomwalters@268: - configuration files can have any newline format on all platforms tomwalters@268: - liberal acceptance of file format tomwalters@268: - key/values with no section tomwalters@268: - removal of whitespace around sections, keys and values tomwalters@268: - support for multi-line values (values with embedded newline characters) tomwalters@268: - optional support for multiple keys with the same name tomwalters@268: - optional case-insensitive sections and keys (for ASCII characters only) tomwalters@268: - saves files with sections and keys in the same order as they were loaded tomwalters@268: - preserves comments on the file, section and keys where possible. tomwalters@268: - supports both char or wchar_t programming interfaces tomwalters@268: - supports both MBCS (system locale) and UTF-8 file encodings tomwalters@268: - system locale does not need to be UTF-8 on Linux/Unix to load UTF-8 file tomwalters@268: - support for non-ASCII characters in section, keys, values and comments tomwalters@268: - support for non-standard character types or file encodings tomwalters@268: via user-written converter classes tomwalters@268: - support for adding/modifying values programmatically tomwalters@268: - compiles cleanly in the following compilers: tomwalters@268: - Windows/VC6 (warning level 3) tomwalters@268: - Windows/VC.NET 2003 (warning level 4) tomwalters@268: - Windows/VC 2005 (warning level 4) tomwalters@268: - Linux/gcc (-Wall) tomwalters@268: tomwalters@268: tomwalters@268: @section usage USAGE SUMMARY tomwalters@268: tomwalters@268: -# Define the appropriate symbol for the converter you wish to use and tomwalters@268: include the SimpleIni.h header file. If no specific converter is defined tomwalters@268: then the default converter is used. The default conversion mode uses tomwalters@268: SI_CONVERT_WIN32 on Windows and SI_CONVERT_GENERIC on all other tomwalters@268: platforms. If you are using ICU then SI_CONVERT_ICU is supported on all tomwalters@268: platforms. tomwalters@268: -# Declare an instance the appropriate class. Note that the following tomwalters@268: definitions are just shortcuts for commonly used types. Other types tomwalters@268: (PRUnichar, unsigned short, unsigned char) are also possible. tomwalters@268: tomwalters@268:
Interface Case-sensitive Load UTF-8 Load MBCS Typedef tomwalters@268:
SI_CONVERT_GENERIC tomwalters@268:
char No Yes Yes #1 CSimpleIniA tomwalters@268:
char Yes Yes Yes CSimpleIniCaseA tomwalters@268:
wchar_t No Yes Yes CSimpleIniW tomwalters@268:
wchar_t Yes Yes Yes CSimpleIniCaseW tomwalters@268:
SI_CONVERT_WIN32 tomwalters@268:
char No No #2 Yes CSimpleIniA tomwalters@268:
char Yes Yes Yes CSimpleIniCaseA tomwalters@268:
wchar_t No Yes Yes CSimpleIniW tomwalters@268:
wchar_t Yes Yes Yes CSimpleIniCaseW tomwalters@268:
SI_CONVERT_ICU tomwalters@268:
char No Yes Yes CSimpleIniA tomwalters@268:
char Yes Yes Yes CSimpleIniCaseA tomwalters@268:
UChar No Yes Yes CSimpleIniW tomwalters@268:
UChar Yes Yes Yes CSimpleIniCaseW tomwalters@268:
tomwalters@268: #1 On Windows you are better to use CSimpleIniA with SI_CONVERT_WIN32.
tomwalters@268: #2 Only affects Windows. On Windows this uses MBCS functions and tomwalters@268: so may fold case incorrectly leading to uncertain results. tomwalters@268: -# Call Load() or LoadFile() to load and parse the INI configuration file tomwalters@268: -# Access and modify the data of the file using the following functions tomwalters@268: tomwalters@268:
GetAllSections Return all section names tomwalters@268:
GetAllKeys Return all key names within a section tomwalters@268:
GetAllValues Return all values within a section & key tomwalters@268:
GetSection Return all key names and values in a section tomwalters@268:
GetSectionSize Return the number of keys in a section tomwalters@268:
GetValue Return a value for a section & key tomwalters@268:
SetValue Add or update a value for a section & key tomwalters@268:
Delete Remove a section, or a key from a section tomwalters@268:
tomwalters@268: -# Call Save() or SaveFile() to save the INI configuration data tomwalters@268: tomwalters@268: @section iostreams IO STREAMS tomwalters@268: tomwalters@268: SimpleIni supports reading from and writing to STL IO streams. Enable this tomwalters@268: by defining SI_SUPPORT_IOSTREAMS before including the SimpleIni.h header tomwalters@268: file. Ensure that if the streams are backed by a file (e.g. ifstream or tomwalters@268: ofstream) then the flag ios_base::binary has been used when the file was tomwalters@268: opened. tomwalters@268: tomwalters@268: @section multiline MULTI-LINE VALUES tomwalters@268: tomwalters@268: Values that span multiple lines are created using the following format. tomwalters@268: tomwalters@268:
tomwalters@268:         key = <<
tomwalters@268: 
tomwalters@268:     Note the following:
tomwalters@268:     - The text used for ENDTAG can be anything and is used to find
tomwalters@268:       where the multi-line text ends.
tomwalters@268:     - The newline after ENDTAG in the start tag, and the newline
tomwalters@268:       before ENDTAG in the end tag is not included in the data value.
tomwalters@268:     - The ending tag must be on it's own line with no whitespace before
tomwalters@268:       or after it.
tomwalters@268:     - The multi-line value is modified at load so that each line in the value
tomwalters@268:       is delimited by a single '\\n' character on all platforms. At save time
tomwalters@268:       it will be converted into the newline format used by the current
tomwalters@268:       platform.
tomwalters@268: 
tomwalters@268:     @section comments COMMENTS
tomwalters@268: 
tomwalters@268:     Comments are preserved in the file within the following restrictions:
tomwalters@268:     - Every file may have a single "file comment". It must start with the
tomwalters@268:       first character in the file, and will end with the first non-comment
tomwalters@268:       line in the file.
tomwalters@268:     - Every section may have a single "section comment". It will start
tomwalters@268:       with the first comment line following the file comment, or the last
tomwalters@268:       data entry. It ends at the beginning of the section.
tomwalters@268:     - Every key may have a single "key comment". This comment will start
tomwalters@268:       with the first comment line following the section start, or the file
tomwalters@268:       comment if there is no section name.
tomwalters@268:     - Comments are set at the time that the file, section or key is first
tomwalters@268:       created. The only way to modify a comment on a section or a key is to
tomwalters@268:       delete that entry and recreate it with the new comment. There is no
tomwalters@268:       way to change the file comment.
tomwalters@268: 
tomwalters@268:     @section save SAVE ORDER
tomwalters@268: 
tomwalters@268:     The sections and keys are written out in the same order as they were
tomwalters@268:     read in from the file. Sections and keys added to the data after the
tomwalters@268:     file has been loaded will be added to the end of the file when it is
tomwalters@268:     written. There is no way to specify the location of a section or key
tomwalters@268:     other than in first-created, first-saved order.
tomwalters@268: 
tomwalters@268:     @section notes NOTES
tomwalters@268: 
tomwalters@268:     - To load UTF-8 data on Windows 95, you need to use Microsoft Layer for
tomwalters@268:       Unicode, or SI_CONVERT_GENERIC, or SI_CONVERT_ICU.
tomwalters@268:     - When using SI_CONVERT_GENERIC, ConvertUTF.c must be compiled and linked.
tomwalters@268:     - When using SI_CONVERT_ICU, ICU header files must be on the include
tomwalters@268:       path and icuuc.lib must be linked in.
tomwalters@268:     - To load a UTF-8 file on Windows AND expose it with SI_CHAR == char,
tomwalters@268:       you should use SI_CONVERT_GENERIC.
tomwalters@268:     - The collation (sorting) order used for sections and keys returned from
tomwalters@268:       iterators is NOT DEFINED. If collation order of the text is important
tomwalters@268:       then it should be done yourself by either supplying a replacement
tomwalters@268:       SI_STRLESS class, or by sorting the strings external to this library.
tomwalters@268:     - Usage of the  header on Windows can be disabled by defining
tomwalters@268:       SI_NO_MBCS. This is defined automatically on Windows CE platforms.
tomwalters@268: 
tomwalters@268: 
tomwalters@268:     @section licence MIT LICENCE
tomwalters@268: 
tomwalters@268:     The licence text below is the boilerplate "MIT Licence" used from:
tomwalters@268:     http://www.opensource.org/licenses/mit-license.php
tomwalters@268: 
tomwalters@268:     Copyright (c) 2006-2008, Brodie Thiesfield
tomwalters@268: 
tomwalters@268:     Permission is hereby granted, free of charge, to any person obtaining a copy
tomwalters@268:     of this software and associated documentation files (the "Software"), to deal
tomwalters@268:     in the Software without restriction, including without limitation the rights
tomwalters@268:     to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
tomwalters@268:     copies of the Software, and to permit persons to whom the Software is furnished
tomwalters@268:     to do so, subject to the following conditions:
tomwalters@268: 
tomwalters@268:     The above copyright notice and this permission notice shall be included in
tomwalters@268:     all copies or substantial portions of the Software.
tomwalters@268: 
tomwalters@268:     THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
tomwalters@268:     IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
tomwalters@268:     FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
tomwalters@268:     COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
tomwalters@268:     IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
tomwalters@268:     CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
tomwalters@268: */
tomwalters@268: 
tomwalters@268: #ifndef INCLUDED_SimpleIni_h
tomwalters@268: #define INCLUDED_SimpleIni_h
tomwalters@268: 
tomwalters@268: #if defined(_MSC_VER) && (_MSC_VER >= 1020)
tomwalters@268: # pragma once
tomwalters@268: #endif
tomwalters@268: 
tomwalters@268: // Disable these warnings in MSVC:
tomwalters@268: //  4127 "conditional expression is constant" as the conversion classes trigger
tomwalters@268: //  it with the statement if (sizeof(SI_CHAR) == sizeof(char)). This test will
tomwalters@268: //  be optimized away in a release build.
tomwalters@268: //  4503 'insert' : decorated name length exceeded, name was truncated
tomwalters@268: //  4702 "unreachable code" as the MS STL header causes it in release mode.
tomwalters@268: //  Again, the code causing the warning will be cleaned up by the compiler.
tomwalters@268: //  4786 "identifier truncated to 256 characters" as this is thrown hundreds
tomwalters@268: //  of times VC6 as soon as STL is used.
tomwalters@268: #ifdef _MSC_VER
tomwalters@268: # pragma warning (push)
tomwalters@268: # pragma warning (disable: 4127 4503 4702 4786)
tomwalters@268: #endif
tomwalters@268: 
tomwalters@268: #include 
tomwalters@268: #include 
tomwalters@268: #include 
tomwalters@268: #include 
tomwalters@268: #include 
tomwalters@268: #include 
tomwalters@268: 
tomwalters@268: #ifdef SI_SUPPORT_IOSTREAMS
tomwalters@268: # include 
tomwalters@268: #endif // SI_SUPPORT_IOSTREAMS
tomwalters@268: 
tomwalters@268: #ifdef _DEBUG
tomwalters@268: # ifndef assert
tomwalters@268: #  include 
tomwalters@268: # endif
tomwalters@268: # define SI_ASSERT(x)   assert(x)
tomwalters@268: #else
tomwalters@268: # define SI_ASSERT(x)
tomwalters@268: #endif
tomwalters@268: 
tomwalters@268: enum SI_Error {
tomwalters@268:     SI_OK       =  0,   //!< No error
tomwalters@268:     SI_UPDATED  =  1,   //!< An existing value was updated
tomwalters@268:     SI_INSERTED =  2,   //!< A new value was inserted
tomwalters@268: 
tomwalters@268:     // note: test for any error with (retval < 0)
tomwalters@268:     SI_FAIL     = -1,   //!< Generic failure
tomwalters@268:     SI_NOMEM    = -2,   //!< Out of memory error
tomwalters@268:     SI_FILE     = -3    //!< File error (see errno for detail error)
tomwalters@268: };
tomwalters@268: 
tomwalters@268: #define SI_UTF8_SIGNATURE     "\xEF\xBB\xBF"
tomwalters@268: 
tomwalters@268: #ifdef _WIN32
tomwalters@268: # define SI_NEWLINE_A   "\r\n"
tomwalters@268: # define SI_NEWLINE_W   L"\r\n"
tomwalters@268: #else // !_WIN32
tomwalters@268: # define SI_NEWLINE_A   "\n"
tomwalters@268: # define SI_NEWLINE_W   L"\n"
tomwalters@268: #endif // _WIN32
tomwalters@268: 
tomwalters@268: #if defined(SI_CONVERT_ICU)
tomwalters@268: # include 
tomwalters@268: #endif
tomwalters@268: 
tomwalters@268: #if defined(_WIN32)
tomwalters@268: # define SI_HAS_WIDE_FILE
tomwalters@268: # define SI_WCHAR_T     wchar_t
tomwalters@268: #elif defined(SI_CONVERT_ICU)
tomwalters@268: # define SI_HAS_WIDE_FILE
tomwalters@268: # define SI_WCHAR_T     UChar
tomwalters@268: #endif
tomwalters@268: 
tomwalters@268: 
tomwalters@268: // ---------------------------------------------------------------------------
tomwalters@268: //                              MAIN TEMPLATE CLASS
tomwalters@268: // ---------------------------------------------------------------------------
tomwalters@268: 
tomwalters@268: /** Simple INI file reader.
tomwalters@268: 
tomwalters@268:     This can be instantiated with the choice of unicode or native characterset,
tomwalters@268:     and case sensitive or insensitive comparisons of section and key names.
tomwalters@268:     The supported combinations are pre-defined with the following typedefs:
tomwalters@268: 
tomwalters@268:     
tomwalters@268:         
Interface Case-sensitive Typedef tomwalters@268:
char No CSimpleIniA tomwalters@268:
char Yes CSimpleIniCaseA tomwalters@268:
wchar_t No CSimpleIniW tomwalters@268:
wchar_t Yes CSimpleIniCaseW tomwalters@268:
tomwalters@268: tomwalters@268: Note that using other types for the SI_CHAR is supported. For instance, tomwalters@268: unsigned char, unsigned short, etc. Note that where the alternative type tomwalters@268: is a different size to char/wchar_t you may need to supply new helper tomwalters@268: classes for SI_STRLESS and SI_CONVERTER. tomwalters@268: */ tomwalters@268: template tomwalters@268: class CSimpleIniTempl tomwalters@268: { tomwalters@268: public: tomwalters@268: /** key entry */ tomwalters@268: struct Entry { tomwalters@268: const SI_CHAR * pItem; tomwalters@268: const SI_CHAR * pComment; tomwalters@268: int nOrder; tomwalters@268: tomwalters@268: Entry(const SI_CHAR * a_pszItem = NULL, int a_nOrder = 0) tomwalters@268: : pItem(a_pszItem) tomwalters@268: , pComment(NULL) tomwalters@268: , nOrder(a_nOrder) tomwalters@268: { } tomwalters@268: Entry(const SI_CHAR * a_pszItem, const SI_CHAR * a_pszComment, int a_nOrder) tomwalters@268: : pItem(a_pszItem) tomwalters@268: , pComment(a_pszComment) tomwalters@268: , nOrder(a_nOrder) tomwalters@268: { } tomwalters@268: Entry(const Entry & rhs) { operator=(rhs); } tomwalters@268: Entry & operator=(const Entry & rhs) { tomwalters@268: pItem = rhs.pItem; tomwalters@268: pComment = rhs.pComment; tomwalters@268: nOrder = rhs.nOrder; tomwalters@268: return *this; tomwalters@268: } tomwalters@268: tomwalters@268: #if defined(_MSC_VER) && _MSC_VER <= 1200 tomwalters@268: /** STL of VC6 doesn't allow me to specify my own comparator for list::sort() */ tomwalters@268: bool operator<(const Entry & rhs) const { return LoadOrder()(*this, rhs); } tomwalters@268: bool operator>(const Entry & rhs) const { return LoadOrder()(rhs, *this); } tomwalters@268: #endif tomwalters@268: tomwalters@268: /** Strict less ordering by name of key only */ tomwalters@268: struct KeyOrder : std::binary_function { tomwalters@268: bool operator()(const Entry & lhs, const Entry & rhs) const { tomwalters@268: const static SI_STRLESS isLess = SI_STRLESS(); tomwalters@268: return isLess(lhs.pItem, rhs.pItem); tomwalters@268: } tomwalters@268: }; tomwalters@268: tomwalters@268: /** Strict less ordering by order, and then name of key */ tomwalters@268: struct LoadOrder : std::binary_function { tomwalters@268: bool operator()(const Entry & lhs, const Entry & rhs) const { tomwalters@268: if (lhs.nOrder != rhs.nOrder) { tomwalters@268: return lhs.nOrder < rhs.nOrder; tomwalters@268: } tomwalters@268: return KeyOrder()(lhs.pItem, rhs.pItem); tomwalters@268: } tomwalters@268: }; tomwalters@268: }; tomwalters@268: tomwalters@268: /** map keys to values */ tomwalters@268: typedef std::multimap TKeyVal; tomwalters@268: tomwalters@268: /** map sections to key/value map */ tomwalters@268: typedef std::map TSection; tomwalters@268: tomwalters@268: /** set of dependent string pointers. Note that these pointers are tomwalters@268: dependent on memory owned by CSimpleIni. tomwalters@268: */ tomwalters@268: typedef std::list TNamesDepend; tomwalters@268: tomwalters@268: /** interface definition for the OutputWriter object to pass to Save() tomwalters@268: in order to output the INI file data. tomwalters@268: */ tomwalters@268: class OutputWriter { tomwalters@268: public: tomwalters@268: OutputWriter() { } tomwalters@268: virtual ~OutputWriter() { } tomwalters@268: virtual void Write(const char * a_pBuf) = 0; tomwalters@268: private: tomwalters@268: OutputWriter(const OutputWriter &); // disable tomwalters@268: OutputWriter & operator=(const OutputWriter &); // disable tomwalters@268: }; tomwalters@268: tomwalters@268: /** OutputWriter class to write the INI data to a file */ tomwalters@268: class FileWriter : public OutputWriter { tomwalters@268: FILE * m_file; tomwalters@268: public: tomwalters@268: FileWriter(FILE * a_file) : m_file(a_file) { } tomwalters@268: void Write(const char * a_pBuf) { tomwalters@268: fputs(a_pBuf, m_file); tomwalters@268: } tomwalters@268: private: tomwalters@268: FileWriter(const FileWriter &); // disable tomwalters@268: FileWriter & operator=(const FileWriter &); // disable tomwalters@268: }; tomwalters@268: tomwalters@268: /** OutputWriter class to write the INI data to a string */ tomwalters@268: class StringWriter : public OutputWriter { tomwalters@268: std::string & m_string; tomwalters@268: public: tomwalters@268: StringWriter(std::string & a_string) : m_string(a_string) { } tomwalters@268: void Write(const char * a_pBuf) { tomwalters@268: m_string.append(a_pBuf); tomwalters@268: } tomwalters@268: private: tomwalters@268: StringWriter(const StringWriter &); // disable tomwalters@268: StringWriter & operator=(const StringWriter &); // disable tomwalters@268: }; tomwalters@268: tomwalters@268: #ifdef SI_SUPPORT_IOSTREAMS tomwalters@268: /** OutputWriter class to write the INI data to an ostream */ tomwalters@268: class StreamWriter : public OutputWriter { tomwalters@268: std::ostream & m_ostream; tomwalters@268: public: tomwalters@268: StreamWriter(std::ostream & a_ostream) : m_ostream(a_ostream) { } tomwalters@268: void Write(const char * a_pBuf) { tomwalters@268: m_ostream << a_pBuf; tomwalters@268: } tomwalters@268: private: tomwalters@268: StreamWriter(const StreamWriter &); // disable tomwalters@268: StreamWriter & operator=(const StreamWriter &); // disable tomwalters@268: }; tomwalters@268: #endif // SI_SUPPORT_IOSTREAMS tomwalters@268: tomwalters@268: /** Characterset conversion utility class to convert strings to the tomwalters@268: same format as is used for the storage. tomwalters@268: */ tomwalters@268: class Converter : private SI_CONVERTER { tomwalters@268: public: tomwalters@268: Converter(bool a_bStoreIsUtf8) : SI_CONVERTER(a_bStoreIsUtf8) { tomwalters@268: m_scratch.resize(1024); tomwalters@268: } tomwalters@268: Converter(const Converter & rhs) { operator=(rhs); } tomwalters@268: Converter & operator=(const Converter & rhs) { tomwalters@268: m_scratch = rhs.m_scratch; tomwalters@268: return *this; tomwalters@268: } tomwalters@268: bool ConvertToStore(const SI_CHAR * a_pszString) { sness@613: size_t uLen = this->SizeToStore(a_pszString); tomwalters@268: if (uLen == (size_t)(-1)) { tomwalters@268: return false; tomwalters@268: } tomwalters@268: while (uLen > m_scratch.size()) { tomwalters@268: m_scratch.resize(m_scratch.size() * 2); tomwalters@268: } tomwalters@268: return SI_CONVERTER::ConvertToStore( tomwalters@268: a_pszString, tomwalters@268: const_cast(m_scratch.data()), tomwalters@268: m_scratch.size()); tomwalters@268: } tomwalters@268: const char * Data() { return m_scratch.data(); } tomwalters@268: private: tomwalters@268: std::string m_scratch; tomwalters@268: }; tomwalters@268: tomwalters@268: public: tomwalters@268: /*-----------------------------------------------------------------------*/ tomwalters@268: tomwalters@268: /** Default constructor. tomwalters@268: tomwalters@268: @param a_bIsUtf8 See the method SetUnicode() for details. tomwalters@268: @param a_bMultiKey See the method SetMultiKey() for details. tomwalters@268: @param a_bMultiLine See the method SetMultiLine() for details. tomwalters@268: */ tomwalters@268: CSimpleIniTempl( tomwalters@268: bool a_bIsUtf8 = false, tomwalters@268: bool a_bMultiKey = false, tomwalters@268: bool a_bMultiLine = false tomwalters@268: ); tomwalters@268: tomwalters@268: /** Destructor */ tomwalters@268: ~CSimpleIniTempl(); tomwalters@268: tomwalters@268: /** Deallocate all memory stored by this object */ tomwalters@268: void Reset(); tomwalters@268: tomwalters@268: /*-----------------------------------------------------------------------*/ tomwalters@268: /** @{ @name Settings */ tomwalters@268: tomwalters@268: /** Set the storage format of the INI data. This affects both the loading tomwalters@268: and saving of the INI data using all of the Load/Save API functions. tomwalters@268: This value cannot be changed after any INI data has been loaded. tomwalters@268: tomwalters@268: If the file is not set to Unicode (UTF-8), then the data encoding is tomwalters@268: assumed to be the OS native encoding. This encoding is the system tomwalters@268: locale on Linux/Unix and the legacy MBCS encoding on Windows NT/2K/XP. tomwalters@268: If the storage format is set to Unicode then the file will be loaded tomwalters@268: as UTF-8 encoded data regardless of the native file encoding. If tomwalters@268: SI_CHAR == char then all of the char* parameters take and return UTF-8 tomwalters@268: encoded data regardless of the system locale. tomwalters@268: tomwalters@268: \param a_bIsUtf8 Assume UTF-8 encoding for the source? tomwalters@268: */ tomwalters@268: void SetUnicode(bool a_bIsUtf8 = true) { tomwalters@268: if (!m_pData) m_bStoreIsUtf8 = a_bIsUtf8; tomwalters@268: } tomwalters@268: tomwalters@268: /** Get the storage format of the INI data. */ tomwalters@268: bool IsUnicode() const { return m_bStoreIsUtf8; } tomwalters@268: tomwalters@268: /** Should multiple identical keys be permitted in the file. If set to false tomwalters@268: then the last value encountered will be used as the value of the key. tomwalters@268: If set to true, then all values will be available to be queried. For tomwalters@268: example, with the following input: tomwalters@268: tomwalters@268:
tomwalters@268:         [section]
tomwalters@268:         test=value1
tomwalters@268:         test=value2
tomwalters@268:         
tomwalters@268: tomwalters@268: Then with SetMultiKey(true), both of the values "value1" and "value2" tomwalters@268: will be returned for the key test. If SetMultiKey(false) is used, then tomwalters@268: the value for "test" will only be "value2". This value may be changed tomwalters@268: at any time. tomwalters@268: tomwalters@268: \param a_bAllowMultiKey Allow multi-keys in the source? tomwalters@268: */ tomwalters@268: void SetMultiKey(bool a_bAllowMultiKey = true) { tomwalters@268: m_bAllowMultiKey = a_bAllowMultiKey; tomwalters@268: } tomwalters@268: tomwalters@268: /** Get the storage format of the INI data. */ tomwalters@268: bool IsMultiKey() const { return m_bAllowMultiKey; } tomwalters@268: tomwalters@268: /** Should data values be permitted to span multiple lines in the file. If tomwalters@268: set to false then the multi-line construct << tomwalters@268: SI_CHAR FORMAT tomwalters@268: char same format as when loaded (MBCS or UTF-8) tomwalters@268: wchar_t UTF-8 tomwalters@268: other UTF-8 tomwalters@268: tomwalters@268: tomwalters@268: Note that comments from the original data is preserved as per the tomwalters@268: documentation on comments. The order of the sections and values tomwalters@268: from the original file will be preserved. tomwalters@268: tomwalters@268: Any data prepended or appended to the output device must use the the tomwalters@268: same format (MBCS or UTF-8). You may use the GetConverter() method to tomwalters@268: convert text to the correct format regardless of the output format tomwalters@268: being used by SimpleIni. tomwalters@268: tomwalters@268: To add a BOM to UTF-8 data, write it out manually at the very beginning tomwalters@268: like is done in SaveFile when a_bUseBOM is true. tomwalters@268: tomwalters@268: @param a_oOutput Output writer to write the data to. tomwalters@268: tomwalters@268: @param a_bAddSignature Prepend the UTF-8 BOM if the output data is in tomwalters@268: UTF-8 format. If it is not UTF-8 then this value is tomwalters@268: ignored. Do not set this to true if anything has tomwalters@268: already been written to the OutputWriter. tomwalters@268: tomwalters@268: @return SI_Error See error definitions tomwalters@268: */ tomwalters@268: SI_Error Save( tomwalters@268: OutputWriter & a_oOutput, tomwalters@268: bool a_bAddSignature = false tomwalters@268: ) const; tomwalters@268: tomwalters@268: #ifdef SI_SUPPORT_IOSTREAMS tomwalters@268: /** Save the INI data to an ostream. See Save() for details. tomwalters@268: tomwalters@268: @param a_ostream String to have the INI data appended to. tomwalters@268: tomwalters@268: @param a_bAddSignature Prepend the UTF-8 BOM if the output data is in tomwalters@268: UTF-8 format. If it is not UTF-8 then this value is tomwalters@268: ignored. Do not set this to true if anything has tomwalters@268: already been written to the stream. tomwalters@268: tomwalters@268: @return SI_Error See error definitions tomwalters@268: */ tomwalters@268: SI_Error Save( tomwalters@268: std::ostream & a_ostream, tomwalters@268: bool a_bAddSignature = false tomwalters@268: ) const tomwalters@268: { tomwalters@268: StreamWriter writer(a_ostream); tomwalters@268: return Save(writer, a_bAddSignature); tomwalters@268: } tomwalters@268: #endif // SI_SUPPORT_IOSTREAMS tomwalters@268: tomwalters@268: /** Append the INI data to a string. See Save() for details. tomwalters@268: tomwalters@268: @param a_sBuffer String to have the INI data appended to. tomwalters@268: tomwalters@268: @param a_bAddSignature Prepend the UTF-8 BOM if the output data is in tomwalters@268: UTF-8 format. If it is not UTF-8 then this value is tomwalters@268: ignored. Do not set this to true if anything has tomwalters@268: already been written to the string. tomwalters@268: tomwalters@268: @return SI_Error See error definitions tomwalters@268: */ tomwalters@268: SI_Error Save( tomwalters@268: std::string & a_sBuffer, tomwalters@268: bool a_bAddSignature = false tomwalters@268: ) const tomwalters@268: { tomwalters@268: StringWriter writer(a_sBuffer); tomwalters@268: return Save(writer, a_bAddSignature); tomwalters@268: } tomwalters@268: tomwalters@268: /*-----------------------------------------------------------------------*/ tomwalters@268: /** @} tomwalters@268: @{ @name Accessing INI Data */ tomwalters@268: tomwalters@268: /** Retrieve all section names. The list is returned as an STL vector of tomwalters@268: names and can be iterated or searched as necessary. Note that the tomwalters@268: sort order of the returned strings is NOT DEFINED. You can sort tomwalters@268: the names into the load order if desired. Search this file for ".sort" tomwalters@268: for an example. tomwalters@268: tomwalters@268: NOTE! This structure contains only pointers to strings. The actual tomwalters@268: string data is stored in memory owned by CSimpleIni. Ensure that the tomwalters@268: CSimpleIni object is not destroyed or Reset() while these pointers tomwalters@268: are in use! tomwalters@268: tomwalters@268: @param a_names Vector that will receive all of the section tomwalters@268: names. See note above! tomwalters@268: */ tomwalters@268: void GetAllSections( tomwalters@268: TNamesDepend & a_names tomwalters@268: ) const; tomwalters@268: tomwalters@268: /** Retrieve all unique key names in a section. The sort order of the tomwalters@268: returned strings is NOT DEFINED. You can sort the names into the load tomwalters@268: order if desired. Search this file for ".sort" for an example. Only tomwalters@268: unique key names are returned. tomwalters@268: tomwalters@268: NOTE! This structure contains only pointers to strings. The actual tomwalters@268: string data is stored in memory owned by CSimpleIni. Ensure that the tomwalters@268: CSimpleIni object is not destroyed or Reset() while these strings tomwalters@268: are in use! tomwalters@268: tomwalters@268: @param a_pSection Section to request data for tomwalters@268: @param a_names List that will receive all of the key tomwalters@268: names. See note above! tomwalters@268: tomwalters@268: @return true Section was found. tomwalters@268: @return false Matching section was not found. tomwalters@268: */ tomwalters@268: bool GetAllKeys( tomwalters@268: const SI_CHAR * a_pSection, tomwalters@268: TNamesDepend & a_names tomwalters@268: ) const; tomwalters@268: tomwalters@268: /** Retrieve all values for a specific key. This method can be used when tomwalters@268: multiple keys are both enabled and disabled. Note that the sort order tomwalters@268: of the returned strings is NOT DEFINED. You can sort the names into tomwalters@268: the load order if desired. Search this file for ".sort" for an example. tomwalters@268: tomwalters@268: NOTE! The returned values are pointers to string data stored in memory tomwalters@268: owned by CSimpleIni. Ensure that the CSimpleIni object is not destroyed tomwalters@268: or Reset while you are using this pointer! tomwalters@268: tomwalters@268: @param a_pSection Section to search tomwalters@268: @param a_pKey Key to search for tomwalters@268: @param a_values List to return if the key is not found tomwalters@268: tomwalters@268: @return true Key was found. tomwalters@268: @return false Matching section/key was not found. tomwalters@268: */ tomwalters@268: bool GetAllValues( tomwalters@268: const SI_CHAR * a_pSection, tomwalters@268: const SI_CHAR * a_pKey, tomwalters@268: TNamesDepend & a_values tomwalters@268: ) const; tomwalters@268: tomwalters@268: /** Query the number of keys in a specific section. Note that if multiple tomwalters@268: keys are enabled, then this value may be different to the number of tomwalters@268: keys returned by GetAllKeys. tomwalters@268: tomwalters@268: @param a_pSection Section to request data for tomwalters@268: tomwalters@268: @return -1 Section does not exist in the file tomwalters@268: @return >=0 Number of keys in the section tomwalters@268: */ tomwalters@268: int GetSectionSize( tomwalters@268: const SI_CHAR * a_pSection tomwalters@268: ) const; tomwalters@268: tomwalters@268: /** Retrieve all key and value pairs for a section. The data is returned tomwalters@268: as a pointer to an STL map and can be iterated or searched as tomwalters@268: desired. Note that multiple entries for the same key may exist when tomwalters@268: multiple keys have been enabled. tomwalters@268: tomwalters@268: NOTE! This structure contains only pointers to strings. The actual tomwalters@268: string data is stored in memory owned by CSimpleIni. Ensure that the tomwalters@268: CSimpleIni object is not destroyed or Reset() while these strings tomwalters@268: are in use! tomwalters@268: tomwalters@268: @param a_pSection Name of the section to return tomwalters@268: @return boolean Was a section matching the supplied tomwalters@268: name found. tomwalters@268: */ tomwalters@268: const TKeyVal * GetSection( tomwalters@268: const SI_CHAR * a_pSection tomwalters@268: ) const; tomwalters@268: tomwalters@268: /** Retrieve the value for a specific key. If multiple keys are enabled tomwalters@268: (see SetMultiKey) then only the first value associated with that key tomwalters@268: will be returned, see GetAllValues for getting all values with multikey. tomwalters@268: tomwalters@268: NOTE! The returned value is a pointer to string data stored in memory tomwalters@268: owned by CSimpleIni. Ensure that the CSimpleIni object is not destroyed tomwalters@268: or Reset while you are using this pointer! tomwalters@268: tomwalters@268: @param a_pSection Section to search tomwalters@268: @param a_pKey Key to search for tomwalters@268: @param a_pDefault Value to return if the key is not found tomwalters@268: @param a_pHasMultiple Optionally receive notification of if there are tomwalters@268: multiple entries for this key. tomwalters@268: tomwalters@268: @return a_pDefault Key was not found in the section tomwalters@268: @return other Value of the key tomwalters@268: */ tomwalters@268: const SI_CHAR * GetValue( tomwalters@268: const SI_CHAR * a_pSection, tomwalters@268: const SI_CHAR * a_pKey, tomwalters@268: const SI_CHAR * a_pDefault = NULL, tomwalters@268: bool * a_pHasMultiple = NULL tomwalters@268: ) const; tomwalters@268: tomwalters@268: /** Retrieve a numeric value for a specific key. If multiple keys are enabled tomwalters@268: (see SetMultiKey) then only the first value associated with that key tomwalters@268: will be returned, see GetAllValues for getting all values with multikey. tomwalters@268: tomwalters@268: @param a_pSection Section to search tomwalters@268: @param a_pKey Key to search for tomwalters@268: @param a_nDefault Value to return if the key is not found tomwalters@268: @param a_pHasMultiple Optionally receive notification of if there are tomwalters@268: multiple entries for this key. tomwalters@268: tomwalters@268: @return a_nDefault Key was not found in the section tomwalters@268: @return other Value of the key tomwalters@268: */ tomwalters@268: long GetLongValue( tomwalters@268: const SI_CHAR * a_pSection, tomwalters@268: const SI_CHAR * a_pKey, tomwalters@268: long a_nDefault = 0, tomwalters@268: bool * a_pHasMultiple = NULL tomwalters@268: ) const; tomwalters@268: tomwalters@268: /** Retrieve a boolean value for a specific key. If multiple keys are enabled tomwalters@268: (see SetMultiKey) then only the first value associated with that key tomwalters@268: will be returned, see GetAllValues for getting all values with multikey. tomwalters@268: tomwalters@268: Strings starting with "t", "y", "on" or "1" are returned as logically true. tomwalters@268: Strings starting with "f", "n", "of" or "0" are returned as logically false. tomwalters@268: For all other values the default is returned. Character comparisons are tomwalters@268: case-insensitive. tomwalters@268: tomwalters@268: @param a_pSection Section to search tomwalters@268: @param a_pKey Key to search for tomwalters@268: @param a_bDefault Value to return if the key is not found tomwalters@268: @param a_pHasMultiple Optionally receive notification of if there are tomwalters@268: multiple entries for this key. tomwalters@268: tomwalters@268: @return a_nDefault Key was not found in the section tomwalters@268: @return other Value of the key tomwalters@268: */ tomwalters@268: bool GetBoolValue( tomwalters@268: const SI_CHAR * a_pSection, tomwalters@268: const SI_CHAR * a_pKey, tomwalters@268: bool a_bDefault = false, tomwalters@268: bool * a_pHasMultiple = NULL tomwalters@268: ) const; tomwalters@268: tomwalters@268: /** Add or update a section or value. This will always insert tomwalters@268: when multiple keys are enabled. tomwalters@268: tomwalters@268: @param a_pSection Section to add or update tomwalters@268: @param a_pKey Key to add or update. Set to NULL to tomwalters@268: create an empty section. tomwalters@268: @param a_pValue Value to set. Set to NULL to create an tomwalters@268: empty section. tomwalters@268: @param a_pComment Comment to be associated with the section or the tomwalters@268: key. If a_pKey is NULL then it will be associated tomwalters@268: with the section, otherwise the key. Note that a tomwalters@268: comment may be set ONLY when the section or key is tomwalters@268: first created (i.e. when this function returns the tomwalters@268: value SI_INSERTED). If you wish to create a section tomwalters@268: with a comment then you need to create the section tomwalters@268: separately to the key. The comment string must be tomwalters@268: in full comment form already (have a comment tomwalters@268: character starting every line). tomwalters@268: @param a_bForceReplace Should all existing values in a multi-key INI tomwalters@268: file be replaced with this entry. This option has tomwalters@268: no effect if not using multi-key files. The tomwalters@268: difference between Delete/SetValue and SetValue tomwalters@268: with a_bForceReplace = true, is that the load tomwalters@268: order and comment will be preserved this way. tomwalters@268: tomwalters@268: @return SI_Error See error definitions tomwalters@268: @return SI_UPDATED Value was updated tomwalters@268: @return SI_INSERTED Value was inserted tomwalters@268: */ tomwalters@268: SI_Error SetValue( tomwalters@268: const SI_CHAR * a_pSection, tomwalters@268: const SI_CHAR * a_pKey, tomwalters@268: const SI_CHAR * a_pValue, tomwalters@268: const SI_CHAR * a_pComment = NULL, tomwalters@268: bool a_bForceReplace = false tomwalters@268: ) tomwalters@268: { tomwalters@268: return AddEntry(a_pSection, a_pKey, a_pValue, a_pComment, a_bForceReplace, true); tomwalters@268: } tomwalters@268: tomwalters@268: /** Add or update a numeric value. This will always insert tomwalters@268: when multiple keys are enabled. tomwalters@268: tomwalters@268: @param a_pSection Section to add or update tomwalters@268: @param a_pKey Key to add or update. tomwalters@268: @param a_nValue Value to set. tomwalters@268: @param a_pComment Comment to be associated with the key. See the tomwalters@268: notes on SetValue() for comments. tomwalters@268: @param a_bUseHex By default the value will be written to the file tomwalters@268: in decimal format. Set this to true to write it tomwalters@268: as hexadecimal. tomwalters@268: @param a_bForceReplace Should all existing values in a multi-key INI tomwalters@268: file be replaced with this entry. This option has tomwalters@268: no effect if not using multi-key files. The tomwalters@268: difference between Delete/SetLongValue and tomwalters@268: SetLongValue with a_bForceReplace = true, is that tomwalters@268: the load order and comment will be preserved this tomwalters@268: way. tomwalters@268: tomwalters@268: @return SI_Error See error definitions tomwalters@268: @return SI_UPDATED Value was updated tomwalters@268: @return SI_INSERTED Value was inserted tomwalters@268: */ tomwalters@268: SI_Error SetLongValue( tomwalters@268: const SI_CHAR * a_pSection, tomwalters@268: const SI_CHAR * a_pKey, tomwalters@268: long a_nValue, tomwalters@268: const SI_CHAR * a_pComment = NULL, tomwalters@268: bool a_bUseHex = false, tomwalters@268: bool a_bForceReplace = false tomwalters@268: ); tomwalters@268: tomwalters@268: /** Add or update a boolean value. This will always insert tomwalters@268: when multiple keys are enabled. tomwalters@268: tomwalters@268: @param a_pSection Section to add or update tomwalters@268: @param a_pKey Key to add or update. tomwalters@268: @param a_bValue Value to set. tomwalters@268: @param a_pComment Comment to be associated with the key. See the tomwalters@268: notes on SetValue() for comments. tomwalters@268: @param a_bForceReplace Should all existing values in a multi-key INI tomwalters@268: file be replaced with this entry. This option has tomwalters@268: no effect if not using multi-key files. The tomwalters@268: difference between Delete/SetBoolValue and tomwalters@268: SetBoolValue with a_bForceReplace = true, is that tomwalters@268: the load order and comment will be preserved this tomwalters@268: way. tomwalters@268: tomwalters@268: @return SI_Error See error definitions tomwalters@268: @return SI_UPDATED Value was updated tomwalters@268: @return SI_INSERTED Value was inserted tomwalters@268: */ tomwalters@268: SI_Error SetBoolValue( tomwalters@268: const SI_CHAR * a_pSection, tomwalters@268: const SI_CHAR * a_pKey, tomwalters@268: bool a_bValue, tomwalters@268: const SI_CHAR * a_pComment = NULL, tomwalters@268: bool a_bForceReplace = false tomwalters@268: ); tomwalters@268: tomwalters@268: /** Delete an entire section, or a key from a section. Note that the tomwalters@268: data returned by GetSection is invalid and must not be used after tomwalters@268: anything has been deleted from that section using this method. tomwalters@268: Note when multiple keys is enabled, this will delete all keys with tomwalters@268: that name; there is no way to selectively delete individual key/values tomwalters@268: in this situation. tomwalters@268: tomwalters@268: @param a_pSection Section to delete key from, or if tomwalters@268: a_pKey is NULL, the section to remove. tomwalters@268: @param a_pKey Key to remove from the section. Set to tomwalters@268: NULL to remove the entire section. tomwalters@268: @param a_bRemoveEmpty If the section is empty after this key has tomwalters@268: been deleted, should the empty section be tomwalters@268: removed? tomwalters@268: tomwalters@268: @return true Key or section was deleted. tomwalters@268: @return false Key or section was not found. tomwalters@268: */ tomwalters@268: bool Delete( tomwalters@268: const SI_CHAR * a_pSection, tomwalters@268: const SI_CHAR * a_pKey, tomwalters@268: bool a_bRemoveEmpty = false tomwalters@268: ); tomwalters@268: tomwalters@268: /*-----------------------------------------------------------------------*/ tomwalters@268: /** @} tomwalters@268: @{ @name Converter */ tomwalters@268: tomwalters@268: /** Return a conversion object to convert text to the same encoding tomwalters@268: as is used by the Save(), SaveFile() and SaveString() functions. tomwalters@268: Use this to prepare the strings that you wish to append or prepend tomwalters@268: to the output INI data. tomwalters@268: */ tomwalters@268: Converter GetConverter() const { tomwalters@268: return Converter(m_bStoreIsUtf8); tomwalters@268: } tomwalters@268: tomwalters@268: /*-----------------------------------------------------------------------*/ tomwalters@268: /** @} */ tomwalters@268: tomwalters@268: private: tomwalters@268: // copying is not permitted tomwalters@268: CSimpleIniTempl(const CSimpleIniTempl &); // disabled tomwalters@268: CSimpleIniTempl & operator=(const CSimpleIniTempl &); // disabled tomwalters@268: tomwalters@268: /** Parse the data looking for a file comment and store it if found. tomwalters@268: */ tomwalters@268: SI_Error FindFileComment( tomwalters@268: SI_CHAR *& a_pData, tomwalters@268: bool a_bCopyStrings tomwalters@268: ); tomwalters@268: tomwalters@268: /** Parse the data looking for the next valid entry. The memory pointed to tomwalters@268: by a_pData is modified by inserting NULL characters. The pointer is tomwalters@268: updated to the current location in the block of text. tomwalters@268: */ tomwalters@268: bool FindEntry( tomwalters@268: SI_CHAR *& a_pData, tomwalters@268: const SI_CHAR *& a_pSection, tomwalters@268: const SI_CHAR *& a_pKey, tomwalters@268: const SI_CHAR *& a_pVal, tomwalters@268: const SI_CHAR *& a_pComment tomwalters@268: ) const; tomwalters@268: tomwalters@268: /** Add the section/key/value to our data. tomwalters@268: tomwalters@268: @param a_pSection Section name. Sections will be created if they tomwalters@268: don't already exist. tomwalters@268: @param a_pKey Key name. May be NULL to create an empty section. tomwalters@268: Existing entries will be updated. New entries will tomwalters@268: be created. tomwalters@268: @param a_pValue Value for the key. tomwalters@268: @param a_pComment Comment to be associated with the section or the tomwalters@268: key. If a_pKey is NULL then it will be associated tomwalters@268: with the section, otherwise the key. This must be tomwalters@268: a string in full comment form already (have a tomwalters@268: comment character starting every line). tomwalters@268: @param a_bForceReplace Should all existing values in a multi-key INI tomwalters@268: file be replaced with this entry. This option has tomwalters@268: no effect if not using multi-key files. The tomwalters@268: difference between Delete/AddEntry and AddEntry tomwalters@268: with a_bForceReplace = true, is that the load tomwalters@268: order and comment will be preserved this way. tomwalters@268: @param a_bCopyStrings Should copies of the strings be made or not. tomwalters@268: If false then the pointers will be used as is. tomwalters@268: */ tomwalters@268: SI_Error AddEntry( tomwalters@268: const SI_CHAR * a_pSection, tomwalters@268: const SI_CHAR * a_pKey, tomwalters@268: const SI_CHAR * a_pValue, tomwalters@268: const SI_CHAR * a_pComment, tomwalters@268: bool a_bForceReplace, tomwalters@268: bool a_bCopyStrings tomwalters@268: ); tomwalters@268: tomwalters@268: /** Is the supplied character a whitespace character? */ tomwalters@268: inline bool IsSpace(SI_CHAR ch) const { tomwalters@268: return (ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n'); tomwalters@268: } tomwalters@268: tomwalters@268: /** Does the supplied character start a comment line? */ tomwalters@268: inline bool IsComment(SI_CHAR ch) const { tomwalters@268: return (ch == ';' || ch == '#'); tomwalters@268: } tomwalters@268: tomwalters@268: tomwalters@268: /** Skip over a newline character (or characters) for either DOS or UNIX */ tomwalters@268: inline void SkipNewLine(SI_CHAR *& a_pData) const { tomwalters@268: a_pData += (*a_pData == '\r' && *(a_pData+1) == '\n') ? 2 : 1; tomwalters@268: } tomwalters@268: tomwalters@268: /** Make a copy of the supplied string, replacing the original pointer */ tomwalters@268: SI_Error CopyString(const SI_CHAR *& a_pString); tomwalters@268: tomwalters@268: /** Delete a string from the copied strings buffer if necessary */ tomwalters@268: void DeleteString(const SI_CHAR * a_pString); tomwalters@268: tomwalters@268: /** Internal use of our string comparison function */ tomwalters@268: bool IsLess(const SI_CHAR * a_pLeft, const SI_CHAR * a_pRight) const { tomwalters@268: const static SI_STRLESS isLess = SI_STRLESS(); tomwalters@268: return isLess(a_pLeft, a_pRight); tomwalters@268: } tomwalters@268: tomwalters@268: bool IsMultiLineTag(const SI_CHAR * a_pData) const; tomwalters@268: bool IsMultiLineData(const SI_CHAR * a_pData) const; tomwalters@268: bool LoadMultiLineText( tomwalters@268: SI_CHAR *& a_pData, tomwalters@268: const SI_CHAR *& a_pVal, tomwalters@268: const SI_CHAR * a_pTagName, tomwalters@268: bool a_bAllowBlankLinesInComment = false tomwalters@268: ) const; tomwalters@268: bool IsNewLineChar(SI_CHAR a_c) const; tomwalters@268: tomwalters@268: bool OutputMultiLineText( tomwalters@268: OutputWriter & a_oOutput, tomwalters@268: Converter & a_oConverter, tomwalters@268: const SI_CHAR * a_pText tomwalters@268: ) const; tomwalters@268: tomwalters@268: private: tomwalters@268: /** Copy of the INI file data in our character format. This will be tomwalters@268: modified when parsed to have NULL characters added after all tomwalters@268: interesting string entries. All of the string pointers to sections, tomwalters@268: keys and values point into this block of memory. tomwalters@268: */ tomwalters@268: SI_CHAR * m_pData; tomwalters@268: tomwalters@268: /** Length of the data that we have stored. Used when deleting strings tomwalters@268: to determine if the string is stored here or in the allocated string tomwalters@268: buffer. tomwalters@268: */ tomwalters@268: size_t m_uDataLen; tomwalters@268: tomwalters@268: /** File comment for this data, if one exists. */ tomwalters@268: const SI_CHAR * m_pFileComment; tomwalters@268: tomwalters@268: /** Parsed INI data. Section -> (Key -> Value). */ tomwalters@268: TSection m_data; tomwalters@268: tomwalters@268: /** This vector stores allocated memory for copies of strings that have tomwalters@268: been supplied after the file load. It will be empty unless SetValue() tomwalters@268: has been called. tomwalters@268: */ tomwalters@268: TNamesDepend m_strings; tomwalters@268: tomwalters@268: /** Is the format of our datafile UTF-8 or MBCS? */ tomwalters@268: bool m_bStoreIsUtf8; tomwalters@268: tomwalters@268: /** Are multiple values permitted for the same key? */ tomwalters@268: bool m_bAllowMultiKey; tomwalters@268: tomwalters@268: /** Are data values permitted to span multiple lines? */ tomwalters@268: bool m_bAllowMultiLine; tomwalters@268: tomwalters@268: /** Should spaces be written out surrounding the equals sign? */ tomwalters@268: bool m_bSpaces; tomwalters@268: tomwalters@268: /** Next order value, used to ensure sections and keys are output in the tomwalters@268: same order that they are loaded/added. tomwalters@268: */ tomwalters@268: int m_nOrder; tomwalters@268: }; tomwalters@268: tomwalters@268: // --------------------------------------------------------------------------- tomwalters@268: // IMPLEMENTATION tomwalters@268: // --------------------------------------------------------------------------- tomwalters@268: tomwalters@268: template tomwalters@268: CSimpleIniTempl::CSimpleIniTempl( tomwalters@268: bool a_bIsUtf8, tomwalters@268: bool a_bAllowMultiKey, tomwalters@268: bool a_bAllowMultiLine tomwalters@268: ) tomwalters@268: : m_pData(0) tomwalters@268: , m_uDataLen(0) tomwalters@268: , m_pFileComment(NULL) tomwalters@268: , m_bStoreIsUtf8(a_bIsUtf8) tomwalters@268: , m_bAllowMultiKey(a_bAllowMultiKey) tomwalters@268: , m_bAllowMultiLine(a_bAllowMultiLine) tomwalters@268: , m_bSpaces(true) tomwalters@268: , m_nOrder(0) tomwalters@268: { } tomwalters@268: tomwalters@268: template tomwalters@268: CSimpleIniTempl::~CSimpleIniTempl() tomwalters@268: { tomwalters@268: Reset(); tomwalters@268: } tomwalters@268: tomwalters@268: template tomwalters@268: void tomwalters@268: CSimpleIniTempl::Reset() tomwalters@268: { tomwalters@268: // remove all data tomwalters@268: delete[] m_pData; tomwalters@268: m_pData = NULL; tomwalters@268: m_uDataLen = 0; tomwalters@268: m_pFileComment = NULL; tomwalters@268: if (!m_data.empty()) { tomwalters@268: m_data.erase(m_data.begin(), m_data.end()); tomwalters@268: } tomwalters@268: tomwalters@268: // remove all strings tomwalters@268: if (!m_strings.empty()) { tomwalters@268: typename TNamesDepend::iterator i = m_strings.begin(); tomwalters@268: for (; i != m_strings.end(); ++i) { tomwalters@268: delete[] const_cast(i->pItem); tomwalters@268: } tomwalters@268: m_strings.erase(m_strings.begin(), m_strings.end()); tomwalters@268: } tomwalters@268: } tomwalters@268: tomwalters@268: template tomwalters@268: SI_Error tomwalters@268: CSimpleIniTempl::LoadFile( tomwalters@268: const char * a_pszFile tomwalters@268: ) tomwalters@268: { tomwalters@268: FILE * fp = NULL; tomwalters@268: #if __STDC_WANT_SECURE_LIB__ && !_WIN32_WCE tomwalters@268: fopen_s(&fp, a_pszFile, "rb"); tomwalters@268: #else // !__STDC_WANT_SECURE_LIB__ tomwalters@268: fp = fopen(a_pszFile, "rb"); tomwalters@268: #endif // __STDC_WANT_SECURE_LIB__ tomwalters@268: if (!fp) { tomwalters@268: return SI_FILE; tomwalters@268: } tomwalters@268: SI_Error rc = LoadFile(fp); tomwalters@268: fclose(fp); tomwalters@268: return rc; tomwalters@268: } tomwalters@268: tomwalters@268: #ifdef SI_HAS_WIDE_FILE tomwalters@268: template tomwalters@268: SI_Error tomwalters@268: CSimpleIniTempl::LoadFile( tomwalters@268: const SI_WCHAR_T * a_pwszFile tomwalters@268: ) tomwalters@268: { tomwalters@268: #ifdef _WIN32 tomwalters@268: FILE * fp = NULL; tomwalters@268: #if __STDC_WANT_SECURE_LIB__ && !_WIN32_WCE tomwalters@268: _wfopen_s(&fp, a_pwszFile, L"rb"); tomwalters@268: #else // !__STDC_WANT_SECURE_LIB__ tomwalters@268: fp = _wfopen(a_pwszFile, L"rb"); tomwalters@268: #endif // __STDC_WANT_SECURE_LIB__ tomwalters@268: if (!fp) return SI_FILE; tomwalters@268: SI_Error rc = LoadFile(fp); tomwalters@268: fclose(fp); tomwalters@268: return rc; tomwalters@268: #else // !_WIN32 (therefore SI_CONVERT_ICU) tomwalters@268: char szFile[256]; tomwalters@268: u_austrncpy(szFile, a_pwszFile, sizeof(szFile)); tomwalters@268: return LoadFile(szFile); tomwalters@268: #endif // _WIN32 tomwalters@268: } tomwalters@268: #endif // SI_HAS_WIDE_FILE tomwalters@268: tomwalters@268: template tomwalters@268: SI_Error tomwalters@268: CSimpleIniTempl::LoadFile( tomwalters@268: FILE * a_fpFile tomwalters@268: ) tomwalters@268: { tomwalters@268: // load the raw file data tomwalters@268: int retval = fseek(a_fpFile, 0, SEEK_END); tomwalters@268: if (retval != 0) { tomwalters@268: return SI_FILE; tomwalters@268: } tomwalters@268: long lSize = ftell(a_fpFile); tomwalters@268: if (lSize < 0) { tomwalters@268: return SI_FILE; tomwalters@268: } tomwalters@268: if (lSize == 0) { tomwalters@268: return SI_OK; tomwalters@268: } tomwalters@268: char * pData = new char[lSize]; tomwalters@268: if (!pData) { tomwalters@268: return SI_NOMEM; tomwalters@268: } tomwalters@268: fseek(a_fpFile, 0, SEEK_SET); tomwalters@268: size_t uRead = fread(pData, sizeof(char), lSize, a_fpFile); tomwalters@268: if (uRead != (size_t) lSize) { tomwalters@268: delete[] pData; tomwalters@268: return SI_FILE; tomwalters@268: } tomwalters@268: tomwalters@268: // convert the raw data to unicode tomwalters@268: SI_Error rc = Load(pData, uRead); tomwalters@268: delete[] pData; tomwalters@268: return rc; tomwalters@268: } tomwalters@268: tomwalters@268: template tomwalters@268: SI_Error tomwalters@268: CSimpleIniTempl::Load( tomwalters@268: const char * a_pData, tomwalters@268: size_t a_uDataLen tomwalters@268: ) tomwalters@268: { tomwalters@268: SI_CONVERTER converter(m_bStoreIsUtf8); tomwalters@268: tomwalters@268: if (a_uDataLen == 0) { tomwalters@268: return SI_OK; tomwalters@268: } tomwalters@268: tomwalters@268: // consume the UTF-8 BOM if it exists tomwalters@268: if (m_bStoreIsUtf8 && a_uDataLen >= 3) { tomwalters@268: if (memcmp(a_pData, SI_UTF8_SIGNATURE, 3) == 0) { tomwalters@268: a_pData += 3; tomwalters@268: a_uDataLen -= 3; tomwalters@268: } tomwalters@268: } tomwalters@268: tomwalters@268: // determine the length of the converted data tomwalters@268: size_t uLen = converter.SizeFromStore(a_pData, a_uDataLen); tomwalters@268: if (uLen == (size_t)(-1)) { tomwalters@268: return SI_FAIL; tomwalters@268: } tomwalters@268: tomwalters@268: // allocate memory for the data, ensure that there is a NULL tomwalters@268: // terminator wherever the converted data ends tomwalters@268: SI_CHAR * pData = new SI_CHAR[uLen+1]; tomwalters@268: if (!pData) { tomwalters@268: return SI_NOMEM; tomwalters@268: } tomwalters@268: memset(pData, 0, sizeof(SI_CHAR)*(uLen+1)); tomwalters@268: tomwalters@268: // convert the data tomwalters@268: if (!converter.ConvertFromStore(a_pData, a_uDataLen, pData, uLen)) { tomwalters@268: delete[] pData; tomwalters@268: return SI_FAIL; tomwalters@268: } tomwalters@268: tomwalters@268: // parse it tomwalters@268: const static SI_CHAR empty = 0; tomwalters@268: SI_CHAR * pWork = pData; tomwalters@268: const SI_CHAR * pSection = ∅ tomwalters@268: const SI_CHAR * pItem = NULL; tomwalters@268: const SI_CHAR * pVal = NULL; tomwalters@268: const SI_CHAR * pComment = NULL; tomwalters@268: tomwalters@268: // We copy the strings if we are loading data into this class when we tomwalters@268: // already have stored some. tomwalters@268: bool bCopyStrings = (m_pData != NULL); tomwalters@268: tomwalters@268: // find a file comment if it exists, this is a comment that starts at the tomwalters@268: // beginning of the file and continues until the first blank line. tomwalters@268: SI_Error rc = FindFileComment(pWork, bCopyStrings); tomwalters@268: if (rc < 0) return rc; tomwalters@268: tomwalters@268: // add every entry in the file to the data table tomwalters@268: while (FindEntry(pWork, pSection, pItem, pVal, pComment)) { tomwalters@268: rc = AddEntry(pSection, pItem, pVal, pComment, false, bCopyStrings); tomwalters@268: if (rc < 0) return rc; tomwalters@268: } tomwalters@268: tomwalters@268: // store these strings if we didn't copy them tomwalters@268: if (bCopyStrings) { tomwalters@268: delete[] pData; tomwalters@268: } tomwalters@268: else { tomwalters@268: m_pData = pData; tomwalters@268: m_uDataLen = uLen+1; tomwalters@268: } tomwalters@268: tomwalters@268: return SI_OK; tomwalters@268: } tomwalters@268: tomwalters@268: #ifdef SI_SUPPORT_IOSTREAMS tomwalters@268: template tomwalters@268: SI_Error tomwalters@268: CSimpleIniTempl::Load( tomwalters@268: std::istream & a_istream tomwalters@268: ) tomwalters@268: { tomwalters@268: std::string strData; tomwalters@268: char szBuf[512]; tomwalters@268: do { tomwalters@268: a_istream.get(szBuf, sizeof(szBuf), '\0'); tomwalters@268: strData.append(szBuf); tomwalters@268: } tomwalters@268: while (a_istream.good()); tomwalters@268: return Load(strData); tomwalters@268: } tomwalters@268: #endif // SI_SUPPORT_IOSTREAMS tomwalters@268: tomwalters@268: template tomwalters@268: SI_Error tomwalters@268: CSimpleIniTempl::FindFileComment( tomwalters@268: SI_CHAR *& a_pData, tomwalters@268: bool a_bCopyStrings tomwalters@268: ) tomwalters@268: { tomwalters@268: // there can only be a single file comment tomwalters@268: if (m_pFileComment) { tomwalters@268: return SI_OK; tomwalters@268: } tomwalters@268: tomwalters@268: // Load the file comment as multi-line text, this will modify all of tomwalters@268: // the newline characters to be single \n chars tomwalters@268: if (!LoadMultiLineText(a_pData, m_pFileComment, NULL, false)) { tomwalters@268: return SI_OK; tomwalters@268: } tomwalters@268: tomwalters@268: // copy the string if necessary tomwalters@268: if (a_bCopyStrings) { tomwalters@268: SI_Error rc = CopyString(m_pFileComment); tomwalters@268: if (rc < 0) return rc; tomwalters@268: } tomwalters@268: tomwalters@268: return SI_OK; tomwalters@268: } tomwalters@268: tomwalters@268: template tomwalters@268: bool tomwalters@268: CSimpleIniTempl::FindEntry( tomwalters@268: SI_CHAR *& a_pData, tomwalters@268: const SI_CHAR *& a_pSection, tomwalters@268: const SI_CHAR *& a_pKey, tomwalters@268: const SI_CHAR *& a_pVal, tomwalters@268: const SI_CHAR *& a_pComment tomwalters@268: ) const tomwalters@268: { tomwalters@268: a_pComment = NULL; tomwalters@268: tomwalters@268: SI_CHAR * pTrail = NULL; tomwalters@268: while (*a_pData) { tomwalters@268: // skip spaces and empty lines tomwalters@268: while (*a_pData && IsSpace(*a_pData)) { tomwalters@268: ++a_pData; tomwalters@268: } tomwalters@268: if (!*a_pData) { tomwalters@268: break; tomwalters@268: } tomwalters@268: tomwalters@268: // skip processing of comment lines but keep a pointer to tomwalters@268: // the start of the comment. tomwalters@268: if (IsComment(*a_pData)) { tomwalters@268: LoadMultiLineText(a_pData, a_pComment, NULL, true); tomwalters@268: continue; tomwalters@268: } tomwalters@268: tomwalters@268: // process section names tomwalters@268: if (*a_pData == '[') { tomwalters@268: // skip leading spaces tomwalters@268: ++a_pData; tomwalters@268: while (*a_pData && IsSpace(*a_pData)) { tomwalters@268: ++a_pData; tomwalters@268: } tomwalters@268: tomwalters@268: // find the end of the section name (it may contain spaces) tomwalters@268: // and convert it to lowercase as necessary tomwalters@268: a_pSection = a_pData; tomwalters@268: while (*a_pData && *a_pData != ']' && !IsNewLineChar(*a_pData)) { tomwalters@268: ++a_pData; tomwalters@268: } tomwalters@268: tomwalters@268: // if it's an invalid line, just skip it tomwalters@268: if (*a_pData != ']') { tomwalters@268: continue; tomwalters@268: } tomwalters@268: tomwalters@268: // remove trailing spaces from the section tomwalters@268: pTrail = a_pData - 1; tomwalters@268: while (pTrail >= a_pSection && IsSpace(*pTrail)) { tomwalters@268: --pTrail; tomwalters@268: } tomwalters@268: ++pTrail; tomwalters@268: *pTrail = 0; tomwalters@268: tomwalters@268: // skip to the end of the line tomwalters@268: ++a_pData; // safe as checked that it == ']' above tomwalters@268: while (*a_pData && !IsNewLineChar(*a_pData)) { tomwalters@268: ++a_pData; tomwalters@268: } tomwalters@268: tomwalters@268: a_pKey = NULL; tomwalters@268: a_pVal = NULL; tomwalters@268: return true; tomwalters@268: } tomwalters@268: tomwalters@268: // find the end of the key name (it may contain spaces) tomwalters@268: // and convert it to lowercase as necessary tomwalters@268: a_pKey = a_pData; tomwalters@268: while (*a_pData && *a_pData != '=' && !IsNewLineChar(*a_pData)) { tomwalters@268: ++a_pData; tomwalters@268: } tomwalters@268: tomwalters@268: // if it's an invalid line, just skip it tomwalters@268: if (*a_pData != '=') { tomwalters@268: continue; tomwalters@268: } tomwalters@268: tomwalters@268: // empty keys are invalid tomwalters@268: if (a_pKey == a_pData) { tomwalters@268: while (*a_pData && !IsNewLineChar(*a_pData)) { tomwalters@268: ++a_pData; tomwalters@268: } tomwalters@268: continue; tomwalters@268: } tomwalters@268: tomwalters@268: // remove trailing spaces from the key tomwalters@268: pTrail = a_pData - 1; tomwalters@268: while (pTrail >= a_pKey && IsSpace(*pTrail)) { tomwalters@268: --pTrail; tomwalters@268: } tomwalters@268: ++pTrail; tomwalters@268: *pTrail = 0; tomwalters@268: tomwalters@268: // skip leading whitespace on the value tomwalters@268: ++a_pData; // safe as checked that it == '=' above tomwalters@268: while (*a_pData && !IsNewLineChar(*a_pData) && IsSpace(*a_pData)) { tomwalters@268: ++a_pData; tomwalters@268: } tomwalters@268: tomwalters@268: // find the end of the value which is the end of this line tomwalters@268: a_pVal = a_pData; tomwalters@268: while (*a_pData && !IsNewLineChar(*a_pData)) { tomwalters@268: ++a_pData; tomwalters@268: } tomwalters@268: tomwalters@268: // remove trailing spaces from the value tomwalters@268: pTrail = a_pData - 1; tomwalters@268: if (*a_pData) { // prepare for the next round tomwalters@268: SkipNewLine(a_pData); tomwalters@268: } tomwalters@268: while (pTrail >= a_pVal && IsSpace(*pTrail)) { tomwalters@268: --pTrail; tomwalters@268: } tomwalters@268: ++pTrail; tomwalters@268: *pTrail = 0; tomwalters@268: tomwalters@268: // check for multi-line entries tomwalters@268: if (m_bAllowMultiLine && IsMultiLineTag(a_pVal)) { tomwalters@268: // skip the "<<<" to get the tag that will end the multiline tomwalters@268: const SI_CHAR * pTagName = a_pVal + 3; tomwalters@268: return LoadMultiLineText(a_pData, a_pVal, pTagName); tomwalters@268: } tomwalters@268: tomwalters@268: // return the standard entry tomwalters@268: return true; tomwalters@268: } tomwalters@268: tomwalters@268: return false; tomwalters@268: } tomwalters@268: tomwalters@268: template tomwalters@268: bool tomwalters@268: CSimpleIniTempl::IsMultiLineTag( tomwalters@268: const SI_CHAR * a_pVal tomwalters@268: ) const tomwalters@268: { tomwalters@268: // check for the "<<<" prefix for a multi-line entry tomwalters@268: if (*a_pVal++ != '<') return false; tomwalters@268: if (*a_pVal++ != '<') return false; tomwalters@268: if (*a_pVal++ != '<') return false; tomwalters@268: return true; tomwalters@268: } tomwalters@268: tomwalters@268: template tomwalters@268: bool tomwalters@268: CSimpleIniTempl::IsMultiLineData( tomwalters@268: const SI_CHAR * a_pData tomwalters@268: ) const tomwalters@268: { tomwalters@268: // data is multi-line if it has any of the following features: tomwalters@268: // * whitespace prefix tomwalters@268: // * embedded newlines tomwalters@268: // * whitespace suffix tomwalters@268: tomwalters@268: // empty string tomwalters@268: if (!*a_pData) { tomwalters@268: return false; tomwalters@268: } tomwalters@268: tomwalters@268: // check for prefix tomwalters@268: if (IsSpace(*a_pData)) { tomwalters@268: return true; tomwalters@268: } tomwalters@268: tomwalters@268: // embedded newlines tomwalters@268: while (*a_pData) { tomwalters@268: if (IsNewLineChar(*a_pData)) { tomwalters@268: return true; tomwalters@268: } tomwalters@268: ++a_pData; tomwalters@268: } tomwalters@268: tomwalters@268: // check for suffix tomwalters@268: if (IsSpace(*--a_pData)) { tomwalters@268: return true; tomwalters@268: } tomwalters@268: tomwalters@268: return false; tomwalters@268: } tomwalters@268: tomwalters@268: template tomwalters@268: bool tomwalters@268: CSimpleIniTempl::IsNewLineChar( tomwalters@268: SI_CHAR a_c tomwalters@268: ) const tomwalters@268: { tomwalters@268: return (a_c == '\n' || a_c == '\r'); tomwalters@268: } tomwalters@268: tomwalters@268: template tomwalters@268: bool tomwalters@268: CSimpleIniTempl::LoadMultiLineText( tomwalters@268: SI_CHAR *& a_pData, tomwalters@268: const SI_CHAR *& a_pVal, tomwalters@268: const SI_CHAR * a_pTagName, tomwalters@268: bool a_bAllowBlankLinesInComment tomwalters@268: ) const tomwalters@268: { tomwalters@268: // we modify this data to strip all newlines down to a single '\n' tomwalters@268: // character. This means that on Windows we need to strip out some tomwalters@268: // characters which will make the data shorter. tomwalters@268: // i.e. LINE1-LINE1\r\nLINE2-LINE2\0 will become tomwalters@268: // LINE1-LINE1\nLINE2-LINE2\0 tomwalters@268: // The pDataLine entry is the pointer to the location in memory that tomwalters@268: // the current line needs to start to run following the existing one. tomwalters@268: // This may be the same as pCurrLine in which case no move is needed. tomwalters@268: SI_CHAR * pDataLine = a_pData; tomwalters@268: SI_CHAR * pCurrLine; tomwalters@268: tomwalters@268: // value starts at the current line tomwalters@268: a_pVal = a_pData; tomwalters@268: tomwalters@268: // find the end tag. This tag must start in column 1 and be tomwalters@268: // followed by a newline. No whitespace removal is done while tomwalters@268: // searching for this tag. tomwalters@268: SI_CHAR cEndOfLineChar = *a_pData; tomwalters@268: for(;;) { tomwalters@268: // if we are loading comments then we need a comment character as tomwalters@268: // the first character on every line tomwalters@268: if (!a_pTagName && !IsComment(*a_pData)) { tomwalters@268: // if we aren't allowing blank lines then we're done tomwalters@268: if (!a_bAllowBlankLinesInComment) { tomwalters@268: break; tomwalters@268: } tomwalters@268: tomwalters@268: // if we are allowing blank lines then we only include them tomwalters@268: // in this comment if another comment follows, so read ahead tomwalters@268: // to find out. tomwalters@268: SI_CHAR * pCurr = a_pData; tomwalters@268: int nNewLines = 0; tomwalters@268: while (IsSpace(*pCurr)) { tomwalters@268: if (IsNewLineChar(*pCurr)) { tomwalters@268: ++nNewLines; tomwalters@268: SkipNewLine(pCurr); tomwalters@268: } tomwalters@268: else { tomwalters@268: ++pCurr; tomwalters@268: } tomwalters@268: } tomwalters@268: tomwalters@268: // we have a comment, add the blank lines to the output tomwalters@268: // and continue processing from here tomwalters@268: if (IsComment(*pCurr)) { tomwalters@268: for (; nNewLines > 0; --nNewLines) *pDataLine++ = '\n'; tomwalters@268: a_pData = pCurr; tomwalters@268: continue; tomwalters@268: } tomwalters@268: tomwalters@268: // the comment ends here tomwalters@268: break; tomwalters@268: } tomwalters@268: tomwalters@268: // find the end of this line tomwalters@268: pCurrLine = a_pData; tomwalters@268: while (*a_pData && !IsNewLineChar(*a_pData)) ++a_pData; tomwalters@268: tomwalters@268: // move this line down to the location that it should be if necessary tomwalters@268: if (pDataLine < pCurrLine) { tomwalters@268: size_t nLen = (size_t) (a_pData - pCurrLine); tomwalters@268: memmove(pDataLine, pCurrLine, nLen * sizeof(SI_CHAR)); tomwalters@268: pDataLine[nLen] = '\0'; tomwalters@268: } tomwalters@268: tomwalters@268: // end the line with a NULL tomwalters@268: cEndOfLineChar = *a_pData; tomwalters@268: *a_pData = 0; tomwalters@268: tomwalters@268: // if are looking for a tag then do the check now. This is done before tomwalters@268: // checking for end of the data, so that if we have the tag at the end tomwalters@268: // of the data then the tag is removed correctly. tomwalters@268: if (a_pTagName && tomwalters@268: (!IsLess(pDataLine, a_pTagName) && !IsLess(a_pTagName, pDataLine))) tomwalters@268: { tomwalters@268: break; tomwalters@268: } tomwalters@268: tomwalters@268: // if we are at the end of the data then we just automatically end tomwalters@268: // this entry and return the current data. tomwalters@268: if (!cEndOfLineChar) { tomwalters@268: return true; tomwalters@268: } tomwalters@268: tomwalters@268: // otherwise we need to process this newline to ensure that it consists tomwalters@268: // of just a single \n character. tomwalters@268: pDataLine += (a_pData - pCurrLine); tomwalters@268: *a_pData = cEndOfLineChar; tomwalters@268: SkipNewLine(a_pData); tomwalters@268: *pDataLine++ = '\n'; tomwalters@268: } tomwalters@268: tomwalters@268: // if we didn't find a comment at all then return false tomwalters@268: if (a_pVal == a_pData) { tomwalters@268: a_pVal = NULL; tomwalters@268: return false; tomwalters@268: } tomwalters@268: tomwalters@268: // the data (which ends at the end of the last line) needs to be tomwalters@268: // null-terminated BEFORE before the newline character(s). If the tomwalters@268: // user wants a new line in the multi-line data then they need to tomwalters@268: // add an empty line before the tag. tomwalters@268: *--pDataLine = '\0'; tomwalters@268: tomwalters@268: // if looking for a tag and if we aren't at the end of the data, tomwalters@268: // then move a_pData to the start of the next line. tomwalters@268: if (a_pTagName && cEndOfLineChar) { tomwalters@268: SI_ASSERT(IsNewLineChar(cEndOfLineChar)); tomwalters@268: *a_pData = cEndOfLineChar; tomwalters@268: SkipNewLine(a_pData); tomwalters@268: } tomwalters@268: tomwalters@268: return true; tomwalters@268: } tomwalters@268: tomwalters@268: template tomwalters@268: SI_Error tomwalters@268: CSimpleIniTempl::CopyString( tomwalters@268: const SI_CHAR *& a_pString tomwalters@268: ) tomwalters@268: { tomwalters@268: size_t uLen = 0; tomwalters@268: if (sizeof(SI_CHAR) == sizeof(char)) { tomwalters@268: uLen = strlen((const char *)a_pString); tomwalters@268: } tomwalters@268: else if (sizeof(SI_CHAR) == sizeof(wchar_t)) { tomwalters@268: uLen = wcslen((const wchar_t *)a_pString); tomwalters@268: } tomwalters@268: else { tomwalters@268: for ( ; a_pString[uLen]; ++uLen) /*loop*/ ; tomwalters@268: } tomwalters@268: ++uLen; // NULL character tomwalters@268: SI_CHAR * pCopy = new SI_CHAR[uLen]; tomwalters@268: if (!pCopy) { tomwalters@268: return SI_NOMEM; tomwalters@268: } tomwalters@268: memcpy(pCopy, a_pString, sizeof(SI_CHAR)*uLen); tomwalters@268: m_strings.push_back(pCopy); tomwalters@268: a_pString = pCopy; tomwalters@268: return SI_OK; tomwalters@268: } tomwalters@268: tomwalters@268: template tomwalters@268: SI_Error tomwalters@268: CSimpleIniTempl::AddEntry( tomwalters@268: const SI_CHAR * a_pSection, tomwalters@268: const SI_CHAR * a_pKey, tomwalters@268: const SI_CHAR * a_pValue, tomwalters@268: const SI_CHAR * a_pComment, tomwalters@268: bool a_bForceReplace, tomwalters@268: bool a_bCopyStrings tomwalters@268: ) tomwalters@268: { tomwalters@268: SI_Error rc; tomwalters@268: bool bInserted = false; tomwalters@268: tomwalters@268: SI_ASSERT(!a_pComment || IsComment(*a_pComment)); tomwalters@268: tomwalters@268: // if we are copying strings then make a copy of the comment now tomwalters@268: // because we will need it when we add the entry. tomwalters@268: if (a_bCopyStrings && a_pComment) { tomwalters@268: rc = CopyString(a_pComment); tomwalters@268: if (rc < 0) return rc; tomwalters@268: } tomwalters@268: tomwalters@268: // create the section entry if necessary tomwalters@268: typename TSection::iterator iSection = m_data.find(a_pSection); tomwalters@268: if (iSection == m_data.end()) { tomwalters@268: // if the section doesn't exist then we need a copy as the tomwalters@268: // string needs to last beyond the end of this function tomwalters@268: if (a_bCopyStrings) { tomwalters@268: rc = CopyString(a_pSection); tomwalters@268: if (rc < 0) return rc; tomwalters@268: } tomwalters@268: tomwalters@268: // only set the comment if this is a section only entry tomwalters@268: Entry oSection(a_pSection, ++m_nOrder); tomwalters@268: if (a_pComment && (!a_pKey || !a_pValue)) { tomwalters@268: oSection.pComment = a_pComment; tomwalters@268: } tomwalters@268: tomwalters@268: typename TSection::value_type oEntry(oSection, TKeyVal()); tomwalters@268: typedef typename TSection::iterator SectionIterator; tomwalters@268: std::pair i = m_data.insert(oEntry); tomwalters@268: iSection = i.first; tomwalters@268: bInserted = true; tomwalters@268: } tomwalters@268: if (!a_pKey || !a_pValue) { tomwalters@268: // section only entries are specified with pItem and pVal as NULL tomwalters@268: return bInserted ? SI_INSERTED : SI_UPDATED; tomwalters@268: } tomwalters@268: tomwalters@268: // check for existence of the key tomwalters@268: TKeyVal & keyval = iSection->second; tomwalters@268: typename TKeyVal::iterator iKey = keyval.find(a_pKey); tomwalters@268: tomwalters@268: // remove all existing entries but save the load order and tomwalters@268: // comment of the first entry tomwalters@268: int nLoadOrder = ++m_nOrder; tomwalters@268: if (iKey != keyval.end() && m_bAllowMultiKey && a_bForceReplace) { tomwalters@268: const SI_CHAR * pComment = NULL; tomwalters@268: while (iKey != keyval.end() && !IsLess(a_pKey, iKey->first.pItem)) { tomwalters@268: if (iKey->first.nOrder < nLoadOrder) { tomwalters@268: nLoadOrder = iKey->first.nOrder; tomwalters@268: pComment = iKey->first.pComment; tomwalters@268: } tomwalters@268: ++iKey; tomwalters@268: } tomwalters@268: if (pComment) { tomwalters@268: DeleteString(a_pComment); tomwalters@268: a_pComment = pComment; tomwalters@268: CopyString(a_pComment); tomwalters@268: } tomwalters@268: Delete(a_pSection, a_pKey); tomwalters@268: iKey = keyval.end(); tomwalters@268: } tomwalters@268: tomwalters@268: // make string copies if necessary tomwalters@268: bool bForceCreateNewKey = m_bAllowMultiKey && !a_bForceReplace; tomwalters@268: if (a_bCopyStrings) { tomwalters@268: if (bForceCreateNewKey || iKey == keyval.end()) { tomwalters@268: // if the key doesn't exist then we need a copy as the tomwalters@268: // string needs to last beyond the end of this function tomwalters@268: // because we will be inserting the key next tomwalters@268: rc = CopyString(a_pKey); tomwalters@268: if (rc < 0) return rc; tomwalters@268: } tomwalters@268: tomwalters@268: // we always need a copy of the value tomwalters@268: rc = CopyString(a_pValue); tomwalters@268: if (rc < 0) return rc; tomwalters@268: } tomwalters@268: tomwalters@268: // create the key entry tomwalters@268: if (iKey == keyval.end() || bForceCreateNewKey) { tomwalters@268: Entry oKey(a_pKey, nLoadOrder); tomwalters@268: if (a_pComment) { tomwalters@268: oKey.pComment = a_pComment; tomwalters@268: } tomwalters@330: typename TKeyVal::value_type oEntry(oKey, (const SI_CHAR *) NULL); tomwalters@268: iKey = keyval.insert(oEntry); tomwalters@268: bInserted = true; tomwalters@268: } tomwalters@268: iKey->second = a_pValue; tomwalters@268: return bInserted ? SI_INSERTED : SI_UPDATED; tomwalters@268: } tomwalters@268: tomwalters@268: template tomwalters@268: const SI_CHAR * tomwalters@268: CSimpleIniTempl::GetValue( tomwalters@268: const SI_CHAR * a_pSection, tomwalters@268: const SI_CHAR * a_pKey, tomwalters@268: const SI_CHAR * a_pDefault, tomwalters@268: bool * a_pHasMultiple tomwalters@268: ) const tomwalters@268: { tomwalters@268: if (a_pHasMultiple) { tomwalters@268: *a_pHasMultiple = false; tomwalters@268: } tomwalters@268: if (!a_pSection || !a_pKey) { tomwalters@268: return a_pDefault; tomwalters@268: } tomwalters@268: typename TSection::const_iterator iSection = m_data.find(a_pSection); tomwalters@268: if (iSection == m_data.end()) { tomwalters@268: return a_pDefault; tomwalters@268: } tomwalters@268: typename TKeyVal::const_iterator iKeyVal = iSection->second.find(a_pKey); tomwalters@268: if (iKeyVal == iSection->second.end()) { tomwalters@268: return a_pDefault; tomwalters@268: } tomwalters@268: tomwalters@268: // check for multiple entries with the same key tomwalters@268: if (m_bAllowMultiKey && a_pHasMultiple) { tomwalters@268: typename TKeyVal::const_iterator iTemp = iKeyVal; tomwalters@268: if (++iTemp != iSection->second.end()) { tomwalters@268: if (!IsLess(a_pKey, iTemp->first.pItem)) { tomwalters@268: *a_pHasMultiple = true; tomwalters@268: } tomwalters@268: } tomwalters@268: } tomwalters@268: tomwalters@268: return iKeyVal->second; tomwalters@268: } tomwalters@268: tomwalters@268: template tomwalters@268: long tomwalters@268: CSimpleIniTempl::GetLongValue( tomwalters@268: const SI_CHAR * a_pSection, tomwalters@268: const SI_CHAR * a_pKey, tomwalters@268: long a_nDefault, tomwalters@268: bool * a_pHasMultiple tomwalters@268: ) const tomwalters@268: { tomwalters@268: // return the default if we don't have a value tomwalters@268: const SI_CHAR * pszValue = GetValue(a_pSection, a_pKey, NULL, a_pHasMultiple); tomwalters@268: if (!pszValue || !*pszValue) return a_nDefault; tomwalters@268: tomwalters@268: // convert to UTF-8/MBCS which for a numeric value will be the same as ASCII tomwalters@268: char szValue[64] = { 0 }; tomwalters@268: SI_CONVERTER c(m_bStoreIsUtf8); tomwalters@268: if (!c.ConvertToStore(pszValue, szValue, sizeof(szValue))) { tomwalters@268: return a_nDefault; tomwalters@268: } tomwalters@268: tomwalters@268: // handle the value as hex if prefaced with "0x" tomwalters@268: long nValue = a_nDefault; tomwalters@268: char * pszSuffix = szValue; tomwalters@268: if (szValue[0] == '0' && (szValue[1] == 'x' || szValue[1] == 'X')) { tomwalters@268: if (!szValue[2]) return a_nDefault; tomwalters@268: nValue = strtol(&szValue[2], &pszSuffix, 16); tomwalters@268: } tomwalters@268: else { tomwalters@268: nValue = strtol(szValue, &pszSuffix, 10); tomwalters@268: } tomwalters@268: tomwalters@268: // any invalid strings will return the default value tomwalters@268: if (*pszSuffix) { tomwalters@268: return a_nDefault; tomwalters@268: } tomwalters@268: tomwalters@268: return nValue; tomwalters@268: } tomwalters@268: tomwalters@268: template tomwalters@268: SI_Error tomwalters@268: CSimpleIniTempl::SetLongValue( tomwalters@268: const SI_CHAR * a_pSection, tomwalters@268: const SI_CHAR * a_pKey, tomwalters@268: long a_nValue, tomwalters@268: const SI_CHAR * a_pComment, tomwalters@268: bool a_bUseHex, tomwalters@268: bool a_bForceReplace tomwalters@268: ) tomwalters@268: { tomwalters@268: // use SetValue to create sections tomwalters@268: if (!a_pSection || !a_pKey) return SI_FAIL; tomwalters@268: tomwalters@268: // convert to an ASCII string tomwalters@268: char szInput[64]; tomwalters@268: #if __STDC_WANT_SECURE_LIB__ && !_WIN32_WCE tomwalters@268: sprintf_s(szInput, a_bUseHex ? "0x%lx" : "%ld", a_nValue); tomwalters@268: #else // !__STDC_WANT_SECURE_LIB__ tomwalters@268: sprintf(szInput, a_bUseHex ? "0x%lx" : "%ld", a_nValue); tomwalters@268: #endif // __STDC_WANT_SECURE_LIB__ tomwalters@268: tomwalters@268: // convert to output text tomwalters@268: SI_CHAR szOutput[64]; tomwalters@268: SI_CONVERTER c(m_bStoreIsUtf8); tomwalters@268: c.ConvertFromStore(szInput, strlen(szInput) + 1, tomwalters@268: szOutput, sizeof(szOutput) / sizeof(SI_CHAR)); tomwalters@268: tomwalters@268: // actually add it tomwalters@268: return AddEntry(a_pSection, a_pKey, szOutput, a_pComment, a_bForceReplace, true); tomwalters@268: } tomwalters@268: tomwalters@268: template tomwalters@268: bool tomwalters@268: CSimpleIniTempl::GetBoolValue( tomwalters@268: const SI_CHAR * a_pSection, tomwalters@268: const SI_CHAR * a_pKey, tomwalters@268: bool a_bDefault, tomwalters@268: bool * a_pHasMultiple tomwalters@268: ) const tomwalters@268: { tomwalters@268: // return the default if we don't have a value tomwalters@268: const SI_CHAR * pszValue = GetValue(a_pSection, a_pKey, NULL, a_pHasMultiple); tomwalters@268: if (!pszValue || !*pszValue) return a_bDefault; tomwalters@268: tomwalters@268: // we only look at the minimum number of characters tomwalters@268: switch (pszValue[0]) { tomwalters@268: case 't': case 'T': // true tomwalters@268: case 'y': case 'Y': // yes tomwalters@268: case '1': // 1 (one) tomwalters@268: return true; tomwalters@268: tomwalters@268: case 'f': case 'F': // false tomwalters@268: case 'n': case 'N': // no tomwalters@268: case '0': // 0 (zero) tomwalters@268: return false; tomwalters@268: tomwalters@268: case 'o': case 'O': tomwalters@268: if (pszValue[1] == 'n' || pszValue[1] == 'N') return true; // on tomwalters@268: if (pszValue[1] == 'f' || pszValue[1] == 'F') return false; // off tomwalters@268: break; tomwalters@268: } tomwalters@268: tomwalters@268: // no recognized value, return the default tomwalters@268: return a_bDefault; tomwalters@268: } tomwalters@268: tomwalters@268: template tomwalters@268: SI_Error tomwalters@268: CSimpleIniTempl::SetBoolValue( tomwalters@268: const SI_CHAR * a_pSection, tomwalters@268: const SI_CHAR * a_pKey, tomwalters@268: bool a_bValue, tomwalters@268: const SI_CHAR * a_pComment, tomwalters@268: bool a_bForceReplace tomwalters@268: ) tomwalters@268: { tomwalters@268: // use SetValue to create sections tomwalters@268: if (!a_pSection || !a_pKey) return SI_FAIL; tomwalters@268: tomwalters@268: // convert to an ASCII string tomwalters@268: const char * pszInput = a_bValue ? "true" : "false"; tomwalters@268: tomwalters@268: // convert to output text tomwalters@268: SI_CHAR szOutput[64]; tomwalters@268: SI_CONVERTER c(m_bStoreIsUtf8); tomwalters@268: c.ConvertFromStore(pszInput, strlen(pszInput) + 1, tomwalters@268: szOutput, sizeof(szOutput) / sizeof(SI_CHAR)); tomwalters@268: tomwalters@268: // actually add it tomwalters@268: return AddEntry(a_pSection, a_pKey, szOutput, a_pComment, a_bForceReplace, true); tomwalters@268: } tomwalters@268: tomwalters@268: template tomwalters@268: bool tomwalters@268: CSimpleIniTempl::GetAllValues( tomwalters@268: const SI_CHAR * a_pSection, tomwalters@268: const SI_CHAR * a_pKey, tomwalters@268: TNamesDepend & a_values tomwalters@268: ) const tomwalters@268: { tomwalters@268: a_values.clear(); tomwalters@268: tomwalters@268: if (!a_pSection || !a_pKey) { tomwalters@268: return false; tomwalters@268: } tomwalters@268: typename TSection::const_iterator iSection = m_data.find(a_pSection); tomwalters@268: if (iSection == m_data.end()) { tomwalters@268: return false; tomwalters@268: } tomwalters@268: typename TKeyVal::const_iterator iKeyVal = iSection->second.find(a_pKey); tomwalters@268: if (iKeyVal == iSection->second.end()) { tomwalters@268: return false; tomwalters@268: } tomwalters@268: tomwalters@268: // insert all values for this key tomwalters@268: a_values.push_back(Entry(iKeyVal->second, iKeyVal->first.pComment, iKeyVal->first.nOrder)); tomwalters@268: if (m_bAllowMultiKey) { tomwalters@268: ++iKeyVal; tomwalters@268: while (iKeyVal != iSection->second.end() && !IsLess(a_pKey, iKeyVal->first.pItem)) { tomwalters@268: a_values.push_back(Entry(iKeyVal->second, iKeyVal->first.pComment, iKeyVal->first.nOrder)); tomwalters@268: ++iKeyVal; tomwalters@268: } tomwalters@268: } tomwalters@268: tomwalters@268: return true; tomwalters@268: } tomwalters@268: tomwalters@268: template tomwalters@268: int tomwalters@268: CSimpleIniTempl::GetSectionSize( tomwalters@268: const SI_CHAR * a_pSection tomwalters@268: ) const tomwalters@268: { tomwalters@268: if (!a_pSection) { tomwalters@268: return -1; tomwalters@268: } tomwalters@268: tomwalters@268: typename TSection::const_iterator iSection = m_data.find(a_pSection); tomwalters@268: if (iSection == m_data.end()) { tomwalters@268: return -1; tomwalters@268: } tomwalters@268: const TKeyVal & section = iSection->second; tomwalters@268: tomwalters@268: // if multi-key isn't permitted then the section size is tomwalters@268: // the number of keys that we have. tomwalters@268: if (!m_bAllowMultiKey || section.empty()) { tomwalters@268: return (int) section.size(); tomwalters@268: } tomwalters@268: tomwalters@268: // otherwise we need to count them tomwalters@268: int nCount = 0; tomwalters@268: const SI_CHAR * pLastKey = NULL; tomwalters@268: typename TKeyVal::const_iterator iKeyVal = section.begin(); tomwalters@268: for (int n = 0; iKeyVal != section.end(); ++iKeyVal, ++n) { tomwalters@268: if (!pLastKey || IsLess(pLastKey, iKeyVal->first.pItem)) { tomwalters@268: ++nCount; tomwalters@268: pLastKey = iKeyVal->first.pItem; tomwalters@268: } tomwalters@268: } tomwalters@268: return nCount; tomwalters@268: } tomwalters@268: tomwalters@268: template tomwalters@268: const typename CSimpleIniTempl::TKeyVal * tomwalters@268: CSimpleIniTempl::GetSection( tomwalters@268: const SI_CHAR * a_pSection tomwalters@268: ) const tomwalters@268: { tomwalters@268: if (a_pSection) { tomwalters@268: typename TSection::const_iterator i = m_data.find(a_pSection); tomwalters@268: if (i != m_data.end()) { tomwalters@268: return &(i->second); tomwalters@268: } tomwalters@268: } tomwalters@268: return 0; tomwalters@268: } tomwalters@268: tomwalters@268: template tomwalters@268: void tomwalters@268: CSimpleIniTempl::GetAllSections( tomwalters@268: TNamesDepend & a_names tomwalters@268: ) const tomwalters@268: { tomwalters@268: a_names.clear(); tomwalters@268: typename TSection::const_iterator i = m_data.begin(); tomwalters@268: for (int n = 0; i != m_data.end(); ++i, ++n ) { tomwalters@268: a_names.push_back(i->first); tomwalters@268: } tomwalters@268: } tomwalters@268: tomwalters@268: template tomwalters@268: bool tomwalters@268: CSimpleIniTempl::GetAllKeys( tomwalters@268: const SI_CHAR * a_pSection, tomwalters@268: TNamesDepend & a_names tomwalters@268: ) const tomwalters@268: { tomwalters@268: a_names.clear(); tomwalters@268: tomwalters@268: if (!a_pSection) { tomwalters@268: return false; tomwalters@268: } tomwalters@268: tomwalters@268: typename TSection::const_iterator iSection = m_data.find(a_pSection); tomwalters@268: if (iSection == m_data.end()) { tomwalters@268: return false; tomwalters@268: } tomwalters@268: tomwalters@268: const TKeyVal & section = iSection->second; tomwalters@268: const SI_CHAR * pLastKey = NULL; tomwalters@268: typename TKeyVal::const_iterator iKeyVal = section.begin(); tomwalters@268: for (int n = 0; iKeyVal != section.end(); ++iKeyVal, ++n ) { tomwalters@268: if (!pLastKey || IsLess(pLastKey, iKeyVal->first.pItem)) { tomwalters@268: a_names.push_back(iKeyVal->first); tomwalters@268: pLastKey = iKeyVal->first.pItem; tomwalters@268: } tomwalters@268: } tomwalters@268: tomwalters@268: return true; tomwalters@268: } tomwalters@268: tomwalters@268: template tomwalters@268: SI_Error tomwalters@268: CSimpleIniTempl::SaveFile( tomwalters@268: const char * a_pszFile, tomwalters@268: bool a_bAddSignature tomwalters@268: ) const tomwalters@268: { tomwalters@268: FILE * fp = NULL; tomwalters@268: #if __STDC_WANT_SECURE_LIB__ && !_WIN32_WCE tomwalters@268: fopen_s(&fp, a_pszFile, "wb"); tomwalters@268: #else // !__STDC_WANT_SECURE_LIB__ tomwalters@268: fp = fopen(a_pszFile, "wb"); tomwalters@268: #endif // __STDC_WANT_SECURE_LIB__ tomwalters@268: if (!fp) return SI_FILE; tomwalters@268: SI_Error rc = SaveFile(fp, a_bAddSignature); tomwalters@268: fclose(fp); tomwalters@268: return rc; tomwalters@268: } tomwalters@268: tomwalters@268: #ifdef SI_HAS_WIDE_FILE tomwalters@268: template tomwalters@268: SI_Error tomwalters@268: CSimpleIniTempl::SaveFile( tomwalters@268: const SI_WCHAR_T * a_pwszFile, tomwalters@268: bool a_bAddSignature tomwalters@268: ) const tomwalters@268: { tomwalters@268: #ifdef _WIN32 tomwalters@268: FILE * fp = NULL; tomwalters@268: #if __STDC_WANT_SECURE_LIB__ && !_WIN32_WCE tomwalters@268: _wfopen_s(&fp, a_pwszFile, L"wb"); tomwalters@268: #else // !__STDC_WANT_SECURE_LIB__ tomwalters@268: fp = _wfopen(a_pwszFile, L"wb"); tomwalters@268: #endif // __STDC_WANT_SECURE_LIB__ tomwalters@268: if (!fp) return SI_FILE; tomwalters@268: SI_Error rc = SaveFile(fp, a_bAddSignature); tomwalters@268: fclose(fp); tomwalters@268: return rc; tomwalters@268: #else // !_WIN32 (therefore SI_CONVERT_ICU) tomwalters@268: char szFile[256]; tomwalters@268: u_austrncpy(szFile, a_pwszFile, sizeof(szFile)); tomwalters@268: return SaveFile(szFile, a_bAddSignature); tomwalters@268: #endif // _WIN32 tomwalters@268: } tomwalters@268: #endif // SI_HAS_WIDE_FILE tomwalters@268: tomwalters@268: template tomwalters@268: SI_Error tomwalters@268: CSimpleIniTempl::SaveFile( tomwalters@268: FILE * a_pFile, tomwalters@268: bool a_bAddSignature tomwalters@268: ) const tomwalters@268: { tomwalters@268: FileWriter writer(a_pFile); tomwalters@268: return Save(writer, a_bAddSignature); tomwalters@268: } tomwalters@268: tomwalters@268: template tomwalters@268: SI_Error tomwalters@268: CSimpleIniTempl::Save( tomwalters@268: OutputWriter & a_oOutput, tomwalters@268: bool a_bAddSignature tomwalters@268: ) const tomwalters@268: { tomwalters@268: Converter convert(m_bStoreIsUtf8); tomwalters@268: tomwalters@268: // add the UTF-8 signature if it is desired tomwalters@268: if (m_bStoreIsUtf8 && a_bAddSignature) { tomwalters@268: a_oOutput.Write(SI_UTF8_SIGNATURE); tomwalters@268: } tomwalters@268: tomwalters@268: // get all of the sections sorted in load order tomwalters@268: TNamesDepend oSections; tomwalters@268: GetAllSections(oSections); tomwalters@268: #if defined(_MSC_VER) && _MSC_VER <= 1200 tomwalters@268: oSections.sort(); tomwalters@268: #elif defined(__BORLANDC__) tomwalters@268: oSections.sort(Entry::LoadOrder()); tomwalters@268: #else tomwalters@268: oSections.sort(typename Entry::LoadOrder()); tomwalters@268: #endif tomwalters@268: tomwalters@268: // write the file comment if we have one tomwalters@268: bool bNeedNewLine = false; tomwalters@268: if (m_pFileComment) { tomwalters@268: if (!OutputMultiLineText(a_oOutput, convert, m_pFileComment)) { tomwalters@268: return SI_FAIL; tomwalters@268: } tomwalters@268: bNeedNewLine = true; tomwalters@268: } tomwalters@268: tomwalters@268: // iterate through our sections and output the data tomwalters@268: typename TNamesDepend::const_iterator iSection = oSections.begin(); tomwalters@268: for ( ; iSection != oSections.end(); ++iSection ) { tomwalters@268: // write out the comment if there is one tomwalters@268: if (iSection->pComment) { tomwalters@268: if (bNeedNewLine) { tomwalters@268: a_oOutput.Write(SI_NEWLINE_A); tomwalters@268: a_oOutput.Write(SI_NEWLINE_A); tomwalters@268: } tomwalters@268: if (!OutputMultiLineText(a_oOutput, convert, iSection->pComment)) { tomwalters@268: return SI_FAIL; tomwalters@268: } tomwalters@268: bNeedNewLine = false; tomwalters@268: } tomwalters@268: tomwalters@268: if (bNeedNewLine) { tomwalters@268: a_oOutput.Write(SI_NEWLINE_A); tomwalters@268: a_oOutput.Write(SI_NEWLINE_A); tomwalters@268: bNeedNewLine = false; tomwalters@268: } tomwalters@268: tomwalters@268: // write the section (unless there is no section name) tomwalters@268: if (*iSection->pItem) { tomwalters@268: if (!convert.ConvertToStore(iSection->pItem)) { tomwalters@268: return SI_FAIL; tomwalters@268: } tomwalters@268: a_oOutput.Write("["); tomwalters@268: a_oOutput.Write(convert.Data()); tomwalters@268: a_oOutput.Write("]"); tomwalters@268: a_oOutput.Write(SI_NEWLINE_A); tomwalters@268: } tomwalters@268: tomwalters@268: // get all of the keys sorted in load order tomwalters@268: TNamesDepend oKeys; tomwalters@268: GetAllKeys(iSection->pItem, oKeys); tomwalters@268: #if defined(_MSC_VER) && _MSC_VER <= 1200 tomwalters@268: oKeys.sort(); tomwalters@268: #elif defined(__BORLANDC__) tomwalters@268: oKeys.sort(Entry::LoadOrder()); tomwalters@268: #else tomwalters@268: oKeys.sort(typename Entry::LoadOrder()); tomwalters@268: #endif tomwalters@268: tomwalters@268: // write all keys and values tomwalters@268: typename TNamesDepend::const_iterator iKey = oKeys.begin(); tomwalters@268: for ( ; iKey != oKeys.end(); ++iKey) { tomwalters@268: // get all values for this key tomwalters@268: TNamesDepend oValues; tomwalters@268: GetAllValues(iSection->pItem, iKey->pItem, oValues); tomwalters@268: tomwalters@268: typename TNamesDepend::const_iterator iValue = oValues.begin(); tomwalters@268: for ( ; iValue != oValues.end(); ++iValue) { tomwalters@268: // write out the comment if there is one tomwalters@268: if (iValue->pComment) { tomwalters@268: a_oOutput.Write(SI_NEWLINE_A); tomwalters@268: if (!OutputMultiLineText(a_oOutput, convert, iValue->pComment)) { tomwalters@268: return SI_FAIL; tomwalters@268: } tomwalters@268: } tomwalters@268: tomwalters@268: // write the key tomwalters@268: if (!convert.ConvertToStore(iKey->pItem)) { tomwalters@268: return SI_FAIL; tomwalters@268: } tomwalters@268: a_oOutput.Write(convert.Data()); tomwalters@268: tomwalters@268: // write the value tomwalters@268: if (!convert.ConvertToStore(iValue->pItem)) { tomwalters@268: return SI_FAIL; tomwalters@268: } tomwalters@268: a_oOutput.Write(m_bSpaces ? " = " : "="); tomwalters@268: if (m_bAllowMultiLine && IsMultiLineData(iValue->pItem)) { tomwalters@268: // multi-line data needs to be processed specially to ensure tomwalters@268: // that we use the correct newline format for the current system tomwalters@268: a_oOutput.Write("<<pItem)) { tomwalters@268: return SI_FAIL; tomwalters@268: } tomwalters@268: a_oOutput.Write("SI-END-OF-MULTILINE-TEXT"); tomwalters@268: } tomwalters@268: else { tomwalters@268: a_oOutput.Write(convert.Data()); tomwalters@268: } tomwalters@268: a_oOutput.Write(SI_NEWLINE_A); tomwalters@268: } tomwalters@268: } tomwalters@268: tomwalters@268: bNeedNewLine = true; tomwalters@268: } tomwalters@268: tomwalters@268: return SI_OK; tomwalters@268: } tomwalters@268: tomwalters@268: template tomwalters@268: bool tomwalters@268: CSimpleIniTempl::OutputMultiLineText( tomwalters@268: OutputWriter & a_oOutput, tomwalters@268: Converter & a_oConverter, tomwalters@268: const SI_CHAR * a_pText tomwalters@268: ) const tomwalters@268: { tomwalters@268: const SI_CHAR * pEndOfLine; tomwalters@268: SI_CHAR cEndOfLineChar = *a_pText; tomwalters@268: while (cEndOfLineChar) { tomwalters@268: // find the end of this line tomwalters@268: pEndOfLine = a_pText; tomwalters@268: for (; *pEndOfLine && *pEndOfLine != '\n'; ++pEndOfLine) /*loop*/ ; tomwalters@268: cEndOfLineChar = *pEndOfLine; tomwalters@268: tomwalters@268: // temporarily null terminate, convert and output the line tomwalters@268: *const_cast(pEndOfLine) = 0; tomwalters@268: if (!a_oConverter.ConvertToStore(a_pText)) { tomwalters@268: return false; tomwalters@268: } tomwalters@268: *const_cast(pEndOfLine) = cEndOfLineChar; tomwalters@268: a_pText += (pEndOfLine - a_pText) + 1; tomwalters@268: a_oOutput.Write(a_oConverter.Data()); tomwalters@268: a_oOutput.Write(SI_NEWLINE_A); tomwalters@268: } tomwalters@268: return true; tomwalters@268: } tomwalters@268: tomwalters@268: template tomwalters@268: bool tomwalters@268: CSimpleIniTempl::Delete( tomwalters@268: const SI_CHAR * a_pSection, tomwalters@268: const SI_CHAR * a_pKey, tomwalters@268: bool a_bRemoveEmpty tomwalters@268: ) tomwalters@268: { tomwalters@268: if (!a_pSection) { tomwalters@268: return false; tomwalters@268: } tomwalters@268: tomwalters@268: typename TSection::iterator iSection = m_data.find(a_pSection); tomwalters@268: if (iSection == m_data.end()) { tomwalters@268: return false; tomwalters@268: } tomwalters@268: tomwalters@268: // remove a single key if we have a keyname tomwalters@268: if (a_pKey) { tomwalters@268: typename TKeyVal::iterator iKeyVal = iSection->second.find(a_pKey); tomwalters@268: if (iKeyVal == iSection->second.end()) { tomwalters@268: return false; tomwalters@268: } tomwalters@268: tomwalters@268: // remove any copied strings and then the key tomwalters@268: typename TKeyVal::iterator iDelete; tomwalters@268: do { tomwalters@268: iDelete = iKeyVal++; tomwalters@268: tomwalters@268: DeleteString(iDelete->first.pItem); tomwalters@268: DeleteString(iDelete->second); tomwalters@268: iSection->second.erase(iDelete); tomwalters@268: } tomwalters@268: while (iKeyVal != iSection->second.end() tomwalters@268: && !IsLess(a_pKey, iKeyVal->first.pItem)); tomwalters@268: tomwalters@268: // done now if the section is not empty or we are not pruning away tomwalters@268: // the empty sections. Otherwise let it fall through into the section tomwalters@268: // deletion code tomwalters@268: if (!a_bRemoveEmpty || !iSection->second.empty()) { tomwalters@268: return true; tomwalters@268: } tomwalters@268: } tomwalters@268: else { tomwalters@268: // delete all copied strings from this section. The actual tomwalters@268: // entries will be removed when the section is removed. tomwalters@268: typename TKeyVal::iterator iKeyVal = iSection->second.begin(); tomwalters@268: for ( ; iKeyVal != iSection->second.end(); ++iKeyVal) { tomwalters@268: DeleteString(iKeyVal->first.pItem); tomwalters@268: DeleteString(iKeyVal->second); tomwalters@268: } tomwalters@268: } tomwalters@268: tomwalters@268: // delete the section itself tomwalters@268: DeleteString(iSection->first.pItem); tomwalters@268: m_data.erase(iSection); tomwalters@268: tomwalters@268: return true; tomwalters@268: } tomwalters@268: tomwalters@268: template tomwalters@268: void tomwalters@268: CSimpleIniTempl::DeleteString( tomwalters@268: const SI_CHAR * a_pString tomwalters@268: ) tomwalters@268: { tomwalters@268: // strings may exist either inside the data block, or they will be tomwalters@268: // individually allocated and stored in m_strings. We only physically tomwalters@268: // delete those stored in m_strings. tomwalters@268: if (a_pString < m_pData || a_pString >= m_pData + m_uDataLen) { tomwalters@268: typename TNamesDepend::iterator i = m_strings.begin(); tomwalters@268: for (;i != m_strings.end(); ++i) { tomwalters@268: if (a_pString == i->pItem) { tomwalters@268: delete[] const_cast(i->pItem); tomwalters@268: m_strings.erase(i); tomwalters@268: break; tomwalters@268: } tomwalters@268: } tomwalters@268: } tomwalters@268: } tomwalters@268: tomwalters@268: // --------------------------------------------------------------------------- tomwalters@268: // CONVERSION FUNCTIONS tomwalters@268: // --------------------------------------------------------------------------- tomwalters@268: tomwalters@268: // Defines the conversion classes for different libraries. Before including tomwalters@268: // SimpleIni.h, set the converter that you wish you use by defining one of the tomwalters@268: // following symbols. tomwalters@268: // tomwalters@268: // SI_CONVERT_GENERIC Use the Unicode reference conversion library in tomwalters@268: // the accompanying files ConvertUTF.h/c tomwalters@268: // SI_CONVERT_ICU Use the IBM ICU conversion library. Requires tomwalters@268: // ICU headers on include path and icuuc.lib tomwalters@268: // SI_CONVERT_WIN32 Use the Win32 API functions for conversion. tomwalters@268: tomwalters@268: #if !defined(SI_CONVERT_GENERIC) && !defined(SI_CONVERT_WIN32) && !defined(SI_CONVERT_ICU) tomwalters@268: # ifdef _WIN32 tomwalters@268: # define SI_CONVERT_WIN32 tomwalters@268: # else tomwalters@268: # define SI_CONVERT_GENERIC tomwalters@268: # endif tomwalters@268: #endif tomwalters@268: tomwalters@268: /** tomwalters@268: * Generic case-sensitive less than comparison. This class returns numerically tomwalters@268: * ordered ASCII case-sensitive text for all possible sizes and types of tomwalters@268: * SI_CHAR. tomwalters@268: */ tomwalters@268: template tomwalters@268: struct SI_GenericCase { tomwalters@268: bool operator()(const SI_CHAR * pLeft, const SI_CHAR * pRight) const { tomwalters@268: long cmp; tomwalters@268: for ( ;*pLeft && *pRight; ++pLeft, ++pRight) { tomwalters@268: cmp = (long) *pLeft - (long) *pRight; tomwalters@268: if (cmp != 0) { tomwalters@268: return cmp < 0; tomwalters@268: } tomwalters@268: } tomwalters@268: return *pRight != 0; tomwalters@268: } tomwalters@268: }; tomwalters@268: tomwalters@268: /** tomwalters@268: * Generic ASCII case-insensitive less than comparison. This class returns tomwalters@268: * numerically ordered ASCII case-insensitive text for all possible sizes tomwalters@268: * and types of SI_CHAR. It is not safe for MBCS text comparison where tomwalters@268: * ASCII A-Z characters are used in the encoding of multi-byte characters. tomwalters@268: */ tomwalters@268: template tomwalters@268: struct SI_GenericNoCase { tomwalters@268: inline SI_CHAR locase(SI_CHAR ch) const { tomwalters@268: return (ch < 'A' || ch > 'Z') ? ch : (ch - 'A' + 'a'); tomwalters@268: } tomwalters@268: bool operator()(const SI_CHAR * pLeft, const SI_CHAR * pRight) const { tomwalters@268: long cmp; tomwalters@268: for ( ;*pLeft && *pRight; ++pLeft, ++pRight) { tomwalters@268: cmp = (long) locase(*pLeft) - (long) locase(*pRight); tomwalters@268: if (cmp != 0) { tomwalters@268: return cmp < 0; tomwalters@268: } tomwalters@268: } tomwalters@268: return *pRight != 0; tomwalters@268: } tomwalters@268: }; tomwalters@268: tomwalters@268: /** tomwalters@268: * Null conversion class for MBCS/UTF-8 to char (or equivalent). tomwalters@268: */ tomwalters@268: template tomwalters@268: class SI_ConvertA { tomwalters@268: bool m_bStoreIsUtf8; tomwalters@268: protected: tomwalters@268: SI_ConvertA() { } tomwalters@268: public: tomwalters@268: SI_ConvertA(bool a_bStoreIsUtf8) : m_bStoreIsUtf8(a_bStoreIsUtf8) { } tomwalters@268: tomwalters@268: /* copy and assignment */ tomwalters@268: SI_ConvertA(const SI_ConvertA & rhs) { operator=(rhs); } tomwalters@268: SI_ConvertA & operator=(const SI_ConvertA & rhs) { tomwalters@268: m_bStoreIsUtf8 = rhs.m_bStoreIsUtf8; tomwalters@268: return *this; tomwalters@268: } tomwalters@268: tomwalters@268: /** Calculate the number of SI_CHAR required for converting the input tomwalters@268: * from the storage format. The storage format is always UTF-8 or MBCS. tomwalters@268: * tomwalters@268: * @param a_pInputData Data in storage format to be converted to SI_CHAR. tomwalters@268: * @param a_uInputDataLen Length of storage format data in bytes. This tomwalters@268: * must be the actual length of the data, including tomwalters@268: * NULL byte if NULL terminated string is required. tomwalters@268: * @return Number of SI_CHAR required by the string when tomwalters@268: * converted. If there are embedded NULL bytes in the tomwalters@268: * input data, only the string up and not including tomwalters@268: * the NULL byte will be converted. tomwalters@268: * @return -1 cast to size_t on a conversion error. tomwalters@268: */ tomwalters@268: size_t SizeFromStore( tomwalters@268: const char * a_pInputData, tomwalters@268: size_t a_uInputDataLen) tomwalters@268: { tomwalters@268: (void)a_pInputData; tomwalters@268: SI_ASSERT(a_uInputDataLen != (size_t) -1); tomwalters@268: tomwalters@268: // ASCII/MBCS/UTF-8 needs no conversion tomwalters@268: return a_uInputDataLen; tomwalters@268: } tomwalters@268: tomwalters@268: /** Convert the input string from the storage format to SI_CHAR. tomwalters@268: * The storage format is always UTF-8 or MBCS. tomwalters@268: * tomwalters@268: * @param a_pInputData Data in storage format to be converted to SI_CHAR. tomwalters@268: * @param a_uInputDataLen Length of storage format data in bytes. This tomwalters@268: * must be the actual length of the data, including tomwalters@268: * NULL byte if NULL terminated string is required. tomwalters@268: * @param a_pOutputData Pointer to the output buffer to received the tomwalters@268: * converted data. tomwalters@268: * @param a_uOutputDataSize Size of the output buffer in SI_CHAR. tomwalters@268: * @return true if all of the input data was successfully tomwalters@268: * converted. tomwalters@268: */ tomwalters@268: bool ConvertFromStore( tomwalters@268: const char * a_pInputData, tomwalters@268: size_t a_uInputDataLen, tomwalters@268: SI_CHAR * a_pOutputData, tomwalters@268: size_t a_uOutputDataSize) tomwalters@268: { tomwalters@268: // ASCII/MBCS/UTF-8 needs no conversion tomwalters@268: if (a_uInputDataLen > a_uOutputDataSize) { tomwalters@268: return false; tomwalters@268: } tomwalters@268: memcpy(a_pOutputData, a_pInputData, a_uInputDataLen); tomwalters@268: return true; tomwalters@268: } tomwalters@268: tomwalters@268: /** Calculate the number of char required by the storage format of this tomwalters@268: * data. The storage format is always UTF-8 or MBCS. tomwalters@268: * tomwalters@268: * @param a_pInputData NULL terminated string to calculate the number of tomwalters@268: * bytes required to be converted to storage format. tomwalters@268: * @return Number of bytes required by the string when tomwalters@268: * converted to storage format. This size always tomwalters@268: * includes space for the terminating NULL character. tomwalters@268: * @return -1 cast to size_t on a conversion error. tomwalters@268: */ tomwalters@268: size_t SizeToStore( tomwalters@268: const SI_CHAR * a_pInputData) tomwalters@268: { tomwalters@268: // ASCII/MBCS/UTF-8 needs no conversion tomwalters@268: return strlen((const char *)a_pInputData) + 1; tomwalters@268: } tomwalters@268: tomwalters@268: /** Convert the input string to the storage format of this data. tomwalters@268: * The storage format is always UTF-8 or MBCS. tomwalters@268: * tomwalters@268: * @param a_pInputData NULL terminated source string to convert. All of tomwalters@268: * the data will be converted including the tomwalters@268: * terminating NULL character. tomwalters@268: * @param a_pOutputData Pointer to the buffer to receive the converted tomwalters@268: * string. tomwalters@268: * @param a_uOutputDataSize Size of the output buffer in char. tomwalters@268: * @return true if all of the input data, including the tomwalters@268: * terminating NULL character was successfully tomwalters@268: * converted. tomwalters@268: */ tomwalters@268: bool ConvertToStore( tomwalters@268: const SI_CHAR * a_pInputData, tomwalters@268: char * a_pOutputData, tomwalters@268: size_t a_uOutputDataSize) tomwalters@268: { tomwalters@268: // calc input string length (SI_CHAR type and size independent) tomwalters@268: size_t uInputLen = strlen((const char *)a_pInputData) + 1; tomwalters@268: if (uInputLen > a_uOutputDataSize) { tomwalters@268: return false; tomwalters@268: } tomwalters@268: tomwalters@268: // ascii/UTF-8 needs no conversion tomwalters@268: memcpy(a_pOutputData, a_pInputData, uInputLen); tomwalters@268: return true; tomwalters@268: } tomwalters@268: }; tomwalters@268: tomwalters@268: tomwalters@268: // --------------------------------------------------------------------------- tomwalters@268: // SI_CONVERT_GENERIC tomwalters@268: // --------------------------------------------------------------------------- tomwalters@268: #ifdef SI_CONVERT_GENERIC tomwalters@268: tomwalters@268: #define SI_Case SI_GenericCase tomwalters@268: #define SI_NoCase SI_GenericNoCase tomwalters@268: tomwalters@268: #include tomwalters@268: #include "ConvertUTF.h" tomwalters@268: tomwalters@268: /** tomwalters@268: * Converts UTF-8 to a wchar_t (or equivalent) using the Unicode reference tomwalters@268: * library functions. This can be used on all platforms. tomwalters@268: */ tomwalters@268: template tomwalters@268: class SI_ConvertW { tomwalters@268: bool m_bStoreIsUtf8; tomwalters@268: protected: tomwalters@268: SI_ConvertW() { } tomwalters@268: public: tomwalters@268: SI_ConvertW(bool a_bStoreIsUtf8) : m_bStoreIsUtf8(a_bStoreIsUtf8) { } tomwalters@268: tomwalters@268: /* copy and assignment */ tomwalters@268: SI_ConvertW(const SI_ConvertW & rhs) { operator=(rhs); } tomwalters@268: SI_ConvertW & operator=(const SI_ConvertW & rhs) { tomwalters@268: m_bStoreIsUtf8 = rhs.m_bStoreIsUtf8; tomwalters@268: return *this; tomwalters@268: } tomwalters@268: tomwalters@268: /** Calculate the number of SI_CHAR required for converting the input tomwalters@268: * from the storage format. The storage format is always UTF-8 or MBCS. tomwalters@268: * tomwalters@268: * @param a_pInputData Data in storage format to be converted to SI_CHAR. tomwalters@268: * @param a_uInputDataLen Length of storage format data in bytes. This tomwalters@268: * must be the actual length of the data, including tomwalters@268: * NULL byte if NULL terminated string is required. tomwalters@268: * @return Number of SI_CHAR required by the string when tomwalters@268: * converted. If there are embedded NULL bytes in the tomwalters@268: * input data, only the string up and not including tomwalters@268: * the NULL byte will be converted. tomwalters@268: * @return -1 cast to size_t on a conversion error. tomwalters@268: */ tomwalters@268: size_t SizeFromStore( tomwalters@268: const char * a_pInputData, tomwalters@268: size_t a_uInputDataLen) tomwalters@268: { tomwalters@268: SI_ASSERT(a_uInputDataLen != (size_t) -1); tomwalters@268: tomwalters@268: if (m_bStoreIsUtf8) { tomwalters@268: // worst case scenario for UTF-8 to wchar_t is 1 char -> 1 wchar_t tomwalters@268: // so we just return the same number of characters required as for tomwalters@268: // the source text. tomwalters@268: return a_uInputDataLen; tomwalters@268: } tomwalters@268: else { tomwalters@268: return mbstowcs(NULL, a_pInputData, a_uInputDataLen); tomwalters@268: } tomwalters@268: } tomwalters@268: tomwalters@268: /** Convert the input string from the storage format to SI_CHAR. tomwalters@268: * The storage format is always UTF-8 or MBCS. tomwalters@268: * tomwalters@268: * @param a_pInputData Data in storage format to be converted to SI_CHAR. tomwalters@268: * @param a_uInputDataLen Length of storage format data in bytes. This tomwalters@268: * must be the actual length of the data, including tomwalters@268: * NULL byte if NULL terminated string is required. tomwalters@268: * @param a_pOutputData Pointer to the output buffer to received the tomwalters@268: * converted data. tomwalters@268: * @param a_uOutputDataSize Size of the output buffer in SI_CHAR. tomwalters@268: * @return true if all of the input data was successfully tomwalters@268: * converted. tomwalters@268: */ tomwalters@268: bool ConvertFromStore( tomwalters@268: const char * a_pInputData, tomwalters@268: size_t a_uInputDataLen, tomwalters@268: SI_CHAR * a_pOutputData, tomwalters@268: size_t a_uOutputDataSize) tomwalters@268: { tomwalters@268: if (m_bStoreIsUtf8) { tomwalters@268: // This uses the Unicode reference implementation to do the tomwalters@268: // conversion from UTF-8 to wchar_t. The required files are tomwalters@268: // ConvertUTF.h and ConvertUTF.c which should be included in tomwalters@268: // the distribution but are publically available from unicode.org tomwalters@268: // at http://www.unicode.org/Public/PROGRAMS/CVTUTF/ tomwalters@268: ConversionResult retval; tomwalters@268: const UTF8 * pUtf8 = (const UTF8 *) a_pInputData; tomwalters@268: if (sizeof(wchar_t) == sizeof(UTF32)) { tomwalters@268: UTF32 * pUtf32 = (UTF32 *) a_pOutputData; tomwalters@268: retval = ConvertUTF8toUTF32( tomwalters@268: &pUtf8, pUtf8 + a_uInputDataLen, tomwalters@268: &pUtf32, pUtf32 + a_uOutputDataSize, tomwalters@268: lenientConversion); tomwalters@268: } tomwalters@268: else if (sizeof(wchar_t) == sizeof(UTF16)) { tomwalters@268: UTF16 * pUtf16 = (UTF16 *) a_pOutputData; tomwalters@268: retval = ConvertUTF8toUTF16( tomwalters@268: &pUtf8, pUtf8 + a_uInputDataLen, tomwalters@268: &pUtf16, pUtf16 + a_uOutputDataSize, tomwalters@268: lenientConversion); tomwalters@268: } tomwalters@268: return retval == conversionOK; tomwalters@268: } tomwalters@268: else { tomwalters@268: size_t retval = mbstowcs(a_pOutputData, tomwalters@268: a_pInputData, a_uOutputDataSize); tomwalters@268: return retval != (size_t)(-1); tomwalters@268: } tomwalters@268: } tomwalters@268: tomwalters@268: /** Calculate the number of char required by the storage format of this tomwalters@268: * data. The storage format is always UTF-8 or MBCS. tomwalters@268: * tomwalters@268: * @param a_pInputData NULL terminated string to calculate the number of tomwalters@268: * bytes required to be converted to storage format. tomwalters@268: * @return Number of bytes required by the string when tomwalters@268: * converted to storage format. This size always tomwalters@268: * includes space for the terminating NULL character. tomwalters@268: * @return -1 cast to size_t on a conversion error. tomwalters@268: */ tomwalters@268: size_t SizeToStore( tomwalters@268: const SI_CHAR * a_pInputData) tomwalters@268: { tomwalters@268: if (m_bStoreIsUtf8) { tomwalters@268: // worst case scenario for wchar_t to UTF-8 is 1 wchar_t -> 6 char tomwalters@268: size_t uLen = 0; tomwalters@268: while (a_pInputData[uLen]) { tomwalters@268: ++uLen; tomwalters@268: } tomwalters@268: return (6 * uLen) + 1; tomwalters@268: } tomwalters@268: else { tomwalters@268: size_t uLen = wcstombs(NULL, a_pInputData, 0); tomwalters@268: if (uLen == (size_t)(-1)) { tomwalters@268: return uLen; tomwalters@268: } tomwalters@268: return uLen + 1; // include NULL terminator tomwalters@268: } tomwalters@268: } tomwalters@268: tomwalters@268: /** Convert the input string to the storage format of this data. tomwalters@268: * The storage format is always UTF-8 or MBCS. tomwalters@268: * tomwalters@268: * @param a_pInputData NULL terminated source string to convert. All of tomwalters@268: * the data will be converted including the tomwalters@268: * terminating NULL character. tomwalters@268: * @param a_pOutputData Pointer to the buffer to receive the converted tomwalters@268: * string. tomwalters@268: * @param a_uOutputDataSize Size of the output buffer in char. tomwalters@268: * @return true if all of the input data, including the tomwalters@268: * terminating NULL character was successfully tomwalters@268: * converted. tomwalters@268: */ tomwalters@268: bool ConvertToStore( tomwalters@268: const SI_CHAR * a_pInputData, tomwalters@268: char * a_pOutputData, tomwalters@268: size_t a_uOutputDataSize tomwalters@268: ) tomwalters@268: { tomwalters@268: if (m_bStoreIsUtf8) { tomwalters@268: // calc input string length (SI_CHAR type and size independent) tomwalters@268: size_t uInputLen = 0; tomwalters@268: while (a_pInputData[uInputLen]) { tomwalters@268: ++uInputLen; tomwalters@268: } tomwalters@268: ++uInputLen; // include the NULL char tomwalters@268: tomwalters@268: // This uses the Unicode reference implementation to do the tomwalters@268: // conversion from wchar_t to UTF-8. The required files are tomwalters@268: // ConvertUTF.h and ConvertUTF.c which should be included in tomwalters@268: // the distribution but are publically available from unicode.org tomwalters@268: // at http://www.unicode.org/Public/PROGRAMS/CVTUTF/ tomwalters@268: ConversionResult retval; tomwalters@268: UTF8 * pUtf8 = (UTF8 *) a_pOutputData; tomwalters@268: if (sizeof(wchar_t) == sizeof(UTF32)) { tomwalters@268: const UTF32 * pUtf32 = (const UTF32 *) a_pInputData; tomwalters@268: retval = ConvertUTF32toUTF8( tomwalters@268: &pUtf32, pUtf32 + uInputLen, tomwalters@268: &pUtf8, pUtf8 + a_uOutputDataSize, tomwalters@268: lenientConversion); tomwalters@268: } tomwalters@268: else if (sizeof(wchar_t) == sizeof(UTF16)) { tomwalters@268: const UTF16 * pUtf16 = (const UTF16 *) a_pInputData; tomwalters@268: retval = ConvertUTF16toUTF8( tomwalters@268: &pUtf16, pUtf16 + uInputLen, tomwalters@268: &pUtf8, pUtf8 + a_uOutputDataSize, tomwalters@268: lenientConversion); tomwalters@268: } tomwalters@268: return retval == conversionOK; tomwalters@268: } tomwalters@268: else { tomwalters@268: size_t retval = wcstombs(a_pOutputData, tomwalters@268: a_pInputData, a_uOutputDataSize); tomwalters@268: return retval != (size_t) -1; tomwalters@268: } tomwalters@268: } tomwalters@268: }; tomwalters@268: tomwalters@268: #endif // SI_CONVERT_GENERIC tomwalters@268: tomwalters@268: tomwalters@268: // --------------------------------------------------------------------------- tomwalters@268: // SI_CONVERT_ICU tomwalters@268: // --------------------------------------------------------------------------- tomwalters@268: #ifdef SI_CONVERT_ICU tomwalters@268: tomwalters@268: #define SI_Case SI_GenericCase tomwalters@268: #define SI_NoCase SI_GenericNoCase tomwalters@268: tomwalters@268: #include tomwalters@268: tomwalters@268: /** tomwalters@268: * Converts MBCS/UTF-8 to UChar using ICU. This can be used on all platforms. tomwalters@268: */ tomwalters@268: template tomwalters@268: class SI_ConvertW { tomwalters@268: const char * m_pEncoding; tomwalters@268: UConverter * m_pConverter; tomwalters@268: protected: tomwalters@268: SI_ConvertW() : m_pEncoding(NULL), m_pConverter(NULL) { } tomwalters@268: public: tomwalters@268: SI_ConvertW(bool a_bStoreIsUtf8) : m_pConverter(NULL) { tomwalters@268: m_pEncoding = a_bStoreIsUtf8 ? "UTF-8" : NULL; tomwalters@268: } tomwalters@268: tomwalters@268: /* copy and assignment */ tomwalters@268: SI_ConvertW(const SI_ConvertW & rhs) { operator=(rhs); } tomwalters@268: SI_ConvertW & operator=(const SI_ConvertW & rhs) { tomwalters@268: m_pEncoding = rhs.m_pEncoding; tomwalters@268: m_pConverter = NULL; tomwalters@268: return *this; tomwalters@268: } tomwalters@268: ~SI_ConvertW() { if (m_pConverter) ucnv_close(m_pConverter); } tomwalters@268: tomwalters@268: /** Calculate the number of UChar required for converting the input tomwalters@268: * from the storage format. The storage format is always UTF-8 or MBCS. tomwalters@268: * tomwalters@268: * @param a_pInputData Data in storage format to be converted to UChar. tomwalters@268: * @param a_uInputDataLen Length of storage format data in bytes. This tomwalters@268: * must be the actual length of the data, including tomwalters@268: * NULL byte if NULL terminated string is required. tomwalters@268: * @return Number of UChar required by the string when tomwalters@268: * converted. If there are embedded NULL bytes in the tomwalters@268: * input data, only the string up and not including tomwalters@268: * the NULL byte will be converted. tomwalters@268: * @return -1 cast to size_t on a conversion error. tomwalters@268: */ tomwalters@268: size_t SizeFromStore( tomwalters@268: const char * a_pInputData, tomwalters@268: size_t a_uInputDataLen) tomwalters@268: { tomwalters@268: SI_ASSERT(a_uInputDataLen != (size_t) -1); tomwalters@268: tomwalters@268: UErrorCode nError; tomwalters@268: tomwalters@268: if (!m_pConverter) { tomwalters@268: nError = U_ZERO_ERROR; tomwalters@268: m_pConverter = ucnv_open(m_pEncoding, &nError); tomwalters@268: if (U_FAILURE(nError)) { tomwalters@268: return (size_t) -1; tomwalters@268: } tomwalters@268: } tomwalters@268: tomwalters@268: nError = U_ZERO_ERROR; tomwalters@268: ucnv_resetToUnicode(m_pConverter); tomwalters@268: int32_t nLen = ucnv_toUChars(m_pConverter, NULL, 0, tomwalters@268: a_pInputData, (int32_t) a_uInputDataLen, &nError); tomwalters@268: if (nError != U_BUFFER_OVERFLOW_ERROR) { tomwalters@268: return (size_t) -1; tomwalters@268: } tomwalters@268: tomwalters@268: return (size_t) nLen; tomwalters@268: } tomwalters@268: tomwalters@268: /** Convert the input string from the storage format to UChar. tomwalters@268: * The storage format is always UTF-8 or MBCS. tomwalters@268: * tomwalters@268: * @param a_pInputData Data in storage format to be converted to UChar. tomwalters@268: * @param a_uInputDataLen Length of storage format data in bytes. This tomwalters@268: * must be the actual length of the data, including tomwalters@268: * NULL byte if NULL terminated string is required. tomwalters@268: * @param a_pOutputData Pointer to the output buffer to received the tomwalters@268: * converted data. tomwalters@268: * @param a_uOutputDataSize Size of the output buffer in UChar. tomwalters@268: * @return true if all of the input data was successfully tomwalters@268: * converted. tomwalters@268: */ tomwalters@268: bool ConvertFromStore( tomwalters@268: const char * a_pInputData, tomwalters@268: size_t a_uInputDataLen, tomwalters@268: UChar * a_pOutputData, tomwalters@268: size_t a_uOutputDataSize) tomwalters@268: { tomwalters@268: UErrorCode nError; tomwalters@268: tomwalters@268: if (!m_pConverter) { tomwalters@268: nError = U_ZERO_ERROR; tomwalters@268: m_pConverter = ucnv_open(m_pEncoding, &nError); tomwalters@268: if (U_FAILURE(nError)) { tomwalters@268: return false; tomwalters@268: } tomwalters@268: } tomwalters@268: tomwalters@268: nError = U_ZERO_ERROR; tomwalters@268: ucnv_resetToUnicode(m_pConverter); tomwalters@268: ucnv_toUChars(m_pConverter, tomwalters@268: a_pOutputData, (int32_t) a_uOutputDataSize, tomwalters@268: a_pInputData, (int32_t) a_uInputDataLen, &nError); tomwalters@268: if (U_FAILURE(nError)) { tomwalters@268: return false; tomwalters@268: } tomwalters@268: tomwalters@268: return true; tomwalters@268: } tomwalters@268: tomwalters@268: /** Calculate the number of char required by the storage format of this tomwalters@268: * data. The storage format is always UTF-8 or MBCS. tomwalters@268: * tomwalters@268: * @param a_pInputData NULL terminated string to calculate the number of tomwalters@268: * bytes required to be converted to storage format. tomwalters@268: * @return Number of bytes required by the string when tomwalters@268: * converted to storage format. This size always tomwalters@268: * includes space for the terminating NULL character. tomwalters@268: * @return -1 cast to size_t on a conversion error. tomwalters@268: */ tomwalters@268: size_t SizeToStore( tomwalters@268: const UChar * a_pInputData) tomwalters@268: { tomwalters@268: UErrorCode nError; tomwalters@268: tomwalters@268: if (!m_pConverter) { tomwalters@268: nError = U_ZERO_ERROR; tomwalters@268: m_pConverter = ucnv_open(m_pEncoding, &nError); tomwalters@268: if (U_FAILURE(nError)) { tomwalters@268: return (size_t) -1; tomwalters@268: } tomwalters@268: } tomwalters@268: tomwalters@268: nError = U_ZERO_ERROR; tomwalters@268: ucnv_resetFromUnicode(m_pConverter); tomwalters@268: int32_t nLen = ucnv_fromUChars(m_pConverter, NULL, 0, tomwalters@268: a_pInputData, -1, &nError); tomwalters@268: if (nError != U_BUFFER_OVERFLOW_ERROR) { tomwalters@268: return (size_t) -1; tomwalters@268: } tomwalters@268: tomwalters@268: return (size_t) nLen + 1; tomwalters@268: } tomwalters@268: tomwalters@268: /** Convert the input string to the storage format of this data. tomwalters@268: * The storage format is always UTF-8 or MBCS. tomwalters@268: * tomwalters@268: * @param a_pInputData NULL terminated source string to convert. All of tomwalters@268: * the data will be converted including the tomwalters@268: * terminating NULL character. tomwalters@268: * @param a_pOutputData Pointer to the buffer to receive the converted tomwalters@268: * string. tomwalters@268: * @param a_pOutputDataSize Size of the output buffer in char. tomwalters@268: * @return true if all of the input data, including the tomwalters@268: * terminating NULL character was successfully tomwalters@268: * converted. tomwalters@268: */ tomwalters@268: bool ConvertToStore( tomwalters@268: const UChar * a_pInputData, tomwalters@268: char * a_pOutputData, tomwalters@268: size_t a_uOutputDataSize) tomwalters@268: { tomwalters@268: UErrorCode nError; tomwalters@268: tomwalters@268: if (!m_pConverter) { tomwalters@268: nError = U_ZERO_ERROR; tomwalters@268: m_pConverter = ucnv_open(m_pEncoding, &nError); tomwalters@268: if (U_FAILURE(nError)) { tomwalters@268: return false; tomwalters@268: } tomwalters@268: } tomwalters@268: tomwalters@268: nError = U_ZERO_ERROR; tomwalters@268: ucnv_resetFromUnicode(m_pConverter); tomwalters@268: ucnv_fromUChars(m_pConverter, tomwalters@268: a_pOutputData, (int32_t) a_uOutputDataSize, tomwalters@268: a_pInputData, -1, &nError); tomwalters@268: if (U_FAILURE(nError)) { tomwalters@268: return false; tomwalters@268: } tomwalters@268: tomwalters@268: return true; tomwalters@268: } tomwalters@268: }; tomwalters@268: tomwalters@268: #endif // SI_CONVERT_ICU tomwalters@268: tomwalters@268: tomwalters@268: // --------------------------------------------------------------------------- tomwalters@268: // SI_CONVERT_WIN32 tomwalters@268: // --------------------------------------------------------------------------- tomwalters@268: #ifdef SI_CONVERT_WIN32 tomwalters@268: tomwalters@268: #define SI_Case SI_GenericCase tomwalters@268: tomwalters@268: // Windows CE doesn't have errno or MBCS libraries tomwalters@268: #ifdef _WIN32_WCE tomwalters@268: # ifndef SI_NO_MBCS tomwalters@268: # define SI_NO_MBCS tomwalters@268: # endif tomwalters@268: #endif tomwalters@268: tomwalters@268: #include tomwalters@268: #ifdef SI_NO_MBCS tomwalters@268: # define SI_NoCase SI_GenericNoCase tomwalters@268: #else // !SI_NO_MBCS tomwalters@268: /** tomwalters@268: * Case-insensitive comparison class using Win32 MBCS functions. This class tomwalters@268: * returns a case-insensitive semi-collation order for MBCS text. It may not tomwalters@268: * be safe for UTF-8 text returned in char format as we don't know what tomwalters@268: * characters will be folded by the function! Therefore, if you are using tomwalters@268: * SI_CHAR == char and SetUnicode(true), then you need to use the generic tomwalters@268: * SI_NoCase class instead. tomwalters@268: */ tomwalters@268: #include tomwalters@268: template tomwalters@268: struct SI_NoCase { tomwalters@268: bool operator()(const SI_CHAR * pLeft, const SI_CHAR * pRight) const { tomwalters@268: if (sizeof(SI_CHAR) == sizeof(char)) { tomwalters@268: return _mbsicmp((const unsigned char *)pLeft, tomwalters@268: (const unsigned char *)pRight) < 0; tomwalters@268: } tomwalters@268: if (sizeof(SI_CHAR) == sizeof(wchar_t)) { tomwalters@268: return _wcsicmp((const wchar_t *)pLeft, tomwalters@268: (const wchar_t *)pRight) < 0; tomwalters@268: } tomwalters@268: return SI_GenericNoCase()(pLeft, pRight); tomwalters@268: } tomwalters@268: }; tomwalters@268: #endif // SI_NO_MBCS tomwalters@268: tomwalters@268: /** tomwalters@268: * Converts MBCS and UTF-8 to a wchar_t (or equivalent) on Windows. This uses tomwalters@268: * only the Win32 functions and doesn't require the external Unicode UTF-8 tomwalters@268: * conversion library. It will not work on Windows 95 without using Microsoft tomwalters@268: * Layer for Unicode in your application. tomwalters@268: */ tomwalters@268: template tomwalters@268: class SI_ConvertW { tomwalters@268: UINT m_uCodePage; tomwalters@268: protected: tomwalters@268: SI_ConvertW() { } tomwalters@268: public: tomwalters@268: SI_ConvertW(bool a_bStoreIsUtf8) { tomwalters@268: m_uCodePage = a_bStoreIsUtf8 ? CP_UTF8 : CP_ACP; tomwalters@268: } tomwalters@268: tomwalters@268: /* copy and assignment */ tomwalters@268: SI_ConvertW(const SI_ConvertW & rhs) { operator=(rhs); } tomwalters@268: SI_ConvertW & operator=(const SI_ConvertW & rhs) { tomwalters@268: m_uCodePage = rhs.m_uCodePage; tomwalters@268: return *this; tomwalters@268: } tomwalters@268: tomwalters@268: /** Calculate the number of SI_CHAR required for converting the input tomwalters@268: * from the storage format. The storage format is always UTF-8 or MBCS. tomwalters@268: * tomwalters@268: * @param a_pInputData Data in storage format to be converted to SI_CHAR. tomwalters@268: * @param a_uInputDataLen Length of storage format data in bytes. This tomwalters@268: * must be the actual length of the data, including tomwalters@268: * NULL byte if NULL terminated string is required. tomwalters@268: * @return Number of SI_CHAR required by the string when tomwalters@268: * converted. If there are embedded NULL bytes in the tomwalters@268: * input data, only the string up and not including tomwalters@268: * the NULL byte will be converted. tomwalters@268: * @return -1 cast to size_t on a conversion error. tomwalters@268: */ tomwalters@268: size_t SizeFromStore( tomwalters@268: const char * a_pInputData, tomwalters@268: size_t a_uInputDataLen) tomwalters@268: { tomwalters@268: SI_ASSERT(a_uInputDataLen != (size_t) -1); tomwalters@268: tomwalters@268: int retval = MultiByteToWideChar( tomwalters@268: m_uCodePage, 0, tomwalters@268: a_pInputData, (int) a_uInputDataLen, tomwalters@268: 0, 0); tomwalters@268: return (size_t)(retval > 0 ? retval : -1); tomwalters@268: } tomwalters@268: tomwalters@268: /** Convert the input string from the storage format to SI_CHAR. tomwalters@268: * The storage format is always UTF-8 or MBCS. tomwalters@268: * tomwalters@268: * @param a_pInputData Data in storage format to be converted to SI_CHAR. tomwalters@268: * @param a_uInputDataLen Length of storage format data in bytes. This tomwalters@268: * must be the actual length of the data, including tomwalters@268: * NULL byte if NULL terminated string is required. tomwalters@268: * @param a_pOutputData Pointer to the output buffer to received the tomwalters@268: * converted data. tomwalters@268: * @param a_uOutputDataSize Size of the output buffer in SI_CHAR. tomwalters@268: * @return true if all of the input data was successfully tomwalters@268: * converted. tomwalters@268: */ tomwalters@268: bool ConvertFromStore( tomwalters@268: const char * a_pInputData, tomwalters@268: size_t a_uInputDataLen, tomwalters@268: SI_CHAR * a_pOutputData, tomwalters@268: size_t a_uOutputDataSize) tomwalters@268: { tomwalters@268: int nSize = MultiByteToWideChar( tomwalters@268: m_uCodePage, 0, tomwalters@268: a_pInputData, (int) a_uInputDataLen, tomwalters@268: (wchar_t *) a_pOutputData, (int) a_uOutputDataSize); tomwalters@268: return (nSize > 0); tomwalters@268: } tomwalters@268: tomwalters@268: /** Calculate the number of char required by the storage format of this tomwalters@268: * data. The storage format is always UTF-8. tomwalters@268: * tomwalters@268: * @param a_pInputData NULL terminated string to calculate the number of tomwalters@268: * bytes required to be converted to storage format. tomwalters@268: * @return Number of bytes required by the string when tomwalters@268: * converted to storage format. This size always tomwalters@268: * includes space for the terminating NULL character. tomwalters@268: * @return -1 cast to size_t on a conversion error. tomwalters@268: */ tomwalters@268: size_t SizeToStore( tomwalters@268: const SI_CHAR * a_pInputData) tomwalters@268: { tomwalters@268: int retval = WideCharToMultiByte( tomwalters@268: m_uCodePage, 0, tomwalters@268: (const wchar_t *) a_pInputData, -1, tomwalters@268: 0, 0, 0, 0); tomwalters@268: return (size_t) (retval > 0 ? retval : -1); tomwalters@268: } tomwalters@268: tomwalters@268: /** Convert the input string to the storage format of this data. tomwalters@268: * The storage format is always UTF-8 or MBCS. tomwalters@268: * tomwalters@268: * @param a_pInputData NULL terminated source string to convert. All of tomwalters@268: * the data will be converted including the tomwalters@268: * terminating NULL character. tomwalters@268: * @param a_pOutputData Pointer to the buffer to receive the converted tomwalters@268: * string. tomwalters@268: * @param a_pOutputDataSize Size of the output buffer in char. tomwalters@268: * @return true if all of the input data, including the tomwalters@268: * terminating NULL character was successfully tomwalters@268: * converted. tomwalters@268: */ tomwalters@268: bool ConvertToStore( tomwalters@268: const SI_CHAR * a_pInputData, tomwalters@268: char * a_pOutputData, tomwalters@268: size_t a_uOutputDataSize) tomwalters@268: { tomwalters@268: int retval = WideCharToMultiByte( tomwalters@268: m_uCodePage, 0, tomwalters@268: (const wchar_t *) a_pInputData, -1, tomwalters@268: a_pOutputData, (int) a_uOutputDataSize, 0, 0); tomwalters@268: return retval > 0; tomwalters@268: } tomwalters@268: }; tomwalters@268: tomwalters@268: #endif // SI_CONVERT_WIN32 tomwalters@268: tomwalters@268: tomwalters@268: // --------------------------------------------------------------------------- tomwalters@268: // TYPE DEFINITIONS tomwalters@268: // --------------------------------------------------------------------------- tomwalters@268: tomwalters@268: typedef CSimpleIniTempl,SI_ConvertA > CSimpleIniA; tomwalters@268: typedef CSimpleIniTempl,SI_ConvertA > CSimpleIniCaseA; tomwalters@268: tomwalters@268: #if defined(SI_CONVERT_ICU) tomwalters@268: typedef CSimpleIniTempl,SI_ConvertW > CSimpleIniW; tomwalters@268: typedef CSimpleIniTempl,SI_ConvertW > CSimpleIniCaseW; tomwalters@268: #else tomwalters@268: typedef CSimpleIniTempl,SI_ConvertW > CSimpleIniW; tomwalters@268: typedef CSimpleIniTempl,SI_ConvertW > CSimpleIniCaseW; tomwalters@268: #endif tomwalters@268: tomwalters@268: #ifdef _UNICODE tomwalters@268: # define CSimpleIni CSimpleIniW tomwalters@268: # define CSimpleIniCase CSimpleIniCaseW tomwalters@268: # define SI_NEWLINE SI_NEWLINE_W tomwalters@268: #else // !_UNICODE tomwalters@268: # define CSimpleIni CSimpleIniA tomwalters@268: # define CSimpleIniCase CSimpleIniCaseA tomwalters@268: # define SI_NEWLINE SI_NEWLINE_A tomwalters@268: #endif // _UNICODE tomwalters@268: tomwalters@268: #ifdef _MSC_VER tomwalters@268: # pragma warning (pop) tomwalters@268: #endif tomwalters@268: tomwalters@268: #endif // INCLUDED_SimpleIni_h tomwalters@268: