Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
hrydgard
GitHub Repository: hrydgard/ppsspp
Path: blob/master/Common/Data/Format/IniFile.cpp
5657 views
1
// IniFile
2
// Taken from Dolphin but relicensed by me, Henrik Rydgard, under the MIT
3
// license as I wrote the whole thing originally and it has barely changed.
4
5
#include <cstdlib>
6
#include <cstdio>
7
#include <algorithm> // for sort
8
9
#include <inttypes.h>
10
11
// Hm, what's this for?
12
#ifndef _MSC_VER
13
#include <strings.h>
14
#endif
15
16
#include <iostream>
17
#include <sstream>
18
#include <string>
19
#include <vector>
20
21
#include "Common/Data/Format/IniFile.h"
22
#include "Common/Data/Text/Parsers.h"
23
#include "Common/File/VFS/VFS.h"
24
#include "Common/File/FileUtil.h"
25
#include "Common/Log.h"
26
#include "Common/Math/math_util.h"
27
28
#include "Common/StringUtils.h"
29
30
// This unescapes # signs.
31
// NOTE: These parse functions can make better use of the string_view - the pos argument should not be needed, for example.
32
static bool ParseLineKey(std::string_view line, size_t &pos, std::string *keyOut) {
33
std::string key = "";
34
35
while (pos < line.size()) {
36
size_t next = line.find_first_of("=#", pos);
37
if (next == line.npos || next == 0) {
38
// Key never ended or empty, invalid.
39
return false;
40
} else if (line[next] == '#') {
41
if (line[next - 1] != '\\') {
42
// Value commented out before =, so not valid.
43
return false;
44
}
45
46
// Escaped.
47
key += line.substr(pos, next - pos - 1);
48
key.push_back('#');
49
pos = next + 1;
50
} else if (line[next] == '=') {
51
// Hurray, done.
52
key += line.substr(pos, next - pos);
53
pos = next + 1;
54
break;
55
}
56
}
57
58
if (keyOut) {
59
*keyOut = StripSpaces(key);
60
}
61
return true;
62
}
63
64
static bool ParseLineValue(std::string_view line, size_t &pos, std::string *valueOut) {
65
std::string value;
66
67
std::string_view strippedLine = StripSpaces(line.substr(pos));
68
if (strippedLine.size() >= 2 && strippedLine[0] == '"' && strippedLine[strippedLine.size() - 1] == '"') {
69
// Don't remove comment if is surrounded by " "
70
value += line.substr(pos);
71
pos = line.npos; // Won't enter the while below
72
}
73
74
while (pos < line.size()) {
75
size_t next = line.find('#', pos);
76
if (next == line.npos) {
77
value += line.substr(pos);
78
pos = line.npos;
79
break;
80
} else if (line[next - 1] != '\\') {
81
// It wasn't escaped, so finish before the #.
82
value += line.substr(pos, next - pos);
83
// Include the comment's # in pos.
84
pos = next;
85
break;
86
} else {
87
// Escaped.
88
value += line.substr(pos, next - pos - 1);
89
value.push_back('#');
90
pos = next + 1;
91
}
92
}
93
94
if (valueOut) {
95
*valueOut = StripQuotes(StripSpaces(value));
96
}
97
98
return true;
99
}
100
101
static bool ParseLineComment(std::string_view line, size_t &pos, std::string *commentOut) {
102
// Don't bother with anything if we don't need the comment data.
103
if (commentOut) {
104
// Include any whitespace/formatting in the comment.
105
size_t commentStartPos = pos;
106
if (commentStartPos != line.npos) {
107
while (commentStartPos > 0 && line[commentStartPos - 1] <= ' ') {
108
--commentStartPos;
109
}
110
111
*commentOut = line.substr(commentStartPos);
112
} else {
113
// There was no comment.
114
commentOut->clear();
115
}
116
}
117
118
pos = line.npos;
119
return true;
120
}
121
122
static bool ParseLine(std::string_view line, std::string* keyOut, std::string* valueOut, std::string* commentOut)
123
{
124
// Rules:
125
// 1. A line starting with ; is commented out.
126
// 2. A # in a line (and all the space before it) is the comment.
127
// 3. A \# in a line is not part of a comment and becomes # in the value.
128
// 4. Whitespace around values is removed.
129
// 5. Double quotes around values is removed.
130
// 6. Value surrounded by double quotes don't parsed to strip comment.
131
132
if (line.size() < 2 || line[0] == ';')
133
return false;
134
135
size_t pos = 0;
136
if (!ParseLineKey(line, pos, keyOut))
137
return false;
138
if (!ParseLineValue(line, pos, valueOut))
139
return false;
140
if (!ParseLineComment(line, pos, commentOut))
141
return false;
142
143
return true;
144
}
145
146
static std::string EscapeHash(std::string_view value) {
147
std::string result = "";
148
149
for (size_t pos = 0; pos < value.size(); ) {
150
size_t next = value.find('#', pos);
151
if (next == value.npos) {
152
result += value.substr(pos);
153
pos = value.npos;
154
} else {
155
result += value.substr(pos, next - pos);
156
result += "\\#";
157
pos = next + 1;
158
}
159
}
160
161
return result;
162
}
163
164
ParsedIniLine::ParsedIniLine(std::string_view line) {
165
line = StripSpaces(line);
166
if (line.empty()) {
167
key.clear();
168
value.clear();
169
comment.clear();
170
} else if (line[0] == '#') {
171
key.clear();
172
value.clear();
173
comment = line;
174
} else {
175
ParseLine(line, &key, &value, &comment);
176
}
177
}
178
179
void ParsedIniLine::Reconstruct(std::string *output) const {
180
if (!key.empty()) {
181
*output = EscapeHash(key) + " = " + EscapeHash(value) + comment;
182
} else {
183
*output = comment;
184
}
185
}
186
187
void Section::Clear() {
188
lines_.clear();
189
}
190
191
bool Section::GetKeys(std::vector<std::string> *keys) const {
192
keys->clear();
193
keys->reserve(lines_.size());
194
for (const auto &line : lines_) {
195
if (!line.Key().empty())
196
keys->emplace_back(line.Key());
197
}
198
return true;
199
}
200
201
ParsedIniLine *Section::GetLine(std::string_view key) {
202
for (auto &line : lines_) {
203
if (equalsNoCase(line.Key(), key))
204
return &line;
205
}
206
return nullptr;
207
}
208
209
const ParsedIniLine *Section::GetLine(std::string_view key) const {
210
for (auto &line : lines_) {
211
if (equalsNoCase(line.Key(), key))
212
return &line;
213
}
214
return nullptr;
215
}
216
217
void Section::Set(std::string_view key, uint32_t newValue) {
218
char temp[128];
219
snprintf(temp, sizeof(temp), "0x%08x", newValue);
220
Set(key, std::string_view(temp));
221
}
222
223
void Section::Set(std::string_view key, uint64_t newValue) {
224
char temp[128];
225
snprintf(temp, sizeof(temp), "0x%016" PRIx64, newValue);
226
Set(key, std::string_view(temp));
227
}
228
229
void Section::Set(std::string_view key, float newValue) {
230
_dbg_assert_(!my_isnanorinf(newValue));
231
char temp[64];
232
snprintf(temp, sizeof(temp), "%f", newValue);
233
Set(key, std::string_view(temp));
234
}
235
236
void Section::Set(std::string_view key, double newValue) {
237
char temp[64];
238
snprintf(temp, sizeof(temp), "%f", newValue);
239
Set(key, std::string_view(temp));
240
}
241
242
void Section::Set(std::string_view key, int newValue) {
243
char temp[32];
244
snprintf(temp, sizeof(temp), "%d", newValue);
245
Set(key, std::string_view(temp));
246
}
247
248
void Section::Set(std::string_view key, std::string_view newValue) {
249
ParsedIniLine *line = GetLine(key);
250
if (line) {
251
line->SetValue(newValue);
252
} else {
253
// The key did not already exist in this section - let's add it.
254
lines_.emplace_back(ParsedIniLine(key, newValue));
255
}
256
}
257
258
void Section::Set(std::string_view key, std::string_view newValue, std::string_view defaultValue) {
259
if (newValue != defaultValue)
260
Set(key, newValue);
261
else
262
Delete(key);
263
}
264
265
bool Section::Get(std::string_view key, std::string *value) const {
266
const ParsedIniLine *line = GetLine(key);
267
if (!line) {
268
return false;
269
}
270
*value = line->Value();
271
return true;
272
}
273
274
void Section::Set(std::string_view key, const float newValue, const float defaultValue)
275
{
276
if (newValue != defaultValue)
277
Set(key, newValue);
278
else
279
Delete(key);
280
}
281
282
void Section::Set(std::string_view key, int newValue, int defaultValue)
283
{
284
if (newValue != defaultValue)
285
Set(key, newValue);
286
else
287
Delete(key);
288
}
289
290
void Section::Set(std::string_view key, bool newValue, bool defaultValue)
291
{
292
if (newValue != defaultValue)
293
Set(key, newValue);
294
else
295
Delete(key);
296
}
297
298
void Section::Set(std::string_view key, const std::vector<std::string> &newValues) {
299
std::string temp;
300
// Join the strings with ,
301
for (const auto &value : newValues) {
302
temp += value;
303
temp.push_back(',');
304
}
305
// remove last ,
306
if (!temp.empty())
307
temp.pop_back();
308
Set(key, temp.c_str());
309
}
310
311
bool Section::Get(std::string_view key, std::vector<std::string> *values) const {
312
std::string temp;
313
bool retval = Get(key, &temp);
314
if (!retval) {
315
return false;
316
}
317
SplitString(temp, ',', *values, true);
318
return true;
319
}
320
321
bool Section::Get(std::string_view key, int *value) const {
322
std::string temp;
323
bool retval = Get(key, &temp);
324
if (retval && TryParse(temp, value))
325
return true;
326
return false;
327
}
328
329
bool Section::Get(std::string_view key, uint32_t *value) const {
330
std::string temp;
331
bool retval = Get(key, &temp);
332
if (retval && TryParse(temp, value))
333
return true;
334
return false;
335
}
336
337
bool Section::Get(std::string_view key, uint64_t *value) const {
338
std::string temp;
339
bool retval = Get(key, &temp);
340
if (retval && TryParse(temp, value))
341
return true;
342
return false;
343
}
344
345
bool Section::Get(std::string_view key, bool *value) const {
346
std::string temp;
347
bool retval = Get(key, &temp);
348
if (retval && TryParse(temp, value))
349
return true;
350
return false;
351
}
352
353
bool Section::Get(std::string_view key, float *value) const {
354
std::string temp;
355
bool retval = Get(key, &temp);
356
if (retval && TryParse(temp, value))
357
return true;
358
return false;
359
}
360
361
bool Section::Get(std::string_view key, double* value) const {
362
std::string temp;
363
bool retval = Get(key, &temp);
364
if (retval && TryParse(temp, value))
365
return true;
366
return false;
367
}
368
369
bool Section::HasKey(std::string_view key) const {
370
for (auto &line : lines_) {
371
if (equalsNoCase(key, line.Key()))
372
return true;
373
}
374
return false;
375
}
376
377
void Section::AddComment(std::string_view comment) {
378
lines_.emplace_back(ParsedIniLine::CommentOnly("# " + std::string(comment)));
379
}
380
381
std::map<std::string, std::string> Section::ToMap() const {
382
std::map<std::string, std::string> outMap;
383
for (auto &line : lines_) {
384
if (!line.Key().empty()) {
385
outMap.emplace(line.Key(), line.Value());
386
}
387
}
388
return outMap;
389
}
390
391
bool Section::Delete(std::string_view key) {
392
ParsedIniLine *line = GetLine(key);
393
for (auto liter = lines_.begin(); liter != lines_.end(); ++liter) {
394
if (line == &*liter) {
395
lines_.erase(liter);
396
return true;
397
}
398
}
399
return false;
400
}
401
402
// IniFile
403
404
const Section *IniFile::GetSection(std::string_view sectionName) const {
405
for (const auto &iter : sections)
406
if (equalsNoCase(iter->name(), sectionName))
407
return iter.get();
408
return nullptr;
409
}
410
411
Section* IniFile::GetSection(std::string_view sectionName) {
412
for (const auto &iter : sections)
413
if (equalsNoCase(iter->name(), sectionName))
414
return iter.get();
415
return nullptr;
416
}
417
418
Section* IniFile::GetOrCreateSection(std::string_view sectionName) {
419
Section *section = GetSection(sectionName);
420
if (!section) {
421
sections.push_back(std::make_unique<Section>(sectionName));
422
section = sections.back().get();
423
}
424
return section;
425
}
426
427
bool IniFile::DeleteSection(std::string_view sectionName) {
428
Section* s = GetSection(sectionName);
429
if (!s)
430
return false;
431
432
for (auto iter = sections.begin(); iter != sections.end(); ++iter) {
433
if (iter->get() == s) {
434
sections.erase(iter);
435
return true;
436
}
437
}
438
return false;
439
}
440
441
void IniFile::SortSections() {
442
std::sort(sections.begin(), sections.end());
443
}
444
445
bool IniFile::Load(const Path &path) {
446
sections.clear();
447
sections.push_back(std::make_unique<Section>(""));
448
// first section consists of the comments before the first real section
449
450
// Open file
451
std::string data;
452
if (!File::ReadTextFileToString(path, &data)) {
453
return false;
454
}
455
std::stringstream sstream(data);
456
bool success = Load(sstream);
457
return success;
458
}
459
460
bool IniFile::LoadFromVFS(VFSInterface &vfs, const std::string &filename) {
461
size_t size;
462
uint8_t *data = vfs.ReadFile(filename.c_str(), &size);
463
if (!data)
464
return false;
465
std::string str((const char*)data, size);
466
delete [] data;
467
468
std::stringstream sstream(str);
469
return Load(sstream);
470
}
471
472
bool IniFile::Load(std::istream &in) {
473
std::string linebuf;
474
475
while (std::getline(in, linebuf)) {
476
std::string_view line = StripSpaces(std::string_view(linebuf));
477
// Remove UTF-8 byte order marks.
478
if (line.substr(0, 3) == "\xEF\xBB\xBF") {
479
line = line.substr(3);
480
}
481
482
#ifndef _WIN32
483
// Check for CRLF eol and convert it to LF
484
if (!line.empty() && line.at(line.size() - 1) == '\r') {
485
line = line.substr(0, line.size() - 1);
486
}
487
#endif
488
489
if (!line.empty()) {
490
size_t sectionNameEnd = std::string::npos;
491
if (line[0] == '[') {
492
sectionNameEnd = line.find(']');
493
}
494
495
if (sectionNameEnd != std::string::npos) {
496
// New section!
497
std::string_view sub = line.substr(1, sectionNameEnd - 1);
498
sections.push_back(std::make_unique<Section>(sub));
499
500
if (sectionNameEnd + 1 < line.size()) {
501
sections.back()->comment = line.substr(sectionNameEnd + 1);
502
}
503
} else {
504
if (sections.empty()) {
505
sections.push_back(std::make_unique<Section>(""));
506
}
507
sections.back()->lines_.emplace_back(line);
508
}
509
}
510
}
511
512
return true;
513
}
514
515
bool IniFile::Save(const Path &filename)
516
{
517
FILE *file = File::OpenCFile(filename, "w");
518
if (!file) {
519
return false;
520
}
521
522
// UTF-8 byte order mark. To make sure notepad doesn't go nuts.
523
// TODO: Do we still need this? It's annoying.
524
fprintf(file, "\xEF\xBB\xBF");
525
526
for (const auto &section : sections) {
527
if (!section->name().empty() && (!section->lines_.empty() || !section->comment.empty())) {
528
fprintf(file, "[%s]%s\n", section->name().c_str(), section->comment.c_str());
529
}
530
for (const auto &line : section->lines_) {
531
std::string buffer;
532
line.Reconstruct(&buffer);
533
fprintf(file, "%s\n", buffer.c_str());
534
}
535
}
536
537
fclose(file);
538
return true;
539
}
540
541