Logo Search packages:      
Sourcecode: kazehakase version File versions

kz-xml.c

/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */

/*
 *  Copyright (C) 2003 Takuro Ashie
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2, or (at your option)
 *  any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 *
 *  $Id: kz-xml.c,v 1.24 2004/11/22 01:08:41 ikezoe Exp $
 */

#include "kz-xml.h"

#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include "gobject-utils.h"

struct _KzXMLPriv
{
      GMarkupParseContext *context;
};

static void kz_xml_class_init   (KzXMLClass *klass);
static void kz_xml_init         (KzXML      *xml);
static void kz_xml_dispose      (GObject    *object);

static void kz_xml_attr_free    (KzXMLAttr *attr);

static GObjectClass *parent_class = NULL;


KZ_OBJECT_GET_TYPE(kz_xml, "KzXML", KzXML,
               kz_xml_class_init, kz_xml_init,
               G_TYPE_OBJECT)
KZ_OBJECT_FINALIZE(kz_xml, KzXML)


static void
kz_xml_class_init (KzXMLClass *klass)
{
      GObjectClass *object_class;

      parent_class = g_type_class_peek_parent (klass);
      object_class = (GObjectClass *) klass;

      object_class->dispose  = kz_xml_dispose;
      object_class->finalize = kz_xml_finalize;
}


static void
kz_xml_init (KzXML *xml)
{
      xml->file          = NULL;
      xml->dtd           = NULL;
      xml->encoding      = g_strdup("UTF-8");
      xml->root          = kz_xml_node_new(xml, KZ_XML_NODE_DOC_ROOT);
      xml->priv          = g_new0(KzXMLPriv, 1);
      xml->priv->context = NULL;
}


static void
kz_xml_dispose (GObject *object)
{
      KzXML *xml = KZ_XML(object);

      g_free(xml->file);
      xml->file = NULL;

      g_free(xml->dtd);
      xml->dtd = NULL;

      g_free(xml->encoding);
      xml->encoding = NULL;

      if (xml->root)
            kz_xml_node_unref(xml->root);
      xml->root = NULL;

      if (G_OBJECT_CLASS (parent_class)->dispose)
            G_OBJECT_CLASS (parent_class)->dispose(object);
}


KzXML *
kz_xml_new (void)
{
      KzXML *xml = g_object_new(KZ_TYPE_XML, NULL);
      return xml;
}


typedef struct _ParseContext
{
      KzXML     *xml;
      KzXMLNode *current;
      gint       nest_level;
} ParseContext;


static void
start_element_handler (GMarkupParseContext *context,
                   const gchar         *element_name,
                   const gchar        **attr_names,
                   const gchar        **attr_values,
                   gpointer             user_data,
                   GError             **error)
{
      ParseContext *ctx = user_data;
      KzXMLNode *node;
      KzXMLElement *element;
      gint i;

      node = kz_xml_element_node_new(element_name);
      kz_xml_node_append_child(ctx->current, node);
      element = node->content;
      for (i = 0; attr_names[i]; i++)
            kz_xml_node_set_attr(node, attr_names[i], attr_values[i]);
      ctx->current = node;
      ctx->nest_level++;
}

static void
end_element_handler (GMarkupParseContext *context,
                 const gchar         *element_name,
                 gpointer             user_data,
                 GError             **error)
{
      ParseContext *ctx = user_data;

      ctx->current = ctx->current->parent;
      ctx->nest_level--;
}


static void
text_handler (GMarkupParseContext *context,
            const gchar         *text,
            gsize                text_len,  
            gpointer             user_data,
            GError             **error)
{
      ParseContext *ctx = user_data;
      KzXMLNode *node;

      node = kz_xml_node_new(ctx->xml, KZ_XML_NODE_TEXT);
      node->content = g_strndup(text, text_len);
      kz_xml_node_append_child(ctx->current, node);
}


