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



Page generated by Doxygen 1.8.14 for MRPT 1.9.9 Git: 7d5e6d718 Fri Aug 24 01:51:28 2018 +0200 at lun nov 2 08:35:50 CET 2020