Logo Search packages:      
Sourcecode: galeon version File versions

xbel.c

/* -*- mode: c; c-style: k&r; c-basic-offset: 8 -*- */
/* 
 *  Copyright (C) 2002 Ricardo Fernández Pascual
 *
 *  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.
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

/**
 * Saving and loading bookmarks in XBEL
 */

/* system includes */
#include <string.h>
#include <stdlib.h>
#include <stdio.h>

#include <libxml/xmlmemory.h>
#include <libxml/parser.h>
#include <libxml/tree.h>

#include <glib/gi18n.h>
#include <libgnome/gnome-util.h>

#include "xbel.h"

#define XBEL_GALEON_OWNER "http://galeon.sourceforge.net/"

/**
 * Private typedefs
 */
typedef struct _GbXBELMetadata GbXBELMetadata;

/**
 * Private functions, only availble from this file
 */
static GbBookmarkSet *  gb_xbel_load_from_file        (GbIO *io, const gchar *filename);
static GbBookmarkSet *  gb_xbel_load_from_string      (GbIO *io, const gchar *data);
static gboolean         gb_xbel_save_to_file          (GbIO *io, GbBookmarkSet *set,
                                           const gchar *filename);
static gboolean         gb_xbel_save_to_string        (GbIO *io, GbBookmarkSet *set, gchar **data);
static gchar *          gb_xbel_format_name           (GbIO *io);
static GList *          gb_xbel_extensions            (GbIO *io);

static GbBookmarkSet *  xbel_read               (xmlDocPtr item);
static GbBookmark *     xbel_read_node                (GbBookmarkSet *set, xmlNodePtr node);

static xmlNodePtr get_child               (xmlNodePtr parent, const gchar *name);
static gchar *          get_child_value_string        (xmlNodePtr parent, const gchar *name);
static gboolean         get_child_value_boolean       (xmlNodePtr parent, const gchar *name);
static glong            get_child_value_long          (xmlNodePtr parent, const gchar *name);
static GbXBELMetadata * xbel_read_metadata            (xmlNodePtr node);
static GbXBELMetadata * xbel_read_metadata_node       (xmlNodePtr node);

static void             gb_xbel_metadata_free         (GbXBELMetadata *md);
static GbXBELMetadata * gb_xbel_metadata_new          (void);

static void       xbel_save_node                (xmlNodePtr xmlNode, GbBookmarkSet *set, 
                                           GbBookmark *b);
static void       xbel_save_site                (xmlNodePtr xmlNode, GbSite *b);
static xmlDocPtr  xbel_save_root                (GbBookmarkSet *set);
static void       xbel_save_folder        (xmlNodePtr xmlNode, GbBookmarkSet *set,
                                           GbFolder *b);
static void       xbel_save_alias               (xmlNodePtr xmlNode, GbBookmark *b);
static void       xbel_save_alias_placeholder   (xmlNodePtr xmlNode, GbBookmark *b);

static GbXBELMetadata * xbel_save_common_data         (xmlNodePtr xmlNode, GbBookmark *b);
static void             xbel_save_galeon_metadata     (xmlNodePtr node, GbXBELMetadata *md);

static void       xbel_save_node_link_xml_node  (GbBookmark *b);

static gchar *          xbel_escape_smart_site_history_item (const gchar *item);
static gchar *          xbel_unescape_smart_site_history_item (const gchar *item);

static xmlDtdPtr  xml_doc_get_dtd               (xmlDocPtr doc);
static xmlNodePtr xml_node_update_text_child    (xmlNodePtr parent, xmlNsPtr ns,
                                           const xmlChar *name, const xmlChar *content);
static void             xml_node_remove_child         (xmlNodePtr parent, const xmlChar *name);

static xmlAttrPtr       xmlSetRawProp (xmlNodePtr node, const xmlChar *name, const xmlChar *value);
static xmlChar *        xmlGetRawProp (xmlNodePtr node, const xmlChar *name);

/* safe strlen */
#define sstrlen(s) ((s) ? strlen ((s)) : 0)

/**
 * Galeon's XBEL metadata info
 */ 
00104 struct _GbXBELMetadata 
{
      gchar *nick;
      gchar *smarturl;
      guint entry_width;
      gboolean entry_folded;
      gchar *pixmap;
      gboolean toolbar;
      gboolean context;
      gboolean default_f;
      gboolean autofolder;
      gboolean vfolder;
      gchar *vfolder_search_text;
      gchar *vfolder_search_options;
      gchar *vfolder_search_recently_created;
      gchar *vfolder_search_recently_visited;
      gchar *autobm_search_text;
      gchar *autobm_search_options;
      GTime t_add;
      GTime t_vis;
      GTime t_mod;
      guint tb_style;
      guint acc_key;
      guint acc_mod;
      GbFileFormatVersion version;
      GSList *smart_site_history;
};

/**
 * XBEL object
 */

G_DEFINE_TYPE (GbXBEL, gb_xbel, GB_TYPE_IO);

static void
gb_xbel_class_init (GbXBELClass *klass)
{

      klass->parent_class.gb_io_load_from_file = gb_xbel_load_from_file;
      klass->parent_class.gb_io_load_from_string = gb_xbel_load_from_string;
      klass->parent_class.gb_io_save_to_file = gb_xbel_save_to_file;
      klass->parent_class.gb_io_save_to_string = gb_xbel_save_to_string;
      klass->parent_class.gb_io_format_name = gb_xbel_format_name;
      klass->parent_class.gb_io_extensions = gb_xbel_extensions;
      
}

static void 
gb_xbel_init (GbXBEL *io)
{
}

GbXBEL *
gb_xbel_new (void)
{
      return g_object_new (GB_TYPE_XBEL, NULL);
}

/**
 * Start the import of a XBEL document, with a given filename and
 * the default bookmarks folder
 */
GbBookmarkSet *
gb_xbel_load_from_file (GbIO *io, const gchar *filename)
{
      GbBookmarkSet *set = NULL;
      xmlDocPtr doc;
      
      if (!(g_file_test (filename, G_FILE_TEST_EXISTS))) {
            /* no bookmarks, ERROR */
            return NULL;
      }

      doc = xmlParseFile (filename);
      
      if (doc) {
            set = xbel_read (doc);
      } else {
            g_warning ("unable to parse bookmarks file: %s", filename);
            return NULL;
      }
      
      if (set)
      {
            gb_bookmark_set_set_filename (set, filename);
            gb_bookmark_set_set_io (set, io);
            gb_bookmark_set_set_needs_saving (set, FALSE);
      }

      return set;
}