static void
passthrough_handler (GMarkupParseContext *context,
                 const gchar         *text,
                 gsize                text_len,  
                 gpointer             user_data,
                 GError             **error)
{
      ParseContext *ctx = user_data;
      KzXMLNode *node;
      KzXMLNodeType type = KZ_XML_NODE_OTHER;

      if (g_str_has_prefix(text, "<?xml")
          && ctx->current == ctx->xml->root
          && !ctx->current->children)
      {
            type = KZ_XML_NODE_XML_DECL;
      }
      else if (g_str_has_prefix(text, "<?"))
      {
            type = KZ_XML_NODE_PI;
      }
      else if (g_str_has_prefix(text, "<!--"))
      {
            type = KZ_XML_NODE_COMMENT;
      }
      else if (g_str_has_prefix(text, "<!DOCTYPE"))
      {
            type = KZ_XML_NODE_DOCTYPE;
      }
      else if (g_str_has_prefix(text, "<![CDATA["))
      {
            type = KZ_XML_NODE_CDATA;
      }
      else
      {
            type = KZ_XML_NODE_TEXT;
      }

      node = kz_xml_node_new(ctx->xml, type);
      kz_xml_node_append_child(ctx->current, node);
      node->content = g_strndup(text, text_len);

      if (type == KZ_XML_NODE_XML_DECL)
      {
            node = kz_xml_node_new(ctx->xml, KZ_XML_NODE_TEXT);
            kz_xml_node_append_child(ctx->current, node);
            node->content = g_strdup("\n");
      }
}


static void
error_handler (GMarkupParseContext *context,
             GError              *error,
             gpointer             user_data)
{
      /* ParseContext *ctx = user_data; */
}


static GMarkupParser parser = {
      start_element_handler,
      end_element_handler,
      text_handler,
      passthrough_handler,
      error_handler,
};


void
kz_xml_clear_content(KzXML *xml)
{
      g_free(xml->dtd);
      xml->dtd = NULL;
      g_free(xml->encoding);
      xml->encoding = NULL;
      g_list_foreach(xml->root->children,
                   (GFunc) kz_xml_node_unref, NULL);
      g_list_free(xml->root->children);
      xml->root->children = NULL;
}


gboolean
kz_xml_load_xml (KzXML *xml, const gchar *buffer, guint length)
{
      GMarkupParseContext *context;
      ParseContext *ctx;
      gboolean ret = FALSE;
      GError *error = NULL; 

      g_return_val_if_fail(KZ_IS_XML(xml), FALSE);
      if (buffer == NULL) return FALSE;

      context = xml->priv->context;
      if (!context)
      {
            kz_xml_clear_content(xml);

            ctx = g_new0(ParseContext, 1);
            ctx->xml        = xml;
            ctx->current    = xml->root;
            ctx->nest_level = 0;
            context = g_markup_parse_context_new
                        (&parser, 0,
                         ctx, (GDestroyNotify) g_free);
            xml->priv->context = context;
      }

      if (g_markup_parse_context_parse(context, buffer, length, &error))
      {
            if (g_markup_parse_context_end_parse(context, NULL))
                  ret = TRUE;
      }
      else
      {
            g_warning("XML parse error!: %s", error->message);
            g_error_free(error);
      }
      g_markup_parse_context_free (context);
      xml->priv->context = NULL;

      return ret;
}


gboolean
kz_xml_load (KzXML *xml, const gchar *filename)
{
      gchar *buffer = NULL;
      gsize length;
      gboolean ret;
      GError *error = NULL;

      ret = g_file_get_contents (filename, &buffer, &length, &error);

      if (error)
      {
            g_warning(error->message);
            g_error_free(error);
      }

      if (!ret) return FALSE;

      ret = kz_xml_load_xml(xml, buffer, length);
      g_free(buffer);

      return ret;
}


gboolean
kz_xml_save (KzXML *xml, const gchar *filename)
{
      FILE *fp;
      const gchar *file = filename ? filename : xml->file;
      gchar *str;

      g_return_val_if_fail(file && *file, FALSE);

      fp = fopen(file, "w");
      if (!fp) return FALSE;

      str = kz_xml_node_to_xml(xml->root);

      if (!str || !*str)
      {
            g_free(str);
            fclose(fp);
            return FALSE;
      }

      fwrite(str, strlen(str), 1, fp);
      fclose(fp);
      g_free(str);

      return TRUE;
}


