/*   EXTRAITS DE LA LICENCE
	Copyright CEA, contributeurs : Luc BILLARD et Damien
	CALISTE, laboratoire L_Sim, (2001-2005)
  
	Adresse mèl :
	BILLARD, non joignable par mèl ;
	CALISTE, damien P caliste AT cea P fr.

	Ce logiciel est un programme informatique servant à visualiser des
	structures atomiques dans un rendu pseudo-3D. 

	Ce logiciel est régi par la licence CeCILL soumise au droit français et
	respectant les principes de diffusion des logiciels libres. Vous pouvez
	utiliser, modifier et/ou redistribuer ce programme sous les conditions
	de la licence CeCILL telle que diffusée par le CEA, le CNRS et l'INRIA 
	sur le site "http://www.cecill.info".

	Le fait que vous puissiez accéder à cet en-tête signifie que vous avez 
	pris connaissance de la licence CeCILL, et que vous en avez accepté les
	termes (cf. le fichier Documentation/licence.fr.txt fourni avec ce logiciel).
*/

/*   LICENCE SUM UP
	Copyright CEA, contributors : Luc BILLARD et Damien
	CALISTE, laboratoire L_Sim, (2001-2005)

	E-mail address:
	BILLARD, not reachable any more ;
	CALISTE, damien P caliste AT cea P fr.

	This software is a computer program whose purpose is to visualize atomic
	configurations in 3D.

	This software is governed by the CeCILL  license under French law and
	abiding by the rules of distribution of free software.  You can  use,
	modify and/ or redistribute the software under the terms of the CeCILL
	license as circulated by CEA, CNRS and INRIA at the following URL
	"http://www.cecill.info".

	The fact that you are presently reading this means that you have had
	knowledge of the CeCILL license and that you accept its terms. You can
	find a copy of this licence shipped with this software at Documentation/licence.en.txt.
*/

#include "visu_configFile.h"

#include "visu_tools.h"
#include "visu_basic.h"
#include "visu_commandLine.h"
#include "coreTools/toolConfigFile.h"

#include <stdlib.h>
#include <string.h>
#include <unistd.h> /* For the access markers R_OK, W_OK ... */

/**
 * SECTION:visu_configFile
 * @short_description: Defines methods to access (read/write) to
 * config files and to create different entries.
 *
 * <para>V_Sim uses two different configuration files. The first
 * stores the configuration of the program itself and is called
 * parameters file. The second stores differents values that control
 * the way files are rendered. It is called resources file. For
 * example, their is an entry in the parameters file that controls
 * your favorite rendering method ; and there is an entry in the
 * resources file that codes that vacancy elements are rendered by
 * cube in the atomic rendering method. Most methods of this part uses
 * a first argument usually called 'kind', that control if the method
 * will apply on the parameters file or on the resources
 * file. #VISU_CONFIG_FILE_PARAMETER and #VISU_CONFIG_FILE_RESOURCE are
 * the two flags that should be used for the 'kind' argument.</para>
 *
 * <para>There are different paths where these files can be
 * stored. These paths are stored in V_Sim with an order : for example
 * parameters file are first looked for in the current working
 * directory, then in the $HOME/.v_sim directory and finally in the
 * installation directory. This is transparent for the user and
 * visu_config_file_getValidPath() is the right method to access to the
 * best readable configuration file.</para>
 *
 * <para>Different part of V_Sim can add entries in these files. The
 * method visu_config_file_addEntry() is designed to this purpose. The
 * entries are defined by their name and they appear in the
 * configuration file as 'name:' followed by the data associated to
 * this entry. In the parameters file, the data are on the same
 * line. In the resources file, the data begin the line after and can
 * be longer that one line. When a configuration file is read, the
 * method associated to each entry (VisuConfigFileReadFunc()) is
 * called with a copy of their data lines. The method
 * visu_config_file_addExportFunction() should be used to add a callback
 * when the configurations files are written, then each part of V_Sim
 * that have entries can put some lines in the configuration
 * files.</para>
 */

#define PARAMETER_HEADER     "#V_Sim parameters file"
#define RESOURCE_HEADER      "#V_Sim resources file"
#define VERSION_HEADER       "3.0"

static const gchar *RESOURCES_FILENAMES[] = {"v_sim.res.xml", "v_sim.res", (gchar*)0};
static const gchar *PARAMETERS_FILENAMES[] = {"v_sim.par", (gchar*)0};

#define FLAG_RESOURCES_PATH "main_resourcesPath"
#define DESC_RESOURCES_PATH "Favorite paths to find and save the resources file ; chain[:chain]"
#define DEFAULT_RESOURCES_PATH ""
static gboolean readResourcesPaths(VisuConfigFileEntry *entry, gchar **lines, int nbLines, int position,
				   GError **error);
static void exportResourcesPaths(GString *data, VisuData *dataObj);
static void _addPath(VisuConfigFile *conf, char* dir);

/**
 * VisuConfigFileEntry:
 *
 * This is the common name of the structure.
 */
struct _VisuConfigFileEntry
{
  guint refCount;

  /* Name of the key. */
  gchar *key;
  gchar *description;
  GQuark kquark; /* Quark associated to the key. */
  /* Version, default is 3.0. */
  float version;
  /* If set, entry is obsolete and newKey should replaces it. */
  gchar *newKey;

  /* A parameter or a resource */
  int kind;

  /* Number of line used by this resources.
     This is not used if the entry is a parameter
     since, parameters are on the same line than
     the key and are one line. */
  guint nbLines;

  /* This method is called when a file is read
     and the entry is found. */
  VisuConfigFileReadFunc read;
  gboolean withLabel;
  gchar *label;
  guint nValues;
  guint iToken;
  gchar **tokens;
  gpointer storage;
  union {
    int i[2];
    float f[2];
  } range;
  VisuConfigFileEnumFunc toEnum;
  gchar *errMess;

  /* Tag, tags are used to ignore or not some
     entries when a file is read. */
  gchar *tag;
};

typedef enum
  {
    _format_raw,
    _format_xml
  } _format_export;
_format_export format = _format_raw;
struct writeFunc_struct
{
  VisuConfigFileExportFunc writeFunc;
};

/* This hashtable stores all the known entries.
   The keys are the name of the entry (its key), and
   the value is a pointer to a VisuConfigFileEntry. */
static GHashTable *knownTags = NULL;
static VisuConfigFile *resources = NULL, *parameters = NULL;

/* Local methods. */
static VisuConfigFileEntry* entry_ref(VisuConfigFileEntry *entry);
static void entry_unref(VisuConfigFileEntry *entry);

/* Generic reading routines. */
static gboolean _readTokens(VisuConfigFileEntry *entry, gchar **lines, int nbLines,
                            int position, GError **error);
static gboolean _readBooleanv(VisuConfigFileEntry *entry, gchar **lines, int nbLines,
                              int position, GError **error);
static gboolean _readIntv(VisuConfigFileEntry *entry, gchar **lines, int nbLines,
                          int position, GError **error);
static gboolean _readFloatv(VisuConfigFileEntry *entry, gchar **lines, int nbLines,
                            int position, GError **error);
static gboolean _readStipplev(VisuConfigFileEntry *entry, gchar **lines, int nbLines,
                              int position, GError **error);
static gboolean _readEnum(VisuConfigFileEntry *entry, gchar **lines, int nbLines,
                          int position, GError **error);
static gboolean _readString(VisuConfigFileEntry *entry, gchar **lines, int nbLines,
                            int position, GError **error);

static gint compareStringsInGList(gconstpointer a, gconstpointer b)
{
  return strcmp((char*)a, (char*)b);
}

/**
 * visu_config_file_entry_get_type:
 * 
 * Create and retrieve a #GType for a #VisuConfigFileEntry object.
 *
 * Since: 3.7
 *
 * Returns: a new type for #VisuConfigFileEntry structures.
 */
GType visu_config_file_entry_get_type(void)
{
  static GType g_define_type_id = 0;

  if (g_define_type_id == 0)
    g_define_type_id =
      g_boxed_type_register_static("VisuConfigFileEntry", 
                                   (GBoxedCopyFunc)entry_ref,
                                   (GBoxedFreeFunc)entry_unref);
  return g_define_type_id;
}
static VisuConfigFileEntry* entry_init(const gchar *key, const gchar *description,
                                       VisuConfigFileKind kind, guint nbLines)
{
  VisuConfigFileEntry *entry;

  g_return_val_if_fail(key && *key, (VisuConfigFileEntry*)0);
  g_return_val_if_fail(description, (VisuConfigFileEntry*)0);
  g_return_val_if_fail(nbLines > 0 && (kind == VISU_CONFIG_FILE_KIND_PARAMETER ||
				       kind == VISU_CONFIG_FILE_KIND_RESOURCE),
		       (VisuConfigFileEntry*)0);

  entry = g_malloc(sizeof(VisuConfigFileEntry));
  entry->refCount = 1;
  entry->key = g_strdup(key);
  entry->kquark = g_quark_from_static_string(entry->key);
  entry->description = g_strdup(description);
  entry->kind = kind;
  if (kind == VISU_CONFIG_FILE_KIND_PARAMETER)
    entry->nbLines = 1;
  else
    entry->nbLines = nbLines;
  entry->withLabel = FALSE;
  entry->label = (gchar*)0;
  entry->storage = (gpointer)0;
  entry->tokens = (gchar**)0;
  entry->errMess = (gchar*)0;
  entry->tag = (gchar*)0;
  entry->newKey = (gchar*)0;
  entry->version = 3.0f;
  
  return entry;
}
static VisuConfigFileEntry* entry_ref(VisuConfigFileEntry *entry)
{
  if (!entry)
    return (VisuConfigFileEntry*)0;

  entry->refCount += 1;
  return entry;
}
static void entry_unref(VisuConfigFileEntry *entry)
{
  entry->refCount -= 1;
  if (!entry->refCount)
    {
      g_free(entry->key);
      g_free(entry->description);
      g_free(entry->newKey);
      g_free(entry->tag);
      g_free(entry->label);
      g_free(entry->errMess);
      g_strfreev(entry->tokens);
      g_free(entry);
    }
}

/**
 * VisuConfigFile:
 *
 * Structure used to define #VisuConfigFile objects.
 *
 * Since: 3.8
 */
struct _VisuConfigFilePrivate
{
  VisuConfigFileKind kind;

  /* This hashtable stores all the known entries.
     The keys are the name of the entry (its key), and
     the value is a pointer to a VisuConfigFileEntry. */
  GHashTable *entryList;
  GList *exportList;

