Logo Search packages:      
Sourcecode: scummvm version File versions  Download package

config-file.cpp

/* 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.
 *
 * $URL: https://scummvm.svn.sourceforge.net/svnroot/scummvm/scummvm/tags/release-1-1-1/common/config-file.cpp $
 * $Id: config-file.cpp 46746 2009-12-30 14:55:33Z mthreepwood $
 *
 */

#include "common/config-file.h"
#include "common/file.h"
#include "common/savefile.h"
#include "common/system.h"
#include "common/util.h"

#define MAXLINELEN 256

namespace Common {

/**
 * 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!
 */
00042 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() {
}

00055 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] == ';') {
                  // 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
                  // comment is 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(*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' occured 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(*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 &section) {
      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 &section) 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 &section) 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 &section) {
      assert(isValidName(key));
      assert(isValidName(section));

      Section *s = getSection(section);
      if (s)
             s->removeKey(key);
}

bool ConfigFile::getKey(const String &key, const String &section, 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 &section, 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 &section) const {
      const Section *s = getSection(section);

      return s->getKeys();
}

ConfigFile::Section *ConfigFile::getSection(const String &section) {
      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 &section) 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

Generated by  Doxygen 1.6.0   Back to index