KzXMLNode *
kz_xml_node_new(KzXML *xml, KzXMLNodeType type)
{
      KzXMLNode *node;

      g_return_val_if_fail(type > KZ_XML_NODE_INVALID &&
                       type < KZ_XML_N_NODE_TYPES,
                       NULL);
      node = g_new0(KzXMLNode, 1);
      node->type        = type;
      node->content     = NULL;
      node->parent      = NULL;
      node->children    = NULL;
      node->ref_count   = 1;

      switch (type) {
      case KZ_XML_NODE_DOC_ROOT:
      {
            KzXMLNode *decl, *space;

            g_return_val_if_fail(KZ_IS_XML(xml), node);

            node->content = xml;

            decl = kz_xml_node_new(xml, KZ_XML_NODE_XML_DECL);
            if (xml->encoding && *xml->encoding)
                  decl->content = g_strdup_printf("<?xml version=\"1.0\""
                                          " encoding=\"%s\"?>",
                                          xml->encoding);
            else
                  decl->content = g_strdup("<?xml version=\"1.0\"?>");
            kz_xml_node_append_child(node, decl);

            space = kz_xml_text_node_new("\n");
            kz_xml_node_append_child(node, space);

            break;
      }
      case KZ_XML_NODE_ELEMENT:
      {
            KzXMLElement *element = g_new0(KzXMLElement, 1);
            element->name  = NULL;
            element->attrs = NULL;
            node->content = element;
            break;
      }
      case KZ_XML_NODE_XML_DECL:
            break;
      case KZ_XML_NODE_TEXT:
      case KZ_XML_NODE_COMMENT:
      case KZ_XML_NODE_PI:
      case KZ_XML_NODE_CDATA:
      case KZ_XML_NODE_DOCTYPE:
      case KZ_XML_NODE_OTHER:
            break;
      default:
            g_return_val_if_reached(node);
            break;
      }

      return node;
}


KzXMLNode *
kz_xml_node_ref(KzXMLNode *node)
{
      g_return_val_if_fail(node, NULL);

      node->ref_count++;

      return node;
}


void
kz_xml_node_unref(KzXMLNode *node)
{
      g_return_if_fail(node);

      node->ref_count--;

      if (node->ref_count != 0) return;

      g_list_foreach(node->children,
                   (GFunc)kz_xml_node_unref, NULL);
      g_list_free(node->children);
      node->children = NULL;

      if (node->type == KZ_XML_NODE_ELEMENT)
      {
            KzXMLElement *element = node->content;

            g_free(element->name);
            g_list_foreach(element->attrs, (GFunc)kz_xml_attr_free, NULL);
            g_list_free(element->attrs);
            g_free(element);
      }
      else if (node->type != KZ_XML_NODE_DOC_ROOT)
      {
            g_free(node->content);
      }

      g_free(node);
}


static void
kz_xml_attr_free (KzXMLAttr *attr)
{
      g_free(attr->name);
      g_free(attr->value);
      g_free(attr);
}


static void
kz_xml_node_append_xml_string (KzXMLNode *node, GString *gstr)
{
      KzXMLElement *element = NULL;
      GList *list;

      g_return_if_fail(node && gstr);

      if (node->type == KZ_XML_NODE_ELEMENT)
      {
            element = node->content;
            g_string_append_printf(gstr, "<%s", element->name);
            for (list = element->attrs; list; list = g_list_next(list))
            {
                  KzXMLAttr *attr = list->data;
                  gchar *escaped;

                  escaped = g_markup_escape_text(attr->value, -1);
                  g_string_append_printf(gstr, " %s=\"%s\"",
                                     attr->name, escaped);
                  g_free(escaped);
            }

            if (!node->children)
                  g_string_append(gstr, "/");
            g_string_append(gstr, ">");
      }

      if (element || node->type == KZ_XML_NODE_DOC_ROOT)
      {
            for (list = node->children; list; list = g_list_next(list))
                  kz_xml_node_append_xml_string(list->data, gstr);
      }
      else if (node->type == KZ_XML_NODE_TEXT)
      {
            gchar *escaped;

            escaped = g_markup_escape_text(node->content, -1);
            if (escaped)
            {
                  g_string_append(gstr, escaped);
                  g_free(escaped);
            }
      }
      else
      {
            g_string_append(gstr, node->content);
      }

      if (element && node->children)
            g_string_append_printf(gstr, "</%s>", element->name);
}


