mirror of
https://github.com/libretro/scummvm.git
synced 2024-12-14 05:38:56 +00:00
164604805e
It will still not work when the new section name is already taken, but at at least when it is not taken it should work now.
384 lines
10 KiB
C++
384 lines
10 KiB
C++
/* ScummVM - Graphic Adventure Engine
|
|
*
|
|
* ScummVM is the legal property of its developers, whose names
|
|
* are too numerous to list here. Please refer to the COPYRIGHT
|
|
* file distributed with this source distribution.
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation; either version 2
|
|
* of the License, or (at your option) any later version.
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*
|
|
*/
|
|
|
|
#include "common/config-file.h"
|
|
#include "common/file.h"
|
|
#include "common/savefile.h"
|
|
#include "common/system.h"
|
|
#include "common/textconsole.h"
|
|
|
|
namespace Common {
|
|
|
|
bool ConfigFile::isValidName(const String &name) {
|
|
const char *p = name.c_str();
|
|
while (*p && (isalnum(static_cast<unsigned char>(*p)) || *p == '-' || *p == '_' || *p == '.'))
|
|
p++;
|
|
return *p == 0;
|
|
}
|
|
|
|
ConfigFile::ConfigFile() {
|
|
}
|
|
|
|
ConfigFile::~ConfigFile() {
|
|
}
|
|
|
|
void ConfigFile::clear() {
|
|
_sections.clear();
|
|
}
|
|
|
|
bool ConfigFile::loadFromFile(const String &filename) {
|
|
File file;
|
|
if (file.open(filename))
|
|
return loadFromStream(file);
|
|
else
|
|
return false;
|
|
}
|
|
|
|
bool ConfigFile::loadFromSaveFile(const char *filename) {
|
|
assert(g_system);
|
|
SaveFileManager *saveFileMan = g_system->getSavefileManager();
|
|
SeekableReadStream *loadFile;
|
|
|
|
assert(saveFileMan);
|
|
if (!(loadFile = saveFileMan->openForLoading(filename)))
|
|
return false;
|
|
|
|
bool status = loadFromStream(*loadFile);
|
|
delete loadFile;
|
|
return status;
|
|
}
|
|
|
|
bool ConfigFile::loadFromStream(SeekableReadStream &stream) {
|
|
Section section;
|
|
KeyValue kv;
|
|
String comment;
|
|
int lineno = 0;
|
|
|
|
// TODO: Detect if a section occurs multiple times (or likewise, if
|
|
// a key occurs multiple times inside one section).
|
|
|
|
while (!stream.eos() && !stream.err()) {
|
|
lineno++;
|
|
|
|
// Read a line
|
|
String line = stream.readLine();
|
|
|
|
if (line.size() == 0) {
|
|
// Do nothing
|
|
} else if (line[0] == '#' || line[0] == ';' || line.hasPrefix("//")) {
|
|
// Accumulate comments here. Once we encounter either the start
|
|
// of a new section, or a key-value-pair, we associate the value
|
|
// of the 'comment' variable with that entity. The semicolon and
|
|
// C++-style comments are used for Living Books games in Mohawk.
|
|
comment += line;
|
|
comment += "\n";
|
|
} else if (line[0] == '(') {
|
|
// HACK: The following is a hack added by Kirben to support the
|
|
// "map.ini" used in the HE SCUMM game "SPY Fox in Hold the Mustard".
|
|
//
|
|
// It would be nice if this hack could be restricted to that game,
|
|
// but the current design of this class doesn't allow to do that
|
|
// in a nice fashion (a "isMustard" parameter is *not* a nice
|
|
// solution).
|
|
comment += line;
|
|
comment += "\n";
|
|
} else if (line[0] == '[') {
|
|
// It's a new section which begins here.
|
|
const char *p = line.c_str() + 1;
|
|
// Get the section name, and check whether it's valid (that
|
|
// is, verify that it only consists of alphanumerics,
|
|
// periods, dashes and underscores). Mohawk Living Books games
|
|
// can have periods in their section names.
|
|
while (*p && (isalnum(static_cast<unsigned char>(*p)) || *p == '-' || *p == '_' || *p == '.'))
|
|
p++;
|
|
|
|
if (*p == '\0')
|
|
error("ConfigFile::loadFromStream: missing ] in line %d", lineno);
|
|
else if (*p != ']')
|
|
error("ConfigFile::loadFromStream: Invalid character '%c' occurred in section name in line %d", *p, lineno);
|
|
|
|
// Previous section is finished now, store it.
|
|
if (!section.name.empty())
|
|
_sections.push_back(section);
|
|
|
|
section.name = String(line.c_str() + 1, p);
|
|
section.keys.clear();
|
|
section.comment = comment;
|
|
comment.clear();
|
|
|
|
assert(isValidName(section.name));
|
|
} else {
|
|
// This line should be a line with a 'key=value' pair, or an empty one.
|
|
|
|
// Skip leading whitespaces
|
|
const char *t = line.c_str();
|
|
while (isspace(static_cast<unsigned char>(*t)))
|
|
t++;
|
|
|
|
// Skip empty lines / lines with only whitespace
|
|
if (*t == 0)
|
|
continue;
|
|
|
|
// If no section has been set, this config file is invalid!
|
|
if (section.name.empty()) {
|
|
error("ConfigFile::loadFromStream: Key/value pair found outside a section in line %d", lineno);
|
|
}
|
|
|
|
// Split string at '=' into 'key' and 'value'. First, find the "=" delimeter.
|
|
const char *p = strchr(t, '=');
|
|
if (!p)
|
|
error("Config file buggy: Junk found in line line %d: '%s'", lineno, t);
|
|
|
|
// Extract the key/value pair
|
|
kv.key = String(t, p);
|
|
kv.value = String(p + 1);
|
|
|
|
// Trim of spaces
|
|
kv.key.trim();
|
|
kv.value.trim();
|
|
|
|
// Store comment
|
|
kv.comment = comment;
|
|
comment.clear();
|
|
|
|
assert(isValidName(kv.key));
|
|
|
|
section.keys.push_back(kv);
|
|
}
|
|
}
|
|
|
|
// Save last section
|
|
if (!section.name.empty())
|
|
_sections.push_back(section);
|
|
|
|
return (!stream.err() || stream.eos());
|
|
}
|
|
|
|
bool ConfigFile::saveToFile(const String &filename) {
|
|
DumpFile file;
|
|
if (file.open(filename))
|
|
return saveToStream(file);
|
|
else
|
|
return false;
|
|
}
|
|
|
|
bool ConfigFile::saveToSaveFile(const char *filename) {
|
|
assert(g_system);
|
|
SaveFileManager *saveFileMan = g_system->getSavefileManager();
|
|
WriteStream *saveFile;
|
|
|
|
assert(saveFileMan);
|
|
if (!(saveFile = saveFileMan->openForSaving(filename)))
|
|
return false;
|
|
|
|
bool status = saveToStream(*saveFile);
|
|
delete saveFile;
|
|
return status;
|
|
}
|
|
|
|
bool ConfigFile::saveToStream(WriteStream &stream) {
|
|
for (List<Section>::iterator i = _sections.begin(); i != _sections.end(); ++i) {
|
|
// Write out the section comment, if any
|
|
if (! i->comment.empty()) {
|
|
stream.writeString(i->comment);
|
|
}
|
|
|
|
// Write out the section name
|
|
stream.writeByte('[');
|
|
stream.writeString(i->name);
|
|
stream.writeByte(']');
|
|
stream.writeByte('\n');
|
|
|
|
// Write out the key/value pairs
|
|
for (List<KeyValue>::iterator kv = i->keys.begin(); kv != i->keys.end(); ++kv) {
|
|
// Write out the comment, if any
|
|
if (! kv->comment.empty()) {
|
|
stream.writeString(kv->comment);
|
|
}
|
|
// Write out the key/value pair
|
|
stream.writeString(kv->key);
|
|
stream.writeByte('=');
|
|
stream.writeString(kv->value);
|
|
stream.writeByte('\n');
|
|
}
|
|
}
|
|
|
|
stream.flush();
|
|
return !stream.err();
|
|
}
|
|
|
|
|
|
void ConfigFile::removeSection(const String §ion) {
|
|
assert(isValidName(section));
|
|
for (List<Section>::iterator i = _sections.begin(); i != _sections.end(); ++i) {
|
|
if (section.equalsIgnoreCase(i->name)) {
|
|
_sections.erase(i);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool ConfigFile::hasSection(const String §ion) const {
|
|
assert(isValidName(section));
|
|
const Section *s = getSection(section);
|
|
return s != 0;
|
|
}
|
|
|
|
void ConfigFile::renameSection(const String &oldName, const String &newName) {
|
|
assert(isValidName(oldName));
|
|
assert(isValidName(newName));
|
|
|
|
Section *os = getSection(oldName);
|
|
const Section *ns = getSection(newName);
|
|
if (os) {
|
|
// HACK: For now we just print a warning, for more info see the TODO
|
|
// below.
|
|
if (ns)
|
|
warning("ConfigFile::renameSection: Section name \"%s\" already used", newName.c_str());
|
|
else
|
|
os->name = newName;
|
|
}
|
|
// TODO: Check here whether there already is a section with the
|
|
// new name. Not sure how to cope with that case, we could:
|
|
// - simply remove the existing "newName" section
|
|
// - error out
|
|
// - merge the two sections "oldName" and "newName"
|
|
}
|
|
|
|
|
|
bool ConfigFile::hasKey(const String &key, const String §ion) const {
|
|
assert(isValidName(key));
|
|
assert(isValidName(section));
|
|
|
|
const Section *s = getSection(section);
|
|
if (!s)
|
|
return false;
|
|
return s->hasKey(key);
|
|
}
|
|
|
|
void ConfigFile::removeKey(const String &key, const String §ion) {
|
|
assert(isValidName(key));
|
|
assert(isValidName(section));
|
|
|
|
Section *s = getSection(section);
|
|
if (s)
|
|
s->removeKey(key);
|
|
}
|
|
|
|
bool ConfigFile::getKey(const String &key, const String §ion, String &value) const {
|
|
assert(isValidName(key));
|
|
assert(isValidName(section));
|
|
|
|
const Section *s = getSection(section);
|
|
if (!s)
|
|
return false;
|
|
const KeyValue *kv = s->getKey(key);
|
|
if (!kv)
|
|
return false;
|
|
value = kv->value;
|
|
return true;
|
|
}
|
|
|
|
void ConfigFile::setKey(const String &key, const String §ion, const String &value) {
|
|
assert(isValidName(key));
|
|
assert(isValidName(section));
|
|
// TODO: Verify that value is valid, too. In particular, it shouldn't
|
|
// contain CR or LF...
|
|
|
|
Section *s = getSection(section);
|
|
if (!s) {
|
|
KeyValue newKV;
|
|
newKV.key = key;
|
|
newKV.value = value;
|
|
|
|
Section newSection;
|
|
newSection.name = section;
|
|
newSection.keys.push_back(newKV);
|
|
|
|
_sections.push_back(newSection);
|
|
} else {
|
|
s->setKey(key, value);
|
|
}
|
|
}
|
|
|
|
const ConfigFile::SectionKeyList ConfigFile::getKeys(const String §ion) const {
|
|
const Section *s = getSection(section);
|
|
|
|
return s->getKeys();
|
|
}
|
|
|
|
ConfigFile::Section *ConfigFile::getSection(const String §ion) {
|
|
for (List<Section>::iterator i = _sections.begin(); i != _sections.end(); ++i) {
|
|
if (section.equalsIgnoreCase(i->name)) {
|
|
return &(*i);
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
const ConfigFile::Section *ConfigFile::getSection(const String §ion) const {
|
|
for (List<Section>::const_iterator i = _sections.begin(); i != _sections.end(); ++i) {
|
|
if (section.equalsIgnoreCase(i->name)) {
|
|
return &(*i);
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
bool ConfigFile::Section::hasKey(const String &key) const {
|
|
return getKey(key) != 0;
|
|
}
|
|
|
|
const ConfigFile::KeyValue* ConfigFile::Section::getKey(const String &key) const {
|
|
for (List<KeyValue>::const_iterator i = keys.begin(); i != keys.end(); ++i) {
|
|
if (key.equalsIgnoreCase(i->key)) {
|
|
return &(*i);
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void ConfigFile::Section::setKey(const String &key, const String &value) {
|
|
for (List<KeyValue>::iterator i = keys.begin(); i != keys.end(); ++i) {
|
|
if (key.equalsIgnoreCase(i->key)) {
|
|
i->value = value;
|
|
return;
|
|
}
|
|
}
|
|
|
|
KeyValue newKV;
|
|
newKV.key = key;
|
|
newKV.value = value;
|
|
keys.push_back(newKV);
|
|
}
|
|
|
|
void ConfigFile::Section::removeKey(const String &key) {
|
|
for (List<KeyValue>::iterator i = keys.begin(); i != keys.end(); ++i) {
|
|
if (key.equalsIgnoreCase(i->key)) {
|
|
keys.erase(i);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
} // End of namespace Common
|