#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include "gskxml.h"
#include "../gskerror.h"
#include "../gsksocketaddress.h"
#include "../gskghelpers.h"
#include "../config.h"

typedef struct _Parser Parser;
typedef struct _TypeInfo TypeInfo;
typedef struct _TypeTagInfo TypeTagInfo;

struct _TypeTagInfo
{
  GskXmlObjectHandler    handler;
  gpointer               data;
  GDestroyNotify         destroy;
};

struct _TypeInfo
{
  GType type;
  GHashTable *nickname_to_type; /* GskXmlString => type */
  GHashTable *tag_to_info;      /* GskXmlString => TypeTagInfo */

  GskXmlContextParserFunc func;
  GskXmlContextToXmlFunc  to_xml;
  gpointer                data;
  GDestroyNotify          destroy;

  GskXmlObjectWriter     add_misc_handler;
  gpointer               add_misc_data;
  GDestroyNotify         add_misc_destroy;

  GskXmlValidateFunc     validator;
  gpointer               validator_data;
  GDestroyNotify         validator_data_destroy;
};

struct _GskXmlContext
{
  GHashTable *type_info;         /* GType => TypeInfo */
};

static TypeInfo *
try_type_info (GskXmlContext *context,
               GType          type)
{
  return g_hash_table_lookup (context->type_info, (gpointer) type);
}
static TypeInfo *
force_type_info (GskXmlContext *context,
                 GType          type)
{
  TypeInfo *info = g_hash_table_lookup (context->type_info, (gpointer) type);
  if (info == NULL)
    {
      info = g_new0 (TypeInfo, 1);
      info->type = type;
      g_hash_table_insert (context->type_info, (gpointer) type, info);
    }
  return info;
}

static gboolean
find_best_child (GskXmlNode *node,
                 GskXmlNode **out,
                 GError     **error)
{
  guint i;
  if (node->type == GSK_XML_NODE_TYPE_TEXT)
    {
      g_set_error (error, GSK_G_ERROR_DOMAIN, GSK_ERROR_BAD_FORMAT,
                   "expected xml element, got text node");
      return FALSE;
    }
  g_assert (node->type == GSK_XML_NODE_TYPE_ELEMENT);
  if (node->v_element.n_children == 0)
    {
      *out = NULL;
      return TRUE;
    }
  *out = NULL;
  for (i = 0; i < node->v_element.n_children; i++)
    {
      if (node->v_element.children[i]->type == GSK_XML_NODE_TYPE_ELEMENT)
        {
          if (*out)
            {
              g_set_error (error, GSK_G_ERROR_DOMAIN, GSK_ERROR_BAD_FORMAT,
                           "two element nodes found: cannot tell which to use");
              return FALSE;
            }
          *out = node->v_element.children[i];
        }
    }

  /* use lone text node if nothing else is available. */
  if (*out == NULL)
    {
      g_return_val_if_fail (node->v_element.n_children == 1, FALSE);
      *out = node->v_element.children[0];
    }

  return TRUE;
}

void           gsk_xml_context_register_nickname (GskXmlContext *context,
                                                  GType          base_type,
						  const char    *nickname,
                                                  GType          type)
{
  TypeInfo *type_info = force_type_info (context, base_type);
  GskXmlString *nick = gsk_xml_string_new (nickname);
  if (type_info->nickname_to_type == NULL)
    type_info->nickname_to_type = g_hash_table_new_full (NULL, NULL,
                                                         (GDestroyNotify) gsk_xml_string_unref, NULL);
  g_return_if_fail (g_hash_table_lookup (type_info->nickname_to_type, nick) == NULL);
  g_hash_table_insert (type_info->nickname_to_type, nick, (gpointer) type);
}


void           gsk_xml_context_register_parser   (GskXmlContext         *context,
                                                  GType                  type,
						  GskXmlContextParserFunc func,
						  GskXmlContextToXmlFunc  to_xml,
						  gpointer               data,
						  GDestroyNotify         destroy)
{
  TypeInfo *type_info = force_type_info (context, type);
  g_return_if_fail (func != NULL);
  g_return_if_fail (to_xml != NULL);
  g_return_if_fail (type_info->func == NULL);

  type_info->func = func;
  type_info->to_xml = to_xml;
  type_info->data = data;
  type_info->destroy = destroy;
}