gchar *
kz_xml_node_to_xml (KzXMLNode *node)
{
      GString *gstr;

      gstr = g_string_new("");

      kz_xml_node_append_xml_string(node, gstr);

      return g_string_free(gstr, FALSE);
}


static void
kz_xml_node_append_string (KzXMLNode *node, GString *gstr)
{
      GList *list;

      g_return_if_fail(node && gstr);

      if (node->type == KZ_XML_NODE_TEXT)
            g_string_append(gstr, node->content);

      for (list = node->children; list; list = g_list_next(list))
            kz_xml_node_append_string(list->data, gstr);
}


gchar *
kz_xml_node_to_str (KzXMLNode *node)
{
      GString *gstr;

      gstr = g_string_new("");

      kz_xml_node_append_string(node, gstr);

      return g_string_free(gstr, FALSE);
}


KzXMLNode *
kz_xml_element_node_new (const gchar *name)
{
      KzXMLNode *node;
      KzXMLElement *element;

      g_return_val_if_fail(name && *name, NULL);

      node = kz_xml_node_new(NULL, KZ_XML_NODE_ELEMENT);
      element = node->content;
      element->name = g_strdup(name);

      return node;
}


const gchar *
kz_xml_node_name (KzXMLNode *node)
{
      KzXMLElement *element;

      g_return_val_if_fail(node, NULL);

      if (node->type != KZ_XML_NODE_ELEMENT) return NULL;

      element = node->content;
      g_return_val_if_fail(element, NULL);

      return element->name;
}


gboolean
kz_xml_node_name_is (KzXMLNode *node, const gchar *name)
{
      KzXMLElement *element;

      g_return_val_if_fail(node, FALSE);
      g_return_val_if_fail(name, FALSE);

      if (node->type != KZ_XML_NODE_ELEMENT) return FALSE;

      element = node->content;
      g_return_val_if_fail(element, FALSE);
      g_return_val_if_fail(element->name, FALSE);

      if (!strcmp(element->name, name))
            return TRUE;
      else
            return FALSE;
}


const gchar *
kz_xml_node_get_attr (KzXMLNode *node, const gchar *attr_name)
{
      KzXMLElement *element;
      GList *list;

      g_return_val_if_fail(node, NULL);
      g_return_val_if_fail(node->type == KZ_XML_NODE_ELEMENT, NULL);
      g_return_val_if_fail(attr_name, NULL);

      element = node->content;
      g_return_val_if_fail(element, NULL);

      for (list = element->attrs; list; list = g_list_next(list))
      {
            KzXMLAttr *attr = list->data;

            if (!strcmp(attr_name, attr->name))
                  return attr->value;
      }

      return NULL;
}


void
kz_xml_node_set_attr (KzXMLNode *node, const gchar *name, const gchar *value)
{
      KzXMLElement *element;
      GList *list;
      KzXMLAttr *attr;
      gboolean found = FALSE;

      g_return_if_fail(node);
      g_return_if_fail(node->type == KZ_XML_NODE_ELEMENT);
      g_return_if_fail(name);
      g_return_if_fail(value);

      element = node->content;
      g_return_if_fail(element);

      for (list = element->attrs; list; list = g_list_next(list))
      {
            attr = list->data;

            if (!attr->name || strcmp(name, attr->name)) continue;

            if (!found)
            {
                  g_free(attr->value);
                  attr->value = g_strdup(value);
                  found = TRUE;
            }
            else
            {
                  g_warning("Attribute %s is duplicated!", attr->name);
                  element->attrs
                        = g_list_remove(element->attrs, attr);
                  g_free(attr->name);
                  g_free(attr->value);
                  g_free(attr);
            }
      }

      if (!found)
      {
            attr = g_new0(KzXMLAttr, 1);
            attr->name  = g_strdup(name);
            attr->value = g_strdup(value);
            element->attrs = g_list_append(element->attrs, attr);
      }
}


