Main MRPT website > C++ reference for MRPT 1.9.9
SimpleIni.h
Go to the documentation of this file.
1 /* +------------------------------------------------------------------------+
2  | Mobile Robot Programming Toolkit (MRPT) |
3  | http://www.mrpt.org/ |
4  | |
5  | Copyright (c) 2005-2017, Individual contributors, see AUTHORS file |
6  | See: http://www.mrpt.org/Authors - All rights reserved. |
7  | Released under BSD License. See details in http://www.mrpt.org/License |
8  +------------------------------------------------------------------------+ */
9 
10 #ifndef INCLUDED_SimpleIni_h
11 #define INCLUDED_SimpleIni_h
12 
13 // Disable these warnings in MSVC:
14 // 4127 "conditional expression is constant" as the conversion classes trigger
15 // it with the statement if (sizeof(SI_CHAR) == sizeof(char)). This test will
16 // be optimized away in a release build.
17 // 4503 'insert' : decorated name length exceeded, name was truncated
18 // 4702 "unreachable code" as the MS STL header causes it in release mode.
19 // Again, the code causing the warning will be cleaned up by the compiler.
20 // 4786 "identifier truncated to 256 characters" as this is thrown hundreds
21 // of times VC6 as soon as STL is used.
22 #ifdef _MSC_VER
23 #pragma warning(disable : 4127 4503 4702 4786)
24 #endif
25 
26 #include <string>
27 #include <map>
28 #include <list>
29 #include <algorithm>
30 #include <stdio.h>
31 #include <cstring>
32 #include <mrpt/utils/utils_defs.h>
35 
36 #define SI_SUPPORT_IOSTREAMS
37 
38 #ifdef SI_SUPPORT_IOSTREAMS
39 #include <iostream>
40 #endif // SI_SUPPORT_IOSTREAMS
41 
42 #ifdef _DEBUG
43 #include <assert.h>
44 #define SI_ASSERT(x) assert(x)
45 #else
46 #define SI_ASSERT(x)
47 #endif
48 
49 namespace mrpt
50 {
51 namespace utils
52 {
53 namespace simpleini
54 {
56 {
57  /** No error */
58  SI_OK = 0,
59  /** An existing value was updated */
61  /** A new value was inserted */
63 
64  // note: test for any error with (retval < 0)
65  /** Generic failure */
66  SI_FAIL = -1,
67  /** Out of memory error */
68  SI_NOMEM = -2,
69  /** File error (see errno for detail error) */
70  SI_FILE = -3
71 };
72 
73 #ifdef _WIN32
74 #define SI_NEWLINE_A "\r\n"
75 #define SI_NEWLINE_W L"\r\n"
76 #else // !_WIN32
77 #define SI_NEWLINE_A "\n"
78 #define SI_NEWLINE_W L"\n"
79 #endif // _WIN32
80 
81 #if defined(_WIN32)
82 #define SI_HAS_WIDE_FILE
83 #define SI_WCHAR_T wchar_t
84 #elif defined(SI_CONVERT_ICU)
85 #define SI_HAS_WIDE_FILE
86 #define SI_WCHAR_T UChar
87 #endif
88 
89 // ---------------------------------------------------------------------------
90 // MAIN TEMPLATE CLASS
91 // ---------------------------------------------------------------------------
92 
93 /** Simple INI file reader.
94 
95  This can be instantiated with the choice of unicode or native characterset,
96  and case sensitive or insensitive comparisons of section and key names.
97  The supported combinations are pre-defined with the following typedefs:
98 
99  <table>
100  <tr><th>Interface <th>Case-sensitive <th>Typedef
101  <tr><td>char <td>No <td>CSimpleIniA
102  <tr><td>char <td>Yes <td>CSimpleIniCaseA
103  <tr><td>wchar_t <td>No <td>CSimpleIniW
104  <tr><td>wchar_t <td>Yes <td>CSimpleIniCaseW
105  </table>
106 
107  Note that using other types for the SI_CHAR is supported. For instance,
108  unsigned char, unsigned short, etc. Note that where the alternative type
109  is a different size to char/wchar_t you may need to supply new helper
110  classes for SI_STRLESS and SI_CONVERTER.
111  */
112 template <class SI_CHAR, class SI_STRLESS, class SI_CONVERTER>
114 {
115  public:
116  /** key entry */
117  struct Entry
118  {
119  const SI_CHAR* pItem;
120  const SI_CHAR* pComment;
121  int nOrder;
122 
123  Entry(const SI_CHAR* a_pszItem = nullptr, int a_nOrder = 0)
124  : pItem(a_pszItem), pComment(nullptr), nOrder(a_nOrder)
125  {
126  }
127  Entry(const Entry& rhs) { operator=(rhs); }
128  Entry& operator=(const Entry& rhs)
129  {
130  pItem = rhs.pItem;
131  pComment = rhs.pComment;
132  nOrder = rhs.nOrder;
133  return *this;
134  }
135 
136 #if (defined(_MSC_VER) && _MSC_VER <= 1200) || defined(__BORLANDC__)
137  /** STL of VC6 doesn't allow me to specify my own comparator for
138  * list::sort() */
139  bool operator<(const Entry& rhs) const
140  {
141  return LoadOrder()(*this, rhs);
142  }
143  bool operator>(const Entry& rhs) const
144  {
145  return LoadOrder()(rhs, *this);
146  }
147 #endif
148 
149  /** Strict less ordering by name of key only */
150  struct KeyOrder : std::binary_function<Entry, Entry, bool>
151  {
152  bool operator()(const Entry& lhs, const Entry& rhs) const
153  {
154  const static SI_STRLESS isLess = SI_STRLESS();
155  return isLess(lhs.pItem, rhs.pItem);
156  }
157  };
158 
159  /** Strict less ordering by order, and then name of key */
160  struct LoadOrder : std::binary_function<Entry, Entry, bool>
161  {
162  bool operator()(const Entry& lhs, const Entry& rhs) const
163  {
164  if (lhs.nOrder != rhs.nOrder)
165  {
166  return lhs.nOrder < rhs.nOrder;
167  }
168  return KeyOrder()(lhs.pItem, rhs.pItem);
169  }
170  };
171  };
172 
173  /** map keys to values */
174  typedef std::multimap<Entry, const SI_CHAR*, typename Entry::KeyOrder>
176 
177  /** map sections to key/value map */
178  typedef std::map<Entry, TKeyVal, typename Entry::KeyOrder> TSection;
179 
180  /** set of dependent string pointers. Note that these pointers are
181  dependent on memory owned by CSimpleIni.
182  */
183  typedef std::list<Entry> TNamesDepend;
184 
185  /** interface definition for the OutputWriter object to pass to Save()
186  in order to output the INI file data.
187  */
189  {
190  public:
192  virtual ~OutputWriter() {}
193  virtual void Write(const char* a_pBuf) = 0;
194 
195  private:
196  OutputWriter(const OutputWriter&); // disable
197  OutputWriter& operator=(const OutputWriter&); // disable
198  };
199 
200  /** OutputWriter class to write the INI data to a file */
201  class FileWriter : public OutputWriter
202  {
203  FILE* m_file;
204 
205  public:
206  FileWriter(FILE* a_file) : m_file(a_file) {}
207  void Write(const char* a_pBuf) override { fputs(a_pBuf, m_file); }
208  private:
209  FileWriter(const FileWriter&); // disable
210  FileWriter& operator=(const FileWriter&); // disable
211  };
212 
213  /** OutputWriter class to write the INI data to a string */
214  class StringWriter : public OutputWriter
215  {
217 
218  public:
219  StringWriter(std::string& a_string) : m_string(a_string) {}
220  void Write(const char* a_pBuf) override { m_string.append(a_pBuf); }
221  private:
222  StringWriter(const StringWriter&); // disable
223  StringWriter& operator=(const StringWriter&); // disable
224  };
225 
226 #ifdef SI_SUPPORT_IOSTREAMS
227  /** OutputWriter class to write the INI data to an ostream */
228  class StreamWriter : public OutputWriter
229  {
230  std::ostream& m_ostream;
231 
232  public:
233  StreamWriter(std::ostream& a_ostream) : m_ostream(a_ostream) {}
234  void Write(const char* a_pBuf) { m_ostream << a_pBuf; }
235  private:
236  StreamWriter(const StreamWriter&); // disable
237  StreamWriter& operator=(const StreamWriter&); // disable
238  };
239 #endif // SI_SUPPORT_IOSTREAMS
240 
241  /** Characterset conversion utility class to convert strings to the
242  same format as is used for the storage.
243  */
244  class Converter : private SI_CONVERTER
245  {
246  public:
247  Converter() : SI_CONVERTER() { m_scratch.resize(1024); }
248  Converter(const Converter& rhs) { operator=(rhs); }
250  {
251  m_scratch = rhs.m_scratch;
252  return *this;
253  }
254  bool ConvertToStore(const SI_CHAR* a_pszString)
255  {
256  size_t uLen = this->SizeToStore(a_pszString);
257  if (uLen == (size_t)(-1))
258  {
259  return false;
260  }
261  while (uLen > m_scratch.size())
262  {
263  m_scratch.resize(m_scratch.size() * 2);
264  }
265  return SI_CONVERTER::ConvertToStore(
266  a_pszString, const_cast<char*>(m_scratch.data()),
267  m_scratch.size());
268  }
269  const char* Data() { return m_scratch.data(); }
270  private:
272  };
273 
274  public:
275  /*-----------------------------------------------------------------------*/
276 
277  /** Default constructor.
278 
279  @param a_bMultiKey See the method SetMultiKey() for details.
280  @param a_bMultiLine See the method SetMultiLine() for details.
281  */
282  CSimpleIniTempl(bool a_bMultiKey = false, bool a_bMultiLine = false);
283 
284  /** Destructor */
286 
287  /** Deallocate all memory stored by this object */
288  void Reset();
289 
290  /*-----------------------------------------------------------------------*/
291  /** @{ @name Settings */
292 
293  /** Should multiple identical keys be permitted in the file. If set to false
294  then the last value encountered will be used as the value of the key.
295  If set to true, then all values will be available to be queried. For
296  example, with the following input:
297 
298  <pre>
299  [section]
300  test=value1
301  test=value2
302  </pre>
303 
304  Then with SetMultiKey(true), both of the values "value1" and "value2"
305  will be returned for the key test. If SetMultiKey(false) is used, then
306  the value for "test" will only be "value2". This value may be changed
307  at any time.
308 
309  \param a_bAllowMultiKey Allow multi-keys in the source?
310  */
311  void SetMultiKey(bool a_bAllowMultiKey = true)
312  {
313  m_bAllowMultiKey = a_bAllowMultiKey;
314  }
315 
316  /** Get the storage format of the INI data. */
317  bool IsMultiKey() const { return m_bAllowMultiKey; }
318  /** Should data values be permitted to span multiple lines in the file. If
319  set to false then the multi-line construct <<<TAG as a value will be
320  returned as is instead of loading the data. This value may be changed
321  at any time.
322 
323  \param a_bAllowMultiLine Allow multi-line values in the source?
324  */
325  void SetMultiLine(bool a_bAllowMultiLine = true)
326  {
327  m_bAllowMultiLine = a_bAllowMultiLine;
328  }
329 
330  /** Query the status of multi-line data */
331  bool IsMultiLine() const { return m_bAllowMultiLine; }
332  /*-----------------------------------------------------------------------*/
333  /** @}
334  @{ @name Loading INI Data */
335 
336  /** Load an INI file from disk into memory
337 
338  @param a_pszFile Path of the file to be loaded. This will be passed
339  to fopen() and so must be a valid path for the
340  current platform.
341 
342  @return SI_Error See error definitions
343  */
344  SI_Error LoadFile(const char* a_pszFile);
345 
346 #ifdef SI_HAS_WIDE_FILE
347  /** Load an INI file from disk into memory
348 
349  @param a_pwszFile Path of the file to be loaded in UTF-16.
350 
351  @return SI_Error See error definitions
352  */
353  SI_Error LoadFile(const SI_WCHAR_T* a_pwszFile);
354 #endif // SI_HAS_WIDE_FILE
355 
356  /** Load the file from a file pointer.
357 
358  @param a_fpFile Valid file pointer to read the file data from. The
359  file will be read until end of file.
360 
361  @return SI_Error See error definitions
362  */
363  SI_Error LoadFile(FILE* a_fpFile);
364 
365 #ifdef SI_SUPPORT_IOSTREAMS
366  /** Load INI file data from an istream.
367 
368  @param a_istream Stream to read from
369 
370  @return SI_Error See error definitions
371  */
372  SI_Error Load(std::istream& a_istream);
373 #endif // SI_SUPPORT_IOSTREAMS
374 
375  /** Load INI file data direct from a std::string
376 
377  @param a_strData Data to be loaded
378 
379  @return SI_Error See error definitions
380  */
381  SI_Error Load(const std::string& a_strData)
382  {
383  return Load(a_strData.c_str(), a_strData.size());
384  }
385 
386  /** Load INI file data direct from memory
387 
388  @param a_pData Data to be loaded
389  @param a_uDataLen Length of the data in bytes
390 
391  @return SI_Error See error definitions
392  */
393  SI_Error Load(const char* a_pData, size_t a_uDataLen);
394 
395  /*-----------------------------------------------------------------------*/
396  /** @}
397  @{ @name Saving INI Data */
398 
399  /** Save an INI file from memory to disk
400 
401  @param a_pszFile Path of the file to be saved. This will be passed
402  to fopen() and so must be a valid path for the
403  current platform.
404 
405  @return SI_Error See error definitions
406  */
407  SI_Error SaveFile(const char* a_pszFile) const;
408 
409 #ifdef SI_HAS_WIDE_FILE
410  /** Save an INI file from memory to disk
411 
412  @param a_pwszFile Path of the file to be saved in UTF-16.
413 
414  @return SI_Error See error definitions
415  */
416  SI_Error SaveFile(const SI_WCHAR_T* a_pwszFile) const;
417 #endif // _WIN32
418 
419  /** Save the INI data to a file. See Save() for details.
420 
421  @param a_pFile Handle to a file. File should be opened for
422  binary output.
423 
424  @return SI_Error See error definitions
425  */
426  SI_Error SaveFile(FILE* a_pFile) const;
427 
428  /** Save the INI data. The data will be written to the output device
429  in a format appropriate to the current data, selected by:
430 
431  <table>
432  <tr><th>SI_CHAR <th>FORMAT
433  <tr><td>char <td>same format as when loaded (MBCS or UTF-8)
434  <tr><td>wchar_t <td>UTF-8
435  <tr><td>other <td>UTF-8
436  </table>
437 
438  Note that comments, etc from the original data are not preserved. Only
439  valid data contents stored in the file are written out. The order of
440  the sections and values from the original file will be preserved.
441 
442  Any data prepended or appended to the output device must use the the
443  same format (MBCS or UTF-8). You may use the GetConverter() method to
444  convert text to the correct format regardless of the output format
445  being used by SimpleIni.
446 
447  To add a BOM to UTF-8 data, write it out manually at the very beginning
448  like is done in SaveFile when a_bUseBOM is true.
449 
450  @param a_oOutput Output writer to write the data to.
451 
452  @return SI_Error See error definitions
453  */
454  SI_Error Save(OutputWriter& a_oOutput) const;
455 
456 #ifdef SI_SUPPORT_IOSTREAMS
457  /** Save the INI data to an ostream. See Save() for details.
458 
459  @param a_ostream String to have the INI data appended to.
460 
461  @return SI_Error See error definitions
462  */
463  SI_Error Save(std::ostream& a_ostream) const
464  {
465  StreamWriter writer(a_ostream);
466  return Save(writer);
467  }
468 #endif // SI_SUPPORT_IOSTREAMS
469 
470  /** Append the INI data to a string. See Save() for details.
471 
472  @param a_sBuffer String to have the INI data appended to.
473 
474  @return SI_Error See error definitions
475  */
476  SI_Error Save(std::string& a_sBuffer) const
477  {
478  StringWriter writer(a_sBuffer);
479  return Save(writer);
480  }
481 
482  /*-----------------------------------------------------------------------*/
483  /** @}
484  @{ @name Accessing INI Data */
485 
486  /** Retrieve all section names. The list is returned as an STL vector of
487  names and can be iterated or searched as necessary. Note that the
488  collation order of the returned strings is NOT DEFINED.
489 
490  NOTE! This structure contains only pointers to strings. The actual
491  string data is stored in memory owned by CSimpleIni. Ensure that the
492  CSimpleIni object is not destroyed or Reset() while these pointers
493  are in use!
494 
495  @param a_names Vector that will receive all of the section
496  names. See note above!
497  */
498  void GetAllSections(TNamesDepend& a_names) const;
499 
500  /** Retrieve all unique key names in a section. The collation order of the
501  returned strings is NOT DEFINED. Only unique key names are returned.
502 
503  NOTE! This structure contains only pointers to strings. The actual
504  string data is stored in memory owned by CSimpleIni. Ensure that the
505  CSimpleIni object is not destroyed or Reset() while these strings
506  are in use!
507 
508  @param a_pSection Section to request data for
509  @param a_names List that will receive all of the key
510  names. See note above!
511 
512  @return true Section was found.
513  @return false Matching section was not found.
514  */
515  bool GetAllKeys(const SI_CHAR* a_pSection, TNamesDepend& a_names) const;
516 
517  /** Retrieve all values for a specific key. This method can be used when
518  multiple keys are both enabled and disabled.
519 
520  NOTE! The returned values are pointers to string data stored in memory
521  owned by CSimpleIni. Ensure that the CSimpleIni object is not destroyed
522  or Reset while you are using this pointer!
523 
524  @param a_pSection Section to search
525  @param a_pKey Key to search for
526  @param a_values List to return if the key is not found
527 
528  @return true Key was found.
529  @return false Matching section/key was not found.
530  */
531  bool GetAllValues(
532  const SI_CHAR* a_pSection, const SI_CHAR* a_pKey,
533  TNamesDepend& a_values) const;
534 
535  /** Query the number of keys in a specific section. Note that if multiple
536  keys are enabled, then this value may be different to the number of
537  keys returned by GetAllKeys.
538 
539  @param a_pSection Section to request data for
540 
541  @return -1 Section does not exist in the file
542  @return >=0 Number of keys in the section
543  */
544  int GetSectionSize(const SI_CHAR* a_pSection) const;
545 
546  /** Retrieve all key and value pairs for a section. The data is returned
547  as a pointer to an STL map and can be iterated or searched as
548  desired. Note that multiple entries for the same key may exist when
549  multiple keys have been enabled.
550 
551  NOTE! This structure contains only pointers to strings. The actual
552  string data is stored in memory owned by CSimpleIni. Ensure that the
553  CSimpleIni object is not destroyed or Reset() while these strings
554  are in use!
555 
556  @param a_pSection Name of the section to return
557  @return boolean Was a section matching the supplied
558  name found.
559  */
560  const TKeyVal* GetSection(const SI_CHAR* a_pSection) const;
561 
562  /** Retrieve the value for a specific key. If multiple keys are enabled
563  (see SetMultiKey) then only the first value associated with that key
564  will be returned, see GetAllValues for getting all values with multikey.
565 
566  NOTE! The returned value is a pointer to string data stored in memory
567  owned by CSimpleIni. Ensure that the CSimpleIni object is not destroyed
568  or Reset while you are using this pointer!
569 
570  @param a_pSection Section to search
571  @param a_pKey Key to search for
572  @param a_pDefault Value to return if the key is not found
573  @param a_pHasMultiple Optionally receive notification of if there are
574  multiple entries for this key.
575 
576  @return a_pDefault Key was not found in the section
577  @return other Value of the key
578  */
579  const SI_CHAR* GetValue(
580  const SI_CHAR* a_pSection, const SI_CHAR* a_pKey,
581  const SI_CHAR* a_pDefault = nullptr,
582  bool* a_pHasMultiple = nullptr) const;
583 
584  /** Add or update a section or value. This will always insert
585  when multiple keys are enabled.
586 
587  @param a_pSection Section to add or update
588  @param a_pKey Key to add or update. Set to nullptr to
589  create an empty section.
590  @param a_pValue Value to set. Set to nullptr to create an
591  empty section.
592  @param a_pComment Comment to be associated with the section or the
593  key. If a_pKey is nullptr then it will be associated
594  with the section, otherwise the key. Note that a
595  comment may be set ONLY when the section or key is
596  first created (i.e. when this function returns the
597  value SI_INSERTED). If you wish to create a section
598  with a comment then you need to create the section
599  separately to the key. The comment string must be
600  in full comment form already (have a comment
601  character starting every line).
602 
603  @return SI_Error See error definitions
604  @return SI_UPDATED Value was updated
605  @return SI_INSERTED Value was inserted
606  */
608  const SI_CHAR* a_pSection, const SI_CHAR* a_pKey,
609  const SI_CHAR* a_pValue, const SI_CHAR* a_pComment = nullptr)
610  {
611  return AddEntry(a_pSection, a_pKey, a_pValue, a_pComment, true);
612  }
613 
614  /** Delete an entire section, or a key from a section. Note that the
615  data returned by GetSection is invalid and must not be used after
616  anything has been deleted from that section using this method.
617  Note when multiple keys is enabled, this will delete all keys with
618  that name; there is no way to selectively delete individual key/values
619  in this situation.
620 
621  @param a_pSection Section to delete key from, or if
622  a_pKey is nullptr, the section to remove.
623  @param a_pKey Key to remove from the section. Set to
624  nullptr to remove the entire section.
625  @param a_bRemoveEmpty If the section is empty after this key has
626  been deleted, should the empty section be
627  removed?
628 
629  @return true Key or section was deleted.
630  @return false Key or section was not found.
631  */
632  bool Delete(
633  const SI_CHAR* a_pSection, const SI_CHAR* a_pKey,
634  bool a_bRemoveEmpty = false);
635 
636  /*-----------------------------------------------------------------------*/
637  /** @}
638  @{ @name Converter */
639 
640  /** Return a conversion object to convert text to the same encoding
641  as is used by the Save(), SaveFile() and SaveString() functions.
642  Use this to prepare the strings that you wish to append or prepend
643  to the output INI data.
644  */
645  Converter GetConverter() const { return Converter(); }
646  /*-----------------------------------------------------------------------*/
647  /** @} */
648 
649  private:
650  /** Parse the data looking for a file comment and store it if found.
651  */
652  SI_Error FindFileComment(SI_CHAR*& a_pData, bool a_bCopyStrings);
653 
654  /** Parse the data looking for the next valid entry. The memory pointed to
655  by a_pData is modified by inserting nullptr characters. The pointer is
656  updated to the current location in the block of text.
657  */
658  bool FindEntry(
659  SI_CHAR*& a_pData, const SI_CHAR*& a_pSection, const SI_CHAR*& a_pKey,
660  const SI_CHAR*& a_pVal, const SI_CHAR*& a_pComment) const;
661 
662  /** Add the section/key/value to our data.
663 
664  @param a_pSection Section name. Sections will be created if they
665  don't already exist.
666  @param a_pKey Key name. May be nullptr to create an empty section.
667  Existing entries will be updated. New entries will
668  be created.
669  @param a_pValue Value for the key.
670  @param a_pComment Comment to be associated with the section or the
671  key. If a_pKey is nullptr then it will be associated
672  with the section, otherwise the key. This must be
673  a string in full comment form already (have a
674  comment character starting every line).
675  @param a_bCopyStrings Should copies of the strings be made or not.
676  If false then the pointers will be used as is.
677  */
679  const SI_CHAR* a_pSection, const SI_CHAR* a_pKey,
680  const SI_CHAR* a_pValue, const SI_CHAR* a_pComment,
681  bool a_bCopyStrings);
682 
683  /** Is the supplied character a whitespace character? */
684  inline bool IsSpace(SI_CHAR ch) const
685  {
686  return (ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n');
687  }
689  /** Does the supplied character start a comment line? */
690  inline bool IsComment(SI_CHAR ch) const { return (ch == ';' || ch == '#'); }
691  /** Skip over a newline character (or characters) for either DOS or UNIX */
692  inline void SkipNewLine(SI_CHAR*& a_pData) const
693  {
694  a_pData += (*a_pData == '\r' && *(a_pData + 1) == '\n') ? 2 : 1;
695  }
696 
697  /** Make a copy of the supplied string, replacing the original pointer */
698  SI_Error CopyString(const SI_CHAR*& a_pString);
699 
700  /** Delete a string from the copied strings buffer if necessary */
701  void DeleteString(const SI_CHAR* a_pString);
702 
703  /** Internal use of our string comparison function */
704  bool IsLess(const SI_CHAR* a_pLeft, const SI_CHAR* a_pRight) const
705  {
706  const static SI_STRLESS isLess = SI_STRLESS();
707  return isLess(a_pLeft, a_pRight);
708  }
709 
710  bool IsMultiLineTag(const SI_CHAR* a_pData) const;
711  bool IsMultiLineData(const SI_CHAR* a_pData) const;
712  bool LoadMultiLineText(
713  SI_CHAR*& a_pData, const SI_CHAR*& a_pVal, const SI_CHAR* a_pTagName,
714  bool a_bAllowBlankLinesInComment = false) const;
715  bool IsNewLineChar(SI_CHAR a_c) const;
716 
717  bool OutputMultiLineText(
718  OutputWriter& a_oOutput, Converter& a_oConverter,
719  const SI_CHAR* a_pText) const;
720 
721  private:
722  /** Copy of the INI file data in our character format. This will be
723  modified when parsed to have nullptr characters added after all
724  interesting string entries. All of the string pointers to sections,
725  keys and values point into this block of memory.
726  */
727  SI_CHAR* m_pData;
728 
729  /** Length of the data that we have stored. Used when deleting strings
730  to determine if the string is stored here or in the allocated string
731  buffer.
732  */
733  size_t m_uDataLen;
734 
735  /** File comment for this data, if one exists. */
736  const SI_CHAR* m_pFileComment;
737 
738  /** Parsed INI data. Section -> (Key -> Value). */
740 
741  /** This vector stores allocated memory for copies of strings that have
742  been supplied after the file load. It will be empty unless SetValue()
743  has been called.
744  */
746 
747  /** Are multiple values permitted for the same key? */
748  bool m_bAllowMultiKey;
749 
750  /** Are data values permitted to span multiple lines? */
751  bool m_bAllowMultiLine;
753  /** Next order value, used to ensure sections and keys are output in the
754  same order that they are loaded/added.
755  */
756  int m_nOrder;
757 };
758 
759 // ---------------------------------------------------------------------------
760 // IMPLEMENTATION
761 // ---------------------------------------------------------------------------
762 
763 template <class SI_CHAR, class SI_STRLESS, class SI_CONVERTER>
765  bool a_bAllowMultiKey, bool a_bAllowMultiLine)
766  : m_pData(0),
767  m_uDataLen(0),
768  m_pFileComment(NULL),
769  m_bAllowMultiKey(a_bAllowMultiKey),
770  m_bAllowMultiLine(a_bAllowMultiLine),
771  m_nOrder(0)
772 {
773 }
774 
775 template <class SI_CHAR, class SI_STRLESS, class SI_CONVERTER>
777 {
778  Reset();
779 }
780 
781 template <class SI_CHAR, class SI_STRLESS, class SI_CONVERTER>
783 {
784  // remove all data
785  delete[] m_pData;
786  m_pData = nullptr;
787  m_uDataLen = 0;
788  m_pFileComment = nullptr;
789  if (!m_data.empty())
790  {
791  m_data.erase(m_data.begin(), m_data.end());
792  }
793 
794  // remove all strings
795  if (!m_strings.empty())
796  {
797  typename TNamesDepend::iterator i = m_strings.begin();
798  for (; i != m_strings.end(); ++i)
799  {
800  delete[] const_cast<SI_CHAR*>(i->pItem);
801  }
802  m_strings.erase(m_strings.begin(), m_strings.end());
803  }
804 }
805 
806 template <class SI_CHAR, class SI_STRLESS, class SI_CONVERTER>
808  const char* a_pszFile)
809 {
810  FILE* fp = nullptr;
811 #if __STDC_WANT_SECURE_LIB__
812  fopen_s(&fp, a_pszFile, "rb");
813 #else
814  fp = fopen(a_pszFile, "rb");
815 #endif
816  if (!fp)
817  {
818  return SI_FILE;
819  }
820  SI_Error rc = LoadFile(fp);
821  fclose(fp);
822  return rc;
823 }
824 
825 #ifdef SI_HAS_WIDE_FILE
826 template <class SI_CHAR, class SI_STRLESS, class SI_CONVERTER>
828  const SI_WCHAR_T* a_pwszFile)
829 {
830 #ifdef _WIN32
831  FILE* fp = nullptr;
832 #if __STDC_WANT_SECURE_LIB__
833  _wfopen_s(&fp, a_pwszFile, L"rb");
834 #else
835  fp = _wfopen(a_pwszFile, L"rb");
836 #endif
837  if (!fp) return SI_FILE;
838  SI_Error rc = LoadFile(fp);
839  fclose(fp);
840  return rc;
841 #else // SI_CONVERT_ICU
842  char szFile[256];
843  u_austrncpy(szFile, a_pwszFile, sizeof(szFile));
844  return LoadFile(szFile);
845 #endif
846 }
847 #endif // SI_HAS_WIDE_FILE
848 
849 template <class SI_CHAR, class SI_STRLESS, class SI_CONVERTER>
851  FILE* a_fpFile)
852 {
853  // load the raw file data
854  int retval = fseek(a_fpFile, 0, SEEK_END);
855  if (retval != 0)
856  {
857  return SI_FILE;
858  }
859  long lSize = ftell(a_fpFile);
860  if (lSize < 0)
861  {
862  return SI_FILE;
863  }
864  char* pData = new char[lSize];
865  if (!pData)
866  {
867  return SI_NOMEM;
868  }
869  fseek(a_fpFile, 0, SEEK_SET);
870  size_t uRead = fread(pData, sizeof(char), lSize, a_fpFile);
871  if (uRead != (size_t)lSize)
872  {
873  delete[] pData;
874  return SI_FILE;
875  }
876 
877  // convert the raw data to unicode
878  SI_Error rc = Load(pData, uRead);
879  delete[] pData;
880  return rc;
881 }
882 
883 template <class SI_CHAR, class SI_STRLESS, class SI_CONVERTER>
885  const char* a_pData, size_t a_uDataLen)
886 {
887  SI_CONVERTER converter;
888 
889  // determine the length of the converted data
890  size_t uLen = converter.SizeFromStore(a_pData, a_uDataLen);
891  if (uLen == (size_t)(-1))
892  {
893  return SI_FAIL;
894  }
895 
896  // allocate memory for the data, ensure that there is a NULL
897  // terminator wherever the converted data ends
898  SI_CHAR* pData = new SI_CHAR[uLen + 1];
899  if (!pData)
900  {
901  return SI_NOMEM;
902  }
903  memset(pData, 0, sizeof(SI_CHAR) * (uLen + 1));
904 
905  // convert the data
906  if (!converter.ConvertFromStore(a_pData, a_uDataLen, pData, uLen))
907  {
908  delete[] pData;
909  return SI_FAIL;
910  }
911 
912  // parse it
913  const static SI_CHAR empty = 0;
914  SI_CHAR* pWork = pData;
915  const SI_CHAR* pSection = &empty;
916  const SI_CHAR* pItem = nullptr;
917  const SI_CHAR* pVal = nullptr;
918  const SI_CHAR* pComment = nullptr;
919 
920  // We copy the strings if we are loading data into this class when we
921  // already have stored some.
922  bool bCopyStrings = (m_pData != nullptr);
923 
924  // find a file comment if it exists, this is a comment that starts at the
925  // beginning of the file and continues until the first blank line.
926  SI_Error rc = FindFileComment(pWork, bCopyStrings);
927  if (rc < 0) return rc;
928 
929  // add every entry in the file to the data table
930  while (FindEntry(pWork, pSection, pItem, pVal, pComment))
931  {
932  rc = AddEntry(pSection, pItem, pVal, pComment, bCopyStrings);
933  if (rc < 0) return rc;
934  }
935 
936  // store these strings if we didn't copy them
937  if (bCopyStrings)
938  {
939  delete[] pData;
940  }
941  else
942  {
943  m_pData = pData;
944  m_uDataLen = uLen + 1;
945  }
946 
947  return SI_OK;
948 }
949 
950 #ifdef SI_SUPPORT_IOSTREAMS
951 template <class SI_CHAR, class SI_STRLESS, class SI_CONVERTER>
953  std::istream& a_istream)
954 {
955  std::string strData;
956  char szBuf[512];
957  do
958  {
959  a_istream.get(szBuf, sizeof(szBuf), '\0');
960  strData.append(szBuf);
961  } while (a_istream.good());
962  return Load(strData);
963 }
964 #endif // SI_SUPPORT_IOSTREAMS
965 
966 template <class SI_CHAR, class SI_STRLESS, class SI_CONVERTER>
968  SI_CHAR*& a_pData, bool a_bCopyStrings)
969 {
970  // there can only be a single file comment
971  if (m_pFileComment)
972  {
973  return SI_OK;
974  }
975 
976  // Load the file comment as multi-line text, this will modify all of
977  // the newline characters to be single \n chars
978  if (!LoadMultiLineText(a_pData, m_pFileComment, nullptr, false))
979  {
980  return SI_OK;
981  }
982 
983  // copy the string if necessary
984  if (a_bCopyStrings)
985  {
986  SI_Error rc = CopyString(m_pFileComment);
987  if (rc < 0) return rc;
988  }
989 
990  return SI_OK;
991 }
992 
993 template <class SI_CHAR, class SI_STRLESS, class SI_CONVERTER>
995  SI_CHAR*& a_pData, const SI_CHAR*& a_pSection, const SI_CHAR*& a_pKey,
996  const SI_CHAR*& a_pVal, const SI_CHAR*& a_pComment) const
997 {
998  a_pComment = nullptr;
999 
1000  SI_CHAR* pTrail = nullptr;
1001  while (*a_pData)
1002  {
1003  // skip spaces and empty lines
1004  while (*a_pData && IsSpace(*a_pData))
1005  {
1006  ++a_pData;
1007  }
1008  if (!*a_pData)
1009  {
1010  break;
1011  }
1012 
1013  // skip processing of comment lines but keep a pointer to
1014  // the start of the comment.
1015  if (IsComment(*a_pData))
1016  {
1017  LoadMultiLineText(a_pData, a_pComment, nullptr, true);
1018  continue;
1019  }
1020 
1021  // process section names
1022  if (*a_pData == '[')
1023  {
1024  // skip leading spaces
1025  ++a_pData;
1026  while (*a_pData && IsSpace(*a_pData))
1027  {
1028  ++a_pData;
1029  }
1030 
1031  // find the end of the section name (it may contain spaces)
1032  // and convert it to lowercase as necessary
1033  a_pSection = a_pData;
1034  while (*a_pData && *a_pData != ']' && !IsNewLineChar(*a_pData))
1035  {
1036  ++a_pData;
1037  }
1038 
1039  // if it's an invalid line, just skip it
1040  if (*a_pData != ']')
1041  {
1042  continue;
1043  }
1044 
1045  // remove trailing spaces from the section
1046  pTrail = a_pData - 1;
1047  while (pTrail >= a_pSection && IsSpace(*pTrail))
1048  {
1049  --pTrail;
1050  }
1051  ++pTrail;
1052  *pTrail = 0;
1053 
1054  // skip to the end of the line
1055  ++a_pData; // safe as checked that it == ']' above
1056  while (*a_pData && !IsNewLineChar(*a_pData))
1057  {
1058  ++a_pData;
1059  }
1060 
1061  a_pKey = nullptr;
1062  a_pVal = nullptr;
1063  return true;
1064  }
1065 
1066  // find the end of the key name (it may contain spaces)
1067  // and convert it to lowercase as necessary
1068  a_pKey = a_pData;
1069  while (*a_pData && *a_pData != '=' && !IsNewLineChar(*a_pData))
1070  {
1071  ++a_pData;
1072  }
1073 
1074  // if it's an invalid line, just skip it
1075  if (*a_pData != '=')
1076  {
1077  continue;
1078  }
1079 
1080  // empty keys are invalid
1081  if (a_pKey == a_pData)
1082  {
1083  while (*a_pData && !IsNewLineChar(*a_pData))
1084  {
1085  ++a_pData;
1086  }
1087  continue;
1088  }
1089 
1090  // remove trailing spaces from the key
1091  pTrail = a_pData - 1;
1092  while (pTrail >= a_pKey && IsSpace(*pTrail))
1093  {
1094  --pTrail;
1095  }
1096  ++pTrail;
1097  *pTrail = 0;
1098 
1099  // skip leading whitespace on the value
1100  ++a_pData; // safe as checked that it == '=' above
1101  while (*a_pData && !IsNewLineChar(*a_pData) && IsSpace(*a_pData))
1102  {
1103  ++a_pData;
1104  }
1105 
1106  // find the end of the value which is the end of this line
1107  a_pVal = a_pData;
1108  while (*a_pData && !IsNewLineChar(*a_pData))
1109  {
1110  ++a_pData;
1111  }
1112 
1113  // remove trailing spaces from the value
1114  pTrail = a_pData - 1;
1115  if (*a_pData)
1116  { // prepare for the next round
1117  SkipNewLine(a_pData);
1118  }
1119  while (pTrail >= a_pVal && IsSpace(*pTrail))
1120  {
1121  --pTrail;
1122  }
1123  ++pTrail;
1124  *pTrail = 0;
1125 
1126  // check for multi-line entries
1127  if (m_bAllowMultiLine && IsMultiLineTag(a_pVal))
1128  {
1129  // skip the "<<<" to get the tag that will end the multiline
1130  const SI_CHAR* pTagName = a_pVal + 3;
1131  return LoadMultiLineText(a_pData, a_pVal, pTagName);
1132  }
1133 
1134  // return the standard entry
1135  return true;
1136  }
1137 
1138  return false;
1139 }
1140 
1141 template <class SI_CHAR, class SI_STRLESS, class SI_CONVERTER>
1143  const SI_CHAR* a_pVal) const
1144 {
1145  // check for the "<<<" prefix for a multi-line entry
1146  if (*a_pVal++ != '<') return false;
1147  if (*a_pVal++ != '<') return false;
1148  if (*a_pVal++ != '<') return false;
1149  return true;
1150 }
1151 
1152 template <class SI_CHAR, class SI_STRLESS, class SI_CONVERTER>
1154  const SI_CHAR* a_pData) const
1155 {
1156  // data is multi-line if it has any of the following features:
1157  // * whitespace prefix
1158  // * embedded newlines
1159  // * whitespace suffix
1160 
1161  // empty string
1162  if (!*a_pData)
1163  {
1164  return false;
1165  }
1166 
1167  // check for prefix
1168  if (IsSpace(*a_pData))
1169  {
1170  return true;
1171  }
1172 
1173  // embedded newlines
1174  while (*a_pData)
1175  {
1176  if (IsNewLineChar(*a_pData))
1177  {
1178  return true;
1179  }
1180  ++a_pData;
1181  }
1182 
1183  // check for suffix
1184  if (IsSpace(*--a_pData))
1185  {
1186  return true;
1187  }
1188 
1189  return false;
1190 }
1191 
1192 template <class SI_CHAR, class SI_STRLESS, class SI_CONVERTER>
1194  SI_CHAR a_c) const
1195 {
1196  return (a_c == '\n' || a_c == '\r');
1197 }
1198 
1199 template <class SI_CHAR, class SI_STRLESS, class SI_CONVERTER>
1201  SI_CHAR*& a_pData, const SI_CHAR*& a_pVal, const SI_CHAR* a_pTagName,
1202  bool a_bAllowBlankLinesInComment) const
1203 {
1204  // we modify this data to strip all newlines down to a single '\n'
1205  // character. This means that on Windows we need to strip out some
1206  // characters which will make the data shorter.
1207  // i.e. LINE1-LINE1\r\nLINE2-LINE2\0 will become
1208  // LINE1-LINE1\nLINE2-LINE2\0
1209  // The pDataLine entry is the pointer to the location in memory that
1210  // the current line needs to start to run following the existing one.
1211  // This may be the same as pCurrLine in which case no move is needed.
1212  SI_CHAR* pDataLine = a_pData;
1213  SI_CHAR* pCurrLine;
1214 
1215  // value starts at the current line
1216  a_pVal = a_pData;
1217 
1218  // find the end tag. This tag must start in column 1 and be
1219  // followed by a newline. No whitespace removal is done while
1220  // searching for this tag.
1221  SI_CHAR cEndOfLineChar = *a_pData;
1222  for (;;)
1223  {
1224  // if we are loading comments then we need a comment character as
1225  // the first character on every line
1226  if (!a_pTagName && !IsComment(*a_pData))
1227  {
1228  // if we aren't allowing blank lines then we're done
1229  if (!a_bAllowBlankLinesInComment)
1230  {
1231  break;
1232  }
1233 
1234  // if we are allowing blank lines then we only include them
1235  // in this comment if another comment follows, so read ahead
1236  // to find out.
1237  SI_CHAR* pCurr = a_pData;
1238  int nNewLines = 0;
1239  while (IsSpace(*pCurr))
1240  {
1241  if (IsNewLineChar(*pCurr))
1242  {
1243  ++nNewLines;
1244  SkipNewLine(pCurr);
1245  }
1246  else
1247  {
1248  ++pCurr;
1249  }
1250  }
1251 
1252  // we have a comment, add the blank lines to the output
1253  // and continue processing from here
1254  if (IsComment(*pCurr))
1255  {
1256  for (; nNewLines > 0; --nNewLines) *pDataLine++ = '\n';
1257  a_pData = pCurr;
1258  continue;
1259  }
1260 
1261  // the comment ends here
1262  break;
1263  }
1264 
1265  // find the end of this line
1266  pCurrLine = a_pData;
1267  while (*a_pData && !IsNewLineChar(*a_pData)) ++a_pData;
1268 
1269  // move this line down to the location that it should be if necessary
1270  if (pDataLine < pCurrLine)
1271  {
1272  memmove(pDataLine, pCurrLine, a_pData - pCurrLine);
1273  pDataLine[a_pData - pCurrLine] = '\0';
1274  }
1275 
1276  // end the line with a NULL
1277  cEndOfLineChar = *a_pData;
1278  *a_pData = 0;
1279 
1280  // if are looking for a tag then do the check now. This is done before
1281  // checking for end of the data, so that if we have the tag at the end
1282  // of the data then the tag is removed correctly.
1283  if (a_pTagName &&
1284  (!IsLess(pDataLine, a_pTagName) && !IsLess(a_pTagName, pDataLine)))
1285  {
1286  break;
1287  }
1288 
1289  // if we are at the end of the data then we just automatically end
1290  // this entry and return the current data.
1291  if (!cEndOfLineChar)
1292  {
1293  return true;
1294  }
1295 
1296  // otherwise we need to process this newline to ensure that it consists
1297  // of just a single \n character.
1298  pDataLine += (a_pData - pCurrLine);
1299  *a_pData = cEndOfLineChar;
1300  SkipNewLine(a_pData);
1301  *pDataLine++ = '\n';
1302  }
1303 
1304  // if we didn't find a comment at all then return false
1305  if (a_pVal == a_pData)
1306  {
1307  a_pVal = nullptr;
1308  return false;
1309  }
1310 
1311  // the data (which ends at the end of the last line) needs to be
1312  // null-terminated BEFORE before the newline character(s). If the
1313  // user wants a new line in the multi-line data then they need to
1314  // add an empty line before the tag.
1315  *--pDataLine = '\0';
1316 
1317  // if looking for a tag and if we aren't at the end of the data,
1318  // then move a_pData to the start of the next line.
1319  if (a_pTagName && cEndOfLineChar)
1320  {
1321  SI_ASSERT(IsNewLineChar(cEndOfLineChar));
1322  *a_pData = cEndOfLineChar;
1323  SkipNewLine(a_pData);
1324  }
1325 
1326  return true;
1327 }
1328 
1329 template <class SI_CHAR, class SI_STRLESS, class SI_CONVERTER>
1331  const SI_CHAR*& a_pString)
1332 {
1333  size_t uLen = 0;
1334  if (sizeof(SI_CHAR) == sizeof(char))
1335  {
1336  uLen = strlen((const char*)a_pString);
1337  }
1338  else if (sizeof(SI_CHAR) == sizeof(wchar_t))
1339  {
1340  uLen = wcslen((const wchar_t*)a_pString);
1341  }
1342  else
1343  {
1344  for (; a_pString[uLen]; ++uLen) /*loop*/
1345  ;
1346  }
1347  ++uLen; // nullptr character
1348  SI_CHAR* pCopy = new SI_CHAR[uLen];
1349  if (!pCopy)
1350  {
1351  return SI_NOMEM;
1352  }
1353  memcpy(pCopy, a_pString, sizeof(SI_CHAR) * uLen);
1354  m_strings.push_back(pCopy);
1355  a_pString = pCopy;
1356  return SI_OK;
1357 }
1358 
1359 template <class SI_CHAR, class SI_STRLESS, class SI_CONVERTER>
1361  const SI_CHAR* a_pSection, const SI_CHAR* a_pKey, const SI_CHAR* a_pValue,
1362  const SI_CHAR* a_pComment, bool a_bCopyStrings)
1363 {
1364  SI_Error rc;
1365  bool bInserted = false;
1366 
1367  SI_ASSERT(!a_pComment || IsComment(*a_pComment));
1368 
1369  // if we are copying strings then make a copy of the comment now
1370  // because we will need it when we add the entry.
1371  if (a_bCopyStrings && a_pComment)
1372  {
1373  rc = CopyString(a_pComment);
1374  if (rc < 0) return rc;
1375  }
1376 
1377  // check for existence of the section first if we need string copies
1378  typename TSection::iterator iSection = m_data.end();
1379  if (a_bCopyStrings)
1380  {
1381  iSection = m_data.find(a_pSection);
1382  if (iSection == m_data.end())
1383  {
1384  // if the section doesn't exist then we need a copy as the
1385  // string needs to last beyond the end of this function
1386  // because we will be inserting the section next
1387  rc = CopyString(a_pSection);
1388  if (rc < 0) return rc;
1389  }
1390  }
1391 
1392  // create the section entry
1393  if (iSection == m_data.end())
1394  {
1395  Entry oKey(a_pSection, ++m_nOrder);
1396  if (a_pComment && (!a_pKey || !a_pValue))
1397  {
1398  oKey.pComment = a_pComment;
1399  }
1400  typename TSection::value_type oEntry(oKey, TKeyVal());
1401  typedef typename TSection::iterator SectionIterator;
1402  std::pair<SectionIterator, bool> i = m_data.insert(oEntry);
1403  iSection = i.first;
1404  bInserted = true;
1405  }
1406  if (!a_pKey || !a_pValue)
1407  {
1408  // section only entries are specified with pItem and pVal as NULL
1409  return bInserted ? SI_INSERTED : SI_UPDATED;
1410  }
1411 
1412  // check for existence of the key
1413  TKeyVal& keyval = iSection->second;
1414  typename TKeyVal::iterator iKey = keyval.find(a_pKey);
1415 
1416  // make string copies if necessary
1417  if (a_bCopyStrings)
1418  {
1419  if (m_bAllowMultiKey || iKey == keyval.end())
1420  {
1421  // if the key doesn't exist then we need a copy as the
1422  // string needs to last beyond the end of this function
1423  // because we will be inserting the key next
1424  rc = CopyString(a_pKey);
1425  if (rc < 0) return rc;
1426  }
1427 
1428  // we always need a copy of the value
1429  rc = CopyString(a_pValue);
1430  if (rc < 0) return rc;
1431  }
1432 
1433  // create the key entry
1434  if (iKey == keyval.end() || m_bAllowMultiKey)
1435  {
1436  Entry oKey(a_pKey, ++m_nOrder);
1437  if (a_pComment)
1438  {
1439  oKey.pComment = a_pComment;
1440  }
1441  typename TKeyVal::value_type oEntry(
1442  oKey, static_cast<const char*>(nullptr));
1443  iKey = keyval.insert(oEntry);
1444  bInserted = true;
1445  }
1446  iKey->second = a_pValue;
1447  return bInserted ? SI_INSERTED : SI_UPDATED;
1448 }
1449 
1450 template <class SI_CHAR, class SI_STRLESS, class SI_CONVERTER>
1452  const SI_CHAR* a_pSection, const SI_CHAR* a_pKey, const SI_CHAR* a_pDefault,
1453  bool* a_pHasMultiple) const
1454 {
1455  if (a_pHasMultiple)
1456  {
1457  *a_pHasMultiple = false;
1458  }
1459  if (!a_pSection || !a_pKey)
1460  {
1461  return a_pDefault;
1462  }
1463  typename TSection::const_iterator iSection = m_data.find(a_pSection);
1464  if (iSection == m_data.end())
1465  {
1466  return a_pDefault;
1467  }
1468  typename TKeyVal::const_iterator iKeyVal = iSection->second.find(a_pKey);
1469  if (iKeyVal == iSection->second.end())
1470  {
1471  return a_pDefault;
1472  }
1473 
1474  // check for multiple entries with the same key
1475  if (m_bAllowMultiKey && a_pHasMultiple)
1476  {
1477  typename TKeyVal::const_iterator iTemp = iKeyVal;
1478  if (++iTemp != iSection->second.end())
1479  {
1480  if (!IsLess(a_pKey, iTemp->first.pItem))
1481  {
1482  *a_pHasMultiple = true;
1483  }
1484  }
1485  }
1486 
1487  return iKeyVal->second;
1488 }
1489 
1490 template <class SI_CHAR, class SI_STRLESS, class SI_CONVERTER>
1492  const SI_CHAR* a_pSection, const SI_CHAR* a_pKey,
1493  TNamesDepend& a_values) const
1494 {
1495  if (!a_pSection || !a_pKey)
1496  {
1497  return false;
1498  }
1499  typename TSection::const_iterator iSection = m_data.find(a_pSection);
1500  if (iSection == m_data.end())
1501  {
1502  return false;
1503  }
1504  typename TKeyVal::const_iterator iKeyVal = iSection->second.find(a_pKey);
1505  if (iKeyVal == iSection->second.end())
1506  {
1507  return false;
1508  }
1509 
1510  // insert all values for this key
1511  a_values.push_back(iKeyVal->second);
1512  if (m_bAllowMultiKey)
1513  {
1514  ++iKeyVal;
1515  while (iKeyVal != iSection->second.end() &&
1516  !IsLess(a_pKey, iKeyVal->first.pItem))
1517  {
1518  a_values.push_back(Entry(iKeyVal->second, iKeyVal->first.nOrder));
1519  ++iKeyVal;
1520  }
1521  }
1522 
1523  return true;
1524 }
1525 
1526 template <class SI_CHAR, class SI_STRLESS, class SI_CONVERTER>
1528  const SI_CHAR* a_pSection) const
1529 {
1530  if (!a_pSection)
1531  {
1532  return -1;
1533  }
1534 
1535  typename TSection::const_iterator iSection = m_data.find(a_pSection);
1536  if (iSection == m_data.end())
1537  {
1538  return -1;
1539  }
1540  const TKeyVal& section = iSection->second;
1541 
1542  // if multi-key isn't permitted then the section size is
1543  // the number of keys that we have.
1544  if (!m_bAllowMultiKey || section.empty())
1545  {
1546  return (int)section.size();
1547  }
1548 
1549  // otherwise we need to count them
1550  int nCount = 0;
1551  const SI_CHAR* pLastKey = nullptr;
1552  typename TKeyVal::const_iterator iKeyVal = section.begin();
1553  for (int n = 0; iKeyVal != section.end(); ++iKeyVal, ++n)
1554  {
1555  if (!pLastKey || IsLess(pLastKey, iKeyVal->first.pItem))
1556  {
1557  ++nCount;
1558  pLastKey = iKeyVal->first.pItem;
1559  }
1560  }
1561  return nCount;
1562 }
1563 
1564 template <class SI_CHAR, class SI_STRLESS, class SI_CONVERTER>
1567  const SI_CHAR* a_pSection) const
1568 {
1569  if (a_pSection)
1570  {
1571  typename TSection::const_iterator i = m_data.find(a_pSection);
1572  if (i != m_data.end())
1573  {
1574  return &(i->second);
1575  }
1576  }
1577  return 0;
1578 }
1579 
1580 template <class SI_CHAR, class SI_STRLESS, class SI_CONVERTER>
1582  TNamesDepend& a_names) const
1583 {
1584  typename TSection::const_iterator i = m_data.begin();
1585  for (int n = 0; i != m_data.end(); ++i, ++n)
1586  {
1587  a_names.push_back(i->first);
1588  }
1589 }
1590 
1591 template <class SI_CHAR, class SI_STRLESS, class SI_CONVERTER>
1593  const SI_CHAR* a_pSection, TNamesDepend& a_names) const
1594 {
1595  if (!a_pSection)
1596  {
1597  return false;
1598  }
1599 
1600  typename TSection::const_iterator iSection = m_data.find(a_pSection);
1601  if (iSection == m_data.end())
1602  {
1603  return false;
1604  }
1605 
1606  const TKeyVal& section = iSection->second;
1607  const SI_CHAR* pLastKey = nullptr;
1608  typename TKeyVal::const_iterator iKeyVal = section.begin();
1609  for (int n = 0; iKeyVal != section.end(); ++iKeyVal, ++n)
1610  {
1611  if (!pLastKey || IsLess(pLastKey, iKeyVal->first.pItem))
1612  {
1613  a_names.push_back(iKeyVal->first);
1614  pLastKey = iKeyVal->first.pItem;
1615  }
1616  }
1617 
1618  return true;
1619 }
1620 
1621 template <class SI_CHAR, class SI_STRLESS, class SI_CONVERTER>
1623  const char* a_pszFile) const
1624 {
1625  FILE* fp = nullptr;
1626 #if __STDC_WANT_SECURE_LIB__
1627  fopen_s(&fp, a_pszFile, "wb");
1628 #else
1629  fp = fopen(a_pszFile, "wb");
1630 #endif
1631  if (!fp) return SI_FILE;
1632  SI_Error rc = SaveFile(fp);
1633  fclose(fp);
1634  return rc;
1635 }
1636 
1637 #ifdef SI_HAS_WIDE_FILE
1638 template <class SI_CHAR, class SI_STRLESS, class SI_CONVERTER>
1640  const SI_WCHAR_T* a_pwszFile) const
1641 {
1642 #ifdef _WIN32
1643  FILE* fp = _wfopen(a_pwszFile, L"wb");
1644  if (!fp) return SI_FILE;
1645  SI_Error rc = SaveFile(fp);
1646  fclose(fp);
1647  return rc;
1648 #else // SI_CONVERT_ICU
1649  char szFile[256];
1650  u_austrncpy(szFile, a_pwszFile, sizeof(szFile));
1651  return SaveFile(szFile);
1652 #endif
1653 }
1654 #endif // SI_HAS_WIDE_FILE
1655 
1656 template <class SI_CHAR, class SI_STRLESS, class SI_CONVERTER>
1658  FILE* a_pFile) const
1659 {
1660  FileWriter writer(a_pFile);
1661  return Save(writer);
1662 }
1663 
1664 template <class SI_CHAR, class SI_STRLESS, class SI_CONVERTER>
1666  OutputWriter& a_oOutput) const
1667 {
1668  Converter convert;
1669 
1670  // get all of the sections sorted in load order
1671  TNamesDepend oSections;
1672  GetAllSections(oSections);
1673 #if (defined(_MSC_VER) && _MSC_VER <= 1200) || defined(__BORLANDC__)
1674  oSections.sort();
1675 #else
1676  oSections.sort(typename Entry::LoadOrder());
1677 #endif
1678 
1679  // write the file comment if we have one
1680  bool bNeedNewLine = false;
1681  if (m_pFileComment)
1682  {
1683  if (!OutputMultiLineText(a_oOutput, convert, m_pFileComment))
1684  {
1685  return SI_FAIL;
1686  }
1687  bNeedNewLine = true;
1688  }
1689 
1690  // iterate through our sections and output the data
1691  typename TNamesDepend::const_iterator iSection = oSections.begin();
1692  for (; iSection != oSections.end(); ++iSection)
1693  {
1694  // write out the comment if there is one
1695  if (iSection->pComment)
1696  {
1697  if (!convert.ConvertToStore(iSection->pComment))
1698  {
1699  return SI_FAIL;
1700  }
1701  if (bNeedNewLine)
1702  {
1703  a_oOutput.Write(SI_NEWLINE_A);
1704  a_oOutput.Write(SI_NEWLINE_A);
1705  }
1706  a_oOutput.Write(convert.Data());
1707  a_oOutput.Write(SI_NEWLINE_A);
1708  bNeedNewLine = false;
1709  }
1710 
1711  if (bNeedNewLine)
1712  {
1713  a_oOutput.Write(SI_NEWLINE_A);
1714  a_oOutput.Write(SI_NEWLINE_A);
1715  bNeedNewLine = false;
1716  }
1717 
1718  // write the section (unless there is no section name)
1719  if (*iSection->pItem)
1720  {
1721  if (!convert.ConvertToStore(iSection->pItem))
1722  {
1723  return SI_FAIL;
1724  }
1725  a_oOutput.Write("[");
1726  a_oOutput.Write(convert.Data());
1727  a_oOutput.Write("]");
1728  a_oOutput.Write(SI_NEWLINE_A);
1729  }
1730 
1731  // get all of the keys sorted in load order
1732  TNamesDepend oKeys;
1733  GetAllKeys(iSection->pItem, oKeys);
1734 #if (defined(_MSC_VER) && _MSC_VER <= 1200) || defined(__BORLANDC__)
1735  oKeys.sort();
1736 #else
1737  oKeys.sort(typename Entry::LoadOrder());
1738 #endif
1739 
1740  // write all keys and values
1741  typename TNamesDepend::const_iterator iKey = oKeys.begin();
1742  for (; iKey != oKeys.end(); ++iKey)
1743  {
1744  // get all values for this key
1745  TNamesDepend oValues;
1746  GetAllValues(iSection->pItem, iKey->pItem, oValues);
1747 
1748  // write out the comment if there is one
1749  if (iKey->pComment)
1750  {
1751  a_oOutput.Write(SI_NEWLINE_A);
1752  if (!OutputMultiLineText(a_oOutput, convert, iKey->pComment))
1753  {
1754  return SI_FAIL;
1755  }
1756  }
1757 
1758  typename TNamesDepend::const_iterator iValue = oValues.begin();
1759  for (; iValue != oValues.end(); ++iValue)
1760  {
1761  // write the key
1762  if (!convert.ConvertToStore(iKey->pItem))
1763  {
1764  return SI_FAIL;
1765  }
1766  a_oOutput.Write(convert.Data());
1767 
1768  // write the value
1769  if (!convert.ConvertToStore(iValue->pItem))
1770  {
1771  return SI_FAIL;
1772  }
1773  a_oOutput.Write("=");
1774  if (m_bAllowMultiLine && IsMultiLineData(iValue->pItem))
1775  {
1776  // multi-line data needs to be processed specially to ensure
1777  // that we use the correct newline format for the current
1778  // system
1779  a_oOutput.Write("<<<SI-END-OF-MULTILINE-TEXT" SI_NEWLINE_A);
1780  if (!OutputMultiLineText(a_oOutput, convert, iValue->pItem))
1781  {
1782  return SI_FAIL;
1783  }
1784  a_oOutput.Write("SI-END-OF-MULTILINE-TEXT");
1785  }
1786  else
1787  {
1788  a_oOutput.Write(convert.Data());
1789  }
1790  a_oOutput.Write(SI_NEWLINE_A);
1791  }
1792  }
1793 
1794  bNeedNewLine = true;
1795  }
1796 
1797  return SI_OK;
1798 }
1799 
1800 template <class SI_CHAR, class SI_STRLESS, class SI_CONVERTER>
1802  OutputWriter& a_oOutput, Converter& a_oConverter,
1803  const SI_CHAR* a_pText) const
1804 {
1805  const SI_CHAR* pEndOfLine;
1806  SI_CHAR cEndOfLineChar = *a_pText;
1807  while (cEndOfLineChar)
1808  {
1809  // find the end of this line
1810  pEndOfLine = a_pText;
1811  for (; *pEndOfLine && *pEndOfLine != '\n'; ++pEndOfLine) /*loop*/
1812  ;
1813  cEndOfLineChar = *pEndOfLine;
1814 
1815  // temporarily null terminate, convert and output the line
1816  *const_cast<SI_CHAR*>(pEndOfLine) = 0;
1817  if (!a_oConverter.ConvertToStore(a_pText))
1818  {
1819  return false;
1820  }
1821  *const_cast<SI_CHAR*>(pEndOfLine) = cEndOfLineChar;
1822  a_pText += (pEndOfLine - a_pText) + 1;
1823  a_oOutput.Write(a_oConverter.Data());
1824  a_oOutput.Write(SI_NEWLINE_A);
1825  }
1826  return true;
1827 }
1828 
1829 template <class SI_CHAR, class SI_STRLESS, class SI_CONVERTER>
1831  const SI_CHAR* a_pSection, const SI_CHAR* a_pKey, bool a_bRemoveEmpty)
1832 {
1833  if (!a_pSection)
1834  {
1835  return false;
1836  }
1837 
1838  typename TSection::iterator iSection = m_data.find(a_pSection);
1839  if (iSection == m_data.end())
1840  {
1841  return false;
1842  }
1843 
1844  // remove a single key if we have a keyname
1845  if (a_pKey)
1846  {
1847  typename TKeyVal::iterator iKeyVal = iSection->second.find(a_pKey);
1848  if (iKeyVal == iSection->second.end())
1849  {
1850  return false;
1851  }
1852 
1853  // remove any copied strings and then the key
1854  typename TKeyVal::iterator iDelete;
1855  do
1856  {
1857  iDelete = iKeyVal++;
1858 
1859  DeleteString(iDelete->first.pItem);
1860  DeleteString(iDelete->second);
1861  iSection->second.erase(iDelete);
1862  } while (iKeyVal != iSection->second.end() &&
1863  !IsLess(a_pKey, iKeyVal->first.pItem));
1864 
1865  // done now if the section is not empty or we are not pruning away
1866  // the empty sections. Otherwise let it fall through into the section
1867  // deletion code
1868  if (!a_bRemoveEmpty || !iSection->second.empty())
1869  {
1870  return true;
1871  }
1872  }
1873  else
1874  {
1875  // delete all copied strings from this section. The actual
1876  // entries will be removed when the section is removed.
1877  typename TKeyVal::iterator iKeyVal = iSection->second.begin();
1878  for (; iKeyVal != iSection->second.end(); ++iKeyVal)
1879  {
1880  DeleteString(iKeyVal->first.pItem);
1881  DeleteString(iKeyVal->second);
1882  }
1883  }
1884 
1885  // delete the section itself
1886  DeleteString(iSection->first.pItem);
1887  m_data.erase(iSection);
1888 
1889  return true;
1890 }
1891 
1892 template <class SI_CHAR, class SI_STRLESS, class SI_CONVERTER>
1894  const SI_CHAR* a_pString)
1895 {
1896  // strings may exist either inside the data block, or they will be
1897  // individually allocated and stored in m_strings. We only physically
1898  // delete those stored in m_strings.
1899  if (a_pString < m_pData || a_pString >= m_pData + m_uDataLen)
1900  {
1901  typename TNamesDepend::iterator i = m_strings.begin();
1902  for (; i != m_strings.end(); ++i)
1903  {
1904  if (a_pString == i->pItem)
1905  {
1906  delete[] const_cast<SI_CHAR*>(i->pItem);
1907  m_strings.erase(i);
1908  break;
1909  }
1910  }
1911  }
1912 }
1913 
1914 // ---------------------------------------------------------------------------
1915 // CONVERSION FUNCTIONS
1916 // ---------------------------------------------------------------------------
1917 
1918 /**
1919  * Generic case-sensitive less than comparison. This class returns numerically
1920  * ordered ASCII case-sensitive text for all possible sizes and types of
1921  * SI_CHAR.
1922  */
1923 template <class SI_CHAR>
1925 {
1926  bool operator()(const SI_CHAR* pLeft, const SI_CHAR* pRight) const
1927  {
1928  long cmp;
1929  for (; *pLeft && *pRight; ++pLeft, ++pRight)
1930  {
1931  cmp = (long)*pLeft - (long)*pRight;
1932  if (cmp != 0)
1933  {
1934  return cmp < 0;
1935  }
1936  }
1937  return *pRight != 0;
1938  }
1939 };
1940 
1941 /**
1942  * Generic ASCII case-insensitive less than comparison. This class returns
1943  * numerically ordered ASCII case-insensitive text for all possible sizes
1944  * and types of SI_CHAR. It is not safe for MBCS text comparison where
1945  * ASCII A-Z characters are used in the encoding of multi-byte characters.
1946  */
1947 template <class SI_CHAR>
1949 {
1950  inline SI_CHAR locase(SI_CHAR ch) const
1951  {
1952  return (ch < 'A' || ch > 'Z') ? ch : (ch - 'A' + 'a');
1953  }
1954  bool operator()(const SI_CHAR* pLeft, const SI_CHAR* pRight) const
1955  {
1956  long cmp;
1957  for (; *pLeft && *pRight; ++pLeft, ++pRight)
1958  {
1959  cmp = (long)locase(*pLeft) - (long)locase(*pRight);
1960  if (cmp != 0)
1961  {
1962  return cmp < 0;
1963  }
1964  }
1965  return *pRight != 0;
1966  }
1967 };
1968 
1969 /**
1970  * Null conversion class for MBCS/UTF-8 to char (or equivalent).
1971  */
1972 template <class SI_CHAR>
1974 {
1975  public:
1977  /* copy and assignment */
1978  SI_ConvertA(const SI_ConvertA& rhs) { operator=(rhs); }
1979  SI_ConvertA& operator=(const SI_ConvertA& rhs) { return *this; }
1980  /** Calculate the number of SI_CHAR required for converting the input
1981  * from the storage format. The storage format is always UTF-8 or MBCS.
1982  *
1983  * @param a_pInputData Data in storage format to be converted to SI_CHAR.
1984  * @param a_uInputDataLen Length of storage format data in bytes. This
1985  * must be the actual length of the data, including
1986  * nullptr byte if nullptr terminated string is
1987  * required.
1988  * @return Number of SI_CHAR required by the string when
1989  * converted. If there are embedded nullptr bytes in
1990  * the
1991  * input data, only the string up and not including
1992  * the nullptr byte will be converted.
1993  * @return -1 cast to size_t on a conversion error.
1994  */
1995  virtual size_t SizeFromStore(
1996  const char* a_pInputData, size_t a_uInputDataLen)
1997  {
1998  (void)a_pInputData;
1999  SI_ASSERT(a_uInputDataLen != (size_t)-1);
2000 
2001  // ASCII/MBCS/UTF-8 needs no conversion
2002  return a_uInputDataLen;
2003  }
2004 
2005  /** Convert the input string from the storage format to SI_CHAR.
2006  * The storage format is always UTF-8 or MBCS.
2007  *
2008  * @param a_pInputData Data in storage format to be converted to SI_CHAR.
2009  * @param a_uInputDataLen Length of storage format data in bytes. This
2010  * must be the actual length of the data, including
2011  * nullptr byte if nullptr terminated string is
2012  * required.
2013  * @param a_pOutputData Pointer to the output buffer to received the
2014  * converted data.
2015  * @param a_uOutputDataSize Size of the output buffer in SI_CHAR.
2016  * @return true if all of the input data was successfully
2017  * converted.
2018  */
2019  virtual bool ConvertFromStore(
2020  const char* a_pInputData, size_t a_uInputDataLen,
2021  SI_CHAR* a_pOutputData, size_t a_uOutputDataSize)
2022  {
2023  // ASCII/MBCS/UTF-8 needs no conversion
2024  if (a_uInputDataLen > a_uOutputDataSize)
2025  {
2026  return false;
2027  }
2028  memcpy(a_pOutputData, a_pInputData, a_uInputDataLen);
2029  return true;
2030  }
2031 
2032  /** Calculate the number of char required by the storage format of this
2033  * data. The storage format is always UTF-8 or MBCS.
2034  *
2035  * @param a_pInputData nullptr terminated string to calculate the number of
2036  * bytes required to be converted to storage format.
2037  * @return Number of bytes required by the string when
2038  * converted to storage format. This size always
2039  * includes space for the terminating nullptr
2040  * character.
2041  * @return -1 cast to size_t on a conversion error.
2042  */
2043  size_t SizeToStore(const SI_CHAR* a_pInputData)
2044  {
2045  // ASCII/MBCS/UTF-8 needs no conversion
2046  return strlen((const char*)a_pInputData) + 1;
2047  }
2048 
2049  /** Convert the input string to the storage format of this data.
2050  * The storage format is always UTF-8 or MBCS.
2051  *
2052  * @param a_pInputData nullptr terminated source string to convert. All of
2053  * the data will be converted including the
2054  * terminating nullptr character.
2055  * @param a_pOutputData Pointer to the buffer to receive the converted
2056  * string.
2057  * @param a_uOutputDataSize Size of the output buffer in char.
2058  * @return true if all of the input data, including the
2059  * terminating nullptr character was successfully
2060  * converted.
2061  */
2063  const SI_CHAR* a_pInputData, char* a_pOutputData,
2064  size_t a_uOutputDataSize)
2065  {
2066  // calc input string length (SI_CHAR type and size independent)
2067  size_t uInputLen = strlen((const char*)a_pInputData) + 1;
2068  if (uInputLen > a_uOutputDataSize)
2069  {
2070  return false;
2071  }
2072 
2073  // ascii/UTF-8 needs no conversion
2074  memcpy(a_pOutputData, a_pInputData, uInputLen);
2075  return true;
2076  }
2077 };
2078 
2079 /** MRPT custom INI file parser to allow minimal file preprocessing:
2080 * - multiline entries via an end-of-line backslash ('\')
2081 */
2082 struct MRPT_IniFileParser : public SI_ConvertA<char>
2083 {
2085  /* copy and assignment */
2087  {
2089  }
2091  {
2093  return *this;
2094  }
2095 
2096  virtual size_t SizeFromStore(
2097  const char* a_pInputData, size_t a_uInputDataLen) override
2098  {
2099  SI_ASSERT(a_uInputDataLen != (size_t)-1);
2100  return do_parse(a_pInputData, a_uInputDataLen, nullptr);
2101  }
2102 
2103  virtual bool ConvertFromStore(
2104  const char* a_pInputData, size_t a_uInputDataLen, char* a_pOutputData,
2105  size_t a_uOutputDataSize) override
2106  {
2107  this->do_parse(a_pInputData, a_uInputDataLen, a_pOutputData);
2108  return true;
2109  }
2110 
2111  private:
2113  {
2114  std::map<std::string, std::string> defined_vars;
2115  std::map<std::string, double> defined_vars_values;
2116  unsigned int line_count = 1;
2117  };
2118 
2119  // Return a string or a number (as string) if expr = "$eval{...}"
2121  {
2122  expr = mrpt::system::trim(expr);
2123  while (expr.size() > 6)
2124  {
2125  auto p = expr.find("$eval{");
2126  if (p == std::string::npos) break; // done!
2127  auto pend = expr.find("}", p);
2128  if (pend == std::string::npos)
2129  throw std::runtime_error(
2130  mrpt::format(
2131  "Line %u: Expected closing `}` near: `%s`",
2132  pc.line_count, expr.c_str()));
2133 
2134  const auto substr = expr.substr(p + 6, pend - p - 6);
2135  mrpt::math::CRuntimeCompiledExpression cexpr;
2136  cexpr.compile(
2137  substr, pc.defined_vars_values,
2138  mrpt::format("Line %u: ", pc.line_count));
2139 
2140  std::string new_expr = expr.substr(0, p);
2141  new_expr += mrpt::format("%e", cexpr.eval());
2142  new_expr += expr.substr(pend + 1);
2143 
2144  new_expr.swap(expr);
2145  }
2146  return expr;
2147  }
2148 
2150  ParseContext& pc, const std::string& var_name,
2151  const std::string& var_value)
2152  {
2153  if (!var_name.empty())
2154  {
2155  pc.defined_vars[var_name] = var_value;
2156  if (!var_value.empty())
2157  {
2158  pc.defined_vars_values[var_name] =
2159  ::atof(parse_process_var_eval(pc, var_value).c_str());
2160  }
2161  }
2162  }
2163 
2164  /** Shared code for the two virtual methods. If out_str==NULL, just count
2165  * output bytes */
2166  size_t do_parse(const char* in_str, const size_t in_len, char* out_str)
2167  {
2168  ParseContext pc;
2169  size_t out_len = 0, i = 0;
2170  while (i < in_len)
2171  {
2172  const char c = in_str[i];
2173  if (c == '\n')
2174  {
2175  pc.line_count++;
2176  }
2177 
2178  if (c == '\\' && i < in_len - 1 &&
2179  (in_str[i + 1] == '\r' || in_str[i + 1] == '\n'))
2180  {
2181  // Skip the backslash + one newline: CR "\r", LF "\n", CR+LF
2182  // "\r\n"
2183  if (i < in_len - 2 && in_str[i + 1] == '\r' &&
2184  in_str[i + 2] == '\n')
2185  {
2186  // out_len += 0;
2187  i += 3;
2188  }
2189  else if (in_str[i + 1] == '\r' || in_str[i + 1] == '\n')
2190  {
2191  // out_len += 0;
2192  i += 2;
2193  }
2194  else
2195  {
2196  throw std::runtime_error(
2197  "MRPT_IniFileParser: parse error, shouldn't reach "
2198  "here!");
2199  }
2200  }
2201  else
2202  {
2203  // Handle "@define varname value"
2204  if (in_len > i + 7 && !::strncmp(in_str + i, "@define", 7))
2205  {
2206  // Extract rest of this line:
2207  i += 7;
2208  std::string var_name, var_value;
2209  bool in_var_name = false, done_var_name = false;
2210  while (i < in_len && in_str[i] != '\r' && in_str[i] != '\n')
2211  {
2212  const char c = in_str[i];
2213  i++;
2214  if (c != ' ' && c != '\t')
2215  {
2216  // not whitespace
2217  if (!in_var_name && !done_var_name)
2218  {
2219  in_var_name = true;
2220  }
2221  }
2222  else
2223  {
2224  // whitespace
2225  if (in_var_name)
2226  {
2227  in_var_name = false;
2228  done_var_name = true;
2229  }
2230  }
2231  if (in_var_name)
2232  {
2233  var_name += c;
2234  }
2235  if (done_var_name)
2236  {
2237  var_value += c;
2238  }
2239  }
2240 
2241  parse_process_var_define(pc, var_name, var_value);
2242  continue;
2243  }
2244 
2245  // Handle "${varname}"
2246  if (in_len > i + 4 && in_str[i] == '$' && in_str[i + 1] == '{')
2247  {
2248  // extract varname:
2249  i += 2;
2250  std::string varname;
2251  bool end_ok = false;
2252  while (i < in_len && in_str[i] != '\n' && in_str[i] != '\r')
2253  {
2254  const char ch = in_str[i];
2255  i++;
2256  if (ch == '}')
2257  {
2258  end_ok = true;
2259  break;
2260  }
2261  varname += ch;
2262  }
2263  if (!end_ok)
2264  {
2265  throw std::runtime_error(
2266  mrpt::format(
2267  "Line %u: Expected closing `}` near: `%s`",
2268  pc.line_count, varname.c_str()));
2269  }
2270 
2271  const auto it = pc.defined_vars.find(varname);
2272  if (it == pc.defined_vars.end())
2273  throw std::runtime_error(
2274  mrpt::format(
2275  "Line %u: Unknown variable `${%s}`",
2276  pc.line_count, varname.c_str()));
2277 
2278  const auto str_out = parse_process_var_eval(pc, it->second);
2279 
2280  for (const char ch : str_out)
2281  {
2282  if (out_str) out_str[out_len] = ch;
2283  out_len++;
2284  }
2285  continue;
2286  }
2287 
2288  // Handle "$eval{expression}"
2289  if (in_len > i + 7 && !strncmp(in_str + i, "$eval{", 6))
2290  {
2291  // extract expression:
2292  std::string expr;
2293  bool end_ok = false;
2294  while (i < in_len && in_str[i] != '\n' && in_str[i] != '\r')
2295  {
2296  const char ch = in_str[i];
2297  i++;
2298  expr += ch;
2299  if (ch == '}')
2300  {
2301  end_ok = true;
2302  break;
2303  }
2304  }
2305  if (!end_ok)
2306  {
2307  throw std::runtime_error(
2308  mrpt::format(
2309  "Line %u: Expected closing `}` near: `%s`",
2310  pc.line_count, expr.c_str()));
2311  }
2312 
2313  const std::string res = parse_process_var_eval(pc, expr);
2314 
2315  for (const char ch : res)
2316  {
2317  if (out_str) out_str[out_len] = ch;
2318  out_len++;
2319  }
2320  continue;
2321  }
2322 
2323  // Normal case:
2324  if (out_str)
2325  {
2326  out_str[out_len] = c;
2327  }
2328  out_len++;
2329  i++;
2330  }
2331  }
2332  return out_len;
2333  }
2334 };
2335 
2336 // ---------------------------------------------------------------------------
2337 // TYPE DEFINITIONS
2338 // ---------------------------------------------------------------------------
2339 
2340 typedef CSimpleIniTempl<char, SI_GenericNoCase<char>, SI_ConvertA<char>>
2344 
2347 
2348 #ifdef _UNICODE
2349 #define CSimpleIni CSimpleIniW
2350 #define CSimpleIniCase CSimpleIniCaseW
2351 #define SI_NEWLINE SI_NEWLINE_W
2352 #else // !_UNICODE
2353 #define CSimpleIni CSimpleIniA
2354 #define CSimpleIniCase CSimpleIniCaseA
2355 #define SI_NEWLINE SI_NEWLINE_A
2356 #endif // _UNICODE
2357 
2358 } // end namespace
2359 } // end namespace
2360 } // end namespace
2361 
2362 #endif // INCLUDED_SimpleIni_h
Converter & operator=(const Converter &rhs)
Definition: SimpleIni.h:249
OutputWriter & operator=(const OutputWriter &)
SI_CHAR locase(SI_CHAR ch) const
Definition: SimpleIni.h:1950
FileWriter & operator=(const FileWriter &)
std::map< std::string, std::string > defined_vars
Definition: SimpleIni.h:2114
int m_nOrder
Next order value, used to ensure sections and keys are output in the same order that they are loaded/...
Definition: SimpleIni.h:752
Null conversion class for MBCS/UTF-8 to char (or equivalent).
Definition: SimpleIni.h:1973
std::string format(const char *fmt,...) MRPT_printf_format_check(1
A std::string version of C sprintf.
int GetSectionSize(const SI_CHAR *a_pSection) const
Query the number of keys in a specific section.
Definition: SimpleIni.h:1527
bool IsSpace(SI_CHAR ch) const
Is the supplied character a whitespace character?
Definition: SimpleIni.h:680
bool operator()(const Entry &lhs, const Entry &rhs) const
Definition: SimpleIni.h:162
int void fclose(FILE *f)
An OS-independent version of fclose.
Definition: os.cpp:272
OutputWriter class to write the INI data to an ostream.
Definition: SimpleIni.h:228
const SI_CHAR * pItem
Definition: SimpleIni.h:119
Entry & operator=(const Entry &rhs)
Definition: SimpleIni.h:128
GLenum GLsizei n
Definition: glext.h:5074
Scalar * iterator
Definition: eigen_plugins.h:26
bool m_bAllowMultiKey
Are multiple values permitted for the same key?
Definition: SimpleIni.h:744
#define SEEK_END
Definition: zconf.h:303
bool IsMultiKey() const
Get the storage format of the INI data.
Definition: SimpleIni.h:317
bool operator()(const Entry &lhs, const Entry &rhs) const
Definition: SimpleIni.h:152
bool IsMultiLineTag(const SI_CHAR *a_pData) const
Definition: SimpleIni.h:1142
std::map< std::string, double > defined_vars_values
Definition: SimpleIni.h:2115
void SetMultiLine(bool a_bAllowMultiLine=true)
Should data values be permitted to span multiple lines in the file.
Definition: SimpleIni.h:325
Entry(const SI_CHAR *a_pszItem=nullptr, int a_nOrder=0)
Definition: SimpleIni.h:123
interface definition for the OutputWriter object to pass to Save() in order to output the INI file da...
Definition: SimpleIni.h:188
void SkipNewLine(SI_CHAR *&a_pData) const
Skip over a newline character (or characters) for either DOS or UNIX.
Definition: SimpleIni.h:688
void DeleteString(const SI_CHAR *a_pString)
Delete a string from the copied strings buffer if necessary.
Definition: SimpleIni.h:1893
Simple INI file reader.
Definition: SimpleIni.h:113
const Scalar * const_iterator
Definition: eigen_plugins.h:27
SI_Error LoadFile(const char *a_pszFile)
Load an INI file from disk into memory.
Definition: SimpleIni.h:807
SI_Error SetValue(const SI_CHAR *a_pSection, const SI_CHAR *a_pKey, const SI_CHAR *a_pValue, const SI_CHAR *a_pComment=nullptr)
Add or update a section or value.
Definition: SimpleIni.h:604
bool m_bAllowMultiLine
Are data values permitted to span multiple lines?
Definition: SimpleIni.h:747
size_t SizeToStore(const SI_CHAR *a_pInputData)
Calculate the number of char required by the storage format of this data.
Definition: SimpleIni.h:2043
#define SI_ASSERT(x)
Definition: SimpleIni.h:46
void SetMultiKey(bool a_bAllowMultiKey=true)
Should multiple identical keys be permitted in the file.
Definition: SimpleIni.h:311
MRPT custom INI file parser to allow minimal file preprocessing:
Definition: SimpleIni.h:2082
size_t do_parse(const char *in_str, const size_t in_len, char *out_str)
Shared code for the two virtual methods.
Definition: SimpleIni.h:2166
#define SI_WCHAR_T
Definition: SimpleIni.h:83
bool ConvertToStore(const SI_CHAR *a_pszString)
Definition: SimpleIni.h:254
bool GetAllKeys(const SI_CHAR *a_pSection, TNamesDepend &a_names) const
Retrieve all unique key names in a section.
Definition: SimpleIni.h:1592
CSimpleIniTempl< char, SI_GenericNoCase< char >, MRPT_IniFileParser > MRPT_CSimpleIni
Definition: SimpleIni.h:2346
SI_Error AddEntry(const SI_CHAR *a_pSection, const SI_CHAR *a_pKey, const SI_CHAR *a_pValue, const SI_CHAR *a_pComment, bool a_bCopyStrings)
Add the section/key/value to our data.
Definition: SimpleIni.h:1360
const GLubyte * c
Definition: glext.h:6313
TNamesDepend m_strings
This vector stores allocated memory for copies of strings that have been supplied after the file load...
Definition: SimpleIni.h:741
Converter GetConverter() const
Return a conversion object to convert text to the same encoding as is used by the Save()...
Definition: SimpleIni.h:641
virtual bool ConvertFromStore(const char *a_pInputData, size_t a_uInputDataLen, SI_CHAR *a_pOutputData, size_t a_uOutputDataSize)
Convert the input string from the storage format to SI_CHAR.
Definition: SimpleIni.h:2019
int nOrder
Definition: SimpleIni.h:121
CSimpleIniTempl< char, SI_GenericNoCase< char >, SI_ConvertA< char > > CSimpleIniA
Definition: SimpleIni.h:2341
std::map< Entry, TKeyVal, typename Entry::KeyOrder > TSection
map sections to key/value map
Definition: SimpleIni.h:178
virtual bool ConvertFromStore(const char *a_pInputData, size_t a_uInputDataLen, char *a_pOutputData, size_t a_uOutputDataSize) override
Convert the input string from the storage format to SI_CHAR.
Definition: SimpleIni.h:2103
Strict less ordering by name of key only.
Definition: SimpleIni.h:150
void GetAllSections(TNamesDepend &a_names) const
Retrieve all section names.
Definition: SimpleIni.h:1581
SI_Error Load(std::istream &a_istream)
Load INI file data from an istream.
Definition: SimpleIni.h:952
bool operator()(const SI_CHAR *pLeft, const SI_CHAR *pRight) const
Definition: SimpleIni.h:1954
bool empty() const
Definition: ts_hash_map.h:197
File error (see errno for detail error)
Definition: SimpleIni.h:70
GLsizei const GLchar ** string
Definition: glext.h:4101
An existing value was updated.
Definition: SimpleIni.h:60
SI_ConvertA & operator=(const SI_ConvertA &rhs)
Definition: SimpleIni.h:1979
virtual size_t SizeFromStore(const char *a_pInputData, size_t a_uInputDataLen) override
Calculate the number of SI_CHAR required for converting the input from the storage format...
Definition: SimpleIni.h:2096
Characterset conversion utility class to convert strings to the same format as is used for the storag...
Definition: SimpleIni.h:244
CSimpleIniTempl(bool a_bMultiKey=false, bool a_bMultiLine=false)
Default constructor.
Definition: SimpleIni.h:764
SI_ConvertA(const SI_ConvertA &rhs)
Definition: SimpleIni.h:1978
bool IsMultiLineData(const SI_CHAR *a_pData) const
Definition: SimpleIni.h:1153
SI_CHAR * m_pData
Copy of the INI file data in our character format.
Definition: SimpleIni.h:723
bool operator()(const SI_CHAR *pLeft, const SI_CHAR *pRight) const
Definition: SimpleIni.h:1926
key entry
Definition: SimpleIni.h:117
TSection m_data
Parsed INI data.
Definition: SimpleIni.h:735
SI_Error SaveFile(const char *a_pszFile) const
Save an INI file from memory to disk.
Definition: SimpleIni.h:1622
bool Delete(const SI_CHAR *a_pSection, const SI_CHAR *a_pKey, bool a_bRemoveEmpty=false)
Delete an entire section, or a key from a section.
Definition: SimpleIni.h:1830
Generic ASCII case-insensitive less than comparison.
Definition: SimpleIni.h:1948
bool IsNewLineChar(SI_CHAR a_c) const
Definition: SimpleIni.h:1193
This is the global namespace for all Mobile Robot Programming Toolkit (MRPT) libraries.
bool ConvertToStore(const SI_CHAR *a_pInputData, char *a_pOutputData, size_t a_uOutputDataSize)
Convert the input string to the storage format of this data.
Definition: SimpleIni.h:2062
MRPT_IniFileParser(const MRPT_IniFileParser &rhs)
Definition: SimpleIni.h:2086
const SI_CHAR * pComment
Definition: SimpleIni.h:120
bool FindEntry(SI_CHAR *&a_pData, const SI_CHAR *&a_pSection, const SI_CHAR *&a_pKey, const SI_CHAR *&a_pVal, const SI_CHAR *&a_pComment) const
Parse the data looking for the next valid entry.
Definition: SimpleIni.h:994
size_t m_uDataLen
Length of the data that we have stored.
Definition: SimpleIni.h:729
SI_Error FindFileComment(SI_CHAR *&a_pData, bool a_bCopyStrings)
Parse the data looking for a file comment and store it if found.
Definition: SimpleIni.h:967
void Write(const char *a_pBuf) override
Definition: SimpleIni.h:207
virtual void Write(const char *a_pBuf)=0
#define SEEK_SET
Definition: zconf.h:301
bool OutputMultiLineText(OutputWriter &a_oOutput, Converter &a_oConverter, const SI_CHAR *a_pText) const
Definition: SimpleIni.h:1801
A new value was inserted.
Definition: SimpleIni.h:62
const TKeyVal * GetSection(const SI_CHAR *a_pSection) const
Retrieve all key and value pairs for a section.
Definition: SimpleIni.h:1566
OutputWriter class to write the INI data to a file.
Definition: SimpleIni.h:201
void Write(const char *a_pBuf) override
Definition: SimpleIni.h:220
Generic case-sensitive less than comparison.
Definition: SimpleIni.h:1924
CSimpleIniTempl< char, SI_GenericCase< char >, SI_ConvertA< char > > CSimpleIniCaseA
Definition: SimpleIni.h:2343
const SI_CHAR * GetValue(const SI_CHAR *a_pSection, const SI_CHAR *a_pKey, const SI_CHAR *a_pDefault=nullptr, bool *a_pHasMultiple=nullptr) const
Retrieve the value for a specific key.
Definition: SimpleIni.h:1451
std::list< Entry > TNamesDepend
set of dependent string pointers.
Definition: SimpleIni.h:183
bool GetAllValues(const SI_CHAR *a_pSection, const SI_CHAR *a_pKey, TNamesDepend &a_values) const
Retrieve all values for a specific key.
Definition: SimpleIni.h:1491
void parse_process_var_define(ParseContext &pc, const std::string &var_name, const std::string &var_value)
Definition: SimpleIni.h:2149
const SI_CHAR * m_pFileComment
File comment for this data, if one exists.
Definition: SimpleIni.h:732
StreamWriter & operator=(const StreamWriter &)
virtual size_t SizeFromStore(const char *a_pInputData, size_t a_uInputDataLen)
Calculate the number of SI_CHAR required for converting the input from the storage format...
Definition: SimpleIni.h:1995
Strict less ordering by order, and then name of key.
Definition: SimpleIni.h:160
std::string trim(const std::string &str)
Removes leading and trailing spaces.
void Reset()
Deallocate all memory stored by this object.
Definition: SimpleIni.h:782
MRPT_IniFileParser & operator=(const MRPT_IniFileParser &rhs)
Definition: SimpleIni.h:2090
FILE * fopen(const char *fileName, const char *mode) noexcept
An OS-independent version of fopen.
Definition: os.cpp:254
typedef void(APIENTRYP PFNGLBLENDCOLORPROC)(GLclampf red
SI_Error Save(OutputWriter &a_oOutput) const
Save the INI data.
Definition: SimpleIni.h:1665
SI_Error CopyString(const SI_CHAR *&a_pString)
Make a copy of the supplied string, replacing the original pointer.
Definition: SimpleIni.h:1330
bool operator<(const TMatchingPair &a, const TMatchingPair &b)
A comparison operator, for sorting lists of TMatchingPair&#39;s, first order by this_idx, if equals, by other_idx.
GLuint res
Definition: glext.h:7268
bool IsMultiLine() const
Query the status of multi-line data.
Definition: SimpleIni.h:331
std::multimap< Entry, const SI_CHAR *, typename Entry::KeyOrder > TKeyVal
map keys to values
Definition: SimpleIni.h:175
std::string parse_process_var_eval(const ParseContext &pc, std::string expr)
Definition: SimpleIni.h:2120
#define SI_NEWLINE_A
Definition: SimpleIni.h:74
GLfloat GLfloat p
Definition: glext.h:6305
bool LoadMultiLineText(SI_CHAR *&a_pData, const SI_CHAR *&a_pVal, const SI_CHAR *a_pTagName, bool a_bAllowBlankLinesInComment=false) const
Definition: SimpleIni.h:1200
Out of memory error.
Definition: SimpleIni.h:68
bool IsComment(SI_CHAR ch) const
Does the supplied character start a comment line?
Definition: SimpleIni.h:686
OutputWriter class to write the INI data to a string.
Definition: SimpleIni.h:214
void memcpy(void *dest, size_t destSize, const void *src, size_t copyCount) noexcept
An OS and compiler independent version of "memcpy".
Definition: os.cpp:355
Entry(const Entry &rhs)
Definition: SimpleIni.h:127
bool IsLess(const SI_CHAR *a_pLeft, const SI_CHAR *a_pRight) const
Internal use of our string comparison function.
Definition: SimpleIni.h:700
StringWriter & operator=(const StringWriter &)



Page generated by Doxygen 1.8.14 for MRPT 1.9.9 Git: ae4571287 Thu Nov 23 00:06:53 2017 +0100 at dom oct 27 23:51:55 CET 2019