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

/*
 *  Copyright (C) 2004 Hiroyuki Ikezoe
 *  Copyright (C) 2003-2004 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.
 *
 */

#include "kazehakase.h"
#include "kz-scrap-bookmark.h"
#include "kz-xml.h"

#include <string.h>
#include <stdlib.h>
#include <time.h>

static gboolean kz_scrap_bookmark_is_supported       (KzBookmark  *bookmark,
						      const gchar *buf);
static void     kz_scrap_bookmark_init               (KzBookmark  *bookmark);
static gboolean kz_scrap_bookmark_from_string        (KzBookmark  *bookmark,
						      const gchar *buffer,
						      guint        length,
						      GError     **error);
static gchar   *kz_scrap_bookmark_to_string          (KzBookmark  *bookmark);

static void     kz_scrap_bookmark_notify             (GObject     *object,
						      GParamSpec  *pspec,
						      KzXML       *xml);

static void     kz_scrap_bookmark_build_tree         (KzBookmark  *bookmark);
static void     kz_scrap_bookmark_insert_xml_node    (KzBookmark  *bookmark,
						      KzBookmark  *parent,
						      KzBookmark  *sibling);
static void     kz_scrap_bookmark_remove_xml_node    (KzBookmark  *bookmark);
static void     kz_scrap_bookmark_connect_signals    (KzBookmark  *bookmark);
static void     kz_scrap_bookmark_disconnect_signals (KzBookmark  *bookmark);
static void     cb_bookmark_insert_child   (KzBookmark  *bookmark,
					    KzBookmark  *child,
					    KzBookmark  *sibling);
static void     cb_bookmark_remove_child   (KzBookmark  *bookmark,
					    KzBookmark  *child);
static void     cb_bookmark_notify         (GObject     *object,
					    GParamSpec  *spec);

static void     xml_node_set_title         (KzXMLNode   *parent,
					    const gchar *title);


static GQuark xml_quark = 0;
static GQuark node_quark = 0;
static GQuark building_quark = 0;

#define BOOKMARK_SET_BUILDING(b) \
	g_object_set_qdata(G_OBJECT(b), building_quark, GINT_TO_POINTER(TRUE))
#define BOOKMARK_UNSET_BUILDING(b) \
	g_object_set_qdata(G_OBJECT(b), building_quark, GINT_TO_POINTER(FALSE))
#define BOOKMARK_IS_BUILDING(b) \
	GPOINTER_TO_INT(g_object_get_qdata(G_OBJECT(b), building_quark))


#define XMLNS "http://www.w3.org/1999/xhtml"
#define DTD   "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict-dtd\">"

static KzBookmarkFileType scrap_bookmark_file_type =
{
	priority_hint: 0,
	file_type:     "SCRAPBOOKMARK",
	init:          kz_scrap_bookmark_init,
	is_supported:  kz_scrap_bookmark_is_supported,
	from_string:   kz_scrap_bookmark_from_string,
	to_string:     kz_scrap_bookmark_to_string,
};


KzBookmarkFileType *
kz_scrap_bookmark_get_file_types (gint idx)
{
	if (idx == 0)
		return &scrap_bookmark_file_type;
	else
		return NULL;
}


static gboolean
kz_scrap_bookmark_is_supported(KzBookmark *bookmark, const gchar *buf)
{
	const gchar *pos;

	g_return_val_if_fail(buf, FALSE);

	if (strncmp(buf, "<?xml", 5)) return FALSE;

	pos = buf;

	/* find first element */
	do
		pos = strchr(pos + 1, '<');
	while (pos && pos[1] == '!');

	if (pos && !strncmp(pos, "<html xmlns", 11))
		return TRUE;

	return FALSE;
}


