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