GbBookmarkSet *
gb_xbel_load_from_string (GbIO *io, const gchar *data)
{
      GbBookmarkSet *set = NULL;
      xmlDocPtr doc;
      
      doc = xmlParseMemory (data, sstrlen (data));
      
      if (doc) {
            set = xbel_read (doc);
      } else {
            g_warning ("unable to parse bookmarks data: %s", data);
      }

      if (set)
      {
            gb_bookmark_set_set_io (set, io);
      }

      return set;
}

gboolean
gb_xbel_save_to_file (GbIO *io, GbBookmarkSet *set,
                  const gchar *filename)
{
        xmlDocPtr doc;

        g_return_val_if_fail (filename != NULL, FALSE);

        doc = xbel_save_root (set);

        /* Try to save this document */
        if (xmlSaveFormatFile (filename, doc, 1) <= 0) 
      {
            /* ERROR */
            return FALSE;
        }

      return TRUE;
}


gboolean
gb_xbel_save_to_string (GbIO *io, GbBookmarkSet *set, gchar **data)
{
        xmlDocPtr doc;
      xmlChar *mem;
      gint size;
      
        g_return_val_if_fail (data != NULL, FALSE);

      /* Add the root XBEL node */
        doc = xbel_save_root (set);

        /* Try to save this document */
        xmlDocDumpMemory (doc, &mem, &size);
      if (size <= 0) 
      {
            /* ERROR */
            return FALSE;
        }

      *data = mem;

      return TRUE;
}



gchar *
gb_xbel_format_name (GbIO *io)
{
      return g_strdup (_("XBEL bookmarks format"));
}

GList *
gb_xbel_extensions (GbIO *io)
{
      static gchar *extensions[] = { "xbel", "xml", NULL };
      GList *l = NULL;
      int i;
      for (i = 0; extensions[i] != NULL; i++)
      {
            l = g_list_append (l, g_strdup (extensions[i]));
      }
      return l;
}

static GbBookmarkSet * 
xbel_read (xmlDocPtr doc) 
{
      GbBookmarkSet *set = NULL;
      GbBookmark *root;
      xmlNodePtr item = doc->children;    

      /* FIXME: very hacky */
      while (!(item->children && !strcmp (item->name, "xbel"))  && item->next)
      {
            item = item->next;
      }

      g_return_val_if_fail (item, NULL);

      set = gb_bookmark_set_new ();
      gb_bookmark_set_set_xbel_doc (set, doc);
      root = xbel_read_node (set, item);
      if (!root)
      {
            g_object_unref (set);
            return NULL;
      }


      if (!set->root && GB_IS_FOLDER (root))
      {
            gb_bookmark_set_set_root (set, GB_FOLDER (root));
      }
      
      gb_bookmark_set_resolve_aliases (set);

      g_object_unref (G_OBJECT (root));
      return set;
}

static GbBookmark *
xbel_read_node (GbBookmarkSet *set, xmlNodePtr node)
{ 
      GbBookmark *item = NULL;
      GbXBELMetadata *md = NULL;
      xmlNodePtr node2;
        g_return_val_if_fail (node != NULL, NULL);
      
      if (node->name == NULL) 
      {
            /*  Unknown node */
            g_warning ("xml node with NULL name!");
      } 
      else if (!strcmp (node->name, "folder")
             || !strcmp (node->name, "xbel")) 
      {
            GbFolder *folder;
            gchar *title = get_child_value_string (node, "title");
            gchar *folded_s = xmlGetProp (node, "folded");
            gboolean folded = FALSE;
            if (folded_s
                && (!g_ascii_strcasecmp (folded_s, "yes") 
                  || !g_ascii_strcasecmp (folded_s, "true")))
            {
                  folded = TRUE;
            }
            xmlFree (folded_s);

            md = xbel_read_metadata (node);

            if (!title)
            {
                  xmlNodePtr tn = get_child (node, "title");
                  if (tn != NULL) /* it was present */
                        title = g_strdup ("");
            }
            if (!title) 
            {
                  if (!strcmp (node->name, "xbel"))
                        title = g_strdup (_("XBEL bookmarks root"));
                  else
                        title = g_strdup (_("Untitled folder"));
            }
            if (md->vfolder)
            {
                  GbVFolder *vf = gb_v_folder_new (set, title);
                  folder = (GbFolder *) vf;
                  if (md->vfolder_search_text)
                  {
                        gb_v_folder_set_search_text (vf, md->vfolder_search_text);
                  }
                  if (md->vfolder_search_options)
                  {
                        gb_v_folder_set_search_options (vf, md->vfolder_search_options);
                  }
                  if (md->vfolder_search_recently_visited)
                  {
                        int days = atoi (md->vfolder_search_recently_visited);
                        gb_v_folder_set_rencently_visited (vf, TRUE, days);
                  }
                  else
                  {
                        gb_v_folder_set_rencently_visited (vf, FALSE, 0);
                  }
                  if (md->vfolder_search_recently_created)
                  {
                        int days = atoi (md->vfolder_search_recently_created);
                        gb_v_folder_set_rencently_created (vf, TRUE, days);
                  }
                  else
                  {
                        gb_v_folder_set_rencently_created (vf, FALSE, 0);
                  }
            }
            else if (md->autofolder)
            {
                  folder = GB_FOLDER (gb_auto_folder_new (set, title));
                  if (md->autobm_search_text)
                  {
                        gb_auto_folder_set_search_text (GB_AUTO_FOLDER (folder), md->autobm_search_text);
                  }
                  if (md->autobm_search_options)
                  {
                        gb_auto_folder_set_options (GB_AUTO_FOLDER (folder), md->autobm_search_options);
                  }
            }
            else
            {
                  folder = gb_folder_new (set, title);
            }
            
            g_free (title);

            if (set->root == NULL && !strcmp (node->name, "xbel"))
            {
                  gb_bookmark_set_set_root (set, folder);
            }

            if (set->root == folder)
            {
                  set->file_format_version = md->version;
            }

            gb_folder_set_create_toolbar (folder, md->toolbar);
            gb_folder_set_expanded (folder, !folded);
            /*  TODO: gb_folder_set_toolbar_style */

            if (md->default_f)
            {
                  gb_bookmark_set_set_default_folder (set, folder);
            }
            
            item = (GbBookmark *) folder;

            for (node2 = node->children; node2 != NULL;
                 node2 = node2->next) {
                  GbBookmark *b = xbel_read_node (set, node2);
                  if (b) 
                  {
                        gb_folder_add_child (folder, b, -1);
                        g_object_unref (G_OBJECT (b));
                  }
            }

      } 
      else if (!strcmp (node->name, "bookmark")) 
      {
            gchar *title = get_child_value_string (node, "title");
                gchar *url = xmlGetRawProp (node, "href");
            GbSite *site;
            md = xbel_read_metadata (node);

            /* if title == NULL it may be because it was not specified
               or because it is "". Let's try to check it */
            if (!title)
            {
                  xmlNodePtr tn = get_child (node, "title");
                  if (tn != NULL) /* it was present */
                        title = g_strdup ("");
            }
            if (md->smarturl)
            {
                  site = (GbSite *) gb_smart_site_new (set, title, url, md->smarturl);
                  if (md->entry_width > 0)
                  {
                        gb_smart_site_set_entry_size (GB_SMART_SITE (site), 0, md->entry_width);
                  }
                  gb_smart_site_set_folded (GB_SMART_SITE (site), md->entry_folded);
                  gb_smart_site_set_history (GB_SMART_SITE (site), 0, md->smart_site_history);
            }
            else
            {
                  site = gb_site_new (set, title, url);
            }

            gb_site_set_time_visited (site, md->t_vis);
            gb_site_set_accel (site, md->acc_key, md->acc_mod);

            item = (GbBookmark *) site;
                g_free (title); 
            xmlFree (url);
        } 
      else if (!strcmp (node->name, "alias")) 
      {
            gchar *refstr = xmlGetProp (node, "ref");

            item = GB_BOOKMARK (gb_alias_placeholder_new (set, refstr));
            g_free (refstr);

            md = xbel_read_metadata (node);

            gb_alias_placeholder_set_create_toolbar (GB_ALIAS_PLACEHOLDER (item), md->toolbar);
        }
      else if (!strcmp (node->name, "separator")) 
      {
            item  = GB_BOOKMARK (gb_separator_new (set));
      }
      else 
      {
            /* Unknown or title, info... */
        }

      if (item) 
      {
            gchar *idstr = xmlGetProp (node, "id");
            gchar *pixmap = xmlGetProp (node, "icon");
            gchar *notes = get_child_value_string (node, "desc");
            if (idstr) 
            {
                  gb_bookmark_set_id (item, idstr);
                  xmlFree (idstr);
            }
            /* this is probably wrong, but it is harmless because the setting in
             * the metadata will take precedence, if present. Konqueror uses this attribute */
            if (pixmap) gb_bookmark_set_pixmap (item, pixmap);
            if (notes) gb_bookmark_set_notes (item, notes);
            if (md) 
            {
                  if (md->nick) gb_bookmark_set_nick (item, md->nick);
                  if (md->pixmap) gb_bookmark_set_pixmap (item, md->pixmap);
                  gb_bookmark_set_add_to_context_menu (item, md->context);
                  gb_bookmark_set_time_modified (item, md->t_mod);
                  gb_bookmark_set_time_added (item, md->t_add);
                  
            }
            xmlFree (pixmap);
            g_free (notes);

            gb_bookmark_set_xbel_node (item, node);
      }
      gb_xbel_metadata_free (md);

      return item;
}

