CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
hrydgard

CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!

GitHub Repository: hrydgard/ppsspp
Path: blob/master/Common/Data/Format/IniFile.cpp
Views: 1401
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
8
#include <inttypes.h>
9
10
// Hm, what's this for?
11
#ifndef _MSC_VER
12
#include <strings.h>
13
#endif
14
15
#include <algorithm>
16
#include <iostream>
17
#include <fstream>
18
#include <sstream>
19
#include <string>
20
#include <vector>
21
22
#include "Common/Data/Format/IniFile.h"
23
#include "Common/Data/Text/Parsers.h"
24
#include "Common/File/VFS/VFS.h"
25
#include "Common/File/FileUtil.h"
26
#include "Common/Log.h"
27
#include "Common/Math/math_util.h"
28
29
#include "Common/StringUtils.h"
30
31
// This unescapes # signs.
32
// NOTE: These parse functions can make better use of the string_view - the pos argument should not be needed, for example.
33
static bool ParseLineKey(std::string_view line, size_t &pos, std::string *keyOut) {
34
std::string key = "";
35
36
while (pos < line.size()) {
37
size_t next = line.find_first_of("=#", pos);
38
if (next == line.npos || next == 0) {
39
// Key never ended or empty, invalid.
40
return false;
41
} else if (line[next] == '#') {
42
if (line[next - 1] != '\\') {
43
// Value commented out before =, so not valid.
44
return false;
45
}
46
47
// Escaped.
48
key += line.substr(pos, next - pos - 1);
49
key.push_back('#');
50
pos = next + 1;
51
} else if (line[next] == '=') {
52
// Hurray, done.
53
key += line.substr(pos, next - pos);
54
pos = next + 1;
55
break;
56
}
57
}
58
59
if (keyOut) {
60
*keyOut = StripSpaces(key);
61
}
62
return true;
63
}
64
65
static bool ParseLineValue(std::string_view line, size_t &pos, std::string *valueOut) {
66
std::string value = "";
67
68
std::string_view strippedLine = StripSpaces(line.substr(pos));
69
if (strippedLine.size() >= 2 && strippedLine[0] == '"' && strippedLine[strippedLine.size() - 1] == '"') {
70
// Don't remove comment if is surrounded by " "
71
value += line.substr(pos);
72
pos = line.npos; // Won't enter the while below
73
}
74
75
while (pos < line.size()) {
76
size_t next = line.find('#', pos);
77
if (next == line.npos) {
78
value += line.substr(pos);
79
pos = line.npos;
80
break;
81
} else if (line[next - 1] != '\\') {
82
// It wasn't escaped, so finish before the #.
83
value += line.substr(pos, next - pos);
84
// Include the comment's # in pos.
85
pos = next;
86
break;
87
} else {
88
// Escaped.
89
value += line.substr(pos, next - pos - 1);
90
value.push_back('#');
91
pos = next + 1;
92
}
93
}
94
95
if (valueOut) {
96
*valueOut = StripQuotes(StripSpaces(value));
97
}
98
99
return true;
100
}
101
102
static bool ParseLineComment(std::string_view line, size_t &pos, std::string *commentOut) {
103
// Don't bother with anything if we don't need the comment data.
104
if (commentOut) {
105
// Include any whitespace/formatting in the comment.
106
size_t commentStartPos = pos;
107
if (commentStartPos != line.npos) {
108
while (commentStartPos > 0 && line[commentStartPos - 1] <= ' ') {
109
--commentStartPos;
110
}
111
112
*commentOut = line.substr(commentStartPos);
113
} else {
114
// There was no comment.
115
commentOut->clear();
116
}
117
}
118
119
pos = line.npos;
120
return true;
121
}
122
123
static bool ParseLine(std::string_view line, std::string* keyOut, std::string* valueOut, std::string* commentOut)
124
{
125
// Rules:
126
// 1. A line starting with ; is commented out.
127
// 2. A # in a line (and all the space before it) is the comment.
128
// 3. A \# in a line is not part of a comment and becomes # in the value.
129
// 4. Whitespace around values is removed.
130
// 5. Double quotes around values is removed.
131
// 6. Value surrounded by double quotes don't parsed to strip comment.
132
133
if (line.size() < 2 || line[0] == ';')
134
return false;
135
136
size_t pos = 0;
137
if (!ParseLineKey(line, pos, keyOut))
138
return false;
139
if (!ParseLineValue(line, pos, valueOut))
140
return false;
141
if (!ParseLineComment(line, pos, commentOut))
142
return false;
143
144
return true;
145
}
146
147
static std::string EscapeHash(std::string_view value) {
148
std::string result = "";
149
150
for (size_t pos = 0; pos < value.size(); ) {
151
size_t next = value.find('#', pos);
152
if (next == value.npos) {
153
result += value.substr(pos);
154
pos = value.npos;
155
} else {
156
result += value.substr(pos, next - pos);
157
result += "\\#";
158
pos = next + 1;
159
}
160
}
161
162
return result;
163
}
164
165
void ParsedIniLine::ParseFrom(std::string_view line) {
166
line = StripSpaces(line);
167
if (line.empty()) {
168
key.clear();
169
value.clear();
170
comment.clear();
171
} else if (line[0] == '#') {
172
key.clear();
173
value.clear();
174
comment = line;
175
} else {
176
ParseLine(line, &key, &value, &comment);
177
}
178
}
179
180
void ParsedIniLine::Reconstruct(std::string *output) const {
181
if (!key.empty()) {
182
*output = EscapeHash(key) + " = " + EscapeHash(value) + comment;
183
} else {
184
*output = comment;
185
}
186
}
187
188
void Section::Clear() {
189
lines_.clear();
190
}
191
192
bool Section::GetKeys(std::vector<std::string> &keys) const {
193
keys.clear();
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, (const char *)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, (const char *)temp);
227
}
228
229
void Section::Set(std::string_view key, float newValue) {
230
_dbg_assert_(!my_isnanorinf(newValue));
231
char temp[128];
232
snprintf(temp, sizeof(temp), "%f", newValue);
233
Set(key, (const char *)temp);
234
}
235
236
void Section::Set(std::string_view key, double newValue) {
237
char temp[128];
238
snprintf(temp, sizeof(temp), "%f", newValue);
239
Set(key, (const char *)temp);
240
}
241
242
void Section::Set(std::string_view key, int newValue) {
243
char temp[128];
244
snprintf(temp, sizeof(temp), "%d", newValue);
245
Set(key, (const char *)temp);
246
}
247
248
void Section::Set(std::string_view key, const char* 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, const std::string& newValue, const std::string& defaultValue)
259
{
260
if (newValue != defaultValue)
261
Set(key, newValue);
262
else
263
Delete(key);
264
}
265
266
bool Section::Get(std::string_view key, std::string* value, const char* defaultValue) const {
267
const ParsedIniLine *line = GetLine(key);
268
if (!line) {
269
if (defaultValue) {
270
*value = defaultValue;
271
}
272
return false;
273
} else {
274
*value = line->Value();
275
}
276
return true;
277
}
278
279
void Section::Set(std::string_view key, const float newValue, const float defaultValue)
280
{
281
if (newValue != defaultValue)
282
Set(key, newValue);
283
else
284
Delete(key);
285
}
286
287
void Section::Set(std::string_view key, int newValue, int defaultValue)
288
{
289
if (newValue != defaultValue)
290
Set(key, newValue);
291
else
292
Delete(key);
293
}
294
295
void Section::Set(std::string_view key, bool newValue, bool defaultValue)
296
{
297
if (newValue != defaultValue)
298
Set(key, newValue);
299
else
300
Delete(key);
301
}
302
303
void Section::Set(std::string_view key, const std::vector<std::string>& newValues)
304
{
305
std::string temp;
306
// Join the strings with ,
307
for (const auto &value : newValues) {
308
temp += value + ",";
309
}
310
// remove last ,
311
if (temp.length())
312
temp.resize(temp.length() - 1);
313
Set(key, temp.c_str());
314
}
315
316
void Section::AddComment(const std::string &comment) {
317
lines_.emplace_back(ParsedIniLine::CommentOnly("# " + comment));
318
}
319
320
bool Section::Get(std::string_view key, std::vector<std::string>& values) const {
321
std::string temp;
322
bool retval = Get(key, &temp, 0);
323
if (!retval || temp.empty()) {
324
return false;
325
}
326
// ignore starting , if any
327
size_t subStart = temp.find_first_not_of(',');
328
size_t subEnd;
329
330
// split by ,
331
while (subStart != std::string::npos) {
332
// Find next ,
333
subEnd = temp.find_first_of(',', subStart);
334
if (subStart != subEnd)
335
// take from first char until next ,
336
values.push_back(StripSpaces(temp.substr(subStart, subEnd - subStart)));
337
338
// Find the next non , char
339
subStart = temp.find_first_not_of(',', subEnd);
340
}
341
342
return true;
343
}
344
345
bool Section::Get(std::string_view key, int* value, int defaultValue) const {
346
std::string temp;
347
bool retval = Get(key, &temp, 0);
348
if (retval && TryParse(temp, value))
349
return true;
350
*value = defaultValue;
351
return false;
352
}
353
354
bool Section::Get(std::string_view key, uint32_t* value, uint32_t defaultValue) const {
355
std::string temp;
356
bool retval = Get(key, &temp, 0);
357
if (retval && TryParse(temp, value))
358
return true;
359
*value = defaultValue;
360
return false;
361
}
362
363
bool Section::Get(std::string_view key, uint64_t* value, uint64_t defaultValue) const {
364
std::string temp;
365
bool retval = Get(key, &temp, 0);
366
if (retval && TryParse(temp, value))
367
return true;
368
*value = defaultValue;
369
return false;
370
}
371
372
bool Section::Get(std::string_view key, bool* value, bool defaultValue) const {
373
std::string temp;
374
bool retval = Get(key, &temp, 0);
375
if (retval && TryParse(temp, value))
376
return true;
377
*value = defaultValue;
378
return false;
379
}
380
381
bool Section::Get(std::string_view key, float* value, float defaultValue) const {
382
std::string temp;
383
bool retval = Get(key, &temp, 0);
384
if (retval && TryParse(temp, value))
385
return true;
386
*value = defaultValue;
387
return false;
388
}
389
390
bool Section::Get(std::string_view key, double* value, double defaultValue) const {
391
std::string temp;
392
bool retval = Get(key, &temp, 0);
393
if (retval && TryParse(temp, value))
394
return true;
395
*value = defaultValue;
396
return false;
397
}
398
399
bool Section::Exists(std::string_view key) const {
400
for (auto &line : lines_) {
401
if (equalsNoCase(key, line.Key()))
402
return true;
403
}
404
return false;
405
}
406
407
std::map<std::string, std::string> Section::ToMap() const {
408
std::map<std::string, std::string> outMap;
409
for (auto &line : lines_) {
410
if (!line.Key().empty()) {
411
outMap[std::string(line.Key())] = line.Value();
412
}
413
}
414
return outMap;
415
}
416
417
bool Section::Delete(std::string_view key) {
418
ParsedIniLine *line = GetLine(key);
419
for (auto liter = lines_.begin(); liter != lines_.end(); ++liter) {
420
if (line == &*liter) {
421
lines_.erase(liter);
422
return true;
423
}
424
}
425
return false;
426
}
427
428
// IniFile
429
430
const Section* IniFile::GetSection(const char* sectionName) const {
431
for (const auto &iter : sections)
432
if (!strcasecmp(iter->name().c_str(), sectionName))
433
return iter.get();
434
return nullptr;
435
}
436
437
Section* IniFile::GetSection(const char* sectionName) {
438
for (const auto &iter : sections)
439
if (!strcasecmp(iter->name().c_str(), sectionName))
440
return iter.get();
441
return nullptr;
442
}
443
444
Section* IniFile::GetOrCreateSection(const char* sectionName) {
445
Section* section = GetSection(sectionName);
446
if (!section) {
447
sections.push_back(std::make_unique<Section>(sectionName));
448
section = sections.back().get();
449
}
450
return section;
451
}
452
453
bool IniFile::DeleteSection(const char* sectionName) {
454
Section* s = GetSection(sectionName);
455
if (!s)
456
return false;
457
458
for (auto iter = sections.begin(); iter != sections.end(); ++iter) {
459
if (iter->get() == s) {
460
sections.erase(iter);
461
return true;
462
}
463
}
464
return false;
465
}
466
467
bool IniFile::Exists(const char* sectionName, const char* key) const {
468
const Section* section = GetSection(sectionName);
469
if (!section)
470
return false;
471
return section->Exists(key);
472
}
473
474
bool IniFile::DeleteKey(const char* sectionName, const char* key) {
475
Section* section = GetSection(sectionName);
476
if (!section)
477
return false;
478
ParsedIniLine *line = section->GetLine(key);
479
for (auto liter = section->lines_.begin(); liter != section->lines_.end(); ++liter) {
480
if (line == &(*liter)) {
481
section->lines_.erase(liter);
482
return true;
483
}
484
}
485
return false; //shouldn't happen
486
}
487
488
// Return a list of all keys in a section
489
bool IniFile::GetKeys(const char* sectionName, std::vector<std::string>& keys) const {
490
const Section *section = GetSection(sectionName);
491
if (!section)
492
return false;
493
return section->GetKeys(keys);
494
}
495
496
void IniFile::SortSections()
497
{
498
std::sort(sections.begin(), sections.end());
499
}
500
501
bool IniFile::Load(const Path &path)
502
{
503
sections.clear();
504
sections.push_back(std::make_unique<Section>(""));
505
// first section consists of the comments before the first real section
506
507
// Open file
508
std::string data;
509
if (!File::ReadTextFileToString(path, &data)) {
510
return false;
511
}
512
std::stringstream sstream(data);
513
bool success = Load(sstream);
514
return success;
515
}
516
517
bool IniFile::LoadFromVFS(VFSInterface &vfs, const std::string &filename) {
518
size_t size;
519
uint8_t *data = vfs.ReadFile(filename.c_str(), &size);
520
if (!data)
521
return false;
522
std::string str((const char*)data, size);
523
delete [] data;
524
525
std::stringstream sstream(str);
526
return Load(sstream);
527
}
528
529
bool IniFile::Load(std::istream &in) {
530
// Maximum number of letters in a line
531
static const int MAX_BYTES = 1024*32;
532
char *templine = new char[MAX_BYTES]; // avoid using up massive stack space
533
534
while (!(in.eof() || in.fail()))
535
{
536
in.getline(templine, MAX_BYTES);
537
std::string line = templine;
538
539
// Remove UTF-8 byte order marks.
540
if (line.substr(0, 3) == "\xEF\xBB\xBF") {
541
line = line.substr(3);
542
}
543
544
#ifndef _WIN32
545
// Check for CRLF eol and convert it to LF
546
if (!line.empty() && line.at(line.size()-1) == '\r') {
547
line.erase(line.size()-1);
548
}
549
#endif
550
551
if (!line.empty()) {
552
size_t sectionNameEnd = std::string::npos;
553
if (line[0] == '[') {
554
sectionNameEnd = line.find(']');
555
}
556
557
if (sectionNameEnd != std::string::npos) {
558
// New section!
559
std::string sub = line.substr(1, sectionNameEnd - 1);
560
sections.push_back(std::make_unique<Section>(sub));
561
562
if (sectionNameEnd + 1 < line.size()) {
563
sections.back()->comment = line.substr(sectionNameEnd + 1);
564
}
565
} else {
566
if (sections.empty()) {
567
sections.push_back(std::make_unique<Section>(""));
568
}
569
ParsedIniLine parsedLine;
570
parsedLine.ParseFrom(line);
571
sections.back()->lines_.push_back(parsedLine);
572
}
573
}
574
}
575
576
delete[] templine;
577
return true;
578
}
579
580
bool IniFile::Save(const Path &filename)
581
{
582
FILE *file = File::OpenCFile(filename, "w");
583
if (!file) {
584
return false;
585
}
586
587
// UTF-8 byte order mark. To make sure notepad doesn't go nuts.
588
// TODO: Do we still need this? It's annoying.
589
fprintf(file, "\xEF\xBB\xBF");
590
591
for (const auto &section : sections) {
592
if (!section->name().empty() && (!section->lines_.empty() || !section->comment.empty())) {
593
fprintf(file, "[%s]%s\n", section->name().c_str(), section->comment.c_str());
594
}
595
for (const auto &line : section->lines_) {
596
std::string buffer;
597
line.Reconstruct(&buffer);
598
fprintf(file, "%s\n", buffer.c_str());
599
}
600
}
601
602
fclose(file);
603
return true;
604
}
605
606
bool IniFile::Get(const char* sectionName, const char* key, std::string* value, const char* defaultValue)
607
{
608
Section* section = GetSection(sectionName);
609
if (!section) {
610
if (defaultValue) {
611
*value = defaultValue;
612
}
613
return false;
614
}
615
return section->Get(key, value, defaultValue);
616
}
617
618
bool IniFile::Get(const char *sectionName, const char* key, std::vector<std::string>& values)
619
{
620
Section *section = GetSection(sectionName);
621
if (!section)
622
return false;
623
return section->Get(key, values);
624
}
625
626
bool IniFile::Get(const char* sectionName, const char* key, int* value, int defaultValue)
627
{
628
Section *section = GetSection(sectionName);
629
if (!section) {
630
*value = defaultValue;
631
return false;
632
} else {
633
return section->Get(key, value, defaultValue);
634
}
635
}
636
637
bool IniFile::Get(const char* sectionName, const char* key, uint32_t* value, uint32_t defaultValue)
638
{
639
Section *section = GetSection(sectionName);
640
if (!section) {
641
*value = defaultValue;
642
return false;
643
} else {
644
return section->Get(key, value, defaultValue);
645
}
646
}
647
648
bool IniFile::Get(const char* sectionName, const char* key, uint64_t* value, uint64_t defaultValue)
649
{
650
Section *section = GetSection(sectionName);
651
if (!section) {
652
*value = defaultValue;
653
return false;
654
} else {
655
return section->Get(key, value, defaultValue);
656
}
657
}
658
659
bool IniFile::Get(const char* sectionName, const char* key, bool* value, bool defaultValue)
660
{
661
Section *section = GetSection(sectionName);
662
if (!section) {
663
*value = defaultValue;
664
return false;
665
} else {
666
return section->Get(key, value, defaultValue);
667
}
668
}
669
670