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



Page generated by Doxygen 1.8.14 for MRPT 1.9.9 Git: ad3a9d8ae Tue May 1 23:10:22 2018 -0700 at lun oct 28 00:14:14 CET 2019