/* -*- 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 2634 2006-12-02 06:23:06Z ikezoe $
 */

#include "kz-xml.h"

#include <stdio.h>
#include <string.h>
#include <ctype.h>

typedef struct _KzXMLPrivate    KzXMLPrivate;
struct _KzXMLPrivate
{
	GMarkupParseContext *context;
};
#define KZ_XML_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), KZ_TYPE_XML, KzXMLPrivate))

static void dispose      (GObject    *object);

static void kz_xml_attr_free    (KzXMLAttr *attr);


G_DEFINE_TYPE(KzXML, kz_xml, G_TYPE_OBJECT)

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

	object_class = (GObjectClass *) klass;

	object_class->dispose  = dispose;
	g_type_class_add_private (object_class, sizeof(KzXMLPrivate));
}


static void
kz_xml_init (KzXML *xml)
{
	KzXMLPrivate *priv = KZ_XML_GET_PRIVATE (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);
	priv->context = NULL;
}


static void
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 (kz_xml_parent_class)->dispose)
		G_OBJECT_CLASS (kz_xml_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; 
	KzXMLPrivate *priv;

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

	priv = KZ_XML_GET_PRIVATE (xml);
	context = 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);
		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);
	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);
	}
}