static void
kz_scrap_bookmark_init (KzBookmark *bookmark)
{
	KzXML *xml;

	if (!xml_quark)
		xml_quark = g_quark_from_string("KzScrap::KzXML");
	if (!node_quark)
		node_quark = g_quark_from_string("KzScrap::XMLNode");
	if (!building_quark)
		building_quark = g_quark_from_string("KzScrap::Building");

	xml = kz_xml_new();
	g_object_set_qdata_full(G_OBJECT(bookmark), xml_quark,
				xml, (GDestroyNotify) g_object_unref);

	bookmark->flags |= KZ_BOOKMARK_FILE_FLAG;

	g_signal_connect(G_OBJECT(bookmark), "notify",
			 G_CALLBACK(kz_scrap_bookmark_notify), xml);
	g_signal_connect_after(G_OBJECT(bookmark), "insert-child",
			       G_CALLBACK(cb_bookmark_insert_child), xml);
	g_signal_connect_after(G_OBJECT(bookmark), "remove-child",
			       G_CALLBACK(cb_bookmark_remove_child), xml);
}


static gboolean
kz_scrap_bookmark_from_string (KzBookmark *bookmark,
			       const gchar *buffer, guint length,
			       GError **error)
{
	KzXML *xml;

	g_return_val_if_fail(KZ_IS_BOOKMARK(bookmark), FALSE);

	xml = g_object_get_qdata(G_OBJECT(bookmark), xml_quark);
	g_return_val_if_fail(KZ_IS_XML(xml), FALSE);

	if (!kz_xml_load_xml(xml, buffer, length) ||
	    !kz_xml_get_root_element(xml))
	{
		KzXMLNode *doctype;
		KzXMLNode *node, *head_node, *title_node, *body_node,
			  *title, *space;
		const gchar *bookmark_title;

		/* DOCTYPE */
		doctype = kz_xml_node_new(xml, KZ_XML_NODE_DOCTYPE);
		doctype->content = g_strdup(DTD);
		kz_xml_node_append_child(xml->root, doctype);

		space = kz_xml_text_node_new("\n");
		kz_xml_node_append_child(xml->root, space);

		/* <html> */
		node = kz_xml_element_node_new("html");
		kz_xml_node_set_attr(node, "xmlns", XMLNS);
		kz_xml_node_append_child(xml->root, node);

		/* space1 */
		space = kz_xml_text_node_new("\n");
		kz_xml_node_append_child(node, space);
		{
			/* <head> */
			head_node = kz_xml_element_node_new("head");
			title_node = kz_xml_element_node_new("title");

			bookmark_title = kz_bookmark_get_title(bookmark);
			if(bookmark_title)
				title = kz_xml_text_node_new(bookmark_title);
			else
				title = kz_xml_text_node_new("Scrap Bookmarks");
			kz_xml_node_append_child(title_node, title);
			kz_xml_node_append_child(head_node, title_node);
			kz_xml_node_append_child(node, head_node);

			/* <body> */
			body_node = kz_xml_element_node_new("body");
			space = kz_xml_text_node_new("\n");
			kz_xml_node_append_child(body_node, space);
			kz_xml_node_append_child(node, body_node);
		}

		/* space2 */
		space = kz_xml_text_node_new("\n");
		kz_xml_node_append_child(node, space);
	}

	kz_scrap_bookmark_build_tree(bookmark);

	return TRUE;
}


static gchar *
kz_scrap_bookmark_to_string (KzBookmark *bookmark)
{
	KzXML *xml;

	g_return_val_if_fail(KZ_IS_BOOKMARK(bookmark), NULL);

	xml = g_object_get_qdata(G_OBJECT(bookmark), xml_quark);
	g_return_val_if_fail(KZ_IS_XML(xml), NULL);

	kz_xml_node_arrange_indent(xml->root, 0);

	return kz_xml_node_to_xml(xml->root);
}


static void
kz_scrap_bookmark_notify (GObject *object, GParamSpec *pspec, KzXML *xml)
{
	KzBookmark *bookmark;
	KzXMLNode *node;
	const gchar *prop;
        GValue value = { 0 };

	g_return_if_fail(KZ_IS_BOOKMARK(object));
	g_return_if_fail(KZ_IS_XML(xml));

	bookmark = KZ_BOOKMARK(object);

	if (BOOKMARK_IS_BUILDING(bookmark)) return;

	node = kz_xml_get_root_element(xml);
	if (!node) return;
	g_return_if_fail(kz_xml_node_name_is(node, "html"));

	prop = g_param_spec_get_name(pspec);
	g_return_if_fail(prop);

        g_value_init(&value, G_PARAM_SPEC_VALUE_TYPE(pspec));
        g_object_get_property(object, prop, &value);
}