  /* Store the paths to where it is possible to store
     and/or read resource files. This list is ordered
     and first element is the most prefered path. */
  GList *paths;
  gchar *source;
};

enum
  {
    ENTRYPARSED_SIGNAL,
    VISU_NB_SIGNAL
  };

static guint _signals[VISU_NB_SIGNAL];

static void visu_config_file_finalize    (GObject* obj);

G_DEFINE_TYPE_WITH_CODE(VisuConfigFile, visu_config_file, VISU_TYPE_OBJECT,
                        G_ADD_PRIVATE(VisuConfigFile))

static void visu_config_file_class_init(VisuConfigFileClass *klass)
{
  /* Connect the overloading methods. */
  G_OBJECT_CLASS(klass)->finalize     = visu_config_file_finalize;

  /**
   * VisuConfigFile::parsed:
   * @visuObj: the object emitting the signal.
   * @key: the key that has been parsed.
   *
   * The entry @key of a configuration file has just been successfully parsed.
   *
   * Since: 3.7
   */
  _signals[ENTRYPARSED_SIGNAL] = 
    g_signal_new("parsed", G_TYPE_FROM_CLASS (klass),
                 G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS | G_SIGNAL_DETAILED,
                 0 , NULL, NULL, g_cclosure_marshal_VOID__BOXED,
                 G_TYPE_NONE, 1, VISU_TYPE_CONFIG_FILE_ENTRY);

  knownTags = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, NULL);
}
static void visu_config_file_init(VisuConfigFile *obj)
{
  obj->priv = visu_config_file_get_instance_private(obj);

  obj->priv->entryList = g_hash_table_new_full(g_str_hash, g_str_equal,
                                               NULL, (GDestroyNotify)entry_unref);
  obj->priv->exportList = (GList*)0;

  obj->priv->source = (gchar*)0;

  obj->priv->paths = (GList*)0;
  obj->priv->paths = g_list_prepend(obj->priv->paths, g_strdup(V_SIM_DATA_DIR));
  obj->priv->paths = g_list_prepend(obj->priv->paths, g_strdup(V_SIM_OLD_LOCAL_CONF_DIR));
  obj->priv->paths = g_list_prepend(obj->priv->paths, g_strdup(V_SIM_LOCAL_CONF_DIR));
  obj->priv->paths = g_list_prepend(obj->priv->paths, g_get_current_dir());
}
static void visu_config_file_finalize(GObject *obj)
{
  VisuConfigFile *conf;

  conf = VISU_CONFIG_FILE(obj);

  g_hash_table_destroy(conf->priv->entryList);
  g_list_free(conf->priv->exportList);
  g_list_free_full(conf->priv->paths, g_free);
  g_free(conf->priv->source);

  /* Chain up to the parent class */
  G_OBJECT_CLASS(visu_config_file_parent_class)->finalize(obj);
}
/**
 * visu_config_file_getStatic:
 * @kind: a kind of configuration file.
 *
 * Retrieve the instance used by V_Sim to read resource or parameter files.
 *
 * Since: 3.8
 *
 * Returns: (transfer none): a #VisuConfigFile object, owned by V_Sim.
 **/
VisuConfigFile* visu_config_file_getStatic(VisuConfigFileKind kind)
{
  if (kind == VISU_CONFIG_FILE_KIND_PARAMETER)
    {
      if (!parameters)
        {
          parameters = g_object_new(VISU_TYPE_CONFIG_FILE, NULL);
          parameters->priv->kind = VISU_CONFIG_FILE_KIND_PARAMETER;
          /* Private data. */
          visu_config_file_addEntry(parameters,
                                    FLAG_RESOURCES_PATH,
                                    DESC_RESOURCES_PATH,
                                    1, readResourcesPaths);
          visu_config_file_addExportFunction(parameters, exportResourcesPaths);
        }
      return parameters;
    }
  if (kind == VISU_CONFIG_FILE_KIND_RESOURCE)
    {
      if (!resources)
        {
          resources = g_object_new(VISU_TYPE_CONFIG_FILE, NULL);
          resources->priv->kind = VISU_CONFIG_FILE_KIND_RESOURCE;
        }
      return resources;
    }
  return (VisuConfigFile*)0;
}

static gboolean entry_register(VisuConfigFile *conf, VisuConfigFileEntry *entry)
{
  DBG_fprintf(stderr, "Visu ConfigFile: going to add key '%s'.\n", entry->key);
  if (g_hash_table_lookup(conf->priv->entryList, (gpointer)entry->key))
    return FALSE;

  /* Store it. */
  g_hash_table_insert(conf->priv->entryList, (gpointer)entry->key, (gpointer)entry);

  return TRUE;
}
/**
 * visu_config_file_ignoreEntry:
 * @conf: a #VisuConfigFile object ;
 * @key: a string (should not be NULL) ;
 * @nbLines: an integer ;
 *
 * Create a #VisuConfigFileEntry that will ignore @key when found in a
 * configuration file. This is used for deprecated keys.
 *
 * Since: 3.8
 *
 * Returns: the newly created #VisuConfigFileEntry object.
 **/
VisuConfigFileEntry* visu_config_file_ignoreEntry(VisuConfigFile *conf, const gchar *key,
                                                  guint nbLines)
{
  VisuConfigFileEntry *entry;

  g_return_val_if_fail(VISU_IS_CONFIG_FILE(conf), (VisuConfigFileEntry*)0);

  entry = entry_init(key, "Ignored entry", conf->priv->kind, nbLines);
  if (!entry)
    return (VisuConfigFileEntry*)0;
  entry->read = NULL;

  if (!entry_register(conf, entry))
    {
      g_free(entry);
      entry = (VisuConfigFileEntry*)0;
      g_warning("entry '%s' already exists!", key);
    }

  return entry;
}
/**
 * visu_config_file_addEntry:
 * @conf: a #VisuConfigFile object ;
 * @key: a string (should not be NULL) ;
 * @description: (allow-none): a string (can be NULL) ;
 * @nbLines: an integer ;
 * @readFunc: (scope call): a VisuConfigFileReadFunc.
 *
 * This creates a new #VisuConfigFileEntry object with the given
 * values. The key and description arguments are copied.
 *
 * Returns: the newly created #VisuConfigFileEntry object.
 */
VisuConfigFileEntry* visu_config_file_addEntry(VisuConfigFile *conf, const gchar *key,
                                               const gchar* description, int nbLines,
                                               VisuConfigFileReadFunc readFunc)
{
  VisuConfigFileEntry *entry;

  g_return_val_if_fail(VISU_IS_CONFIG_FILE(conf), (VisuConfigFileEntry*)0);

  entry = entry_init(key, description, conf->priv->kind, nbLines);
  if (!entry)
    return (VisuConfigFileEntry*)0;
  entry->read = readFunc;

  if (!entry_register(conf, entry))
    {
      g_free(entry);
      entry = (VisuConfigFileEntry*)0;
      g_warning("entry '%s' already exists!", key);
    }

  return entry;
}
/**
 * visu_config_file_addTokenizedEntry:
 * @conf: a #VisuConfigFile object ;
 * @key: a string (should not be %NULL) ;
 * @description: (allow-none): a string (can be %NULL) ;
 * @labelled: a boolean.
 *
 * Defines a new #VisuConfigFileEntry object characterized by
 * @key. When @key is found in a configuration file, the data line is
 * separated into tokens that can be retrieved later with
 * visu_config_file_entry_popToken() for instance. If @labelled is
 * %TRUE, the associated label to an entry in the file can be later
 * retrieved with visu_config_file_entry_getLabel().
 *
 * Since: 3.8
 *
 * Returns: the newly created #VisuConfigFileEntry object.
 **/
VisuConfigFileEntry* visu_config_file_addTokenizedEntry(VisuConfigFile *conf, const gchar *key,
                                                        const gchar* description,
                                                        gboolean labelled)
{
  VisuConfigFileEntry *entry;

  g_return_val_if_fail(VISU_IS_CONFIG_FILE(conf), (VisuConfigFileEntry*)0);

  entry = entry_init(key, description, conf->priv->kind, 1);
  if (!entry)
    return (VisuConfigFileEntry*)0;
  entry->read = _readTokens;
  entry->withLabel = labelled;

  if (!entry_register(conf, entry))
    {
      g_free(entry);
      entry = (VisuConfigFileEntry*)0;
      g_warning("entry '%s' already exists!", key);
    }

  return entry;
}
/**
 * visu_config_file_addBooleanEntry:
 * @conf: a #VisuConfigFile object ;
 * @key: a string (should not be NULL) ;
 * @description: (allow-none): a string (can be NULL) ;
 * @location: a pointer where to store a boolean when the entry is
 * parsed.
 * @labelled: a boolean.
 *
 * Defines a #VisuConfigFileEntry that will be a single boolean to
 * read and to store in @location. If @labelled is %TRUE, it retrieves
 * and store a string before the boolean value. It can be accessed
 * later with visu_config_file_entry_getLabel().
 *
 * Since: 3.7
 *
 * Returns: (transfer full): the newly created #VisuConfigFileEntry object.
 **/
VisuConfigFileEntry* visu_config_file_addBooleanEntry(VisuConfigFile *conf, const gchar *key,
                                                      const gchar* description,
                                                      gboolean *location, gboolean labelled)
{
  VisuConfigFileEntry *entry;

  g_return_val_if_fail(location, (VisuConfigFileEntry*)0);
  g_return_val_if_fail(VISU_IS_CONFIG_FILE(conf), (VisuConfigFileEntry*)0);

  entry = entry_init(key, description, conf->priv->kind, 1);
  if (!entry)
    return (VisuConfigFileEntry*)0;
  entry->read = _readBooleanv;
  entry->storage = (gpointer)location;
  entry->nValues = 1;
  entry->withLabel = labelled;

  if (!entry_register(conf, entry))
    {
      g_free(entry);
      entry = (VisuConfigFileEntry*)0;
      g_warning("entry '%s' already exists!", key);
    }

  return entry;
}
/**
 * visu_config_file_addBooleanArrayEntry:
 * @conf: a #VisuConfigFile object ;
 * @key: a string (should not be NULL) ;
 * @description: (allow-none): a string (can be NULL) ;
 * @nValues: the number of floats to read.
 * @location: a pointer where to store booleans when the entry is
 * parsed.
 * @labelled: a boolean.
 *
 * Defines a #VisuConfigFileEntry that will be several booleans to
 * read and to store in @location. If @labelled is %TRUE, it retrieves
 * and store a string before the boolean value. It can be accessed
 * later with visu_config_file_entry_getLabel().
 *
 * Since: 3.8
 *
 * Returns: (transfer full): the newly created #VisuConfigFileEntry object.
 **/
