/* GConf
 * Copyright (C) 1999, 2000 Red Hat Inc.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */

#include "xml-entry.h"
#include <gconf/gconf-internals.h>
#include <stdlib.h>
#include <string.h>
#include <libxml/entities.h>
#include <libxml/globals.h>

static void
entry_sync_if_needed(Entry* e);
static GConfValue*
node_extract_value(xmlNodePtr node, const gchar** locales, GError** err);
static xmlNodePtr
find_schema_subnode_by_locale(xmlNodePtr node, const gchar* locale);
static void
node_unset_by_locale(xmlNodePtr node, const gchar* locale);
static void
node_unset_value(xmlNodePtr node);

struct _Entry {
  gchar* name; /* a relative key */
  gchar* schema_name;
  GConfValue* cached_value;
  xmlNodePtr node;
  gchar* mod_user;
  GTime mod_time;
  guint dirty : 1;
};

Entry*
entry_new (const gchar* relative_name)
{
  Entry* e;

  g_return_val_if_fail(relative_name != NULL, NULL);
  
  e = g_new0(Entry, 1);

  e->name = g_strdup(relative_name);

  e->dirty = TRUE;
  
  return e;
}

void
entry_destroy (Entry* e)
{
  if (e->name)
    g_free(e->name);

  if (e->cached_value)
    gconf_value_free(e->cached_value);

  if (e->mod_user)
    g_free(e->mod_user);

  if (e->node != NULL)
    {
      xmlUnlinkNode(e->node);
      xmlFreeNode(e->node);
      e->node = NULL;
    }
  
  g_free(e);
}

const gchar*
entry_get_name(Entry* e)
{
  return e->name;
}

void
entry_set_node (Entry*e, xmlNodePtr node)
{
  e->node = node;

  e->dirty = TRUE;
}

xmlNodePtr
entry_get_node (Entry*e)
{
  return e->node;
}

GConfValue*
entry_get_value(Entry* e, const gchar** locales, GError** err)
{
  const gchar* sl;
  
  g_return_val_if_fail(e != NULL, NULL);
  
  if (e->cached_value == NULL)
    return NULL;

  /* only schemas have locales for now anyway */
  if (e->cached_value->type != GCONF_VALUE_SCHEMA)
    return e->cached_value;

  g_assert(e->cached_value->type == GCONF_VALUE_SCHEMA);

  sl = gconf_schema_get_locale(gconf_value_get_schema(e->cached_value));

  gconf_log (GCL_DEBUG, "Cached schema value has locale \"%s\", looking for %s",
             sl ? sl : "null",
             locales && locales [0] ? locales[0] : "null");
  
  /* optimize most common cases first */
  if (sl == NULL && (locales == NULL ||
                     *locales == NULL))
    return e->cached_value;
  else if (sl && locales && *locales &&
           strcmp(sl, *locales) == 0)
    return e->cached_value;
  else
    {
      /* We want a locale other than the currently-loaded one */
      GConfValue* newval;
      GError* error = NULL;

      entry_sync_if_needed(e);
      
      newval = node_extract_value(e->node, locales, &error);
      if (newval != NULL)
        {
          /* We found a schema with an acceptable locale */
          gconf_value_free(e->cached_value);
          e->cached_value = newval;
          g_return_val_if_fail(error == NULL, e->cached_value);
        }
      else if (error != NULL)
        {
          /* There was an error */
          gconf_log(GCL_WARNING, _("Ignoring XML node with name `%s': %s"),
                    e->name, error->message);
          g_error_free(error);

          /* Fall back to currently-loaded thing if any */
        }
      /* else fall back to the currently-loaded schema */
    }

  return e->cached_value;
}

void
entry_set_value(Entry* e, const GConfValue* value)
{
  g_return_if_fail(e != NULL);
  
  entry_sync_if_needed(e);
  
  if (e->cached_value)
    gconf_value_free(e->cached_value);
      
  e->cached_value = gconf_value_copy(value);

  e->dirty = TRUE;
}

