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

profile.cc

/*
 * Copyright (C) 2002, 2006 by the Widelands Development Team
 *
 * 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., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */

#include "build_id.h"
#include "error.h"
#include "fileread.h"
#include "filewrite.h"
#include "i18n.h"
#include "profile.h"
#include "wexception.h"
#include "wlapplication.h"

#include <cstdarg>
#include <cctype>
#include <limits>
#include <string>

#include <stdio.h>

#define TRUE_WORDS 4
const char* trueWords[TRUE_WORDS] =
{
      "true",
      "yes",
      "on",
      "1"
};

#define FALSE_WORDS 4
const char* falseWords[FALSE_WORDS] =
{
      "false",
      "no",
      "off",
      "0"
};

Profile g_options(Profile::err_log);

/*
==============================================================================

Section::Value

==============================================================================
*/

Section::Value::Value(const char * const nname, const char * const nval) :
m_used(false), m_name(strdup(nname)), m_value(strdup(nval)) {}

Section::Value::Value(const Section::Value &o) :
m_used(o.m_used), m_name(strdup(o.m_name)), m_value(strdup(o.m_value)) {}

Section::Value::~Value()
{
      free(m_name);
      free(m_value);
}

Section::Value &Section::Value::operator=(const Section::Value &o)
{
      if (this != &o) {
            free(m_name);
            free(m_value);
            m_used = o.m_used;
            m_name = strdup(o.m_name);
            m_value = strdup(o.m_value);
      }
      return *this;
}

bool Section::Value::is_used() const
{
      return m_used;
}

void Section::Value::mark_used()
{
      m_used = true;
}

int Section::Value::get_int() const
{
      char *endp;
      int i;

      i = strtol(m_value, &endp, 0);

      if (*endp)
            throw wexception("%s: '%s' is not an integer", get_name(), m_value);

      return i;
}

float Section::Value::get_float() const
{
      char *endp;
      float f;

      f = strtod(m_value, &endp);

      if (*endp)
            throw wexception("%s: '%s' is not a float", get_name(), m_value);

      return f;
}

bool Section::Value::get_bool() const
{
      int i;
      for (i = 0; i < TRUE_WORDS; i++)
            if (!strcasecmp(m_value, trueWords[i]))
                  return true;
      for (i = 0; i < FALSE_WORDS; i++)
            if (!strcasecmp(m_value, falseWords[i]))
                  return false;

      throw wexception("%s: '%s' is not a boolean value", get_name(), m_value);
}

const char *Section::Value::get_string() const
{
      return m_value;
}

Point Section::Value::get_Point() const
{
      char * endp = m_value;
      const long int x = strtol(endp, &endp, 0);
      const long int y = strtol(endp, &endp, 0);
      if (*endp) throw wexception("%s: '%s' is not a Point", get_name(), m_value);

      return Point(x, y);
}

Coords Section::Value::get_Coords() const
{
      char * endp = m_value;
      const long int x = strtol(endp, &endp, 0);
      const long int y = strtol(endp, &endp, 0);
      if
            (x <  std::numeric_limits<X_Coordinate>::min() or
             x >= std::numeric_limits<X_Coordinate>::max() or
             y <  std::numeric_limits<Y_Coordinate>::min() or
             y >= std::numeric_limits<Y_Coordinate>::max() or
             *endp)
            throw wexception("%s: '%s' is not a Coords", get_name(), m_value);

      return Coords(x, y);
}

void Section::Value::set_string(const char *value)
{
      free(m_value);
      m_value = strdup(value);
}


/*
==============================================================================

Section

==============================================================================
*/

const char *Section::get_name() const {
      return m_section_name;
}

Section::Section(Profile * const prof, const char * const name) :
m_profile(prof), m_used(false), m_section_name(strdup(name)) {}

Section::Section(const Section & o) :
m_profile     (o.m_profile),
m_used        (o.m_used),
m_section_name(strdup(o.m_section_name)),
m_values      (o.m_values)
{assert(this != &o);}

Section::~Section() {free(m_section_name);}

Section &Section::operator=(const Section & o) {
      if (this != &o) {
            free(m_section_name);

            m_profile      = o.m_profile;
            m_used         = o.m_used;
            m_section_name = strdup(o.m_section_name);
            m_values       = o.m_values;
      }

      return *this;
}