VisuConfigFileEntry* visu_config_file_addBooleanArrayEntry(VisuConfigFile *conf, const gchar *key,
                                                           const gchar* description,
                                                           guint nValues, gboolean *location,
                                                           gboolean labelled)
{
  VisuConfigFileEntry *entry;

  g_return_val_if_fail(location, (VisuConfigFileEntry*)0);
  g_return_val_if_fail(VISU_IS_CONFIG_FILE(conf), (VisuConfigFileEntry*)0);

  entry = entry_init(key, description, conf->priv->kind, 1);
  if (!entry)
    return (VisuConfigFileEntry*)0;
  entry->read = _readBooleanv;
  entry->storage = (gpointer)location;
  entry->nValues = nValues;
  entry->withLabel = labelled;

  if (!entry_register(conf, entry))
    {
      g_free(entry);
      entry = (VisuConfigFileEntry*)0;
      g_warning("entry '%s' already exists!", key);
    }

  return entry;
}
/**
 * visu_config_file_addIntegerArrayEntry:
 * @conf: a #VisuConfigFile object ;
 * @key: a string (should not be NULL) ;
 * @description: (allow-none): a string (can be NULL) ;
 * @nValues: the number of floats to read.
 * @location: a pointer where to store floats when the entry is
 * parsed.
 * @clamp: the min and max values allowed.
 * @labelled: TRUE if the entry has a label.
 *
 * Defines a #VisuConfigFileEntry that will parse @nValues usable for stipple and
 * store them consecutively in @location.
 *
 * Since: 3.8
 *
 * Returns: (transfer full): the newly created #VisuConfigFileEntry object.
 **/
VisuConfigFileEntry* visu_config_file_addIntegerArrayEntry(VisuConfigFile *conf, const gchar *key,
                                                           const gchar* description,
                                                           guint nValues, int *location,
                                                           int clamp[2], gboolean labelled)
{
  VisuConfigFileEntry *entry;

  g_return_val_if_fail(location, (VisuConfigFileEntry*)0);
  g_return_val_if_fail(VISU_IS_CONFIG_FILE(conf), (VisuConfigFileEntry*)0);

  entry = entry_init(key, description, conf->priv->kind, 1);
  if (!entry)
    return (VisuConfigFileEntry*)0;
  entry->read = _readIntv;
  entry->storage = (gpointer)location;
  entry->nValues = nValues;
  entry->range.i[0] = clamp[0];
  entry->range.i[1] = clamp[1];
  entry->withLabel = labelled;

  if (!entry_register(conf, entry))
    {
      g_free(entry);
      entry = (VisuConfigFileEntry*)0;
      g_warning("entry '%s' already exists!", key);
    }

  return entry;
}
/**
 * visu_config_file_addFloatArrayEntry:
 * @conf: a #VisuConfigFile object ;
 * @key: a string (should not be NULL) ;
 * @description: (allow-none): a string (can be NULL) ;
 * @nValues: the number of floats to read.
 * @location: a pointer where to store floats when the entry is
 * parsed.
 * @clamp: the min and max values allowed.
 * @labelled: a boolean.
 *
 * Defines a #VisuConfigFileEntry that will parse @nValues floats and
 * store them consecutively in @location. The parsed values are
 * checked to be in @clamp.
 *
 * Since: 3.7
 *
 * Returns: (transfer full): the newly created #VisuConfigFileEntry object.
 **/
VisuConfigFileEntry* visu_config_file_addFloatArrayEntry(VisuConfigFile *conf, const gchar *key,
                                                         const gchar* description,
                                                         guint nValues, float *location,
                                                         float clamp[2], gboolean labelled)
{
  VisuConfigFileEntry *entry;

  g_return_val_if_fail(location, (VisuConfigFileEntry*)0);
  g_return_val_if_fail(VISU_IS_CONFIG_FILE(conf), (VisuConfigFileEntry*)0);

  entry = entry_init(key, description, conf->priv->kind, 1);
  if (!entry)
    return (VisuConfigFileEntry*)0;
  entry->read = _readFloatv;
  entry->storage = (gpointer)location;
  entry->nValues = nValues;
  entry->range.f[0] = clamp[0];
  entry->range.f[1] = clamp[1];
  entry->withLabel = labelled;

  if (!entry_register(conf, entry))
    {
      g_free(entry);
      entry = (VisuConfigFileEntry*)0;
      g_warning("entry '%s' already exists!", key);
    }

  return entry;
}
/**
 * visu_config_file_addStippleArrayEntry:
 * @conf: a #VisuConfigFile object ;
 * @key: a string (should not be NULL) ;
 * @description: (allow-none): a string (can be NULL) ;
 * @nValues: the number of floats to read.
 * @location: a pointer where to store floats when the entry is
 * parsed.
 *
 * Defines a #VisuConfigFileEntry that will parse @nValues usable for stipple and
 * store them consecutively in @location.
 *
 * Since: 3.8
 *
 * Returns: (transfer full): the newly created #VisuConfigFileEntry object.
 **/
VisuConfigFileEntry* visu_config_file_addStippleArrayEntry(VisuConfigFile *conf, const gchar *key,
                                                           const gchar* description,
                                                           guint nValues, guint16 *location)
{
  VisuConfigFileEntry *entry;

  g_return_val_if_fail(location, (VisuConfigFileEntry*)0);
  g_return_val_if_fail(VISU_IS_CONFIG_FILE(conf), (VisuConfigFileEntry*)0);

  entry = entry_init(key, description, conf->priv->kind, 1);
  if (!entry)
    return (VisuConfigFileEntry*)0;
  entry->read = _readStipplev;
  entry->storage = (gpointer)location;
  entry->nValues = nValues;

  if (!entry_register(conf, entry))
    {
      g_free(entry);
      entry = (VisuConfigFileEntry*)0;
      g_warning("entry '%s' already exists!", key);
    }

  return entry;
}
/**
 * visu_config_file_addEnumEntry:
 * @conf: a #VisuConfigFile object ;
 * @key: a string (should not be NULL) ;
 * @description: (allow-none): a string (can be NULL) ;
 * @location: a location to store an enum value.
 * @toEnum: (scope call): a method to convert a string to an enum value.
 * @labelled: a boolean.
 *
 * Defines a #VisuConfigFileEntry that will parse a string and convert
 * it to an enum value with @toEnum function and store it in
 * @location.
 *
 * Since: 3.8
 *
 * Returns: (transfer full): the newly created #VisuConfigFileEntry object.
 **/
VisuConfigFileEntry* visu_config_file_addEnumEntry(VisuConfigFile *conf, const gchar *key,
                                                   const gchar* description, guint *location,
                                                   VisuConfigFileEnumFunc toEnum,
                                                   gboolean labelled)
{
  VisuConfigFileEntry *entry;

  g_return_val_if_fail(location, (VisuConfigFileEntry*)0);
  g_return_val_if_fail(VISU_IS_CONFIG_FILE(conf), (VisuConfigFileEntry*)0);

  entry = entry_init(key, description, conf->priv->kind, 1);
  if (!entry)
    return (VisuConfigFileEntry*)0;
  entry->read = _readEnum;
  entry->storage = (gpointer)location;
  entry->toEnum = toEnum;
  entry->withLabel = labelled;

  if (!entry_register(conf, entry))
    {
      g_free(entry);
      entry = (VisuConfigFileEntry*)0;
      g_warning("entry '%s' already exists!", key);
    }

  return entry;
}
/**
 * visu_config_file_addStringEntry:
 * @conf: a #VisuConfigFile object ;
 * @key: a string (should not be NULL) ;
 * @description: (allow-none): a string (can be NULL) ;
 * @location: a pointer where to store a string when the entry is
 * parsed.
 *
 * Defines a #VisuConfigFileEntry that will be a string to
 * read and to store in @location. If @location already contains a
 * string, it is g_free().
 *
 * Since: 3.7
 *
 * Returns: (transfer full): the newly created #VisuConfigFileEntry object.
 **/
VisuConfigFileEntry* visu_config_file_addStringEntry(VisuConfigFile *conf, const gchar *key,
                                                     const gchar* description,
                                                     gchar **location)
{
  VisuConfigFileEntry *entry;

  g_return_val_if_fail(location, (VisuConfigFileEntry*)0);
  g_return_val_if_fail(VISU_IS_CONFIG_FILE(conf), (VisuConfigFileEntry*)0);

  entry = entry_init(key, description, conf->priv->kind, 1);
  if (!entry)
    return (VisuConfigFileEntry*)0;
  entry->read = _readString;
  entry->storage = (gpointer)location;

  if (!entry_register(conf, entry))
    {
      g_free(entry);
      entry = (VisuConfigFileEntry*)0;
      g_warning("entry '%s' already exists!", key);
    }

  return entry;
}
/**
 * visu_config_file_getEntries:
 * @conf: a #VisuConfigFile object ;
 *
 * This routine should be used for introspections purpose, to know
 * what resources or parameters are available.
 *
 * Returns: (element-type utf8) (transfer none): a #GList own by V_Sim.
 */
GList* visu_config_file_getEntries(VisuConfigFile *conf)
{
  g_return_val_if_fail(VISU_IS_CONFIG_FILE(conf), (GList*)0);

  return g_hash_table_get_values(conf->priv->entryList);
}

/**
 * visu_config_file_addKnownTag:
 * @tag: a string (not nul or empty).
 *
 * If parameter entries have a tag, they are ignored except if their tag
 * has been declared using this method.
 */
void visu_config_file_addKnownTag(gchar* tag)
{
  g_return_if_fail(tag && *tag);

  if (!knownTags)
    g_type_class_ref(VISU_TYPE_CONFIG_FILE);

  g_hash_table_insert(knownTags, (gpointer)tag, GINT_TO_POINTER(1));
}
/**
 * visu_config_file_addExportFunction:
 * @conf: a #VisuConfigFile object ;
 * @writeFunc: (scope call): a VisuConfigFileExportFunc method.
 *
 * This stores the @writeFunc given. It will be called when resources or parameters
 * will be exported to disk.
 */
void visu_config_file_addExportFunction(VisuConfigFile *conf, VisuConfigFileExportFunc writeFunc)
{
  struct writeFunc_struct *str;

  if (!writeFunc)
    return;

  g_return_if_fail(VISU_IS_CONFIG_FILE(conf));

  str = g_malloc(sizeof(struct writeFunc_struct));
  str->writeFunc = writeFunc;
  conf->priv->exportList = g_list_prepend(conf->priv->exportList, str);
}