gboolean
entry_unset_value     (Entry        *e,
                       const gchar  *locale)
{
  g_return_val_if_fail(e != NULL, FALSE);

  if (e->cached_value != NULL)
    {
      if (locale && e->cached_value->type == GCONF_VALUE_SCHEMA)
        {
          GError* error = NULL;
          
          /* Remove the localized node from the XML tree */
          g_assert(e->node != NULL);
          node_unset_by_locale(e->node, locale);

          /* e->cached_value is always non-NULL if some value is
             available; in the schema case there may be leftover
             values */
          gconf_value_free(e->cached_value);
          e->cached_value = node_extract_value(e->node, NULL, &error);

          if (error != NULL)
            {
              gconf_log(GCL_WARNING, "%s", error->message);
              g_error_free(error);
              error = NULL;
            }
        }
      else if (e->cached_value->type == GCONF_VALUE_SCHEMA)
        {
          /* if locale == NULL nuke all the locales */
          if (e->cached_value)
            gconf_value_free(e->cached_value);
          e->cached_value = NULL;
        }
      else
        {
          gconf_value_free(e->cached_value);
          e->cached_value = NULL;
        }

      e->dirty = TRUE;
      
      return TRUE;
    }
  else
    return FALSE;
}

GConfMetaInfo*
entry_get_metainfo(Entry* e)
{
  GConfMetaInfo* gcmi;
  
  g_return_val_if_fail(e != NULL, NULL);

  gcmi = gconf_meta_info_new();

  if (e->schema_name)
    gconf_meta_info_set_schema(gcmi, e->schema_name);

  if (e->mod_time != 0)
    gconf_meta_info_set_mod_time(gcmi, e->mod_time);

  if (e->mod_user)
    gconf_meta_info_set_mod_user(gcmi, e->mod_user);

  return gcmi;
}

const gchar*
entry_get_schema_name (Entry        *e)
{
  return e->schema_name;
}

void
entry_set_schema_name (Entry        *e,
                       const gchar  *name)
{
  if (e->schema_name)
    g_free(e->schema_name);

  e->schema_name = name ? g_strdup(name) : NULL;
  
  e->dirty = TRUE;
}

void
entry_set_mod_time   (Entry        *e,
                      GTime         mod_time)
{
  g_return_if_fail(e != NULL);

  e->mod_time = mod_time;
  e->dirty = TRUE;
}

void
entry_set_mod_user (Entry *e,
                    const gchar* user)
{
  g_return_if_fail(e != NULL);
  
  if (e->mod_user)
    g_free(e->mod_user);
  e->mod_user = g_strdup(user);

  e->dirty = TRUE;
}

/*
 * XML-related cruft
 */

static void
entry_sync_if_needed(Entry* e)
{
  if (!e->dirty)
    return;
  
  if (e->cached_value &&
      e->cached_value->type == GCONF_VALUE_SCHEMA)
    {
      entry_sync_to_node(e);
    }
}

void
entry_fill_from_node(Entry* e)
{
  gchar* tmp;
  GError* error = NULL;

  g_return_if_fail(e->node != NULL);
  
  tmp = my_xmlGetProp(e->node, "schema");
  
  if (tmp != NULL)
    {
      /* Filter any crap schemas that appear, some speed cost */
      gchar* why_bad = NULL;
      if (gconf_valid_key(tmp, &why_bad))
        {
          g_assert(why_bad == NULL);
          e->schema_name = g_strdup(tmp);
        }
      else
        {
          e->schema_name = NULL;
          gconf_log(GCL_WARNING, _("Ignoring schema name `%s', invalid: %s"),
                    tmp, why_bad);
          g_free(why_bad);
        }
          
      xmlFree(tmp);
    }
      
  tmp = my_xmlGetProp(e->node, "mtime");

  if (tmp != NULL)
    {
      e->mod_time = gconf_string_to_gulong(tmp);
      xmlFree(tmp);
    }
  else
    e->mod_time = 0;

  tmp = my_xmlGetProp(e->node, "muser");

  if (tmp != NULL)
    {
      e->mod_user = g_strdup(tmp);
      xmlFree(tmp);
    }
  else
    e->mod_user = NULL;

  entry_sync_if_needed(e);
  
  if (e->cached_value != NULL)
    gconf_value_free(e->cached_value);
  
  e->cached_value = node_extract_value(e->node, NULL, /* FIXME current locale as a guess */
                                       &error);

  if (e->cached_value)
    {
      g_return_if_fail(error == NULL);
      return;
    }
  else if (error != NULL)
    {
      /* Ignore errors from node_extract_value() if we got a schema name,
       * since the node's only purpose may be to store the schema name.
       */
      if (e->schema_name == NULL)
        gconf_log (GCL_WARNING,
                   _("Ignoring XML node `%s': %s"),
                   e->name, error->message);
      g_error_free(error);
    }
}