/** Section::is_used()
 *
 */
00215 bool Section::is_used() const
{
      return m_used;
}

/** Section::mark_used()
 *
 */
00223 void Section::mark_used()
{
      m_used = true;
}

/** Section::check_used()
 *
 * Print a warning for every unused value.
 */
00232 void Section::check_used()
{
      const Value_list::const_iterator values_end = m_values.end();
      for
            (Value_list::const_iterator it = m_values.begin(); it != values_end; ++it)
            if (not it->is_used())
                  m_profile->error
                        ("Section [%s], key '%s' not used (did you spell the name "
                         "correctly?)",
                         get_name(),
                         it->get_name());
}

/** Section::get_val(const char *name)
 *
 * Returns the Value associated with the given keyname.
 *
 * Args: name  name of the key
 *
 * Returns: Pointer to the Value struct; 0 if the key doesn't exist.
 */
00253 Section::Value *Section::get_val(const char *name)
{
      for (Value_list::iterator v = m_values.begin(); v != m_values.end(); v++) {
            if (!strcasecmp(v->get_name(), name)) {
                  v->mark_used();
                  return &*v;
            }
      }

      return 0;
}

/** Section::get_next_val(const char *name)
 *
 * Returns the first unused value associated with the given keyname.
 *
 * Args: name  name of the key; can be 0 to find any key
 *
 * Returns: Pointer to the Value struct; 0 if no more key-value pairs are found
 */
00273 Section::Value *Section::get_next_val(const char *name)
{
      for (Value_list::iterator v = m_values.begin(); v != m_values.end(); v++) {
            if (v->is_used())
                  continue;
            if (!name || !strcasecmp(v->get_name(), name)) {
                  v->mark_used();
                  return &*v;
            }
      }

      return 0;
}

/** Section::create_val(const char *name, const char *value, bool duplicate = false)
 *
 * Set the given key. If duplicate is false, an old key with the given name is
 * replaced if possible.
 *
 * Unlike the set_*() class functions, it doesn't mark the key as used.
 */
00294 Section::Value *Section::create_val(const char *name, const char *value, bool duplicate)
{
      if (!duplicate) {
            for (Value_list::iterator old = m_values.begin(); old != m_values.end(); old++) {
                  if (!strcasecmp(old->get_name(), name)) {
                        old->set_string(value);
                        return &*old;
                  }
            }
      }

      Value v(name, value);
      m_values.push_back(v);
      return &m_values.back();
}

/** Section::get_safe_int(const char *name)
 *
 * Return the integer value of the given key or throw an exception if a
 * problem arises.
 */
00315 int Section::get_safe_int(const char *name)
{
      Value *v = get_val(name);
      if (!v)
            throw wexception("[%s]: missing integer key '%s'", get_name(), name);
      return v->get_int();
}

/** Section::get_safe_float(const char *name)
 *
 * Return the float value of the given key or throw an exception if a
 * problem arises.
 */
00328 float Section::get_safe_float(const char *name)
{
      Value *v = get_val(name);
      if (!v)
            throw wexception("[%s]: missing float key '%s'", get_name(), name);
      return v->get_float();
}

/** Section::get_safe_bool(const char *name)
 *
 * Return the boolean value of the given key or throw an exception if a
 * problem arises.
 */
00341 bool Section::get_safe_bool(const char *name)
{
      Value *v = get_val(name);
      if (!v)
            throw wexception("[%s]: missing boolean key '%s'", get_name(), name);
      return v->get_bool();
}

/** Section::get_safe_string(const char *name)
 *
 * Return the key value as a plain string or throw an exception if the key
 * doesn't exist
 */
00354 const char *Section::get_safe_string(const char *name)
{
      Value *v = get_val(name);
      if (!v)
            throw wexception("[%s]: missing key '%s'", get_name(), name);
      return v->get_string();
}

/** Section::get_safe_Coords(const char * const name)
 *
 * Return the key value as a Coords or throw an exception if the key
 * doesn't exist
 */
00367 Coords Section::get_safe_Coords(const char * const name) {
      const Value * const v = get_val(name);
      if (not v) throw wexception("[%s]: missing key '%s'", get_name(), name);
      return v->get_Coords();
}