/**
 * visu_config_file_entry_setTag:
 * @entry: a #VisuConfigFileEntry object ;
 * @tag: a string.
 *
 * This method is used to set a tag to the given entry. This tag is used
 * to ignore or not the entry when the file is read. The @tag argument
 * is copied.
 */
void visu_config_file_entry_setTag(VisuConfigFileEntry *entry, const gchar *tag)
{
  g_return_if_fail(entry);

  if (entry->tag)
    g_free(entry->tag);
  entry->tag = g_strdup(tag);
}
/**
 * visu_config_file_entry_setVersion:
 * @entry: a #VisuConfigFileEntry object ;
 * @version: the version the entry appear in.
 *
 * Set the version number the entry appear in.
 */
void visu_config_file_entry_setVersion(VisuConfigFileEntry *entry, float version)
{
  g_return_if_fail(entry && version > 3.0f);

  entry->version = version;
}
/**
 * visu_config_file_entry_setReplace:
 * @newEntry: a #VisuConfigFileEntry object ;
 * @oldEntry: idem.
 *
 * Use this method to declare that @oldEntry has become obsolete and
 * has been replaced by @newEntry.
 */
void visu_config_file_entry_setReplace(VisuConfigFileEntry *newEntry,
                                       VisuConfigFileEntry *oldEntry)
{
  g_return_if_fail(newEntry && oldEntry);

  if (oldEntry->newKey)
    g_free(oldEntry->newKey);
  oldEntry->newKey = g_strdup(newEntry->key);
}
/**
 * visu_config_file_entry_getKey:
 * @entry: a #VisuConfigFileEntry object.
 *
 * An entry is defined by its key.
 *
 * Since: 3.8
 *
 * Returns: the key of @entry.
 **/
const gchar* visu_config_file_entry_getKey(const VisuConfigFileEntry *entry)
{
  g_return_val_if_fail(entry, (const gchar*)0);

  return entry->key;
}
/**
 * visu_config_file_entry_getLabel:
 * @entry: a #VisuConfigFileEntry object.
 *
 * An entry can be defined as some values preceeded by a label. After
 * parsing an entry, this label, if it exists can be retrieve with
 * this function. See for instance
 * visu_config_file_addBooleanArrayEntry() to define an entry with a label.
 *
 * Since: 3.8
 *
 * Returns: (allow-none): the label as parsed.
 **/
const gchar* visu_config_file_entry_getLabel(const VisuConfigFileEntry *entry)
{
  g_return_val_if_fail(entry, (const gchar*)0);

  return entry->label;
}
/**
 * visu_config_file_entry_popToken:
 * @entry: a #VisuConfigFileEntry object.
 * @value: (out caller-allocates): a location to store a string.
 *
 * Pop a string from a tokenified data line corresponding to @entry in
 * a configuration file.
 *
 * Since: 3.8
 *
 * Returns: TRUE if @entry still has tokens to be retrieved and @value
 * has be set.
 **/
gboolean visu_config_file_entry_popToken(VisuConfigFileEntry *entry, const gchar **value)
{
  g_return_val_if_fail(entry && entry->tokens, FALSE);

  while (entry->tokens[entry->iToken] && !entry->tokens[entry->iToken][0])
    entry->iToken += 1;
  if (entry->tokens[entry->iToken])
    {
      *value = entry->tokens[entry->iToken];
      entry->iToken += 1;
      return TRUE;
    }
  return FALSE;
}
/**
 * visu_config_file_entry_popTokenAsBoolean:
 * @entry: a #VisuConfigFileEntry object.
 * @nValues: an integer.
 * @values: (array length=nValues): an array of boolean.
 *
 * Read @nValues as boolean from @entry and stores them in
 * @values. These tokens are poped from the current list of tokens of @entry.
 *
 * Since: 3.8
 *
 * Returns: TRUE if @entry has @nValues boolean tokens to be read.
 **/
gboolean visu_config_file_entry_popTokenAsBoolean(VisuConfigFileEntry *entry, guint nValues,
                                                  gboolean *values)
{
  int res;
  guint i, nb;
  int *vals;

  g_return_val_if_fail(entry && entry->tokens, FALSE);
  vals = g_malloc(sizeof(int) * nValues);

  /* Read @size inting point values from tokens. */
  for (nb = 0; entry->tokens[entry->iToken] && nb < nValues; entry->iToken++)
    if (entry->tokens[entry->iToken][0] != '\0')
      {
        res = sscanf(entry->tokens[entry->iToken], "%d", vals + nb);
        if (res != 1)
          {
            visu_config_file_entry_setErrorMessage
              (entry, _("%d boolean value(s) should appear here"), nValues);
            g_free(vals);
            return FALSE;
          }
        nb += 1;
      }
  if (nb != nValues)
    {
      visu_config_file_entry_setErrorMessage
        (entry, _("%d boolean value(s) should appear here but %d has been found"),
         nValues, nb);
      g_free(vals);
      return FALSE;
    }
  for (i = 0; i < nValues; i++)
    values[i] = (vals[i] != FALSE);
  g_free(vals);
  return TRUE;
}
/**
 * visu_config_file_entry_popTokenAsInt:
 * @entry: a #VisuConfigFileEntry object.
 * @nValues: an integer.
 * @values: (array length=nValues): an array of integers.
 * @clamp: a range.
 *
 * Like visu_config_file_entry_popTokenAsBoolean() but for
 * integers. Additionally conduct a range check using @clamp.
 *
 * Since: 3.8
 *
 * Returns: TRUE if @entry has @nValues integer tokens to be read.
 **/
gboolean visu_config_file_entry_popTokenAsInt(VisuConfigFileEntry *entry, guint nValues,
                                              int *values, const int clamp[2])
{
  int res;
  guint i, nb;
  int *vals;

  g_return_val_if_fail(entry && entry->tokens, FALSE);
  vals = g_malloc(sizeof(int) * nValues);

  /* Read @size inting point values from tokens. */
  for (nb = 0; entry->tokens[entry->iToken] && nb < nValues; entry->iToken++)
    if (entry->tokens[entry->iToken][0] != '\0')
      {
        res = sscanf(entry->tokens[entry->iToken], "%d", vals + nb);
        if (res != 1)
          {
            visu_config_file_entry_setErrorMessage
              (entry, _("%d integer value(s) should appear here"), nValues);
            g_free(vals);
            return FALSE;
          }
        nb += 1;
      }
  if (nb != nValues)
    {
      visu_config_file_entry_setErrorMessage
        (entry, _("%d integer value(s) should appear here but %d has been found"),
         nValues, nb);
      g_free(vals);
      return FALSE;
    }
  for (i = 0; i < nValues; i++)
    if (tool_config_file_clampInt(vals + i, vals[i], clamp[0], clamp[1]))
      {
        visu_config_file_entry_setErrorMessage
          (entry, _("wrong range (%d <= v <= %d) for the %s markup"),
           clamp[0], clamp[1], entry->key);
        g_free(vals);
        return FALSE;
      }
  memcpy(values, vals, sizeof(int) * nValues);
  g_free(vals);
  return TRUE;
}
/**
 * visu_config_file_entry_popTokenAsColor:
 * @entry: a #VisuConfigFileEntry object.
 * @color: (out caller-allocates): a location to store a #ToolColor.
 *
 * Like visu_config_file_entry_popToken() but convert the token as a #ToolColor.
 *
 * Since: 3.8
 *
 * Returns: TRUE if @entry has a color tokens to be read.
 **/
gboolean visu_config_file_entry_popTokenAsColor(VisuConfigFileEntry *entry, const ToolColor **color)
{
  gfloat rgba[4];
  gfloat rgColor[2] = {0.f, 1.f};

  g_return_val_if_fail(entry && entry->tokens && color, FALSE);

  *color = tool_color_fromStr(entry->tokens[entry->iToken], (int*)0);
  if (*color)
    {
      entry->iToken += 1;
      return TRUE;
    }

  if (!visu_config_file_entry_popTokenAsFloat(entry, 3, rgba, rgColor))
    return FALSE;

  rgba[3] = 1.f;
  *color = tool_color_getByValues((int*)0, rgba[0], rgba[1], rgba[2], rgba[3]);
  if (!*color)
    *color = tool_color_addFloatRGBA(rgba, (int*)0);

  return TRUE;
}
/**
 * visu_config_file_entry_popTokenAsFloat:
 * @entry: a #VisuConfigFileEntry object.
 * @nValues: an integer.
 * @values: (array length=nValues): an array of floats.
 * @clamp: a range.
 *
 * Like visu_config_file_entry_popTokenAsInt() but for floats.
 *
 * Since: 3.8
 *
 * Returns: TRUE if @entry has @nValues float tokens to be read.
 **/
gboolean visu_config_file_entry_popTokenAsFloat(VisuConfigFileEntry *entry, guint nValues,
                                                float *values, const float clamp[2])
{
  int res;
  guint i, nb;
  float *vals;

  g_return_val_if_fail(entry && entry->tokens, FALSE);
  vals = g_malloc(sizeof(float) * nValues);

  /* Read @size floating point values from tokens. */
  for (nb = 0; entry->tokens[entry->iToken] && nb < nValues; entry->iToken++)
    if (entry->tokens[entry->iToken][0] != '\0')
      {
        res = sscanf(entry->tokens[entry->iToken], "%f", vals + nb);
        if (res != 1)
          {
            visu_config_file_entry_setErrorMessage
              (entry, _("%d floating point values should appear here"), nValues);
            g_free(vals);
            return FALSE;
          }
        nb += 1;
      }
  if (nb != nValues)
    {
      visu_config_file_entry_setErrorMessage
        (entry, _("%d floating point value(s) should appear here but %d has been found"),
         nValues, nb);
      g_free(vals);
      return FALSE;
    }
  for (i = 0; i < nValues; i++)
    if (tool_config_file_clampFloat(vals + i, vals[i], clamp[0], clamp[1]))
      {
        visu_config_file_entry_setErrorMessage
          (entry, _("wrong range (%g <= v <= %g) for the %s markup"),
           clamp[0], clamp[1], entry->key);
        g_free(vals);
        return FALSE;
      }
  memcpy(values, vals, sizeof(float) * nValues);
  g_free(vals);
  return TRUE;
}
/**
 * visu_config_file_entry_popTokenAsEnum:
 * @entry: a #VisuConfigFileEntry object.
 * @value: (out): a location to store an enum value.
 * @toEnum: (scope call): a method to convert a string to an enum
 * value.
 *
 * Parse the next non null token in @entry and parse it with @toEnum
 * function. The result, if valid, is stored in @value.
 *
 * Since: 3.8
 *
 * Returns: TRUE if a token can be found and the conversion succeed.
 **/