static void
node_set_value(xmlNodePtr node, GConfValue* value);

static void
free_childs(xmlNodePtr node)
{
  g_return_if_fail(node != NULL);
  
  if (node->xmlChildrenNode)
    xmlFreeNodeList(node->xmlChildrenNode);
  node->xmlChildrenNode = NULL;
  node->last = NULL;
}

void
entry_sync_to_node (Entry* e)
{
  g_return_if_fail(e != NULL);
  g_return_if_fail(e->node != NULL);
  
  if (!e->dirty)
    return;

  /* Unset all properties, so we don't have old cruft. */
  if (e->node->properties)
    xmlFreePropList(e->node->properties);
  e->node->properties = NULL;
  
  my_xmlSetProp(e->node, "name", e->name);

  if (e->mod_time != 0)
    {
      gchar* str = g_strdup_printf("%u", (guint)e->mod_time);
      my_xmlSetProp(e->node, "mtime", str);
      g_free(str);
    }
  else
    my_xmlSetProp(e->node, "mtime", NULL); /* Unset */

  /* OK if schema_name is NULL, then we unset */
  my_xmlSetProp(e->node, "schema", e->schema_name);

  /* OK if mod_user is NULL, since it unsets */
  my_xmlSetProp(e->node, "muser", e->mod_user);

  if (e->cached_value)
    node_set_value(e->node, e->cached_value);
  else
    node_unset_value(e->node);
  
  e->dirty = FALSE;
}

static void
node_set_schema_value(xmlNodePtr node,
                      GConfValue* value)
{
  GConfSchema* sc;
  const gchar* locale;
  const gchar* type;
  xmlNodePtr found = NULL;

  sc = gconf_value_get_schema (value);

  /* Set the types */
  if (gconf_schema_get_list_type (sc) != GCONF_VALUE_INVALID)
    {
      type = gconf_value_type_to_string(gconf_schema_get_list_type (sc));
      g_assert(type != NULL);
      my_xmlSetProp(node, "list_type", type);
    }
  if (gconf_schema_get_car_type (sc) != GCONF_VALUE_INVALID)
    {
      type = gconf_value_type_to_string(gconf_schema_get_car_type (sc));
      g_assert(type != NULL);
      my_xmlSetProp(node, "car_type", type);
    }
  if (gconf_schema_get_cdr_type (sc) != GCONF_VALUE_INVALID)
    {
      type = gconf_value_type_to_string(gconf_schema_get_cdr_type (sc));
      g_assert(type != NULL);
      my_xmlSetProp(node, "cdr_type", type);
    }
  
  /* unset this in case the node was previously a different type */
  my_xmlSetProp(node, "value", NULL);

  /* set the cross-locale attributes */
  my_xmlSetProp(node, "stype", gconf_value_type_to_string(gconf_schema_get_type (sc)));
  my_xmlSetProp(node, "owner", gconf_schema_get_owner (sc));

  locale = gconf_schema_get_locale(sc);

  gconf_log(GCL_DEBUG, "Setting XML node to schema with locale `%s'",
            locale);
  
  /* Find the node for this locale */

  found = find_schema_subnode_by_locale(node, locale);
  
  if (found == NULL)
    found = xmlNewChild(node, NULL, (xmlChar *)"local_schema", NULL);
  
  /* OK if these are set to NULL, since that unsets the property */
  my_xmlSetProp(found, "locale", gconf_schema_get_locale (sc));
  my_xmlSetProp(found, "short_desc", gconf_schema_get_short_desc (sc));

  free_childs(found);
  
  if (gconf_schema_get_default_value (sc) != NULL)
    {
      xmlNodePtr default_value_node;
      default_value_node = xmlNewChild(found, NULL, (xmlChar *)"default", NULL);
      node_set_value(default_value_node, gconf_schema_get_default_value (sc));
    }
  
  if (gconf_schema_get_long_desc (sc))
    {
      xmlNodePtr ld_node;
      
      ld_node = xmlNewChild(found, NULL, (xmlChar *)"longdesc", 
                            (xmlChar *)gconf_schema_get_long_desc (sc));
    }
}

