C++ Utilities 5.26.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
4
5#include <iostream>
6
7using namespace std;
8
9namespace CppUtilities {
10
12static 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
40void 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
159void 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
217void 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
412void 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
void parse(std::istream &inputStream)
Parses all data from the specified inputStream.
Definition inifile.cpp:40
void make(std::ostream &outputStream)
Write the current data to the specified outputStream.
Definition inifile.cpp:159
Contains all utilities provides 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:412
void parse(std::istream &inputStream, IniFileParseOptions options=IniFileParseOptions::None)
Parses all data from the specified inputStream.
Definition inifile.cpp:217