static GbXBELMetadata *
xbel_read_metadata (xmlNodePtr node)
{
      xmlNodePtr info_node = get_child (node, "info");
        if (info_node) 
      {
                /* Iterate over the metadata children */
                for (info_node = info_node->children;
                 info_node != NULL; info_node = info_node->next) 
            {
                  if (!strcmp (info_node->name, "metadata")) 
                  {
                        gchar *owner = xmlGetProp (info_node, "owner");
                        if (owner && !strcmp (owner, XBEL_GALEON_OWNER))
                        {
                              g_free (owner);
                              /* there can be only one metadata node */
                              return xbel_read_metadata_node (info_node);
                        }
                        g_free (owner);
                  }
            }
      }

      return gb_xbel_metadata_new ();
}


static GbXBELMetadata *
xbel_read_metadata_node (xmlNodePtr node)
{
      GbXBELMetadata *md = gb_xbel_metadata_new ();
      gchar *smart_site_data;
      gchar *file_format_version;
      gchar *smart_site_history;

      /* This is a Galeon metadata element, extract the data from it */
      md->nick = get_child_value_string (node, "nick");
      md->smarturl = get_child_value_string (node, "smarturl");
      smart_site_data = get_child_value_string (node, "smart_site_data");
      smart_site_history = get_child_value_string (node, "smart_site_history");
      md->pixmap = get_child_value_string (node, "pixmap");
      md->toolbar = get_child_value_boolean (node, "create_toolbar");
      md->context = get_child_value_boolean (node, "create_context");
      md->default_f = get_child_value_boolean (node, "default_folder");
      md->autofolder = get_child_value_boolean (node, "autofolder");
      md->vfolder = get_child_value_boolean (node, "vfolder");
      md->vfolder_search_text = get_child_value_string (node, "vfolder_search_text");
      md->vfolder_search_options = get_child_value_string (node, "vfolder_search_options");
      md->vfolder_search_recently_visited = get_child_value_string (node, "vfolder_search_recently_visited");
      md->vfolder_search_recently_created = get_child_value_string (node, "vfolder_search_recently_created");
      md->autobm_search_text = get_child_value_string (node, "autobm_search_text");
      md->autobm_search_options = get_child_value_string (node, "autobm_options");
      md->t_add = get_child_value_long (node, "time_added");
      md->t_vis = get_child_value_long (node, "time_visited");
      md->t_mod = get_child_value_long (node, "time_modified");
      md->tb_style = get_child_value_long (node, "toolbar_style");
      md->acc_key = get_child_value_long (node, "accel_key");
      md->acc_mod = get_child_value_long (node, "accel_mods");
      md->entry_folded = FALSE;
      md->entry_width = 0;
      file_format_version = get_child_value_string (node, "galeon_xbel_version");

      if (smart_site_data)
      {
            gchar **options = g_strsplit (smart_site_data, ",", 0);
            gint i;
            for (i = 0; options[i]; ++i)
            {                 
                  if (!strncmp (options[i], "width=", 6))
                  {
                        md->entry_width = atoi (&options[i][6]);
                  }
                  if (!strncmp (options[i], "folded=", 7))
                  {
                        md->entry_folded = atoi (&options[i][7]);
                  }
            }
            g_free (smart_site_data);
            g_strfreev (options);
      }

      if (smart_site_history)
      {
            gchar **items = g_strsplit (smart_site_history, ",", 0);
            gint i;
            if (items[0] && items[1])
            {
                  /* the first item is the parameter index */
                  /* for now, only one parameter is supported */

                  /* the second is the number of items, and it's ignored for now */

                  if (!strcmp (g_strstrip (items[0]), "0"))
                  {
                        GSList *history = NULL;
                        for (i = 2; items[i]; ++i)
                        {                 
                              gchar *item = xbel_unescape_smart_site_history_item (items[i]);
                              history = g_slist_prepend (history, item);
                        }
                        md->smart_site_history = g_slist_reverse (history);
                  }
            }
            g_free (smart_site_history);
            g_strfreev (items);
      }

      if (file_format_version)
      {
            if (!strcmp (file_format_version, "1"))
            {
                  md->version = GB_FILE_FORMAT_VERSION_GALEON_1;
            }
            else if (!strcmp (file_format_version, "2"))
            {
                  md->version = GB_FILE_FORMAT_VERSION_GALEON_2;
            }
            else
            {
                  md->version = GB_FILE_FORMAT_VERSION_UNKNOWN;
            }
            g_free (file_format_version);
      }

      return md;
}

