Instrument Neutral Distributed Interface INDI 2.2.5
inicpp.h
Go to the documentation of this file.
1/*
2 * inicpp.h
3 *
4 * Created on: 26 Dec 2015
5 * Author: Fabian Meyer
6 * License: MIT
7 */
8
9#ifndef INICPP_H_
10#define INICPP_H_
11
12#include <algorithm>
13#include <fstream>
14#include <istream>
15#include <map>
16#include <assert.h>
17#include <sstream>
18#include <stdexcept>
19#include <vector>
20
21namespace ini
22{
23 /************************************************
24 * Helper Functions
25 ************************************************/
26
28 constexpr const char *whitespaces()
29 {
30 return " \t\n\r\f\v";
31 }
32
34 constexpr const char *indents()
35 {
36 return " \t";
37 }
38
41 inline void trim(std::string &str)
42 {
43 // first erasing from end should be slightly more efficient
44 // because erasing from start potentially moves all chars
45 // multiple indices towards the front.
46
47 auto lastpos = str.find_last_not_of(whitespaces());
48 if(lastpos == std::string::npos)
49 {
50 str.clear();
51 return;
52 }
53
54 str.erase(lastpos + 1);
55 str.erase(0, str.find_first_not_of(whitespaces()));
56 }
57
58 /************************************************
59 * Conversion Functors
60 ************************************************/
61
62 inline bool strToLong(const std::string &value, long &result)
63 {
64 char *endptr;
65 // check if decimal
66 result = std::strtol(value.c_str(), &endptr, 10);
67 if(*endptr == '\0')
68 return true;
69 // check if octal
70 result = std::strtol(value.c_str(), &endptr, 8);
71 if(*endptr == '\0')
72 return true;
73 // check if hex
74 result = std::strtol(value.c_str(), &endptr, 16);
75 if(*endptr == '\0')
76 return true;
77
78 return false;
79 }
80
81 inline bool strToULong(const std::string &value, unsigned long &result)
82 {
83 char *endptr;
84 // check if decimal
85 result = std::strtoul(value.c_str(), &endptr, 10);
86 if(*endptr == '\0')
87 return true;
88 // check if octal
89 result = std::strtoul(value.c_str(), &endptr, 8);
90 if(*endptr == '\0')
91 return true;
92 // check if hex
93 result = std::strtoul(value.c_str(), &endptr, 16);
94 if(*endptr == '\0')
95 return true;
96
97 return false;
98 }
99
100 template<typename T>
101 struct Convert
102 {};
103
104 template<>
105 struct Convert<bool>
106 {
107 void decode(const std::string &value, bool &result)
108 {
109 std::string str(value);
110 std::transform(str.begin(), str.end(), str.begin(), [](const char c){
111 return static_cast<char>(::toupper(c));
112 });
113
114 if(str == "TRUE")
115 result = true;
116 else if(str == "FALSE")
117 result = false;
118 else
119 throw std::invalid_argument("field is not a bool");
120 }
121
122 void encode(const bool value, std::string &result)
123 {
124 result = value ? "true" : "false";
125 }
126 };
127
128 template<>
129 struct Convert<char>
130 {
131 void decode(const std::string &value, char &result)
132 {
133 assert(value.size() > 0);
134 result = value[0];
135 }
136
137 void encode(const char value, std::string &result)
138 {
139 result = value;
140 }
141 };
142
143 template<>
144 struct Convert<unsigned char>
145 {
146 void decode(const std::string &value, unsigned char &result)
147 {
148 assert(value.size() > 0);
149 result = value[0];
150 }
151
152 void encode(const unsigned char value, std::string &result)
153 {
154 result = value;
155 }
156 };
157
158 template<>
159 struct Convert<short>
160 {
161 void decode(const std::string &value, short &result)
162 {
163 long tmp;
164 if(!strToLong(value, tmp))
165 throw std::invalid_argument("field is not a short");
166 result = static_cast<short>(tmp);
167 }
168
169 void encode(const short value, std::string &result)
170 {
171 std::stringstream ss;
172 ss << value;
173 result = ss.str();
174 }
175 };
176
177 template<>
178 struct Convert<unsigned short>
179 {
180 void decode(const std::string &value, unsigned short &result)
181 {
182 unsigned long tmp;
183 if(!strToULong(value, tmp))
184 throw std::invalid_argument("field is not an unsigned short");
185 result = static_cast<unsigned short>(tmp);
186 }
187
188 void encode(const unsigned short value, std::string &result)
189 {
190 std::stringstream ss;
191 ss << value;
192 result = ss.str();
193 }
194 };
195
196 template<>
197 struct Convert<int>
198 {
199 void decode(const std::string &value, int &result)
200 {
201 long tmp;
202 if(!strToLong(value, tmp))
203 throw std::invalid_argument("field is not an int");
204 result = static_cast<int>(tmp);
205 }
206
207 void encode(const int value, std::string &result)
208 {
209 std::stringstream ss;
210 ss << value;
211 result = ss.str();
212 }
213 };
214
215 template<>
216 struct Convert<unsigned int>
217 {
218 void decode(const std::string &value, unsigned int &result)
219 {
220 unsigned long tmp;
221 if(!strToULong(value, tmp))
222 throw std::invalid_argument("field is not an unsigned int");
223 result = static_cast<unsigned int>(tmp);
224 }
225
226 void encode(const unsigned int value, std::string &result)
227 {
228 std::stringstream ss;
229 ss << value;
230 result = ss.str();
231 }
232 };
233
234 template<>
235 struct Convert<long>
236 {
237 void decode(const std::string &value, long &result)
238 {
239 if(!strToLong(value, result))
240 throw std::invalid_argument("field is not a long");
241 }
242
243 void encode(const long value, std::string &result)
244 {
245 std::stringstream ss;
246 ss << value;
247 result = ss.str();
248 }
249 };
250
251 template<>
252 struct Convert<unsigned long>
253 {
254 void decode(const std::string &value, unsigned long &result)
255 {
256 if(!strToULong(value, result))
257 throw std::invalid_argument("field is not an unsigned long");
258 }
259
260 void encode(const unsigned long value, std::string &result)
261 {
262 std::stringstream ss;
263 ss << value;
264 result = ss.str();
265 }
266 };
267
268 template<>
269 struct Convert<double>
270 {
271 void decode(const std::string &value, double &result)
272 {
273 result = std::stod(value);
274 }
275
276 void encode(const double value, std::string &result)
277 {
278 std::stringstream ss;
279 ss << value;
280 result = ss.str();
281 }
282 };
283
284 template<>
285 struct Convert<float>
286 {
287 void decode(const std::string &value, float &result)
288 {
289 result = std::stof(value);
290 }
291
292 void encode(const float value, std::string &result)
293 {
294 std::stringstream ss;
295 ss << value;
296 result = ss.str();
297 }
298 };
299
300 template<>
301 struct Convert<std::string>
302 {
303 void decode(const std::string &value, std::string &result)
304 {
305 result = value;
306 }
307
308 void encode(const std::string &value, std::string &result)
309 {
310 result = value;
311 }
312 };
313
314 template<>
315 struct Convert<const char*>
316 {
317 void encode(const char* const &value, std::string &result)
318 {
319 result = value;
320 }
321
322 void decode(const std::string &value, const char* &result)
323 {
324 result = value.c_str();
325 }
326 };
327
328 template<>
329 struct Convert<char*>
330 {
331 void encode(const char* const &value, std::string &result)
332 {
333 result = value;
334 }
335 };
336
337 template<size_t n>
338 struct Convert<char[n]>
339 {
340 void encode(const char *value, std::string &result)
341 {
342 result = value;
343 }
344 };
345
347 {
348 private:
349 std::string value_;
350
351 public:
352 IniField() : value_()
353 {}
354
355 IniField(const std::string &value) : value_(value)
356 {}
357 IniField(const IniField &field) : value_(field.value_)
358 {}
359
361 {}
362
363 template<typename T>
364 T as() const
365 {
366 Convert<T> conv;
367 T result;
368 conv.decode(value_, result);
369 return result;
370 }
371
372 template<typename T>
373 IniField &operator=(const T &value)
374 {
375 Convert<T> conv;
376 conv.encode(value, value_);
377 return *this;
378 }
379
381 {
382 value_ = field.value_;
383 return *this;
384 }
385 };
386
388 {
389 bool operator()(std::string lhs, std::string rhs) const
390 {
391 std::transform(lhs.begin(), lhs.end(), lhs.begin(), [](const char c){
392 return static_cast<char>(::tolower(c));
393 });
394 std::transform(rhs.begin(), rhs.end(), rhs.begin(), [](const char c){
395 return static_cast<char>(::tolower(c));
396 });
397 return lhs < rhs;
398 }
399 };
400
401 template <typename Comparator>
402 class IniSectionBase : public std::map<std::string, IniField, Comparator>
403 {
404 public:
409 };
410
413
414 template <typename Comparator>
415 class IniFileBase : public std::map<std::string, IniSectionBase<Comparator>, Comparator>
416 {
417 private:
418 char fieldSep_ = '=';
419 char esc_ = '\\';
420 std::vector<std::string> commentPrefixes_ = { "#" , ";" };
421 bool multiLineValues_ = false;
422
423 void eraseComment(const std::string &commentPrefix,
424 std::string &str,
425 std::string::size_type startpos = 0)
426 {
427 size_t prefixpos = str.find(commentPrefix, startpos);
428 if(std::string::npos == prefixpos)
429 return;
430 // Found a comment prefix, is it escaped?
431 if(0 != prefixpos && str[prefixpos - 1] == esc_)
432 {
433 // The comment prefix is escaped, so just delete the escape char
434 // and keep erasing after the comment prefix
435 str.erase(prefixpos - 1, 1);
436 eraseComment(
437 commentPrefix, str, prefixpos - 1 + commentPrefix.size());
438 }
439 else
440 {
441 str.erase(prefixpos);
442 }
443 }
444
445 void eraseComments(std::string &str)
446 {
447 for(const std::string &commentPrefix : commentPrefixes_)
448 eraseComment(commentPrefix, str);
449 }
450
453 std::vector<std::string>::const_iterator findCommentPrefix(const std::string &str,
454 const std::size_t startpos) const
455 {
456 // if startpos is invalid simply return "not found"
457 if(startpos >= str.size())
458 return commentPrefixes_.end();
459
460 for(size_t i = 0; i < commentPrefixes_.size(); ++i)
461 {
462 const std::string &prefix = commentPrefixes_[i];
463 // if this comment prefix is longer than the string view itself
464 // then skip
465 if(prefix.size() + startpos > str.size())
466 continue;
467
468 bool match = true;
469 for(size_t j = 0; j < prefix.size() && match; ++j)
470 match = str[startpos + j] == prefix[j];
471
472 if(match)
473 return commentPrefixes_.begin() + i;
474 }
475
476 return commentPrefixes_.end();
477 }
478
479 void writeEscaped(std::ostream &os, const std::string &str) const
480 {
481 for(size_t i = 0; i < str.length(); ++i)
482 {
483 auto prefixpos = findCommentPrefix(str, i);
484 // if no suitable prefix was found at this position
485 // then simply write the current character
486 if(prefixpos != commentPrefixes_.end())
487 {
488 const std::string &prefix = *prefixpos;
489 os.put(esc_);
490 os.write(prefix.c_str(), prefix.size());
491 i += prefix.size() - 1;
492 }
493 else if (multiLineValues_ && str[i] == '\n')
494 os.write("\n\t", 2);
495 else
496 os.put(str[i]);
497 }
498 }
499
500 public:
501 IniFileBase() = default;
502
503 IniFileBase(const char fieldSep, const char comment)
504 : fieldSep_(fieldSep), commentPrefixes_(1, std::string(1, comment))
505 {}
506
507 IniFileBase(const std::string &filename)
508 {
509 load(filename);
510 }
511
512 IniFileBase(std::istream &is)
513 {
514 decode(is);
515 }
516
517 IniFileBase(const char fieldSep,
518 const std::vector<std::string> &commentPrefixes)
519 : fieldSep_(fieldSep), commentPrefixes_(commentPrefixes)
520 {}
521
522 IniFileBase(const std::string &filename,
523 const char fieldSep,
524 const std::vector<std::string> &commentPrefixes)
525 : fieldSep_(fieldSep), commentPrefixes_(commentPrefixes)
526 {
527 load(filename);
528 }
529
530 IniFileBase(std::istream &is,
531 const char fieldSep,
532 const std::vector<std::string> &commentPrefixes)
533 : fieldSep_(fieldSep), commentPrefixes_(commentPrefixes)
534 {
535 decode(is);
536 }
537
539 {}
540
543 void setFieldSep(const char sep)
544 {
545 fieldSep_ = sep;
546 }
547
553 void setCommentChar(const char comment)
554 {
555 commentPrefixes_ = {std::string(1, comment)};
556 }
557
563 void setCommentPrefixes(const std::vector<std::string> &commentPrefixes)
564 {
565 commentPrefixes_ = commentPrefixes;
566 }
567
571 void setEscapeChar(const char esc)
572 {
573 esc_ = esc;
574 }
575
579 void setMultiLineValues(bool enable)
580 {
581 multiLineValues_ = enable;
582 }
583
586 void decode(std::istream &is)
587 {
588 this->clear();
589 int lineNo = 0;
590 IniSectionBase<Comparator> *currentSection = nullptr;
591 std::string mutliLineValueFieldName = "";
592 std::string line;
593 // iterate file line by line
594 while(!is.eof() && !is.fail())
595 {
596 std::getline(is, line, '\n');
597 eraseComments(line);
598 bool hasIndent = line.find_first_not_of(indents()) != 0;
599 trim(line);
600 ++lineNo;
601
602 // skip if line is empty
603 if(line.size() == 0)
604 continue;
605
606 if(line[0] == '[')
607 {
608 // line is a section
609 // check if the section is also closed on same line
610 std::size_t pos = line.find("]");
611 if(pos == std::string::npos)
612 {
613 std::stringstream ss;
614 ss << "l." << lineNo
615 << ": ini parsing failed, section not closed";
616 throw std::logic_error(ss.str());
617 }
618 // check if the section name is empty
619 if(pos == 1)
620 {
621 std::stringstream ss;
622 ss << "l." << lineNo
623 << ": ini parsing failed, section is empty";
624 throw std::logic_error(ss.str());
625 }
626
627 // retrieve section name
628 std::string secName = line.substr(1, pos - 1);
629 currentSection = &((*this)[secName]);
630
631 // clear multiline value field name
632 // a new section means there is no value to continue
633 mutliLineValueFieldName = "";
634 }
635 else
636 {
637 // line is a field definition
638 // check if section was already opened
639 if(currentSection == nullptr)
640 {
641 std::stringstream ss;
642 ss << "l." << lineNo
643 << ": ini parsing failed, field has no section";
644 throw std::logic_error(ss.str());
645 }
646
647 // find key value separator
648 std::size_t pos = line.find(fieldSep_);
649 if (multiLineValues_ && hasIndent && mutliLineValueFieldName != "")
650 {
651 // extend a multi-line value
652 IniField previous_value = (*currentSection)[mutliLineValueFieldName];
653 std::string value = previous_value.as<std::string>() + "\n" + line;
654 (*currentSection)[mutliLineValueFieldName] = value;
655 }
656 else if(pos == std::string::npos)
657 {
658 std::stringstream ss;
659 ss << "l." << lineNo
660 << ": ini parsing failed, no '"
661 << fieldSep_
662 << "' found";
663 if (multiLineValues_)
664 ss << ", and not a multi-line value continuation";
665 throw std::logic_error(ss.str());
666 }
667 else
668 {
669 // retrieve field name and value
670 std::string name = line.substr(0, pos);
671 trim(name);
672 std::string value = line.substr(pos + 1, std::string::npos);
673 trim(value);
674 (*currentSection)[name] = value;
675 // store last field name for potential multi-line values
676 mutliLineValueFieldName = name;
677 }
678 }
679 }
680 }
681
684 void decode(const std::string &content)
685 {
686 std::istringstream ss(content);
687 decode(ss);
688 }
689
692 void load(const std::string &fileName)
693 {
694 std::ifstream is(fileName.c_str());
695 decode(is);
696 }
697
700 void encode(std::ostream &os) const
701 {
702 // iterate through all sections in this file
703 for(const auto &filePair : *this)
704 {
705 os.put('[');
706 writeEscaped(os, filePair.first);
707 os.put(']');
708 os.put('\n');
709
710 // iterate through all fields in the section
711 for(const auto &secPair : filePair.second)
712 {
713 writeEscaped(os, secPair.first);
714 os.put(fieldSep_);
715 writeEscaped(os, secPair.second.template as<std::string>());
716 os.put('\n');
717 }
718 }
719 }
720
723 std::string encode() const
724 {
725 std::ostringstream ss;
726 encode(ss);
727 return ss.str();
728 }
729
732 void save(const std::string &fileName) const
733 {
734 std::ofstream os(fileName.c_str());
735 encode(os);
736 }
737 };
738
743}
744
745#endif
IniField & operator=(const T &value)
Definition inicpp.h:373
IniField(const IniField &field)
Definition inicpp.h:357
IniField(const std::string &value)
Definition inicpp.h:355
T as() const
Definition inicpp.h:364
IniField & operator=(const IniField &field)
Definition inicpp.h:380
void decode(std::istream &is)
Definition inicpp.h:586
void save(const std::string &fileName) const
Definition inicpp.h:732
void setCommentPrefixes(const std::vector< std::string > &commentPrefixes)
Definition inicpp.h:563
IniFileBase(const char fieldSep, const std::vector< std::string > &commentPrefixes)
Definition inicpp.h:517
IniFileBase(const char fieldSep, const char comment)
Definition inicpp.h:503
void setMultiLineValues(bool enable)
Definition inicpp.h:579
IniFileBase(const std::string &filename)
Definition inicpp.h:507
IniFileBase(std::istream &is, const char fieldSep, const std::vector< std::string > &commentPrefixes)
Definition inicpp.h:530
void load(const std::string &fileName)
Definition inicpp.h:692
void setFieldSep(const char sep)
Definition inicpp.h:543
IniFileBase(std::istream &is)
Definition inicpp.h:512
void setCommentChar(const char comment)
Definition inicpp.h:553
void decode(const std::string &content)
Definition inicpp.h:684
void setEscapeChar(const char esc)
Definition inicpp.h:571
IniFileBase(const std::string &filename, const char fieldSep, const std::vector< std::string > &commentPrefixes)
Definition inicpp.h:522
IniFileBase()=default
void encode(std::ostream &os) const
Definition inicpp.h:700
std::string encode() const
Definition inicpp.h:723
Definition inicpp.h:22
constexpr const char * whitespaces()
Definition inicpp.h:28
bool strToLong(const std::string &value, long &result)
Definition inicpp.h:62
bool strToULong(const std::string &value, unsigned long &result)
Definition inicpp.h:81
void trim(std::string &str)
Definition inicpp.h:41
constexpr const char * indents()
Definition inicpp.h:34
void encode(const bool value, std::string &result)
Definition inicpp.h:122
void decode(const std::string &value, bool &result)
Definition inicpp.h:107
void decode(const std::string &value, char &result)
Definition inicpp.h:131
void encode(const char value, std::string &result)
Definition inicpp.h:137
void encode(const char *const &value, std::string &result)
Definition inicpp.h:331
void encode(const char *value, std::string &result)
Definition inicpp.h:340
void decode(const std::string &value, const char *&result)
Definition inicpp.h:322
void encode(const char *const &value, std::string &result)
Definition inicpp.h:317
void encode(const double value, std::string &result)
Definition inicpp.h:276
void decode(const std::string &value, double &result)
Definition inicpp.h:271
void decode(const std::string &value, float &result)
Definition inicpp.h:287
void encode(const float value, std::string &result)
Definition inicpp.h:292
void decode(const std::string &value, int &result)
Definition inicpp.h:199
void encode(const int value, std::string &result)
Definition inicpp.h:207
void encode(const long value, std::string &result)
Definition inicpp.h:243
void decode(const std::string &value, long &result)
Definition inicpp.h:237
void encode(const short value, std::string &result)
Definition inicpp.h:169
void decode(const std::string &value, short &result)
Definition inicpp.h:161
void decode(const std::string &value, std::string &result)
Definition inicpp.h:303
void encode(const std::string &value, std::string &result)
Definition inicpp.h:308
void encode(const unsigned char value, std::string &result)
Definition inicpp.h:152
void decode(const std::string &value, unsigned char &result)
Definition inicpp.h:146
void encode(const unsigned int value, std::string &result)
Definition inicpp.h:226
void decode(const std::string &value, unsigned int &result)
Definition inicpp.h:218
void encode(const unsigned long value, std::string &result)
Definition inicpp.h:260
void decode(const std::string &value, unsigned long &result)
Definition inicpp.h:254
void encode(const unsigned short value, std::string &result)
Definition inicpp.h:188
void decode(const std::string &value, unsigned short &result)
Definition inicpp.h:180
bool operator()(std::string lhs, std::string rhs) const
Definition inicpp.h:389