/** Section::get_int(const char *name, int def)
 *
 * Returns the integer value of the given key. Falls back to a default value
 * if the key is not found.
 *
 * Args: name  name of the key
 *       def   fallback value
 *
 * Returns: the integer value of the key
 */
00383 int Section::get_int(const char *name, int def)
{
      Value *v = get_val(name);
      if (!v)
            return def;

      try {
            return v->get_int();
      } catch (std::exception &e) {
            m_profile->error("%s", e.what());
            return def;
      }
}

/** Section::get_float(const char *name, float def)
 *
 * Returns the float value of the given key. Falls back to a default value
 * if the key is not found.
 */
00402 float Section::get_float(const char *name, float def)
{
      Value *v = get_val(name);
      if (!v)
            return def;

      try {
            return v->get_float();
      } catch (std::exception &e) {
            m_profile->error("%s", e.what());
            return def;
      }
}

/** Section::get_bool(const char *name, bool def)
 *
 * Returns the boolean value of the given key. Falls back to a default value
 * if the key is not found.
 *
 * Args: name  name of the key
 *       def   fallback value
 *
 * Returns: the boolean value of the key
 */
00426 bool Section::get_bool(const char *name, bool def)
{
      Value *v = get_val(name);
      if (!v)
            return def;

      try {
            return v->get_bool();
      } catch (std::exception &e) {
            m_profile->error("%s", e.what());
            return def;
      }
}

/** Section::get_string(const char *name, const char *def)
 *
 * Returns the value of the given key. Falls back to a default value if the
 * key is not found.
 *
 * Args: name  name of the key
 *       def   fallback value
 *
 * Returns: the string associated with the key; never returns 0 if the key
 *          has been found
 */
00451 const char *Section::get_string(const char *name, const char *def)
{
      Value *v = get_val(name);
      if (!v)
            return def;
      return v->get_string();
}

Point Section::get_Point(const char * const name, const Point def)
{const Value * const v = get_val(name); return v ? v->get_Point() : def;}

/** Section::get_Coords(const char * const name, const Coords def)
 *
 * Returns the value of the given key. Falls back to a default value if the
 * key is not found.
 *
 * Args: name  name of the key
 *       def   fallback value
 *
 * Returns: the Coords value of the key
 */
00472 Coords Section::get_Coords(const char * const name, Coords def) {
      const Value * const v = get_val(name);
      if (not v) return def;
      return v->get_Coords();
}

/** Section::get_next_int(const char *name, int *value)
 *
 * Retrieve the next unused key with the given name as an integer.
 *
 * Args: name   name of the key, can be 0 to find all unused keys
 *       value  value of the key is stored here
 *
 * Returns: the name of the key, or 0 if none has been found
 */
00487 const char *Section::get_next_int(const char *name, int *value)
{
      Value *v = get_next_val(name);
      if (!v)
            return 0;

   if (value)
      *value = v->get_int();
      return v->get_name();
}

/** Section::get_next_float(const char *name, float *value)
 *
 * Retrieve the next unused key with the given name as a float.
 */
00502 const char *Section::get_next_float(const char *name, float *value)
{
      Value *v = get_next_val(name);
      if (!v)
            return 0;

   if (value)
      *value = v->get_float();
      return v->get_name();
}

/** Section::get_next_bool(const char *name, int *value)
 *
 * Retrieve the next unused key with the given name as a boolean value.
 *
 * Args: name   name of the key, can be 0 to find all unused keys
 *       value  value of the key is stored here
 *
 * Returns: the name of the key, or 0 if none has been found
 */
00522 const char *Section::get_next_bool(const char *name, bool *value)
{
      Value *v = get_next_val(name);
      if (!v)
            return 0;

   if (value)
      *value = v->get_bool();
      return v->get_name();
}

/** Section::get_next_string(const char *name, int *value)
 *
 * Retrieve the next unused key with the given name.
 *
 * \param name   name of the key, can be 0 to find all unused keys
 * \param value  value of the key is stored here
 *
 * \return the name of the key, or 0 if none has been found
 */