static void
gb_xbel_metadata_free (GbXBELMetadata *md)
{
      if (!md) return;
      g_slist_foreach (md->smart_site_history, (GFunc) g_free, NULL);
      g_slist_free (md->smart_site_history);
      g_free (md->nick);
      g_free (md->smarturl);
      g_free (md->pixmap);
      g_free (md->vfolder_search_text);
      g_free (md->vfolder_search_options);
      g_free (md->vfolder_search_recently_visited);
      g_free (md->vfolder_search_recently_created);
      g_free (md->autobm_search_text);
      g_free (md->autobm_search_options);
      g_free (md);
}

static GbXBELMetadata *
gb_xbel_metadata_new (void)
{
      return g_new0 (GbXBELMetadata, 1);
}

/* saving */

/**
 * Treat a root item specially and use it as the root of the XBEL tree
 */
static xmlDocPtr
xbel_save_root (GbBookmarkSet *set)
{
        xmlNodePtr newNode;
      xmlDtdPtr dtd;
        xmlDocPtr doc;

      if (set->xbel_doc)
      {
            doc = set->xbel_doc;
      }
      else
      {
            doc = xmlNewDoc ("1.0");
            gb_bookmark_set_set_xbel_doc (set, doc);
      }

      set->file_format_version = GB_FILE_FORMAT_VERSION_GALEON_2;

      if (GB_BOOKMARK (set->root)->xbel_node && xmlDocGetRootElement (doc) == GB_BOOKMARK (set->root)->xbel_node)
      {
            newNode = GB_BOOKMARK (set->root)->xbel_node;
      }
      else
      {
            newNode = xmlNewDocNode (doc, NULL, "xbel", NULL);
            xmlDocSetRootElement (doc, newNode);
            /* children xml nodes may have been freed, we must discard them :-( */
            gb_bookmark_set_discard_xml_nodes (set);
            gb_bookmark_set_xbel_node (GB_BOOKMARK (set->root), newNode);
      }

      xmlSetProp(newNode, "version", "1.0");

      /* I'm not sure if the following is OK */
      if (xml_doc_get_dtd (doc) == NULL)
      {
            dtd = xmlNewDtd
                  (doc, "xbel", 
                   "+//IDN python.org//DTD XML Bookmark "
                   "Exchange Language 1.0//EN//XML", 
                   "http://www.python.org/topics/xml/dtds/xbel-1.0.dtd");
            xmlAddPrevSibling (doc->children, (xmlNodePtr) dtd);
      }

        /* In Galeon the root of the tree is just a folder. However,
         XBEL complicates the situation by have a special root element,
         XBEL. This is identical to FOLDER apart from the name of the
         tag... Thus this hack.  */
      if (GB_IS_FOLDER (set->root))
      {
            GbFolder *b = set->root;
            GbXBELMetadata *md;
            GbBookmark *child;

            xmlSetProp (newNode, "folded", b->expanded ? "no" : "yes");
            
            xml_node_update_text_child (newNode, NULL, "title", ((GbBookmark *) b)->name);
            md = xbel_save_common_data (newNode, (GbBookmark *) b);
            
            md->autofolder = GB_IS_AUTO_FOLDER (b);
            md->vfolder = GB_IS_V_FOLDER (b);
            md->toolbar = b->create_toolbar;
            md->default_f = b == set->default_folder;
            /* TODO: md->tb_style =  */
            
            xbel_save_galeon_metadata (newNode, md);
            gb_xbel_metadata_free (md);
            
            if (!gb_folder_is_autogenerated (b))
            {
                  for (child = b->child; child != NULL; child = child->next)
                  {
                        xbel_save_node (newNode, set, child);
                  }
            }
      }
      return doc;
}

/**
 * Add an item of unknown type to the tree. This delegates the
 * responsibility of adding nodes to the tree to other functions.
 */
static void
xbel_save_node (xmlNodePtr parent_node, GbBookmarkSet *set, GbBookmark *b)
{
        g_return_if_fail (parent_node != NULL);

      if (gb_bookmark_is_alias (b))
      {
            xbel_save_alias (parent_node, b);
      }
      else
      {
            if (GB_IS_SITE (b))
            {
                  xbel_save_site (parent_node, GB_SITE (b));
            } 
            else if (GB_IS_FOLDER (b))
            {
                  xbel_save_folder (parent_node, set, GB_FOLDER (b));
            }
            else if (GB_IS_SEPARATOR (b))
            {
                  if (!b->xbel_node)
                  {
                        xmlNodePtr newNode = xmlNewChild (parent_node, NULL, "separator", NULL);
                        gb_bookmark_set_xbel_node (b, newNode);
                  }
                  xbel_save_node_link_xml_node (b);
            }
            else if (GB_IS_ALIAS_PLACEHOLDER (b))
            {
                  xbel_save_alias_placeholder (parent_node, b);
            }
            else
            {
                  const gchar *s = g_type_name (G_TYPE_FROM_INSTANCE (b));
                  g_warning ("Detected unknown bookmark item type \"%s\"", s);
            }
      }
}

/**
 * Link the xmls node of the bookmark in the proper place
 */
