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