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