static void
xbel_save_node_link_xml_node (GbBookmark *b)
{
      xmlNodePtr newNode;
      
      g_return_if_fail (GB_IS_BOOKMARK (b));
      g_return_if_fail (GB_IS_BOOKMARK (b->parent));
      g_return_if_fail (b->xbel_node);
      g_return_if_fail (GB_BOOKMARK (b->parent)->xbel_node);

      if (b->prev)
      {
            /* put it after it's left brother, which must be already correctly positioned */
            g_assert (b->prev->parent == b->parent);
            g_assert (b->prev->xbel_node->parent == GB_BOOKMARK (b->parent)->xbel_node);
            newNode = xmlAddNextSibling (b->prev->xbel_node, b->xbel_node);
      }
      else
      {
            xmlNodePtr prev;
            
            /* try to put it after title and info nodes, or at the beginning */

            prev = get_child (GB_BOOKMARK (b->parent)->xbel_node, "info");
            if (!prev)
            {
                  prev = get_child (GB_BOOKMARK (b->parent)->xbel_node, "title");
            }

            if (prev)
            {
                  if (prev->next != b->xbel_node)
                  {
                        newNode = xmlAddNextSibling (prev, b->xbel_node);
                  }
                  else
                  {
                        newNode = b->xbel_node;
                  }
                  g_assert (newNode->parent);
            }
            else
            {
                  if (GB_BOOKMARK (b->parent)->xbel_node->children)
                  {
                        if (GB_BOOKMARK (b->parent)->xbel_node->children != b->xbel_node)
                        {
                              newNode = xmlAddPrevSibling (GB_BOOKMARK (b->parent)->xbel_node->children, b->xbel_node);
                        }
                        else
                        {
                              newNode = b->xbel_node;
                        }
                        g_assert (newNode->parent);
                  }
                  else
                  {
                        xmlUnlinkNode (b->xbel_node);
                        newNode = xmlAddChild (GB_BOOKMARK (b->parent)->xbel_node, b->xbel_node);
                        g_assert (newNode->parent);
                  }
            }
      }
      
      gb_bookmark_set_xbel_node (b, newNode);

      g_assert (b->xbel_node->parent == GB_BOOKMARK (b->parent)->xbel_node);
}

static void
xbel_save_site (xmlNodePtr parent_node, GbSite *b)
{
        xmlNodePtr newNode;
        GbXBELMetadata *md;
        g_return_if_fail(parent_node != NULL);

        if (GB_BOOKMARK (b)->xbel_node)
      {
            newNode = GB_BOOKMARK (b)->xbel_node;
      }
      else
      {
            newNode = xmlNewChild (parent_node, NULL, "bookmark", NULL);
            gb_bookmark_set_xbel_node (GB_BOOKMARK (b), newNode);
      }

        xmlSetRawProp (newNode, "href", b->url);
        xml_node_update_text_child (newNode, NULL, "title", ((GbBookmark *) b)->name);

      md = xbel_save_common_data (newNode, (GbBookmark *) b);
      if (GB_IS_SMART_SITE (b))
      {
            md->smarturl = g_strdup (GB_SMART_SITE (b)->smarturl);
            md->entry_folded = GB_SMART_SITE (b)->folded;
            md->entry_width = gb_smart_site_get_entry_size (GB_SMART_SITE (b), 0);
            md->smart_site_history = gb_smart_site_get_history (GB_SMART_SITE (b), 0);
      }
      md->t_vis = b->time_visited;
      md->acc_mod = b->accel_mods;
      md->acc_key = b->accel_key;

      xbel_save_galeon_metadata (newNode, md);
      gb_xbel_metadata_free (md);

      xbel_save_node_link_xml_node (GB_BOOKMARK (b));
}

static void
xbel_save_folder (xmlNodePtr parent_node, GbBookmarkSet *set, GbFolder *b)
{
        xmlNodePtr newNode;
        GbXBELMetadata *md;
      g_return_if_fail (parent_node != NULL);
      
        if (GB_BOOKMARK (b)->xbel_node)
      {
            newNode = GB_BOOKMARK (b)->xbel_node;
      }
      else
      {
            newNode = xmlNewChild (parent_node, NULL, "folder", NULL);
            gb_bookmark_set_xbel_node (GB_BOOKMARK (b), newNode);
      }

        xmlSetProp (newNode, "folded", b->expanded ? "no" : "yes");
        xml_node_update_text_child (newNode, NULL, "title", ((GbBookmark *) b)->name);

      md = xbel_save_common_data (newNode, GB_BOOKMARK (b));
      
      md->toolbar = b->create_toolbar;
      md->default_f = b == set->default_folder;
      /* TODO: md->tb_style =  */

      md->autofolder = GB_IS_AUTO_FOLDER (b);
      
      md->vfolder = GB_IS_V_FOLDER (b);
      if (md->vfolder)
      {
            GbVFolder *vf = GB_V_FOLDER (b);
            int days;
            md->vfolder_search_text = g_strdup (gb_v_folder_get_search_text (vf));
            md->vfolder_search_options = gb_v_folder_get_search_options (vf);
            if (gb_v_folder_get_rencently_visited (vf, &days))
            {
                  md->vfolder_search_recently_visited = g_strdup_printf ("%d", days);
            }
            if (gb_v_folder_get_rencently_created (vf, &days))
            {
                  md->vfolder_search_recently_created = g_strdup_printf ("%d", days);
            }
      }
      if (md->autofolder)
      {
            md->autobm_search_text = g_strdup (gb_auto_folder_get_search_text (GB_AUTO_FOLDER (b)));
            md->autobm_search_options = gb_auto_folder_get_options (GB_AUTO_FOLDER (b));
      }

      xbel_save_galeon_metadata (newNode, md);
      gb_xbel_metadata_free (md);
      
        /* now iterate over the children of this folder */
      if (!gb_bookmark_is_alias (b) && !gb_folder_is_autogenerated (b))
      {
            GbBookmark *child;
            for (child = b->child; child != NULL; child = child->next)
                  xbel_save_node (newNode, set, child);
      }

      xbel_save_node_link_xml_node (GB_BOOKMARK (b));
}


static void
xbel_save_alias (xmlNodePtr parent_node, GbBookmark *b)
{
        xmlNodePtr newNode;
      GbXBELMetadata *md;
      GbBookmark *real = gb_bookmark_real_bookmark (b);

        g_return_if_fail (parent_node != NULL);

        if (GB_BOOKMARK (b)->xbel_node)
      {
            newNode = GB_BOOKMARK (b)->xbel_node;
      }
      else
      {
            newNode = xmlNewChild (parent_node, NULL, "alias", NULL);
            gb_bookmark_set_xbel_node (GB_BOOKMARK (b), newNode);
      }

      xmlSetProp (newNode, "ref", real->id);

      md = xbel_save_common_data (newNode, b);
      if (GB_IS_FOLDER (b)) 
      {
            md->toolbar = GB_FOLDER (b)->create_toolbar;
      }

        if (md->toolbar 
          || md->context
          || md->tb_style
          || md->t_add)
      {
            xbel_save_galeon_metadata (newNode, md);
      }
      gb_xbel_metadata_free (md);

      xbel_save_node_link_xml_node (GB_BOOKMARK (b));
}