static void
parse_link_node (KzBookmark *bookmark, 
		 KzBookmark *parent_bookmark,
		 KzXMLNode *parent)
{
	KzXMLNode *node;

	g_return_if_fail(KZ_IS_BOOKMARK(bookmark));

	if (!kz_xml_node_is_element(parent)) return;

	for (node = kz_xml_node_first_child(parent);
	     node;
	     node = kz_xml_node_next(node))
	{
		if (!kz_xml_node_is_element(node)) continue;

		if (kz_xml_node_name_is(node, "a"))
		{
			/* FIXME! why does "file://" prefix need? */
			const gchar *uri = kz_xml_node_get_attr(node, "href");
			gchar *link = g_strdup_printf("file://%s%s",
						      kz_bookmark_get_location(parent_bookmark),
						      uri);
			
			kz_bookmark_set_link(bookmark, link);
			g_free(link);
		}	
	}
}


static void
parse_bookmark_node (KzBookmark *bookmark, 
		     KzBookmark *parent_bookmark,
		     KzXMLNode *parent)
{
	KzXMLNode *node;

	g_return_if_fail(KZ_IS_BOOKMARK(bookmark));

	if (!kz_xml_node_is_element(parent)) return;

	for (node = kz_xml_node_first_child(parent);
	     node;
	     node = kz_xml_node_next(node))
	{
		if (!kz_xml_node_is_element(node)) continue;

		if (kz_xml_node_name_is(node, "span"))
		{
			const gchar *id = kz_xml_node_get_attr(node, "id");
			if (!strcmp(id, "link"))
			{
				parse_link_node(bookmark, parent_bookmark, node);
			}
			else if (!strcmp(id, "title"))
			{
				gchar *title = kz_xml_node_to_str(node);
				kz_bookmark_set_title(bookmark, title);
				g_free(title);
			}
		}
		else if (kz_xml_node_name_is(node, "div"))
		{
			const gchar *id = kz_xml_node_get_attr(node, "id");
			if (!strcmp(id, "description"))
			{
				gchar *desc = kz_xml_node_to_xml(node);
				kz_bookmark_set_description(bookmark, desc);
				g_free(desc);
			}			
		}
	}
}


static void
parse_body_node (KzBookmark *bookmark, KzXMLNode *parent)
{
	KzXMLNode *node;

	g_return_if_fail(KZ_IS_BOOKMARK(bookmark));

	if (!kz_xml_node_is_element(parent)) return;

	for (node = kz_xml_node_first_child(parent);
	     node;
	     node = kz_xml_node_next(node))
	{
		if (!kz_xml_node_is_element(node)) continue;

		if (kz_xml_node_name_is(node, "div"))
		{
			if (!strcmp(kz_xml_node_get_attr(node, "id"), "bookmark"))
			{
				KzBookmark *child_bookmark = kz_bookmark_new();

				BOOKMARK_SET_BUILDING(bookmark);
				g_object_set_qdata(G_OBJECT(child_bookmark),
						   node_quark, node);
				parse_bookmark_node(child_bookmark, bookmark, node);
				kz_bookmark_append(bookmark, child_bookmark);
				BOOKMARK_UNSET_BUILDING(bookmark);
				g_object_unref(child_bookmark);
			}
		}
	}
}