static void
node_set_value(xmlNodePtr node, GConfValue* value)
{
  const gchar* type;
  gchar* value_str;

  g_return_if_fail(node != NULL);
  g_return_if_fail(value != NULL);
  g_return_if_fail(value->type != GCONF_VALUE_INVALID);
  
  type = gconf_value_type_to_string(value->type);

  g_assert(type != NULL);
  
  my_xmlSetProp(node, "type", type);

  switch (value->type)
    {
    case GCONF_VALUE_INT:
    case GCONF_VALUE_FLOAT:
    case GCONF_VALUE_BOOL:
      free_childs(node);
      
      value_str = gconf_value_to_string(value);
  
      my_xmlSetProp(node, "value", value_str);

      g_free(value_str);
      break;
    case GCONF_VALUE_STRING:
      {
        xmlNodePtr child;
        xmlChar* encoded;
        
        free_childs(node);

        encoded = xmlEncodeEntitiesReentrant(node->doc,
                                             (xmlChar *)gconf_value_get_string(value));
        
        child = xmlNewChild(node, NULL, (xmlChar *)"stringvalue",
                            encoded);
        xmlFree(encoded);
      }
      break;      
    case GCONF_VALUE_SCHEMA:
      {
        node_set_schema_value(node, value);
      }
      break;
    case GCONF_VALUE_LIST:
      {
        GSList* list;

        free_childs(node);

        my_xmlSetProp(node, "ltype",
                      gconf_value_type_to_string(gconf_value_get_list_type(value)));
        
        /* Add a new child for each node */
        list = gconf_value_get_list(value);

        while (list != NULL)
          {
            xmlNodePtr child;
            /* this is O(1) because libxml saves the list tail */
            child = xmlNewChild(node, NULL, (xmlChar *)"li", NULL);

            g_return_if_fail(list->data != NULL);
            
            node_set_value(child, (GConfValue*)list->data);
            
            list = g_slist_next(list);
          }
      }
      break;
      
    case GCONF_VALUE_PAIR:
      {
        xmlNodePtr car, cdr;

        free_childs(node);

        car = xmlNewChild(node, NULL, (xmlChar *)"car", NULL);
        cdr = xmlNewChild(node, NULL, (xmlChar *)"cdr", NULL);

        g_return_if_fail(gconf_value_get_car(value) != NULL);
        g_return_if_fail(gconf_value_get_cdr(value) != NULL);
        
        node_set_value(car, gconf_value_get_car(value));
        node_set_value(cdr, gconf_value_get_cdr(value));
      }
      break;
      
    default:
      g_assert_not_reached();
      break;
    }
}

static xmlNodePtr
find_schema_subnode_by_locale(xmlNodePtr node, const gchar* locale)
{
  xmlNodePtr iter;
  xmlNodePtr found = NULL;
    
  iter = node->xmlChildrenNode;
      
  while (iter != NULL)
    {
      if (iter->type == XML_ELEMENT_NODE &&
          strcmp((char *)iter->name, "local_schema") == 0)
        {
          char* this_locale = my_xmlGetProp(iter, "locale");
          
          if (locale && this_locale &&
              strcmp(locale, this_locale) == 0)
            {
              found = iter;
              xmlFree(this_locale);
              break;
            }
          else if (this_locale == NULL &&
                   locale == NULL)
            {
              found = iter;
              break;
            }
          else if (this_locale != NULL)
            xmlFree(this_locale);
        }
      iter = iter->next;
    }

  return found;
}

static void
node_unset_value(xmlNodePtr node)
{
  free_childs(node);
  my_xmlSetProp(node, "value", NULL);
  my_xmlSetProp(node, "type", NULL);
  my_xmlSetProp(node, "stype", NULL);
  my_xmlSetProp(node, "ltype", NULL);
  my_xmlSetProp(node, "owner", NULL);
  my_xmlSetProp(node, "list_type", NULL);
  my_xmlSetProp(node, "car_type", NULL);
  my_xmlSetProp(node, "cdr_type", NULL);
}

