C++ Utilities  5.10.5
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 
217 void AdvancedIniFile::parse(std::istream &inputStream, IniFileParseOptions)
218 {
219  inputStream.exceptions(ios_base::failbit | ios_base::badbit);
220 
221  // define variables for state machine
222  enum State { Init, CommentBlock, InlineComment, SectionInlineComment, SectionName, SectionEnd, Key, Value } state = Init;
223  char currentCharacter;
224 
225  // keep track of current comment, section, key and value
226  std::string commentBlock, inlineComment, sectionName, key, value;
227  std::size_t keyPadding = 0, valuePadding = 0;
228  commentBlock.reserve(256);
229  inlineComment.reserve(256);
230  sectionName.reserve(16);
231  key.reserve(16);
232  value.reserve(256);
233 
234  // define function to add entry
235  const auto finishKeyValue = [&, this] {
236  if (key.empty() && value.empty() && state != Value) {
237  return;
238  }
239  if (sections.empty()) {
240  sections.emplace_back(Section{ .flags = IniFileSectionFlags::Implicit });
241  }
242  sections.back().fields.emplace_back(Field{ .key = key,
243  .value = value,
244  .precedingCommentBlock = commentBlock,
245  .followingInlineComment = inlineComment,
246  .paddedKeyLength = key.size() + keyPadding,
247  .flags = (!value.empty() || state == Value ? IniFileFieldFlags::HasValue : IniFileFieldFlags::None) });
248  key.clear();
249  value.clear();
250  commentBlock.clear();
251  inlineComment.clear();
252  keyPadding = valuePadding = 0;
253  };
254 
255  // parse the file char by char
256  try {
257  while (inputStream.get(currentCharacter)) {
258  // handle next character
259  switch (state) {
260  case Init:
261  switch (currentCharacter) {
262  case '\n':
263  commentBlock += currentCharacter;
264  break;
265  case '#':
266  commentBlock += currentCharacter;
267  state = CommentBlock;
268  break;
269  case '=':
270  keyPadding = valuePadding = 0;
271  state = Value;
272  break;
273  case '[':
274  sectionName.clear();
275  state = SectionName;
276  break;
277  default:
278  addChar(currentCharacter, key, keyPadding);
279  state = Key;
280  }
281  break;
282  case Key:
283  switch (currentCharacter) {
284  case '\n':
285  finishKeyValue();
286  state = Init;
287  break;
288  case '#':
289  state = InlineComment;
290  inlineComment += currentCharacter;
291  break;
292  case '=':
293  valuePadding = 0;
294  state = Value;
295  break;
296  default:
297  addChar(currentCharacter, key, keyPadding);
298  }
299  break;
300  case CommentBlock:
301  switch (currentCharacter) {
302  case '\n':
303  state = Init;
304  [[fallthrough]];
305  default:
306  commentBlock += currentCharacter;
307  }
308  break;
309  case InlineComment:
310  case SectionInlineComment:
311  switch (currentCharacter) {
312  case '\n':
313  switch (state) {
314  case InlineComment:
315  finishKeyValue();
316  break;
317  case SectionInlineComment:
318  sections.back().followingInlineComment = inlineComment;
319  inlineComment.clear();
320  break;
321  default:;
322  }
323  state = Init;
324  break;
325  default:
326  inlineComment += currentCharacter;
327  }
328  break;
329  case SectionName:
330  switch (currentCharacter) {
331  case ']':
332  state = SectionEnd;
333  sections.emplace_back(Section{ .name = sectionName });
334  sections.back().precedingCommentBlock = commentBlock;
335  sectionName.clear();
336  commentBlock.clear();
337  break;
338  default:
339  sectionName += currentCharacter;
340  }
341  break;
342  case SectionEnd:
343  switch (currentCharacter) {
344  case '\n':
345  state = Init;
346  break;
347  case '#':
348  state = SectionInlineComment;
349  inlineComment += currentCharacter;
350  break;
351  case '=':
352  keyPadding = valuePadding = 0;
353  state = Value;
354  break;
355  case ' ':
356  break;
357  default:
358  state = Key;
359  addChar(currentCharacter, key, keyPadding);
360  }
361  break;
362  case Value:
363  switch (currentCharacter) {
364  case '\n':
365  finishKeyValue();
366  state = Init;
367  break;
368  case '#':
369  state = InlineComment;
370  inlineComment += currentCharacter;
371  break;
372  default:
373  addChar(currentCharacter, value, valuePadding);
374  }
375  break;
376  }
377  }
378  } catch (const std::ios_base::failure &) {
379  if (!inputStream.eof()) {
380  throw;
381  }
382 
383  // handle eof
384  switch (state) {
385  case Init:
386  case CommentBlock:
387  sections.emplace_back(Section{ .precedingCommentBlock = commentBlock, .flags = IniFileSectionFlags::Implicit });
388  break;
389  case SectionName:
390  sections.emplace_back(Section{ .name = sectionName, .precedingCommentBlock = commentBlock, .flags = IniFileSectionFlags::Implicit });
391  break;
392  case SectionEnd:
393  case SectionInlineComment:
394  sections.emplace_back(Section{ .name = sectionName, .precedingCommentBlock = commentBlock, .followingInlineComment = inlineComment });
395  break;
396  case Key:
397  case Value:
398  case InlineComment:
399  finishKeyValue();
400  break;
401  }
402  }
403 }
404 
412 void AdvancedIniFile::make(ostream &outputStream, IniFileMakeOptions)
413 {
414  outputStream.exceptions(ios_base::failbit | ios_base::badbit);
415  for (const auto &section : sections) {
416  if (!section.precedingCommentBlock.empty()) {
417  outputStream << section.precedingCommentBlock;
418  }
419  if (!(section.flags & IniFileSectionFlags::Implicit)) {
420  outputStream << '[' << section.name << ']';
421  if (!section.followingInlineComment.empty()) {
422  outputStream << ' ' << section.followingInlineComment;
423  }
424  outputStream << '\n';
425  }
426  for (const auto &field : section.fields) {
427  if (!field.precedingCommentBlock.empty()) {
428  outputStream << field.precedingCommentBlock;
429  }
430  outputStream << field.key;
431  for (auto charsWritten = field.key.size(); charsWritten < field.paddedKeyLength; ++charsWritten) {
432  outputStream << ' ';
433  }
434  if (field.flags & IniFileFieldFlags::HasValue) {
435  outputStream << '=' << ' ' << field.value;
436  }
437  if (!field.followingInlineComment.empty()) {
438  if (field.flags & IniFileFieldFlags::HasValue) {
439  outputStream << ' ';
440  }
441  outputStream << field.followingInlineComment;
442  }
443  outputStream << '\n';
444  }
445  }
446 }
447 
448 } // namespace CppUtilities
Contains all utilities provides by the c++utilities library.
IniFileMakeOptions
Definition: inifile.h:64
IniFileParseOptions
Definition: inifile.h:60
The AdvancedIniFile::Field class represents a field within an INI file.
Definition: inifile.h:80
The AdvancedIniFile::Section class represents a section within an INI file.
Definition: inifile.h:89