const GList *
kz_xml_node_get_attrs (KzXMLNode *node)
{
      KzXMLElement *element;

      g_return_val_if_fail(node, NULL);
      g_return_val_if_fail(node->type == KZ_XML_NODE_ELEMENT, NULL);

      element = node->content;
      g_return_val_if_fail(element, NULL);

      return element->attrs;
}


KzXMLNode *
kz_xml_node_next (KzXMLNode *node)
{
      GList *list;

      g_return_val_if_fail(node, NULL);

      if (!node->parent) return NULL;
      if (!node->parent->children) return NULL;

      list = g_list_find(node->parent->children, node);
      if (!list) return NULL;

      list = g_list_next(list);
      if (list)
            return list->data;
      else
            return NULL;
}


KzXMLNode *
kz_xml_node_prev (KzXMLNode *node)
{
      GList *list;

      g_return_val_if_fail(node, NULL);

      if (!node->parent) return NULL;
      if (!node->parent->children) return NULL;

      list = g_list_find(node->parent->children, node);
      if (!list) return NULL;

      list = g_list_previous(list);
      if (list)
            return list->data;
      else
            return NULL;
}


KzXMLNode *
kz_xml_node_parent (KzXMLNode *node)
{
      g_return_val_if_fail(node, NULL);

      return node->parent;
}


KzXMLNode *
kz_xml_node_first_child (KzXMLNode *node)
{
      g_return_val_if_fail(node, NULL);

      if (node->children)
            return node->children->data;
      else
            return NULL;
}


KzXMLNode *
kz_xml_node_last_child (KzXMLNode *node)
{
      GList *list;

      g_return_val_if_fail(node, NULL);

      list = g_list_last(node->children);

      if (list)
            return list->data;
      else
            return NULL;
}


KzXMLNode *
kz_xml_get_root_element (KzXML *xml)
{
      KzXMLNode *node;

      g_return_val_if_fail(KZ_IS_XML(xml), NULL);

      node = kz_xml_node_first_child(xml->root);

      for (; node; node = kz_xml_node_next(node))
      {
            if (node->type == KZ_XML_NODE_ELEMENT)
            {
                  return node;
            }
      }

      return NULL;
}


gboolean
kz_xml_node_is_element (KzXMLNode *node)
{
      g_return_val_if_fail(node, FALSE);

      if (node->type == KZ_XML_NODE_ELEMENT)
            return TRUE;
      else
            return FALSE;
}


void
kz_xml_node_append_child (KzXMLNode *node, KzXMLNode *child)
{
      kz_xml_node_insert_before(node, child, NULL);
}


void
kz_xml_node_insert_before (KzXMLNode *node,
                     KzXMLNode *child, KzXMLNode *sibling)
{
      GList *list = NULL;

      g_return_if_fail(node);
      g_return_if_fail(child);

      if (sibling)
      {
            list = g_list_find(node->children, sibling);
            g_return_if_fail(list);
      }
      node->children = g_list_insert_before(node->children, list, child);
      child->parent = node;
}


KzXMLNode *
kz_xml_node_remove_child (KzXMLNode *node, KzXMLNode *child)
{
      g_return_val_if_fail(node, NULL);
      g_return_val_if_fail(child, NULL);

      node->children = g_list_remove(node->children, child);
      child->parent = NULL;

      return child;
}


