1 | /** @mainpage |
---|
2 | |
---|
3 | <table> |
---|
4 | <tr><th>Library <td>SimpleIni |
---|
5 | <tr><th>File <td>SimpleIni.h |
---|
6 | <tr><th>Author <td>Brodie Thiesfield [code at jellycan dot com] |
---|
7 | <tr><th>Source <td>https://github.com/brofield/simpleini |
---|
8 | <tr><th>Version <td>4.17 |
---|
9 | </table> |
---|
10 | |
---|
11 | Jump to the @link CSimpleIniTempl CSimpleIni @endlink interface documentation. |
---|
12 | |
---|
13 | @section intro INTRODUCTION |
---|
14 | |
---|
15 | This component allows an INI-style configuration file to be used on both |
---|
16 | Windows and Linux/Unix. It is fast, simple and source code using this |
---|
17 | component will compile unchanged on either OS. |
---|
18 | |
---|
19 | |
---|
20 | @section features FEATURES |
---|
21 | |
---|
22 | - MIT Licence allows free use in all software (including GPL and commercial) |
---|
23 | - multi-platform (Windows 95/98/ME/NT/2K/XP/2003, Windows CE, Linux, Unix) |
---|
24 | - loading and saving of INI-style configuration files |
---|
25 | - configuration files can have any newline format on all platforms |
---|
26 | - liberal acceptance of file format |
---|
27 | - key/values with no section |
---|
28 | - removal of whitespace around sections, keys and values |
---|
29 | - support for multi-line values (values with embedded newline characters) |
---|
30 | - optional support for multiple keys with the same name |
---|
31 | - optional case-insensitive sections and keys (for ASCII characters only) |
---|
32 | - saves files with sections and keys in the same order as they were loaded |
---|
33 | - preserves comments on the file, section and keys where possible. |
---|
34 | - supports both char or wchar_t programming interfaces |
---|
35 | - supports both MBCS (system locale) and UTF-8 file encodings |
---|
36 | - system locale does not need to be UTF-8 on Linux/Unix to load UTF-8 file |
---|
37 | - support for non-ASCII characters in section, keys, values and comments |
---|
38 | - support for non-standard character types or file encodings |
---|
39 | via user-written converter classes |
---|
40 | - support for adding/modifying values programmatically |
---|
41 | - compiles cleanly in the following compilers: |
---|
42 | - Windows/VC6 (warning level 3) |
---|
43 | - Windows/VC.NET 2003 (warning level 4) |
---|
44 | - Windows/VC 2005 (warning level 4) |
---|
45 | - Linux/gcc (-Wall) |
---|
46 | |
---|
47 | |
---|
48 | @section usage USAGE SUMMARY |
---|
49 | |
---|
50 | -# Define the appropriate symbol for the converter you wish to use and |
---|
51 | include the SimpleIni.h header file. If no specific converter is defined |
---|
52 | then the default converter is used. The default conversion mode uses |
---|
53 | SI_CONVERT_WIN32 on Windows and SI_CONVERT_GENERIC on all other |
---|
54 | platforms. If you are using ICU then SI_CONVERT_ICU is supported on all |
---|
55 | platforms. |
---|
56 | -# Declare an instance the appropriate class. Note that the following |
---|
57 | definitions are just shortcuts for commonly used types. Other types |
---|
58 | (PRUnichar, unsigned short, unsigned char) are also possible. |
---|
59 | <table> |
---|
60 | <tr><th>Interface <th>Case-sensitive <th>Load UTF-8 <th>Load MBCS <th>Typedef |
---|
61 | <tr><th>SI_CONVERT_GENERIC |
---|
62 | <tr><td>char <td>No <td>Yes <td>Yes #1 <td>CSimpleIniA |
---|
63 | <tr><td>char <td>Yes <td>Yes <td>Yes <td>CSimpleIniCaseA |
---|
64 | <tr><td>wchar_t <td>No <td>Yes <td>Yes <td>CSimpleIniW |
---|
65 | <tr><td>wchar_t <td>Yes <td>Yes <td>Yes <td>CSimpleIniCaseW |
---|
66 | <tr><th>SI_CONVERT_WIN32 |
---|
67 | <tr><td>char <td>No <td>No #2 <td>Yes <td>CSimpleIniA |
---|
68 | <tr><td>char <td>Yes <td>Yes <td>Yes <td>CSimpleIniCaseA |
---|
69 | <tr><td>wchar_t <td>No <td>Yes <td>Yes <td>CSimpleIniW |
---|
70 | <tr><td>wchar_t <td>Yes <td>Yes <td>Yes <td>CSimpleIniCaseW |
---|
71 | <tr><th>SI_CONVERT_ICU |
---|
72 | <tr><td>char <td>No <td>Yes <td>Yes <td>CSimpleIniA |
---|
73 | <tr><td>char <td>Yes <td>Yes <td>Yes <td>CSimpleIniCaseA |
---|
74 | <tr><td>UChar <td>No <td>Yes <td>Yes <td>CSimpleIniW |
---|
75 | <tr><td>UChar <td>Yes <td>Yes <td>Yes <td>CSimpleIniCaseW |
---|
76 | </table> |
---|
77 | #1 On Windows you are better to use CSimpleIniA with SI_CONVERT_WIN32.<br> |
---|
78 | #2 Only affects Windows. On Windows this uses MBCS functions and |
---|
79 | so may fold case incorrectly leading to uncertain results. |
---|
80 | -# Call LoadData() or LoadFile() to load and parse the INI configuration file |
---|
81 | -# Access and modify the data of the file using the following functions |
---|
82 | <table> |
---|
83 | <tr><td>GetAllSections <td>Return all section names |
---|
84 | <tr><td>GetAllKeys <td>Return all key names within a section |
---|
85 | <tr><td>GetAllValues <td>Return all values within a section & key |
---|
86 | <tr><td>GetSection <td>Return all key names and values in a section |
---|
87 | <tr><td>GetSectionSize <td>Return the number of keys in a section |
---|
88 | <tr><td>GetValue <td>Return a value for a section & key |
---|
89 | <tr><td>SetValue <td>Add or update a value for a section & key |
---|
90 | <tr><td>Delete <td>Remove a section, or a key from a section |
---|
91 | </table> |
---|
92 | -# Call Save() or SaveFile() to save the INI configuration data |
---|
93 | |
---|
94 | @section iostreams IO STREAMS |
---|
95 | |
---|
96 | SimpleIni supports reading from and writing to STL IO streams. Enable this |
---|
97 | by defining SI_SUPPORT_IOSTREAMS before including the SimpleIni.h header |
---|
98 | file. Ensure that if the streams are backed by a file (e.g. ifstream or |
---|
99 | ofstream) then the flag ios_base::binary has been used when the file was |
---|
100 | opened. |
---|
101 | |
---|
102 | @section multiline MULTI-LINE VALUES |
---|
103 | |
---|
104 | Values that span multiple lines are created using the following format. |
---|
105 | |
---|
106 | <pre> |
---|
107 | key = <<<ENDTAG |
---|
108 | .... multiline value .... |
---|
109 | ENDTAG |
---|
110 | </pre> |
---|
111 | |
---|
112 | Note the following: |
---|
113 | - The text used for ENDTAG can be anything and is used to find |
---|
114 | where the multi-line text ends. |
---|
115 | - The newline after ENDTAG in the start tag, and the newline |
---|
116 | before ENDTAG in the end tag is not included in the data value. |
---|
117 | - The ending tag must be on it's own line with no whitespace before |
---|
118 | or after it. |
---|
119 | - The multi-line value is modified at load so that each line in the value |
---|
120 | is delimited by a single '\\n' character on all platforms. At save time |
---|
121 | it will be converted into the newline format used by the current |
---|
122 | platform. |
---|
123 | |
---|
124 | @section comments COMMENTS |
---|
125 | |
---|
126 | Comments are preserved in the file within the following restrictions: |
---|
127 | - Every file may have a single "file comment". It must start with the |
---|
128 | first character in the file, and will end with the first non-comment |
---|
129 | line in the file. |
---|
130 | - Every section may have a single "section comment". It will start |
---|
131 | with the first comment line following the file comment, or the last |
---|
132 | data entry. It ends at the beginning of the section. |
---|
133 | - Every key may have a single "key comment". This comment will start |
---|
134 | with the first comment line following the section start, or the file |
---|
135 | comment if there is no section name. |
---|
136 | - Comments are set at the time that the file, section or key is first |
---|
137 | created. The only way to modify a comment on a section or a key is to |
---|
138 | delete that entry and recreate it with the new comment. There is no |
---|
139 | way to change the file comment. |
---|
140 | |
---|
141 | @section save SAVE ORDER |
---|
142 | |
---|
143 | The sections and keys are written out in the same order as they were |
---|
144 | read in from the file. Sections and keys added to the data after the |
---|
145 | file has been loaded will be added to the end of the file when it is |
---|
146 | written. There is no way to specify the location of a section or key |
---|
147 | other than in first-created, first-saved order. |
---|
148 | |
---|
149 | @section notes NOTES |
---|
150 | |
---|
151 | - To load UTF-8 data on Windows 95, you need to use Microsoft Layer for |
---|
152 | Unicode, or SI_CONVERT_GENERIC, or SI_CONVERT_ICU. |
---|
153 | - When using SI_CONVERT_GENERIC, ConvertUTF.c must be compiled and linked. |
---|
154 | - When using SI_CONVERT_ICU, ICU header files must be on the include |
---|
155 | path and icuuc.lib must be linked in. |
---|
156 | - To load a UTF-8 file on Windows AND expose it with SI_CHAR == char, |
---|
157 | you should use SI_CONVERT_GENERIC. |
---|
158 | - The collation (sorting) order used for sections and keys returned from |
---|
159 | iterators is NOT DEFINED. If collation order of the text is important |
---|
160 | then it should be done yourself by either supplying a replacement |
---|
161 | SI_STRLESS class, or by sorting the strings external to this library. |
---|
162 | - Usage of the <mbstring.h> header on Windows can be disabled by defining |
---|
163 | SI_NO_MBCS. This is defined automatically on Windows CE platforms. |
---|
164 | |
---|
165 | @section contrib CONTRIBUTIONS |
---|
166 | |
---|
167 | - 2010/05/03: Tobias Gehrig: added GetDoubleValue() |
---|
168 | |
---|
169 | @section licence MIT LICENCE |
---|
170 | |
---|
171 | The licence text below is the boilerplate "MIT Licence" used from: |
---|
172 | http://www.opensource.org/licenses/mit-license.php |
---|
173 | |
---|
174 | Copyright (c) 2006-2012, Brodie Thiesfield |
---|
175 | |
---|
176 | Permission is hereby granted, free of charge, to any person obtaining a copy |
---|
177 | of this software and associated documentation files (the "Software"), to deal |
---|
178 | in the Software without restriction, including without limitation the rights |
---|
179 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
---|
180 | copies of the Software, and to permit persons to whom the Software is furnished |
---|
181 | to do so, subject to the following conditions: |
---|
182 | |
---|
183 | The above copyright notice and this permission notice shall be included in |
---|
184 | all copies or substantial portions of the Software. |
---|
185 | |
---|
186 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
---|
187 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS |
---|
188 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR |
---|
189 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER |
---|
190 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN |
---|
191 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
---|
192 | */ |
---|
193 | |
---|
194 | #ifndef INCLUDED_SimpleIni_h |
---|
195 | #define INCLUDED_SimpleIni_h |
---|
196 | |
---|
197 | #if defined(_MSC_VER) && (_MSC_VER >= 1020) |
---|
198 | # pragma once |
---|
199 | #endif |
---|
200 | |
---|
201 | // Disable these warnings in MSVC: |
---|
202 | // 4127 "conditional expression is constant" as the conversion classes trigger |
---|
203 | // it with the statement if (sizeof(SI_CHAR) == sizeof(char)). This test will |
---|
204 | // be optimized away in a release build. |
---|
205 | // 4503 'insert' : decorated name length exceeded, name was truncated |
---|
206 | // 4702 "unreachable code" as the MS STL header causes it in release mode. |
---|
207 | // Again, the code causing the warning will be cleaned up by the compiler. |
---|
208 | // 4786 "identifier truncated to 256 characters" as this is thrown hundreds |
---|
209 | // of times VC6 as soon as STL is used. |
---|
210 | #ifdef _MSC_VER |
---|
211 | # pragma warning (push) |
---|
212 | # pragma warning (disable: 4127 4503 4702 4786) |
---|
213 | #endif |
---|
214 | |
---|
215 | #include <cstring> |
---|
216 | #include <string> |
---|
217 | #include <map> |
---|
218 | #include <list> |
---|
219 | #include <algorithm> |
---|
220 | #include <stdlib.h> |
---|
221 | #include <stdio.h> |
---|
222 | |
---|
223 | #ifdef SI_SUPPORT_IOSTREAMS |
---|
224 | # include <iostream> |
---|
225 | #endif // SI_SUPPORT_IOSTREAMS |
---|
226 | |
---|
227 | #ifdef _DEBUG |
---|
228 | # ifndef assert |
---|
229 | # include <cassert> |
---|
230 | # endif |
---|
231 | # define SI_ASSERT(x) assert(x) |
---|
232 | #else |
---|
233 | # define SI_ASSERT(x) |
---|
234 | #endif |
---|
235 | |
---|
236 | enum SI_Error { |
---|
237 | SI_OK = 0, //!< No error |
---|
238 | SI_UPDATED = 1, //!< An existing value was updated |
---|
239 | SI_INSERTED = 2, //!< A new value was inserted |
---|
240 | |
---|
241 | // note: test for any error with (retval < 0) |
---|
242 | SI_FAIL = -1, //!< Generic failure |
---|
243 | SI_NOMEM = -2, //!< Out of memory error |
---|
244 | SI_FILE = -3 //!< File error (see errno for detail error) |
---|
245 | }; |
---|
246 | |
---|
247 | #define SI_UTF8_SIGNATURE "\xEF\xBB\xBF" |
---|
248 | |
---|
249 | #ifdef _WIN32 |
---|
250 | # define SI_NEWLINE_A "\r\n" |
---|
251 | # define SI_NEWLINE_W L"\r\n" |
---|
252 | #else // !_WIN32 |
---|
253 | # define SI_NEWLINE_A "\n" |
---|
254 | # define SI_NEWLINE_W L"\n" |
---|
255 | #endif // _WIN32 |
---|
256 | |
---|
257 | #if defined(SI_CONVERT_ICU) |
---|
258 | # include <unicode/ustring.h> |
---|
259 | #endif |
---|
260 | |
---|
261 | #if defined(_WIN32) |
---|
262 | # define SI_HAS_WIDE_FILE |
---|
263 | # define SI_WCHAR_T wchar_t |
---|
264 | #elif defined(SI_CONVERT_ICU) |
---|
265 | # define SI_HAS_WIDE_FILE |
---|
266 | # define SI_WCHAR_T UChar |
---|
267 | #endif |
---|
268 | |
---|
269 | |
---|
270 | // --------------------------------------------------------------------------- |
---|
271 | // MAIN TEMPLATE CLASS |
---|
272 | // --------------------------------------------------------------------------- |
---|
273 | |
---|
274 | /** Simple INI file reader. |
---|
275 | |
---|
276 | This can be instantiated with the choice of unicode or native characterset, |
---|
277 | and case sensitive or insensitive comparisons of section and key names. |
---|
278 | The supported combinations are pre-defined with the following typedefs: |
---|
279 | |
---|
280 | <table> |
---|
281 | <tr><th>Interface <th>Case-sensitive <th>Typedef |
---|
282 | <tr><td>char <td>No <td>CSimpleIniA |
---|
283 | <tr><td>char <td>Yes <td>CSimpleIniCaseA |
---|
284 | <tr><td>wchar_t <td>No <td>CSimpleIniW |
---|
285 | <tr><td>wchar_t <td>Yes <td>CSimpleIniCaseW |
---|
286 | </table> |
---|
287 | |
---|
288 | Note that using other types for the SI_CHAR is supported. For instance, |
---|
289 | unsigned char, unsigned short, etc. Note that where the alternative type |
---|
290 | is a different size to char/wchar_t you may need to supply new helper |
---|
291 | classes for SI_STRLESS and SI_CONVERTER. |
---|
292 | */ |
---|
293 | template<class SI_CHAR, class SI_STRLESS, class SI_CONVERTER> |
---|
294 | class CSimpleIniTempl |
---|
295 | { |
---|
296 | public: |
---|
297 | typedef SI_CHAR SI_CHAR_T; |
---|
298 | |
---|
299 | /** key entry */ |
---|
300 | struct Entry { |
---|
301 | const SI_CHAR * pItem; |
---|
302 | const SI_CHAR * pComment; |
---|
303 | int nOrder; |
---|
304 | |
---|
305 | Entry(const SI_CHAR * a_pszItem = NULL, int a_nOrder = 0) |
---|
306 | : pItem(a_pszItem) |
---|
307 | , pComment(NULL) |
---|
308 | , nOrder(a_nOrder) |
---|
309 | { } |
---|
310 | Entry(const SI_CHAR * a_pszItem, const SI_CHAR * a_pszComment, int a_nOrder) |
---|
311 | : pItem(a_pszItem) |
---|
312 | , pComment(a_pszComment) |
---|
313 | , nOrder(a_nOrder) |
---|
314 | { } |
---|
315 | Entry(const Entry & rhs) { operator=(rhs); } |
---|
316 | Entry & operator=(const Entry & rhs) { |
---|
317 | pItem = rhs.pItem; |
---|
318 | pComment = rhs.pComment; |
---|
319 | nOrder = rhs.nOrder; |
---|
320 | return *this; |
---|
321 | } |
---|
322 | |
---|
323 | #if defined(_MSC_VER) && _MSC_VER <= 1200 |
---|
324 | /** STL of VC6 doesn't allow me to specify my own comparator for list::sort() */ |
---|
325 | bool operator<(const Entry & rhs) const { return LoadOrder()(*this, rhs); } |
---|
326 | bool operator>(const Entry & rhs) const { return LoadOrder()(rhs, *this); } |
---|
327 | #endif |
---|
328 | |
---|
329 | /** Strict less ordering by name of key only */ |
---|
330 | struct KeyOrder : std::binary_function<Entry, Entry, bool> { |
---|
331 | bool operator()(const Entry & lhs, const Entry & rhs) const { |
---|
332 | const static SI_STRLESS isLess = SI_STRLESS(); |
---|
333 | return isLess(lhs.pItem, rhs.pItem); |
---|
334 | } |
---|
335 | }; |
---|
336 | |
---|
337 | /** Strict less ordering by order, and then name of key */ |
---|
338 | struct LoadOrder : std::binary_function<Entry, Entry, bool> { |
---|
339 | bool operator()(const Entry & lhs, const Entry & rhs) const { |
---|
340 | if (lhs.nOrder != rhs.nOrder) { |
---|
341 | return lhs.nOrder < rhs.nOrder; |
---|
342 | } |
---|
343 | return KeyOrder()(lhs.pItem, rhs.pItem); |
---|
344 | } |
---|
345 | }; |
---|
346 | }; |
---|
347 | |
---|
348 | /** map keys to values */ |
---|
349 | typedef std::multimap<Entry,const SI_CHAR *,typename Entry::KeyOrder> TKeyVal; |
---|
350 | |
---|
351 | /** map sections to key/value map */ |
---|
352 | typedef std::map<Entry,TKeyVal,typename Entry::KeyOrder> TSection; |
---|
353 | |
---|
354 | /** set of dependent string pointers. Note that these pointers are |
---|
355 | dependent on memory owned by CSimpleIni. |
---|
356 | */ |
---|
357 | typedef std::list<Entry> TNamesDepend; |
---|
358 | |
---|
359 | /** interface definition for the OutputWriter object to pass to Save() |
---|
360 | in order to output the INI file data. |
---|
361 | */ |
---|
362 | class OutputWriter { |
---|
363 | public: |
---|
364 | OutputWriter() { } |
---|
365 | virtual ~OutputWriter() { } |
---|
366 | virtual void Write(const char * a_pBuf) = 0; |
---|
367 | private: |
---|
368 | OutputWriter(const OutputWriter &); // disable |
---|
369 | OutputWriter & operator=(const OutputWriter &); // disable |
---|
370 | }; |
---|
371 | |
---|
372 | /** OutputWriter class to write the INI data to a file */ |
---|
373 | class FileWriter : public OutputWriter { |
---|
374 | FILE * m_file; |
---|
375 | public: |
---|
376 | FileWriter(FILE * a_file) : m_file(a_file) { } |
---|
377 | void Write(const char * a_pBuf) { |
---|
378 | fputs(a_pBuf, m_file); |
---|
379 | } |
---|
380 | private: |
---|
381 | FileWriter(const FileWriter &); // disable |
---|
382 | FileWriter & operator=(const FileWriter &); // disable |
---|
383 | }; |
---|
384 | |
---|
385 | /** OutputWriter class to write the INI data to a string */ |
---|
386 | class StringWriter : public OutputWriter { |
---|
387 | std::string & m_string; |
---|
388 | public: |
---|
389 | StringWriter(std::string & a_string) : m_string(a_string) { } |
---|
390 | void Write(const char * a_pBuf) { |
---|
391 | m_string.append(a_pBuf); |
---|
392 | } |
---|
393 | private: |
---|
394 | StringWriter(const StringWriter &); // disable |
---|
395 | StringWriter & operator=(const StringWriter &); // disable |
---|
396 | }; |
---|
397 | |
---|
398 | #ifdef SI_SUPPORT_IOSTREAMS |
---|
399 | /** OutputWriter class to write the INI data to an ostream */ |
---|
400 | class StreamWriter : public OutputWriter { |
---|
401 | std::ostream & m_ostream; |
---|
402 | public: |
---|
403 | StreamWriter(std::ostream & a_ostream) : m_ostream(a_ostream) { } |
---|
404 | void Write(const char * a_pBuf) { |
---|
405 | m_ostream << a_pBuf; |
---|
406 | } |
---|
407 | private: |
---|
408 | StreamWriter(const StreamWriter &); // disable |
---|
409 | StreamWriter & operator=(const StreamWriter &); // disable |
---|
410 | }; |
---|
411 | #endif // SI_SUPPORT_IOSTREAMS |
---|
412 | |
---|
413 | /** Characterset conversion utility class to convert strings to the |
---|
414 | same format as is used for the storage. |
---|
415 | */ |
---|
416 | class Converter : private SI_CONVERTER { |
---|
417 | public: |
---|
418 | Converter(bool a_bStoreIsUtf8) : SI_CONVERTER(a_bStoreIsUtf8) { |
---|
419 | m_scratch.resize(1024); |
---|
420 | } |
---|
421 | Converter(const Converter & rhs) { operator=(rhs); } |
---|
422 | Converter & operator=(const Converter & rhs) { |
---|
423 | m_scratch = rhs.m_scratch; |
---|
424 | return *this; |
---|
425 | } |
---|
426 | bool ConvertToStore(const SI_CHAR * a_pszString) { |
---|
427 | size_t uLen = SI_CONVERTER::SizeToStore(a_pszString); |
---|
428 | if (uLen == (size_t)(-1)) { |
---|
429 | return false; |
---|
430 | } |
---|
431 | while (uLen > m_scratch.size()) { |
---|
432 | m_scratch.resize(m_scratch.size() * 2); |
---|
433 | } |
---|
434 | return SI_CONVERTER::ConvertToStore( |
---|
435 | a_pszString, |
---|
436 | const_cast<char*>(m_scratch.data()), |
---|
437 | m_scratch.size()); |
---|
438 | } |
---|
439 | const char * Data() { return m_scratch.data(); } |
---|
440 | private: |
---|
441 | std::string m_scratch; |
---|
442 | }; |
---|
443 | |
---|
444 | public: |
---|
445 | /*-----------------------------------------------------------------------*/ |
---|
446 | |
---|
447 | /** Default constructor. |
---|
448 | |
---|
449 | @param a_bIsUtf8 See the method SetUnicode() for details. |
---|
450 | @param a_bMultiKey See the method SetMultiKey() for details. |
---|
451 | @param a_bMultiLine See the method SetMultiLine() for details. |
---|
452 | */ |
---|
453 | CSimpleIniTempl( |
---|
454 | bool a_bIsUtf8 = false, |
---|
455 | bool a_bMultiKey = false, |
---|
456 | bool a_bMultiLine = false |
---|
457 | ); |
---|
458 | |
---|
459 | /** Destructor */ |
---|
460 | ~CSimpleIniTempl(); |
---|
461 | |
---|
462 | /** Deallocate all memory stored by this object */ |
---|
463 | void Reset(); |
---|
464 | |
---|
465 | /** Has any data been loaded */ |
---|
466 | bool IsEmpty() const { return m_data.empty(); } |
---|
467 | |
---|
468 | /*-----------------------------------------------------------------------*/ |
---|
469 | /** @{ @name Settings */ |
---|
470 | |
---|
471 | /** Set the storage format of the INI data. This affects both the loading |
---|
472 | and saving of the INI data using all of the Load/Save API functions. |
---|
473 | This value cannot be changed after any INI data has been loaded. |
---|
474 | |
---|
475 | If the file is not set to Unicode (UTF-8), then the data encoding is |
---|
476 | assumed to be the OS native encoding. This encoding is the system |
---|
477 | locale on Linux/Unix and the legacy MBCS encoding on Windows NT/2K/XP. |
---|
478 | If the storage format is set to Unicode then the file will be loaded |
---|
479 | as UTF-8 encoded data regardless of the native file encoding. If |
---|
480 | SI_CHAR == char then all of the char* parameters take and return UTF-8 |
---|
481 | encoded data regardless of the system locale. |
---|
482 | |
---|
483 | \param a_bIsUtf8 Assume UTF-8 encoding for the source? |
---|
484 | */ |
---|
485 | void SetUnicode(bool a_bIsUtf8 = true) { |
---|
486 | if (!m_pData) m_bStoreIsUtf8 = a_bIsUtf8; |
---|
487 | } |
---|
488 | |
---|
489 | /** Get the storage format of the INI data. */ |
---|
490 | bool IsUnicode() const { return m_bStoreIsUtf8; } |
---|
491 | |
---|
492 | /** Should multiple identical keys be permitted in the file. If set to false |
---|
493 | then the last value encountered will be used as the value of the key. |
---|
494 | If set to true, then all values will be available to be queried. For |
---|
495 | example, with the following input: |
---|
496 | |
---|
497 | <pre> |
---|
498 | [section] |
---|
499 | test=value1 |
---|
500 | test=value2 |
---|
501 | </pre> |
---|
502 | |
---|
503 | Then with SetMultiKey(true), both of the values "value1" and "value2" |
---|
504 | will be returned for the key test. If SetMultiKey(false) is used, then |
---|
505 | the value for "test" will only be "value2". This value may be changed |
---|
506 | at any time. |
---|
507 | |
---|
508 | \param a_bAllowMultiKey Allow multi-keys in the source? |
---|
509 | */ |
---|
510 | void SetMultiKey(bool a_bAllowMultiKey = true) { |
---|
511 | m_bAllowMultiKey = a_bAllowMultiKey; |
---|
512 | } |
---|
513 | |
---|
514 | /** Get the storage format of the INI data. */ |
---|
515 | bool IsMultiKey() const { return m_bAllowMultiKey; } |
---|
516 | |
---|
517 | /** Should data values be permitted to span multiple lines in the file. If |
---|
518 | set to false then the multi-line construct <<<TAG as a value will be |
---|
519 | returned as is instead of loading the data. This value may be changed |
---|
520 | at any time. |
---|
521 | |
---|
522 | \param a_bAllowMultiLine Allow multi-line values in the source? |
---|
523 | */ |
---|
524 | void SetMultiLine(bool a_bAllowMultiLine = true) { |
---|
525 | m_bAllowMultiLine = a_bAllowMultiLine; |
---|
526 | } |
---|
527 | |
---|
528 | /** Query the status of multi-line data */ |
---|
529 | bool IsMultiLine() const { return m_bAllowMultiLine; } |
---|
530 | |
---|
531 | /** Should spaces be added around the equals sign when writing key/value |
---|
532 | pairs out. When true, the result will be "key = value". When false, |
---|
533 | the result will be "key=value". This value may be changed at any time. |
---|
534 | |
---|
535 | \param a_bSpaces Add spaces around the equals sign? |
---|
536 | */ |
---|
537 | void SetSpaces(bool a_bSpaces = true) { |
---|
538 | m_bSpaces = a_bSpaces; |
---|
539 | } |
---|
540 | |
---|
541 | /** Query the status of spaces output */ |
---|
542 | bool UsingSpaces() const { return m_bSpaces; } |
---|
543 | |
---|
544 | /*-----------------------------------------------------------------------*/ |
---|
545 | /** @} |
---|
546 | @{ @name Loading INI Data */ |
---|
547 | |
---|
548 | /** Load an INI file from disk into memory |
---|
549 | |
---|
550 | @param a_pszFile Path of the file to be loaded. This will be passed |
---|
551 | to fopen() and so must be a valid path for the |
---|
552 | current platform. |
---|
553 | |
---|
554 | @return SI_Error See error definitions |
---|
555 | */ |
---|
556 | SI_Error LoadFile( |
---|
557 | const char * a_pszFile |
---|
558 | ); |
---|
559 | |
---|
560 | #ifdef SI_HAS_WIDE_FILE |
---|
561 | /** Load an INI file from disk into memory |
---|
562 | |
---|
563 | @param a_pwszFile Path of the file to be loaded in UTF-16. |
---|
564 | |
---|
565 | @return SI_Error See error definitions |
---|
566 | */ |
---|
567 | SI_Error LoadFile( |
---|
568 | const SI_WCHAR_T * a_pwszFile |
---|
569 | ); |
---|
570 | #endif // SI_HAS_WIDE_FILE |
---|
571 | |
---|
572 | /** Load the file from a file pointer. |
---|
573 | |
---|
574 | @param a_fpFile Valid file pointer to read the file data from. The |
---|
575 | file will be read until end of file. |
---|
576 | |
---|
577 | @return SI_Error See error definitions |
---|
578 | */ |
---|
579 | SI_Error LoadFile( |
---|
580 | FILE * a_fpFile |
---|
581 | ); |
---|
582 | |
---|
583 | #ifdef SI_SUPPORT_IOSTREAMS |
---|
584 | /** Load INI file data from an istream. |
---|
585 | |
---|
586 | @param a_istream Stream to read from |
---|
587 | |
---|
588 | @return SI_Error See error definitions |
---|
589 | */ |
---|
590 | SI_Error LoadData( |
---|
591 | std::istream & a_istream |
---|
592 | ); |
---|
593 | #endif // SI_SUPPORT_IOSTREAMS |
---|
594 | |
---|
595 | /** Load INI file data direct from a std::string |
---|
596 | |
---|
597 | @param a_strData Data to be loaded |
---|
598 | |
---|
599 | @return SI_Error See error definitions |
---|
600 | */ |
---|
601 | SI_Error LoadData(const std::string & a_strData) { |
---|
602 | return LoadData(a_strData.c_str(), a_strData.size()); |
---|
603 | } |
---|
604 | |
---|
605 | /** Load INI file data direct from memory |
---|
606 | |
---|
607 | @param a_pData Data to be loaded |
---|
608 | @param a_uDataLen Length of the data in bytes |
---|
609 | |
---|
610 | @return SI_Error See error definitions |
---|
611 | */ |
---|
612 | SI_Error LoadData( |
---|
613 | const char * a_pData, |
---|
614 | size_t a_uDataLen |
---|
615 | ); |
---|
616 | |
---|
617 | /*-----------------------------------------------------------------------*/ |
---|
618 | /** @} |
---|
619 | @{ @name Saving INI Data */ |
---|
620 | |
---|
621 | /** Save an INI file from memory to disk |
---|
622 | |
---|
623 | @param a_pszFile Path of the file to be saved. This will be passed |
---|
624 | to fopen() and so must be a valid path for the |
---|
625 | current platform. |
---|
626 | |
---|
627 | @param a_bAddSignature Prepend the UTF-8 BOM if the output data is |
---|
628 | in UTF-8 format. If it is not UTF-8 then |
---|
629 | this parameter is ignored. |
---|
630 | |
---|
631 | @return SI_Error See error definitions |
---|
632 | */ |
---|
633 | SI_Error SaveFile( |
---|
634 | const char * a_pszFile, |
---|
635 | bool a_bAddSignature = true |
---|
636 | ) const; |
---|
637 | |
---|
638 | #ifdef SI_HAS_WIDE_FILE |
---|
639 | /** Save an INI file from memory to disk |
---|
640 | |
---|
641 | @param a_pwszFile Path of the file to be saved in UTF-16. |
---|
642 | |
---|
643 | @param a_bAddSignature Prepend the UTF-8 BOM if the output data is |
---|
644 | in UTF-8 format. If it is not UTF-8 then |
---|
645 | this parameter is ignored. |
---|
646 | |
---|
647 | @return SI_Error See error definitions |
---|
648 | */ |
---|
649 | SI_Error SaveFile( |
---|
650 | const SI_WCHAR_T * a_pwszFile, |
---|
651 | bool a_bAddSignature = true |
---|
652 | ) const; |
---|
653 | #endif // _WIN32 |
---|
654 | |
---|
655 | /** Save the INI data to a file. See Save() for details. |
---|
656 | |
---|
657 | @param a_pFile Handle to a file. File should be opened for |
---|
658 | binary output. |
---|
659 | |
---|
660 | @param a_bAddSignature Prepend the UTF-8 BOM if the output data is in |
---|
661 | UTF-8 format. If it is not UTF-8 then this value is |
---|
662 | ignored. Do not set this to true if anything has |
---|
663 | already been written to the file. |
---|
664 | |
---|
665 | @return SI_Error See error definitions |
---|
666 | */ |
---|
667 | SI_Error SaveFile( |
---|
668 | FILE * a_pFile, |
---|
669 | bool a_bAddSignature = false |
---|
670 | ) const; |
---|
671 | |
---|
672 | /** Save the INI data. The data will be written to the output device |
---|
673 | in a format appropriate to the current data, selected by: |
---|
674 | |
---|
675 | <table> |
---|
676 | <tr><th>SI_CHAR <th>FORMAT |
---|
677 | <tr><td>char <td>same format as when loaded (MBCS or UTF-8) |
---|
678 | <tr><td>wchar_t <td>UTF-8 |
---|
679 | <tr><td>other <td>UTF-8 |
---|
680 | </table> |
---|
681 | |
---|
682 | Note that comments from the original data is preserved as per the |
---|
683 | documentation on comments. The order of the sections and values |
---|
684 | from the original file will be preserved. |
---|
685 | |
---|
686 | Any data prepended or appended to the output device must use the the |
---|
687 | same format (MBCS or UTF-8). You may use the GetConverter() method to |
---|
688 | convert text to the correct format regardless of the output format |
---|
689 | being used by SimpleIni. |
---|
690 | |
---|
691 | To add a BOM to UTF-8 data, write it out manually at the very beginning |
---|
692 | like is done in SaveFile when a_bUseBOM is true. |
---|
693 | |
---|
694 | @param a_oOutput Output writer to write the data to. |
---|
695 | |
---|
696 | @param a_bAddSignature Prepend the UTF-8 BOM if the output data is in |
---|
697 | UTF-8 format. If it is not UTF-8 then this value is |
---|
698 | ignored. Do not set this to true if anything has |
---|
699 | already been written to the OutputWriter. |
---|
700 | |
---|
701 | @return SI_Error See error definitions |
---|
702 | */ |
---|
703 | SI_Error Save( |
---|
704 | OutputWriter & a_oOutput, |
---|
705 | bool a_bAddSignature = false |
---|
706 | ) const; |
---|
707 | |
---|
708 | #ifdef SI_SUPPORT_IOSTREAMS |
---|
709 | /** Save the INI data to an ostream. See Save() for details. |
---|
710 | |
---|
711 | @param a_ostream String to have the INI data appended to. |
---|
712 | |
---|
713 | @param a_bAddSignature Prepend the UTF-8 BOM if the output data is in |
---|
714 | UTF-8 format. If it is not UTF-8 then this value is |
---|
715 | ignored. Do not set this to true if anything has |
---|
716 | already been written to the stream. |
---|
717 | |
---|
718 | @return SI_Error See error definitions |
---|
719 | */ |
---|
720 | SI_Error Save( |
---|
721 | std::ostream & a_ostream, |
---|
722 | bool a_bAddSignature = false |
---|
723 | ) const |
---|
724 | { |
---|
725 | StreamWriter writer(a_ostream); |
---|
726 | return Save(writer, a_bAddSignature); |
---|
727 | } |
---|
728 | #endif // SI_SUPPORT_IOSTREAMS |
---|
729 | |
---|
730 | /** Append the INI data to a string. See Save() for details. |
---|
731 | |
---|
732 | @param a_sBuffer String to have the INI data appended to. |
---|
733 | |
---|
734 | @param a_bAddSignature Prepend the UTF-8 BOM if the output data is in |
---|
735 | UTF-8 format. If it is not UTF-8 then this value is |
---|
736 | ignored. Do not set this to true if anything has |
---|
737 | already been written to the string. |
---|
738 | |
---|
739 | @return SI_Error See error definitions |
---|
740 | */ |
---|
741 | SI_Error Save( |
---|
742 | std::string & a_sBuffer, |
---|
743 | bool a_bAddSignature = false |
---|
744 | ) const |
---|
745 | { |
---|
746 | StringWriter writer(a_sBuffer); |
---|
747 | return Save(writer, a_bAddSignature); |
---|
748 | } |
---|
749 | |
---|
750 | /*-----------------------------------------------------------------------*/ |
---|
751 | /** @} |
---|
752 | @{ @name Accessing INI Data */ |
---|
753 | |
---|
754 | /** Retrieve all section names. The list is returned as an STL vector of |
---|
755 | names and can be iterated or searched as necessary. Note that the |
---|
756 | sort order of the returned strings is NOT DEFINED. You can sort |
---|
757 | the names into the load order if desired. Search this file for ".sort" |
---|
758 | for an example. |
---|
759 | |
---|
760 | NOTE! This structure contains only pointers to strings. The actual |
---|
761 | string data is stored in memory owned by CSimpleIni. Ensure that the |
---|
762 | CSimpleIni object is not destroyed or Reset() while these pointers |
---|
763 | are in use! |
---|
764 | |
---|
765 | @param a_names Vector that will receive all of the section |
---|
766 | names. See note above! |
---|
767 | */ |
---|
768 | void GetAllSections( |
---|
769 | TNamesDepend & a_names |
---|
770 | ) const; |
---|
771 | |
---|
772 | /** Retrieve all unique key names in a section. The sort order of the |
---|
773 | returned strings is NOT DEFINED. You can sort the names into the load |
---|
774 | order if desired. Search this file for ".sort" for an example. Only |
---|
775 | unique key names are returned. |
---|
776 | |
---|
777 | NOTE! This structure contains only pointers to strings. The actual |
---|
778 | string data is stored in memory owned by CSimpleIni. Ensure that the |
---|
779 | CSimpleIni object is not destroyed or Reset() while these strings |
---|
780 | are in use! |
---|
781 | |
---|
782 | @param a_pSection Section to request data for |
---|
783 | @param a_names List that will receive all of the key |
---|
784 | names. See note above! |
---|
785 | |
---|
786 | @return true Section was found. |
---|
787 | @return false Matching section was not found. |
---|
788 | */ |
---|
789 | bool GetAllKeys( |
---|
790 | const SI_CHAR * a_pSection, |
---|
791 | TNamesDepend & a_names |
---|
792 | ) const; |
---|
793 | |
---|
794 | /** Retrieve all values for a specific key. This method can be used when |
---|
795 | multiple keys are both enabled and disabled. Note that the sort order |
---|
796 | of the returned strings is NOT DEFINED. You can sort the names into |
---|
797 | the load order if desired. Search this file for ".sort" for an example. |
---|
798 | |
---|
799 | NOTE! The returned values are pointers to string data stored in memory |
---|
800 | owned by CSimpleIni. Ensure that the CSimpleIni object is not destroyed |
---|
801 | or Reset while you are using this pointer! |
---|
802 | |
---|
803 | @param a_pSection Section to search |
---|
804 | @param a_pKey Key to search for |
---|
805 | @param a_values List to return if the key is not found |
---|
806 | |
---|
807 | @return true Key was found. |
---|
808 | @return false Matching section/key was not found. |
---|
809 | */ |
---|
810 | bool GetAllValues( |
---|
811 | const SI_CHAR * a_pSection, |
---|
812 | const SI_CHAR * a_pKey, |
---|
813 | TNamesDepend & a_values |
---|
814 | ) const; |
---|
815 | |
---|
816 | /** Query the number of keys in a specific section. Note that if multiple |
---|
817 | keys are enabled, then this value may be different to the number of |
---|
818 | keys returned by GetAllKeys. |
---|
819 | |
---|
820 | @param a_pSection Section to request data for |
---|
821 | |
---|
822 | @return -1 Section does not exist in the file |
---|
823 | @return >=0 Number of keys in the section |
---|
824 | */ |
---|
825 | int GetSectionSize( |
---|
826 | const SI_CHAR * a_pSection |
---|
827 | ) const; |
---|
828 | |
---|
829 | /** Retrieve all key and value pairs for a section. The data is returned |
---|
830 | as a pointer to an STL map and can be iterated or searched as |
---|
831 | desired. Note that multiple entries for the same key may exist when |
---|
832 | multiple keys have been enabled. |
---|
833 | |
---|
834 | NOTE! This structure contains only pointers to strings. The actual |
---|
835 | string data is stored in memory owned by CSimpleIni. Ensure that the |
---|
836 | CSimpleIni object is not destroyed or Reset() while these strings |
---|
837 | are in use! |
---|
838 | |
---|
839 | @param a_pSection Name of the section to return |
---|
840 | @return boolean Was a section matching the supplied |
---|
841 | name found. |
---|
842 | */ |
---|
843 | const TKeyVal * GetSection( |
---|
844 | const SI_CHAR * a_pSection |
---|
845 | ) const; |
---|
846 | |
---|
847 | /** Retrieve the value for a specific key. If multiple keys are enabled |
---|
848 | (see SetMultiKey) then only the first value associated with that key |
---|
849 | will be returned, see GetAllValues for getting all values with multikey. |
---|
850 | |
---|
851 | NOTE! The returned value is a pointer to string data stored in memory |
---|
852 | owned by CSimpleIni. Ensure that the CSimpleIni object is not destroyed |
---|
853 | or Reset while you are using this pointer! |
---|
854 | |
---|
855 | @param a_pSection Section to search |
---|
856 | @param a_pKey Key to search for |
---|
857 | @param a_pDefault Value to return if the key is not found |
---|
858 | @param a_pHasMultiple Optionally receive notification of if there are |
---|
859 | multiple entries for this key. |
---|
860 | |
---|
861 | @return a_pDefault Key was not found in the section |
---|
862 | @return other Value of the key |
---|
863 | */ |
---|
864 | const SI_CHAR * GetValue( |
---|
865 | const SI_CHAR * a_pSection, |
---|
866 | const SI_CHAR * a_pKey, |
---|
867 | const SI_CHAR * a_pDefault = NULL, |
---|
868 | bool * a_pHasMultiple = NULL |
---|
869 | ) const; |
---|
870 | |
---|
871 | /** Retrieve a numeric value for a specific key. If multiple keys are enabled |
---|
872 | (see SetMultiKey) then only the first value associated with that key |
---|
873 | will be returned, see GetAllValues for getting all values with multikey. |
---|
874 | |
---|
875 | @param a_pSection Section to search |
---|
876 | @param a_pKey Key to search for |
---|
877 | @param a_nDefault Value to return if the key is not found |
---|
878 | @param a_pHasMultiple Optionally receive notification of if there are |
---|
879 | multiple entries for this key. |
---|
880 | |
---|
881 | @return a_nDefault Key was not found in the section |
---|
882 | @return other Value of the key |
---|
883 | */ |
---|
884 | long GetLongValue( |
---|
885 | const SI_CHAR * a_pSection, |
---|
886 | const SI_CHAR * a_pKey, |
---|
887 | long a_nDefault = 0, |
---|
888 | bool * a_pHasMultiple = NULL |
---|
889 | ) const; |
---|
890 | |
---|
891 | /** Retrieve a numeric value for a specific key. If multiple keys are enabled |
---|
892 | (see SetMultiKey) then only the first value associated with that key |
---|
893 | will be returned, see GetAllValues for getting all values with multikey. |
---|
894 | |
---|
895 | @param a_pSection Section to search |
---|
896 | @param a_pKey Key to search for |
---|
897 | @param a_nDefault Value to return if the key is not found |
---|
898 | @param a_pHasMultiple Optionally receive notification of if there are |
---|
899 | multiple entries for this key. |
---|
900 | |
---|
901 | @return a_nDefault Key was not found in the section |
---|
902 | @return other Value of the key |
---|
903 | */ |
---|
904 | double GetDoubleValue( |
---|
905 | const SI_CHAR * a_pSection, |
---|
906 | const SI_CHAR * a_pKey, |
---|
907 | double a_nDefault = 0, |
---|
908 | bool * a_pHasMultiple = NULL |
---|
909 | ) const; |
---|
910 | |
---|
911 | /** Retrieve a boolean value for a specific key. If multiple keys are enabled |
---|
912 | (see SetMultiKey) then only the first value associated with that key |
---|
913 | will be returned, see GetAllValues for getting all values with multikey. |
---|
914 | |
---|
915 | Strings starting with "t", "y", "on" or "1" are returned as logically true. |
---|
916 | Strings starting with "f", "n", "of" or "0" are returned as logically false. |
---|
917 | For all other values the default is returned. Character comparisons are |
---|
918 | case-insensitive. |
---|
919 | |
---|
920 | @param a_pSection Section to search |
---|
921 | @param a_pKey Key to search for |
---|
922 | @param a_bDefault Value to return if the key is not found |
---|
923 | @param a_pHasMultiple Optionally receive notification of if there are |
---|
924 | multiple entries for this key. |
---|
925 | |
---|
926 | @return a_nDefault Key was not found in the section |
---|
927 | @return other Value of the key |
---|
928 | */ |
---|
929 | bool GetBoolValue( |
---|
930 | const SI_CHAR * a_pSection, |
---|
931 | const SI_CHAR * a_pKey, |
---|
932 | bool a_bDefault = false, |
---|
933 | bool * a_pHasMultiple = NULL |
---|
934 | ) const; |
---|
935 | |
---|
936 | /** Add or update a section or value. This will always insert |
---|
937 | when multiple keys are enabled. |
---|
938 | |
---|
939 | @param a_pSection Section to add or update |
---|
940 | @param a_pKey Key to add or update. Set to NULL to |
---|
941 | create an empty section. |
---|
942 | @param a_pValue Value to set. Set to NULL to create an |
---|
943 | empty section. |
---|
944 | @param a_pComment Comment to be associated with the section or the |
---|
945 | key. If a_pKey is NULL then it will be associated |
---|
946 | with the section, otherwise the key. Note that a |
---|
947 | comment may be set ONLY when the section or key is |
---|
948 | first created (i.e. when this function returns the |
---|
949 | value SI_INSERTED). If you wish to create a section |
---|
950 | with a comment then you need to create the section |
---|
951 | separately to the key. The comment string must be |
---|
952 | in full comment form already (have a comment |
---|
953 | character starting every line). |
---|
954 | @param a_bForceReplace Should all existing values in a multi-key INI |
---|
955 | file be replaced with this entry. This option has |
---|
956 | no effect if not using multi-key files. The |
---|
957 | difference between Delete/SetValue and SetValue |
---|
958 | with a_bForceReplace = true, is that the load |
---|
959 | order and comment will be preserved this way. |
---|
960 | |
---|
961 | @return SI_Error See error definitions |
---|
962 | @return SI_UPDATED Value was updated |
---|
963 | @return SI_INSERTED Value was inserted |
---|
964 | */ |
---|
965 | SI_Error SetValue( |
---|
966 | const SI_CHAR * a_pSection, |
---|
967 | const SI_CHAR * a_pKey, |
---|
968 | const SI_CHAR * a_pValue, |
---|
969 | const SI_CHAR * a_pComment = NULL, |
---|
970 | bool a_bForceReplace = false |
---|
971 | ) |
---|
972 | { |
---|
973 | return AddEntry(a_pSection, a_pKey, a_pValue, a_pComment, a_bForceReplace, true); |
---|
974 | } |
---|
975 | |
---|
976 | /** Add or update a numeric value. This will always insert |
---|
977 | when multiple keys are enabled. |
---|
978 | |
---|
979 | @param a_pSection Section to add or update |
---|
980 | @param a_pKey Key to add or update. |
---|
981 | @param a_nValue Value to set. |
---|
982 | @param a_pComment Comment to be associated with the key. See the |
---|
983 | notes on SetValue() for comments. |
---|
984 | @param a_bUseHex By default the value will be written to the file |
---|
985 | in decimal format. Set this to true to write it |
---|
986 | as hexadecimal. |
---|
987 | @param a_bForceReplace Should all existing values in a multi-key INI |
---|
988 | file be replaced with this entry. This option has |
---|
989 | no effect if not using multi-key files. The |
---|
990 | difference between Delete/SetLongValue and |
---|
991 | SetLongValue with a_bForceReplace = true, is that |
---|
992 | the load order and comment will be preserved this |
---|
993 | way. |
---|
994 | |
---|
995 | @return SI_Error See error definitions |
---|
996 | @return SI_UPDATED Value was updated |
---|
997 | @return SI_INSERTED Value was inserted |
---|
998 | */ |
---|
999 | SI_Error SetLongValue( |
---|
1000 | const SI_CHAR * a_pSection, |
---|
1001 | const SI_CHAR * a_pKey, |
---|
1002 | long a_nValue, |
---|
1003 | const SI_CHAR * a_pComment = NULL, |
---|
1004 | bool a_bUseHex = false, |
---|
1005 | bool a_bForceReplace = false |
---|
1006 | ); |
---|
1007 | |
---|
1008 | /** Add or update a double value. This will always insert |
---|
1009 | when multiple keys are enabled. |
---|
1010 | |
---|
1011 | @param a_pSection Section to add or update |
---|
1012 | @param a_pKey Key to add or update. |
---|
1013 | @param a_nValue Value to set. |
---|
1014 | @param a_pComment Comment to be associated with the key. See the |
---|
1015 | notes on SetValue() for comments. |
---|
1016 | @param a_bForceReplace Should all existing values in a multi-key INI |
---|
1017 | file be replaced with this entry. This option has |
---|
1018 | no effect if not using multi-key files. The |
---|
1019 | difference between Delete/SetDoubleValue and |
---|
1020 | SetDoubleValue with a_bForceReplace = true, is that |
---|
1021 | the load order and comment will be preserved this |
---|
1022 | way. |
---|
1023 | |
---|
1024 | @return SI_Error See error definitions |
---|
1025 | @return SI_UPDATED Value was updated |
---|
1026 | @return SI_INSERTED Value was inserted |
---|
1027 | */ |
---|
1028 | SI_Error SetDoubleValue( |
---|
1029 | const SI_CHAR * a_pSection, |
---|
1030 | const SI_CHAR * a_pKey, |
---|
1031 | double a_nValue, |
---|
1032 | const SI_CHAR * a_pComment = NULL, |
---|
1033 | bool a_bForceReplace = false |
---|
1034 | ); |
---|
1035 | |
---|
1036 | /** Add or update a boolean value. This will always insert |
---|
1037 | when multiple keys are enabled. |
---|
1038 | |
---|
1039 | @param a_pSection Section to add or update |
---|
1040 | @param a_pKey Key to add or update. |
---|
1041 | @param a_bValue Value to set. |
---|
1042 | @param a_pComment Comment to be associated with the key. See the |
---|
1043 | notes on SetValue() for comments. |
---|
1044 | @param a_bForceReplace Should all existing values in a multi-key INI |
---|
1045 | file be replaced with this entry. This option has |
---|
1046 | no effect if not using multi-key files. The |
---|
1047 | difference between Delete/SetBoolValue and |
---|
1048 | SetBoolValue with a_bForceReplace = true, is that |
---|
1049 | the load order and comment will be preserved this |
---|
1050 | way. |
---|
1051 | |
---|
1052 | @return SI_Error See error definitions |
---|
1053 | @return SI_UPDATED Value was updated |
---|
1054 | @return SI_INSERTED Value was inserted |
---|
1055 | */ |
---|
1056 | SI_Error SetBoolValue( |
---|
1057 | const SI_CHAR * a_pSection, |
---|
1058 | const SI_CHAR * a_pKey, |
---|
1059 | bool a_bValue, |
---|
1060 | const SI_CHAR * a_pComment = NULL, |
---|
1061 | bool a_bForceReplace = false |
---|
1062 | ); |
---|
1063 | |
---|
1064 | /** Delete an entire section, or a key from a section. Note that the |
---|
1065 | data returned by GetSection is invalid and must not be used after |
---|
1066 | anything has been deleted from that section using this method. |
---|
1067 | Note when multiple keys is enabled, this will delete all keys with |
---|
1068 | that name; there is no way to selectively delete individual key/values |
---|
1069 | in this situation. |
---|
1070 | |
---|
1071 | @param a_pSection Section to delete key from, or if |
---|
1072 | a_pKey is NULL, the section to remove. |
---|
1073 | @param a_pKey Key to remove from the section. Set to |
---|
1074 | NULL to remove the entire section. |
---|
1075 | @param a_bRemoveEmpty If the section is empty after this key has |
---|
1076 | been deleted, should the empty section be |
---|
1077 | removed? |
---|
1078 | |
---|
1079 | @return true Key or section was deleted. |
---|
1080 | @return false Key or section was not found. |
---|
1081 | */ |
---|
1082 | bool Delete( |
---|
1083 | const SI_CHAR * a_pSection, |
---|
1084 | const SI_CHAR * a_pKey, |
---|
1085 | bool a_bRemoveEmpty = false |
---|
1086 | ); |
---|
1087 | |
---|
1088 | /*-----------------------------------------------------------------------*/ |
---|
1089 | /** @} |
---|
1090 | @{ @name Converter */ |
---|
1091 | |
---|
1092 | /** Return a conversion object to convert text to the same encoding |
---|
1093 | as is used by the Save(), SaveFile() and SaveString() functions. |
---|
1094 | Use this to prepare the strings that you wish to append or prepend |
---|
1095 | to the output INI data. |
---|
1096 | */ |
---|
1097 | Converter GetConverter() const { |
---|
1098 | return Converter(m_bStoreIsUtf8); |
---|
1099 | } |
---|
1100 | |
---|
1101 | /*-----------------------------------------------------------------------*/ |
---|
1102 | /** @} */ |
---|
1103 | |
---|
1104 | private: |
---|
1105 | // copying is not permitted |
---|
1106 | CSimpleIniTempl(const CSimpleIniTempl &); // disabled |
---|
1107 | CSimpleIniTempl & operator=(const CSimpleIniTempl &); // disabled |
---|
1108 | |
---|
1109 | /** Parse the data looking for a file comment and store it if found. |
---|
1110 | */ |
---|
1111 | SI_Error FindFileComment( |
---|
1112 | SI_CHAR *& a_pData, |
---|
1113 | bool a_bCopyStrings |
---|
1114 | ); |
---|
1115 | |
---|
1116 | /** Parse the data looking for the next valid entry. The memory pointed to |
---|
1117 | by a_pData is modified by inserting NULL characters. The pointer is |
---|
1118 | updated to the current location in the block of text. |
---|
1119 | */ |
---|
1120 | bool FindEntry( |
---|
1121 | SI_CHAR *& a_pData, |
---|
1122 | const SI_CHAR *& a_pSection, |
---|
1123 | const SI_CHAR *& a_pKey, |
---|
1124 | const SI_CHAR *& a_pVal, |
---|
1125 | const SI_CHAR *& a_pComment |
---|
1126 | ) const; |
---|
1127 | |
---|
1128 | /** Add the section/key/value to our data. |
---|
1129 | |
---|
1130 | @param a_pSection Section name. Sections will be created if they |
---|
1131 | don't already exist. |
---|
1132 | @param a_pKey Key name. May be NULL to create an empty section. |
---|
1133 | Existing entries will be updated. New entries will |
---|
1134 | be created. |
---|
1135 | @param a_pValue Value for the key. |
---|
1136 | @param a_pComment Comment to be associated with the section or the |
---|
1137 | key. If a_pKey is NULL then it will be associated |
---|
1138 | with the section, otherwise the key. This must be |
---|
1139 | a string in full comment form already (have a |
---|
1140 | comment character starting every line). |
---|
1141 | @param a_bForceReplace Should all existing values in a multi-key INI |
---|
1142 | file be replaced with this entry. This option has |
---|
1143 | no effect if not using multi-key files. The |
---|
1144 | difference between Delete/AddEntry and AddEntry |
---|
1145 | with a_bForceReplace = true, is that the load |
---|
1146 | order and comment will be preserved this way. |
---|
1147 | @param a_bCopyStrings Should copies of the strings be made or not. |
---|
1148 | If false then the pointers will be used as is. |
---|
1149 | */ |
---|
1150 | SI_Error AddEntry( |
---|
1151 | const SI_CHAR * a_pSection, |
---|
1152 | const SI_CHAR * a_pKey, |
---|
1153 | const SI_CHAR * a_pValue, |
---|
1154 | const SI_CHAR * a_pComment, |
---|
1155 | bool a_bForceReplace, |
---|
1156 | bool a_bCopyStrings |
---|
1157 | ); |
---|
1158 | |
---|
1159 | /** Is the supplied character a whitespace character? */ |
---|
1160 | inline bool IsSpace(SI_CHAR ch) const { |
---|
1161 | return (ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n'); |
---|
1162 | } |
---|
1163 | |
---|
1164 | /** Does the supplied character start a comment line? */ |
---|
1165 | inline bool IsComment(SI_CHAR ch) const { |
---|
1166 | return (ch == ';' || ch == '#'); |
---|
1167 | } |
---|
1168 | |
---|
1169 | |
---|
1170 | /** Skip over a newline character (or characters) for either DOS or UNIX */ |
---|
1171 | inline void SkipNewLine(SI_CHAR *& a_pData) const { |
---|
1172 | a_pData += (*a_pData == '\r' && *(a_pData+1) == '\n') ? 2 : 1; |
---|
1173 | } |
---|
1174 | |
---|
1175 | /** Make a copy of the supplied string, replacing the original pointer */ |
---|
1176 | SI_Error CopyString(const SI_CHAR *& a_pString); |
---|
1177 | |
---|
1178 | /** Delete a string from the copied strings buffer if necessary */ |
---|
1179 | void DeleteString(const SI_CHAR * a_pString); |
---|
1180 | |
---|
1181 | /** Internal use of our string comparison function */ |
---|
1182 | bool IsLess(const SI_CHAR * a_pLeft, const SI_CHAR * a_pRight) const { |
---|
1183 | const static SI_STRLESS isLess = SI_STRLESS(); |
---|
1184 | return isLess(a_pLeft, a_pRight); |
---|
1185 | } |
---|
1186 | |
---|
1187 | bool IsMultiLineTag(const SI_CHAR * a_pData) const; |
---|
1188 | bool IsMultiLineData(const SI_CHAR * a_pData) const; |
---|
1189 | bool LoadMultiLineText( |
---|
1190 | SI_CHAR *& a_pData, |
---|
1191 | const SI_CHAR *& a_pVal, |
---|
1192 | const SI_CHAR * a_pTagName, |
---|
1193 | bool a_bAllowBlankLinesInComment = false |
---|
1194 | ) const; |
---|
1195 | bool IsNewLineChar(SI_CHAR a_c) const; |
---|
1196 | |
---|
1197 | bool OutputMultiLineText( |
---|
1198 | OutputWriter & a_oOutput, |
---|
1199 | Converter & a_oConverter, |
---|
1200 | const SI_CHAR * a_pText |
---|
1201 | ) const; |
---|
1202 | |
---|
1203 | private: |
---|
1204 | /** Copy of the INI file data in our character format. This will be |
---|
1205 | modified when parsed to have NULL characters added after all |
---|
1206 | interesting string entries. All of the string pointers to sections, |
---|
1207 | keys and values point into this block of memory. |
---|
1208 | */ |
---|
1209 | SI_CHAR * m_pData; |
---|
1210 | |
---|
1211 | /** Length of the data that we have stored. Used when deleting strings |
---|
1212 | to determine if the string is stored here or in the allocated string |
---|
1213 | buffer. |
---|
1214 | */ |
---|
1215 | size_t m_uDataLen; |
---|
1216 | |
---|
1217 | /** File comment for this data, if one exists. */ |
---|
1218 | const SI_CHAR * m_pFileComment; |
---|
1219 | |
---|
1220 | /** Parsed INI data. Section -> (Key -> Value). */ |
---|
1221 | TSection m_data; |
---|
1222 | |
---|
1223 | /** This vector stores allocated memory for copies of strings that have |
---|
1224 | been supplied after the file load. It will be empty unless SetValue() |
---|
1225 | has been called. |
---|
1226 | */ |
---|
1227 | TNamesDepend m_strings; |
---|
1228 | |
---|
1229 | /** Is the format of our datafile UTF-8 or MBCS? */ |
---|
1230 | bool m_bStoreIsUtf8; |
---|
1231 | |
---|
1232 | /** Are multiple values permitted for the same key? */ |
---|
1233 | bool m_bAllowMultiKey; |
---|
1234 | |
---|
1235 | /** Are data values permitted to span multiple lines? */ |
---|
1236 | bool m_bAllowMultiLine; |
---|
1237 | |
---|
1238 | /** Should spaces be written out surrounding the equals sign? */ |
---|
1239 | bool m_bSpaces; |
---|
1240 | |
---|
1241 | /** Next order value, used to ensure sections and keys are output in the |
---|
1242 | same order that they are loaded/added. |
---|
1243 | */ |
---|
1244 | int m_nOrder; |
---|
1245 | }; |
---|
1246 | |
---|
1247 | // --------------------------------------------------------------------------- |
---|
1248 | // IMPLEMENTATION |
---|
1249 | // --------------------------------------------------------------------------- |
---|
1250 | |
---|
1251 | template<class SI_CHAR, class SI_STRLESS, class SI_CONVERTER> |
---|
1252 | CSimpleIniTempl<SI_CHAR,SI_STRLESS,SI_CONVERTER>::CSimpleIniTempl( |
---|
1253 | bool a_bIsUtf8, |
---|
1254 | bool a_bAllowMultiKey, |
---|
1255 | bool a_bAllowMultiLine |
---|
1256 | ) |
---|
1257 | : m_pData(0) |
---|
1258 | , m_uDataLen(0) |
---|
1259 | , m_pFileComment(NULL) |
---|
1260 | , m_bStoreIsUtf8(a_bIsUtf8) |
---|
1261 | , m_bAllowMultiKey(a_bAllowMultiKey) |
---|
1262 | , m_bAllowMultiLine(a_bAllowMultiLine) |
---|
1263 | , m_bSpaces(true) |
---|
1264 | , m_nOrder(0) |
---|
1265 | { } |
---|
1266 | |
---|
1267 | template<class SI_CHAR, class SI_STRLESS, class SI_CONVERTER> |
---|
1268 | CSimpleIniTempl<SI_CHAR,SI_STRLESS,SI_CONVERTER>::~CSimpleIniTempl() |
---|
1269 | { |
---|
1270 | Reset(); |
---|
1271 | } |
---|
1272 | |
---|
1273 | template<class SI_CHAR, class SI_STRLESS, class SI_CONVERTER> |
---|
1274 | void |
---|
1275 | CSimpleIniTempl<SI_CHAR,SI_STRLESS,SI_CONVERTER>::Reset() |
---|
1276 | { |
---|
1277 | // remove all data |
---|
1278 | delete[] m_pData; |
---|
1279 | m_pData = NULL; |
---|
1280 | m_uDataLen = 0; |
---|
1281 | m_pFileComment = NULL; |
---|
1282 | if (!m_data.empty()) { |
---|
1283 | m_data.erase(m_data.begin(), m_data.end()); |
---|
1284 | } |
---|
1285 | |
---|
1286 | // remove all strings |
---|
1287 | if (!m_strings.empty()) { |
---|
1288 | typename TNamesDepend::iterator i = m_strings.begin(); |
---|
1289 | for (; i != m_strings.end(); ++i) { |
---|
1290 | delete[] const_cast<SI_CHAR*>(i->pItem); |
---|
1291 | } |
---|
1292 | m_strings.erase(m_strings.begin(), m_strings.end()); |
---|
1293 | } |
---|
1294 | } |
---|
1295 | |
---|
1296 | template<class SI_CHAR, class SI_STRLESS, class SI_CONVERTER> |
---|
1297 | SI_Error |
---|
1298 | CSimpleIniTempl<SI_CHAR,SI_STRLESS,SI_CONVERTER>::LoadFile( |
---|
1299 | const char * a_pszFile |
---|
1300 | ) |
---|
1301 | { |
---|
1302 | FILE * fp = NULL; |
---|
1303 | #if __STDC_WANT_SECURE_LIB__ && !_WIN32_WCE |
---|
1304 | fopen_s(&fp, a_pszFile, "rb"); |
---|
1305 | #else // !__STDC_WANT_SECURE_LIB__ |
---|
1306 | fp = fopen(a_pszFile, "rb"); |
---|
1307 | #endif // __STDC_WANT_SECURE_LIB__ |
---|
1308 | if (!fp) { |
---|
1309 | return SI_FILE; |
---|
1310 | } |
---|
1311 | SI_Error rc = LoadFile(fp); |
---|
1312 | fclose(fp); |
---|
1313 | return rc; |
---|
1314 | } |
---|
1315 | |
---|
1316 | #ifdef SI_HAS_WIDE_FILE |
---|
1317 | template<class SI_CHAR, class SI_STRLESS, class SI_CONVERTER> |
---|
1318 | SI_Error |
---|
1319 | CSimpleIniTempl<SI_CHAR,SI_STRLESS,SI_CONVERTER>::LoadFile( |
---|
1320 | const SI_WCHAR_T * a_pwszFile |
---|
1321 | ) |
---|
1322 | { |
---|
1323 | #ifdef _WIN32 |
---|
1324 | FILE * fp = NULL; |
---|
1325 | #if __STDC_WANT_SECURE_LIB__ && !_WIN32_WCE |
---|
1326 | _wfopen_s(&fp, a_pwszFile, L"rb"); |
---|
1327 | #else // !__STDC_WANT_SECURE_LIB__ |
---|
1328 | fp = _wfopen(a_pwszFile, L"rb"); |
---|
1329 | #endif // __STDC_WANT_SECURE_LIB__ |
---|
1330 | if (!fp) return SI_FILE; |
---|
1331 | SI_Error rc = LoadFile(fp); |
---|
1332 | fclose(fp); |
---|
1333 | return rc; |
---|
1334 | #else // !_WIN32 (therefore SI_CONVERT_ICU) |
---|
1335 | char szFile[256]; |
---|
1336 | u_austrncpy(szFile, a_pwszFile, sizeof(szFile)); |
---|
1337 | return LoadFile(szFile); |
---|
1338 | #endif // _WIN32 |
---|
1339 | } |
---|
1340 | #endif // SI_HAS_WIDE_FILE |
---|
1341 | |
---|
1342 | template<class SI_CHAR, class SI_STRLESS, class SI_CONVERTER> |
---|
1343 | SI_Error |
---|
1344 | CSimpleIniTempl<SI_CHAR,SI_STRLESS,SI_CONVERTER>::LoadFile( |
---|
1345 | FILE * a_fpFile |
---|
1346 | ) |
---|
1347 | { |
---|
1348 | // load the raw file data |
---|
1349 | int retval = fseek(a_fpFile, 0, SEEK_END); |
---|
1350 | if (retval != 0) { |
---|
1351 | return SI_FILE; |
---|
1352 | } |
---|
1353 | long lSize = ftell(a_fpFile); |
---|
1354 | if (lSize < 0) { |
---|
1355 | return SI_FILE; |
---|
1356 | } |
---|
1357 | if (lSize == 0) { |
---|
1358 | return SI_OK; |
---|
1359 | } |
---|
1360 | |
---|
1361 | // allocate and ensure NULL terminated |
---|
1362 | char * pData = new char[lSize+1]; |
---|
1363 | if (!pData) { |
---|
1364 | return SI_NOMEM; |
---|
1365 | } |
---|
1366 | pData[lSize] = 0; |
---|
1367 | |
---|
1368 | // load data into buffer |
---|
1369 | fseek(a_fpFile, 0, SEEK_SET); |
---|
1370 | size_t uRead = fread(pData, sizeof(char), lSize, a_fpFile); |
---|
1371 | if (uRead != (size_t) lSize) { |
---|
1372 | delete[] pData; |
---|
1373 | return SI_FILE; |
---|
1374 | } |
---|
1375 | |
---|
1376 | // convert the raw data to unicode |
---|
1377 | SI_Error rc = LoadData(pData, uRead); |
---|
1378 | delete[] pData; |
---|
1379 | return rc; |
---|
1380 | } |
---|
1381 | |
---|
1382 | template<class SI_CHAR, class SI_STRLESS, class SI_CONVERTER> |
---|
1383 | SI_Error |
---|
1384 | CSimpleIniTempl<SI_CHAR,SI_STRLESS,SI_CONVERTER>::LoadData( |
---|
1385 | const char * a_pData, |
---|
1386 | size_t a_uDataLen |
---|
1387 | ) |
---|
1388 | { |
---|
1389 | SI_CONVERTER converter(m_bStoreIsUtf8); |
---|
1390 | |
---|
1391 | if (a_uDataLen == 0) { |
---|
1392 | return SI_OK; |
---|
1393 | } |
---|
1394 | |
---|
1395 | // consume the UTF-8 BOM if it exists |
---|
1396 | if (m_bStoreIsUtf8 && a_uDataLen >= 3) { |
---|
1397 | if (memcmp(a_pData, SI_UTF8_SIGNATURE, 3) == 0) { |
---|
1398 | a_pData += 3; |
---|
1399 | a_uDataLen -= 3; |
---|
1400 | } |
---|
1401 | } |
---|
1402 | |
---|
1403 | // determine the length of the converted data |
---|
1404 | size_t uLen = converter.SizeFromStore(a_pData, a_uDataLen); |
---|
1405 | if (uLen == (size_t)(-1)) { |
---|
1406 | return SI_FAIL; |
---|
1407 | } |
---|
1408 | |
---|
1409 | // allocate memory for the data, ensure that there is a NULL |
---|
1410 | // terminator wherever the converted data ends |
---|
1411 | SI_CHAR * pData = new SI_CHAR[uLen+1]; |
---|
1412 | if (!pData) { |
---|
1413 | return SI_NOMEM; |
---|
1414 | } |
---|
1415 | memset(pData, 0, sizeof(SI_CHAR)*(uLen+1)); |
---|
1416 | |
---|
1417 | // convert the data |
---|
1418 | if (!converter.ConvertFromStore(a_pData, a_uDataLen, pData, uLen)) { |
---|
1419 | delete[] pData; |
---|
1420 | return SI_FAIL; |
---|
1421 | } |
---|
1422 | |
---|
1423 | // parse it |
---|
1424 | const static SI_CHAR empty = 0; |
---|
1425 | SI_CHAR * pWork = pData; |
---|
1426 | const SI_CHAR * pSection = ∅ |
---|
1427 | const SI_CHAR * pItem = NULL; |
---|
1428 | const SI_CHAR * pVal = NULL; |
---|
1429 | const SI_CHAR * pComment = NULL; |
---|
1430 | |
---|
1431 | // We copy the strings if we are loading data into this class when we |
---|
1432 | // already have stored some. |
---|
1433 | bool bCopyStrings = (m_pData != NULL); |
---|
1434 | |
---|
1435 | // find a file comment if it exists, this is a comment that starts at the |
---|
1436 | // beginning of the file and continues until the first blank line. |
---|
1437 | SI_Error rc = FindFileComment(pWork, bCopyStrings); |
---|
1438 | if (rc < 0) return rc; |
---|
1439 | |
---|
1440 | // add every entry in the file to the data table |
---|
1441 | while (FindEntry(pWork, pSection, pItem, pVal, pComment)) { |
---|
1442 | rc = AddEntry(pSection, pItem, pVal, pComment, false, bCopyStrings); |
---|
1443 | if (rc < 0) return rc; |
---|
1444 | } |
---|
1445 | |
---|
1446 | // store these strings if we didn't copy them |
---|
1447 | if (bCopyStrings) { |
---|
1448 | delete[] pData; |
---|
1449 | } |
---|
1450 | else { |
---|
1451 | m_pData = pData; |
---|
1452 | m_uDataLen = uLen+1; |
---|
1453 | } |
---|
1454 | |
---|
1455 | return SI_OK; |
---|
1456 | } |
---|
1457 | |
---|
1458 | #ifdef SI_SUPPORT_IOSTREAMS |
---|
1459 | template<class SI_CHAR, class SI_STRLESS, class SI_CONVERTER> |
---|
1460 | SI_Error |
---|
1461 | CSimpleIniTempl<SI_CHAR,SI_STRLESS,SI_CONVERTER>::LoadData( |
---|
1462 | std::istream & a_istream |
---|
1463 | ) |
---|
1464 | { |
---|
1465 | std::string strData; |
---|
1466 | char szBuf[512]; |
---|
1467 | do { |
---|
1468 | a_istream.get(szBuf, sizeof(szBuf), '\0'); |
---|
1469 | strData.append(szBuf); |
---|
1470 | } |
---|
1471 | while (a_istream.good()); |
---|
1472 | return LoadData(strData); |
---|
1473 | } |
---|
1474 | #endif // SI_SUPPORT_IOSTREAMS |
---|
1475 | |
---|
1476 | template<class SI_CHAR, class SI_STRLESS, class SI_CONVERTER> |
---|
1477 | SI_Error |
---|
1478 | CSimpleIniTempl<SI_CHAR,SI_STRLESS,SI_CONVERTER>::FindFileComment( |
---|
1479 | SI_CHAR *& a_pData, |
---|
1480 | bool a_bCopyStrings |
---|
1481 | ) |
---|
1482 | { |
---|
1483 | // there can only be a single file comment |
---|
1484 | if (m_pFileComment) { |
---|
1485 | return SI_OK; |
---|
1486 | } |
---|
1487 | |
---|
1488 | // Load the file comment as multi-line text, this will modify all of |
---|
1489 | // the newline characters to be single \n chars |
---|
1490 | if (!LoadMultiLineText(a_pData, m_pFileComment, NULL, false)) { |
---|
1491 | return SI_OK; |
---|
1492 | } |
---|
1493 | |
---|
1494 | // copy the string if necessary |
---|
1495 | if (a_bCopyStrings) { |
---|
1496 | SI_Error rc = CopyString(m_pFileComment); |
---|
1497 | if (rc < 0) return rc; |
---|
1498 | } |
---|
1499 | |
---|
1500 | return SI_OK; |
---|
1501 | } |
---|
1502 | |
---|
1503 | template<class SI_CHAR, class SI_STRLESS, class SI_CONVERTER> |
---|
1504 | bool |
---|
1505 | CSimpleIniTempl<SI_CHAR,SI_STRLESS,SI_CONVERTER>::FindEntry( |
---|
1506 | SI_CHAR *& a_pData, |
---|
1507 | const SI_CHAR *& a_pSection, |
---|
1508 | const SI_CHAR *& a_pKey, |
---|
1509 | const SI_CHAR *& a_pVal, |
---|
1510 | const SI_CHAR *& a_pComment |
---|
1511 | ) const |
---|
1512 | { |
---|
1513 | a_pComment = NULL; |
---|
1514 | |
---|
1515 | SI_CHAR * pTrail = NULL; |
---|
1516 | while (*a_pData) { |
---|
1517 | // skip spaces and empty lines |
---|
1518 | while (*a_pData && IsSpace(*a_pData)) { |
---|
1519 | ++a_pData; |
---|
1520 | } |
---|
1521 | if (!*a_pData) { |
---|
1522 | break; |
---|
1523 | } |
---|
1524 | |
---|
1525 | // skip processing of comment lines but keep a pointer to |
---|
1526 | // the start of the comment. |
---|
1527 | if (IsComment(*a_pData)) { |
---|
1528 | LoadMultiLineText(a_pData, a_pComment, NULL, true); |
---|
1529 | continue; |
---|
1530 | } |
---|
1531 | |
---|
1532 | // process section names |
---|
1533 | if (*a_pData == '[') { |
---|
1534 | // skip leading spaces |
---|
1535 | ++a_pData; |
---|
1536 | while (*a_pData && IsSpace(*a_pData)) { |
---|
1537 | ++a_pData; |
---|
1538 | } |
---|
1539 | |
---|
1540 | // find the end of the section name (it may contain spaces) |
---|
1541 | // and convert it to lowercase as necessary |
---|
1542 | a_pSection = a_pData; |
---|
1543 | while (*a_pData && *a_pData != ']' && !IsNewLineChar(*a_pData)) { |
---|
1544 | ++a_pData; |
---|
1545 | } |
---|
1546 | |
---|
1547 | // if it's an invalid line, just skip it |
---|
1548 | if (*a_pData != ']') { |
---|
1549 | continue; |
---|
1550 | } |
---|
1551 | |
---|
1552 | // remove trailing spaces from the section |
---|
1553 | pTrail = a_pData - 1; |
---|
1554 | while (pTrail >= a_pSection && IsSpace(*pTrail)) { |
---|
1555 | --pTrail; |
---|
1556 | } |
---|
1557 | ++pTrail; |
---|
1558 | *pTrail = 0; |
---|
1559 | |
---|
1560 | // skip to the end of the line |
---|
1561 | ++a_pData; // safe as checked that it == ']' above |
---|
1562 | while (*a_pData && !IsNewLineChar(*a_pData)) { |
---|
1563 | ++a_pData; |
---|
1564 | } |
---|
1565 | |
---|
1566 | a_pKey = NULL; |
---|
1567 | a_pVal = NULL; |
---|
1568 | return true; |
---|
1569 | } |
---|
1570 | |
---|
1571 | // find the end of the key name (it may contain spaces) |
---|
1572 | // and convert it to lowercase as necessary |
---|
1573 | a_pKey = a_pData; |
---|
1574 | while (*a_pData && *a_pData != '=' && !IsNewLineChar(*a_pData)) { |
---|
1575 | ++a_pData; |
---|
1576 | } |
---|
1577 | |
---|
1578 | // if it's an invalid line, just skip it |
---|
1579 | if (*a_pData != '=') { |
---|
1580 | continue; |
---|
1581 | } |
---|
1582 | |
---|
1583 | // empty keys are invalid |
---|
1584 | if (a_pKey == a_pData) { |
---|
1585 | while (*a_pData && !IsNewLineChar(*a_pData)) { |
---|
1586 | ++a_pData; |
---|
1587 | } |
---|
1588 | continue; |
---|
1589 | } |
---|
1590 | |
---|
1591 | // remove trailing spaces from the key |
---|
1592 | pTrail = a_pData - 1; |
---|
1593 | while (pTrail >= a_pKey && IsSpace(*pTrail)) { |
---|
1594 | --pTrail; |
---|
1595 | } |
---|
1596 | ++pTrail; |
---|
1597 | *pTrail = 0; |
---|
1598 | |
---|
1599 | // skip leading whitespace on the value |
---|
1600 | ++a_pData; // safe as checked that it == '=' above |
---|
1601 | while (*a_pData && !IsNewLineChar(*a_pData) && IsSpace(*a_pData)) { |
---|
1602 | ++a_pData; |
---|
1603 | } |
---|
1604 | |
---|
1605 | // find the end of the value which is the end of this line |
---|
1606 | a_pVal = a_pData; |
---|
1607 | while (*a_pData && !IsNewLineChar(*a_pData)) { |
---|
1608 | ++a_pData; |
---|
1609 | } |
---|
1610 | |
---|
1611 | // remove trailing spaces from the value |
---|
1612 | pTrail = a_pData - 1; |
---|
1613 | if (*a_pData) { // prepare for the next round |
---|
1614 | SkipNewLine(a_pData); |
---|
1615 | } |
---|
1616 | while (pTrail >= a_pVal && IsSpace(*pTrail)) { |
---|
1617 | --pTrail; |
---|
1618 | } |
---|
1619 | ++pTrail; |
---|
1620 | *pTrail = 0; |
---|
1621 | |
---|
1622 | // check for multi-line entries |
---|
1623 | if (m_bAllowMultiLine && IsMultiLineTag(a_pVal)) { |
---|
1624 | // skip the "<<<" to get the tag that will end the multiline |
---|
1625 | const SI_CHAR * pTagName = a_pVal + 3; |
---|
1626 | return LoadMultiLineText(a_pData, a_pVal, pTagName); |
---|
1627 | } |
---|
1628 | |
---|
1629 | // return the standard entry |
---|
1630 | return true; |
---|
1631 | } |
---|
1632 | |
---|
1633 | return false; |
---|
1634 | } |
---|
1635 | |
---|
1636 | template<class SI_CHAR, class SI_STRLESS, class SI_CONVERTER> |
---|
1637 | bool |
---|
1638 | CSimpleIniTempl<SI_CHAR,SI_STRLESS,SI_CONVERTER>::IsMultiLineTag( |
---|
1639 | const SI_CHAR * a_pVal |
---|
1640 | ) const |
---|
1641 | { |
---|
1642 | // check for the "<<<" prefix for a multi-line entry |
---|
1643 | if (*a_pVal++ != '<') return false; |
---|
1644 | if (*a_pVal++ != '<') return false; |
---|
1645 | if (*a_pVal++ != '<') return false; |
---|
1646 | return true; |
---|
1647 | } |
---|
1648 | |
---|
1649 | template<class SI_CHAR, class SI_STRLESS, class SI_CONVERTER> |
---|
1650 | bool |
---|
1651 | CSimpleIniTempl<SI_CHAR,SI_STRLESS,SI_CONVERTER>::IsMultiLineData( |
---|
1652 | const SI_CHAR * a_pData |
---|
1653 | ) const |
---|
1654 | { |
---|
1655 | // data is multi-line if it has any of the following features: |
---|
1656 | // * whitespace prefix |
---|
1657 | // * embedded newlines |
---|
1658 | // * whitespace suffix |
---|
1659 | |
---|
1660 | // empty string |
---|
1661 | if (!*a_pData) { |
---|
1662 | return false; |
---|
1663 | } |
---|
1664 | |
---|
1665 | // check for prefix |
---|
1666 | if (IsSpace(*a_pData)) { |
---|
1667 | return true; |
---|
1668 | } |
---|
1669 | |
---|
1670 | // embedded newlines |
---|
1671 | while (*a_pData) { |
---|
1672 | if (IsNewLineChar(*a_pData)) { |
---|
1673 | return true; |
---|
1674 | } |
---|
1675 | ++a_pData; |
---|
1676 | } |
---|
1677 | |
---|
1678 | // check for suffix |
---|
1679 | if (IsSpace(*--a_pData)) { |
---|
1680 | return true; |
---|
1681 | } |
---|
1682 | |
---|
1683 | return false; |
---|
1684 | } |
---|
1685 | |
---|
1686 | template<class SI_CHAR, class SI_STRLESS, class SI_CONVERTER> |
---|
1687 | bool |
---|
1688 | CSimpleIniTempl<SI_CHAR,SI_STRLESS,SI_CONVERTER>::IsNewLineChar( |
---|
1689 | SI_CHAR a_c |
---|
1690 | ) const |
---|
1691 | { |
---|
1692 | return (a_c == '\n' || a_c == '\r'); |
---|
1693 | } |
---|
1694 | |
---|
1695 | template<class SI_CHAR, class SI_STRLESS, class SI_CONVERTER> |
---|
1696 | bool |
---|
1697 | CSimpleIniTempl<SI_CHAR,SI_STRLESS,SI_CONVERTER>::LoadMultiLineText( |
---|
1698 | SI_CHAR *& a_pData, |
---|
1699 | const SI_CHAR *& a_pVal, |
---|
1700 | const SI_CHAR * a_pTagName, |
---|
1701 | bool a_bAllowBlankLinesInComment |
---|
1702 | ) const |
---|
1703 | { |
---|
1704 | // we modify this data to strip all newlines down to a single '\n' |
---|
1705 | // character. This means that on Windows we need to strip out some |
---|
1706 | // characters which will make the data shorter. |
---|
1707 | // i.e. LINE1-LINE1\r\nLINE2-LINE2\0 will become |
---|
1708 | // LINE1-LINE1\nLINE2-LINE2\0 |
---|
1709 | // The pDataLine entry is the pointer to the location in memory that |
---|
1710 | // the current line needs to start to run following the existing one. |
---|
1711 | // This may be the same as pCurrLine in which case no move is needed. |
---|
1712 | SI_CHAR * pDataLine = a_pData; |
---|
1713 | SI_CHAR * pCurrLine; |
---|
1714 | |
---|
1715 | // value starts at the current line |
---|
1716 | a_pVal = a_pData; |
---|
1717 | |
---|
1718 | // find the end tag. This tag must start in column 1 and be |
---|
1719 | // followed by a newline. No whitespace removal is done while |
---|
1720 | // searching for this tag. |
---|
1721 | SI_CHAR cEndOfLineChar = *a_pData; |
---|
1722 | for(;;) { |
---|
1723 | // if we are loading comments then we need a comment character as |
---|
1724 | // the first character on every line |
---|
1725 | if (!a_pTagName && !IsComment(*a_pData)) { |
---|
1726 | // if we aren't allowing blank lines then we're done |
---|
1727 | if (!a_bAllowBlankLinesInComment) { |
---|
1728 | break; |
---|
1729 | } |
---|
1730 | |
---|
1731 | // if we are allowing blank lines then we only include them |
---|
1732 | // in this comment if another comment follows, so read ahead |
---|
1733 | // to find out. |
---|
1734 | SI_CHAR * pCurr = a_pData; |
---|
1735 | int nNewLines = 0; |
---|
1736 | while (IsSpace(*pCurr)) { |
---|
1737 | if (IsNewLineChar(*pCurr)) { |
---|
1738 | ++nNewLines; |
---|
1739 | SkipNewLine(pCurr); |
---|
1740 | } |
---|
1741 | else { |
---|
1742 | ++pCurr; |
---|
1743 | } |
---|
1744 | } |
---|
1745 | |
---|
1746 | // we have a comment, add the blank lines to the output |
---|
1747 | // and continue processing from here |
---|
1748 | if (IsComment(*pCurr)) { |
---|
1749 | for (; nNewLines > 0; --nNewLines) *pDataLine++ = '\n'; |
---|
1750 | a_pData = pCurr; |
---|
1751 | continue; |
---|
1752 | } |
---|
1753 | |
---|
1754 | // the comment ends here |
---|
1755 | break; |
---|
1756 | } |
---|
1757 | |
---|
1758 | // find the end of this line |
---|
1759 | pCurrLine = a_pData; |
---|
1760 | while (*a_pData && !IsNewLineChar(*a_pData)) ++a_pData; |
---|
1761 | |
---|
1762 | // move this line down to the location that it should be if necessary |
---|
1763 | if (pDataLine < pCurrLine) { |
---|
1764 | size_t nLen = (size_t) (a_pData - pCurrLine); |
---|
1765 | memmove(pDataLine, pCurrLine, nLen * sizeof(SI_CHAR)); |
---|
1766 | pDataLine[nLen] = '\0'; |
---|
1767 | } |
---|
1768 | |
---|
1769 | // end the line with a NULL |
---|
1770 | cEndOfLineChar = *a_pData; |
---|
1771 | *a_pData = 0; |
---|
1772 | |
---|
1773 | // if are looking for a tag then do the check now. This is done before |
---|
1774 | // checking for end of the data, so that if we have the tag at the end |
---|
1775 | // of the data then the tag is removed correctly. |
---|
1776 | if (a_pTagName && |
---|
1777 | (!IsLess(pDataLine, a_pTagName) && !IsLess(a_pTagName, pDataLine))) |
---|
1778 | { |
---|
1779 | break; |
---|
1780 | } |
---|
1781 | |
---|
1782 | // if we are at the end of the data then we just automatically end |
---|
1783 | // this entry and return the current data. |
---|
1784 | if (!cEndOfLineChar) { |
---|
1785 | return true; |
---|
1786 | } |
---|
1787 | |
---|
1788 | // otherwise we need to process this newline to ensure that it consists |
---|
1789 | // of just a single \n character. |
---|
1790 | pDataLine += (a_pData - pCurrLine); |
---|
1791 | *a_pData = cEndOfLineChar; |
---|
1792 | SkipNewLine(a_pData); |
---|
1793 | *pDataLine++ = '\n'; |
---|
1794 | } |
---|
1795 | |
---|
1796 | // if we didn't find a comment at all then return false |
---|
1797 | if (a_pVal == a_pData) { |
---|
1798 | a_pVal = NULL; |
---|
1799 | return false; |
---|
1800 | } |
---|
1801 | |
---|
1802 | // the data (which ends at the end of the last line) needs to be |
---|
1803 | // null-terminated BEFORE before the newline character(s). If the |
---|
1804 | // user wants a new line in the multi-line data then they need to |
---|
1805 | // add an empty line before the tag. |
---|
1806 | *--pDataLine = '\0'; |
---|
1807 | |
---|
1808 | // if looking for a tag and if we aren't at the end of the data, |
---|
1809 | // then move a_pData to the start of the next line. |
---|
1810 | if (a_pTagName && cEndOfLineChar) { |
---|
1811 | SI_ASSERT(IsNewLineChar(cEndOfLineChar)); |
---|
1812 | *a_pData = cEndOfLineChar; |
---|
1813 | SkipNewLine(a_pData); |
---|
1814 | } |
---|
1815 | |
---|
1816 | return true; |
---|
1817 | } |
---|
1818 | |
---|
1819 | template<class SI_CHAR, class SI_STRLESS, class SI_CONVERTER> |
---|
1820 | SI_Error |
---|
1821 | CSimpleIniTempl<SI_CHAR,SI_STRLESS,SI_CONVERTER>::CopyString( |
---|
1822 | const SI_CHAR *& a_pString |
---|
1823 | ) |
---|
1824 | { |
---|
1825 | size_t uLen = 0; |
---|
1826 | if (sizeof(SI_CHAR) == sizeof(char)) { |
---|
1827 | uLen = strlen((const char *)a_pString); |
---|
1828 | } |
---|
1829 | else if (sizeof(SI_CHAR) == sizeof(wchar_t)) { |
---|
1830 | uLen = wcslen((const wchar_t *)a_pString); |
---|
1831 | } |
---|
1832 | else { |
---|
1833 | for ( ; a_pString[uLen]; ++uLen) /*loop*/ ; |
---|
1834 | } |
---|
1835 | ++uLen; // NULL character |
---|
1836 | SI_CHAR * pCopy = new SI_CHAR[uLen]; |
---|
1837 | if (!pCopy) { |
---|
1838 | return SI_NOMEM; |
---|
1839 | } |
---|
1840 | memcpy(pCopy, a_pString, sizeof(SI_CHAR)*uLen); |
---|
1841 | m_strings.push_back(pCopy); |
---|
1842 | a_pString = pCopy; |
---|
1843 | return SI_OK; |
---|
1844 | } |
---|
1845 | |
---|
1846 | template<class SI_CHAR, class SI_STRLESS, class SI_CONVERTER> |
---|
1847 | SI_Error |
---|
1848 | CSimpleIniTempl<SI_CHAR,SI_STRLESS,SI_CONVERTER>::AddEntry( |
---|
1849 | const SI_CHAR * a_pSection, |
---|
1850 | const SI_CHAR * a_pKey, |
---|
1851 | const SI_CHAR * a_pValue, |
---|
1852 | const SI_CHAR * a_pComment, |
---|
1853 | bool a_bForceReplace, |
---|
1854 | bool a_bCopyStrings |
---|
1855 | ) |
---|
1856 | { |
---|
1857 | SI_Error rc; |
---|
1858 | bool bInserted = false; |
---|
1859 | |
---|
1860 | SI_ASSERT(!a_pComment || IsComment(*a_pComment)); |
---|
1861 | |
---|
1862 | // if we are copying strings then make a copy of the comment now |
---|
1863 | // because we will need it when we add the entry. |
---|
1864 | if (a_bCopyStrings && a_pComment) { |
---|
1865 | rc = CopyString(a_pComment); |
---|
1866 | if (rc < 0) return rc; |
---|
1867 | } |
---|
1868 | |
---|
1869 | // create the section entry if necessary |
---|
1870 | typename TSection::iterator iSection = m_data.find(a_pSection); |
---|
1871 | if (iSection == m_data.end()) { |
---|
1872 | // if the section doesn't exist then we need a copy as the |
---|
1873 | // string needs to last beyond the end of this function |
---|
1874 | if (a_bCopyStrings) { |
---|
1875 | rc = CopyString(a_pSection); |
---|
1876 | if (rc < 0) return rc; |
---|
1877 | } |
---|
1878 | |
---|
1879 | // only set the comment if this is a section only entry |
---|
1880 | Entry oSection(a_pSection, ++m_nOrder); |
---|
1881 | if (a_pComment && (!a_pKey || !a_pValue)) { |
---|
1882 | oSection.pComment = a_pComment; |
---|
1883 | } |
---|
1884 | |
---|
1885 | typename TSection::value_type oEntry(oSection, TKeyVal()); |
---|
1886 | typedef typename TSection::iterator SectionIterator; |
---|
1887 | std::pair<SectionIterator,bool> i = m_data.insert(oEntry); |
---|
1888 | iSection = i.first; |
---|
1889 | bInserted = true; |
---|
1890 | } |
---|
1891 | if (!a_pKey || !a_pValue) { |
---|
1892 | // section only entries are specified with pItem and pVal as NULL |
---|
1893 | return bInserted ? SI_INSERTED : SI_UPDATED; |
---|
1894 | } |
---|
1895 | |
---|
1896 | // check for existence of the key |
---|
1897 | TKeyVal & keyval = iSection->second; |
---|
1898 | typename TKeyVal::iterator iKey = keyval.find(a_pKey); |
---|
1899 | |
---|
1900 | // remove all existing entries but save the load order and |
---|
1901 | // comment of the first entry |
---|
1902 | int nLoadOrder = ++m_nOrder; |
---|
1903 | if (iKey != keyval.end() && m_bAllowMultiKey && a_bForceReplace) { |
---|
1904 | const SI_CHAR * pComment = NULL; |
---|
1905 | while (iKey != keyval.end() && !IsLess(a_pKey, iKey->first.pItem)) { |
---|
1906 | if (iKey->first.nOrder < nLoadOrder) { |
---|
1907 | nLoadOrder = iKey->first.nOrder; |
---|
1908 | pComment = iKey->first.pComment; |
---|
1909 | } |
---|
1910 | ++iKey; |
---|
1911 | } |
---|
1912 | if (pComment) { |
---|
1913 | DeleteString(a_pComment); |
---|
1914 | a_pComment = pComment; |
---|
1915 | CopyString(a_pComment); |
---|
1916 | } |
---|
1917 | Delete(a_pSection, a_pKey); |
---|
1918 | iKey = keyval.end(); |
---|
1919 | } |
---|
1920 | |
---|
1921 | // make string copies if necessary |
---|
1922 | bool bForceCreateNewKey = m_bAllowMultiKey && !a_bForceReplace; |
---|
1923 | if (a_bCopyStrings) { |
---|
1924 | if (bForceCreateNewKey || iKey == keyval.end()) { |
---|
1925 | // if the key doesn't exist then we need a copy as the |
---|
1926 | // string needs to last beyond the end of this function |
---|
1927 | // because we will be inserting the key next |
---|
1928 | rc = CopyString(a_pKey); |
---|
1929 | if (rc < 0) return rc; |
---|
1930 | } |
---|
1931 | |
---|
1932 | // we always need a copy of the value |
---|
1933 | rc = CopyString(a_pValue); |
---|
1934 | if (rc < 0) return rc; |
---|
1935 | } |
---|
1936 | |
---|
1937 | // create the key entry |
---|
1938 | if (iKey == keyval.end() || bForceCreateNewKey) { |
---|
1939 | Entry oKey(a_pKey, nLoadOrder); |
---|
1940 | if (a_pComment) { |
---|
1941 | oKey.pComment = a_pComment; |
---|
1942 | } |
---|
1943 | typename TKeyVal::value_type oEntry(oKey, static_cast<const SI_CHAR *>(NULL)); |
---|
1944 | iKey = keyval.insert(oEntry); |
---|
1945 | bInserted = true; |
---|
1946 | } |
---|
1947 | iKey->second = a_pValue; |
---|
1948 | return bInserted ? SI_INSERTED : SI_UPDATED; |
---|
1949 | } |
---|
1950 | |
---|
1951 | template<class SI_CHAR, class SI_STRLESS, class SI_CONVERTER> |
---|
1952 | const SI_CHAR * |
---|
1953 | CSimpleIniTempl<SI_CHAR,SI_STRLESS,SI_CONVERTER>::GetValue( |
---|
1954 | const SI_CHAR * a_pSection, |
---|
1955 | const SI_CHAR * a_pKey, |
---|
1956 | const SI_CHAR * a_pDefault, |
---|
1957 | bool * a_pHasMultiple |
---|
1958 | ) const |
---|
1959 | { |
---|
1960 | if (a_pHasMultiple) { |
---|
1961 | *a_pHasMultiple = false; |
---|
1962 | } |
---|
1963 | if (!a_pSection || !a_pKey) { |
---|
1964 | return a_pDefault; |
---|
1965 | } |
---|
1966 | typename TSection::const_iterator iSection = m_data.find(a_pSection); |
---|
1967 | if (iSection == m_data.end()) { |
---|
1968 | return a_pDefault; |
---|
1969 | } |
---|
1970 | typename TKeyVal::const_iterator iKeyVal = iSection->second.find(a_pKey); |
---|
1971 | if (iKeyVal == iSection->second.end()) { |
---|
1972 | return a_pDefault; |
---|
1973 | } |
---|
1974 | |
---|
1975 | // check for multiple entries with the same key |
---|
1976 | if (m_bAllowMultiKey && a_pHasMultiple) { |
---|
1977 | typename TKeyVal::const_iterator iTemp = iKeyVal; |
---|
1978 | if (++iTemp != iSection->second.end()) { |
---|
1979 | if (!IsLess(a_pKey, iTemp->first.pItem)) { |
---|
1980 | *a_pHasMultiple = true; |
---|
1981 | } |
---|
1982 | } |
---|
1983 | } |
---|
1984 | |
---|
1985 | return iKeyVal->second; |
---|
1986 | } |
---|
1987 | |
---|
1988 | template<class SI_CHAR, class SI_STRLESS, class SI_CONVERTER> |
---|
1989 | long |
---|
1990 | CSimpleIniTempl<SI_CHAR,SI_STRLESS,SI_CONVERTER>::GetLongValue( |
---|
1991 | const SI_CHAR * a_pSection, |
---|
1992 | const SI_CHAR * a_pKey, |
---|
1993 | long a_nDefault, |
---|
1994 | bool * a_pHasMultiple |
---|
1995 | ) const |
---|
1996 | { |
---|
1997 | // return the default if we don't have a value |
---|
1998 | const SI_CHAR * pszValue = GetValue(a_pSection, a_pKey, NULL, a_pHasMultiple); |
---|
1999 | if (!pszValue || !*pszValue) return a_nDefault; |
---|
2000 | |
---|
2001 | // convert to UTF-8/MBCS which for a numeric value will be the same as ASCII |
---|
2002 | char szValue[64] = { 0 }; |
---|
2003 | SI_CONVERTER c(m_bStoreIsUtf8); |
---|
2004 | if (!c.ConvertToStore(pszValue, szValue, sizeof(szValue))) { |
---|
2005 | return a_nDefault; |
---|
2006 | } |
---|
2007 | |
---|
2008 | // handle the value as hex if prefaced with "0x" |
---|
2009 | long nValue = a_nDefault; |
---|
2010 | char * pszSuffix = szValue; |
---|
2011 | if (szValue[0] == '0' && (szValue[1] == 'x' || szValue[1] == 'X')) { |
---|
2012 | if (!szValue[2]) return a_nDefault; |
---|
2013 | nValue = ::strtol(&szValue[2], &pszSuffix, 16); |
---|
2014 | } |
---|
2015 | else { |
---|
2016 | nValue = ::strtol(szValue, &pszSuffix, 10); |
---|
2017 | } |
---|
2018 | |
---|
2019 | // any invalid strings will return the default value |
---|
2020 | if (*pszSuffix) { |
---|
2021 | return a_nDefault; |
---|
2022 | } |
---|
2023 | |
---|
2024 | return nValue; |
---|
2025 | } |
---|
2026 | |
---|
2027 | template<class SI_CHAR, class SI_STRLESS, class SI_CONVERTER> |
---|
2028 | SI_Error |
---|
2029 | CSimpleIniTempl<SI_CHAR,SI_STRLESS,SI_CONVERTER>::SetLongValue( |
---|
2030 | const SI_CHAR * a_pSection, |
---|
2031 | const SI_CHAR * a_pKey, |
---|
2032 | long a_nValue, |
---|
2033 | const SI_CHAR * a_pComment, |
---|
2034 | bool a_bUseHex, |
---|
2035 | bool a_bForceReplace |
---|
2036 | ) |
---|
2037 | { |
---|
2038 | // use SetValue to create sections |
---|
2039 | if (!a_pSection || !a_pKey) return SI_FAIL; |
---|
2040 | |
---|
2041 | // convert to an ASCII string |
---|
2042 | char szInput[64]; |
---|
2043 | #if __STDC_WANT_SECURE_LIB__ && !_WIN32_WCE |
---|
2044 | sprintf_s(szInput, a_bUseHex ? "0x%lx" : "%ld", a_nValue); |
---|
2045 | #else // !__STDC_WANT_SECURE_LIB__ |
---|
2046 | sprintf(szInput, a_bUseHex ? "0x%lx" : "%ld", a_nValue); |
---|
2047 | #endif // __STDC_WANT_SECURE_LIB__ |
---|
2048 | |
---|
2049 | // convert to output text |
---|
2050 | SI_CHAR szOutput[64]; |
---|
2051 | SI_CONVERTER c(m_bStoreIsUtf8); |
---|
2052 | c.ConvertFromStore(szInput, strlen(szInput) + 1, |
---|
2053 | szOutput, sizeof(szOutput) / sizeof(SI_CHAR)); |
---|
2054 | |
---|
2055 | // actually add it |
---|
2056 | return AddEntry(a_pSection, a_pKey, szOutput, a_pComment, a_bForceReplace, true); |
---|
2057 | } |
---|
2058 | |
---|
2059 | template<class SI_CHAR, class SI_STRLESS, class SI_CONVERTER> |
---|
2060 | double |
---|
2061 | CSimpleIniTempl<SI_CHAR,SI_STRLESS,SI_CONVERTER>::GetDoubleValue( |
---|
2062 | const SI_CHAR * a_pSection, |
---|
2063 | const SI_CHAR * a_pKey, |
---|
2064 | double a_nDefault, |
---|
2065 | bool * a_pHasMultiple |
---|
2066 | ) const |
---|
2067 | { |
---|
2068 | // return the default if we don't have a value |
---|
2069 | const SI_CHAR * pszValue = GetValue(a_pSection, a_pKey, NULL, a_pHasMultiple); |
---|
2070 | if (!pszValue || !*pszValue) return a_nDefault; |
---|
2071 | |
---|
2072 | // convert to UTF-8/MBCS which for a numeric value will be the same as ASCII |
---|
2073 | char szValue[64] = { 0 }; |
---|
2074 | SI_CONVERTER c(m_bStoreIsUtf8); |
---|
2075 | if (!c.ConvertToStore(pszValue, szValue, sizeof(szValue))) { |
---|
2076 | return a_nDefault; |
---|
2077 | } |
---|
2078 | |
---|
2079 | char * pszSuffix = NULL; |
---|
2080 | double nValue = strtod(szValue, &pszSuffix); |
---|
2081 | |
---|
2082 | // any invalid strings will return the default value |
---|
2083 | if (!pszSuffix || *pszSuffix) { |
---|
2084 | return a_nDefault; |
---|
2085 | } |
---|
2086 | |
---|
2087 | return nValue; |
---|
2088 | } |
---|
2089 | |
---|
2090 | template<class SI_CHAR, class SI_STRLESS, class SI_CONVERTER> |
---|
2091 | SI_Error |
---|
2092 | CSimpleIniTempl<SI_CHAR,SI_STRLESS,SI_CONVERTER>::SetDoubleValue( |
---|
2093 | const SI_CHAR * a_pSection, |
---|
2094 | const SI_CHAR * a_pKey, |
---|
2095 | double a_nValue, |
---|
2096 | const SI_CHAR * a_pComment, |
---|
2097 | bool a_bForceReplace |
---|
2098 | ) |
---|
2099 | { |
---|
2100 | // use SetValue to create sections |
---|
2101 | if (!a_pSection || !a_pKey) return SI_FAIL; |
---|
2102 | |
---|
2103 | // convert to an ASCII string |
---|
2104 | char szInput[64]; |
---|
2105 | #if __STDC_WANT_SECURE_LIB__ && !_WIN32_WCE |
---|
2106 | sprintf_s(szInput, "%f", a_nValue); |
---|
2107 | #else // !__STDC_WANT_SECURE_LIB__ |
---|
2108 | sprintf(szInput, "%f", a_nValue); |
---|
2109 | #endif // __STDC_WANT_SECURE_LIB__ |
---|
2110 | |
---|
2111 | // convert to output text |
---|
2112 | SI_CHAR szOutput[64]; |
---|
2113 | SI_CONVERTER c(m_bStoreIsUtf8); |
---|
2114 | c.ConvertFromStore(szInput, strlen(szInput) + 1, |
---|
2115 | szOutput, sizeof(szOutput) / sizeof(SI_CHAR)); |
---|
2116 | |
---|
2117 | // actually add it |
---|
2118 | return AddEntry(a_pSection, a_pKey, szOutput, a_pComment, a_bForceReplace, true); |
---|
2119 | } |
---|
2120 | |
---|
2121 | template<class SI_CHAR, class SI_STRLESS, class SI_CONVERTER> |
---|
2122 | bool |
---|
2123 | CSimpleIniTempl<SI_CHAR,SI_STRLESS,SI_CONVERTER>::GetBoolValue( |
---|
2124 | const SI_CHAR * a_pSection, |
---|
2125 | const SI_CHAR * a_pKey, |
---|
2126 | bool a_bDefault, |
---|
2127 | bool * a_pHasMultiple |
---|
2128 | ) const |
---|
2129 | { |
---|
2130 | // return the default if we don't have a value |
---|
2131 | const SI_CHAR * pszValue = GetValue(a_pSection, a_pKey, NULL, a_pHasMultiple); |
---|
2132 | if (!pszValue || !*pszValue) return a_bDefault; |
---|
2133 | |
---|
2134 | // we only look at the minimum number of characters |
---|
2135 | switch (pszValue[0]) { |
---|
2136 | case 't': case 'T': // true |
---|
2137 | case 'y': case 'Y': // yes |
---|
2138 | case '1': // 1 (one) |
---|
2139 | return true; |
---|
2140 | |
---|
2141 | case 'f': case 'F': // false |
---|
2142 | case 'n': case 'N': // no |
---|
2143 | case '0': // 0 (zero) |
---|
2144 | return false; |
---|
2145 | |
---|
2146 | case 'o': case 'O': |
---|
2147 | if (pszValue[1] == 'n' || pszValue[1] == 'N') return true; // on |
---|
2148 | if (pszValue[1] == 'f' || pszValue[1] == 'F') return false; // off |
---|
2149 | break; |
---|
2150 | } |
---|
2151 | |
---|
2152 | // no recognized value, return the default |
---|
2153 | return a_bDefault; |
---|
2154 | } |
---|
2155 | |
---|
2156 | template<class SI_CHAR, class SI_STRLESS, class SI_CONVERTER> |
---|
2157 | SI_Error |
---|
2158 | CSimpleIniTempl<SI_CHAR,SI_STRLESS,SI_CONVERTER>::SetBoolValue( |
---|
2159 | const SI_CHAR * a_pSection, |
---|
2160 | const SI_CHAR * a_pKey, |
---|
2161 | bool a_bValue, |
---|
2162 | const SI_CHAR * a_pComment, |
---|
2163 | bool a_bForceReplace |
---|
2164 | ) |
---|
2165 | { |
---|
2166 | // use SetValue to create sections |
---|
2167 | if (!a_pSection || !a_pKey) return SI_FAIL; |
---|
2168 | |
---|
2169 | // convert to an ASCII string |
---|
2170 | const char * pszInput = a_bValue ? "true" : "false"; |
---|
2171 | |
---|
2172 | // convert to output text |
---|
2173 | SI_CHAR szOutput[64]; |
---|
2174 | SI_CONVERTER c(m_bStoreIsUtf8); |
---|
2175 | c.ConvertFromStore(pszInput, strlen(pszInput) + 1, |
---|
2176 | szOutput, sizeof(szOutput) / sizeof(SI_CHAR)); |
---|
2177 | |
---|
2178 | // actually add it |
---|
2179 | return AddEntry(a_pSection, a_pKey, szOutput, a_pComment, a_bForceReplace, true); |
---|
2180 | } |
---|
2181 | |
---|
2182 | template<class SI_CHAR, class SI_STRLESS, class SI_CONVERTER> |
---|
2183 | bool |
---|
2184 | CSimpleIniTempl<SI_CHAR,SI_STRLESS,SI_CONVERTER>::GetAllValues( |
---|
2185 | const SI_CHAR * a_pSection, |
---|
2186 | const SI_CHAR * a_pKey, |
---|
2187 | TNamesDepend & a_values |
---|
2188 | ) const |
---|
2189 | { |
---|
2190 | a_values.clear(); |
---|
2191 | |
---|
2192 | if (!a_pSection || !a_pKey) { |
---|
2193 | return false; |
---|
2194 | } |
---|
2195 | typename TSection::const_iterator iSection = m_data.find(a_pSection); |
---|
2196 | if (iSection == m_data.end()) { |
---|
2197 | return false; |
---|
2198 | } |
---|
2199 | typename TKeyVal::const_iterator iKeyVal = iSection->second.find(a_pKey); |
---|
2200 | if (iKeyVal == iSection->second.end()) { |
---|
2201 | return false; |
---|
2202 | } |
---|
2203 | |
---|
2204 | // insert all values for this key |
---|
2205 | a_values.push_back(Entry(iKeyVal->second, iKeyVal->first.pComment, iKeyVal->first.nOrder)); |
---|
2206 | if (m_bAllowMultiKey) { |
---|
2207 | ++iKeyVal; |
---|
2208 | while (iKeyVal != iSection->second.end() && !IsLess(a_pKey, iKeyVal->first.pItem)) { |
---|
2209 | a_values.push_back(Entry(iKeyVal->second, iKeyVal->first.pComment, iKeyVal->first.nOrder)); |
---|
2210 | ++iKeyVal; |
---|
2211 | } |
---|
2212 | } |
---|
2213 | |
---|
2214 | return true; |
---|
2215 | } |
---|
2216 | |
---|
2217 | template<class SI_CHAR, class SI_STRLESS, class SI_CONVERTER> |
---|
2218 | int |
---|
2219 | CSimpleIniTempl<SI_CHAR,SI_STRLESS,SI_CONVERTER>::GetSectionSize( |
---|
2220 | const SI_CHAR * a_pSection |
---|
2221 | ) const |
---|
2222 | { |
---|
2223 | if (!a_pSection) { |
---|
2224 | return -1; |
---|
2225 | } |
---|
2226 | |
---|
2227 | typename TSection::const_iterator iSection = m_data.find(a_pSection); |
---|
2228 | if (iSection == m_data.end()) { |
---|
2229 | return -1; |
---|
2230 | } |
---|
2231 | const TKeyVal & section = iSection->second; |
---|
2232 | |
---|
2233 | // if multi-key isn't permitted then the section size is |
---|
2234 | // the number of keys that we have. |
---|
2235 | if (!m_bAllowMultiKey || section.empty()) { |
---|
2236 | return (int) section.size(); |
---|
2237 | } |
---|
2238 | |
---|
2239 | // otherwise we need to count them |
---|
2240 | int nCount = 0; |
---|
2241 | const SI_CHAR * pLastKey = NULL; |
---|
2242 | typename TKeyVal::const_iterator iKeyVal = section.begin(); |
---|
2243 | for (int n = 0; iKeyVal != section.end(); ++iKeyVal, ++n) { |
---|
2244 | if (!pLastKey || IsLess(pLastKey, iKeyVal->first.pItem)) { |
---|
2245 | ++nCount; |
---|
2246 | pLastKey = iKeyVal->first.pItem; |
---|
2247 | } |
---|
2248 | } |
---|
2249 | return nCount; |
---|
2250 | } |
---|
2251 | |
---|
2252 | template<class SI_CHAR, class SI_STRLESS, class SI_CONVERTER> |
---|
2253 | const typename CSimpleIniTempl<SI_CHAR,SI_STRLESS,SI_CONVERTER>::TKeyVal * |
---|
2254 | CSimpleIniTempl<SI_CHAR,SI_STRLESS,SI_CONVERTER>::GetSection( |
---|
2255 | const SI_CHAR * a_pSection |
---|
2256 | ) const |
---|
2257 | { |
---|
2258 | if (a_pSection) { |
---|
2259 | typename TSection::const_iterator i = m_data.find(a_pSection); |
---|
2260 | if (i != m_data.end()) { |
---|
2261 | return &(i->second); |
---|
2262 | } |
---|
2263 | } |
---|
2264 | return 0; |
---|
2265 | } |
---|
2266 | |
---|
2267 | template<class SI_CHAR, class SI_STRLESS, class SI_CONVERTER> |
---|
2268 | void |
---|
2269 | CSimpleIniTempl<SI_CHAR,SI_STRLESS,SI_CONVERTER>::GetAllSections( |
---|
2270 | TNamesDepend & a_names |
---|
2271 | ) const |
---|
2272 | { |
---|
2273 | a_names.clear(); |
---|
2274 | typename TSection::const_iterator i = m_data.begin(); |
---|
2275 | for (int n = 0; i != m_data.end(); ++i, ++n ) { |
---|
2276 | a_names.push_back(i->first); |
---|
2277 | } |
---|
2278 | } |
---|
2279 | |
---|
2280 | template<class SI_CHAR, class SI_STRLESS, class SI_CONVERTER> |
---|
2281 | bool |
---|
2282 | CSimpleIniTempl<SI_CHAR,SI_STRLESS,SI_CONVERTER>::GetAllKeys( |
---|
2283 | const SI_CHAR * a_pSection, |
---|
2284 | TNamesDepend & a_names |
---|
2285 | ) const |
---|
2286 | { |
---|
2287 | a_names.clear(); |
---|
2288 | |
---|
2289 | if (!a_pSection) { |
---|
2290 | return false; |
---|
2291 | } |
---|
2292 | |
---|
2293 | typename TSection::const_iterator iSection = m_data.find(a_pSection); |
---|
2294 | if (iSection == m_data.end()) { |
---|
2295 | return false; |
---|
2296 | } |
---|
2297 | |
---|
2298 | const TKeyVal & section = iSection->second; |
---|
2299 | const SI_CHAR * pLastKey = NULL; |
---|
2300 | typename TKeyVal::const_iterator iKeyVal = section.begin(); |
---|
2301 | for (int n = 0; iKeyVal != section.end(); ++iKeyVal, ++n ) { |
---|
2302 | if (!pLastKey || IsLess(pLastKey, iKeyVal->first.pItem)) { |
---|
2303 | a_names.push_back(iKeyVal->first); |
---|
2304 | pLastKey = iKeyVal->first.pItem; |
---|
2305 | } |
---|
2306 | } |
---|
2307 | |
---|
2308 | return true; |
---|
2309 | } |
---|
2310 | |
---|
2311 | template<class SI_CHAR, class SI_STRLESS, class SI_CONVERTER> |
---|
2312 | SI_Error |
---|
2313 | CSimpleIniTempl<SI_CHAR,SI_STRLESS,SI_CONVERTER>::SaveFile( |
---|
2314 | const char * a_pszFile, |
---|
2315 | bool a_bAddSignature |
---|
2316 | ) const |
---|
2317 | { |
---|
2318 | FILE * fp = NULL; |
---|
2319 | #if __STDC_WANT_SECURE_LIB__ && !_WIN32_WCE |
---|
2320 | fopen_s(&fp, a_pszFile, "wb"); |
---|
2321 | #else // !__STDC_WANT_SECURE_LIB__ |
---|
2322 | fp = fopen(a_pszFile, "wb"); |
---|
2323 | #endif // __STDC_WANT_SECURE_LIB__ |
---|
2324 | if (!fp) return SI_FILE; |
---|
2325 | SI_Error rc = SaveFile(fp, a_bAddSignature); |
---|
2326 | fclose(fp); |
---|
2327 | return rc; |
---|
2328 | } |
---|
2329 | |
---|
2330 | #ifdef SI_HAS_WIDE_FILE |
---|
2331 | template<class SI_CHAR, class SI_STRLESS, class SI_CONVERTER> |
---|
2332 | SI_Error |
---|
2333 | CSimpleIniTempl<SI_CHAR,SI_STRLESS,SI_CONVERTER>::SaveFile( |
---|
2334 | const SI_WCHAR_T * a_pwszFile, |
---|
2335 | bool a_bAddSignature |
---|
2336 | ) const |
---|
2337 | { |
---|
2338 | #ifdef _WIN32 |
---|
2339 | FILE * fp = NULL; |
---|
2340 | #if __STDC_WANT_SECURE_LIB__ && !_WIN32_WCE |
---|
2341 | _wfopen_s(&fp, a_pwszFile, L"wb"); |
---|
2342 | #else // !__STDC_WANT_SECURE_LIB__ |
---|
2343 | fp = _wfopen(a_pwszFile, L"wb"); |
---|
2344 | #endif // __STDC_WANT_SECURE_LIB__ |
---|
2345 | if (!fp) return SI_FILE; |
---|
2346 | SI_Error rc = SaveFile(fp, a_bAddSignature); |
---|
2347 | fclose(fp); |
---|
2348 | return rc; |
---|
2349 | #else // !_WIN32 (therefore SI_CONVERT_ICU) |
---|
2350 | char szFile[256]; |
---|
2351 | u_austrncpy(szFile, a_pwszFile, sizeof(szFile)); |
---|
2352 | return SaveFile(szFile, a_bAddSignature); |
---|
2353 | #endif // _WIN32 |
---|
2354 | } |
---|
2355 | #endif // SI_HAS_WIDE_FILE |
---|
2356 | |
---|
2357 | template<class SI_CHAR, class SI_STRLESS, class SI_CONVERTER> |
---|
2358 | SI_Error |
---|
2359 | CSimpleIniTempl<SI_CHAR,SI_STRLESS,SI_CONVERTER>::SaveFile( |
---|
2360 | FILE * a_pFile, |
---|
2361 | bool a_bAddSignature |
---|
2362 | ) const |
---|
2363 | { |
---|
2364 | FileWriter writer(a_pFile); |
---|
2365 | return Save(writer, a_bAddSignature); |
---|
2366 | } |
---|
2367 | |
---|
2368 | template<class SI_CHAR, class SI_STRLESS, class SI_CONVERTER> |
---|
2369 | SI_Error |
---|
2370 | CSimpleIniTempl<SI_CHAR,SI_STRLESS,SI_CONVERTER>::Save( |
---|
2371 | OutputWriter & a_oOutput, |
---|
2372 | bool a_bAddSignature |
---|
2373 | ) const |
---|
2374 | { |
---|
2375 | Converter convert(m_bStoreIsUtf8); |
---|
2376 | |
---|
2377 | // add the UTF-8 signature if it is desired |
---|
2378 | if (m_bStoreIsUtf8 && a_bAddSignature) { |
---|
2379 | a_oOutput.Write(SI_UTF8_SIGNATURE); |
---|
2380 | } |
---|
2381 | |
---|
2382 | // get all of the sections sorted in load order |
---|
2383 | TNamesDepend oSections; |
---|
2384 | GetAllSections(oSections); |
---|
2385 | #if defined(_MSC_VER) && _MSC_VER <= 1200 |
---|
2386 | oSections.sort(); |
---|
2387 | #elif defined(__BORLANDC__) |
---|
2388 | oSections.sort(Entry::LoadOrder()); |
---|
2389 | #else |
---|
2390 | oSections.sort(typename Entry::LoadOrder()); |
---|
2391 | #endif |
---|
2392 | |
---|
2393 | // write the file comment if we have one |
---|
2394 | bool bNeedNewLine = false; |
---|
2395 | if (m_pFileComment) { |
---|
2396 | if (!OutputMultiLineText(a_oOutput, convert, m_pFileComment)) { |
---|
2397 | return SI_FAIL; |
---|
2398 | } |
---|
2399 | bNeedNewLine = true; |
---|
2400 | } |
---|
2401 | |
---|
2402 | // iterate through our sections and output the data |
---|
2403 | typename TNamesDepend::const_iterator iSection = oSections.begin(); |
---|
2404 | for ( ; iSection != oSections.end(); ++iSection ) { |
---|
2405 | // write out the comment if there is one |
---|
2406 | if (iSection->pComment) { |
---|
2407 | if (bNeedNewLine) { |
---|
2408 | a_oOutput.Write(SI_NEWLINE_A); |
---|
2409 | a_oOutput.Write(SI_NEWLINE_A); |
---|
2410 | } |
---|
2411 | if (!OutputMultiLineText(a_oOutput, convert, iSection->pComment)) { |
---|
2412 | return SI_FAIL; |
---|
2413 | } |
---|
2414 | bNeedNewLine = false; |
---|
2415 | } |
---|
2416 | |
---|
2417 | if (bNeedNewLine) { |
---|
2418 | a_oOutput.Write(SI_NEWLINE_A); |
---|
2419 | a_oOutput.Write(SI_NEWLINE_A); |
---|
2420 | bNeedNewLine = false; |
---|
2421 | } |
---|
2422 | |
---|
2423 | // write the section (unless there is no section name) |
---|
2424 | if (*iSection->pItem) { |
---|
2425 | if (!convert.ConvertToStore(iSection->pItem)) { |
---|
2426 | return SI_FAIL; |
---|
2427 | } |
---|
2428 | a_oOutput.Write("["); |
---|
2429 | a_oOutput.Write(convert.Data()); |
---|
2430 | a_oOutput.Write("]"); |
---|
2431 | a_oOutput.Write(SI_NEWLINE_A); |
---|
2432 | } |
---|
2433 | |
---|
2434 | // get all of the keys sorted in load order |
---|
2435 | TNamesDepend oKeys; |
---|
2436 | GetAllKeys(iSection->pItem, oKeys); |
---|
2437 | #if defined(_MSC_VER) && _MSC_VER <= 1200 |
---|
2438 | oKeys.sort(); |
---|
2439 | #elif defined(__BORLANDC__) |
---|
2440 | oKeys.sort(Entry::LoadOrder()); |
---|
2441 | #else |
---|
2442 | oKeys.sort(typename Entry::LoadOrder()); |
---|
2443 | #endif |
---|
2444 | |
---|
2445 | // write all keys and values |
---|
2446 | typename TNamesDepend::const_iterator iKey = oKeys.begin(); |
---|
2447 | for ( ; iKey != oKeys.end(); ++iKey) { |
---|
2448 | // get all values for this key |
---|
2449 | TNamesDepend oValues; |
---|
2450 | GetAllValues(iSection->pItem, iKey->pItem, oValues); |
---|
2451 | |
---|
2452 | typename TNamesDepend::const_iterator iValue = oValues.begin(); |
---|
2453 | for ( ; iValue != oValues.end(); ++iValue) { |
---|
2454 | // write out the comment if there is one |
---|
2455 | if (iValue->pComment) { |
---|
2456 | a_oOutput.Write(SI_NEWLINE_A); |
---|
2457 | if (!OutputMultiLineText(a_oOutput, convert, iValue->pComment)) { |
---|
2458 | return SI_FAIL; |
---|
2459 | } |
---|
2460 | } |
---|
2461 | |
---|
2462 | // write the key |
---|
2463 | if (!convert.ConvertToStore(iKey->pItem)) { |
---|
2464 | return SI_FAIL; |
---|
2465 | } |
---|
2466 | a_oOutput.Write(convert.Data()); |
---|
2467 | |
---|
2468 | // write the value |
---|
2469 | if (!convert.ConvertToStore(iValue->pItem)) { |
---|
2470 | return SI_FAIL; |
---|
2471 | } |
---|
2472 | a_oOutput.Write(m_bSpaces ? " = " : "="); |
---|
2473 | if (m_bAllowMultiLine && IsMultiLineData(iValue->pItem)) { |
---|
2474 | // multi-line data needs to be processed specially to ensure |
---|
2475 | // that we use the correct newline format for the current system |
---|
2476 | a_oOutput.Write("<<<END_OF_TEXT" SI_NEWLINE_A); |
---|
2477 | if (!OutputMultiLineText(a_oOutput, convert, iValue->pItem)) { |
---|
2478 | return SI_FAIL; |
---|
2479 | } |
---|
2480 | a_oOutput.Write("END_OF_TEXT"); |
---|
2481 | } |
---|
2482 | else { |
---|
2483 | a_oOutput.Write(convert.Data()); |
---|
2484 | } |
---|
2485 | a_oOutput.Write(SI_NEWLINE_A); |
---|
2486 | } |
---|
2487 | } |
---|
2488 | |
---|
2489 | bNeedNewLine = true; |
---|
2490 | } |
---|
2491 | |
---|
2492 | return SI_OK; |
---|
2493 | } |
---|
2494 | |
---|
2495 | template<class SI_CHAR, class SI_STRLESS, class SI_CONVERTER> |
---|
2496 | bool |
---|
2497 | CSimpleIniTempl<SI_CHAR,SI_STRLESS,SI_CONVERTER>::OutputMultiLineText( |
---|
2498 | OutputWriter & a_oOutput, |
---|
2499 | Converter & a_oConverter, |
---|
2500 | const SI_CHAR * a_pText |
---|
2501 | ) const |
---|
2502 | { |
---|
2503 | const SI_CHAR * pEndOfLine; |
---|
2504 | SI_CHAR cEndOfLineChar = *a_pText; |
---|
2505 | while (cEndOfLineChar) { |
---|
2506 | // find the end of this line |
---|
2507 | pEndOfLine = a_pText; |
---|
2508 | for (; *pEndOfLine && *pEndOfLine != '\n'; ++pEndOfLine) /*loop*/ ; |
---|
2509 | cEndOfLineChar = *pEndOfLine; |
---|
2510 | |
---|
2511 | // temporarily null terminate, convert and output the line |
---|
2512 | *const_cast<SI_CHAR*>(pEndOfLine) = 0; |
---|
2513 | if (!a_oConverter.ConvertToStore(a_pText)) { |
---|
2514 | return false; |
---|
2515 | } |
---|
2516 | *const_cast<SI_CHAR*>(pEndOfLine) = cEndOfLineChar; |
---|
2517 | a_pText += (pEndOfLine - a_pText) + 1; |
---|
2518 | a_oOutput.Write(a_oConverter.Data()); |
---|
2519 | a_oOutput.Write(SI_NEWLINE_A); |
---|
2520 | } |
---|
2521 | return true; |
---|
2522 | } |
---|
2523 | |
---|
2524 | template<class SI_CHAR, class SI_STRLESS, class SI_CONVERTER> |
---|
2525 | bool |
---|
2526 | CSimpleIniTempl<SI_CHAR,SI_STRLESS,SI_CONVERTER>::Delete( |
---|
2527 | const SI_CHAR * a_pSection, |
---|
2528 | const SI_CHAR * a_pKey, |
---|
2529 | bool a_bRemoveEmpty |
---|
2530 | ) |
---|
2531 | { |
---|
2532 | if (!a_pSection) { |
---|
2533 | return false; |
---|
2534 | } |
---|
2535 | |
---|
2536 | typename TSection::iterator iSection = m_data.find(a_pSection); |
---|
2537 | if (iSection == m_data.end()) { |
---|
2538 | return false; |
---|
2539 | } |
---|
2540 | |
---|
2541 | // remove a single key if we have a keyname |
---|
2542 | if (a_pKey) { |
---|
2543 | typename TKeyVal::iterator iKeyVal = iSection->second.find(a_pKey); |
---|
2544 | if (iKeyVal == iSection->second.end()) { |
---|
2545 | return false; |
---|
2546 | } |
---|
2547 | |
---|
2548 | // remove any copied strings and then the key |
---|
2549 | typename TKeyVal::iterator iDelete; |
---|
2550 | do { |
---|
2551 | iDelete = iKeyVal++; |
---|
2552 | |
---|
2553 | DeleteString(iDelete->first.pItem); |
---|
2554 | DeleteString(iDelete->second); |
---|
2555 | iSection->second.erase(iDelete); |
---|
2556 | } |
---|
2557 | while (iKeyVal != iSection->second.end() |
---|
2558 | && !IsLess(a_pKey, iKeyVal->first.pItem)); |
---|
2559 | |
---|
2560 | // done now if the section is not empty or we are not pruning away |
---|
2561 | // the empty sections. Otherwise let it fall through into the section |
---|
2562 | // deletion code |
---|
2563 | if (!a_bRemoveEmpty || !iSection->second.empty()) { |
---|
2564 | return true; |
---|
2565 | } |
---|
2566 | } |
---|
2567 | else { |
---|
2568 | // delete all copied strings from this section. The actual |
---|
2569 | // entries will be removed when the section is removed. |
---|
2570 | typename TKeyVal::iterator iKeyVal = iSection->second.begin(); |
---|
2571 | for ( ; iKeyVal != iSection->second.end(); ++iKeyVal) { |
---|
2572 | DeleteString(iKeyVal->first.pItem); |
---|
2573 | DeleteString(iKeyVal->second); |
---|
2574 | } |
---|
2575 | } |
---|
2576 | |
---|
2577 | // delete the section itself |
---|
2578 | DeleteString(iSection->first.pItem); |
---|
2579 | m_data.erase(iSection); |
---|
2580 | |
---|
2581 | return true; |
---|
2582 | } |
---|
2583 | |
---|
2584 | template<class SI_CHAR, class SI_STRLESS, class SI_CONVERTER> |
---|
2585 | void |
---|
2586 | CSimpleIniTempl<SI_CHAR,SI_STRLESS,SI_CONVERTER>::DeleteString( |
---|
2587 | const SI_CHAR * a_pString |
---|
2588 | ) |
---|
2589 | { |
---|
2590 | // strings may exist either inside the data block, or they will be |
---|
2591 | // individually allocated and stored in m_strings. We only physically |
---|
2592 | // delete those stored in m_strings. |
---|
2593 | if (a_pString < m_pData || a_pString >= m_pData + m_uDataLen) { |
---|
2594 | typename TNamesDepend::iterator i = m_strings.begin(); |
---|
2595 | for (;i != m_strings.end(); ++i) { |
---|
2596 | if (a_pString == i->pItem) { |
---|
2597 | delete[] const_cast<SI_CHAR*>(i->pItem); |
---|
2598 | m_strings.erase(i); |
---|
2599 | break; |
---|
2600 | } |
---|
2601 | } |
---|
2602 | } |
---|
2603 | } |
---|
2604 | |
---|
2605 | // --------------------------------------------------------------------------- |
---|
2606 | // CONVERSION FUNCTIONS |
---|
2607 | // --------------------------------------------------------------------------- |
---|
2608 | |
---|
2609 | // Defines the conversion classes for different libraries. Before including |
---|
2610 | // SimpleIni.h, set the converter that you wish you use by defining one of the |
---|
2611 | // following symbols. |
---|
2612 | // |
---|
2613 | // SI_CONVERT_GENERIC Use the Unicode reference conversion library in |
---|
2614 | // the accompanying files ConvertUTF.h/c |
---|
2615 | // SI_CONVERT_ICU Use the IBM ICU conversion library. Requires |
---|
2616 | // ICU headers on include path and icuuc.lib |
---|
2617 | // SI_CONVERT_WIN32 Use the Win32 API functions for conversion. |
---|
2618 | |
---|
2619 | #if !defined(SI_CONVERT_GENERIC) && !defined(SI_CONVERT_WIN32) && !defined(SI_CONVERT_ICU) |
---|
2620 | # ifdef _WIN32 |
---|
2621 | # define SI_CONVERT_WIN32 |
---|
2622 | # else |
---|
2623 | # define SI_CONVERT_GENERIC |
---|
2624 | # endif |
---|
2625 | #endif |
---|
2626 | |
---|
2627 | /** |
---|
2628 | * Generic case-sensitive less than comparison. This class returns numerically |
---|
2629 | * ordered ASCII case-sensitive text for all possible sizes and types of |
---|
2630 | * SI_CHAR. |
---|
2631 | */ |
---|
2632 | template<class SI_CHAR> |
---|
2633 | struct SI_GenericCase { |
---|
2634 | bool operator()(const SI_CHAR * pLeft, const SI_CHAR * pRight) const { |
---|
2635 | long cmp; |
---|
2636 | for ( ;*pLeft && *pRight; ++pLeft, ++pRight) { |
---|
2637 | cmp = (long) *pLeft - (long) *pRight; |
---|
2638 | if (cmp != 0) { |
---|
2639 | return cmp < 0; |
---|
2640 | } |
---|
2641 | } |
---|
2642 | return *pRight != 0; |
---|
2643 | } |
---|
2644 | }; |
---|
2645 | |
---|
2646 | /** |
---|
2647 | * Generic ASCII case-insensitive less than comparison. This class returns |
---|
2648 | * numerically ordered ASCII case-insensitive text for all possible sizes |
---|
2649 | * and types of SI_CHAR. It is not safe for MBCS text comparison where |
---|
2650 | * ASCII A-Z characters are used in the encoding of multi-byte characters. |
---|
2651 | */ |
---|
2652 | template<class SI_CHAR> |
---|
2653 | struct SI_GenericNoCase { |
---|
2654 | inline SI_CHAR locase(SI_CHAR ch) const { |
---|
2655 | return (ch < 'A' || ch > 'Z') ? ch : (ch - 'A' + 'a'); |
---|
2656 | } |
---|
2657 | bool operator()(const SI_CHAR * pLeft, const SI_CHAR * pRight) const { |
---|
2658 | long cmp; |
---|
2659 | for ( ;*pLeft && *pRight; ++pLeft, ++pRight) { |
---|
2660 | cmp = (long) locase(*pLeft) - (long) locase(*pRight); |
---|
2661 | if (cmp != 0) { |
---|
2662 | return cmp < 0; |
---|
2663 | } |
---|
2664 | } |
---|
2665 | return *pRight != 0; |
---|
2666 | } |
---|
2667 | }; |
---|
2668 | |
---|
2669 | /** |
---|
2670 | * Null conversion class for MBCS/UTF-8 to char (or equivalent). |
---|
2671 | */ |
---|
2672 | template<class SI_CHAR> |
---|
2673 | class SI_ConvertA { |
---|
2674 | bool m_bStoreIsUtf8; |
---|
2675 | protected: |
---|
2676 | SI_ConvertA() { } |
---|
2677 | public: |
---|
2678 | SI_ConvertA(bool a_bStoreIsUtf8) : m_bStoreIsUtf8(a_bStoreIsUtf8) { } |
---|
2679 | |
---|
2680 | /* copy and assignment */ |
---|
2681 | SI_ConvertA(const SI_ConvertA & rhs) { operator=(rhs); } |
---|
2682 | SI_ConvertA & operator=(const SI_ConvertA & rhs) { |
---|
2683 | m_bStoreIsUtf8 = rhs.m_bStoreIsUtf8; |
---|
2684 | return *this; |
---|
2685 | } |
---|
2686 | |
---|
2687 | /** Calculate the number of SI_CHAR required for converting the input |
---|
2688 | * from the storage format. The storage format is always UTF-8 or MBCS. |
---|
2689 | * |
---|
2690 | * @param a_pInputData Data in storage format to be converted to SI_CHAR. |
---|
2691 | * @param a_uInputDataLen Length of storage format data in bytes. This |
---|
2692 | * must be the actual length of the data, including |
---|
2693 | * NULL byte if NULL terminated string is required. |
---|
2694 | * @return Number of SI_CHAR required by the string when |
---|
2695 | * converted. If there are embedded NULL bytes in the |
---|
2696 | * input data, only the string up and not including |
---|
2697 | * the NULL byte will be converted. |
---|
2698 | * @return -1 cast to size_t on a conversion error. |
---|
2699 | */ |
---|
2700 | size_t SizeFromStore( |
---|
2701 | const char * a_pInputData, |
---|
2702 | size_t a_uInputDataLen) |
---|
2703 | { |
---|
2704 | (void)a_pInputData; |
---|
2705 | SI_ASSERT(a_uInputDataLen != (size_t) -1); |
---|
2706 | |
---|
2707 | // ASCII/MBCS/UTF-8 needs no conversion |
---|
2708 | return a_uInputDataLen; |
---|
2709 | } |
---|
2710 | |
---|
2711 | /** Convert the input string from the storage format to SI_CHAR. |
---|
2712 | * The storage format is always UTF-8 or MBCS. |
---|
2713 | * |
---|
2714 | * @param a_pInputData Data in storage format to be converted to SI_CHAR. |
---|
2715 | * @param a_uInputDataLen Length of storage format data in bytes. This |
---|
2716 | * must be the actual length of the data, including |
---|
2717 | * NULL byte if NULL terminated string is required. |
---|
2718 | * @param a_pOutputData Pointer to the output buffer to received the |
---|
2719 | * converted data. |
---|
2720 | * @param a_uOutputDataSize Size of the output buffer in SI_CHAR. |
---|
2721 | * @return true if all of the input data was successfully |
---|
2722 | * converted. |
---|
2723 | */ |
---|
2724 | bool ConvertFromStore( |
---|
2725 | const char * a_pInputData, |
---|
2726 | size_t a_uInputDataLen, |
---|
2727 | SI_CHAR * a_pOutputData, |
---|
2728 | size_t a_uOutputDataSize) |
---|
2729 | { |
---|
2730 | // ASCII/MBCS/UTF-8 needs no conversion |
---|
2731 | if (a_uInputDataLen > a_uOutputDataSize) { |
---|
2732 | return false; |
---|
2733 | } |
---|
2734 | memcpy(a_pOutputData, a_pInputData, a_uInputDataLen); |
---|
2735 | return true; |
---|
2736 | } |
---|
2737 | |
---|
2738 | /** Calculate the number of char required by the storage format of this |
---|
2739 | * data. The storage format is always UTF-8 or MBCS. |
---|
2740 | * |
---|
2741 | * @param a_pInputData NULL terminated string to calculate the number of |
---|
2742 | * bytes required to be converted to storage format. |
---|
2743 | * @return Number of bytes required by the string when |
---|
2744 | * converted to storage format. This size always |
---|
2745 | * includes space for the terminating NULL character. |
---|
2746 | * @return -1 cast to size_t on a conversion error. |
---|
2747 | */ |
---|
2748 | size_t SizeToStore( |
---|
2749 | const SI_CHAR * a_pInputData) |
---|
2750 | { |
---|
2751 | // ASCII/MBCS/UTF-8 needs no conversion |
---|
2752 | return strlen((const char *)a_pInputData) + 1; |
---|
2753 | } |
---|
2754 | |
---|
2755 | /** Convert the input string to the storage format of this data. |
---|
2756 | * The storage format is always UTF-8 or MBCS. |
---|
2757 | * |
---|
2758 | * @param a_pInputData NULL terminated source string to convert. All of |
---|
2759 | * the data will be converted including the |
---|
2760 | * terminating NULL character. |
---|
2761 | * @param a_pOutputData Pointer to the buffer to receive the converted |
---|
2762 | * string. |
---|
2763 | * @param a_uOutputDataSize Size of the output buffer in char. |
---|
2764 | * @return true if all of the input data, including the |
---|
2765 | * terminating NULL character was successfully |
---|
2766 | * converted. |
---|
2767 | */ |
---|
2768 | bool ConvertToStore( |
---|
2769 | const SI_CHAR * a_pInputData, |
---|
2770 | char * a_pOutputData, |
---|
2771 | size_t a_uOutputDataSize) |
---|
2772 | { |
---|
2773 | // calc input string length (SI_CHAR type and size independent) |
---|
2774 | size_t uInputLen = strlen((const char *)a_pInputData) + 1; |
---|
2775 | if (uInputLen > a_uOutputDataSize) { |
---|
2776 | return false; |
---|
2777 | } |
---|
2778 | |
---|
2779 | // ascii/UTF-8 needs no conversion |
---|
2780 | memcpy(a_pOutputData, a_pInputData, uInputLen); |
---|
2781 | return true; |
---|
2782 | } |
---|
2783 | }; |
---|
2784 | |
---|
2785 | |
---|
2786 | // --------------------------------------------------------------------------- |
---|
2787 | // SI_CONVERT_GENERIC |
---|
2788 | // --------------------------------------------------------------------------- |
---|
2789 | #ifdef SI_CONVERT_GENERIC |
---|
2790 | |
---|
2791 | #define SI_Case SI_GenericCase |
---|
2792 | #define SI_NoCase SI_GenericNoCase |
---|
2793 | |
---|
2794 | #include <wchar.h> |
---|
2795 | #include "ConvertUTF.h" |
---|
2796 | |
---|
2797 | /** |
---|
2798 | * Converts UTF-8 to a wchar_t (or equivalent) using the Unicode reference |
---|
2799 | * library functions. This can be used on all platforms. |
---|
2800 | */ |
---|
2801 | template<class SI_CHAR> |
---|
2802 | class SI_ConvertW { |
---|
2803 | bool m_bStoreIsUtf8; |
---|
2804 | protected: |
---|
2805 | SI_ConvertW() { } |
---|
2806 | public: |
---|
2807 | SI_ConvertW(bool a_bStoreIsUtf8) : m_bStoreIsUtf8(a_bStoreIsUtf8) { } |
---|
2808 | |
---|
2809 | /* copy and assignment */ |
---|
2810 | SI_ConvertW(const SI_ConvertW & rhs) { operator=(rhs); } |
---|
2811 | SI_ConvertW & operator=(const SI_ConvertW & rhs) { |
---|
2812 | m_bStoreIsUtf8 = rhs.m_bStoreIsUtf8; |
---|
2813 | return *this; |
---|
2814 | } |
---|
2815 | |
---|
2816 | /** Calculate the number of SI_CHAR required for converting the input |
---|
2817 | * from the storage format. The storage format is always UTF-8 or MBCS. |
---|
2818 | * |
---|
2819 | * @param a_pInputData Data in storage format to be converted to SI_CHAR. |
---|
2820 | * @param a_uInputDataLen Length of storage format data in bytes. This |
---|
2821 | * must be the actual length of the data, including |
---|
2822 | * NULL byte if NULL terminated string is required. |
---|
2823 | * @return Number of SI_CHAR required by the string when |
---|
2824 | * converted. If there are embedded NULL bytes in the |
---|
2825 | * input data, only the string up and not including |
---|
2826 | * the NULL byte will be converted. |
---|
2827 | * @return -1 cast to size_t on a conversion error. |
---|
2828 | */ |
---|
2829 | size_t SizeFromStore( |
---|
2830 | const char * a_pInputData, |
---|
2831 | size_t a_uInputDataLen) |
---|
2832 | { |
---|
2833 | SI_ASSERT(a_uInputDataLen != (size_t) -1); |
---|
2834 | |
---|
2835 | if (m_bStoreIsUtf8) { |
---|
2836 | // worst case scenario for UTF-8 to wchar_t is 1 char -> 1 wchar_t |
---|
2837 | // so we just return the same number of characters required as for |
---|
2838 | // the source text. |
---|
2839 | return a_uInputDataLen; |
---|
2840 | } |
---|
2841 | |
---|
2842 | #if defined(SI_NO_MBSTOWCS_NULL) || (!defined(_MSC_VER) && !defined(_linux)) |
---|
2843 | // fall back processing for platforms that don't support a NULL dest to mbstowcs |
---|
2844 | // worst case scenario is 1:1, this will be a sufficient buffer size |
---|
2845 | (void)a_pInputData; |
---|
2846 | return a_uInputDataLen; |
---|
2847 | #else |
---|
2848 | // get the actual required buffer size |
---|
2849 | return mbstowcs(NULL, a_pInputData, a_uInputDataLen); |
---|
2850 | #endif |
---|
2851 | } |
---|
2852 | |
---|
2853 | /** Convert the input string from the storage format to SI_CHAR. |
---|
2854 | * The storage format is always UTF-8 or MBCS. |
---|
2855 | * |
---|
2856 | * @param a_pInputData Data in storage format to be converted to SI_CHAR. |
---|
2857 | * @param a_uInputDataLen Length of storage format data in bytes. This |
---|
2858 | * must be the actual length of the data, including |
---|
2859 | * NULL byte if NULL terminated string is required. |
---|
2860 | * @param a_pOutputData Pointer to the output buffer to received the |
---|
2861 | * converted data. |
---|
2862 | * @param a_uOutputDataSize Size of the output buffer in SI_CHAR. |
---|
2863 | * @return true if all of the input data was successfully |
---|
2864 | * converted. |
---|
2865 | */ |
---|
2866 | bool ConvertFromStore( |
---|
2867 | const char * a_pInputData, |
---|
2868 | size_t a_uInputDataLen, |
---|
2869 | SI_CHAR * a_pOutputData, |
---|
2870 | size_t a_uOutputDataSize) |
---|
2871 | { |
---|
2872 | if (m_bStoreIsUtf8) { |
---|
2873 | // This uses the Unicode reference implementation to do the |
---|
2874 | // conversion from UTF-8 to wchar_t. The required files are |
---|
2875 | // ConvertUTF.h and ConvertUTF.c which should be included in |
---|
2876 | // the distribution but are publically available from unicode.org |
---|
2877 | // at http://www.unicode.org/Public/PROGRAMS/CVTUTF/ |
---|
2878 | ConversionResult retval; |
---|
2879 | const UTF8 * pUtf8 = (const UTF8 *) a_pInputData; |
---|
2880 | if (sizeof(wchar_t) == sizeof(UTF32)) { |
---|
2881 | UTF32 * pUtf32 = (UTF32 *) a_pOutputData; |
---|
2882 | retval = ConvertUTF8toUTF32( |
---|
2883 | &pUtf8, pUtf8 + a_uInputDataLen, |
---|
2884 | &pUtf32, pUtf32 + a_uOutputDataSize, |
---|
2885 | lenientConversion); |
---|
2886 | } |
---|
2887 | else if (sizeof(wchar_t) == sizeof(UTF16)) { |
---|
2888 | UTF16 * pUtf16 = (UTF16 *) a_pOutputData; |
---|
2889 | retval = ConvertUTF8toUTF16( |
---|
2890 | &pUtf8, pUtf8 + a_uInputDataLen, |
---|
2891 | &pUtf16, pUtf16 + a_uOutputDataSize, |
---|
2892 | lenientConversion); |
---|
2893 | } |
---|
2894 | return retval == conversionOK; |
---|
2895 | } |
---|
2896 | |
---|
2897 | // convert to wchar_t |
---|
2898 | size_t retval = mbstowcs(a_pOutputData, |
---|
2899 | a_pInputData, a_uOutputDataSize); |
---|
2900 | return retval != (size_t)(-1); |
---|
2901 | } |
---|
2902 | |
---|
2903 | /** Calculate the number of char required by the storage format of this |
---|
2904 | * data. The storage format is always UTF-8 or MBCS. |
---|
2905 | * |
---|
2906 | * @param a_pInputData NULL terminated string to calculate the number of |
---|
2907 | * bytes required to be converted to storage format. |
---|
2908 | * @return Number of bytes required by the string when |
---|
2909 | * converted to storage format. This size always |
---|
2910 | * includes space for the terminating NULL character. |
---|
2911 | * @return -1 cast to size_t on a conversion error. |
---|
2912 | */ |
---|
2913 | size_t SizeToStore( |
---|
2914 | const SI_CHAR * a_pInputData) |
---|
2915 | { |
---|
2916 | if (m_bStoreIsUtf8) { |
---|
2917 | // worst case scenario for wchar_t to UTF-8 is 1 wchar_t -> 6 char |
---|
2918 | size_t uLen = 0; |
---|
2919 | while (a_pInputData[uLen]) { |
---|
2920 | ++uLen; |
---|
2921 | } |
---|
2922 | return (6 * uLen) + 1; |
---|
2923 | } |
---|
2924 | else { |
---|
2925 | size_t uLen = wcstombs(NULL, a_pInputData, 0); |
---|
2926 | if (uLen == (size_t)(-1)) { |
---|
2927 | return uLen; |
---|
2928 | } |
---|
2929 | return uLen + 1; // include NULL terminator |
---|
2930 | } |
---|
2931 | } |
---|
2932 | |
---|
2933 | /** Convert the input string to the storage format of this data. |
---|
2934 | * The storage format is always UTF-8 or MBCS. |
---|
2935 | * |
---|
2936 | * @param a_pInputData NULL terminated source string to convert. All of |
---|
2937 | * the data will be converted including the |
---|
2938 | * terminating NULL character. |
---|
2939 | * @param a_pOutputData Pointer to the buffer to receive the converted |
---|
2940 | * string. |
---|
2941 | * @param a_uOutputDataSize Size of the output buffer in char. |
---|
2942 | * @return true if all of the input data, including the |
---|
2943 | * terminating NULL character was successfully |
---|
2944 | * converted. |
---|
2945 | */ |
---|
2946 | bool ConvertToStore( |
---|
2947 | const SI_CHAR * a_pInputData, |
---|
2948 | char * a_pOutputData, |
---|
2949 | size_t a_uOutputDataSize |
---|
2950 | ) |
---|
2951 | { |
---|
2952 | if (m_bStoreIsUtf8) { |
---|
2953 | // calc input string length (SI_CHAR type and size independent) |
---|
2954 | size_t uInputLen = 0; |
---|
2955 | while (a_pInputData[uInputLen]) { |
---|
2956 | ++uInputLen; |
---|
2957 | } |
---|
2958 | ++uInputLen; // include the NULL char |
---|
2959 | |
---|
2960 | // This uses the Unicode reference implementation to do the |
---|
2961 | // conversion from wchar_t to UTF-8. The required files are |
---|
2962 | // ConvertUTF.h and ConvertUTF.c which should be included in |
---|
2963 | // the distribution but are publically available from unicode.org |
---|
2964 | // at http://www.unicode.org/Public/PROGRAMS/CVTUTF/ |
---|
2965 | ConversionResult retval; |
---|
2966 | UTF8 * pUtf8 = (UTF8 *) a_pOutputData; |
---|
2967 | if (sizeof(wchar_t) == sizeof(UTF32)) { |
---|
2968 | const UTF32 * pUtf32 = (const UTF32 *) a_pInputData; |
---|
2969 | retval = ConvertUTF32toUTF8( |
---|
2970 | &pUtf32, pUtf32 + uInputLen, |
---|
2971 | &pUtf8, pUtf8 + a_uOutputDataSize, |
---|
2972 | lenientConversion); |
---|
2973 | } |
---|
2974 | else if (sizeof(wchar_t) == sizeof(UTF16)) { |
---|
2975 | const UTF16 * pUtf16 = (const UTF16 *) a_pInputData; |
---|
2976 | retval = ConvertUTF16toUTF8( |
---|
2977 | &pUtf16, pUtf16 + uInputLen, |
---|
2978 | &pUtf8, pUtf8 + a_uOutputDataSize, |
---|
2979 | lenientConversion); |
---|
2980 | } |
---|
2981 | return retval == conversionOK; |
---|
2982 | } |
---|
2983 | else { |
---|
2984 | size_t retval = wcstombs(a_pOutputData, |
---|
2985 | a_pInputData, a_uOutputDataSize); |
---|
2986 | return retval != (size_t) -1; |
---|
2987 | } |
---|
2988 | } |
---|
2989 | }; |
---|
2990 | |
---|
2991 | #endif // SI_CONVERT_GENERIC |
---|
2992 | |
---|
2993 | |
---|
2994 | // --------------------------------------------------------------------------- |
---|
2995 | // SI_CONVERT_ICU |
---|
2996 | // --------------------------------------------------------------------------- |
---|
2997 | #ifdef SI_CONVERT_ICU |
---|
2998 | |
---|
2999 | #define SI_Case SI_GenericCase |
---|
3000 | #define SI_NoCase SI_GenericNoCase |
---|
3001 | |
---|
3002 | #include <unicode/ucnv.h> |
---|
3003 | |
---|
3004 | /** |
---|
3005 | * Converts MBCS/UTF-8 to UChar using ICU. This can be used on all platforms. |
---|
3006 | */ |
---|
3007 | template<class SI_CHAR> |
---|
3008 | class SI_ConvertW { |
---|
3009 | const char * m_pEncoding; |
---|
3010 | UConverter * m_pConverter; |
---|
3011 | protected: |
---|
3012 | SI_ConvertW() : m_pEncoding(NULL), m_pConverter(NULL) { } |
---|
3013 | public: |
---|
3014 | SI_ConvertW(bool a_bStoreIsUtf8) : m_pConverter(NULL) { |
---|
3015 | m_pEncoding = a_bStoreIsUtf8 ? "UTF-8" : NULL; |
---|
3016 | } |
---|
3017 | |
---|
3018 | /* copy and assignment */ |
---|
3019 | SI_ConvertW(const SI_ConvertW & rhs) { operator=(rhs); } |
---|
3020 | SI_ConvertW & operator=(const SI_ConvertW & rhs) { |
---|
3021 | m_pEncoding = rhs.m_pEncoding; |
---|
3022 | m_pConverter = NULL; |
---|
3023 | return *this; |
---|
3024 | } |
---|
3025 | ~SI_ConvertW() { if (m_pConverter) ucnv_close(m_pConverter); } |
---|
3026 | |
---|
3027 | /** Calculate the number of UChar required for converting the input |
---|
3028 | * from the storage format. The storage format is always UTF-8 or MBCS. |
---|
3029 | * |
---|
3030 | * @param a_pInputData Data in storage format to be converted to UChar. |
---|
3031 | * @param a_uInputDataLen Length of storage format data in bytes. This |
---|
3032 | * must be the actual length of the data, including |
---|
3033 | * NULL byte if NULL terminated string is required. |
---|
3034 | * @return Number of UChar required by the string when |
---|
3035 | * converted. If there are embedded NULL bytes in the |
---|
3036 | * input data, only the string up and not including |
---|
3037 | * the NULL byte will be converted. |
---|
3038 | * @return -1 cast to size_t on a conversion error. |
---|
3039 | */ |
---|
3040 | size_t SizeFromStore( |
---|
3041 | const char * a_pInputData, |
---|
3042 | size_t a_uInputDataLen) |
---|
3043 | { |
---|
3044 | SI_ASSERT(a_uInputDataLen != (size_t) -1); |
---|
3045 | |
---|
3046 | UErrorCode nError; |
---|
3047 | |
---|
3048 | if (!m_pConverter) { |
---|
3049 | nError = U_ZERO_ERROR; |
---|
3050 | m_pConverter = ucnv_open(m_pEncoding, &nError); |
---|
3051 | if (U_FAILURE(nError)) { |
---|
3052 | return (size_t) -1; |
---|
3053 | } |
---|
3054 | } |
---|
3055 | |
---|
3056 | nError = U_ZERO_ERROR; |
---|
3057 | int32_t nLen = ucnv_toUChars(m_pConverter, NULL, 0, |
---|
3058 | a_pInputData, (int32_t) a_uInputDataLen, &nError); |
---|
3059 | if (U_FAILURE(nError) && nError != U_BUFFER_OVERFLOW_ERROR) { |
---|
3060 | return (size_t) -1; |
---|
3061 | } |
---|
3062 | |
---|
3063 | return (size_t) nLen; |
---|
3064 | } |
---|
3065 | |
---|
3066 | /** Convert the input string from the storage format to UChar. |
---|
3067 | * The storage format is always UTF-8 or MBCS. |
---|
3068 | * |
---|
3069 | * @param a_pInputData Data in storage format to be converted to UChar. |
---|
3070 | * @param a_uInputDataLen Length of storage format data in bytes. This |
---|
3071 | * must be the actual length of the data, including |
---|
3072 | * NULL byte if NULL terminated string is required. |
---|
3073 | * @param a_pOutputData Pointer to the output buffer to received the |
---|
3074 | * converted data. |
---|
3075 | * @param a_uOutputDataSize Size of the output buffer in UChar. |
---|
3076 | * @return true if all of the input data was successfully |
---|
3077 | * converted. |
---|
3078 | */ |
---|
3079 | bool ConvertFromStore( |
---|
3080 | const char * a_pInputData, |
---|
3081 | size_t a_uInputDataLen, |
---|
3082 | UChar * a_pOutputData, |
---|
3083 | size_t a_uOutputDataSize) |
---|
3084 | { |
---|
3085 | UErrorCode nError; |
---|
3086 | |
---|
3087 | if (!m_pConverter) { |
---|
3088 | nError = U_ZERO_ERROR; |
---|
3089 | m_pConverter = ucnv_open(m_pEncoding, &nError); |
---|
3090 | if (U_FAILURE(nError)) { |
---|
3091 | return false; |
---|
3092 | } |
---|
3093 | } |
---|
3094 | |
---|
3095 | nError = U_ZERO_ERROR; |
---|
3096 | ucnv_toUChars(m_pConverter, |
---|
3097 | a_pOutputData, (int32_t) a_uOutputDataSize, |
---|
3098 | a_pInputData, (int32_t) a_uInputDataLen, &nError); |
---|
3099 | if (U_FAILURE(nError)) { |
---|
3100 | return false; |
---|
3101 | } |
---|
3102 | |
---|
3103 | return true; |
---|
3104 | } |
---|
3105 | |
---|
3106 | /** Calculate the number of char required by the storage format of this |
---|
3107 | * data. The storage format is always UTF-8 or MBCS. |
---|
3108 | * |
---|
3109 | * @param a_pInputData NULL terminated string to calculate the number of |
---|
3110 | * bytes required to be converted to storage format. |
---|
3111 | * @return Number of bytes required by the string when |
---|
3112 | * converted to storage format. This size always |
---|
3113 | * includes space for the terminating NULL character. |
---|
3114 | * @return -1 cast to size_t on a conversion error. |
---|
3115 | */ |
---|
3116 | size_t SizeToStore( |
---|
3117 | const UChar * a_pInputData) |
---|
3118 | { |
---|
3119 | UErrorCode nError; |
---|
3120 | |
---|
3121 | if (!m_pConverter) { |
---|
3122 | nError = U_ZERO_ERROR; |
---|
3123 | m_pConverter = ucnv_open(m_pEncoding, &nError); |
---|
3124 | if (U_FAILURE(nError)) { |
---|
3125 | return (size_t) -1; |
---|
3126 | } |
---|
3127 | } |
---|
3128 | |
---|
3129 | nError = U_ZERO_ERROR; |
---|
3130 | int32_t nLen = ucnv_fromUChars(m_pConverter, NULL, 0, |
---|
3131 | a_pInputData, -1, &nError); |
---|
3132 | if (U_FAILURE(nError) && nError != U_BUFFER_OVERFLOW_ERROR) { |
---|
3133 | return (size_t) -1; |
---|
3134 | } |
---|
3135 | |
---|
3136 | return (size_t) nLen + 1; |
---|
3137 | } |
---|
3138 | |
---|
3139 | /** Convert the input string to the storage format of this data. |
---|
3140 | * The storage format is always UTF-8 or MBCS. |
---|
3141 | * |
---|
3142 | * @param a_pInputData NULL terminated source string to convert. All of |
---|
3143 | * the data will be converted including the |
---|
3144 | * terminating NULL character. |
---|
3145 | * @param a_pOutputData Pointer to the buffer to receive the converted |
---|
3146 | * string. |
---|
3147 | * @param a_pOutputDataSize Size of the output buffer in char. |
---|
3148 | * @return true if all of the input data, including the |
---|
3149 | * terminating NULL character was successfully |
---|
3150 | * converted. |
---|
3151 | */ |
---|
3152 | bool ConvertToStore( |
---|
3153 | const UChar * a_pInputData, |
---|
3154 | char * a_pOutputData, |
---|
3155 | size_t a_uOutputDataSize) |
---|
3156 | { |
---|
3157 | UErrorCode nError; |
---|
3158 | |
---|
3159 | if (!m_pConverter) { |
---|
3160 | nError = U_ZERO_ERROR; |
---|
3161 | m_pConverter = ucnv_open(m_pEncoding, &nError); |
---|
3162 | if (U_FAILURE(nError)) { |
---|
3163 | return false; |
---|
3164 | } |
---|
3165 | } |
---|
3166 | |
---|
3167 | nError = U_ZERO_ERROR; |
---|
3168 | ucnv_fromUChars(m_pConverter, |
---|
3169 | a_pOutputData, (int32_t) a_uOutputDataSize, |
---|
3170 | a_pInputData, -1, &nError); |
---|
3171 | if (U_FAILURE(nError)) { |
---|
3172 | return false; |
---|
3173 | } |
---|
3174 | |
---|
3175 | return true; |
---|
3176 | } |
---|
3177 | }; |
---|
3178 | |
---|
3179 | #endif // SI_CONVERT_ICU |
---|
3180 | |
---|
3181 | |
---|
3182 | // --------------------------------------------------------------------------- |
---|
3183 | // SI_CONVERT_WIN32 |
---|
3184 | // --------------------------------------------------------------------------- |
---|
3185 | #ifdef SI_CONVERT_WIN32 |
---|
3186 | |
---|
3187 | #define SI_Case SI_GenericCase |
---|
3188 | |
---|
3189 | // Windows CE doesn't have errno or MBCS libraries |
---|
3190 | #ifdef _WIN32_WCE |
---|
3191 | # ifndef SI_NO_MBCS |
---|
3192 | # define SI_NO_MBCS |
---|
3193 | # endif |
---|
3194 | #endif |
---|
3195 | |
---|
3196 | #include <windows.h> |
---|
3197 | #ifdef SI_NO_MBCS |
---|
3198 | # define SI_NoCase SI_GenericNoCase |
---|
3199 | #else // !SI_NO_MBCS |
---|
3200 | /** |
---|
3201 | * Case-insensitive comparison class using Win32 MBCS functions. This class |
---|
3202 | * returns a case-insensitive semi-collation order for MBCS text. It may not |
---|
3203 | * be safe for UTF-8 text returned in char format as we don't know what |
---|
3204 | * characters will be folded by the function! Therefore, if you are using |
---|
3205 | * SI_CHAR == char and SetUnicode(true), then you need to use the generic |
---|
3206 | * SI_NoCase class instead. |
---|
3207 | */ |
---|
3208 | #include <mbstring.h> |
---|
3209 | template<class SI_CHAR> |
---|
3210 | struct SI_NoCase { |
---|
3211 | bool operator()(const SI_CHAR * pLeft, const SI_CHAR * pRight) const { |
---|
3212 | if (sizeof(SI_CHAR) == sizeof(char)) { |
---|
3213 | return _mbsicmp((const unsigned char *)pLeft, |
---|
3214 | (const unsigned char *)pRight) < 0; |
---|
3215 | } |
---|
3216 | if (sizeof(SI_CHAR) == sizeof(wchar_t)) { |
---|
3217 | return _wcsicmp((const wchar_t *)pLeft, |
---|
3218 | (const wchar_t *)pRight) < 0; |
---|
3219 | } |
---|
3220 | return SI_GenericNoCase<SI_CHAR>()(pLeft, pRight); |
---|
3221 | } |
---|
3222 | }; |
---|
3223 | #endif // SI_NO_MBCS |
---|
3224 | |
---|
3225 | /** |
---|
3226 | * Converts MBCS and UTF-8 to a wchar_t (or equivalent) on Windows. This uses |
---|
3227 | * only the Win32 functions and doesn't require the external Unicode UTF-8 |
---|
3228 | * conversion library. It will not work on Windows 95 without using Microsoft |
---|
3229 | * Layer for Unicode in your application. |
---|
3230 | */ |
---|
3231 | template<class SI_CHAR> |
---|
3232 | class SI_ConvertW { |
---|
3233 | UINT m_uCodePage; |
---|
3234 | protected: |
---|
3235 | SI_ConvertW() { } |
---|
3236 | public: |
---|
3237 | SI_ConvertW(bool a_bStoreIsUtf8) { |
---|
3238 | m_uCodePage = a_bStoreIsUtf8 ? CP_UTF8 : CP_ACP; |
---|
3239 | } |
---|
3240 | |
---|
3241 | /* copy and assignment */ |
---|
3242 | SI_ConvertW(const SI_ConvertW & rhs) { operator=(rhs); } |
---|
3243 | SI_ConvertW & operator=(const SI_ConvertW & rhs) { |
---|
3244 | m_uCodePage = rhs.m_uCodePage; |
---|
3245 | return *this; |
---|
3246 | } |
---|
3247 | |
---|
3248 | /** Calculate the number of SI_CHAR required for converting the input |
---|
3249 | * from the storage format. The storage format is always UTF-8 or MBCS. |
---|
3250 | * |
---|
3251 | * @param a_pInputData Data in storage format to be converted to SI_CHAR. |
---|
3252 | * @param a_uInputDataLen Length of storage format data in bytes. This |
---|
3253 | * must be the actual length of the data, including |
---|
3254 | * NULL byte if NULL terminated string is required. |
---|
3255 | * @return Number of SI_CHAR required by the string when |
---|
3256 | * converted. If there are embedded NULL bytes in the |
---|
3257 | * input data, only the string up and not including |
---|
3258 | * the NULL byte will be converted. |
---|
3259 | * @return -1 cast to size_t on a conversion error. |
---|
3260 | */ |
---|
3261 | size_t SizeFromStore( |
---|
3262 | const char * a_pInputData, |
---|
3263 | size_t a_uInputDataLen) |
---|
3264 | { |
---|
3265 | SI_ASSERT(a_uInputDataLen != (size_t) -1); |
---|
3266 | |
---|
3267 | int retval = MultiByteToWideChar( |
---|
3268 | m_uCodePage, 0, |
---|
3269 | a_pInputData, (int) a_uInputDataLen, |
---|
3270 | 0, 0); |
---|
3271 | return (size_t)(retval > 0 ? retval : -1); |
---|
3272 | } |
---|
3273 | |
---|
3274 | /** Convert the input string from the storage format to SI_CHAR. |
---|
3275 | * The storage format is always UTF-8 or MBCS. |
---|
3276 | * |
---|
3277 | * @param a_pInputData Data in storage format to be converted to SI_CHAR. |
---|
3278 | * @param a_uInputDataLen Length of storage format data in bytes. This |
---|
3279 | * must be the actual length of the data, including |
---|
3280 | * NULL byte if NULL terminated string is required. |
---|
3281 | * @param a_pOutputData Pointer to the output buffer to received the |
---|
3282 | * converted data. |
---|
3283 | * @param a_uOutputDataSize Size of the output buffer in SI_CHAR. |
---|
3284 | * @return true if all of the input data was successfully |
---|
3285 | * converted. |
---|
3286 | */ |
---|
3287 | bool ConvertFromStore( |
---|
3288 | const char * a_pInputData, |
---|
3289 | size_t a_uInputDataLen, |
---|
3290 | SI_CHAR * a_pOutputData, |
---|
3291 | size_t a_uOutputDataSize) |
---|
3292 | { |
---|
3293 | int nSize = MultiByteToWideChar( |
---|
3294 | m_uCodePage, 0, |
---|
3295 | a_pInputData, (int) a_uInputDataLen, |
---|
3296 | (wchar_t *) a_pOutputData, (int) a_uOutputDataSize); |
---|
3297 | return (nSize > 0); |
---|
3298 | } |
---|
3299 | |
---|
3300 | /** Calculate the number of char required by the storage format of this |
---|
3301 | * data. The storage format is always UTF-8. |
---|
3302 | * |
---|
3303 | * @param a_pInputData NULL terminated string to calculate the number of |
---|
3304 | * bytes required to be converted to storage format. |
---|
3305 | * @return Number of bytes required by the string when |
---|
3306 | * converted to storage format. This size always |
---|
3307 | * includes space for the terminating NULL character. |
---|
3308 | * @return -1 cast to size_t on a conversion error. |
---|
3309 | */ |
---|
3310 | size_t SizeToStore( |
---|
3311 | const SI_CHAR * a_pInputData) |
---|
3312 | { |
---|
3313 | int retval = WideCharToMultiByte( |
---|
3314 | m_uCodePage, 0, |
---|
3315 | (const wchar_t *) a_pInputData, -1, |
---|
3316 | 0, 0, 0, 0); |
---|
3317 | return (size_t) (retval > 0 ? retval : -1); |
---|
3318 | } |
---|
3319 | |
---|
3320 | /** Convert the input string to the storage format of this data. |
---|
3321 | * The storage format is always UTF-8 or MBCS. |
---|
3322 | * |
---|
3323 | * @param a_pInputData NULL terminated source string to convert. All of |
---|
3324 | * the data will be converted including the |
---|
3325 | * terminating NULL character. |
---|
3326 | * @param a_pOutputData Pointer to the buffer to receive the converted |
---|
3327 | * string. |
---|
3328 | * @param a_pOutputDataSize Size of the output buffer in char. |
---|
3329 | * @return true if all of the input data, including the |
---|
3330 | * terminating NULL character was successfully |
---|
3331 | * converted. |
---|
3332 | */ |
---|
3333 | bool ConvertToStore( |
---|
3334 | const SI_CHAR * a_pInputData, |
---|
3335 | char * a_pOutputData, |
---|
3336 | size_t a_uOutputDataSize) |
---|
3337 | { |
---|
3338 | int retval = WideCharToMultiByte( |
---|
3339 | m_uCodePage, 0, |
---|
3340 | (const wchar_t *) a_pInputData, -1, |
---|
3341 | a_pOutputData, (int) a_uOutputDataSize, 0, 0); |
---|
3342 | return retval > 0; |
---|
3343 | } |
---|
3344 | }; |
---|
3345 | |
---|
3346 | #endif // SI_CONVERT_WIN32 |
---|
3347 | |
---|
3348 | |
---|
3349 | // --------------------------------------------------------------------------- |
---|
3350 | // TYPE DEFINITIONS |
---|
3351 | // --------------------------------------------------------------------------- |
---|
3352 | |
---|
3353 | typedef CSimpleIniTempl<char, |
---|
3354 | SI_NoCase<char>,SI_ConvertA<char> > CSimpleIniA; |
---|
3355 | typedef CSimpleIniTempl<char, |
---|
3356 | SI_Case<char>,SI_ConvertA<char> > CSimpleIniCaseA; |
---|
3357 | |
---|
3358 | #if defined(SI_CONVERT_ICU) |
---|
3359 | typedef CSimpleIniTempl<UChar, |
---|
3360 | SI_NoCase<UChar>,SI_ConvertW<UChar> > CSimpleIniW; |
---|
3361 | typedef CSimpleIniTempl<UChar, |
---|
3362 | SI_Case<UChar>,SI_ConvertW<UChar> > CSimpleIniCaseW; |
---|
3363 | #else |
---|
3364 | typedef CSimpleIniTempl<wchar_t, |
---|
3365 | SI_NoCase<wchar_t>,SI_ConvertW<wchar_t> > CSimpleIniW; |
---|
3366 | typedef CSimpleIniTempl<wchar_t, |
---|
3367 | SI_Case<wchar_t>,SI_ConvertW<wchar_t> > CSimpleIniCaseW; |
---|
3368 | #endif |
---|
3369 | |
---|
3370 | #ifdef _UNICODE |
---|
3371 | # define CSimpleIni CSimpleIniW |
---|
3372 | # define CSimpleIniCase CSimpleIniCaseW |
---|
3373 | # define SI_NEWLINE SI_NEWLINE_W |
---|
3374 | #else // !_UNICODE |
---|
3375 | # define CSimpleIni CSimpleIniA |
---|
3376 | # define CSimpleIniCase CSimpleIniCaseA |
---|
3377 | # define SI_NEWLINE SI_NEWLINE_A |
---|
3378 | #endif // _UNICODE |
---|
3379 | |
---|
3380 | #ifdef _MSC_VER |
---|
3381 | # pragma warning (pop) |
---|
3382 | #endif |
---|
3383 | |
---|
3384 | #endif // INCLUDED_SimpleIni_h |
---|
3385 | |
---|