C++ Utilities  5.4.0
Useful C++ classes and routines such as argument parser, IO and conversion utilities
inifile.cpp
Go to the documentation of this file.
1 #include "./inifile.h"
2 
3 #include "../conversion/stringconversion.h"
4 
5 #include <iostream>
6 
7 using namespace std;
8 
9 namespace CppUtilities {
10 
12 static void addChar(char c, std::string &to, std::size_t &padding)
13 {
14  if (c == ' ') {
15  ++padding;
16  return;
17  }
18  if (!to.empty()) {
19  while (padding) {
20  to += ' ';
21  --padding;
22  }
23  } else {
24  padding = 0;
25  }
26  to += c;
27 };
29 
40 void IniFile::parse(std::istream &inputStream)
41 {
42  inputStream.exceptions(ios_base::failbit | ios_base::badbit);
43 
44  // define variables for state machine
45  enum State { Init, Comment, SectionName, Key, Value } state = Init;
46  char currentCharacter;
47 
48  // keep track of current scope, key and value and number of postponed whitespaces
49  std::size_t whitespace = 0;
50  string sectionName, key, value;
51  sectionName.reserve(16);
52  key.reserve(16);
53  value.reserve(256);
54 
55  // define function to add a key/value pair
56  const auto finishKeyValue = [&state, &sectionName, &key, &value, &whitespace, this] {
57  if (key.empty() && value.empty() && state != Value) {
58  return;
59  }
60  if (m_data.empty() || m_data.back().first != sectionName) {
61  m_data.emplace_back(make_pair(sectionName, decltype(m_data)::value_type::second_type()));
62  }
63  m_data.back().second.insert(make_pair(key, value));
64  key.clear();
65  value.clear();
66  whitespace = 0;
67  };
68 
69  // parse the file char by char
70  try {
71  while (inputStream.get(currentCharacter)) {
72  // handle next character
73  switch (state) {
74  case Init:
75  switch (currentCharacter) {
76  case '\n':
77  break;
78  case '#':
79  state = Comment;
80  break;
81  case '=':
82  whitespace = 0;
83  state = Value;
84  break;
85  case '[':
86  sectionName.clear();
87  state = SectionName;
88  break;
89  default:
90  addChar(currentCharacter, key, whitespace);
91  state = Key;
92  }
93  break;
94  case Key:
95  switch (currentCharacter) {
96  case '\n':
97  finishKeyValue();
98  state = Init;
99  break;
100  case '#':
101  finishKeyValue();
102  state = Comment;
103  break;
104  case '=':
105  whitespace = 0;
106  state = Value;
107  break;
108  default:
109  addChar(currentCharacter, key, whitespace);
110  }
111  break;
112  case Comment:
113  switch (currentCharacter) {
114  case '\n':
115  state = Init;
116  break;
117  default:;
118  }
119  break;
120  case SectionName:
121  switch (currentCharacter) {
122  case ']':
123  state = Init;
124  break;
125  default:
126  sectionName += currentCharacter;
127  }
128  break;
129  case Value:
130  switch (currentCharacter) {
131  case '\n':
132  finishKeyValue();
133  state = Init;
134  break;
135  case '#':
136  finishKeyValue();
137  state = Comment;
138  break;
139  default:
140  addChar(currentCharacter, value, whitespace);
141  }
142  break;
143  }
144  }
145  } catch (const std::ios_base::failure &) {
146  if (!inputStream.eof()) {
147  throw;
148  }
149 
150  // handle eof
151  finishKeyValue();
152  }
153 }
154 
159 void IniFile::make(ostream &outputStream)
160 {
161  outputStream.exceptions(ios_base::failbit | ios_base::badbit);
162  for (const auto &section : m_data) {
163  outputStream << '[' << section.first << ']' << '\n';
164  for (const auto &field : section.second) {
165  outputStream << field.first << '=' << field.second << '\n';
166  }
167  outputStream << '\n';
168  }
169 }
170 
214 void AdvancedIniFile::parse(std::istream &inputStream, IniFileParseOptions)
215 {
216  inputStream.exceptions(ios_base::failbit | ios_base::badbit);
217 
218  // define variables for state machine
219  enum State { Init, CommentBlock, InlineComment, SectionInlineComment, SectionName, SectionEnd, Key, Value } state = Init;
220  char currentCharacter;
221 
222  // keep track of current comment, section, key and value
223  std::string commentBlock, inlineComment, sectionName, key, value;
224  std::size_t keyPadding = 0, valuePadding = 0;
225  commentBlock.reserve(256);
226  inlineComment.reserve(256);
227  sectionName.reserve(16);
228  key.reserve(16);
229  value.reserve(256);
230 
231  // define function to add entry
232  const auto finishKeyValue = [&, this] {
233  if (key.empty() && value.empty() && state != Value) {
234  return;
235  }
236  if (sections.empty()) {
237  sections.emplace_back(Section{ .flags = IniFileSectionFlags::Implicit });
238  }
239  sections.back().fields.emplace_back(Field{ .key = key,
240  .value = value,
241  .precedingCommentBlock = commentBlock,
242  .followingInlineComment = inlineComment,
243  .paddedKeyLength = key.size() + keyPadding,
244  .flags = (!value.empty() || state == Value ? IniFileFieldFlags::HasValue : IniFileFieldFlags::None) });
245  key.clear();
246  value.clear();
247  commentBlock.clear();
248  inlineComment.clear();
249  keyPadding = valuePadding = 0;
250  };
251 
252  // parse the file char by char
253  try {
254  while (inputStream.get(currentCharacter)) {
255  // handle next character
256  switch (state) {
257  case Init:
258  switch (currentCharacter) {
259  case '\n':
260  commentBlock += currentCharacter;
261  break;
262  case '#':
263  commentBlock += currentCharacter;
264  state = CommentBlock;
265  break;
266  case '=':
267  keyPadding = valuePadding = 0;
268  state = Value;
269  break;
270  case '[':
271  sectionName.clear();
272  state = SectionName;
273  break;
274  default:
275  addChar(currentCharacter, key, keyPadding);
276  state = Key;
277  }
278  break;
279  case Key:
280  switch (currentCharacter) {
281  case '\n':
282  finishKeyValue();
283  state = Init;
284  break;
285  case '#':
286  state = InlineComment;
287  inlineComment += currentCharacter;
288  break;
289  case '=':
290  valuePadding = 0;
291  state = Value;
292  break;
293  default:
294  addChar(currentCharacter, key, keyPadding);
295  }
296  break;
297  case CommentBlock:
298  switch (currentCharacter) {
299  case '\n':
300  state = Init;
301  [[fallthrough]];
302  default:
303  commentBlock += currentCharacter;
304  }
305  break;
306  case InlineComment:
307  case SectionInlineComment:
308  switch (currentCharacter) {
309  case '\n':
310  switch (state) {
311  case InlineComment:
312  finishKeyValue();
313  break;
314  case SectionInlineComment:
315  sections.back().followingInlineComment = inlineComment;
316  inlineComment.clear();
317  break;
318  default:;
319  }
320  state = Init;
321  break;
322  default:
323  inlineComment += currentCharacter;
324  }
325  break;
326  case SectionName:
327  switch (currentCharacter) {
328  case ']':
329  state = SectionEnd;
330  sections.emplace_back(Section{ .name = sectionName });
331  sections.back().precedingCommentBlock = commentBlock;
332  sectionName.clear();
333  commentBlock.clear();
334  break;
335  default:
336  sectionName += currentCharacter;
337  }
338  break;
339  case SectionEnd:
340  switch (currentCharacter) {
341  case '\n':
342  state = Init;
343  break;
344  case '#':
345  state = SectionInlineComment;
346  inlineComment += currentCharacter;
347  break;
348  case '=':
349  keyPadding = valuePadding = 0;
350  state = Value;
351  break;
352  case ' ':
353  break;
354  default:
355  state = Key;
356  addChar(currentCharacter, key, keyPadding);
357  }
358  break;
359  case Value:
360  switch (currentCharacter) {
361  case '\n':
362  finishKeyValue();
363  state = Init;
364  break;
365  case '#':
366  state = InlineComment;
367  inlineComment += currentCharacter;
368  break;
369  default:
370  addChar(currentCharacter, value, valuePadding);
371  }
372  break;
373  }
374  }
375  } catch (const std::ios_base::failure &) {
376  if (!inputStream.eof()) {
377  throw;
378  }
379 
380  // handle eof
381  switch (state) {
382  case Init:
383  case CommentBlock:
384  sections.emplace_back(Section{ .precedingCommentBlock = commentBlock, .flags = IniFileSectionFlags::Implicit });
385  break;
386  case SectionName:
387  sections.emplace_back(Section{ .name = sectionName, .precedingCommentBlock = commentBlock, .flags = IniFileSectionFlags::Implicit });
388  break;
389  case SectionEnd:
390  case SectionInlineComment:
391  sections.emplace_back(Section{ .name = sectionName, .precedingCommentBlock = commentBlock, .followingInlineComment = inlineComment });
392  break;
393  case Key:
394  case Value:
395  case InlineComment:
396  finishKeyValue();
397  break;
398  }
399  }
400 }
401 
409 void AdvancedIniFile::make(ostream &outputStream, IniFileMakeOptions)
410 {
411  outputStream.exceptions(ios_base::failbit | ios_base::badbit);
412  for (const auto &section : sections) {
413  if (!section.precedingCommentBlock.empty()) {
414  outputStream << section.precedingCommentBlock;
415  }
416  if (!(section.flags & IniFileSectionFlags::Implicit)) {
417  outputStream << '[' << section.name << ']';
418  if (!section.followingInlineComment.empty()) {
419  outputStream << ' ' << section.followingInlineComment;
420  }
421  outputStream << '\n';
422  }
423  for (const auto &field : section.fields) {
424  if (!field.precedingCommentBlock.empty()) {
425  outputStream << field.precedingCommentBlock;
426  }
427  outputStream << field.key;
428  for (auto charsWritten = field.key.size(); charsWritten < field.paddedKeyLength; ++charsWritten) {
429  outputStream << ' ';
430  }
431  if (field.flags & IniFileFieldFlags::HasValue) {
432  outputStream << '=' << ' ' << field.value;
433  }
434  if (!field.followingInlineComment.empty()) {
435  if (field.flags & IniFileFieldFlags::HasValue) {
436  outputStream << ' ';
437  }
438  outputStream << field.followingInlineComment;
439  }
440  outputStream << '\n';
441  }
442  }
443 }
444 
445 } // namespace CppUtilities
CppUtilities::IniFileParseOptions
IniFileParseOptions
Definition: inifile.h:60
CppUtilities::AdvancedIniFile::Section::precedingCommentBlock
std::string precedingCommentBlock
Definition: inifile.h:99
CppUtilities::IniFileMakeOptions
IniFileMakeOptions
Definition: inifile.h:64
inifile.h
CppUtilities::AdvancedIniFile::Section::flags
IniFileSectionFlags flags
Definition: inifile.h:101
CppUtilities
Contains all utilities provides by the c++utilities library.
Definition: argumentparser.h:17
CppUtilities::Value
@ Value
Definition: argumentparser.cpp:39
CppUtilities::AdvancedIniFile::Section
The AdvancedIniFile::Section class represents a section within an INI file.
Definition: inifile.h:89
CppUtilities::AdvancedIniFile::Field
The AdvancedIniFile::Field class represents a field within an INI file.
Definition: inifile.h:80
CppUtilities::AdvancedIniFile::Field::key
std::string key
Definition: inifile.h:81
CppUtilities::AdvancedIniFile::Section::name
std::string name
Definition: inifile.h:97