static void
xbel_save_alias_placeholder (xmlNodePtr parent_node, GbBookmark *b)
{
        xmlNodePtr newNode;
      GbXBELMetadata *md;

        g_return_if_fail (parent_node != NULL);

        if (GB_BOOKMARK (b)->xbel_node)
      {
            newNode = GB_BOOKMARK (b)->xbel_node;
      }
      else
      {
            newNode = xmlNewChild (parent_node, NULL, "alias", NULL);
            gb_bookmark_set_xbel_node (GB_BOOKMARK (b), newNode);
      }

      if (GB_ALIAS_PLACEHOLDER (b)->alias_of_id)
      {
            xmlSetProp (newNode, "ref", GB_ALIAS_PLACEHOLDER (b)->alias_of_id);
      }

      md = xbel_save_common_data (newNode, b);
      if (GB_IS_FOLDER (b)) 
      {
            md->toolbar = GB_FOLDER (b)->create_toolbar;
      }

        if (md->toolbar 
          || md->context
          || md->tb_style
          || md->t_add)
      {
            xbel_save_galeon_metadata (newNode, md);
      }
      gb_xbel_metadata_free (md);

      xbel_save_node_link_xml_node (GB_BOOKMARK (b));
}

static GbXBELMetadata *
xbel_save_common_data (xmlNodePtr bookmark_node, GbBookmark *b)
{
      GbXBELMetadata *md = gb_xbel_metadata_new ();

      g_return_val_if_fail (b->xbel_node == bookmark_node, NULL);

      if (b->id != NULL && !gb_bookmark_is_alias (b))
      {
            xmlSetProp (bookmark_node, "id", b->id);
      }
      if (!gb_bookmark_is_alias (b) && sstrlen (b->notes) > 0) 
      {
            xml_node_update_text_child (bookmark_node, NULL, "desc", b->notes);
      }
      else
      {
            xml_node_remove_child (bookmark_node, "desc");
      }

      if (sstrlen (b->nick) > 0)
      {
            md->nick = g_strdup (b->nick);
      }
      if (sstrlen (b->pixmap_file) > 0)
      {
            md->pixmap = g_strdup (b->pixmap_file);
      }
      md->context = b->add_to_context_menu;
      md->t_add = b->time_added;
      md->t_mod = b->time_modified;
      
      if (b->set && b == GB_BOOKMARK (b->set->root))
      {
            md->version = b->set->file_format_version;
      }

      return md;
}

static xmlNodePtr
xbel_save_galeon_metadata_find_node (xmlNodePtr node)
{
        xmlNodePtr infoNode;
        xmlNodePtr metaNode;
      xmlNodePtr first_info = NULL;
      
        for (infoNode = node->children ; infoNode != NULL ; infoNode = infoNode->next)
      {
            if (!strcmp (infoNode->name, "info"))
            {
                  if (!first_info)
                  {
                        first_info = infoNode;
                  }

                  for (metaNode = infoNode->children ; metaNode != NULL ; metaNode = metaNode->next)
                  {
                        if (!strcmp (metaNode->name, "metadata"))
                        {
                              gchar *owner = xmlGetProp (metaNode, "owner");
                              if (owner && !strcmp (owner, XBEL_GALEON_OWNER))
                              {
                                    g_free (owner);
                                    return metaNode;
                              }
                              g_free (owner);
                        }
                  }
            }
      }

      infoNode = first_info ? first_info : xmlNewChild (node, NULL, "info", NULL);
      metaNode = xmlNewChild (infoNode, NULL, "metadata", NULL);
      xmlSetProp (metaNode, "owner", XBEL_GALEON_OWNER);
      
      return metaNode;
}

static void
xbel_save_galeon_metadata_remove_metadata (xmlNodePtr node)
{
        xmlNodePtr infoNode;
        xmlNodePtr metaNode;
      xmlNodePtr first_info = NULL;
      xmlNodePtr found = NULL;
      
        for (infoNode = node->children ; infoNode != NULL ; infoNode = infoNode->next)
      {
            if (!strcmp (infoNode->name, "info"))
            {
                  if (!first_info)
                  {
                        first_info = infoNode;
                  }

                  for (metaNode = infoNode->children ; metaNode != NULL ; metaNode = metaNode->next)
                  {
                        if (!strcmp (metaNode->name, "metadata"))
                        {
                              gchar *owner = xmlGetProp (metaNode, "owner");
                              if (owner && !strcmp (owner, XBEL_GALEON_OWNER))
                              {
                                    g_free (owner);
                                    found = metaNode;
                                    break;
                              }
                              g_free (owner);
                        }
                  }
            }
      }

      if (found)
      {
            xmlUnlinkNode (found);
            xmlFreeNode (found);
      }
}