static void
parse_head_node (KzBookmark *bookmark, KzXMLNode *parent)
{
	KzXMLNode *node;

	g_return_if_fail(KZ_IS_BOOKMARK(bookmark));

	if (!kz_xml_node_is_element(parent)) return;

	for (node = kz_xml_node_first_child(parent);
	     node;
	     node = kz_xml_node_next(node))
	{
		if (!kz_xml_node_is_element(node)) continue;

		if (kz_xml_node_name_is(node, "title"))
		{
			gchar *title;

			if (kz_bookmark_get_title(bookmark) &&
			    !g_object_get_qdata(G_OBJECT(bookmark), xml_quark))
			{
				g_warning("title element is duplicated!");
				continue;
			}
			title = kz_xml_node_to_str(node);
			kz_bookmark_set_title(bookmark, title);
			g_free(title);
		}
	}
}


static KzXMLNode *
xml_node_get_named_node (KzXMLNode *parent, const gchar *name)
{
	KzXMLNode *node;

	g_return_val_if_fail(parent, NULL);
	g_return_val_if_fail(name && *name, NULL);

	for (node = kz_xml_node_first_child(parent);
	     node;
	     node = kz_xml_node_next(node))
	{
		if (kz_xml_node_name_is(node, name))
			return node;
	}

	return NULL;
}


static KzXMLNode *
xml_node_get_named_node_with_id (KzXMLNode *parent, 
				 const gchar *name,
				 const gchar *id)
{
	KzXMLNode *node;

	g_return_val_if_fail(parent, NULL);
	g_return_val_if_fail(name && *name, NULL);
	g_return_val_if_fail(id && *id, NULL);

	for (node = kz_xml_node_first_child(parent);
	     node;
	     node = kz_xml_node_next(node))
	{
		if (kz_xml_node_name_is(node, name) &&
		    !strcmp(kz_xml_node_get_attr(node, "id"), id))
			return node;
	}

	return NULL;
}


static void
parse_html_node (KzBookmark *bookmark, KzXMLNode *parent)
{
	KzXMLNode *node;

	g_return_if_fail(KZ_IS_BOOKMARK(bookmark));

	if (!kz_xml_node_is_element(parent)) return;

	for (node = kz_xml_node_first_child(parent);
	     node;
	     node = kz_xml_node_next(node))
	{
		if (!kz_xml_node_is_element(node)) continue;

		if (kz_xml_node_name_is(node, "head"))
		{
			parse_head_node(bookmark, node);
		}
		else if (kz_xml_node_name_is(node, "body"))
		{
			parse_body_node(bookmark, node);
		}
	}
}


static void
kz_scrap_bookmark_build_tree (KzBookmark *bookmark)
{
	KzXML *xml;
	KzXMLNode *node;

	g_return_if_fail(KZ_IS_BOOKMARK(bookmark));

	xml = g_object_get_qdata(G_OBJECT(bookmark), xml_quark);
	g_return_if_fail(KZ_IS_XML(xml));

	node = kz_xml_get_root_element (xml);
	if (!node) return;
	g_return_if_fail (kz_xml_node_name_is(node, "html"));

	BOOKMARK_SET_BUILDING(bookmark);
	kz_bookmark_set_link(bookmark, 
			     kz_bookmark_get_location(bookmark));
	g_object_set_qdata(G_OBJECT(bookmark),
			   node_quark, node);
	parse_html_node(bookmark, node);
	BOOKMARK_UNSET_BUILDING(bookmark);
}


static void
xml_node_append_element_with_id (KzXMLNode *parent,
				 const gchar *contents,
				 const gchar *name,
				 const gchar *id)
{
	KzXMLNode *node, *text_node, *space;

	/*
	 *  <name id="id">contents</div>\n
	 */

	/*  */
	node = kz_xml_element_node_new(name);
	kz_xml_node_set_attr(node, "id", id);
	kz_xml_node_append_child(parent, node);

	/* contents */
	text_node = kz_xml_text_node_new(contents);
	kz_xml_node_append_child(node, text_node);
	/* space1 */
	space = kz_xml_text_node_new("\n");
	kz_xml_node_append_child(parent, space);
}


