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



Page generated by Doxygen 1.8.14 for MRPT 2.0.0 Git: b38439d21 Tue Mar 31 19:58:06 2020 +0200 at miƩ abr 1 00:50:30 CEST 2020