gboolean visu_config_file_entry_popTokenAsEnum(VisuConfigFileEntry *entry, guint *value,
                                               VisuConfigFileEnumFunc toEnum)
{
  g_return_val_if_fail(entry && entry->tokens, FALSE);

  while (entry->tokens[entry->iToken] && !entry->tokens[entry->iToken][0])
    entry->iToken += 1;
  if (!entry->tokens[entry->iToken])
    {
      visu_config_file_entry_setErrorMessage
        (entry, _("missing string for %s markup"), entry->key);
      return FALSE;
    }
  if (!toEnum(entry->tokens[entry->iToken], value))
    {
      visu_config_file_entry_setErrorMessage
        (entry, _("'%s' is not a valid value for %s markup"),
         entry->tokens[entry->iToken], entry->key);
      entry->iToken += 1;
      return FALSE;
    }
  entry->iToken += 1;
  return TRUE;
}
/**
 * visu_config_file_entry_popAllTokens:
 * @entry: a #VisuConfigFileEntry object.
 *
 * Join the remaining tokens of @entry into a string.
 *
 * Since: 3.8
 *
 * Returns: a newly created string.
 **/
gchar* visu_config_file_entry_popAllTokens(VisuConfigFileEntry *entry)
{
  g_return_val_if_fail(entry, (gchar*)0);
  
  return g_strjoinv(" ", entry->tokens + entry->iToken);
}
/**
 * visu_config_file_entry_setErrorMessage:
 * @entry: a #VisuConfigFileEntry object.
 * @mess: a string.
 * @...: additional values, like in printf.
 *
 * Set error to @entry and format a message.
 *
 * Since: 3.8
 **/
void visu_config_file_entry_setErrorMessage(VisuConfigFileEntry *entry, const gchar *mess, ...)
{
  va_list arglist;

  g_return_if_fail(entry && mess);

  va_start(arglist, mess);
  entry->errMess = g_strdup_vprintf(mess, arglist);
  va_end(arglist);
}

static gchar* _getKey(const gchar *buf, gchar **key, gchar **tag, guint iLine, GError **error)
{
  gchar *key_, *tag_, *end, *ret;

  *key = (gchar*)0;
  *tag = (gchar*)0;

  ret = strchr(buf, ':');
  if (!ret)
    return (gchar*)0;

  key_ = g_strndup(buf, ret - buf);
  key_ = g_strstrip(key_);
  *key = key_;

  /* Look for the tag */
  tag_ = strchr(key_, '[');
  if (tag_)
    {
      *tag_ = '\0';
      tag_ += 1;
      end = strchr(tag_, ']');
      if (end)
        {
          *end = '\0';
          tag_ = g_strdup(tag_);
          *tag = tag_;
        }
      else
        *error = g_error_new(TOOL_CONFIG_FILE_ERROR, TOOL_CONFIG_FILE_ERROR_TAG,
                             _("Parse error at line %d,"
                               " the tag '%s' is not closed.\n"),
                             iLine, tag_);
    }

  DBG_fprintf(stderr,"Visu ConfigFile: read a flag (tag): '%s' (%s).\n", key_, tag_);
  return ret;
}
static VisuConfigFileEntry* _getEntry(VisuConfigFile *conf,
                                      const gchar *key, guint iLine, GError **error)
{
  VisuConfigFileEntry *entry;

  DBG_fprintf(stderr, "Visu ConfigFile: found entry '%s'.\n", key);
  entry = (VisuConfigFileEntry*)g_hash_table_lookup(conf->priv->entryList,
                                                    (gpointer)key);
  if (!entry)
    *error = g_error_new(TOOL_CONFIG_FILE_ERROR, TOOL_CONFIG_FILE_ERROR_MARKUP,
                         _("Parse error at line %d,"
                           " '%s' is an unknown markup.\n"),
                         iLine, key);
  else if (entry->newKey)
    g_warning(_("Markup '%s' is obsolete, replaced by '%s'."), key, entry->newKey);
  return entry;
}
static void _appendMessage(GString *message, GError **error,
                           const gchar *key, const gchar *context)
{
  if (*error)
    {
      g_string_append(message, (*error)->message);
      if (key)
        g_string_append_printf(message, _(" read line (%s) : '%s'\n"), key, context);
      else
        g_string_append_printf(message, _(" read line : '%s'\n"), context);
      g_error_free(*error);
      *error = (GError*)0;
    }
}
static gboolean _parse(VisuConfigFile *conf, VisuConfigFileEntry *entry,
                       gchar **tokens, guint iLine, GError **error)
{
  gboolean ret;

  ret = TRUE;
  if (tokens)
    {
      if (entry->read)
        ret = entry->read(entry, tokens, entry->nbLines, iLine, error);
      else if (entry->newKey)
        g_warning("Deprecated entry '%s', use '%s' instead.", entry->key, entry->newKey);
      if (ret)
        {
          DBG_fprintf(stderr, "Visu ConfigFile: entry '%s' parsed (%d).\n", entry->key, ret);
          g_signal_emit(conf, _signals[ENTRYPARSED_SIGNAL], entry->kquark, entry);
          if (entry->errMess)
            {
              *error = g_error_new(TOOL_CONFIG_FILE_ERROR, TOOL_CONFIG_FILE_ERROR_READ,
                                   _("Parse error at line %d, %s.\n"), iLine, entry->errMess);
              g_free(entry->errMess);
              entry->errMess = (gchar*)0;
            }
        }
      g_strfreev(entry->tokens);
      entry->tokens = (gchar**)0;
    }
  return ret;
}

static gboolean _loadRaw(VisuConfigFile *conf, const char* fileName,
                         GError **error)
{
  GIOChannel *ioFile;
  GString *line= (GString*)0;
  GIOStatus status;
  guint nbLine, i, iLine;
  gchar *deuxPoints;
  gchar **tokens, **splits;
  gchar *key, *tag;
  char *startDef, *endDef;
  VisuConfigFileEntry *entry;
  GString *message;

  ioFile = g_io_channel_new_file(fileName, "r", error);
  if (*error)
    return FALSE;

  message = g_string_new("");
  line = g_string_new("");
  iLine = 0;

  status = G_IO_STATUS_NORMAL;
  while (status == G_IO_STATUS_NORMAL)
    {
      status = g_io_channel_read_line_string(ioFile, line, NULL, error);
      if (*error)
        {
          g_string_free(line, TRUE);
          g_string_free(message, TRUE);
          return FALSE;
        }
      iLine += 1;

      if (status == G_IO_STATUS_EOF || line->str[0] == '#' || line->str[0] == '\n')
        continue;

      entry = (VisuConfigFileEntry*)0;

      deuxPoints = _getKey(line->str, &key, &tag, iLine, error);
      _appendMessage(message, error, (const gchar*)0, line->str);
      
      if (key)
        {
          if (tag && !g_hash_table_lookup(knownTags, (gpointer)tag))
            {
              entry = (VisuConfigFileEntry*)0;
              DBG_fprintf(stderr, "Visu ConfigFile: the entry '%s' has an unknown tag (%s),"
                          " it will be dismissed.\n", key, tag);
            }
          else
            {
              entry = _getEntry(conf, key, iLine, error);
              _appendMessage(message, error, (const gchar*)0, line->str);
            }
          g_free(key);
          if (tag)
            g_free(tag);
        }

      if (entry)
        {
          nbLine = entry->nbLines;
          /* Tricks for backward compatibility. */
          if (!strcmp(entry->key, "pair_link"))
            nbLine = 2;
          tokens = g_malloc0(sizeof(gchar*) * (nbLine + 1));
          if (conf->priv->kind == VISU_CONFIG_FILE_KIND_RESOURCE)
            for (i = 0; i < nbLine; i++)
              {
                status = g_io_channel_read_line_string(ioFile, line,
                                                       NULL, error);
                if (*error)
                  {
                    g_string_free(line, TRUE);
                    g_string_free(message, TRUE);
                    return FALSE;
                  }
                iLine += 1;
                if (status != G_IO_STATUS_NORMAL)
                  {
                    g_strfreev(tokens);
                    tokens = (gchar**)0;
                    *error = g_error_new(TOOL_CONFIG_FILE_ERROR,
                                         TOOL_CONFIG_FILE_ERROR_MISSING,
                                         _("Parse error at line %d,"
                                           " '%s' needs %d lines but only %d were read.\n"),
                                         iLine, entry->key, nbLine, i);
                    break;
                  }
                tokens[i] = g_strdup(line->str);
              }
          else
            tokens[0] = g_strdup(deuxPoints + 1);
          /* Tricks for backward compatibility. */
          if (entry->withLabel || !strcmp(entry->key, "shade_palette"))
            {
              i = 0;
              if (!strcmp(entry->key, "isosurface_color") ||
                  !strcmp(entry->key, "isosurface_properties"))
                {
                  splits = g_strsplit_set(tokens[0], "\"", 3);
                  i += 1;
                }
              else if (!strcmp(entry->key, "shade_palette"))
                {
                  splits = g_malloc0(sizeof(gchar*) * 3);
                  startDef = strchr(tokens[0], '(');
                  endDef = strchr(tokens[0], ')');
                  if (!startDef || !endDef)
                    {
                      *error = g_error_new(TOOL_CONFIG_FILE_ERROR, TOOL_CONFIG_FILE_ERROR_MISSING,
                                           _("Parse error at line %d, cannot find parenthesis"
                                             " containing the description of a shade.\n"), iLine);
                    }
                  else
                    {
                      splits[0] = g_strndup(tokens[0], startDef - tokens[0] - 1);
                      splits[1] = g_strndup(startDef + 1, endDef - startDef - 1);
                    }
                }
              else if (!strcmp(entry->key, "pair_link"))
                {
                  nbLine = 1;
                  i = 0;
                  splits = g_malloc0(sizeof(gchar*) * 3);
                  splits[0] = g_strdup(tokens[0]);
                  splits[1] = g_strdup(tokens[1]);
                }
              else
                {
                  splits = g_strsplit_set(tokens[0], " \n", TOOL_MAX_LINE_LENGTH);
                  while (splits[i] && !splits[i][0]) i++;
                }
              g_free(entry->label);
              entry->label = g_strdup(splits[i]);
              g_free(tokens[0]);
              tokens[0] = (splits[i] && splits[i + 1]) ? g_strjoinv(" ", splits + i + 1) : g_strdup("");
              g_strfreev(splits);
            }
          if (!error || !*error)
            _parse(conf, entry, tokens, iLine, error);
          _appendMessage(message, error, entry->key, tokens[0]);
          g_strfreev(tokens);
        }
    }
  g_string_free(line, TRUE);

  status = g_io_channel_shutdown(ioFile, FALSE, error);
  g_io_channel_unref(ioFile);
  if (status != G_IO_STATUS_NORMAL)
    {
      g_string_free(message, TRUE);
      return FALSE;
    }
  DBG_fprintf(stderr, "Visu ConfigFile: read OK (error len = %d).\n", (int)message->len);

  if (message->len > 0)
    {
      DBG_fprintf(stderr, " | %s\n", message->str);
      *error = g_error_new(TOOL_CONFIG_FILE_ERROR, TOOL_CONFIG_FILE_ERROR_READ,
                           "%s", message->str);
    }
  g_string_free(message, TRUE);

  return (*error == (GError*)0);
}