static void
xml_node_set_link (KzXMLNode *node, const gchar *uri)
{
	KzXMLNode *link_node, *a_node,
		  *text_node, *space;
	gchar *anchor;

	anchor = g_strdup_printf("#%s", uri);

	link_node = xml_node_get_named_node_with_id(node, "span", "llink"); 

	if (link_node)
	{
		KzXMLNode *child;

		/* remove all child node */
		child = kz_xml_node_first_child(link_node);
		while (child)
		{
			KzXMLNode *next = kz_xml_node_next(child);
			kz_xml_node_remove_child(link_node, child);
			kz_xml_node_unref(child);
			child = next;
		}
	}
	else
	{
		link_node = kz_xml_element_node_new("span");
		kz_xml_node_set_attr(link_node, "id", "link");
		kz_xml_node_append_child(node, link_node);
		space = kz_xml_text_node_new("\n");
		kz_xml_node_append_child(node, space);
	}
	
	a_node = kz_xml_element_node_new("a");
	kz_xml_node_set_attr(a_node, "href", anchor);
	kz_xml_node_set_attr(a_node, "name", anchor);
	kz_xml_node_set_attr(a_node, "id", uri);
		
	text_node = kz_xml_text_node_new("#");
	kz_xml_node_append_child(a_node, text_node);

	kz_xml_node_append_child(link_node, a_node);
	
	g_free(anchor);
}