GskXmlNode    *gsk_xml_context_serialize_object  (GskXmlContext         *context,
                                                  gpointer               object,
                                                  GError               **error)
{
  GValue value;
  GskXmlNode *rv;
  memset (&value, 0, sizeof (value));
  g_value_init (&value, G_TYPE_OBJECT);
  g_value_set_object (&value, object);
  rv = gsk_xml_context_serialize_value (context, &value, error);
  g_value_unset (&value);
  return rv;
}

GskXmlNode    *gsk_xml_context_serialize_value   (GskXmlContext         *context,
                                                  GValue                *value,
                                                  GError               **error)
{
  GType type;
  for (type = G_VALUE_TYPE (value); type != 0; type = g_type_parent (type))
    {
      TypeInfo *type_info = try_type_info (context, type);
      if (type_info != NULL && type_info->to_xml != NULL)
        {
          GError *e = NULL;
          GskXmlNode *node = type_info->to_xml (context, value, type_info->data, &e);

          /* success */
          if (node != NULL)
            return node;


          /* real error */
          if (e)
            {
              g_propagate_error (error, e);
              return NULL;
            }

          /* continue trying */
        }
    }
  g_set_error (error, GSK_G_ERROR_DOMAIN, GSK_ERROR_BAD_FORMAT,
               "no working serializer registered for type %s", G_VALUE_TYPE_NAME (value));
  return NULL;
}

gboolean       gsk_xml_context_deserialize_value (GskXmlContext         *context,
                                                  GskXmlNode            *node,
                                                  GValue                *out,
						  GError               **error)
{
  GType type;
  for (type = G_VALUE_TYPE (out); type != 0; type = g_type_parent (type))
    {
      TypeInfo *type_info = try_type_info (context, type);
      if (type_info != NULL && type_info->func != NULL)
        {
          GError *e = NULL;
          if (type_info->func (context, node, out, type_info->data, &e))
            return TRUE;

          /* real error */
          if (e)
            {
              g_propagate_error (error, e);
              return FALSE;
            }

          /* continue trying */
        }
    }
  g_set_error (error, GSK_G_ERROR_DOMAIN, GSK_ERROR_BAD_FORMAT,
               "no working serializer registered for type %s",
               G_VALUE_TYPE_NAME (out));
  return FALSE;
}

gpointer       gsk_xml_context_deserialize_object(GskXmlContext         *context,
                                                  GType                  base_type,
                                                  GskXmlNode            *node,
						  GError               **error)
{
  GValue value;
  gpointer rv;
  g_return_val_if_fail (g_type_is_a (base_type, G_TYPE_OBJECT), NULL);
  memset (&value, 0, sizeof (value));
  g_value_init (&value, base_type);
  if (!gsk_xml_context_deserialize_value (context, node, &value, error))
    {
      g_value_unset (&value);
      return NULL;
    }
  rv = g_value_dup_object (&value);
  if (rv == NULL)
    g_set_error (error, GSK_G_ERROR_DOMAIN, GSK_ERROR_BAD_FORMAT,
                 "deserialization indicated success but returned a NULL object");
  return rv;
}


#define DECLARE_PARSER(lc_type)                 \
static gboolean                                 \
parser__##lc_type (GskXmlContext *context,      \
                   GskXmlNode    *node,         \
                   GValue        *value,        \
                   gpointer       data,         \
                   GError       **error)
#define DECLARE_TO_XML(lc_type)                 \
static GskXmlNode *                             \
to_xml__##lc_type (GskXmlContext *context,      \
                   const GValue  *value,        \
                   gpointer       data,         \
                   GError       **error)

#define PARSER_ENSURE_IS_TEXT(typename)                                 \
  if (node == NULL                                                      \
   || node->type != GSK_XML_NODE_TYPE_TEXT)                             \
    {                                                                   \
      g_set_error (error, GSK_G_ERROR_DOMAIN, GSK_ERROR_BAD_FORMAT,     \
                   "error parsing '%s': expected text node", typename); \
      return FALSE;                                                     \
    }

/* --- Builtin Types --- */
/* builtin type: char */
DECLARE_PARSER (char)
{
  if (node == NULL)
    {
      g_value_set_char (value, 0);
      return TRUE;
    }
  PARSER_ENSURE_IS_TEXT ("char");
  g_value_set_char (value, * (char *) node->v_text.content);
  return TRUE;
}