00542 const char *Section::get_next_string(const char *name, const char **value)
{
      Value *v = get_next_val(name);
      if (!v)
            return 0;

   if (value)
      *value = v->get_string();
      return v->get_name();
}

/** Section::get_next_Coords(const char * const name, Coords & value)
 *
 * Retrieve the next unused key with the given name as a Coords value.
 *
 * Args: name   name of the key, can be 0 to find all unused keys
 *       value  value of the key is stored here
 *
 * Returns: the name of the key, or 0 if none has been found
 */
const char * Section::get_next_Coords
00563 (const char * const name, Coords * const value)
{
      const Value * const v = get_next_val(name);
      if (not v) return 0;
      if (value) *value = v->get_Coords();
      return v->get_name();
}

/** Section::set_int(const char *name, int value, bool duplicate = false)
 *
 * Modifies/Creates the given key.
 * If duplicate is true, a duplicate key will be created if the key already
 * exists.
 */
00577 void Section::set_int(const char *name, int value, bool duplicate)
{
      char buffer[32];

      snprintf(buffer, sizeof(buffer), "%i", value);
      create_val(name, buffer, duplicate)->mark_used();
}

/** Section::set_float(const char *name, float value, bool duplicate = false)
 *
 * Modifies/Creates the given key.
 * If duplicate is true, a duplicate key will be created if the key already
 * exists.
 */
00591 void Section::set_float(const char *name, float value, bool duplicate)
{
      char buffer[64];

      snprintf(buffer, sizeof(buffer), "%f", value);
      create_val(name, buffer, duplicate)->mark_used();
}

/** Section::set_bool(const char *name, bool value, bool duplicate = false)
 *
 * Modifies/Creates the given key.
 * If duplicate is true, a duplicate key will be created if the key already
 * exists.
 */
00605 void Section::set_bool(const char *name, bool value, bool duplicate)
{
      const char *string;

      string = value ? "true" : "false";
      create_val(name, string, duplicate)->mark_used();
}

/** Section::set_string(const char *name, const char *string, bool duplicate = false)
 *
 * Modifies/Creates the given key.
 * If duplicate is true, a duplicate key will be created if the key already
 * exists.
 */
00619 void Section::set_string(const char *name, const char *string, bool duplicate)
{
      create_val(name, string, duplicate)->mark_used();
}

/** Section::set_Coords(const char * const name, const Coords value, const bool duplicate = false)
 *
 * Modifies/Creates the given key.
 * If duplicate is true, a duplicate key will be created if the key already
 * exists.
 */
void Section::set_Coords
00631 (const char * const name, const Coords value, const bool duplicate)
{
      char buffer[64];

      snprintf(buffer, sizeof(buffer), "%i %i", value.x, value.y);
      create_val(name, buffer, duplicate)->mark_used();
}


/*
==============================================================================

Profile

==============================================================================
*/

/** Profile::Profile(int error_level = err_throw)
 *
 * Create an empty profile
 */
00652 Profile::Profile(int error_level)
{
      m_error_level = error_level;
}

/** Profile::Profile(const char* filename, const char *global_section = 0, int error_level = err_throw)
 *
 * Parses an ini-style file into sections and key-value pairs.
 * If global_section is not null, keys outside of sections are put into a section
 * of that name.
 */
00663 Profile::Profile(const char* filename, const char *global_section, int error_level)
{
      m_error_level = error_level;
      read(filename, global_section);
}

/** Profile::~Profile()
 *
 * Free allocated resources
 */
00673 Profile::~Profile()
{
}

/** Profile::error(const char *fmt, ...)
 *
 * Output an error message.
 * Depending on the error level, it is thrown as a wexception, logged or ignored.
 */
00682 void Profile::error(const char *fmt, ...) const
{
      if (m_error_level == err_ignore)
            return;

      char buffer[256];
      va_list va;

      va_start(va, fmt);
      vsnprintf(buffer, sizeof(buffer), fmt, va);
      va_end(va);

      if (m_error_level == err_log) log("%s\n", buffer);
      else throw wexception("%s", buffer);
}

/** Profile::check_used()
 *
 * Signal an error if a section or key hasn't been used.
 */
