1 /*
2 * This file is part of Dune Legacy.
3 *
4 * Dune Legacy is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * Dune Legacy is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with Dune Legacy. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18 #include <FileClasses/INIFile.h>
19
20 #include <misc/exceptions.h>
21
22 #include <fstream>
23 #include <iostream>
24 #include <cctype>
25 #include <algorithm>
26 #include <stdio.h>
27
28
INIFileLine(const std::string & completeLine,int lineNumber)29 INIFile::INIFileLine::INIFileLine(const std::string& completeLine, int lineNumber)
30 : completeLine(completeLine), line(lineNumber), nextLine(nullptr), prevLine(nullptr) {
31 }
32
Key(const std::string & completeLine,int lineNumber,int keystringbegin,int keystringlength,int valuestringbegin,int valuestringlength)33 INIFile::Key::Key(const std::string& completeLine, int lineNumber, int keystringbegin, int keystringlength, int valuestringbegin, int valuestringlength)
34 : INIFileLine(completeLine, lineNumber), keyStringBegin(keystringbegin), keyStringLength(keystringlength),
35 valueStringBegin(valuestringbegin), valueStringLength(valuestringlength),
36 nextKey(nullptr), prevKey(nullptr) {
37 }
38
Key(const std::string & keyname,const std::string & value,bool bEscapeIfNeeded,bool bWhitespace)39 INIFile::Key::Key(const std::string& keyname, const std::string& value, bool bEscapeIfNeeded, bool bWhitespace)
40 : INIFileLine(keyname + (bWhitespace ? " = " : "=") + (bEscapeIfNeeded ? escapeValue(value) : value), INVALID_LINE), keyStringBegin(0), keyStringLength(keyname.size()),
41 valueStringBegin(keyname.size() + (bWhitespace ? 3 : 1) + (escapingValueNeeded(value) ? 1 : 0)), valueStringLength(value.size()),
42 nextKey(nullptr), prevKey(nullptr) {
43 }
44
getKeyName() const45 std::string INIFile::Key::getKeyName() const {
46 return completeLine.substr(keyStringBegin,keyStringLength);
47 }
48
getStringValue() const49 std::string INIFile::Key::getStringValue() const {
50 return completeLine.substr(valueStringBegin,valueStringLength);
51 }
52
getIntValue(int defaultValue) const53 int INIFile::Key::getIntValue(int defaultValue) const {
54 std::string value = getStringValue();
55 if(value.size() == 0) {
56 return defaultValue;
57 }
58
59 long ret;
60 if(value.at(0) == '-') {
61 ret = -(atol(value.c_str()+1));
62 } else if (value.at(0) == '+') {
63 ret = atol(value.c_str()+1);
64 } else {
65 ret = atol(value.c_str());
66 }
67
68 return ret;
69 }
70
getBoolValue(bool defaultValue) const71 bool INIFile::Key::getBoolValue(bool defaultValue) const {
72 std::string value = getStringValue();
73 if(value.size() == 0) {
74 return defaultValue;
75 }
76
77 // convert string to lower case
78 std::transform(value.begin(),value.end(), value.begin(), (int(*)(int)) tolower);
79
80 if((value == "true") || (value == "enabled") || (value == "on") || (value == "1")) {
81 return true;
82 } else if((value == "false") || (value == "disabled") || (value == "off") || (value == "0")) {
83 return false;
84 } else {
85 return defaultValue;
86 }
87 }
88
getFloatValue(float defaultValue) const89 float INIFile::Key::getFloatValue(float defaultValue) const {
90 std::string value = getStringValue();
91 if(value.size() == 0) {
92 return defaultValue;
93 }
94
95 float ret = strtof(value.c_str(), nullptr);
96
97 return ret;
98 }
99
getDoubleValue(double defaultValue) const100 double INIFile::Key::getDoubleValue(double defaultValue) const {
101 std::string value = getStringValue();
102 if(value.size() == 0) {
103 return defaultValue;
104 }
105
106 double ret = strtod(value.c_str(), nullptr);
107
108 return ret;
109 }
110
setStringValue(const std::string & newValue,bool bEscapeIfNeeded)111 void INIFile::Key::setStringValue(const std::string& newValue, bool bEscapeIfNeeded) {
112 if(completeLine[valueStringBegin-1] == '"') {
113 completeLine.replace(valueStringBegin-1,valueStringLength+2, bEscapeIfNeeded ? escapeValue(newValue) : newValue);
114 } else {
115 completeLine.replace(valueStringBegin,valueStringLength, bEscapeIfNeeded ? escapeValue(newValue) : newValue);
116 }
117 }
118
setIntValue(int newValue)119 void INIFile::Key::setIntValue(int newValue) {
120 char tmp[20];
121 sprintf(tmp,"%d",newValue);
122 setStringValue(tmp);
123 }
124
setBoolValue(bool newValue)125 void INIFile::Key::setBoolValue(bool newValue) {
126 if(newValue == true) {
127 setStringValue("true");
128 } else {
129 setStringValue("false");
130 }
131 }
132
setDoubleValue(double newValue)133 void INIFile::Key::setDoubleValue(double newValue) {
134 char tmp[30];
135 sprintf(tmp,"%f",newValue);
136 setStringValue(tmp);
137 }
138
escapingValueNeeded(const std::string & value)139 bool INIFile::Key::escapingValueNeeded(const std::string& value) {
140 if(value == "") {
141 return true;
142 } else {
143 // test for non normal char
144 for(unsigned int i = 0; i < value.size(); i++) {
145 if(!isNormalChar(value[i])) {
146 return true;
147 }
148 }
149 return false;
150 }
151 }
152
escapeValue(const std::string & value)153 std::string INIFile::Key::escapeValue(const std::string& value) {
154 if(escapingValueNeeded(value)) {
155 return "\"" + value + "\"";
156 } else {
157 return value;
158 }
159 }
160
161
Section(const std::string & completeLine,int lineNumber,int sectionstringbegin,int sectionstringlength,bool bWhitespace)162 INIFile::Section::Section(const std::string& completeLine, int lineNumber, int sectionstringbegin, int sectionstringlength, bool bWhitespace)
163 : INIFileLine(completeLine, lineNumber), sectionStringBegin(sectionstringbegin), sectionStringLength(sectionstringlength),
164 nextSection(nullptr), prevSection(nullptr), keyRoot(nullptr), bWhitespace(bWhitespace) {
165 }
166
Section(const std::string & sectionname,bool bWhitespace)167 INIFile::Section::Section(const std::string& sectionname, bool bWhitespace)
168 : INIFileLine("[" + sectionname + "]", INVALID_LINE), sectionStringBegin(1), sectionStringLength(sectionname.size()),
169 nextSection(nullptr), prevSection(nullptr), keyRoot(nullptr), bWhitespace(bWhitespace) {
170 }
171
172 /// Get the name for this section
173 /**
174 This method returns the name of this section
175 \return name of this section
176 */
getSectionName() const177 std::string INIFile::Section::getSectionName() const {
178 return completeLine.substr(sectionStringBegin, sectionStringLength);
179 }
180
181
182 /// Get a key iterator pointing at the first key in this section
183 /**
184 This method returns a key iterator pointing at the first key in this section.
185 \return the iterator
186 */
begin() const187 INIFile::KeyIterator INIFile::Section::begin() const {
188 return KeyIterator(keyRoot);
189 }
190
191 /// Get a key iterator pointing past the last key in this section
192 /**
193 This method returns a key iterator pointing past the last key in this section.
194 \return the iterator
195 */
end() const196 INIFile::KeyIterator INIFile::Section::end() const {
197 return KeyIterator();
198 }
199
200 /**
201 This method checks whether the specified key exists in this section.
202 \param key keyname
203 \return true, if the key exists, false otherwise
204 */
hasKey(const std::string & key) const205 bool INIFile::Section::hasKey(const std::string& key) const {
206 return (getKey(key) != nullptr);
207 }
208
getKey(const std::string & keyname) const209 INIFile::Key* INIFile::Section::getKey(const std::string& keyname) const {
210 for(INIFile::Key& key : *this) {
211 if(key.keyStringLength == (int) keyname.size()) {
212 if(strncicmp(keyname.c_str(), key.completeLine.c_str()+key.keyStringBegin, keyname.size()) == 0) {
213 return &key;
214 }
215 }
216 }
217
218 return nullptr;
219 }
220
221
setStringValue(const std::string & key,const std::string & newValue,bool bEscapeIfNeeded)222 void INIFile::Section::setStringValue(const std::string& key, const std::string& newValue, bool bEscapeIfNeeded) {
223 if(hasKey(key)) {
224 getKey(key)->setStringValue(newValue, bEscapeIfNeeded);
225 } else {
226 // create new key
227 if(isValidKeyName(key) == false) {
228 std::cerr << "INIFile: Cannot create key with name " << key << "!" << std::endl;
229 return;
230 }
231
232 Key* curKey = new Key(key, newValue, bEscapeIfNeeded, bWhitespace);
233 Key* pKey = keyRoot;
234 if(pKey == nullptr) {
235 // Section has no key yet
236 if(nextLine == nullptr) {
237 // no line after this section declaration
238 nextLine = curKey;
239 curKey->prevLine = this;
240 curKey->line = line+1;
241 } else {
242 // lines after this section declaration
243 nextLine->prevLine = curKey;
244 curKey->nextLine = nextLine;
245 nextLine = curKey;
246 curKey->prevLine = this;
247
248 curKey->line = line+1;
249 curKey->nextLine->shiftLineNumber(1);
250 }
251 } else {
252 // Section already has some keys
253 while(pKey->nextKey != nullptr) {
254 pKey = pKey->nextKey;
255 }
256
257 if(pKey->nextLine == nullptr) {
258 // no line after this key
259 pKey->nextLine = curKey;
260 curKey->prevLine = pKey;
261 curKey->line = pKey->line+1;
262 } else {
263 // lines after this section declaration
264 pKey->nextLine->prevLine = curKey;
265 curKey->nextLine = pKey->nextLine;
266 pKey->nextLine = curKey;
267 curKey->prevLine = pKey;
268
269 curKey->line = pKey->line+1;
270 curKey->nextLine->shiftLineNumber(1);
271 }
272 }
273
274 insertKey(curKey);
275 }
276 }
277
setIntValue(const std::string & key,int newValue)278 void INIFile::Section::setIntValue(const std::string& key, int newValue) {
279 char tmp[20];
280 sprintf(tmp,"%d",newValue);
281 setStringValue(key, tmp, false);
282 }
283
setBoolValue(const std::string & key,bool newValue)284 void INIFile::Section::setBoolValue(const std::string& key, bool newValue) {
285 if(newValue == true) {
286 setStringValue(key, "true", false);
287 } else {
288 setStringValue(key, "false", false);
289 }
290 }
291
setDoubleValue(const std::string & key,double newValue)292 void INIFile::Section::setDoubleValue(const std::string& key, double newValue) {
293 char tmp[30];
294 sprintf(tmp,"%f",newValue);
295 setStringValue(key, tmp, false);
296 }
297
298
insertKey(Key * newKey)299 void INIFile::Section::insertKey(Key* newKey) {
300 if(keyRoot == nullptr) {
301 // New root element
302 keyRoot = newKey;
303 } else {
304 // insert into list
305 Key* curKey = keyRoot;
306 while(curKey->nextKey != nullptr) {
307 curKey = curKey->nextKey;
308 }
309
310 curKey->nextKey = newKey;
311 newKey->prevKey = curKey;
312 }
313 }
314
315
316
317 // public methods
318
319 /// Constructor for an empty INI-File.
320 /**
321 This constructor creates an empty INI-File.
322 \param bWhitespace Insert whitespace between key an value when creating a new entry
323 \param firstLineComment A comment to put in the first line (no comment is added for an empty string)
324 */
INIFile(bool bWhitespace,const std::string & firstLineComment)325 INIFile::INIFile(bool bWhitespace, const std::string& firstLineComment)
326 : firstLine(nullptr), sectionRoot(nullptr), bWhitespace(bWhitespace)
327 {
328 firstLine = nullptr;
329 sectionRoot = nullptr;
330
331 sectionRoot = new Section("", INVALID_LINE, 0, 0, bWhitespace);
332 if(!firstLineComment.empty()) {
333 firstLine = new INIFileLine("; " + firstLineComment, 0);
334 INIFileLine* blankLine = new INIFileLine("",1);
335 firstLine->nextLine = blankLine;
336 blankLine->prevLine = firstLine;
337 }
338 }
339
340
341 /// Constructor for reading the INI-File from a file.
342 /**
343 This constructor reads the INI-File from the file specified by filename. The file opened in readonly-mode. After
344 reading the file it is closed immediately. If the file does not exist, it is treated as empty.
345 \param filename The file to be opened.
346 \param bWhitespace Insert whitespace between key an value when creating a new entry
347 */
INIFile(const std::string & filename,bool bWhitespace)348 INIFile::INIFile(const std::string& filename, bool bWhitespace)
349 : firstLine(nullptr), sectionRoot(nullptr), bWhitespace(bWhitespace) {
350
351 firstLine = nullptr;
352 sectionRoot = nullptr;
353 SDL_RWops * file;
354
355 // open file
356 if((file = SDL_RWFromFile(filename.c_str(),"r")) != nullptr) {
357 readfile(file);
358 SDL_RWclose(file);
359 } else {
360 sectionRoot = new Section("", INVALID_LINE, 0, 0, bWhitespace);
361 }
362 }
363
364 /// Constructor for reading the INI-File from a SDL_RWops.
365 /**
366 This constructor reads the INI-File from RWopsFile. The RWopsFile can be readonly.
367 \param RWopsFile Pointer to RWopsFile (can be readonly)
368 */
INIFile(SDL_RWops * RWopsFile,bool bWhitespace)369 INIFile::INIFile(SDL_RWops * RWopsFile, bool bWhitespace)
370 : firstLine(nullptr), sectionRoot(nullptr), bWhitespace(bWhitespace) {
371
372 if(RWopsFile == nullptr) {
373 THROW(std::invalid_argument, "RWopsFile == nullptr!");
374 }
375
376 readfile(RWopsFile);
377 }
378
379 /// Destructor.
380 /**
381 This is the destructor. Changes to the INI-Files are not automaticly saved. Call INIFile::SaveChangesTo() for that purpose.
382 */
~INIFile()383 INIFile::~INIFile() {
384 INIFileLine* curLine = firstLine;
385 while(curLine != nullptr) {
386 INIFileLine* tmp = curLine;
387 curLine = curLine->nextLine;
388 delete tmp;
389 }
390
391 // now we have to delete the "" section
392 delete sectionRoot;
393 }
394
395
396 /**
397 This method checks whether the specified section exists.
398 \param section sectionname
399 \return true, if the section exists, false otherwise
400 */
hasSection(const std::string & section) const401 bool INIFile::hasSection(const std::string& section) const {
402 return (getSectionInternal(section) != nullptr);
403 }
404
405
406 /**
407 This method returns a reference to the section specified by sectionname
408 \param sectionname the name of the section
409 \return the section if found, nullptr otherwise
410 */
getSection(const std::string & sectionname) const411 const INIFile::Section& INIFile::getSection(const std::string& sectionname) const {
412 const Section* curSection = getSectionInternal(sectionname);
413
414 if(curSection == nullptr) {
415 throw std::out_of_range("There is no section '" + sectionname + "' in this INI file");
416 } else {
417 return *curSection;
418 }
419 }
420
421
422 /**
423 Removes the whole the specified section
424 \param sectionname the section to remove
425 \return true on success
426
427 */
removeSection(const std::string & sectionname)428 bool INIFile::removeSection(const std::string& sectionname) {
429 clearSection(sectionname, false);
430
431 INIFile::Section* curSection = const_cast<Section*>(getSectionInternal(sectionname));
432 if(curSection == nullptr) {
433 return false;
434 }
435
436 if(curSection == sectionRoot) {
437 // the "" section cannot be removed
438 firstLine = sectionRoot->nextSection;
439 } else {
440 // remove line
441 if(curSection->prevLine != nullptr) {
442 curSection->prevLine->nextLine = curSection->nextLine;
443 }
444
445 if(curSection->nextLine != nullptr) {
446 curSection->nextLine->prevLine = curSection->prevLine;
447 }
448
449 if(firstLine == curSection) {
450 firstLine = curSection->nextLine;
451 }
452
453 // remove section from section list
454 if(curSection->prevSection != nullptr) {
455 curSection->prevSection->nextSection = curSection->nextSection;
456 }
457
458 if(curSection->nextSection != nullptr) {
459 curSection->nextSection->prevSection = curSection->prevSection;
460 }
461
462 delete curSection;
463 }
464
465 return true;
466 }
467
468
469 /**
470 Removes all keys from the specified section
471 \param sectionname the section to clear
472 \param bBlankLineAtSectionEnd add a blank line at the end of the now empty section
473 \return true on success
474 */
clearSection(const std::string & sectionname,bool bBlankLineAtSectionEnd)475 bool INIFile::clearSection(const std::string& sectionname, bool bBlankLineAtSectionEnd) {
476 INIFile::Section* curSection = const_cast<Section*>(getSectionInternal(sectionname));
477 if(curSection == nullptr) {
478 return false;
479 }
480
481 INIFile::INIFileLine* pCurrentLine = (sectionRoot == curSection) ? firstLine : curSection->nextLine;
482
483 while((pCurrentLine != nullptr) && (pCurrentLine != curSection->nextSection)) {
484 INIFile::INIFileLine* tmp = pCurrentLine->nextLine;
485 delete pCurrentLine;
486 pCurrentLine = tmp;
487 }
488
489 if(sectionRoot == curSection) {
490 // the "" section header is no line => the first line is the first section header
491 firstLine = pCurrentLine;
492 } else {
493 curSection->nextLine = pCurrentLine;
494 if(pCurrentLine != nullptr) {
495 pCurrentLine->prevLine = curSection;
496 }
497 }
498
499
500 curSection->keyRoot = nullptr;
501
502 // now we add one blank line if not last section
503 if(bBlankLineAtSectionEnd && (curSection->nextSection != nullptr)) {
504 INIFileLine* blankLine = new INIFileLine("",INVALID_LINE);
505 if(curSection->nextLine != nullptr) {
506 curSection->nextLine->prevLine = blankLine;
507 blankLine->nextLine = curSection->nextLine;
508 }
509
510 curSection->nextLine = blankLine;
511 blankLine->prevLine = curSection;
512 }
513
514 return true;
515 }
516
517
518 /**
519 This method checks whether the specified key exists in the specified section.
520 \param section sectionname
521 \param key keyname
522 \return true, if the key exists, false otherwise
523 */
hasKey(const std::string & section,const std::string & key) const524 bool INIFile::hasKey(const std::string& section, const std::string& key) const {
525 const Section* curSection = getSectionInternal(section);
526 if(curSection == nullptr) {
527 return false;
528 } else {
529 return curSection->hasKey(key);
530 }
531 }
532
533
534 /**
535 This method returns a pointer to the key specified by sectionname and keyname
536 \param sectionname the section
537 \param keyname the name of the key
538 \return the key if found, nullptr otherwise
539 */
getKey(const std::string & sectionname,const std::string & keyname) const540 const INIFile::Key* INIFile::getKey(const std::string& sectionname, const std::string& keyname) const {
541 const INIFile::Section* curSection = getSectionInternal(sectionname);
542 if(curSection == nullptr) {
543 return nullptr;
544 }
545
546 return curSection->getKey(keyname);
547 }
548
549
550 /**
551 Removes one key from this ini file
552 \param sectionname the section containing the key
553 \param keyname the name of the key
554 \return true if removing was successful
555 */
removeKey(const std::string & sectionname,const std::string & keyname)556 bool INIFile::removeKey(const std::string& sectionname, const std::string& keyname) {
557 INIFile::Section* curSection = const_cast<Section*>(getSectionInternal(sectionname));
558 if(curSection == nullptr) {
559 return false;
560 }
561
562 INIFile::Key* key = curSection->getKey(keyname);
563 if(key == nullptr) {
564 return false;
565 }
566
567 // remove line
568 if(key->prevLine != nullptr) {
569 key->prevLine->nextLine = key->nextLine;
570 }
571
572 if(key->nextLine != nullptr) {
573 key->nextLine->prevLine = key->prevLine;
574 }
575
576 if(firstLine == key) {
577 firstLine = key->nextLine;
578 }
579
580 // remove key from section
581 if(key->prevKey != nullptr) {
582 key->prevKey->nextKey = key->nextKey;
583 }
584
585 if(key->nextKey != nullptr) {
586 key->nextKey->prevKey = key->prevKey;
587 }
588
589 if(curSection->keyRoot == key) {
590 curSection->keyRoot = key->nextKey;
591 }
592
593 delete key;
594
595 return true;
596 }
597
598
599
600 /// Reads the string that is adressed by the section/key pair.
601 /**
602 Returns the value that is adressed by the section/key pair as a string. If the key could not be found in
603 this section defaultValue is returned. If no defaultValue is specified then "" is returned.
604 \param section sectionname
605 \param key keyname
606 \param defaultValue default value for defaultValue is ""
607 \return The read value or default
608 */
getStringValue(const std::string & section,const std::string & key,const std::string & defaultValue) const609 std::string INIFile::getStringValue(const std::string& section, const std::string& key, const std::string& defaultValue) const {
610 const Key* curKey = getKey(section,key);
611 if(curKey == nullptr) {
612 return defaultValue;
613 } else {
614 return curKey->getStringValue();
615 }
616 }
617
618
619 /// Reads the int that is adressed by the section/key pair.
620 /**
621 Returns the value that is adressed by the section/key pair as a int. If the key could not be found in
622 this section defaultValue is returned. If no defaultValue is specified then 0 is returned. If the value
623 could not be converted to an int 0 is returned.
624 \param section sectionname
625 \param key keyname
626 \param defaultValue default value for defaultValue is 0
627 \return The read number, defaultValue or 0
628 */
getIntValue(const std::string & section,const std::string & key,int defaultValue) const629 int INIFile::getIntValue(const std::string& section, const std::string& key, int defaultValue) const {
630 const Key* curKey = getKey(section,key);
631 if(curKey == nullptr) {
632 return defaultValue;
633 } else {
634 return curKey->getIntValue(defaultValue);
635 }
636 }
637
638 /// Reads the boolean that is adressed by the section/key pair.
639 /**
640 Returns the value that is adressed by the section/key pair as a boolean. If the key could not be found in
641 this section defaultValue is returned. If no defaultValue is specified then false is returned. If the value
642 is one of "true", "enabled", "on" or "1" then true is returned; if it is one of "false", "disabled", "off" or
643 "0" than false is returned; otherwise defaultValue is returned.
644 \param section sectionname
645 \param key keyname
646 \param defaultValue default value for defaultValue is 0
647 \return true for "true", "enabled", "on" and "1"<br>false for "false", "disabled", "off" and "0"
648 */
getBoolValue(const std::string & section,const std::string & key,bool defaultValue) const649 bool INIFile::getBoolValue(const std::string& section, const std::string& key, bool defaultValue) const {
650 const Key* curKey = getKey(section,key);
651 if(curKey == nullptr) {
652 return defaultValue;
653 } else {
654 return curKey->getBoolValue(defaultValue);
655 }
656 }
657
658 /// Reads the float that is adressed by the section/key pair.
659 /**
660 Returns the value that is adressed by the section/key pair as a double. If the key could not be found in
661 this section defaultValue is returned. If no defaultValue is specified then 0.0f is returned. If the value
662 could not be converted to an float 0.0f is returned.
663 \param section sectionname
664 \param key keyname
665 \param defaultValue default value for defaultValue is 0.0f
666 \return The read number, defaultValue or 0.0f
667 */
getFloatValue(const std::string & section,const std::string & key,float defaultValue) const668 float INIFile::getFloatValue(const std::string& section, const std::string& key, float defaultValue) const {
669 const Key* curKey = getKey(section,key);
670 if(curKey == nullptr) {
671 return defaultValue;
672 } else {
673 return curKey->getFloatValue(defaultValue);
674 }
675 }
676
677
678 /// Reads the double that is adressed by the section/key pair.
679 /**
680 Returns the value that is adressed by the section/key pair as a double. If the key could not be found in
681 this section defaultValue is returned. If no defaultValue is specified then 0.0 is returned. If the value
682 could not be converted to an double 0.0 is returned.
683 \param section sectionname
684 \param key keyname
685 \param defaultValue default value for defaultValue is 0.0
686 \return The read number, defaultValue or 0.0
687 */
getDoubleValue(const std::string & section,const std::string & key,double defaultValue) const688 double INIFile::getDoubleValue(const std::string& section, const std::string& key, double defaultValue) const {
689 const Key* curKey = getKey(section,key);
690 if(curKey == nullptr) {
691 return defaultValue;
692 } else {
693 return curKey->getDoubleValue(defaultValue);
694 }
695 }
696
697 /// Sets the string that is adressed by the section/key pair.
698 /**
699 Sets the string that is adressed by the section/key pair to value. If the section and/or the key does not exist it will
700 be created. A valid sectionname/keyname is not allowed to contain '[',']',';' or '#' and can not start or end with
701 whitespaces (' ' or '\\t').
702 \param section sectionname
703 \param key keyname
704 \param value value that should be set
705 \param bEscapeIfNeeded escape the string if it contains any special characters
706 */
setStringValue(const std::string & section,const std::string & key,const std::string & value,bool bEscapeIfNeeded)707 void INIFile::setStringValue(const std::string& section, const std::string& key, const std::string& value, bool bEscapeIfNeeded) {
708 Section* curSection = getSectionOrCreate(section);
709
710 if(curSection == nullptr) {
711 std::cerr << "INIFile: Cannot create section with name " << section << "!" << std::endl;
712 return;
713 }
714
715 curSection->setStringValue(key, value, bEscapeIfNeeded);
716 }
717
718 /// Sets the int that is adressed by the section/key pair.
719 /**
720 Sets the int that is adressed by the section/key pair to value. If the section and/or the key does not exist it will
721 be created. A valid sectionname/keyname is not allowed to contain '[',']',';' or '#' and can not start or end with
722 whitespaces (' ' or '\\t').
723 \param section sectionname
724 \param key keyname
725 \param value value that should be set
726 */
setIntValue(const std::string & section,const std::string & key,int value)727 void INIFile::setIntValue(const std::string& section, const std::string& key, int value) {
728 char tmp[20];
729 sprintf(tmp,"%d",value);
730 setStringValue(section, key, tmp, false);
731 }
732
733 /// Sets the boolean that is adressed by the section/key pair.
734 /**
735 Sets the boolean that is adressed by the section/key pair to value. If the section and/or the key does not exist it will
736 be created. A valid sectionname/keyname is not allowed to contain '[',']',';' or '#' and can not start or end with
737 whitespaces (' ' or '\\t').
738 \param section sectionname
739 \param key keyname
740 \param value value that should be set
741 */
setBoolValue(const std::string & section,const std::string & key,bool value)742 void INIFile::setBoolValue(const std::string& section, const std::string& key, bool value) {
743 if(value == true) {
744 setStringValue(section, key, "true", false);
745 } else {
746 setStringValue(section, key, "false", false);
747 }
748 }
749
750 /// Sets the double that is adressed by the section/key pair.
751 /**
752 Sets the double that is adressed by the section/key pair to value. If the section and/or the key does not exist it will
753 be created. A valid sectionname/keyname is not allowed to contain '[',']',';' or '#' and can not start or end with
754 whitespaces (' ' or '\\t').
755 \param section sectionname
756 \param key keyname
757 \param value value that should be set
758 */
setDoubleValue(const std::string & section,const std::string & key,double value)759 void INIFile::setDoubleValue(const std::string& section, const std::string& key, double value) {
760 char tmp[30];
761 sprintf(tmp,"%f",value);
762 setStringValue(section, key, tmp, false);
763 }
764
765
766 /// Get a section iterator pointing at the first section
767 /**
768 This method returns a section iterator pointing at the first section (which is the anonymous "" section)
769 \return the iterator
770 */
begin() const771 INIFile::SectionIterator INIFile::begin() const {
772 return SectionIterator(sectionRoot);
773 }
774
775 /// Get a section iterator pointing past the last section
776 /**
777 This method returns a section iterator pointing past the last section.
778 \return the iterator
779 */
end() const780 INIFile::SectionIterator INIFile::end() const {
781 return SectionIterator();
782 }
783
784 /// Get a key iterator pointing at the first key in the specified section
785 /**
786 This method returns a key iterator pointing at the first key in the specified section.
787 \param section the section to iterate over
788 \return the iterator
789 */
begin(const std::string & section) const790 INIFile::KeyIterator INIFile::begin(const std::string& section) const {
791 const Section* curSection = getSectionInternal(section);
792 if(curSection == nullptr) {
793 return KeyIterator(nullptr);
794 } else {
795 return KeyIterator(curSection->keyRoot);
796 }
797 }
798
799 /// Get a key iterator pointing past the end of the specified section
800 /**
801 This method returns a key iterator pointing past the end of the specified section.
802 \param section the section to iterate over
803 \return the iterator
804 */
end(const std::string & section) const805 INIFile::KeyIterator INIFile::end(const std::string& section) const {
806 return KeyIterator();
807 }
808
809 /// Saves the changes made in the INI-File to a file.
810 /**
811 Saves the changes made in the INI-File to a file specified by filename.
812 If something goes wrong false is returned otherwise true.
813 \param filename Filename of the file. This file is opened for writing.
814 \param bDOSLineEnding Use dos line ending
815 \return true on success otherwise false.
816 */
saveChangesTo(const std::string & filename,bool bDOSLineEnding) const817 bool INIFile::saveChangesTo(const std::string& filename, bool bDOSLineEnding) const {
818 SDL_RWops * file;
819 if((file = SDL_RWFromFile(filename.c_str(),"wb")) == nullptr) {
820 return false;
821 }
822
823 bool ret = saveChangesTo(file, bDOSLineEnding);
824 SDL_RWclose(file);
825 return ret;
826 }
827
828 /// Saves the changes made in the INI-File to a RWop.
829 /**
830 Saves the changes made in the INI-File to a RWop specified by file.
831 If something goes wrong false is returned otherwise true.
832 \param file SDL_RWops that is used for writing. (Cannot be readonly)
833 \return true on success otherwise false.
834 */
saveChangesTo(SDL_RWops * file,bool bDOSLineEnding) const835 bool INIFile::saveChangesTo(SDL_RWops * file, bool bDOSLineEnding) const {
836 INIFileLine* curLine = firstLine;
837
838 bool error = false;
839 while(curLine != nullptr) {
840 unsigned int written = SDL_RWwrite(file, curLine->completeLine.c_str(), 1, curLine->completeLine.size());
841 if(written != curLine->completeLine.size()) {
842 std::cout << SDL_GetError() << std::endl;
843 error = true;
844 }
845
846 if(bDOSLineEnding) {
847 // when dos line ending we also put it at the end of the file
848 if((written = SDL_RWwrite(file,"\r\n",2,1)) != 1) {
849 error = true;
850 }
851 } else if(curLine->nextLine != nullptr) {
852 // when no dos line ending we skip the ending at the last line
853 if((written = SDL_RWwrite(file,"\n",1,1)) != 1) {
854 error = true;
855 }
856 }
857 curLine = curLine->nextLine;
858 }
859
860 return !error;
861 }
862
863 // private methods
864
flush() const865 void INIFile::flush() const {
866 INIFileLine* curLine = firstLine;
867
868 while(curLine != nullptr) {
869 std::cout << curLine->completeLine << std::endl;
870 curLine = curLine->nextLine;
871 }
872 }
873
readfile(SDL_RWops * file)874 void INIFile::readfile(SDL_RWops * file) {
875 sectionRoot = new Section("", INVALID_LINE, 0, 0, bWhitespace);
876
877 Section* curSection = sectionRoot;
878
879 std::string completeLine;
880 int lineNum = 0;
881 INIFileLine* curLine = nullptr;
882 INIFileLine* newINIFileLine;
883 Section* newSection;
884 Key* newKey;
885
886 bool readfinished = false;
887
888 while(!readfinished) {
889 lineNum++;
890
891 completeLine = "";
892 unsigned char tmp;
893
894 while(1) {
895 size_t readbytes = SDL_RWread(file,&tmp,1,1);
896 if(readbytes == 0) {
897 readfinished = true;
898 break;
899 } else if(tmp == '\n') {
900 break;
901 } else if(tmp != '\r') {
902 completeLine += tmp;
903 }
904 }
905
906 const unsigned char* line = (const unsigned char*) completeLine.c_str();
907 bool bSyntaxError = false;
908
909 int ret = getNextChar(line,0);
910
911 if(ret == -1) {
912 // empty line or comment
913 newINIFileLine = new INIFileLine(completeLine,lineNum);
914
915 if(curLine == nullptr) {
916 firstLine = newINIFileLine;
917 curLine = newINIFileLine;
918 } else {
919 curLine->nextLine = newINIFileLine;
920 newINIFileLine->prevLine = curLine;
921 curLine = newINIFileLine;
922 }
923 } else {
924
925 if(line[ret] == '[') {
926 // section line
927 int sectionstart = ret+1;
928 int sectionend = skipName(line,ret+1);
929
930 if((line[sectionend] != ']') || (getNextChar(line,sectionend+1) != -1)) {
931 bSyntaxError = true;
932 } else {
933 // valid section line
934 newSection = new Section(completeLine,lineNum,sectionstart,sectionend-sectionstart,bWhitespace);
935
936 if(curLine == nullptr) {
937 firstLine = newSection;
938 curLine = newSection;
939 } else {
940 curLine->nextLine = newSection;
941 newSection->prevLine = curLine;
942 curLine = newSection;
943 }
944
945 insertSection(newSection);
946 curSection = newSection;
947 }
948 } else {
949
950 // might be key/value line
951 int keystart = ret;
952 int keyend = skipKey(line,keystart);
953
954 if(keystart == keyend) {
955 bSyntaxError = true;
956 } else {
957 ret = getNextChar(line,keyend);
958 if((ret == -1) ||(line[ret] != '=')) {
959 bSyntaxError = true;
960 } else {
961 int valuestart = getNextChar(line,ret+1);
962 if(valuestart == -1) {
963 bSyntaxError = true;
964 } else {
965 if(line[valuestart] == '"') {
966 // now get the next '"'
967
968 int valueend = getNextQuote(line,valuestart+1);
969
970 if((valueend == -1) || (getNextChar(line,valueend+1) != -1)) {
971 bSyntaxError = true;
972 } else {
973 // valid key/value line
974 newKey = new Key(completeLine,lineNum,keystart,keyend-keystart,valuestart+1,valueend-valuestart-1);
975
976 if(firstLine == nullptr) {
977 firstLine = newKey;
978 curLine = newKey;
979 } else {
980 curLine->nextLine = newKey;
981 newKey->prevLine = curLine;
982 curLine = newKey;
983 }
984
985 curSection->insertKey(newKey);
986 }
987
988 } else {
989 int valueend = skipValue(line,valuestart);
990
991 if(getNextChar(line,valueend) != -1) {
992 bSyntaxError = true;
993 } else {
994 // valid key/value line
995 newKey = new Key(completeLine,lineNum,keystart,keyend-keystart,valuestart,valueend-valuestart);
996
997 if(firstLine == nullptr) {
998 firstLine = newKey;
999 curLine = newKey;
1000 } else {
1001 curLine->nextLine = newKey;
1002 newKey->prevLine = curLine;
1003 curLine = newKey;
1004 }
1005
1006 curSection->insertKey(newKey);
1007 }
1008 }
1009 }
1010 }
1011 }
1012
1013 }
1014 }
1015
1016 if(bSyntaxError == true) {
1017 if(completeLine.size() < 100) {
1018 // there are some buggy ini-files which have a lot of waste at the end of the file
1019 // and it makes no sense to print all this stuff out. just skip it
1020 std::cerr << "INIFile: Syntax-Error in line " << lineNum << ":" << completeLine << " !" << std::endl;
1021 }
1022 // save this line as a comment
1023 newINIFileLine = new INIFileLine(completeLine,lineNum);
1024
1025 if(curLine == nullptr) {
1026 firstLine = newINIFileLine;
1027 curLine = newINIFileLine;
1028 } else {
1029 curLine->nextLine = newINIFileLine;
1030 newINIFileLine->prevLine = curLine;
1031 curLine = newINIFileLine;
1032 }
1033 }
1034
1035
1036
1037 }
1038 }
1039
insertSection(Section * newSection)1040 void INIFile::insertSection(Section* newSection) {
1041 if(sectionRoot == nullptr) {
1042 // New root element
1043 sectionRoot = newSection;
1044 } else {
1045 // insert into list
1046 Section* curSection = sectionRoot;
1047 while(curSection->nextSection != nullptr) {
1048 curSection = curSection->nextSection;
1049 }
1050
1051 curSection->nextSection = newSection;
1052 newSection->prevSection = curSection;
1053 }
1054 }
1055
1056
getSectionInternal(const std::string & sectionname) const1057 const INIFile::Section* INIFile::getSectionInternal(const std::string& sectionname) const {
1058 Section* curSection = sectionRoot;
1059 int sectionnameSize = sectionname.size();
1060
1061 while(curSection != nullptr) {
1062 if(curSection->sectionStringLength == sectionnameSize) {
1063 if(strncicmp(sectionname.c_str(), curSection->completeLine.c_str()+curSection->sectionStringBegin, sectionnameSize) == 0) {
1064 return curSection;
1065 }
1066 }
1067
1068 curSection = curSection->nextSection;
1069 }
1070
1071 return nullptr;
1072 }
1073
1074
getSectionOrCreate(const std::string & sectionname)1075 INIFile::Section* INIFile::getSectionOrCreate(const std::string& sectionname) {
1076 Section* curSection = const_cast<Section*>(getSectionInternal(sectionname));
1077
1078 if(curSection == nullptr) {
1079 // create new section
1080
1081 if(isValidSectionName(sectionname) == false) {
1082 std::cerr << "INIFile: Cannot create section with name " << sectionname << "!" << std::endl;
1083 return nullptr;
1084 }
1085
1086 curSection = new Section(sectionname, bWhitespace);
1087
1088 if(firstLine == nullptr) {
1089 firstLine = curSection;
1090 } else {
1091 INIFileLine* curLine = firstLine;
1092 while(curLine->nextLine != nullptr) {
1093 curLine = curLine->nextLine;
1094 }
1095
1096
1097 if(curLine->completeLine != "") {
1098 // previous line is not a blank line => add one blank line
1099 INIFileLine* blankLine = new INIFileLine("",INVALID_LINE);
1100 curLine->nextLine = blankLine;
1101 blankLine->prevLine = curLine;
1102
1103 blankLine->nextLine = curSection;
1104 curSection->prevLine = blankLine;
1105 } else {
1106 // previous line is an empty line => directly add new section
1107 curLine->nextLine = curSection;
1108 curSection->prevLine = curLine;
1109 }
1110
1111 curSection->line = curLine->line + 1;
1112 }
1113
1114 insertSection(curSection);
1115 }
1116 return curSection;
1117 }
1118
1119
isValidSectionName(const std::string & sectionname)1120 bool INIFile::isValidSectionName(const std::string& sectionname) {
1121 for(unsigned int i = 0; i < sectionname.size(); i++) {
1122 if( (!isNormalChar(sectionname[i])) && (!isWhitespace(sectionname[i])) ) {
1123 return false;
1124 }
1125 }
1126
1127 if(isWhitespace(sectionname[0]) || isWhitespace(sectionname[sectionname.size()-1])) {
1128 return false;
1129 } else {
1130 return true;
1131 }
1132 }
1133
isValidKeyName(const std::string & keyname)1134 bool INIFile::isValidKeyName(const std::string& keyname) {
1135 for(unsigned int i = 0; i < keyname.size(); i++) {
1136 if( (!isNormalChar(keyname[i])) && (!isWhitespace(keyname[i])) ) {
1137 return false;
1138 }
1139 }
1140
1141 if(isWhitespace(keyname[0]) || isWhitespace(keyname[keyname.size()-1])) {
1142 return false;
1143 }
1144 return true;
1145 }
1146
getNextChar(const unsigned char * line,int startpos)1147 int INIFile::getNextChar(const unsigned char* line, int startpos) {
1148 while(line[startpos] != '\0') {
1149 if((line[startpos] == ';') || (line[startpos] == '#')) {
1150 // comment
1151 return -1;
1152 } else if(!isWhitespace(line[startpos])) {
1153 return startpos;
1154 }
1155 startpos++;
1156 }
1157 return -1;
1158 }
1159
skipName(const unsigned char * line,int startpos)1160 int INIFile::skipName(const unsigned char* line,int startpos) {
1161 while(line[startpos] != '\0') {
1162 if(isNormalChar(line[startpos]) || (line[startpos] == ' ') || (line[startpos] == '\t')) {
1163 startpos++;
1164 } else {
1165 return startpos;
1166 }
1167 }
1168 return startpos;
1169 }
1170
skipValue(const unsigned char * line,int startpos)1171 int INIFile::skipValue(const unsigned char* line,int startpos) {
1172 int i = startpos;
1173 while(line[i] != '\0') {
1174 if(isNormalChar(line[i]) || isWhitespace(line[i])) {
1175 i++;
1176 } else if((line[i] == ';') || (line[i] == '#')) {
1177 // begin of a comment
1178 break;
1179 } else {
1180 // some invalid character
1181 return i;
1182 }
1183 }
1184
1185 // now we go backwards
1186 while(i >= startpos) {
1187 if(isNormalChar(line[i])) {
1188 return i+1;
1189 }
1190 i--;
1191 }
1192 return startpos+1;
1193 }
1194
skipKey(const unsigned char * line,int startpos)1195 int INIFile::skipKey(const unsigned char* line,int startpos) {
1196 int i = startpos;
1197 while(line[i] != '\0') {
1198 if(isNormalChar(line[i]) || isWhitespace(line[i])) {
1199 i++;
1200 } else if((line[i] == ';') || (line[i] == '#') || (line[i] == '=')) {
1201 // begin of a comment or '='
1202 break;
1203 } else {
1204 // some invalid character
1205 return i;
1206 }
1207 }
1208
1209 // now we go backwards
1210 while(i >= startpos) {
1211 if(isNormalChar(line[i])) {
1212 return i+1;
1213 }
1214 i--;
1215 }
1216 return startpos+1;
1217 }
1218
getNextQuote(const unsigned char * line,int startpos)1219 int INIFile::getNextQuote(const unsigned char* line,int startpos) {
1220 while(line[startpos] != '\0') {
1221 if(line[startpos] != '"') {
1222 startpos++;
1223 } else {
1224 return startpos;
1225 }
1226 }
1227 return -1;
1228 }
1229
isWhitespace(unsigned char s)1230 bool INIFile::isWhitespace(unsigned char s) {
1231 if((s == ' ') || (s == '\t') || (s == '\n') || (s == '\r')) {
1232 return true;
1233 } else {
1234 return false;
1235 }
1236 }
1237
isNormalChar(unsigned char s)1238 bool INIFile::isNormalChar(unsigned char s) {
1239 if((!isWhitespace(s)) && (s >= 33) && (s != '"') && (s != ';') && (s != '#') && (s != '[') && (s != ']') && (s != '=')) {
1240 return true;
1241 } else {
1242 return false;
1243 }
1244 }
1245
strncicmp(const char * s1,const char * s2,size_t n)1246 int INIFile::strncicmp(const char *s1, const char *s2, size_t n) {
1247 const char* p1 = s1;
1248 const char* p2 = s2;
1249 while((p1 < s1 + n) && (*p1 != 0) && (toupper(*p1) == toupper(*p2))) {
1250 ++p1;
1251 ++p2;
1252 }
1253 if(s1 + n == p1) {
1254 return 0;
1255 } else {
1256 return (toupper(*p1) - toupper(*p2));
1257 }
1258 }
1259