static void
node_unset_by_locale(xmlNodePtr node, const gchar* locale)
{
  xmlNodePtr found;

  g_return_if_fail(node != NULL);
  g_return_if_fail(locale != NULL);

  if (locale != NULL)
    {
      found = find_schema_subnode_by_locale(node, locale);
      
      if (found != NULL)
        {
          xmlUnlinkNode(found);
          xmlFreeNode(found);
        }
    }
  else
    {
      node_unset_value(node);
    }
}

static void
schema_subnode_extract_data(xmlNodePtr node, GConfSchema* sc)
{
  gchar* sd_str;
  gchar* locale_str;
  GError* error = NULL;
  
  sd_str = my_xmlGetProp(node, "short_desc");
  locale_str = my_xmlGetProp(node, "locale");
  
  if (sd_str)
    {
      gconf_schema_set_short_desc(sc, sd_str);
      xmlFree(sd_str);
    }

  if (locale_str)
    {
      gconf_log(GCL_DEBUG, "found locale `%s'", locale_str);
      gconf_schema_set_locale(sc, locale_str);
      xmlFree(locale_str);
    }
  else
    {
      gconf_log(GCL_DEBUG, "found <%s> with no locale setting",
                node->name ? node->name : (unsigned char*) "null");
    }
  
  if (node->xmlChildrenNode != NULL)
    {
      GConfValue* default_value = NULL;
      xmlChar* ld_str = NULL;
      GSList* bad_nodes = NULL;
      xmlNodePtr iter = node->xmlChildrenNode;

      while (iter != NULL)
        {
          if (iter->type == XML_ELEMENT_NODE)
            {
              if (default_value == NULL &&
                  strcmp((char *)iter->name, "default") == 0)
                {
                  default_value = node_extract_value(iter, NULL, &error);

                  if (error != NULL)
                    {
                      g_assert(default_value == NULL);
                      
                      gconf_log(GCL_WARNING, _("Failed reading default value for schema: %s"), 
                                error->message);
                      g_error_free(error);
                      error = NULL;
                      
                      bad_nodes = g_slist_prepend(bad_nodes, iter);
                    }
                }
              else if (ld_str == NULL &&
                       strcmp((char *)iter->name, "longdesc") == 0)
                {
                  ld_str = xmlNodeGetContent(iter);
                }
              else
                {
                  bad_nodes = g_slist_prepend(bad_nodes, iter);
                }
            }
          else
            bad_nodes = g_slist_prepend(bad_nodes, iter); /* what is this node? */

          iter = iter->next;
        }
      

      /* Remove the bad nodes from the parse tree */
      if (bad_nodes != NULL)
        {
          GSList* tmp = bad_nodes;
          
          while (tmp != NULL)
            {
              xmlUnlinkNode(tmp->data);
              xmlFreeNode(tmp->data);
              
              tmp = g_slist_next(tmp);
            }
          
          g_slist_free(bad_nodes);
        }

      if (default_value != NULL)
        gconf_schema_set_default_value_nocopy(sc, default_value);

      if (ld_str)
        {
          gconf_schema_set_long_desc(sc, (char *)ld_str);
          xmlFree(ld_str);
        }
    }
}

