mirror of
https://github.com/libretro/scummvm.git
synced 2024-12-15 06:08:35 +00:00
9f93e5bb81
svn-id: r21472
359 lines
9.0 KiB
C++
359 lines
9.0 KiB
C++
/* ScummVM - Scumm Interpreter
|
|
* Copyright (C) 2005-2006 The ScummVM project
|
|
*
|
|
* 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.
|
|
*
|
|
* $URL$
|
|
* $Id$
|
|
*
|
|
*/
|
|
|
|
#include "common/stdafx.h"
|
|
|
|
#include "common/config-file.h"
|
|
#include "common/file.h"
|
|
#include "common/util.h"
|
|
|
|
#define MAXLINELEN 256
|
|
|
|
namespace Common {
|
|
|
|
static char *ltrim(char *t) {
|
|
while (isspace(*t))
|
|
t++;
|
|
return t;
|
|
}
|
|
|
|
static char *rtrim(char *t) {
|
|
int l = strlen(t) - 1;
|
|
while (l >= 0 && isspace(t[l]))
|
|
t[l--] = 0;
|
|
return t;
|
|
}
|
|
|
|
/**
|
|
* Check whether the given string is a valid section or key name.
|
|
* For that, it must only consist of letters, numbers, dashes and
|
|
* underscores. In particular, white space and "#", "=", "[", "]"
|
|
* are not valid!
|
|
*/
|
|
bool ConfigFile::isValidName(const Common::String &name) {
|
|
const char *p = name.c_str();
|
|
while (*p && (isalnum(*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.c_str(), File::kFileReadMode))
|
|
return loadFromStream(file);
|
|
else
|
|
return false;
|
|
}
|
|
|
|
bool ConfigFile::loadFromStream(SeekableReadStream &stream) {
|
|
char buf[MAXLINELEN];
|
|
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()) {
|
|
lineno++;
|
|
if (!stream.readLine(buf, MAXLINELEN))
|
|
break;
|
|
|
|
if (buf[0] == '#') {
|
|
// 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.
|
|
comment += buf;
|
|
comment += "\n";
|
|
} else if (buf[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 += buf;
|
|
comment += "\n";
|
|
} else if (buf[0] == '[') {
|
|
// It's a new section which begins here.
|
|
char *p = buf + 1;
|
|
// Get the section name, and check whether it's valid (that
|
|
// is, verify that it only consists of alphanumerics,
|
|
// dashes and underscores).
|
|
while (*p && (isalnum(*p) || *p == '-' || *p == '_'))
|
|
p++;
|
|
|
|
if (*p == '\0')
|
|
error("Config file buggy: missing ] in line %d", lineno);
|
|
else if (*p != ']')
|
|
error("Config file buggy: Invalid character '%c' occured in section name in line %d", *p, lineno);
|
|
|
|
*p = 0;
|
|
|
|
// Previous section is finished now, store it.
|
|
if (!section.name.empty())
|
|
_sections.push_back(section);
|
|
|
|
section.name = buf + 1;
|
|
section.keys.clear();
|
|
section.comment = comment;
|
|
comment.clear();
|
|
|
|
assert(isValidName(section.name));
|
|
} else {
|
|
// Skip leading & trailing whitespaces
|
|
char *t = rtrim(ltrim(buf));
|
|
|
|
// Skip empty lines
|
|
if (*t == 0)
|
|
continue;
|
|
|
|
// If no section has been set, this config file is invalid!
|
|
if (section.name.empty()) {
|
|
error("Config file buggy: Key/value pair found outside a section in line %d", lineno);
|
|
}
|
|
|
|
// Split string at '=' into 'key' and 'value'.
|
|
char *p = strchr(t, '=');
|
|
if (!p)
|
|
error("Config file buggy: Junk found in line line %d: '%s'", lineno, t);
|
|
*p = 0;
|
|
|
|
kv.key = rtrim(t);
|
|
kv.value = ltrim(p + 1);
|
|
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.ioFailed() || stream.eos());
|
|
}
|
|
|
|
bool ConfigFile::saveToFile(const String &filename) {
|
|
File file;
|
|
if (file.open(filename.c_str(), File::kFileWriteMode))
|
|
return saveToStream(file);
|
|
else
|
|
return false;
|
|
}
|
|
|
|
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');
|
|
}
|
|
}
|
|
|
|
return !stream.ioFailed();
|
|
}
|
|
|
|
|
|
void ConfigFile::removeSection(const String §ion) {
|
|
assert(isValidName(section));
|
|
for (List<Section>::iterator i = _sections.begin(); i != _sections.end(); ++i) {
|
|
if (!scumm_stricmp(section.c_str(), i->name.c_str())) {
|
|
_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);
|
|
Section *ns = getSection(newName);
|
|
if (ns) {
|
|
ns->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 (!scumm_stricmp(section.c_str(), i->name.c_str())) {
|
|
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 (!scumm_stricmp(section.c_str(), i->name.c_str())) {
|
|
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 (!scumm_stricmp(key.c_str(), i->key.c_str())) {
|
|
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 (!scumm_stricmp(key.c_str(), i->key.c_str())) {
|
|
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 (!scumm_stricmp(key.c_str(), i->key.c_str())) {
|
|
keys.erase(i);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
} // End of namespace Common
|