DECLARE_TO_XML (char)
{
  char c[2];
  c[0] = g_value_get_char (value);
  c[1] = 0;
  if ((guchar) c[0] >= 0x80)
    {
      g_set_error (error, GSK_G_ERROR_DOMAIN, GSK_ERROR_BAD_FORMAT,
                   "cannot serialize char with high-bit set");
      return NULL;
    }
  return gsk_xml_node_new_text_c (c);
}

/* builtin type: uchar */
DECLARE_PARSER (uchar)
{
  glong v;
  char *end;
  PARSER_ENSURE_IS_TEXT ("uchar");
  v = strtol ((char *) node->v_text.content, &end, 0);
  if (end == (char*)node->v_text.content || *end != '\0')
    {
      g_set_error (error, GSK_G_ERROR_DOMAIN, GSK_ERROR_BAD_FORMAT,
                   "error parsing number for uchar");
      return FALSE;
    }
  if (v < 0 || v > 255)
    {
      g_set_error (error, GSK_G_ERROR_DOMAIN, GSK_ERROR_BAD_FORMAT,
                   "data out-of-range for uchar");
      return FALSE;
    }
  g_value_set_uchar (value, (guchar) v);
  return TRUE;
}

DECLARE_TO_XML (uchar)
{
  char slab[128];
  g_snprintf (slab, sizeof (slab), "%u", g_value_get_uchar (value));
  return gsk_xml_node_new_text_c (slab);
}

/* builtin type: boolean */
DECLARE_PARSER (boolean)
{
  const char *str;
  PARSER_ENSURE_IS_TEXT ("boolean");
  str = (char*)(node->v_text.content);
  if (str[0] == 0)
    {
      g_set_error (error, GSK_G_ERROR_DOMAIN, GSK_ERROR_BAD_FORMAT,
                   "cannot parse boolean from empty string");
      return FALSE;
    }
  switch (str[0])
    {
    case 'y':
    case 'Y':
    case 't':
    case 'T':
    case '1':
      g_value_set_boolean (value, TRUE);
      return TRUE;
    case 'n':
    case 'N':
    case 'f':
    case 'F':
    case '0':
      g_value_set_boolean (value, FALSE);
      return TRUE;
    default:
      g_set_error (error, GSK_G_ERROR_DOMAIN, GSK_ERROR_BAD_FORMAT,
                   "cannot parse boolean from the string '%s'", str);
      return FALSE;
    }
}
DECLARE_TO_XML (boolean)
{
  static GskXmlNode *true_node = NULL;
  static GskXmlNode *false_node = NULL;
  if (g_value_get_boolean (value))
    {
      if (true_node == NULL)
        true_node = gsk_xml_node_new_text_c ("1");
      return gsk_xml_node_ref (true_node);
    }
  else
    {
      if (false_node == NULL)
        false_node = gsk_xml_node_new_text_c ("0");
      return gsk_xml_node_ref (false_node);
    }
}

/* builtin type: int */
DECLARE_PARSER (int)
{
  glong v;
  char *end;
  PARSER_ENSURE_IS_TEXT ("int");
  v = strtol ((char *) node->v_text.content, &end, 0);
  if (end == (char*)node->v_text.content || *end != '\0')
    {
      g_set_error (error, GSK_G_ERROR_DOMAIN, GSK_ERROR_BAD_FORMAT,
                   "error parsing number for int");
      return FALSE;
    }
  if (sizeof (long) != sizeof (int))
    {
      if (v < (glong) G_MININT || v > (glong) G_MAXINT)
        {
          g_set_error (error, GSK_G_ERROR_DOMAIN, GSK_ERROR_BAD_FORMAT,
                       "data out-of-range for int");
          return FALSE;
        }
    }
  g_value_set_int (value, (gint) v);
  return TRUE;
}
DECLARE_TO_XML (int)
{
  char buf[256];
  g_snprintf (buf, sizeof (buf), "%d", g_value_get_int (value));
  return gsk_xml_node_new_text_c (buf);
}

/* builtin type: uint */
DECLARE_PARSER (uint)
{
  gulong v;
  char *end;
  PARSER_ENSURE_IS_TEXT ("uint");
  v = strtoul ((char *) node->v_text.content, &end, 0);
  if (end == (char*)node->v_text.content || *end != '\0')
    {
      g_set_error (error, GSK_G_ERROR_DOMAIN, GSK_ERROR_BAD_FORMAT,
                   "error parsing number for uint");
      return FALSE;
    }
  if (sizeof (long) != sizeof (int))
    {
      if (v > (gulong) G_MAXUINT)
        {
          g_set_error (error, GSK_G_ERROR_DOMAIN, GSK_ERROR_BAD_FORMAT,
                       "data out-of-range for uint");
          return FALSE;
        }
    }
  g_value_set_uint (value, (guint) v);
  return TRUE;
}
DECLARE_TO_XML (uint)
{
  char buf[256];
  g_snprintf (buf, sizeof (buf), "%u", g_value_get_uint (value));
  return gsk_xml_node_new_text_c (buf);
}