struct _dt
{
  VisuConfigFile *conf;

  gboolean parse;
  GString *message;

  VisuConfigFileEntry *entry;
  gchar *tag, *id, *text;
};

static void _element(GMarkupParseContext *context _U_,
                     const gchar         *element_name,
                     const gchar        **attribute_names,
                     const gchar        **attribute_values,
                     gpointer             user_data,
                     GError             **error)
{
  struct _dt *dt = (struct _dt*)user_data;
  guint i;

  if (!strcmp(element_name, "resources"))
    dt->parse = TRUE;
  else if (!strcmp(element_name, "entry"))
    {
      dt->tag  = (gchar*)0;
      dt->id   = (gchar*)0;
      dt->text = (gchar*)0;
      for (i = 0; attribute_names[i]; i++)
        {
          if (!strcmp(attribute_names[i], "name"))
            {
              dt->entry = _getEntry(dt->conf, attribute_values[i], 0, error);
              _appendMessage(dt->message, error, (const gchar*)0, attribute_values[i]);
            }
          else if (!strcmp(attribute_names[i], "id"))
            dt->id = g_strdup(attribute_values[i]);
        }
    }
}
static void _endElement(GMarkupParseContext *context _U_,
                        const gchar         *element_name,
                        gpointer             user_data,
                        GError             **error)
{
  struct _dt *dt = (struct _dt*)user_data;

  if (!strcmp(element_name, "resources"))
    dt->parse = FALSE;
  else if (!strcmp(element_name, "entry") && dt->entry)
    {
      g_free(dt->entry->label);
      dt->entry->label = g_strdup(dt->id);
      _parse(dt->conf, dt->entry, &dt->text, 0, error);
      _appendMessage(dt->message, error, dt->entry->key, dt->text);
      dt->entry = (VisuConfigFileEntry*)0;
      g_free(dt->tag);
      g_free(dt->id);
      g_free(dt->text);
    }
}
static void _text(GMarkupParseContext *context _U_,
                  const gchar         *text,
                  gsize                text_len _U_,
                  gpointer             user_data,
                  GError             **error _U_)
{
  struct _dt *dt = (struct _dt*)user_data;

  if (dt->entry)
    dt->text = g_strdup(text);
}

static gboolean _loadXML(VisuConfigFile *conf, const gchar *filename, GError **error)
{
  GMarkupParseContext* xmlContext;
  GMarkupParser parser;
  gsize size;
  gchar *buffer;
  struct _dt dt;

  /* Read file. */
  buffer = (gchar*)0;
  if (!g_file_get_contents(filename, &buffer, &size, error))
    return FALSE;

  /* Create context. */
  parser.start_element = _element;
  parser.end_element   = _endElement;
  parser.text          = _text;
  parser.passthrough   = NULL;
  parser.error         = NULL;
  dt.conf = conf;
  dt.parse   = FALSE;
  dt.message = g_string_new("");
  dt.entry   = (VisuConfigFileEntry*)0;
  dt.tag     = (gchar*)0;
  dt.id      = (gchar*)0;
  dt.text    = (gchar*)0;
  xmlContext = g_markup_parse_context_new(&parser, 0, &dt, NULL);

  /* Parse data. */
  g_markup_parse_context_parse(xmlContext, buffer, size, error);

  /* Free buffers. */
  g_markup_parse_context_free(xmlContext);
  g_free(buffer);
  if (!*error && dt.message->len > 0)
    *error = g_error_new(TOOL_CONFIG_FILE_ERROR, TOOL_CONFIG_FILE_ERROR_READ,
                         "%s", dt.message->str);
  g_string_free(dt.message, TRUE);

  return (*error == (GError*)0);
}

static void _setSource(VisuConfigFile *conf, const gchar *filename)
{
  gchar *dirname;
  
  g_return_if_fail(VISU_IS_CONFIG_FILE(conf));
  
  g_free(conf->priv->source);
  conf->priv->source = g_strdup(filename);

  dirname = g_path_get_dirname(filename);
  _addPath(resources, dirname);
}

/**
 * visu_config_file_load:
 * @conf: a #VisuConfigFile object ;
 * @filename: the path to file to read ;
 * @error: (allow-none): a pointer to a GError pointer.
 *
 * Try to load the resources/parameters from the file name given in
 * parameter.
 *
 * Returns: TRUE if everything goes right. If @error is not NULL it
 *          should be freed with g_error_free().
 */
gboolean visu_config_file_load(VisuConfigFile *conf, const char* filename, GError **error)
{
  gboolean res;

  DBG_fprintf(stderr, "Visu ConfigFile: parsing '%s' file for"
	      " resources/parameters...\n", filename);

  g_return_val_if_fail(VISU_IS_CONFIG_FILE(conf), FALSE);

  if (conf->priv->kind == VISU_CONFIG_FILE_KIND_RESOURCE && strstr(filename, ".xml"))
    res = _loadXML(conf, filename, error);
  else
    res = _loadRaw(conf, filename, error);

  /* We save the current path. */
  _setSource(conf, filename);

  return res;
}
/**
 * visu_config_file_loadCommandLine:
 * @error: a location for a #GError pointer.
 *
 * For every command line option (e.g. -o axes_line_width=3), call the
 * corresponding ressource callback.
 *
 * Since: 3.8
 *
 * Returns: TRUE on success.
 **/
gboolean visu_config_file_loadCommandLine(GError **error)
{
  GHashTable *options;
  GHashTableIter iter;
  gpointer key;
  ToolOption *value;
  VisuConfigFile *conf;
  VisuConfigFileEntry *entry;
  gchar **tokens;

  if (!parameters || !resources)
    g_type_class_ref(VISU_TYPE_CONFIG_FILE);

  options = commandLineGet_options();
  if (!options)
    return TRUE;

  g_hash_table_iter_init(&iter, options);
  while (g_hash_table_iter_next(&iter, &key, (gpointer*)&value))
    {
      tokens = g_strsplit_set(key, "[]", 3);
      conf = resources;
      entry = (VisuConfigFileEntry*)g_hash_table_lookup(conf->priv->entryList, tokens[0]);
      if (!entry)
        {
          conf = parameters;
          entry = (VisuConfigFileEntry*)g_hash_table_lookup(conf->priv->entryList, tokens[0]);
        }
      if (entry && tokens[1])
        {
          g_free(entry->label);
          entry->label = g_strdup(tokens[1]);
        }
      g_strfreev(tokens);
      if (entry)
        {
          tokens = g_malloc0(sizeof(gchar*) * 2);
          tokens[0] = g_strdelimit(g_strdup_value_contents(tool_option_getValue(value)), "\"", ' ');
          tokens[1] = NULL;
          DBG_fprintf(stderr, "Visu ConfigFile: read '%s' = '%s' from command line.\n",
                      (gchar*)key, tokens[0]);
          if (!_parse(conf, entry, tokens, 0, error))
            return FALSE;
        }
    }
  return TRUE;
}

/**
 * visu_config_file_exportComment:
 * @buffer: the buffer to add a comment to.
 * @comment: a comment.
 *
 * Append to @buffer the given @comment, using the current output
 * style (raw text or XML as instance).
 *
 * Since: 3.7
 **/
void visu_config_file_exportComment(GString *buffer, const gchar *comment)
{
  g_return_if_fail(buffer && comment);

  if (!comment[0])
    {
      g_string_append(buffer, "\n");
      return;
    }

  switch (format)
    {
    case (_format_raw):
      g_string_append_printf(buffer, "# %s\n", comment);
      break;
    case (_format_xml):
      g_string_append_printf(buffer, "    <!-- %s -->\n", comment);
      break;
    }
}
/**
 * visu_config_file_exportEntry:
 * @buffer: the buffer to write the entry to.
 * @name: the name of the entry.
 * @id_value: (allow-none): an id for the entry.
 * @format_: the formatting string for the message.
 * @...: the values to print.
 *
 * Append to @buffer the given @entry, using the current output
 * style (raw text or XML as instance). @id_value can be used to
 * specify the entry apply to, for instance, the name of the
 * #VisuElement the colour property entry apply to.
 *
 * Since: 3.7
 **/
void visu_config_file_exportEntry(GString *buffer, const gchar *name,
                                  const gchar *id_value, const gchar *format_, ...)
{
  va_list arglist;
  gchar *buf;

  g_return_if_fail(buffer && name && format_);

  va_start(arglist, format_);
  buf = g_strdup_vprintf(format_, arglist);
  va_end(arglist);
  switch (format)
    {
    case (_format_raw):
      /* Special case for backward compatibility. */
      if (!strcmp(name, "pair_link"))
        g_string_append_printf(buffer, "%s:\n  %s\n  %s\n", name, (id_value)?id_value:"", buf);
      else if (!strcmp(name, "isosurface_color") || !strcmp(name, "isosurface_properties"))
        g_string_append_printf(buffer, "%s:\n  \"%s\" %s\n", name, (id_value)?id_value:"", buf);
      else
        g_string_append_printf(buffer, "%s:\n  %s  %s\n", name, (id_value)?id_value:"", buf);
      break;
    case (_format_xml):
      g_string_append_printf(buffer, "    <entry name=\"%s\"", name);
      if (id_value && id_value[0])
        g_string_append_printf(buffer, " id=\"%s\"", id_value);
      g_string_append_printf(buffer, ">%s</entry>\n", buf);
      break;
    }
  g_free(buf);
}

/**
 * visu_config_file_saveResourcesToXML:
 * @filename: the path to file to read ;
 * @lines: (out): a pointer to an integer (can be NULL) ;
 * @dataObj: (allow-none): a #VisuData object (can be NULL) ;
 * @error: a location to store a possible error.
 *
 * Same routine as visu_config_file_save() but use an XML format instead.
 *
 * Since: 3.7
 *
 * Returns: TRUE if everything goes right.
 */
