plptools
Loading...
Searching...
No Matches
ini.cc
Go to the documentation of this file.
1/*
2 * This file is part of plptools.
3 *
4 * Copyright (c) 2026 Jason Morley <hello@jbmorley.co.uk>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License along
17 * along with this program; if not, see <https://www.gnu.org/licenses/>.
18 *
19 */
20#include "config.h"
21
22#include <algorithm>
23#include <sstream>
24#include <string>
25#include <unordered_map>
26#include <vector>
27
28#include "ini.h"
29
30std::string ini::serialize(const std::unordered_map<std::string, std::string> &contents) {
31
32 // Sort the keys to ensure we generate stable output.
33 std::vector<std::string> sortedKeys;
34 sortedKeys.reserve(contents.size());
35 for (const auto& entry : contents) {
36 sortedKeys.push_back(entry.first);
37 }
38 std::sort(sortedKeys.begin(), sortedKeys.end());
39
40 // Iterate over the keys outputting the ini file line.
41 std::ostringstream stream;
42 for (const auto& key : sortedKeys) {
43 stream << key << " = " << contents.at(key) << "\r\n";
44 }
45
46 return stream.str();
47}
48
49std::unique_ptr<std::unordered_map<std::string, std::string>> ini::deserialize(const std::string contents) {
50 std::unordered_map<std::string, std::string> result;
51 std::string currentKey;
52 std::string currentValue;
53 std::string currentValueWhitespace;
54
55 enum class ParserState {
56 kKeyStart,
57 kKey,
58 kKeyEnd,
59 kValueStart,
60 kValue,
61 kValueWhitespace,
62 kValueEnd,
63 };
64
65 ParserState state = ParserState::kKeyStart;
66 for (char c : contents) {
67 switch (state) {
68 case ParserState::kKeyStart:
69 if (std::isspace(c)) {
70 // Ignore leading whitespace.
71 continue;
72 } else if (std::isalpha(static_cast<unsigned char>(c))) {
73 currentKey += c;
74 state = ParserState::kKey;
75 } else {
76 return nullptr;
77 }
78 break;
79 case ParserState::kKey:
80 if (c == '=') {
81 state = ParserState::kValueStart;
82 } else if (std::isspace(c)) {
83 state = ParserState::kKeyEnd;
84 } else if (std::isalpha(static_cast<unsigned char>(c))) {
85 currentKey += c;
86 } else {
87 return nullptr;
88 }
89 break;
90 case ParserState::kKeyEnd:
91 if (c == '=') {
92 state = ParserState::kValueStart;
93 } else if (!std::isspace(c)) {
94 return nullptr;
95 }
96 break;
97 case ParserState::kValueStart:
98 if (!std::isspace(c)) {
99 currentValue += c;
100 state = ParserState::kValue;
101 }
102 break;
103 case ParserState::kValue:
104 if (c == '\r') {
105 state = ParserState::kValueEnd;
106 } else if (c == '\n') {
107 result[currentKey] = currentValue;
108 currentKey = "";
109 currentValue = "";
110 currentValueWhitespace = "";
111 state = ParserState::kKeyStart;
112 } else if (std::isspace(c)) {
113 currentValueWhitespace += c;
114 state = ParserState::kValueWhitespace;
115 } else {
116 currentValue += c;
117 }
118 break;
119 case ParserState::kValueWhitespace:
120 if (c == '\r') {
121 currentValueWhitespace = "";
122 state = ParserState::kValueEnd;
123 } else if (c == '\n') {
124 result[currentKey] = currentValue;
125 currentKey = "";
126 currentValue = "";
127 currentValueWhitespace = "";
128 state = ParserState::kKeyStart;
129 } else if (std::isspace(c)) {
130 currentValueWhitespace += c;
131 } else {
132 currentValue += currentValueWhitespace;
133 currentValue += c;
134 currentValueWhitespace = "";
135 state = ParserState::kValue;
136 }
137 break;
138 case ParserState::kValueEnd:
139 if (c == '\n') {
140 result[currentKey] = currentValue;
141 currentKey = "";
142 currentValue = "";
143 currentValueWhitespace = "";
144 state = ParserState::kKeyStart;
145 } else {
146 return nullptr;
147 }
148 break;
149 }
150 }
151
152 // We want to be forgiving of a missing trailing new line, so if we're in state kValue, we emit the last value.
153 // This also resets the parser state to allow us to perform a final integrity check.
154 if (state == ParserState::kValueStart || state == ParserState::kValue || state == ParserState::kValueWhitespace) {
155 result[currentKey] = currentValue;
156 state = ParserState::kKeyStart;
157 }
158
159 // Ensure that we've seen a complete set of lines.
160 if (state != ParserState::kKeyStart) {
161 return nullptr;
162 }
163
164 return std::unique_ptr<std::unordered_map<std::string, std::string>>(
165 new std::unordered_map<std::string, std::string>(std::move(result))
166 );
167}
std::string serialize(const std::unordered_map< std::string, std::string > &contents)
Outputs simple flat ini data.
Definition: ini.cc:30
std::unique_ptr< std::unordered_map< std::string, std::string > > deserialize(const std::string contents)
Simple parser for flat ini data.
Definition: ini.cc:49