/* builtin type: long */
DECLARE_PARSER (long)
{
  glong v;
  char *end;
  PARSER_ENSURE_IS_TEXT ("long");
  v = strtol ((char *) node->v_text.content, &end, 0);
  if (end == (char*)node->v_text.content || *end != '\0')
    {
      g_set_error (error, GSK_G_ERROR_DOMAIN, GSK_ERROR_BAD_FORMAT,
                   "error parsing number for long");
      return FALSE;
    }
  g_value_set_long (value, v);
  return TRUE;
}
DECLARE_TO_XML (long)
{
  char buf[256];
  g_snprintf (buf, sizeof (buf), "%ld", g_value_get_long (value));
  return gsk_xml_node_new_text_c (buf);
}

/* builtin type: ulong */
DECLARE_PARSER (ulong)
{
  gulong v;
  char *end;
  PARSER_ENSURE_IS_TEXT ("ulong");
  v = strtoul ((char *) node->v_text.content, &end, 0);
  if (end == (char*)node->v_text.content || *end != '\0')
    {
      g_set_error (error, GSK_G_ERROR_DOMAIN, GSK_ERROR_BAD_FORMAT,
                   "error parsing number for ulong");
      return FALSE;
    }
  g_value_set_ulong (value, v);
  return TRUE;
}
DECLARE_TO_XML (ulong)
{
  char buf[256];
  g_snprintf (buf, sizeof (buf), "%lu", g_value_get_ulong (value));
  return gsk_xml_node_new_text_c (buf);
}

/* builtin type: int64 */
DECLARE_PARSER (int64)
{
  gint64 v;
  char *end;
  PARSER_ENSURE_IS_TEXT ("int64");
#if HAVE_STRTOLL
  v = strtoll ((char *) node->v_text.content, &end, 0);
#elif HAVE_STRTOQ
  v = strtoq ((char *) node->v_text.content, &end, 0);
#else
#error "no strtoll or strtoq"
#endif
  if (end == (char*)node->v_text.content || *end != '\0')
    {
      g_set_error (error, GSK_G_ERROR_DOMAIN, GSK_ERROR_BAD_FORMAT,
                   "error parsing number for int64");
      return FALSE;
    }
  g_value_set_int64 (value, v);
  return TRUE;
}
DECLARE_TO_XML (int64)
{
  char buf[256];
  g_snprintf (buf, sizeof (buf), "%"G_GINT64_FORMAT, g_value_get_int64 (value));
  return gsk_xml_node_new_text_c (buf);
}

/* builtin type: uint64 */
DECLARE_PARSER (uint64)
{
  guint64 v;
  char *end;
  PARSER_ENSURE_IS_TEXT ("uint64");
  v = g_ascii_strtoull ((char *) node->v_text.content, &end, 0);
  if (end == (char*)node->v_text.content || *end != '\0')
    {
      g_set_error (error, GSK_G_ERROR_DOMAIN, GSK_ERROR_BAD_FORMAT,
                   "error parsing number for uint64");
      return FALSE;
    }
  g_value_set_uint64 (value, v);
  return TRUE;
}
DECLARE_TO_XML (uint64)
{
  char buf[256];
  g_snprintf (buf, sizeof (buf), "%"G_GUINT64_FORMAT, g_value_get_uint64 (value));
  return gsk_xml_node_new_text_c (buf);
}