gboolean visu_config_file_saveResourcesToXML(const char* filename, int *lines,
                                             VisuData *dataObj, GError **error)
{
  gchar *ptCh;
  GString *buffer;
  int nbLine;
  GList *pos;
  gboolean success;

  g_return_val_if_fail(error && !*error, FALSE);

  DBG_fprintf(stderr, "Visu ConfigFile: exporting '%s' file for"
	      " XML resources...\n", filename);

  format = _format_xml;
  buffer = g_string_new("<resources");
  g_string_append_printf(buffer, " version=\"%s\">\n", VERSION_HEADER);
  for (pos = resources->priv->exportList; pos; pos = g_list_next(pos))
    {
      /* g_string_append_printf("  <entry name=\"%s\">\n", ((VisuConfigFileEntry*)pos->data)->key); */
      ((struct writeFunc_struct*)(pos->data))->writeFunc(buffer, dataObj);
    }
  g_string_append(buffer, "  </resources>");

  nbLine = 0;
  ptCh = buffer->str;
  while ((ptCh = strchr(ptCh + 1, '\n')))
    nbLine += 1;

  DBG_fprintf(stderr, "Visu ConfigFile: export OK to string,"
	      " preparing to write file.\n");
  success = tool_XML_substitute(buffer, filename, "resources", error);
  if (!success)
    {
      g_string_free(buffer, TRUE);
      return FALSE;
    }
  success = g_file_set_contents(filename, buffer->str, -1, error);
  DBG_fprintf(stderr, "Visu ConfigFile: write %d lines (%d).\n", nbLine, success);
  g_string_free(buffer, TRUE);

  /* We save the current path. */
  if (success)
    {
      DBG_fprintf(stderr, " | save path '%s' as current.\n", filename);
      _setSource(resources, filename);
    }

  if (lines)
    *lines = nbLine;

  return success;
}

/**
 * visu_config_file_save:
 * @conf: a #VisuConfigFile object ;
 * @fileName: the path to file to read ;
 * @lines: a pointer to an integer (can be NULL) ;
 * @dataObj: (allow-none): a #VisuData object (can be NULL) ;
 * @error: a location to store a possible error.
 *
 * Try to export the resources/parameters to the file name given in
 * parameter. If @lines argument
 * is not NULL, and everything went right, it stores the number of written lines.
 * If the argument @dataObj is not null, only resources related
 * to the #VisuData object should be exported (for parameters files, @dataObj is
 * always NULL).
 *
 * Returns: TRUE if everything goes right.
 */
gboolean visu_config_file_save(VisuConfigFile *conf, const char* fileName, int *lines,
                               VisuData *dataObj, GError **error)
{
  gchar *ptCh;
  GString *exportString;
  int nbLine;
  GList *pos, *lst;
  gboolean success;

  g_return_val_if_fail(error && !*error, FALSE);

  DBG_fprintf(stderr, "Visu ConfigFile: exporting '%s' file for"
	      " resources/parameters...\n", fileName);

  g_return_val_if_fail(VISU_IS_CONFIG_FILE(conf), FALSE);

  format = _format_raw;
  exportString = g_string_new("");
  if (conf->priv->kind == VISU_CONFIG_FILE_KIND_RESOURCE)
    g_string_append_printf(exportString, RESOURCE_HEADER);
  else if (conf->priv->kind == VISU_CONFIG_FILE_KIND_PARAMETER)
    g_string_append_printf(exportString, PARAMETER_HEADER);
  g_string_append_printf(exportString,
			 " v"VERSION_HEADER
			 "\n"
			 "#====================\n"
			 "\n"
			 "#WARNING: this file format is DIFFERENT from that for\n"
			 "#standard v_sim version <= 2.x\n"
			 "\n"
			 "#Line beginning with a # are not parsed.\n"
			 "\n");
  if (conf->priv->kind == VISU_CONFIG_FILE_KIND_RESOURCE)
    g_string_append_printf(exportString,
			   "#The only \"useful\" lines must have the following contents\n"
			   "#several two or more lines patterns:\n"
			   "#resource_name:\n"
			   "#values separeted by blank characters\n"
			   "\n"
			   "#The following resource names are valid :\n");
  else
    g_string_append_printf(exportString,
			   "#The only \"useful\" lines must have the following pattern:\n"
			   "#parameter_name: value\n"
			   "\n"
			   "#The following parameter names are valid :\n");
  lst = visu_config_file_getEntries(conf);
  for (pos = lst; pos; pos = g_list_next(pos))
    if (!((VisuConfigFileEntry*)(pos->data))->newKey)
      g_string_append_printf(exportString, "# %s\n",
                             ((VisuConfigFileEntry*)(pos->data))->key);
  g_string_append_printf(exportString, "\n");
  g_list_free(lst);

  for (pos = conf->priv->exportList; pos; pos = g_list_next(pos))
    ((struct writeFunc_struct*)(pos->data))->writeFunc(exportString, dataObj);

  nbLine = 0;
  ptCh = exportString->str;
  while ((ptCh = strchr(ptCh + 1, '\n')))
    nbLine += 1;

  DBG_fprintf(stderr, "Visu ConfigFile: export OK to string,"
	      " preparing to write file.\n");

  success = g_file_set_contents(fileName, exportString->str, -1, error);
  g_string_free(exportString, TRUE);

  DBG_fprintf(stderr, "Visu ConfigFile: write %d lines (%d).\n", nbLine, success);

  /* We save the current path. */
  _setSource(conf, fileName);

  if (lines)
    *lines = nbLine;

  return success;
}

static gboolean _validateHeader(const gchar *filename, VisuConfigFile *conf)
{
  FILE *file;
  float version;
  char *msg, *header;
  char line[TOOL_MAX_LINE_LENGTH];

  DBG_fprintf(stderr, "Visu ConfigFile: looking for header of \n  '%s' ... ", filename);
  if (strstr(filename, ".xml"))
    {
      DBG_fprintf(stderr, "accepted.\n");
      return TRUE;
    }

  file = fopen(filename, "r");
  if (!file)
    {
      g_warning("The file '%s' should be readable but something goes"
                " nasty when one wants to open it.\n", filename);
      return FALSE;
    }
  version = 0.;
  msg = fgets(line, TOOL_MAX_LINE_LENGTH, file);
  fclose(file);
  if (conf->priv->kind == VISU_CONFIG_FILE_KIND_RESOURCE)
    header = RESOURCE_HEADER;
  else
    header = PARAMETER_HEADER;
  if (msg && !strncmp(line, header, strlen(header))
      && sscanf(line + strlen(header) + 2, "%f", &version))
    if (version >= 3.)
      {
        DBG_fprintf(stderr, "ok.\n");
        return TRUE;
      }
  DBG_fprintf(stderr, "wrong.\n");
  return FALSE;
}

static gchar* getValidFileWithHeader(int mode, VisuConfigFile *conf, GList **list)
{
  gchar *res;
  const gchar** filenames;

  if (conf->priv->kind == VISU_CONFIG_FILE_KIND_RESOURCE)
    filenames = RESOURCES_FILENAMES;
  else
    filenames = PARAMETERS_FILENAMES;

  /* Look for a valid file.
     If it is for writing, a valid file is just given by a valid path.
     If it is for reading, a valid file is a valid path AND has a valid header. */

  /* We get the next valid path. */
  res = tool_getValidPath(list, filenames, mode);
  if (!res)
    {
      DBG_fprintf(stderr, "Visu ConfigFile: no file available.\n");
      return (gchar*)0;
    }

  /* if we are in reading mode, we test the header. */
  if ((mode & R_OK) && !_validateHeader(res, conf))
    {
      g_free(res);
      res = (gchar*)0;
    }
  return res;
}

/**
 * visu_config_file_getPath:
 * @conf: a #VisuConfigFile object.
 *
 * The resource file can be read from different places.
 *
 * Since: 3.6
 *
 * Returns: the path used to read the last resource file.
 */
const gchar* visu_config_file_getPath(VisuConfigFile *conf)
{
  g_return_val_if_fail(VISU_IS_CONFIG_FILE(conf), (const gchar*)0);
  return conf->priv->source;
}
/**
 * visu_config_file_getValidPath:
 * @conf: a #VisuConfigFile object ;
 * @mode: a value from R_OK, W_OK and X_OK as described in unistd.h.
 * @utf8: if 1, the path is return in UTF-8 format, otherwise, the locale
 * of the file system is used.
 *
 * Test the entries of the hadoc list to find
 * a valid position to read or write a config file.
 * It tests access for the specified file.
 *
 * Returns: the first valid path find in the list of known paths.
 */
gchar* visu_config_file_getValidPath(VisuConfigFile *conf, int mode, int utf8)
{
  gchar* file;
  gchar* fileUTF8;
  GList *lst;

  g_return_val_if_fail(VISU_IS_CONFIG_FILE(conf), (gchar*)0);

  lst = conf->priv->paths;
  file = getValidFileWithHeader(mode, conf, &lst);
  if (!file)
    return file;

  if (utf8)
    {
      fileUTF8 = g_filename_from_utf8(file, -1, NULL, NULL, NULL);
      g_free(file);
      return fileUTF8;
    }
  else
    return file;
}
/**
 * visu_config_file_getNextValidPath:
 * @conf: a #VisuConfigFile object ;
 * @accessMode: a value from R_OK, W_OK and X_OK as described in unistd.h ;
 * @list: (element-type filename) (inout) (transfer none): a pointer to a valid *GList ;
 * @utf8: if 1, the path is return in UTF-8 format, otherwise, the locale
 * of the file system is used.
 *
 * Test the entries of the given list to find
 * a valid position to read or write a config file.
 * It tests access for the specified file. After a call to this
 * method the @list argument points to the next entry in the list, after
 * the one found.
 *
 * Returns: the first valid path find in the given list of paths.
 */
gchar* visu_config_file_getNextValidPath(VisuConfigFile *conf, int accessMode, GList **list, int utf8)
{
  gchar* file;
  gchar* fileUTF8;

  g_return_val_if_fail(VISU_IS_CONFIG_FILE(conf), (gchar*)0);
  g_return_val_if_fail(list, (gchar*)0);

  if (!*list)
    return (gchar*)0;

  file = getValidFileWithHeader(accessMode, conf, list);

  if (*list)
    *list = g_list_next(*list);

  if (!file)
    return file;

  if (utf8)
    {
      fileUTF8 = g_filename_from_utf8(file, -1, NULL, NULL, NULL);
      g_free(file);
      return fileUTF8;
    }
  else
    return file;
}
/**
 * visu_config_file_getDefaultFilename:
 * @kind: an integer identifier.
 *
 * This methods is used to get the filename used for different
 * config files.
 *
 * Returns: the filename of config file. The returned *gchar is
 *          owned by V_Sim and should not be freed or modified.
 */