static GConfValue*
schema_node_extract_value(xmlNodePtr node, const gchar** locales)
{
  GConfValue* value = NULL;
  gchar* owner_str;
  gchar* stype_str;
  gchar* list_type_str;
  gchar* car_type_str;
  gchar* cdr_type_str;
  GConfSchema* sc;
  xmlNodePtr iter;
  guint i;
  xmlNodePtr* localized_nodes;
  xmlNodePtr best = NULL;
  
  /* owner, type are for all locales;
     default value, descriptions are per-locale
  */

  owner_str = my_xmlGetProp(node, "owner");
  stype_str = my_xmlGetProp(node, "stype");
  list_type_str = my_xmlGetProp(node, "list_type");
  car_type_str = my_xmlGetProp(node, "car_type");
  cdr_type_str = my_xmlGetProp(node, "cdr_type");

  sc = gconf_schema_new();

  if (owner_str)
    {
      gconf_schema_set_owner(sc, owner_str);
      xmlFree(owner_str);
    }
  if (stype_str)
    {
      GConfValueType stype;
      stype = gconf_value_type_from_string(stype_str);
      gconf_schema_set_type(sc, stype);
      xmlFree(stype_str);
    }
  if (list_type_str)
    {
      GConfValueType type;
      type = gconf_value_type_from_string(list_type_str);
      gconf_schema_set_list_type(sc, type);
      xmlFree(list_type_str);
    }
  if (car_type_str)
    {
      GConfValueType type;
      type = gconf_value_type_from_string(car_type_str);
      gconf_schema_set_car_type(sc, type);
      xmlFree(car_type_str);
    }
  if (cdr_type_str)
    {
      GConfValueType type;
      type = gconf_value_type_from_string(cdr_type_str);
      gconf_schema_set_cdr_type(sc, type);
      xmlFree(cdr_type_str);
    }  
  
  if (locales != NULL && locales[0])
    {
      /* count the number of possible locales */
      int n_locales;
      
      n_locales = 0;
      while (locales[n_locales])
        ++n_locales;
      
      localized_nodes = g_new0(xmlNodePtr, n_locales);
      
      /* Find the node for each possible locale */
      iter = node->xmlChildrenNode;
      
      while (iter != NULL)
        {
          if (iter->type == XML_ELEMENT_NODE &&
              strcmp((char *)iter->name, "local_schema") == 0)
            {
              char* locale_name;
              
              locale_name = my_xmlGetProp(iter, "locale");
              
              if (locale_name != NULL)
                {
                  i = 0;
                  while (locales[i])
                    {
                      if (strcmp(locales[i], locale_name) == 0)
                        {
                          localized_nodes[i] = iter;
                          break;
                        }
                      ++i;
                    }

                  xmlFree(locale_name);
                  
                  /* Quit as soon as we have the best possible locale */
                  if (localized_nodes[0] != NULL)
                    break;
                }
            }
          
          iter = iter->next;
        }

      /* See which is the best locale we managed to load, they are in
         order of preference. */
      
      i = 0;
      best = localized_nodes[i];
      while (best == NULL && i < n_locales)
        {
          best = localized_nodes[i];
          ++i;
        }
      
      g_free(localized_nodes);
    }

  /* If no locale matched, try picking the the null localization,
   * and then try picking the first node
   */
  if (best == NULL)
    best = find_schema_subnode_by_locale (node, NULL);

  if (best == NULL)
    {
      best = node->xmlChildrenNode;
      while (best && best->type != XML_ELEMENT_NODE)
        best = best->next;
    }
  
  /* Extract info from the best locale node */
  if (best != NULL)
    schema_subnode_extract_data(best, sc); 
  
  /* Create a GConfValue with this schema and return it */
  value = gconf_value_new(GCONF_VALUE_SCHEMA);
      
  gconf_value_set_schema_nocopy(value, sc);

  return value;
}