/* builtin type: enum */
DECLARE_PARSER (enum)
{
  GEnumClass *class;
  GEnumValue *enum_value;
  GskXmlString *content;
  PARSER_ENSURE_IS_TEXT ("enum");

  content = node->v_text.content;

  class = g_type_class_ref (G_VALUE_TYPE (value));
  g_return_val_if_fail (G_IS_ENUM_CLASS (class), FALSE);
  enum_value = g_enum_get_value_by_name (class, (char*) content);
  if (enum_value == NULL)
    enum_value = g_enum_get_value_by_nick (class, (char*) content);

  if (enum_value == NULL)
    {
      g_set_error (error, GSK_G_ERROR_DOMAIN, GSK_ERROR_BAD_FORMAT,
                   "no value '%s' for enum %s",
                   (char*) content, G_VALUE_TYPE_NAME (value));
      g_type_class_unref (class);
      return FALSE;
    }
  g_value_set_enum (value, enum_value->value);
  g_type_class_unref (class);
  return TRUE;
}
DECLARE_TO_XML (enum)
{
  GEnumClass *class;
  GEnumValue *enum_value;
  GskXmlNode *rv;
  class = g_type_class_ref (G_VALUE_TYPE (value));
  g_return_val_if_fail (G_IS_ENUM_CLASS (class), FALSE);
  enum_value = g_enum_get_value (class, g_value_get_enum (value));
  if (enum_value == NULL)
    {
      g_set_error (error, GSK_G_ERROR_DOMAIN, GSK_ERROR_BAD_FORMAT,
                   "invalid value %d for enum %s",
                   g_value_get_enum (value),
                   G_VALUE_TYPE_NAME (value));
      g_type_class_unref (class);
      return NULL;
    }
  rv = gsk_xml_node_new_text_c (enum_value->value_name);
  g_type_class_unref (class);
  return rv;
}

/* builtin type: flags */
static gboolean
add_flag (GType        type,
          GFlagsClass *class,
          const char  *start,
          guint        len,
          guint       *flags_inout,
          GError     **error)
{
  char *str = g_alloca (len + 1);
  memcpy (str, start, len);
  GFlagsValue *value;
  str[len] = '\0';

  value = g_flags_get_value_by_name (class, str);
  if (value == NULL)
    value = g_flags_get_value_by_nick (class, str);
  if (value == NULL)
    {
      g_set_error (error, GSK_G_ERROR_DOMAIN, GSK_ERROR_BAD_FORMAT,
                   "no value named %s in flag %s",
                   str, g_type_name (type));
      return FALSE;
    }

  *flags_inout |= value->value;
  return TRUE;
}

DECLARE_PARSER (flags)
{
  guint flags = 0;
  if (node != NULL)
    {
      GType type = G_VALUE_TYPE (value);
      GFlagsClass *class = g_type_class_ref (type);
      const char *str;
      const char *start = NULL;
      PARSER_ENSURE_IS_TEXT ("flags");
      str = (char*)(node->v_text.content);
      while (*str)
        {
          gunichar u = g_utf8_get_char (str);

          if (g_unichar_isspace (u) || *str == ',' || *str == '|')
            {
              if (start != NULL)
                {
                  if (!add_flag (type, class, start, str - start, &flags, error))
                    return FALSE;
                  start = NULL;
                }
            }
          else if (start == NULL)
            start = str;

          str = g_utf8_next_char (str);
        }
      if (start != NULL)
        if (!add_flag (type,class, start, str - start, &flags, error))
          return FALSE;
    }
  g_value_set_flags (value, flags);
  return TRUE;
}

DECLARE_TO_XML (flags)
{
  GFlagsClass *class;
  GskXmlNode *rv;
  char *flag_names[65];
  guint flags = g_value_get_flags (value);
  guint n_flags = 0;
  char *text;
  class = g_type_class_ref (G_VALUE_TYPE (value));
  g_return_val_if_fail (G_IS_FLAGS_CLASS (class), FALSE);
  while (flags != 0)
    {
      GFlagsValue *flag_value = g_flags_get_first_value (class, flags);
      if (flag_value == NULL)
        {
          g_set_error (error, GSK_G_ERROR_DOMAIN, GSK_ERROR_BAD_FORMAT,
                       "invalid value %d for flags %s",
                       flags,
                       G_VALUE_TYPE_NAME (value));
          g_type_class_unref (class);
          return NULL;
        }
      flag_names[n_flags++] = flag_value->value_nick;
    }
  flag_names[n_flags] = NULL;
  text = g_strjoinv (",", flag_names);
  rv = gsk_xml_node_new_text_c (text);
  g_type_class_unref (class);
  g_free (text);
  return rv;
}