const gchar* visu_config_file_getDefaultFilename(VisuConfigFileKind kind)
{
  if (kind == VISU_CONFIG_FILE_KIND_RESOURCE)
    return RESOURCES_FILENAMES[0];
  else
    return PARAMETERS_FILENAMES[0];
}
/**
 * visu_config_file_getPathList:
 * @conf: a #VisuConfigFile object ;
 *
 * V_Sim stores a list of paths where to look for resources or parameters
 * files, this method is used to get these lists.
 *
 * Returns: (transfer none) (element-type filename): the list of the
 * parameters or resources paths. This list is read-only.
 */
GList* visu_config_file_getPathList(VisuConfigFile *conf)
{
  g_return_val_if_fail(VISU_IS_CONFIG_FILE(conf), (GList*)0);

  return conf->priv->paths;
}
static void _addPath(VisuConfigFile *conf, char* dir)
{
  g_return_if_fail(VISU_IS_CONFIG_FILE(conf));
  g_return_if_fail(dir && dir[0]);

  if (g_list_find_custom(conf->priv->paths, (gconstpointer)dir,
                         compareStringsInGList))
    return;
  
  DBG_fprintf(stderr, "Visu ConfigFile: add a new resource directory"
              " to the path :\n '%s'\n", dir);
  conf->priv->paths = g_list_prepend(conf->priv->paths, (gpointer)dir);
}

/**
 * visu_config_file_exportToXML:
 * @conf: a #VisuConfigFile object ;
 * @filename: a string in the encoding of the file system ;
 * @error: a location to store an error.
 *
 * Export all the registered entries for resources or parameters to an
 * XML file.
 *
 * Returns: TRUE if the file is written with success.
 */
gboolean visu_config_file_exportToXML(VisuConfigFile *conf, const gchar *filename, GError **error)
{
  GString *str;
  GList *tmpLst, *lst;
  VisuConfigFileEntry *entry;
  gboolean status;
  gchar *desc;

  g_return_val_if_fail(filename && *filename, FALSE);
  g_return_val_if_fail(VISU_IS_CONFIG_FILE(conf), FALSE);

  str = g_string_new("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n");
  if (conf->priv->kind == VISU_CONFIG_FILE_KIND_PARAMETER)
    g_string_append_printf(str, "<configFile kind=\"parameters\">\n");
  else
    g_string_append_printf(str, "<configFile kind=\"resources\">\n");
  lst = visu_config_file_getEntries(conf);
  for (tmpLst = lst; tmpLst; tmpLst = g_list_next(tmpLst))
    {
      entry = (VisuConfigFileEntry*)tmpLst->data;
      if (entry->tag)
	g_string_append_printf(str, "  <entry name=\"%s\" tag=\"%s\" "
			       "version=\"%f3.1\">\n",
			       entry->key, entry->tag, entry->version);
      else
	g_string_append_printf(str, "  <entry name=\"%s\" version=\"%3.1f\">\n",
			       entry->key, entry->version);
      desc = g_markup_escape_text(entry->description, -1);
      g_string_append_printf(str, "    <description>%s</description>\n", desc);
      g_free(desc);
      if (entry->newKey)
	g_string_append_printf(str, "    <obsolete replacedBy=\"%s\" />\n",
			       entry->newKey);
      g_string_append_printf(str, "  </entry>\n");
    }
  g_string_append_printf(str, "</configFile>\n");
  g_list_free(lst);

  status = g_file_set_contents(filename, str->str, -1, error);
  g_string_free(str, TRUE);
  return status;
}

static gboolean readResourcesPaths(VisuConfigFileEntry *entry _U_, gchar **lines, int nbLines, int position _U_,
				   GError **error _U_)
{
  int i;
  gchar **tokens;
  gchar *key;

  g_return_val_if_fail(nbLines == 1, FALSE);

  tokens = g_strsplit_set(lines[0], ":", -1);
  for (i = 0; tokens[i]; i++)
    {
      key = g_strdup(tokens[i]);
      key = g_strstrip(key);
      if (key[0])
        _addPath(VISU_CONFIG_FILE_RESOURCE, key);
    }
  g_strfreev(tokens);
  return TRUE;
}
static void exportResourcesPaths(GString *data, VisuData *dataObj _U_)
{
  GList *pnt;

  g_string_append_printf(data, "# %s\n", DESC_RESOURCES_PATH);
  g_string_append_printf(data, "%s: ", FLAG_RESOURCES_PATH);
  pnt = visu_config_file_getPathList(resources);
  while(pnt)
    {
      /* We cancel the first and the last because it's the current working dir
	 and the install dir. */
      if (pnt->prev && pnt->next && pnt->next->next)
	g_string_append_printf(data, "%s", (char*)pnt->data);
      if (pnt->prev && pnt->next && pnt->next->next && pnt->next->next->next)
	g_string_append_printf(data, ":");
      pnt = g_list_next(pnt);
    }
  g_string_append_printf(data, "\n\n");
}

/* Specific routines. */
static gboolean _readTokens(VisuConfigFileEntry *entry, gchar **lines, int nbLines,
                            int position _U_, GError **error _U_)
{
  g_return_val_if_fail(nbLines == 1 && !entry->tokens, FALSE);

  /* Tokenize the line of values. */
  entry->tokens = g_strsplit_set(g_strchug(lines[0]), " \n", TOOL_MAX_LINE_LENGTH);
  entry->iToken = 0;
  return TRUE;
}
static gboolean _readBooleanv(VisuConfigFileEntry *entry, gchar **lines, int nbLines,
                              int position, GError **error)
{
  gboolean *vals;

  g_return_val_if_fail(nbLines == 1 && entry->storage && entry->nValues > 0, FALSE);
  vals = g_malloc(sizeof(gboolean) * entry->nValues);

  if (!tool_config_file_readBoolean(lines[0], position, vals, entry->nValues, error))
    {
      g_free(vals);
      return FALSE;
    }

  memcpy(entry->storage, vals, sizeof(gboolean) * entry->nValues);
  g_free(vals);

  return TRUE;
}
static gboolean _readEnum(VisuConfigFileEntry *entry, gchar **lines, int nbLines,
                          int position, GError **error)
{
  guint val;

  g_return_val_if_fail(nbLines == 1 && entry->storage, FALSE);

  if (!entry->toEnum(g_strstrip(lines[0]), &val))
    {
      *error = g_error_new(TOOL_CONFIG_FILE_ERROR, TOOL_CONFIG_FILE_ERROR_VALUE,
			   _("Parse error at line %d: '%s' is not a valid"
                             " value for %s markup.\n"), position, lines[0], entry->key);
      return FALSE;
    }
  *(guint*)entry->storage = val;

  return TRUE;
}
static gboolean _readString(VisuConfigFileEntry *entry, gchar **lines, int nbLines,
                            int position, GError **error)
{
  gchar **str;

  g_return_val_if_fail(nbLines == 1 && entry->storage, FALSE);

  lines[0] = g_strstrip(lines[0]);

  if (!lines[0][0])
    {
      *error = g_error_new(TOOL_CONFIG_FILE_ERROR, TOOL_CONFIG_FILE_ERROR_VALUE,
			   _("Parse error at line %d: 1 string value must appear"
			     " after the %s markup.\n"), position, entry->key);
      return FALSE;
    }
  str = (gchar**)entry->storage;
  g_free(*str);
  *str = g_strdup(lines[0]);

  return TRUE;
}
static gboolean _readIntv(VisuConfigFileEntry *entry, gchar **lines, int nbLines,
                          int position, GError **error)
{
  guint i;
  int *vals;
  
  g_return_val_if_fail(nbLines == 1 && entry->storage && entry->nValues > 0, FALSE);
  vals = g_malloc(sizeof(int) * entry->nValues);

  if (!tool_config_file_readInteger(lines[0], position, vals, entry->nValues, error))
    {
      g_free(vals);
      return FALSE;
    }

  for (i = 0; i < entry->nValues; i++)
    if (tool_config_file_clampInt(vals + i, vals[i], entry->range.i[0], entry->range.i[1]))
      {
        *error = g_error_new(TOOL_CONFIG_FILE_ERROR, TOOL_CONFIG_FILE_ERROR_VALUE,
                             _("Parse error at line %d: %d integer values"
                               "(%d <= v <= %d) must appear after the %s markup.\n"),
                             position, entry->nValues, entry->range.i[0], entry->range.i[1],
                             entry->key);
        g_free(vals);
        return FALSE;
      }
  memcpy(entry->storage, vals, sizeof(int) * entry->nValues);
  g_free(vals);

  return TRUE;
}
static gboolean _readFloatv(VisuConfigFileEntry *entry, gchar **lines, int nbLines,
                            int position, GError **error)
{
  guint i;
  float *vals;
  
  g_return_val_if_fail(nbLines == 1 && entry->storage && entry->nValues > 0, FALSE);
  vals = g_malloc(sizeof(float) * entry->nValues);

  if (!tool_config_file_readFloat(lines[0], position, vals, entry->nValues, error))
    {
      g_free(vals);
      return FALSE;
    }

  for (i = 0; i < entry->nValues; i++)
    if (tool_config_file_clampFloat(vals + i, vals[i], entry->range.f[0], entry->range.f[1]))
      {
        *error = g_error_new(TOOL_CONFIG_FILE_ERROR, TOOL_CONFIG_FILE_ERROR_VALUE,
                             _("Parse error at line %d: %d floating points "
                               "(%g <= v <= %g) must appear after the %s markup."
                               " Read line was '%s'.\n"),
                             position, entry->nValues, entry->range.f[0], entry->range.f[1],
                             entry->key, lines[0]);
        g_free(vals);
        return FALSE;
      }
  memcpy(entry->storage, vals, sizeof(float) * entry->nValues);
  g_free(vals);

  return TRUE;
}
static gboolean _readStipplev(VisuConfigFileEntry *entry, gchar **lines, int nbLines,
                              int position, GError **error)
{
  guint i;
  gint *vals;
  guint16 *storage;
  
  g_return_val_if_fail(nbLines == 1 && entry->storage && entry->nValues > 0, FALSE);
  vals = g_malloc(sizeof(float) * entry->nValues);

  if (!tool_config_file_readInteger(lines[0], position, vals, entry->nValues, error))
    {
      g_free(vals);
      return FALSE;
    }

  storage = (guint16*)entry->storage;
  for (i = 0; i < entry->nValues; i++)
    storage[i] = (guint16)vals[i];
  g_free(vals);

  return TRUE;
}
