ResidualVM logo ResidualVM website - Forums - Contact us BuildBot - Doxygen - Wiki curved edge

ini-file.cpp

Go to the documentation of this file.
00001 /* ScummVM - Graphic Adventure Engine
00002  *
00003  * ScummVM is the legal property of its developers, whose names
00004  * are too numerous to list here. Please refer to the COPYRIGHT
00005  * file distributed with this source distribution.
00006  *
00007  * This program is free software; you can redistribute it and/or
00008  * modify it under the terms of the GNU General Public License
00009  * as published by the Free Software Foundation; either version 2
00010  * of the License, or (at your option) any later version.
00011  *
00012  * This program is distributed in the hope that it will be useful,
00013  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00014  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00015  * GNU General Public License for more details.
00016  *
00017  * You should have received a copy of the GNU General Public License
00018  * along with this program; if not, write to the Free Software
00019  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
00020  *
00021  */
00022 
00023 #include "common/ini-file.h"
00024 #include "common/file.h"
00025 #include "common/savefile.h"
00026 #include "common/system.h"
00027 #include "common/textconsole.h"
00028 
00029 namespace Common {
00030 
00031 bool INIFile::isValidName(const String &name) {
00032     const char *p = name.c_str();
00033     while (*p && (isAlnum(*p) || *p == '-' || *p == '_' || *p == '.'))
00034         p++;
00035     return *p == 0;
00036 }
00037 
00038 INIFile::INIFile() {
00039 }
00040 
00041 INIFile::~INIFile() {
00042 }
00043 
00044 void INIFile::clear() {
00045     _sections.clear();
00046 }
00047 
00048 bool INIFile::loadFromFile(const String &filename) {
00049     File file;
00050     if (file.open(filename))
00051         return loadFromStream(file);
00052     else
00053         return false;
00054 }
00055 
00056 bool INIFile::loadFromSaveFile(const String &filename) {
00057     assert(g_system);
00058     SaveFileManager *saveFileMan = g_system->getSavefileManager();
00059     SeekableReadStream *loadFile;
00060 
00061     assert(saveFileMan);
00062     if (!(loadFile = saveFileMan->openForLoading(filename)))
00063         return false;
00064 
00065     bool status = loadFromStream(*loadFile);
00066     delete loadFile;
00067     return status;
00068 }
00069 
00070 bool INIFile::loadFromStream(SeekableReadStream &stream) {
00071     Section section;
00072     KeyValue kv;
00073     String comment;
00074     int lineno = 0;
00075 
00076     // TODO: Detect if a section occurs multiple times (or likewise, if
00077     // a key occurs multiple times inside one section).
00078 
00079     while (!stream.eos() && !stream.err()) {
00080         lineno++;
00081 
00082         // Read a line
00083         String line = stream.readLine();
00084 
00085         if (line.size() == 0) {
00086             // Do nothing
00087         } else if (line[0] == '#' || line[0] == ';' || line.hasPrefix("//")) {
00088             // Accumulate comments here. Once we encounter either the start
00089             // of a new section, or a key-value-pair, we associate the value
00090             // of the 'comment' variable with that entity. The semicolon and
00091             // C++-style comments are used for Living Books games in Mohawk.
00092             comment += line;
00093             comment += "\n";
00094         } else if (line[0] == '(') {
00095             // HACK: The following is a hack added by Kirben to support the
00096             // "map.ini" used in the HE SCUMM game "SPY Fox in Hold the Mustard".
00097             //
00098             // It would be nice if this hack could be restricted to that game,
00099             // but the current design of this class doesn't allow to do that
00100             // in a nice fashion (a "isMustard" parameter is *not* a nice
00101             // solution).
00102             comment += line;
00103             comment += "\n";
00104         } else if (line[0] == '[') {
00105             // It's a new section which begins here.
00106             const char *p = line.c_str() + 1;
00107             // Get the section name, and check whether it's valid (that
00108             // is, verify that it only consists of alphanumerics,
00109             // periods, dashes and underscores). Mohawk Living Books games
00110             // can have periods in their section names.
00111             while (*p && (isAlnum(*p) || *p == '-' || *p == '_' || *p == '.'))
00112                 p++;
00113 
00114             if (*p == '\0')
00115                 error("INIFile::loadFromStream: missing ] in line %d", lineno);
00116             else if (*p != ']')
00117                 error("INIFile::loadFromStream: Invalid character '%c' occurred in section name in line %d", *p, lineno);
00118 
00119             // Previous section is finished now, store it.
00120             if (!section.name.empty())
00121                 _sections.push_back(section);
00122 
00123             section.name = String(line.c_str() + 1, p);
00124             section.keys.clear();
00125             section.comment = comment;
00126             comment.clear();
00127 
00128             assert(isValidName(section.name));
00129         } else {
00130             // This line should be a line with a 'key=value' pair, or an empty one.
00131 
00132             // Skip leading whitespaces
00133             const char *t = line.c_str();
00134             while (isSpace(*t))
00135                 t++;
00136 
00137             // Skip empty lines / lines with only whitespace
00138             if (*t == 0)
00139                 continue;
00140 
00141             // If no section has been set, this config file is invalid!
00142             if (section.name.empty()) {
00143                 error("INIFile::loadFromStream: Key/value pair found outside a section in line %d", lineno);
00144             }
00145 
00146             // Split string at '=' into 'key' and 'value'. First, find the "=" delimeter.
00147             const char *p = strchr(t, '=');
00148             if (!p)
00149                 error("Config file buggy: Junk found in line line %d: '%s'", lineno, t);
00150 
00151             // Extract the key/value pair
00152             kv.key = String(t, p);
00153             kv.value = String(p + 1);
00154 
00155             // Trim of spaces
00156             kv.key.trim();
00157             kv.value.trim();
00158 
00159             // Store comment
00160             kv.comment = comment;
00161             comment.clear();
00162 
00163             assert(isValidName(kv.key));
00164 
00165             section.keys.push_back(kv);
00166         }
00167     }
00168 
00169     // Save last section
00170     if (!section.name.empty())
00171         _sections.push_back(section);
00172 
00173     return (!stream.err() || stream.eos());
00174 }
00175 
00176 bool INIFile::saveToFile(const String &filename) {
00177     DumpFile file;
00178     if (file.open(filename))
00179         return saveToStream(file);
00180     else
00181         return false;
00182 }
00183 
00184 bool INIFile::saveToSaveFile(const String &filename) {
00185     assert(g_system);
00186     SaveFileManager *saveFileMan = g_system->getSavefileManager();
00187     WriteStream *saveFile;
00188 
00189     assert(saveFileMan);
00190     if (!(saveFile = saveFileMan->openForSaving(filename)))
00191         return false;
00192 
00193     bool status = saveToStream(*saveFile);
00194     delete saveFile;
00195     return status;
00196 }
00197 
00198 bool INIFile::saveToStream(WriteStream &stream) {
00199     for (List<Section>::iterator i = _sections.begin(); i != _sections.end(); ++i) {
00200         // Write out the section comment, if any
00201         if (! i->comment.empty()) {
00202             stream.writeString(i->comment);
00203         }
00204 
00205         // Write out the section name
00206         stream.writeByte('[');
00207         stream.writeString(i->name);
00208         stream.writeByte(']');
00209         stream.writeByte('\n');
00210 
00211         // Write out the key/value pairs
00212         for (List<KeyValue>::iterator kv = i->keys.begin(); kv != i->keys.end(); ++kv) {
00213             // Write out the comment, if any
00214             if (! kv->comment.empty()) {
00215                 stream.writeString(kv->comment);
00216             }
00217             // Write out the key/value pair
00218             stream.writeString(kv->key);
00219             stream.writeByte('=');
00220             stream.writeString(kv->value);
00221             stream.writeByte('\n');
00222         }
00223     }
00224 
00225     stream.flush();
00226     return !stream.err();
00227 }
00228 
00229 void INIFile::addSection(const String &section) {
00230     Section *s = getSection(section);
00231     if (s)
00232         return;
00233 
00234     Section newSection;
00235     newSection.name = section;
00236     _sections.push_back(newSection);
00237 }
00238 
00239 void INIFile::removeSection(const String &section) {
00240     assert(isValidName(section));
00241     for (List<Section>::iterator i = _sections.begin(); i != _sections.end(); ++i) {
00242         if (section.equalsIgnoreCase(i->name)) {
00243             _sections.erase(i);
00244             return;
00245         }
00246     }
00247 }
00248 
00249 bool INIFile::hasSection(const String &section) const {
00250     assert(isValidName(section));
00251     const Section *s = getSection(section);
00252     return s != nullptr;
00253 }
00254 
00255 void INIFile::renameSection(const String &oldName, const String &newName) {
00256     assert(isValidName(oldName));
00257     assert(isValidName(newName));
00258 
00259     Section *os = getSection(oldName);
00260     const Section *ns = getSection(newName);
00261     if (os) {
00262         // HACK: For now we just print a warning, for more info see the TODO
00263         // below.
00264         if (ns)
00265             warning("INIFile::renameSection: Section name \"%s\" already used", newName.c_str());
00266         else
00267             os->name = newName;
00268     }
00269     // TODO: Check here whether there already is a section with the
00270     // new name. Not sure how to cope with that case, we could:
00271     // - simply remove the existing "newName" section
00272     // - error out
00273     // - merge the two sections "oldName" and "newName"
00274 }
00275 
00276 
00277 bool INIFile::hasKey(const String &key, const String &section) const {
00278     assert(isValidName(key));
00279     assert(isValidName(section));
00280 
00281     const Section *s = getSection(section);
00282     if (!s)
00283         return false;
00284     return s->hasKey(key);
00285 }
00286 
00287 void INIFile::removeKey(const String &key, const String &section) {
00288     assert(isValidName(key));
00289     assert(isValidName(section));
00290 
00291     Section *s = getSection(section);
00292     if (s)
00293          s->removeKey(key);
00294 }
00295 
00296 bool INIFile::getKey(const String &key, const String &section, String &value) const {
00297     assert(isValidName(key));
00298     assert(isValidName(section));
00299 
00300     const Section *s = getSection(section);
00301     if (!s)
00302         return false;
00303     const KeyValue *kv = s->getKey(key);
00304     if (!kv)
00305         return false;
00306     value = kv->value;
00307     return true;
00308 }
00309 
00310 void INIFile::setKey(const String &key, const String &section, const String &value) {
00311     assert(isValidName(key));
00312     assert(isValidName(section));
00313     // TODO: Verify that value is valid, too. In particular, it shouldn't
00314     // contain CR or LF...
00315 
00316     Section *s = getSection(section);
00317     if (!s) {
00318         KeyValue newKV;
00319         newKV.key = key;
00320         newKV.value = value;
00321 
00322         Section newSection;
00323         newSection.name = section;
00324         newSection.keys.push_back(newKV);
00325 
00326         _sections.push_back(newSection);
00327     } else {
00328         s->setKey(key, value);
00329     }
00330 }
00331 
00332 const INIFile::SectionKeyList INIFile::getKeys(const String &section) const {
00333     const Section *s = getSection(section);
00334 
00335     return s->getKeys();
00336 }
00337 
00338 INIFile::Section *INIFile::getSection(const String &section) {
00339     for (List<Section>::iterator i = _sections.begin(); i != _sections.end(); ++i) {
00340         if (section.equalsIgnoreCase(i->name)) {
00341             return &(*i);
00342         }
00343     }
00344     return nullptr;
00345 }
00346 
00347 const INIFile::Section *INIFile::getSection(const String &section) const {
00348     for (List<Section>::const_iterator i = _sections.begin(); i != _sections.end(); ++i) {
00349         if (section.equalsIgnoreCase(i->name)) {
00350             return &(*i);
00351         }
00352     }
00353     return nullptr;
00354 }
00355 
00356 bool INIFile::Section::hasKey(const String &key) const {
00357     return getKey(key) != nullptr;
00358 }
00359 
00360 const INIFile::KeyValue* INIFile::Section::getKey(const String &key) const {
00361     for (List<KeyValue>::const_iterator i = keys.begin(); i != keys.end(); ++i) {
00362         if (key.equalsIgnoreCase(i->key)) {
00363             return &(*i);
00364         }
00365     }
00366     return nullptr;
00367 }
00368 
00369 void INIFile::Section::setKey(const String &key, const String &value) {
00370     for (List<KeyValue>::iterator i = keys.begin(); i != keys.end(); ++i) {
00371         if (key.equalsIgnoreCase(i->key)) {
00372             i->value = value;
00373             return;
00374         }
00375     }
00376 
00377     KeyValue newKV;
00378     newKV.key = key;
00379     newKV.value = value;
00380     keys.push_back(newKV);
00381 }
00382 
00383 void INIFile::Section::removeKey(const String &key) {
00384     for (List<KeyValue>::iterator i = keys.begin(); i != keys.end(); ++i) {
00385         if (key.equalsIgnoreCase(i->key)) {
00386             keys.erase(i);
00387             return;
00388         }
00389     }
00390 }
00391 
00392 } // End of namespace Common


Generated on Sat Jun 22 2019 05:00:42 for ResidualVM by doxygen 1.7.1
curved edge   curved edge