KzXMLNode *
kz_xml_node_replace_child (KzXMLNode *node,
                     KzXMLNode *new_child, KzXMLNode *old_child)
{
      g_return_val_if_fail(node, NULL);
      g_return_val_if_fail(old_child, NULL);
      g_return_val_if_fail(new_child, NULL);
      g_return_val_if_fail(g_list_find(node->children, old_child), NULL);

      kz_xml_node_insert_before(node, old_child, new_child);

      return kz_xml_node_remove_child(node, new_child);
}


KzXMLNode *
kz_xml_text_node_new (const gchar *text)
{
      KzXMLNode *node;

      node = kz_xml_node_new(NULL, KZ_XML_NODE_TEXT);
      node->content = g_strdup(text);

      return node;
}


gboolean
kz_xml_node_is_text (KzXMLNode *node)
{
      g_return_val_if_fail(node, FALSE);

      if (node->type == KZ_XML_NODE_TEXT)
            return TRUE;

      return FALSE;
}


void
kz_xml_text_node_replace_text (KzXMLNode *node, const gchar *text)
{
      g_return_if_fail(node && node->type == KZ_XML_NODE_TEXT);
      g_return_if_fail(text);

      g_free(node->content);
      node->content = g_strdup(text);

      return;
}


gboolean
kz_xml_node_is_space (KzXMLNode *node)
{
      const gchar *str;
      gint c;

      g_return_val_if_fail(node, FALSE);

      if (node->type != KZ_XML_NODE_TEXT) return FALSE;
      g_return_val_if_fail(node->content, FALSE);

      str = node->content;
      /*
       * "" is not space.
       * '<title></title>' won't make indent. 
       * Now, '<title></title>' does not make indent.
       * Formerly, 
       * <title></title>
       * is converted to 
       * <title>
       * </title>
       */
      if (strlen(str) == 0)
            return FALSE;
      for (c = *str; *str; str++)
      {
            if (!isspace(*str))
                  return FALSE;
      }

      return TRUE;
}


gboolean
kz_xml_node_remove_trailing_blank_line (KzXMLNode *node)
{
      const gchar *str, *pos;
      gint c, i;

      g_return_val_if_fail(node, FALSE);
      g_return_val_if_fail(kz_xml_node_is_text(node), FALSE);

      if (!node->content) return FALSE;

      str = node->content;
      if (!str) return FALSE;

      for (i = strlen(str); i >= 0; i--)
      {
            pos = str + i;
            c = *str;

            if (isspace(c) && c != '\n') continue;

            *((gchar *)pos) = '\0';
            return TRUE;
      }

      return FALSE;
}


void
kz_xml_node_arrange_indent(KzXMLNode *parent, guint indent_level)
{
      KzXMLNode *node;
      gchar *indent;

      g_return_if_fail(parent);

      /* build indent string */
      {
            gint i, len, size;
            gchar *str = "  ";

            len = strlen(str);
            size = len * indent_level + 2;
            indent = g_alloca(size);
            indent[0] = '\n';
            for (i = 0; i < indent_level; i++)
                  memcpy(1 + indent + i * len, str, len);
            indent[size - 1] = '\0';
      }

      /* search element nodes */
      for (node = kz_xml_node_first_child(parent);
           node;
           node = kz_xml_node_next(node))
      {
            KzXMLNode *child;

            if (!kz_xml_node_is_element(node)) continue;

            /* indent for start tag */
            child = kz_xml_node_prev(node);
            if (child && kz_xml_node_is_space(child))
            {
                  child = kz_xml_node_remove_child(parent, child);
                  kz_xml_node_unref(child);
                  child = kz_xml_text_node_new(indent);
                  kz_xml_node_insert_before(parent, child, node);
            }

            /* indent for end tag */
            child = kz_xml_node_last_child(node);
            if (child && kz_xml_node_is_space(child))
            {
                  child = kz_xml_node_remove_child(node, child);
                  kz_xml_node_unref(child);
                  child = kz_xml_text_node_new(indent);
                  kz_xml_node_append_child(node, child);
            }

            /* indent children */
            kz_xml_node_arrange_indent(node, indent_level + 1);
      }
}

Generated by  Doxygen 1.6.0   Back to index