static void 
xbel_save_galeon_metadata (xmlNodePtr node, GbXBELMetadata *md)
{
        g_return_if_fail (node != NULL);

        if (md->toolbar 
          || md->context
          || md->default_f
          || md->tb_style
          || md->t_add
          || md->t_mod
          || md->t_vis
          || md->acc_key
          || md->acc_mod
          || md->vfolder
          || md->autofolder
          || (sstrlen (md->nick) > 0) 
          || (sstrlen (md->pixmap) > 0) 
          || (sstrlen (md->smarturl) > 0)
          || md->version
          || md->smart_site_history
          || md->vfolder)
      {
            xmlNodePtr metaNode;
            GString *s;

            s = g_string_new ("");

                metaNode = xbel_save_galeon_metadata_find_node (node);
            g_assert (metaNode);

                /* export the nick name */
                if (sstrlen (md->nick) > 0)
            {
                        xml_node_update_text_child (metaNode, NULL, "nick", md->nick);
                }
            else
            {
                        xml_node_remove_child (metaNode, "nick");
                }

                /* pixmap path */
                if (sstrlen (md->pixmap) > 0) 
            {
                        xml_node_update_text_child (metaNode, NULL, "pixmap", md->pixmap);
                }
            else
            {
                        xml_node_remove_child (metaNode, "pixmap");
                }

                /* create a toolbar */
                if (md->toolbar) 
            {
                  xml_node_update_text_child (metaNode, NULL, "create_toolbar", "yes");
                }
            else
            {
                  xml_node_remove_child (metaNode, "create_toolbar");
                }

                /* create a context menu */
                if (md->context) 
            {
                  xml_node_update_text_child (metaNode, NULL, "create_context", "yes");
                }
            else
            {
                  xml_node_remove_child (metaNode, "create_context");
                }

            /* is this the default_folder? */
                if (md->default_f)
            {
                  xml_node_update_text_child (metaNode, NULL, "default_folder", "yes");
                }
            else
            {
                  xml_node_remove_child (metaNode, "default_folder");
                }

            if (md->tb_style != 0) 
            {
                  g_string_printf (s, "%d", md->tb_style);
                  xml_node_update_text_child (metaNode, NULL, "toolbar_style", s->str);
            }
            else
            {
                  xml_node_remove_child (metaNode, "toolbar_style");
            }

            if (md->t_vis != 0)
            {
                  g_string_printf (s, "%d", md->t_vis);
                  xml_node_update_text_child (metaNode, NULL, "time_visited", s->str);
            }
            else
            {
                  xml_node_remove_child (metaNode, "time_visited");
            }

            if (md->t_mod != 0)
            {
                  g_string_printf (s, "%d", md->t_mod);
                  xml_node_update_text_child (metaNode, NULL, "time_modified", s->str);
            }
            else
            {
                  xml_node_remove_child (metaNode, "time_modified");
            }

            if (md->t_add != 0) 
            {
                  g_string_printf (s, "%d", md->t_add);
                  xml_node_update_text_child (metaNode, NULL, "time_added", s->str);
            }
            else
            {
                  xml_node_remove_child (metaNode, "time_added");
            }

            if (md->acc_mod != 0) 
            {
                  g_string_printf (s, "%d", md->acc_mod);
                  xml_node_update_text_child (metaNode, NULL, "accel_mods", s->str);
            }
            else
            {
                  xml_node_remove_child (metaNode, "accel_mods");
            }

            if (md->acc_key != 0) 
            {
                  g_string_printf (s, "%d", md->acc_key);
                  xml_node_update_text_child (metaNode, NULL, "accel_key", s->str);
            }
            else
            {
                  xml_node_remove_child (metaNode, "accel_key");
            }

            if (sstrlen (md->smarturl) > 0) 
            {
                  gchar *smart_data = g_strdup_printf ("width=%d,folded=%d", md->entry_width, 
                                               md->entry_folded);
                  xml_node_update_text_child (metaNode, NULL, "smarturl",
                               md->smarturl);
                  xml_node_update_text_child (metaNode, NULL, "smart_site_data",
                               smart_data);
                  g_free (smart_data);

            }
            else
            {
                  xml_node_remove_child (metaNode, "smarturl");
                  xml_node_remove_child (metaNode, "smart_site_data");
            }

            if (md->smart_site_history)
            {
                  GString *shist = g_string_new (NULL);
                  GSList *li;
                  g_string_printf (shist, "0,%d,", g_slist_length (md->smart_site_history));
                  for (li = md->smart_site_history; li; li = li->next)
                  {
                        gchar *item = xbel_escape_smart_site_history_item (li->data);
                        g_string_append (shist, item);
                        if (li->next)
                        {
                              g_string_append (shist, ",");
                        }
                        g_free (item);
                  }
                  xml_node_update_text_child (metaNode, NULL, "smart_site_history",
                                        shist->str);
                  g_string_free (shist, TRUE);
            }
            else
            {
                  xml_node_remove_child (metaNode, "smart_site_history");
            }

            if (md->autofolder)
            {
                  xml_node_update_text_child (metaNode, NULL, "autofolder", "yes");
                  if (md->autobm_search_text)
                  {
                        xml_node_update_text_child (metaNode, NULL, "autobm_search_text", md->autobm_search_text);
                  }
                  else
                  {
                        xml_node_remove_child (metaNode, "autobm_search_text");
                  }

                  if (md->autobm_search_options)
                  {
                        xml_node_update_text_child (metaNode, NULL, "autobm_options", md->autobm_search_options);
                  }
                  else
                  {
                        xml_node_remove_child (metaNode, "autobm_options");
                  }
            }
            else
            {
                  xml_node_remove_child (metaNode, "autofolder");
                  xml_node_remove_child (metaNode, "autobm_options");
                  xml_node_remove_child (metaNode, "autobm_search_text");
            }

            if (md->vfolder)
            {
                  xml_node_update_text_child (metaNode, NULL, "vfolder", "yes");
                  if (md->vfolder_search_text)
                  {
                        xml_node_update_text_child (metaNode, NULL, "vfolder_search_text", md->vfolder_search_text);
                  }
                  else
                  {
                        xml_node_remove_child (metaNode, "vfolder_search_text");
                  }

                  if (md->vfolder_search_options)
                  {
                        xml_node_update_text_child (metaNode, NULL, "vfolder_search_options", md->vfolder_search_options);
                  }
                  else
                  {
                        xml_node_remove_child (metaNode, "vfolder_search_options");
                  }

                  if (md->vfolder_search_recently_created)
                  {
                        xml_node_update_text_child (metaNode, NULL, "vfolder_search_recently_created", md->vfolder_search_recently_created);
                  }
                  else
                  {
                        xml_node_remove_child (metaNode, "vfolder_search_recently_created");
                  }

                  if (md->vfolder_search_recently_visited)
                  {
                        xml_node_update_text_child (metaNode, NULL, "vfolder_search_recently_visited", md->vfolder_search_recently_visited);
                  }
                  else
                  {
                        xml_node_remove_child (metaNode, "vfolder_search_recently_visited");
                  }
            }
            else
            {
                  xml_node_remove_child (metaNode, "vfolder");
                  xml_node_remove_child (metaNode, "vfolder_search_text");
                  xml_node_remove_child (metaNode, "vfolder_search_options");
                  xml_node_remove_child (metaNode, "vfolder_search_recently_created");
                  xml_node_remove_child (metaNode, "vfolder_search_recently_visited");
            }

            if (md->version)
            {
                  switch (md->version)
                  {
                  case GB_FILE_FORMAT_VERSION_UNKNOWN:
                        /* shouldn't happen */
                        break;
                  case GB_FILE_FORMAT_VERSION_GALEON_1:
                        xml_node_update_text_child (metaNode, NULL, "galeon_xbel_version", "1");
                        break;
                  case GB_FILE_FORMAT_VERSION_GALEON_2:
                        xml_node_update_text_child (metaNode, NULL, "galeon_xbel_version", "2");
                        break;
                  }
            }

            g_string_free (s, TRUE);
        }
      else
      {
            xbel_save_galeon_metadata_remove_metadata (node);
      }
}