00702 void Profile::check_used()
{
      for (Section_list::iterator s = m_sections.begin(); s != m_sections.end(); s++) {
            if (!s->is_used())
                  error("Section [%s] not used (did you spell the name correctly?)", s->get_name());
            else
                  s->check_used();
      }
}

/** Profile::get_section(const char *name)
 *
 * Retrieve the first section of the given name and mark it used.
 *
 * Args: name  name of the section
 *
 * Returns: pointer to the section (or 0 if the section doesn't exist)
 */
00720 Section *Profile::get_section(const char *name)
{
      for (Section_list::iterator s = m_sections.begin(); s != m_sections.end(); s++) {
            if (!strcasecmp(s->get_name(), name)) {
                  s->mark_used();
                  return &*s;
            }
      }

      return 0;
}

/** Profile::get_safe_section(const char *name)
 *
 * Safely get a section of the given name.
 * If the section doesn't exist, an exception is thrown.
 */
00737 Section *Profile::get_safe_section(const char *name)
{
      Section *s = get_section(name);
      if (!s)
            throw wexception("Section [%s] not found", name);
      return s;
}

/** Profile::pull_section(const char *name)
 *
 * Safely get a section of the given name.
 * If the section doesn't exist, it is created.
 * Similar to create_section(), but the section is marked as used.
 */
00751 Section *Profile::pull_section(const char *name)
{
      Section *s = create_section(name);
      s->mark_used();
      return s;
}

/** Profile::get_next_section(const char *name)
 *
 * Retrieve the next unused section of the given name and mark it used.
 *
 * Args: name  name of the section; can be 0 to retrieve any unused section
 *
 * Returns: pointer to the section (or 0 if the section doesn't exist)
 */
00766 Section *Profile::get_next_section(const char *name)
{
      for (Section_list::iterator s = m_sections.begin(); s != m_sections.end(); s++) {
            if (s->is_used())
                  continue;
            if (!name || !strcasecmp(s->get_name(), name)) {
                  s->mark_used();
                  return &*s;
            }
      }

      return 0;
}

/** Profile::create_section(const char *name, bool duplicate = false)
 *
 * Create a section of the given name.
 * If duplicate is true, a duplicate section may be created. Otherwise, a
 * pointer to an existing section is returned.
 */
00786 Section *Profile::create_section(const char *name, bool duplicate)
{
      if (!duplicate) {
            for (Section_list::iterator s = m_sections.begin(); s != m_sections.end(); s++) {
                  if (!strcasecmp(s->get_name(), name))
                        return &*s;
            }
      }

      m_sections.push_back(Section(this, name));
      return &m_sections.back();
}

inline char *skipwhite(char *p)
{
      while (*p && isspace(*p))
            p++;
      return p;
}

inline void rtrim(char *str)
{
      char *p;
      for (p = strchr(str, 0); p > str; p--) {
            if (!isspace(*(p-1)))
                  break;
      }
      *p = 0;
}

inline void killcomments(char *p)
{
      while (*p) {
            if (p[0] == '#' || (p[0] == '/' && p[1] == '/')) {
                  p[0] = 0;
                  break;
            }
            p++;
      }
}

inline char *setEndAt(char *str, char c)
{
      char* s = strchr(str, c);
      if (s)
            s[0] = 0;
      return str;
}

/**
 * Parses an ini-style file into sections and key values. If a section or
 * key name occurs multiple times, an additional entry is created.
 *
 * Args: filename  name of the source file
 */