/* builtin type: float */
DECLARE_PARSER (float)
{
  char *str;
  char *end;
  double d;
  PARSER_ENSURE_IS_TEXT ("float");
  str = (char*)(node->v_text.content);
  d = g_ascii_strtod (str, &end);
  if (str == end || *end != '\0')
    {
      g_set_error (error, GSK_G_ERROR_DOMAIN, GSK_ERROR_BAD_FORMAT,
                   "error parsing float");
      return FALSE;
    }

  /* TODO: check in range for float */

  g_value_set_float (value, d);
  return TRUE;
}
DECLARE_TO_XML (float)
{
  char buf[G_ASCII_DTOSTR_BUF_SIZE];
  g_ascii_formatd (buf, sizeof (buf), "%.12g", g_value_get_float (value));
  return gsk_xml_node_new_text_c (buf);
}

/* builtin type: double */
DECLARE_PARSER (double)
{
  char *str;
  char *end;
  double d;
  PARSER_ENSURE_IS_TEXT ("double");
  str = (char*)(node->v_text.content);
  d = g_ascii_strtod (str, &end);
  if (str == end || *end != '\0')
    {
      g_set_error (error, GSK_G_ERROR_DOMAIN, GSK_ERROR_BAD_FORMAT,
                   "error parsing double");
      return FALSE;
    }
  g_value_set_double (value, d);
  return TRUE;
}
DECLARE_TO_XML (double)
{
  char buf[G_ASCII_DTOSTR_BUF_SIZE];
  g_ascii_dtostr (buf, sizeof (buf), g_value_get_double (value));
  return gsk_xml_node_new_text_c (buf);
}

/* builtin type: string */
DECLARE_PARSER (string)
{
  if (node == NULL)
    {
      g_value_set_string (value, "");
      return TRUE;
    }
  PARSER_ENSURE_IS_TEXT ("string");
  g_value_set_string (value, (char*)(node->v_text.content));
  return TRUE;
}
DECLARE_TO_XML (string)
{
  const char *str = g_value_get_string (value);
  if (str == NULL)
    str = "";           /* XXX: what should we do here? */
  return gsk_xml_node_new_text_c (str);
}