/*************************************/

/**
 * These probably don't belong here...
 */

/**
 * LibXML Utility Function: Iterate through all children of parent returning a
 * pointer to the first child which is called name.
 */
static xmlNodePtr
get_child (xmlNodePtr parent, const gchar *name)
{
        xmlNodePtr child;
        g_return_val_if_fail (parent != NULL, NULL);
        g_return_val_if_fail (sstrlen (name) > 0, NULL);
        for (child = parent->children ; child != NULL ; child = child->next)
            if (!strcmp (child->name, name))
                        return child;
        return NULL;
}

/**
 * LibXML Utility Function: Get the text value of the first child of parent 
 * with the name name.
 */
static gchar *
get_child_value_string (xmlNodePtr parent, const gchar *name)
{
        xmlNodePtr child = get_child (parent, name);
        if (child) {
            gchar *v = xmlNodeGetContent (child);
            gchar *ret = NULL;
            if (v) ret = g_strdup (v);
            xmlFree (v);
            return ret;
      } else return NULL;
}

static gboolean
get_child_value_boolean (xmlNodePtr parent, const gchar *name)
{
      gchar *sval = get_child_value_string (parent, name);
      gboolean ret = (sval 
                  && (!g_ascii_strcasecmp (sval, "yes")
                      || !g_ascii_strcasecmp (sval, "true")))
            ? TRUE : FALSE;
      g_free (sval);
      return ret;
}

static glong
get_child_value_long (xmlNodePtr parent, const gchar *name)
{
      gchar *sval = get_child_value_string (parent, name);
        glong ret = sval ? atol (sval) : 0;
      g_free (sval);
      return ret;
}

/* turns "," into ";" and ";" into "\;" */
static gchar *
xbel_escape_smart_site_history_item (const gchar *item)
{
      GString *s = g_string_new (item);
      gchar *ret;
      int i;
      
      for (i = 0; s->str[i]; ++i)
      {
            if (s->str[i] == ',')
            {
                  s->str[i] = ';';
            }
            else if (s->str[i] == ';')
            {
                  g_string_insert_c (s, i, '\\');
                  ++i;
            }
      }

      ret = s->str;
      g_string_free (s, FALSE);
      return ret;
}

/* reverses the previous function */
static gchar *
xbel_unescape_smart_site_history_item (const gchar *item)
{
      GString *s = g_string_new (item);
      gchar *ret;
      int i;
      
      for (i = 0; s->str[i]; ++i)
      {
            if (s->str[i] == ';')
            {
                  s->str[i] = ',';
            }
            else if (s->str[i] == '\\'
                  && s->str[i + 1] == ';')
            {
                  g_string_erase (s, i, 1);
            }
      }

      ret = s->str;
      g_string_free (s, FALSE);
      return ret;
}

/** 
 * Gets the dtd of a document. There must be a better way... 
 */
static xmlDtdPtr 
xml_doc_get_dtd (xmlDocPtr doc)
{
      xmlNodePtr n;
      for (n = doc->children; n; n = n->next)
      {
            if (n->type == XML_DTD_NODE)
            {
                  return (xmlDtdPtr) n;
            }
      }
      return NULL;
}

static xmlNodePtr 
xml_node_update_text_child (xmlNodePtr parent, xmlNsPtr ns,
                      const xmlChar *name, const xmlChar *content) 
{
        xmlNodePtr child = get_child (parent, name);
      if (child)
      {
            xmlNodePtr dt = xmlNewDocText (parent->doc, content);
            if (child->children)
            {
                  xmlNodePtr old = xmlReplaceNode (child->children, dt);
                  xmlFreeNode (old);
            }
            else
            {
                  xmlAddChild (child, dt);
            }
            return child;
      }
      else
      {
            return xmlNewTextChild (parent, ns, name, content);
      }
}

static void 
xml_node_remove_child (xmlNodePtr parent, const xmlChar *name)
{
        xmlNodePtr child = get_child (parent, name);
      if (child)
      {
            xmlUnlinkNode (child);
            xmlFreeNode (child);
      }
}


/*
 * Helper functions to preserve compatability with encoding of urls in the
 * bookmarks file, this is used because libxml1 was broken ! (see bug 56487)
 */

static gchar *
escape_xml_prop (const gchar *text)
{
        GString *out = g_string_new (NULL);
        guchar c;
        while ((c = *text))
        {
                if (c<0x20 || c>0x7F || c=='&' || c=='<' || c=='>' || c=='%' ||
                c=='"') /* Should anything else be escaped? */
                        g_string_append_printf (out, "%%%02X", c);
                else
                        g_string_append_c (out, c);
                ++text;
        }
        text = out->str;
        g_string_free (out, FALSE); /* don't free char data, caller must */
        return (gchar *)text;
}

/**
 * xmlSetRawProp: set a property in an XML file which has
 * value encoded.  This works around the newline-losingness and broken
 * entitiy handling of libxml1. (see bug #56487)
 */
static xmlAttrPtr
xmlSetRawProp (xmlNodePtr node, const xmlChar *name, const xmlChar *value)
{
      xmlAttrPtr attr;
      gchar *escaped = escape_xml_prop (value);
      attr = xmlSetProp (node, name, escaped);
      g_free (escaped);
      return attr;
}


static gchar *
unescape_hexed_string (const gchar *text)
{
      GString *out = g_string_new (NULL);
      const gchar *u, *cur = text;
      gchar decodebuf[3];

      decodebuf[2] = '\0';

      while ((u = strchr (cur, '%')))
      {
            decodebuf[0]=u[1];
            decodebuf[1]=u[2];
            g_string_append_printf (out, "%.*s%c", u-cur, cur, 
                              (char)strtol(decodebuf, NULL, 16));
            cur = u + 3;
      }

      if (cur && *cur)
      {
            /* attach rest of string */
            g_string_append (out, cur);
      }

      u = out->str;
      g_string_free (out, FALSE); /* don't free char data, caller must */

      return (gchar *)u;
}

/**
 * xmlGetRawProp: get a property in an XML file which has been encoded This
 * works around the newline-losingness and broken entitiy handling of
 * libxml1. (see bug #56487)
 */
static xmlChar *
xmlGetRawProp (xmlNodePtr node, const xmlChar *name)
{
      xmlChar *value;
      gchar *unescaped;
      value = xmlGetProp (node, name);
      if (value == NULL)
      {
            return NULL;
      }
      unescaped = unescape_hexed_string (value);
      xmlFree (value);
      return unescaped;
}

Generated by  Doxygen 1.6.0   Back to index