/* this actually works on any node,
   not just <entry>, such as the <car>
   and <cdr> nodes and the <li> nodes and the
   <default> node
*/
static GConfValue*
node_extract_value(xmlNodePtr node, const gchar** locales, GError** err)
{
  GConfValue* value = NULL;
  gchar* type_str;
  GConfValueType type = GCONF_VALUE_INVALID;
  const gchar* default_locales[] = { "C", NULL };
  
  if (locales == NULL)
    locales = default_locales;
  
  type_str = my_xmlGetProp(node, "type");

  if (type_str == NULL)
    {
      gconf_set_error(err, GCONF_ERROR_PARSE_ERROR,
                      _("No \"type\" attribute for <%s> node"),
                      (node->name ? (char*)node->name : "(nil)"));
      return NULL;
    }
      
  type = gconf_value_type_from_string(type_str);

  xmlFree(type_str);
  
  switch (type)
    {
    case GCONF_VALUE_INVALID:
      {
        gconf_set_error(err, GCONF_ERROR_PARSE_ERROR,
                        _("A node has unknown \"type\" attribute `%s', ignoring"), type_str);
        return NULL;
      }
      break;
    case GCONF_VALUE_INT:
    case GCONF_VALUE_BOOL:
    case GCONF_VALUE_FLOAT:
      {
        gchar* value_str;
        
        value_str = my_xmlGetProp(node, "value");
        
        if (value_str == NULL)
          {
            gconf_set_error(err, GCONF_ERROR_PARSE_ERROR,
                            _("No \"value\" attribute for node"));
            return NULL;
          }
            
        value = gconf_value_new_from_string(type, value_str, err);

        xmlFree(value_str);

        g_return_val_if_fail( (value != NULL) ||
                              (err == NULL) ||
                              (*err != NULL),
                              NULL );
        
        return value;
      }
      break;
    case GCONF_VALUE_STRING:
      {
        xmlNodePtr iter;
        
        iter = node->xmlChildrenNode;

        while (iter != NULL)
          {
            if (iter->type == XML_ELEMENT_NODE)
              {
                GConfValue* v = NULL;

                if (strcmp((char *)iter->name, "stringvalue") == 0)
                  {
                    gchar* s;

                    s = (gchar *)xmlNodeGetContent(iter);

                    v = gconf_value_new(GCONF_VALUE_STRING);

                    /* strdup() caused purely by g_free()/free()
                       difference */
                    gconf_value_set_string(v, s ? s : "");
                    if (s)
                      xmlFree(s);
                        
                    return v;
                  }
                else
                  {
                    /* What the hell is this? */
                    gconf_log(GCL_WARNING,
                              _("Didn't understand XML node <%s> inside an XML list node"),
                              iter->name ? iter->name : (guchar*)"???");
                  }
              }
            iter = iter->next;
          }
        return NULL;
      }
      break;      
    case GCONF_VALUE_SCHEMA:
      return schema_node_extract_value(node, locales);
      break;
    case GCONF_VALUE_LIST:
      {
        xmlNodePtr iter;
        GSList* values = NULL;
        GConfValueType list_type = GCONF_VALUE_INVALID;

        {
          gchar* s;
          s = my_xmlGetProp(node, "ltype");
          if (s != NULL)
            {
              list_type = gconf_value_type_from_string(s);
              xmlFree(s);
            }
        }

        switch (list_type)
          {
          case GCONF_VALUE_INVALID:
          case GCONF_VALUE_LIST:
          case GCONF_VALUE_PAIR:
            gconf_set_error(err, GCONF_ERROR_PARSE_ERROR,
                            _("Invalid type (list, pair, or unknown) in a list node"));
              
            return NULL;
          default:
            break;
          }
        
        iter = node->xmlChildrenNode;

        while (iter != NULL)
          {
            if (iter->type == XML_ELEMENT_NODE)
              {
                GConfValue* v = NULL;
                if (strcmp((char*)iter->name, "li") == 0)
                  {
                    
                    v = node_extract_value(iter, locales, err);
                    if (v == NULL)
                      {
                        if (err && *err)
                          {
                            gconf_log(GCL_WARNING,
                                      _("Bad XML node: %s"),
                                      (*err)->message);
                            /* avoid pile-ups */
                            g_clear_error(err);
                          }
                      }
                    else if (v->type != list_type)
                      {
                        gconf_log(GCL_WARNING, _("List contains a badly-typed node (%s, should be %s)"),
                                  gconf_value_type_to_string(v->type),
                                  gconf_value_type_to_string(list_type));
                        gconf_value_free(v);
                        v = NULL;
                      }
                  }
                else
                  {
                    /* What the hell is this? */
                    gconf_log(GCL_WARNING,
                              _("Didn't understand XML node <%s> inside an XML list node"),
                              iter->name ? iter->name : (guchar*)"???");
                  }

                if (v != NULL)
                  values = g_slist_prepend(values, v);
              }
            iter = iter->next;
          }
        
        /* put them in order, set the value */
        values = g_slist_reverse(values);

        value = gconf_value_new(GCONF_VALUE_LIST);

        gconf_value_set_list_type(value, list_type);
        gconf_value_set_list_nocopy(value, values);

        return value;
      }
      break;
    case GCONF_VALUE_PAIR:
      {
        GConfValue* car = NULL;
        GConfValue* cdr = NULL;
        xmlNodePtr iter;
        
        iter = node->xmlChildrenNode;

        while (iter != NULL)
          {
            if (iter->type == XML_ELEMENT_NODE)
              {
                if (car == NULL && strcmp((char *)iter->name, "car") == 0)
                  {
                    car = node_extract_value(iter, locales, err);
                    if (car == NULL)
                      {
                        if (err && *err)
                          {
                            gconf_log(GCL_WARNING,
                                      _("Ignoring bad car from XML pair: %s"),
                                      (*err)->message);
                            /* prevent pile-ups */
                            g_clear_error(err);
                          }
                      }
                    else if (car->type == GCONF_VALUE_LIST ||
                             car->type == GCONF_VALUE_PAIR)
                      {
                        gconf_log(GCL_WARNING, _("parsing XML file: lists and pairs may not be placed inside a pair"));
                        gconf_value_free(car);
                        car = NULL;
                      }
                  }
                else if (cdr == NULL && strcmp((char *)iter->name, "cdr") == 0)
                  {
                    cdr = node_extract_value(iter, locales, err);
                    if (cdr == NULL)
                      {
                        if (err && *err)
                          {
                            gconf_log(GCL_WARNING,
                                      _("Ignoring bad cdr from XML pair: %s"),
                                      (*err)->message);
                            /* avoid pile-ups */
                            g_clear_error(err);
                          }
                      }
                    else if (cdr->type == GCONF_VALUE_LIST ||
                             cdr->type == GCONF_VALUE_PAIR)
                      {
                        gconf_log(GCL_WARNING,
                                  _("parsing XML file: lists and pairs may not be placed inside a pair"));
                        gconf_value_free(cdr);
                        cdr = NULL;
                      }
                  }
                else
                  {
                    /* What the hell is this? */
                    gconf_log(GCL_WARNING,
                              _("Didn't understand XML node <%s> inside an XML pair node"),
                              iter->name ? (gchar*)iter->name : "???");                    
                  }
              }
            iter = iter->next;
          }

        /* Return the pair if we got both halves */
        if (car && cdr)
          {
            value = gconf_value_new(GCONF_VALUE_PAIR);
            gconf_value_set_car_nocopy(value, car);
            gconf_value_set_cdr_nocopy(value, cdr);

            return value;
          }
        else
          {
            gconf_log(GCL_WARNING, _("Didn't find car and cdr for XML pair node"));
            if (car)
              {
                g_assert(cdr == NULL);
                gconf_value_free(car);
                gconf_set_error(err, GCONF_ERROR_PARSE_ERROR,
                                _("Missing cdr from pair of values in XML file"));
              }
            else if (cdr)
              {
                g_assert(car == NULL);
                gconf_value_free(cdr);
                gconf_set_error(err, GCONF_ERROR_PARSE_ERROR,
                                _("Missing car from pair of values in XML file"));
              }
            else
              {
                gconf_set_error(err, GCONF_ERROR_PARSE_ERROR,
                                _("Missing both car and cdr values from pair in XML file"));
              }

            return NULL;
          }
      }
      break;
    default:
      g_assert_not_reached();
      return NULL;
      break;
    }
}