/* builtin type: object */
DECLARE_PARSER (object)
{
  GType type;
  GObjectClass *class;
  GObject *rv = NULL;

  GArray *params;
  GPtrArray *extra_nodes;

  guint i;

  if (node == NULL)
    {
      g_value_set_object (value, NULL);
      return TRUE;
    }

  params = g_array_new (FALSE, FALSE, sizeof (GParameter));
  extra_nodes = g_ptr_array_new ();
  if (node->type != GSK_XML_NODE_TYPE_ELEMENT)
    {
      g_set_error (error, GSK_G_ERROR_DOMAIN, GSK_ERROR_BAD_FORMAT,
                   "only xml nodes are allowed for generic object parsing");
      return FALSE;
    }
  type = g_type_from_name ((char*) node->v_element.name);
  if (type == 0)
    {
      /* try nicknames */
      type = G_VALUE_TYPE (value);
      while (type != 0)
        {
          TypeInfo *type_info = try_type_info (context, type);
          if (type_info != NULL && type_info->nickname_to_type != NULL)
            {
              GType t = (GType) g_hash_table_lookup (type_info->nickname_to_type, node->v_element.name);
              if (t != 0)
                {
                  type = t;
                  break;
                }
            }
        }
      if (type == 0)
        {
          g_set_error (error, GSK_G_ERROR_DOMAIN, GSK_ERROR_BAD_FORMAT,
                       "no object type found for <%s>", (char*) node->v_element.name);
          return FALSE;
        }
    }

  /* XXX: real error-handling required here? (unsure) */
  g_return_val_if_fail (g_type_is_a (type, G_VALUE_TYPE (value)), FALSE);

  class = g_type_class_ref (type);

  for (i = 0; i < node->v_element.n_children; i++)
    if (node->v_element.children[i]->type == GSK_XML_NODE_TYPE_ELEMENT)
      {
        GskXmlNode *child = node->v_element.children[i];
        GParamSpec *pspec = g_object_class_find_property (class, (char*)(child->v_element.name));
        if (pspec != NULL)
          {
            GParameter p;
            GskXmlNode *value_child;
            p.name = pspec->name;
            memset (&p.value, 0, sizeof (GValue));
            g_value_init (&p.value, G_PARAM_SPEC_VALUE_TYPE (pspec));

            /* find the best child node of 'child' */
            if (!find_best_child (child, &value_child, error)
             || !gsk_xml_context_deserialize_value (context, value_child, &p.value, error))
              {
                gsk_g_error_add_prefix (error, "parsing %s::%s", g_type_name (type), pspec->name);
                goto cleanup_and_return;
              }
            g_array_append_val (params, p);
          }
        else
          {
            g_ptr_array_add (extra_nodes, child);
          }
      }

  /* Construct object */
  rv = g_object_newv (type, params->len, (GParameter *)(params->data));

  /* Handle 'extra' nodes */
  if (extra_nodes->len > 0)
    {
      GPtrArray *hashtables;
      TypeTagInfo *fallback = NULL;
      guint i;
      GType t;

      /* make a straightforward array of hashtables,
         and the best fallback */
      hashtables = g_ptr_array_new ();
      for (t = type; t != 0; t = g_type_parent (t))
        {
          TypeInfo *type_info = try_type_info (context, t);
          if (type_info->tag_to_info)
            g_ptr_array_add (hashtables, type_info->tag_to_info);
          if (fallback == NULL && type_info->tag_to_info)
            fallback = g_hash_table_lookup (type_info->tag_to_info, NULL);
        }

      for (i = 0; i < extra_nodes->len; i++)
        {
          GskXmlNode *n = extra_nodes->pdata[i];
          TypeTagInfo *handler = NULL;
          guint h;
          for (h = 0; h < hashtables->len && handler == NULL; h++)
            handler = g_hash_table_lookup (hashtables->pdata[h], n->v_element.name);
          if (handler == NULL)
            handler = fallback;
          if (!handler)
            {
              g_set_error (error, GSK_G_ERROR_DOMAIN, GSK_ERROR_BAD_FORMAT,
                           "cannot parse %s for %s",
                           (char*)(n->v_element.name), g_type_name (type));
              g_object_unref (rv);
              rv = NULL;

              goto cleanup_and_return;
            }
          if (!handler->handler (context, rv, n, handler->data, error))
            {
              gsk_g_error_add_prefix (error, "parsing %s", g_type_name (type));
              g_object_unref (rv);
              rv = NULL;
              goto cleanup_and_return;
            }
        }
      g_ptr_array_free (hashtables, TRUE);
    }

  /* Validate the object, if possible */
  {
    TypeInfo *type_info;
    GType t;
    for (t = G_OBJECT_TYPE (rv); t != 0; t = g_type_parent (t))
      {
        type_info = try_type_info (context, G_OBJECT_TYPE (rv));
        if (type_info && type_info->validator)
          if (!type_info->validator (context, rv, type_info->validator_data, error))
            {
              g_object_unref (rv);
              rv = NULL;
              goto cleanup_and_return;
            }
      }
  }

cleanup_and_return:
  for (i = 0; i < params->len; i++)
    g_value_unset (&g_array_index (params, GParameter, i).value);
  g_array_free (params, TRUE);
  g_ptr_array_free (extra_nodes, TRUE);
  g_type_class_unref (class);
  if (rv)
    {
      g_value_take_object (value, rv);
      return TRUE;
    }
  else
    return FALSE;
}

DECLARE_TO_XML (object)
{
  GskXmlBuilder *builder = gsk_xml_builder_new (0);
  GObject *object = g_value_get_object (value);
  GParamSpec **pspecs;
  guint n_pspecs;
  guint i;
  GType t;
  GskXmlNode *rv;
  if (object == NULL)
    {
      g_set_error (error, GSK_G_ERROR_DOMAIN, GSK_ERROR_BAD_FORMAT,
                   "cannot serialize a NULL object value");
      return NULL;
    }
  gsk_xml_builder_start_c (builder, G_OBJECT_TYPE_NAME (object), 0, NULL);

  /* properties */
  pspecs = g_object_class_list_properties (G_OBJECT_GET_CLASS (object), &n_pspecs);
  for (i = 0; i < n_pspecs; i++)
    {
      GValue value;
      GParamSpec *p = pspecs[i];
      GskXmlNode *child;
      memset (&value, 0, sizeof (GValue));
      g_value_init (&value, G_PARAM_SPEC_VALUE_TYPE (p));
      g_object_get_property (object, p->name, &value);
      child = gsk_xml_context_serialize_value (context, &value, error);
      gsk_xml_builder_start_c (builder, p->name, 0, NULL);
      if (child)
        {
          gsk_xml_builder_add_node (builder, child);
          gsk_xml_node_unref (child);
        }
      gsk_xml_builder_end (builder, NULL);
      g_value_unset (&value);
    }
  g_free (pspecs);

  /* misc serialization */
  for (t = G_OBJECT_TYPE (object); t != 0; t = g_type_parent (t))
    {
      TypeInfo *type_info = try_type_info (context, t);
      if (type_info && type_info->to_xml)
        {
          if (!type_info->add_misc_handler (context, object, builder, type_info->add_misc_data, error))
            {
              gsk_xml_builder_free (builder);
              return NULL;
            }
        }
    }

  rv = gsk_xml_builder_end (builder, NULL);
  gsk_xml_node_ref (rv);
  gsk_xml_builder_free (builder);
  return rv;
}