static void
xml_node_set_location (KzXMLNode *node, const gchar *location)
{
	KzXMLNode *location_node, *a_node,
		  *text_node, *space;

	location_node = xml_node_get_named_node_with_id(node, "span", "location"); 

	if (location_node)
	{
		KzXMLNode *child;

		/* remove all child node */
		child = kz_xml_node_first_child(location_node);
		while (child)
		{
			KzXMLNode *next = kz_xml_node_next(child);
			kz_xml_node_remove_child(location_node, child);
			kz_xml_node_unref(child);
			child = next;
		}
	}
	else
	{
		space = kz_xml_text_node_new("\n");
		kz_xml_node_append_child(node, space);
		
		location_node = kz_xml_element_node_new("span");
		kz_xml_node_set_attr(location_node, "id", "location");
		kz_xml_node_append_child(node, location_node);
	}

	a_node = kz_xml_element_node_new("a");
	kz_xml_node_set_attr(a_node, "href", location);
		
	text_node = kz_xml_text_node_new(location);
	kz_xml_node_append_child(a_node, text_node);

	kz_xml_node_append_child(location_node, a_node);

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


static void
xml_node_set_title (KzXMLNode *parent, const gchar *title)
{
	KzXMLNode *title_node = NULL, *child;

	g_return_if_fail(parent);

	title_node = xml_node_get_named_node_with_id(parent, "span", "title");

	g_return_if_fail(title_node);

	/* remove old title */
	child = kz_xml_node_first_child(title_node);
	while(child)
	{
		KzXMLNode *next = kz_xml_node_next(child);

		child = kz_xml_node_remove_child(title_node, child);
		kz_xml_node_unref(child);
		child = next;
	}

	/* set new title */
	child = kz_xml_text_node_new(title);
	kz_xml_node_append_child(title_node, child);
}


static void
xml_node_set_description (KzXMLNode *parent, const gchar *desc)
{
	KzXMLNode *div_node = NULL, *child, *desc_node, *space;
	KzXML *xml;
	gsize desc_len = strlen(desc);
	
	g_return_if_fail(parent);

	div_node = xml_node_get_named_node_with_id(parent, "div", "description");
	/* remove old node, or create new desc node */
	if (div_node)
	{
		child = kz_xml_node_first_child(div_node);
		while(child)
		{
			KzXMLNode *next = kz_xml_node_next(child);

			child = kz_xml_node_remove_child(div_node, child);
			kz_xml_node_unref(child);
			child = next;
		}
	}
	else
	{
		KzXMLNode *space1;
		div_node = kz_xml_element_node_new("div");
		kz_xml_node_set_attr(div_node, "id", "description");
		kz_xml_node_append_child(parent, div_node);
		
		space1 = kz_xml_text_node_new("\n");
		kz_xml_node_append_child(div_node, space1);
	}

	/* set new description */
	xml = kz_xml_new();
	kz_xml_load_xml(xml, desc, desc_len);
	desc_node = kz_xml_get_root_element(xml);

	kz_xml_node_append_child(div_node, desc_node);

	space = kz_xml_text_node_new("\n");
	kz_xml_node_append_child(parent, space);
}	


static KzXMLNode *
create_xml_node (KzBookmark *bookmark)
{
	KzXMLNode *node = NULL, *space, *hr;
	guint lm;
	struct tm *date = NULL;
	const gchar *title, *location, *desc;
	gchar last_modified[20];

	g_return_val_if_fail(KZ_IS_BOOKMARK(bookmark), NULL);

	/*
	 * <div id="bookmark">\n
	 * <span id="title">...
	 * <span id="location">...
	 * <span id="date">..
	 * <div id="description">
	 * ..
	 * ..
	 * </div>
	 * </div>
	 */

	node = kz_xml_element_node_new("div");
	kz_xml_node_set_attr(node, "id", "bookmark");

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

	/* last-modified */
	lm = kz_bookmark_get_last_modified(bookmark);
	if (lm)
	{
		gchar link_string[20];
		const time_t t = (time_t)lm;

		date = localtime(&t);
		/* link */
		strftime(link_string, 20, "%Y%m%d%H%M%S", date);
		xml_node_set_link(node, link_string);
	}

	/* title */
	title = kz_bookmark_get_title(bookmark);
	if (title)
		xml_node_append_element_with_id (node, title, "span", "title");
	/* location */
	location = kz_bookmark_get_location(bookmark);
	if (location)
	{
		xml_node_set_location(node, location);
	}
	
	/* last-modified */
	if (date)
	{
		strftime(last_modified, 20, "%Y-%m-%d %H:%M", date);
		xml_node_append_element_with_id (node, last_modified, "span", "date");
	}

	/* description */
	desc = kz_bookmark_get_description(bookmark);
	if (desc)
	{
		xml_node_set_description(node, desc);
	}
	
	hr = kz_xml_element_node_new("hr");
	kz_xml_node_append_child(node, hr);

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


	return node;
}


static void
kz_scrap_bookmark_connect_signals (KzBookmark *bookmark)
{
	g_signal_connect(G_OBJECT(bookmark), "notify",
			 G_CALLBACK(cb_bookmark_notify), NULL);

	if (kz_bookmark_is_file(bookmark)) return;
	if (!kz_bookmark_is_folder(bookmark)) return;

	{
		g_signal_connect_after(G_OBJECT(bookmark), "insert-child",
				       G_CALLBACK(cb_bookmark_insert_child),
				       NULL);
		g_signal_connect_after(G_OBJECT(bookmark), "remove-child",
				       G_CALLBACK(cb_bookmark_remove_child),
				       NULL);
	}
}


static void
kz_scrap_bookmark_disconnect_signals (KzBookmark *bookmark)
{
	g_signal_handlers_disconnect_by_func
		(G_OBJECT(bookmark),
		 G_CALLBACK(cb_bookmark_notify), NULL);

	if (kz_bookmark_is_file(bookmark)) return;
	if (!kz_bookmark_is_folder(bookmark)) return;

	{
		g_signal_handlers_disconnect_by_func
			(G_OBJECT(bookmark),
			 G_CALLBACK(cb_bookmark_insert_child), NULL);
		g_signal_handlers_disconnect_by_func
			(G_OBJECT(bookmark),
			 G_CALLBACK(cb_bookmark_remove_child), NULL);
	}
}


static void
cb_bookmark_insert_child (KzBookmark *bookmark,
			  KzBookmark *child, KzBookmark *sibling)
{
	kz_scrap_bookmark_insert_xml_node(child, bookmark, sibling);
	kz_scrap_bookmark_connect_signals(child);
}


static void
cb_bookmark_remove_child (KzBookmark *bookmark, KzBookmark *child)
{
	kz_scrap_bookmark_disconnect_signals(child);
	kz_scrap_bookmark_remove_xml_node(child);
}


static void
cb_bookmark_notify (GObject *object, GParamSpec *pspec)
{
	KzBookmark *bookmark;
	KzXMLNode *node;
	const gchar *prop;
        GValue value = { 0 };

	g_return_if_fail(KZ_IS_BOOKMARK(object));
	bookmark = KZ_BOOKMARK(object);

	if (BOOKMARK_IS_BUILDING(bookmark)) return;

	node = g_object_get_qdata(G_OBJECT(bookmark), node_quark);
	g_return_if_fail(node);
	g_return_if_fail(kz_xml_node_name_is(node, "div") && 
			 !strcmp(kz_xml_node_get_attr(node, "id"), "bookmark"));

	prop = g_param_spec_get_name(pspec);
	g_return_if_fail(prop);

        g_value_init(&value, G_PARAM_SPEC_VALUE_TYPE(pspec));
        g_object_get_property(object, prop, &value);

	/*
	 * FIXME! We should use hash table.
	 */
	if (!strcmp(prop, "title"))
	{
		gchar *title = g_value_dup_string(&value);

		xml_node_set_title(node, title);
		g_free(title);
	}
	else if (!strcmp(prop, "link"))
	{
		gchar *uri = g_value_dup_string(&value);

		xml_node_set_link(node, uri);
		g_free(uri);
	}
	else if (!strcmp(prop, "location"))
	{
		gchar *location = g_value_dup_string(&value);

		xml_node_set_location(node, location);
		g_free(location);
	}
	else if (!strcmp(prop, "description"))
	{
		gchar *desc = g_value_dup_string(&value);

		xml_node_set_description(node, desc);
		g_free(desc);
	}
}


static void
kz_scrap_bookmark_insert_xml_node (KzBookmark *bookmark,
				   KzBookmark *parent, KzBookmark *sibling)
{
	KzXMLNode *node, *space;
	KzXMLNode *parent_node, *body_node, *sibling_node = NULL;

	g_return_if_fail(KZ_IS_BOOKMARK(parent));
	g_return_if_fail(KZ_IS_BOOKMARK(bookmark));
	g_return_if_fail(!sibling || KZ_IS_BOOKMARK(sibling));

	node = g_object_get_qdata(G_OBJECT(bookmark), node_quark);
	if (node) return;

	parent_node = g_object_get_qdata(G_OBJECT(parent),
					 node_quark);
	if (!parent_node)
	{
		KzXML *xml;

		g_return_if_fail(KZ_IS_BOOKMARK(parent));

		xml = g_object_get_qdata(G_OBJECT(parent), xml_quark);
		g_return_if_fail(KZ_IS_XML(xml));

		parent_node = kz_xml_get_root_element(xml);
		g_return_if_fail (kz_xml_node_name_is(parent_node, "html"));
	}
	
	body_node = xml_node_get_named_node(parent_node, "body");

	if (sibling)
		sibling_node = g_object_get_qdata(G_OBJECT(sibling),
						  node_quark);

	node = create_xml_node(bookmark);
	g_object_set_qdata(G_OBJECT(bookmark), node_quark, node);

	kz_xml_node_insert_before(body_node, node, sibling_node);
	space = kz_xml_text_node_new("\n");
	kz_xml_node_insert_before(body_node, space, kz_xml_node_next(node));
}


static void
kz_scrap_bookmark_remove_xml_node (KzBookmark *bookmark)
{
	KzXMLNode *node, *parent;

	g_return_if_fail(KZ_IS_BOOKMARK(bookmark));

	node = g_object_get_qdata(G_OBJECT(bookmark), node_quark);
	if (!node) return;

	parent = kz_xml_node_parent(node);
	if (parent)
	{
		KzXMLNode *space;

		space = kz_xml_node_next(node);
		if (space && kz_xml_node_is_space(space))
		{
			space = kz_xml_node_remove_child(parent, space);
			kz_xml_node_unref(space);
		}

		node = kz_xml_node_remove_child(parent, node);
	}

	kz_xml_node_unref(node);
	g_object_set_qdata(G_OBJECT(bookmark), node_quark, NULL);
}