/*
 * Enhanced libxml
 */

/* makes setting to NULL or empty string equal to unset */

void
my_xmlSetProp(xmlNodePtr node,
              const gchar* name,
              const gchar* str)
{
  xmlAttrPtr prop;

  prop = xmlSetProp(node, (xmlChar *)name, (xmlChar *)str);

  if (str == NULL || *str == '\0')
    {
      xmlAttrPtr iter;
      xmlAttrPtr prev;

      prev = NULL;
      iter = node->properties;
      while (iter != NULL)
        {
          if (iter == prop)
            break;
          prev = iter;
          iter = iter->next;
        }

      g_assert(iter == prop);
      
      if (prev)
        prev->next = iter->next;
      else
        node->properties = iter->next; /* we were the first node */

      xmlFreeProp(iter);
    }
}

/* Makes empty strings equal to "unset" */
char*
my_xmlGetProp(xmlNodePtr node,
              const gchar* name)
{
  xmlChar* prop;

  prop = xmlGetProp(node, (xmlChar *)name);

  if (prop && *prop == '\0')
    {
      xmlFree(prop);
      return NULL;
    }
  else
    return prop;
}

void
xml_test_entry (void)
{
#ifndef GCONF_DISABLE_TESTS
  


#endif
}


syntax highlighted by Code2HTML, v. 0.9.1