/* builtin type: GskSocketAddress */
DECLARE_PARSER (socket_address)
{
  if (node == NULL)
    {
      g_value_set_object (value, NULL);
      return TRUE;
    }
  if (node->type == GSK_XML_NODE_TYPE_ELEMENT)
    {
      g_set_error (error, GSK_G_ERROR_DOMAIN, GSK_ERROR_BAD_FORMAT,
                   "did not expect element node");
      return FALSE;
    }
  else
    {
      const char *text = (char *) (node->v_text.content);
      gint ip_int[4], ip_port;
      if (memcmp (text, "unix:", 5) == 0)
        {
          GskSocketAddress *addr = gsk_socket_address_local_new (text + 5);
          g_value_set_object (value, G_OBJECT (addr));
          g_object_unref (addr);
          return TRUE;
        }
      else if (sscanf (text, "%d.%d.%d.%d:%d",
                       &ip_int[0],
                       &ip_int[1],
                       &ip_int[2],
                       &ip_int[3],
                       &ip_port) == 5)
        {
          GskSocketAddress *addr;
          guint8 ip_addr[4] = { ip_int[0], ip_int[1], ip_int[2], ip_int[3] };
          addr = gsk_socket_address_ipv4_new (ip_addr, ip_port);
          g_value_set_object (value, G_OBJECT (addr));
          g_object_unref (addr);
          return TRUE;
        }
      else
        {
          /* TODO: other forms.  generally: what about symbolic hostnames? */
          g_set_error (error, GSK_G_ERROR_DOMAIN, GSK_ERROR_BAD_FORMAT,
                       "could not parse socketaddress from %s", text);
          return FALSE;
        }
    }
}
DECLARE_TO_XML (socket_address)
{
  GskSocketAddress *addr = g_value_get_object (value);
  char *addr_str;
  GskXmlNode *rv;
  if (addr == NULL)
    return gsk_xml_node_new_text (gsk_xml_string__);
  addr_str = gsk_socket_address_to_string (addr);
  rv = gsk_xml_node_new_text_c (addr_str);
  g_free (addr_str);
  return rv;
}

GskXmlContext *gsk_xml_context_new               (void)
{
  GskXmlContext *context = g_new (GskXmlContext, 1);
  context->type_info = g_hash_table_new (NULL, NULL);
#define REGISTER(gtype, lc_suffix)                                   \
  gsk_xml_context_register_parser (context, gtype,                   \
                                   parser__##lc_suffix,              \
                                   to_xml__##lc_suffix,              \
                                   NULL, NULL)
  REGISTER (G_TYPE_CHAR, char);
  REGISTER (G_TYPE_UCHAR, uchar);
  REGISTER (G_TYPE_BOOLEAN, boolean);
  REGISTER (G_TYPE_INT, int);
  REGISTER (G_TYPE_UINT, uint);
  REGISTER (G_TYPE_LONG, long);
  REGISTER (G_TYPE_ULONG, ulong);
  REGISTER (G_TYPE_INT64, int64);
  REGISTER (G_TYPE_UINT64, uint64);
  REGISTER (G_TYPE_ENUM, enum);
  REGISTER (G_TYPE_FLAGS, flags);
  REGISTER (G_TYPE_FLOAT, float);
  REGISTER (G_TYPE_DOUBLE, double);
  REGISTER (G_TYPE_STRING, string);
  REGISTER (G_TYPE_OBJECT, object);
  REGISTER (GSK_TYPE_SOCKET_ADDRESS, socket_address);
#undef REGISTER
  return context;
};

GskXmlContext *gsk_xml_context_global            (void)
{
  static GskXmlContext *rv = NULL;
  if (rv == NULL)
    rv = gsk_xml_context_new ();
  return rv;
}


syntax highlighted by Code2HTML, v. 0.9.1