void Profile::read
00842 (const char * const filename,
 const char * const global_section,
 FileSystem & fs)
{
      try {
            FileRead fr;
            fr.Open(fs, filename);

            // line can become quite big. But this should be enough
            //  Previously this was allocated with new and then simply leaked.
            //  We (Valgrind and I) did not like that.
            char buffer[1024 * 30];
            const char * const buf_end = buffer + sizeof(buffer);
            char * line = buffer;
            char *p = 0;
            uint linenr = 0;
            Section *s = 0;

            bool reading_multiline = 0;
            std::string data;
            std::string key;
            bool translate_line = false;
            while (fr.ReadLine(line, buf_end)) {
                  linenr++;

                  if (!reading_multiline)
                        p = line;

                  p = skipwhite(p);
                  if (!p[0] || p[0] == '#' || (p[0] == '/' && p[1] == '/'))
                        continue;

                  if (p[0] == '[') {
                        p++;
                        setEndAt(p, ']');
                        s = create_section(p, true); // may create duplicate
                  } else {
                        char* tail = 0;
                        translate_line = false;
                        if (reading_multiline) {
                              // Note: comments are killed by walking backwards into the string
                              rtrim(p);
                              while (*line != '\'' && *line != '"') {
                                    if (*line == 0)
                                          throw wexception
                                                ("line %i: runaway multiline string", linenr);
                                    if (*line == '_')
                                          translate_line = true;
                                    line++;
                              }

                              // skip " or '
                              line++;

                              char *eot = line+strlen(line)-1;
                              while (*eot != '"' && *eot != '\'') {
                                    *eot = 0;
                                    --eot;
                              }
                              // NOTE: we leave the last '"' and do not remove them
                              tail = line;
                        } else {
                              tail = strchr(p, '=');
                              if (!tail)
                                    throw wexception("line %i: invalid syntax: %s", linenr, line);
                              *tail++ = 0;
                              key = p;
                              if (*tail == '_') {
                                    tail+= 1; // skip =_, which is only used for translations
                                    translate_line = true;
                              }
                              tail = skipwhite(tail);
                              killcomments(tail);
                              rtrim(tail);
                              rtrim(p);

                              // first, check for multiline string
                              if
                                    (strlen(tail) >= 2
                                     and
                                     ((tail[0] == '\'' or tail[0] == '\"')
                                      and
                                      (tail[1] == '\'' or tail[1] == '\"')))
                              {
                                    reading_multiline = true;
                                    tail += 2;
                              }

                              // then remove surrounding '' or ""
                              if (tail[0] == '\'' || tail[0] == '\"') {
                                    tail++;
                              }
                        }
                        if (tail) {
                              char *eot = tail+strlen(tail)-1;
                              if (*eot == '\'' || *eot == '\"') {
                                    *eot = 0;
                                    if (strlen(tail)) {
                                          char *eot2 = tail+strlen(tail)-1;
                                          if (*eot2 == '\'' || *eot2 == '\"') {
                                                reading_multiline = false;
                                                *eot2 = 0;
                                          }
                                    }
                              }

                              // ready to insert
                              if (!s) {
                                    if (global_section)
                                          s = create_section(global_section, true);
                                    else
                                          throw wexception("line %i: key %s outside section", linenr, p);
                              }

                              if (translate_line && strlen(tail))
                                    data += i18n::translate(tail);
                              else
                                    data += tail;
                              if (s && ! reading_multiline) { // error() may or may not throw
                                    s->create_val(key.c_str(), data.c_str(), true); // may create duplicate
                                    data = "";
                              }
                        } else {
                              throw wexception("line %i: syntax error", linenr);
                        }
                  }
            }
      }
      catch (FileNotFound_error &e) {
            //It's no problem if the config file does not exist. (It'll get
            //written on exit anyway)
            log("There's no configuration file, using default values.\n");
      }
      catch (std::exception &e) {
            error("%s: %s", filename, e.what());
      }
}

/**
 * Writes all sections out to the given file.
 * If used_only is true, only used sections and keys are written to the file.
 */
void Profile::write
00985 (const char * const filename, const bool used_only, FileSystem & fs)
{
      FileWrite fw;

      fw.Printf("# Automatically created by Widelands " BUILD_ID "\n\n");

      for (Section_list::iterator s = m_sections.begin(); s != m_sections.end(); s++) {
            if (used_only && !s->is_used())
                  continue;

            fw.Printf("[%s]\n", s->get_name());

            for (Section::Value_list::iterator v = s->m_values.begin(); v != s->m_values.end(); v++) {
                  if (used_only && !v->is_used())
                        continue;

         const char* str = v->get_string();
         bool multiline = false;
         for (uint i = 0; i < strlen(str); i++)
            if (str[i] == '\n')
               multiline = true;

         if (!multiline)
            fw.Printf("%s=%s\n", v->get_name(), v->get_string());
         else
            fw.Printf("%s='%s'\n", v->get_name(), v->get_string());
            }

            fw.Printf("\n");
      }

      fw.Write(fs, filename);
}


Generated by  Doxygen 1.6.0   Back to index