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