midori/midori/midori-array.c
Oliver Hanraths f840f0508c Bookmarks: Database structure improved
The new format uses bookmarks_v2.db.

We require sqlite 3.6.19 now due to foreign keys.

Fixes: https://bugs.launchpad.net/midori/+bug/836707
2012-06-20 01:19:30 +02:00

1162 lines
36 KiB
C

/*
Copyright (C) 2007-2009 Christian Dywan <christian@twotoasts.de>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
See the file COPYING for the full license text.
*/
#include <katze/katze.h>
#if HAVE_CONFIG_H
#include <config.h>
#endif
#include <glib/gstdio.h>
#include <glib/gi18n.h>
#include <sqlite3.h>
#if HAVE_LIBXML
#include <libxml/parser.h>
#include <libxml/tree.h>
#endif
#if HAVE_UNISTD_H
#include <unistd.h>
#endif
#define katze_str_equal(str1, str2) !strcmp (str1, str2)
static void
katze_xbel_parse_info (KatzeItem* item,
xmlNodePtr cur);
static gchar*
katze_item_metadata_to_xbel (KatzeItem* item);
#if HAVE_LIBXML
static KatzeItem*
katze_item_from_xmlNodePtr (xmlNodePtr cur)
{
KatzeItem* item;
item = katze_item_new ();
item->uri = (gchar*)xmlGetProp (cur, (xmlChar*)"href");
cur = cur->xmlChildrenNode;
while (cur)
{
if (katze_str_equal ((gchar*)cur->name, "title"))
item->name = g_strstrip ((gchar*)xmlNodeGetContent (cur));
else if (katze_str_equal ((gchar*)cur->name, "desc"))
item->text = g_strstrip ((gchar*)xmlNodeGetContent (cur));
else if (katze_str_equal ((gchar*)cur->name, "info"))
katze_xbel_parse_info (item, cur);
cur = cur->next;
}
return item;
}
/* Create an array from an xmlNodePtr */
static KatzeArray*
katze_array_from_xmlNodePtr (xmlNodePtr cur)
{
KatzeArray* array;
xmlChar* key;
KatzeItem* item;
array = katze_array_new (KATZE_TYPE_ARRAY);
key = xmlGetProp (cur, (xmlChar*)"folded");
if (key)
{
/* if (!g_ascii_strncasecmp ((gchar*)key, "yes", 3))
folder->folded = TRUE;
else if (!g_ascii_strncasecmp ((gchar*)key, "no", 2))
folder->folded = FALSE;
else
g_warning ("XBEL: Unknown value for folded."); */
xmlFree (key);
}
cur = cur->xmlChildrenNode;
while (cur)
{
if (katze_str_equal ((gchar*)cur->name, "title"))
((KatzeItem*)array)->name = g_strstrip ((gchar*)xmlNodeGetContent (cur));
else if (katze_str_equal ((gchar*)cur->name, "desc"))
((KatzeItem*)array)->text = g_strstrip ((gchar*)xmlNodeGetContent (cur));
else if (katze_str_equal ((gchar*)cur->name, "info"))
katze_xbel_parse_info ((KatzeItem*)array, cur);
else if (katze_str_equal ((gchar*)cur->name, "folder"))
{
item = (KatzeItem*)katze_array_from_xmlNodePtr (cur);
katze_array_add_item (array, item);
}
else if (katze_str_equal ((gchar*)cur->name, "bookmark"))
{
item = katze_item_from_xmlNodePtr (cur);
katze_array_add_item (array, item);
}
else if (katze_str_equal ((gchar*)cur->name, "separator"))
{
item = katze_item_new ();
katze_array_add_item (array, item);
}
cur = cur->next;
}
return array;
}
static void
katze_xbel_parse_info (KatzeItem* item,
xmlNodePtr cur)
{
cur = cur->xmlChildrenNode;
while (cur)
{
if (katze_str_equal ((gchar*)cur->name, "metadata"))
{
xmlChar* owner = xmlGetProp (cur, (xmlChar*)"owner");
if (owner)
g_strstrip ((gchar*)owner);
else
/* Albeit required, "owner" is not set by MicroB */
owner = (xmlChar*)NULL;
/* FIXME: Save metadata from unknown owners */
if (!owner || katze_str_equal ((gchar*)owner, "http://www.twotoasts.de"))
{
xmlAttrPtr properties = cur->properties;
xmlNodePtr children = cur->children;
while (properties)
{
xmlChar* value;
if (katze_str_equal ((gchar*)properties->name, "owner"))
{
properties = properties->next;
continue;
}
value = xmlGetProp (cur, properties->name);
if (properties->ns && properties->ns->prefix)
{
gchar* ns_value = g_strdup_printf ("%s:%s",
properties->ns->prefix, properties->name);
katze_item_set_meta_string (item,
(gchar*)ns_value, (gchar*)value);
g_free (ns_value);
}
else
katze_item_set_meta_string (item,
(gchar*)properties->name, (gchar*)value);
xmlFree (value);
properties = properties->next;
}
while (children)
{
xmlNodePtr grand_children = children->children;
while (grand_children)
{
xmlChar* value = grand_children->content;
gchar* ns_value;
if (!owner)
ns_value = g_strdup_printf (":%s", children->name);
else if (katze_str_equal ((gchar*)owner, "http://www.twotoasts.de"))
ns_value = g_strdup_printf ("midori:%s", children->name);
else /* FIXME: Save metadata from unknown owners */
ns_value = g_strdup_printf (":%s", children->name);
katze_item_set_meta_string (item, ns_value, (gchar*)value);
g_free (ns_value);
grand_children = grand_children->next;
}
children = children->next;
}
}
xmlFree (owner);
}
else if (!katze_str_equal ((gchar*)cur->name, "text"))
g_critical ("Unexpected element <%s> in <metadata>.", cur->name);
cur = cur->next;
}
}
/* Loads the contents from an xmlNodePtr into an array. */
static gboolean
katze_array_from_xmlDocPtr (KatzeArray* array,
xmlDocPtr doc)
{
xmlNodePtr cur;
KatzeItem* item;
cur = xmlDocGetRootElement (doc);
if ((cur = xmlDocGetRootElement (doc)) == NULL)
{
/* Empty document */
return FALSE;
}
if (katze_str_equal ((gchar*)cur->name, "xbel"))
{
/* XBEL 1.0 */
gchar* value;
value = (gchar*)xmlGetProp (cur, (xmlChar*)"version");
if (!value || !katze_str_equal (value, "1.0"))
g_warning ("XBEL version is not 1.0.");
g_free (value);
value = (gchar*)xmlGetProp (cur, (xmlChar*)"title");
katze_item_set_name (KATZE_ITEM (array), value);
g_free (value);
value = (gchar*)xmlGetProp (cur, (xmlChar*)"desc");
katze_item_set_text (KATZE_ITEM (array), value);
g_free (value);
}
else if (katze_str_equal ((gchar*)cur->name, "RDF"))
{
/* Minimal RSS 1.0 support, enough to read bookmarks */
cur = cur->xmlChildrenNode;
while (cur)
{
item = NULL;
if (katze_str_equal ((gchar*)cur->name, "item"))
{
xmlNodePtr cur_item;
item = katze_item_new ();
cur_item = cur->xmlChildrenNode;
while (cur_item)
{
if (katze_str_equal ((gchar*)cur_item->name, "title"))
item->name = g_strstrip ((gchar*)xmlNodeGetContent (cur_item));
else if (katze_str_equal ((gchar*)cur_item->name, "link"))
item->uri = g_strstrip ((gchar*)xmlNodeGetContent (cur_item));
else if (katze_str_equal ((gchar*)cur_item->name, "subject"))
{
gchar* value = g_strstrip ((gchar*)xmlNodeGetContent (cur_item));
/* FIXME: Create a folder according to the tag */
g_free (value);
}
cur_item = cur_item->next;
}
}
else if (katze_str_equal ((gchar*)cur->name, "channel"))
/* Ignored */;
else if (!katze_str_equal ((gchar*)cur->name, "text"))
g_warning ("Unexpected attribute: %s", (gchar*)cur->name);
if (item)
katze_array_add_item (array, item);
cur = cur->next;
}
return TRUE;
}
else
{
/* Wrong document kind */
return FALSE;
}
cur = cur->xmlChildrenNode;
while (cur)
{
item = NULL;
if (katze_str_equal ((gchar*)cur->name, "folder"))
item = (KatzeItem*)katze_array_from_xmlNodePtr (cur);
else if (katze_str_equal ((gchar*)cur->name, "bookmark"))
item = katze_item_from_xmlNodePtr (cur);
else if (katze_str_equal ((gchar*)cur->name, "separator"))
item = katze_item_new ();
else if (katze_str_equal ((gchar*)cur->name, "info"))
katze_xbel_parse_info (KATZE_ITEM (array), cur);
else if (katze_str_equal ((gchar*)cur->name, "title"))
{
xmlNodePtr node = cur->xmlChildrenNode;
katze_item_set_name (KATZE_ITEM (array), (gchar*)node->content);
}
else if (katze_str_equal ((gchar*)cur->name, "desc"))
{
xmlNodePtr node = cur->xmlChildrenNode;
katze_item_set_text (KATZE_ITEM (array), (gchar*)node->content);
}
else if (!katze_str_equal ((gchar*)cur->name, "text"))
g_warning ("Unexpected attribute: %s", (gchar*)cur->name);
if (item)
katze_array_add_item (array, item);
cur = cur->next;
}
return TRUE;
}
static gchar*
katze_unescape_html (const gchar* text)
{
gchar* amp = g_strstr_len (text, -1, "&");
if (amp && *amp)
{
if (!strncmp (amp, "&quot;", 6) || !strncmp (amp, "&amp;", 5)
|| !strncmp (amp, "&lt;", 4) || !strncmp (amp, "&gt;", 4)
|| !strncmp (amp, "&apos;", 6))
{
guint i = 0;
gchar** parts = g_strsplit_set (text, "&;", -1);
GString *unescaped = g_string_new (NULL);
while (parts[i])
{
if (katze_str_equal ("quot", parts[i]))
g_string_append (unescaped, "\"");
else if (katze_str_equal ("amp", parts[i]))
g_string_append (unescaped, "&");
else if (katze_str_equal ("lt", parts[i]))
g_string_append (unescaped, "<");
else if (katze_str_equal ("gt", parts[i]))
g_string_append (unescaped, ">");
else if (katze_str_equal ("apos", parts[i]))
g_string_append (unescaped, "'");
else
g_string_append (unescaped, parts[i]);
i++;
}
g_strfreev (parts);
return g_string_free (unescaped, FALSE);
}
}
return g_strdup (text);
}
static gboolean
katze_array_from_netscape_file (KatzeArray* array,
const gchar* filename)
{
gchar* line = NULL;
GIOChannel* channel = g_io_channel_new_file (filename, "r", 0);
KatzeArray* folder = array;
KatzeItem* item = NULL;
if (!channel)
return FALSE;
while (g_io_channel_read_line (channel, &line, NULL, NULL, NULL)
== G_IO_STATUS_NORMAL)
{
g_strstrip (line);
/* parse lines with bookmarks data only, skip the rest */
if (!strncmp (line, "<D", 2) || !strncmp (line, "</D", 3))
{
gchar** element = g_strsplit_set (line, "<>", -1);
/* current item */
if (katze_str_equal (element[1], "DT"))
{
/* item is bookmark */
if (!strncmp (element[3], "A HREF", 6))
{
gchar** parts = g_strsplit (line, "\"", -1);
item = katze_item_new ();
katze_array_add_item (folder, item);
item->name = katze_unescape_html (element[4]);
item->uri = katze_unescape_html (parts[1]);
g_strfreev (parts);
}
/* item is folder */
if (!strncmp (element[3], "H3", 2))
{
item = (KatzeItem*)katze_array_new (KATZE_TYPE_ARRAY);
katze_array_add_item (folder, item);
folder = (KatzeArray*)item;
item->name = katze_unescape_html (element[4]);
}
}
/* item description */
if (item && katze_str_equal (element[1], "DD"))
{
if (element[2])
item->text = katze_unescape_html (element[2]);
item = NULL;
}
/* end of current folder, level-up */
if (katze_str_equal (element[1], "/DL"))
{
if (folder != array)
folder = katze_item_get_parent ((KatzeItem*)folder);
continue;
}
g_strfreev (element);
}
continue;
}
g_io_channel_shutdown (channel, FALSE, 0);
g_io_channel_unref (channel);
return TRUE;
}
static gboolean
katze_array_from_opera_file (KatzeArray* array,
const gchar* filename)
{
gchar* line = NULL;
GIOChannel* channel = g_io_channel_new_file (filename, "r", 0);
KatzeArray* folder = array;
KatzeItem* item = NULL;
if (!channel)
return FALSE;
while (g_io_channel_read_line (channel, &line, NULL, NULL, NULL)
== G_IO_STATUS_NORMAL)
{
g_strstrip (line);
/* skip file header */
if (katze_str_equal (line, "Options: encoding = utf8, version=3")
|| katze_str_equal (line, "Opera Hotlist version 2.0"))
{
item = NULL;
continue;
}
if (line[0] == '\0')
{
item = NULL;
continue;
}
else if (line[0] == '-')
{
item = NULL;
if (folder != array)
folder = katze_item_get_parent ((KatzeItem*)folder);
else
g_warning ("A level-up although we are at the top level");
continue;
}
if (line[0] == '#')
{
const gchar* element = &line[1];
if (!g_ascii_strncasecmp (element, "FOLDER", 6))
{
item = (KatzeItem*)katze_array_new (KATZE_TYPE_ARRAY);
katze_array_add_item (folder, item);
folder = (KatzeArray*)item;
}
else if (!g_ascii_strncasecmp (element, "URL", 3))
{
item = katze_item_new ();
katze_array_add_item (folder, item);
}
else
g_warning ("Unexpected element: %s", element);
}
else if (item)
{
gchar** parts = g_strsplit (line, "=", 2);
if (parts && parts[0] && parts[1])
{
if (katze_str_equal (parts[0], "NAME"))
item->name = g_strdup (parts[1]);
else if (katze_str_equal (parts[0], "URL"))
item->uri = g_strdup (parts[1]);
else if (katze_str_equal (parts[0], "DESCRIPTION"))
item->text = g_strdup (parts[1]);
else if (katze_str_equal (parts[0], "CREATED"))
item->added = g_ascii_strtoull (parts[1], NULL, 10);
/* FIXME: Implement visited time
else if (katze_str_equal (parts[0], "VISITED"))
item->visited = g_ascii_strtoull (parts[1], NULL, 10); */
else if (katze_str_equal (parts[0], "ON PERSONALBAR"))
katze_item_set_meta_integer (item, "toolbar",
katze_str_equal (parts[1], "YES") ? 1 : -1);
/* FIXME: Implement websites as panels
else if (katze_str_equal (parts[0], "IN PANEL"))
; */
}
else
g_warning ("Broken property: %s", line);
g_strfreev (parts);
}
else
g_warning ("Unexpected property outside of element: %s", line);
}
g_io_channel_shutdown (channel, FALSE, 0);
g_io_channel_unref (channel);
return TRUE;
}
/**
* midori_array_from_file:
* @array: a #KatzeArray
* @filename: a filename to load from
* @format: "xbel", "opera", or %NULL
* @error: a #GError or %NULL
*
* Loads the contents of a file in the specified format.
*
* Since 0.2.2 @format can be %NULL to indicate that the
* file should be loaded if it's any supported format.
*
* Return value: %TRUE on success, %FALSE otherwise
*
* Since: 0.1.6
**/
gboolean
midori_array_from_file (KatzeArray* array,
const gchar* filename,
const gchar* format,
GError** error)
{
g_return_val_if_fail (katze_array_is_a (array, KATZE_TYPE_ITEM), FALSE);
g_return_val_if_fail (filename != NULL, FALSE);
g_return_val_if_fail (!error || !*error, FALSE);
if (g_access (filename, F_OK) != 0)
{
/* File doesn't exist */
if (error)
*error = g_error_new_literal (G_FILE_ERROR, G_FILE_ERROR_NOENT,
_("File not found."));
return FALSE;
}
if (!format)
format = "";
/* netscape html */
if (!*format && g_str_has_suffix (filename, ".html"))
{
FILE* file;
if ((file = g_fopen (filename, "r")))
{
gchar line[50];
while (fgets (line, 50, file))
{
g_strstrip (line);
if (katze_str_equal (line, "<!DOCTYPE NETSCAPE-Bookmark-file-1>"))
{
if (!katze_array_from_netscape_file (array, filename))
{
/* Parsing failed */
fclose (file);
if (error)
*error = g_error_new_literal (G_FILE_ERROR,
G_FILE_ERROR_FAILED, _("Malformed document."));
return FALSE;
}
return TRUE;
}
else
break;
}
fclose (file);
}
}
/* Opera6 */
if (katze_str_equal (format, "opera")
|| (!*format && g_str_has_suffix (filename, ".adr")))
{
FILE* file;
if ((file = g_fopen (filename, "r")))
{
guint verify;
gchar line[50];
verify = 0;
while (fgets (line, 50, file))
{
g_strstrip (line);
if (verify == 0 && katze_str_equal (line, "Opera Hotlist version 2.0"))
verify++;
else if (verify == 1
&& katze_str_equal (line, "Options: encoding = utf8, version=3"))
verify++;
else if (verify == 2)
{
if (!katze_array_from_opera_file (array, filename))
{
/* Parsing failed */
fclose (file);
if (error)
*error = g_error_new_literal (G_FILE_ERROR,
G_FILE_ERROR_FAILED, _("Malformed document."));
return FALSE;
}
return TRUE;
}
else
break;
}
fclose (file);
}
}
/* XBEL */
if (katze_str_equal (format, "xbel")
|| !*format)
{
xmlDocPtr doc;
if ((doc = xmlParseFile (filename)) == NULL)
{
/* No valid xml or broken encoding */
if (error)
*error = g_error_new_literal (G_FILE_ERROR, G_FILE_ERROR_FAILED,
_("Malformed document."));
return FALSE;
}
if (!katze_array_from_xmlDocPtr (array, doc))
{
/* Parsing failed */
xmlFreeDoc (doc);
if (error)
*error = g_error_new_literal (G_FILE_ERROR, G_FILE_ERROR_FAILED,
_("Malformed document."));
return FALSE;
}
xmlFreeDoc (doc);
return TRUE;
}
if (error)
*error = g_error_new_literal (G_FILE_ERROR, G_FILE_ERROR_FAILED,
_("Unrecognized bookmark format."));
return FALSE;
}
#endif
/* Inspired by append_escaped_text() from gmarkup.c in Glib.
The main difference is that we filter out control characters. */
static void
string_append_escaped (GString *str,
const gchar *text)
{
gssize length;
const gchar* p;
const gchar* end;
gunichar c;
length = strlen (text);
p = text;
end = text + length;
while (p != end)
{
const gchar *next;
next = g_utf8_next_char (p);
switch (*p)
{
case '&':
g_string_append (str, "&amp;");
break;
case '<':
g_string_append (str, "&lt;");
break;
case '>':
g_string_append (str, "&gt;");
break;
case '\'':
g_string_append (str, "&apos;");
break;
case '"':
g_string_append (str, "&quot;");
break;
default:
c = g_utf8_get_char (p);
if (g_unichar_iscntrl (c))
g_string_append_c (str, ' ');
else if ((0x1 <= c && c <= 0x8)
|| (0xb <= c && c <= 0xc)
|| (0xe <= c && c <= 0x1f)
|| (0x7f <= c && c <= 0x84)
|| (0x86 <= c && c <= 0x9f))
g_string_append_printf (str, "&#x%x;", c);
else
g_string_append_len (str, p, next - p);
break;
}
p = next;
}
}
static void
string_append_xml_element (GString* string,
const gchar* name,
const gchar* value)
{
if (value)
{
g_string_append_printf (string, "<%s>", name);
string_append_escaped (string, value);
g_string_append_printf (string, "</%s>\n", name);
}
}
static void
string_append_item (GString* string,
KatzeItem* item)
{
gchar* metadata;
g_return_if_fail (KATZE_IS_ITEM (item));
metadata = katze_item_metadata_to_xbel (item);
if (KATZE_IS_ARRAY (item))
{
KatzeItem* _item;
KatzeArray* array = KATZE_ARRAY (item);
GList* list;
g_string_append (string, "<folder>\n");
/* FIXME: " folded=\"no\" */
string_append_xml_element (string, "title", katze_item_get_name (item));
string_append_xml_element (string, "desc", katze_item_get_text (item));
KATZE_ARRAY_FOREACH_ITEM_L (_item, array, list)
string_append_item (string, _item);
g_string_append (string, metadata);
g_string_append (string, "</folder>\n");
g_list_free (list);
}
else if (katze_item_get_uri (item))
{
g_string_append (string, "<bookmark href=\"");
string_append_escaped (string, katze_item_get_uri (item));
g_string_append (string, "\">\n");
string_append_xml_element (string, "title", katze_item_get_name (item));
string_append_xml_element (string, "desc", katze_item_get_text (item));
g_string_append (string, metadata);
g_string_append (string, "</bookmark>\n");
}
else
g_string_append (string, "<separator/>\n");
g_free (metadata);
}
static void
string_append_netscape_item (GString* string,
KatzeItem* item)
{
g_return_if_fail (KATZE_IS_ITEM (item));
if (KATZE_IS_ARRAY (item))
{
KatzeItem* _item;
KatzeArray* array = KATZE_ARRAY (item);
GList* list;
g_string_append (string, "\t<DT><H3 FOLDED ADD_DATE=\"\">");
string_append_escaped (string, katze_item_get_name (item));
g_string_append (string, "</H3>\n");
g_string_append (string, "\t<DL><P>\n");
KATZE_ARRAY_FOREACH_ITEM_L (_item, array, list)
{
g_string_append (string, "\t");
string_append_netscape_item (string, _item);
}
g_string_append (string, "\t</DL><P>\n");
g_list_free (list);
}
else if (katze_item_get_uri (item))
{
g_string_append (string, "\t<DT><A HREF=\"");
string_append_escaped (string, katze_item_get_uri (item));
g_string_append (string, "\" ADD_DATE=\"\" LAST_VISIT=\"\" LAST_MODIFIED=\"\">");
string_append_escaped (string, katze_item_get_name (item));
g_string_append (string, "</A>\n");
if (item->text && g_strcmp0 (item->text, ""))
{
g_string_append (string, "\t<DD>");
string_append_escaped (string, katze_item_get_text (item));
g_string_append (string, "\n");
}
}
}
static gchar*
katze_item_metadata_to_xbel (KatzeItem* item)
{
GList* keys = katze_item_get_meta_keys (item);
GString* markup;
GString* markdown;
/* FIXME: Allow specifying an alternative default namespace */
/* FIXME: Support foreign namespaces with their own URI */
gchar* namespace = NULL;
const gchar* namespace_uri;
gsize i;
const gchar* key;
const gchar* value;
if (!keys)
return NULL;
markup = g_string_new ("<info>\n<metadata");
markdown = g_string_new (NULL);
i = 0;
while ((key = g_list_nth_data (keys, i++)))
if ((value = katze_item_get_meta_string (item, key)))
{
namespace = strchr (key, ':');
if (key[0] == ':') /* MicroB uses un-namespaced children */
{
key = &key[1];
g_string_append_printf (markdown, "<%s>", key);
string_append_escaped (markdown, value);
g_string_append_printf (markdown, "</%s>\n", key);
}
else if (namespace)
{
g_string_append_printf (markup, " %s=\"", key);
string_append_escaped (markup, value);
g_string_append_c (markup, '\"');
}
else
{
g_string_append_printf (markup, " midori:%s=\"", key);
string_append_escaped (markup, value);
g_string_append_c (markup, '\"');
}
}
if (!namespace)
{
namespace_uri = "http://www.twotoasts.de";
g_string_append_printf (markup, " owner=\"%s\"", namespace_uri);
}
if (markdown->len)
g_string_append_printf (markup, ">\n%s</metadata>\n</info>\n", markdown->str);
else
g_string_append_printf (markup, "/>\n</info>\n");
g_string_free (markdown, TRUE);
return g_string_free (markup, FALSE);
}
static gchar*
katze_array_to_xbel (KatzeArray* array,
GError** error)
{
gchar* metadata = katze_item_metadata_to_xbel (KATZE_ITEM (array));
KatzeItem* item;
GList* list;
GString* markup = g_string_new (
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
"<!DOCTYPE xbel PUBLIC \"+//IDN python.org//DTD "
"XML Bookmark Exchange Language 1.0//EN//XML\" "
"\"http://www.python.org/topics/xml/dtds/xbel-1.0.dtd\">\n"
"<xbel version=\"1.0\""
" xmlns:midori=\"http://www.twotoasts.de\""
">\n");
string_append_xml_element (markup, "title", katze_item_get_name (KATZE_ITEM (array)));
string_append_xml_element (markup, "desc", katze_item_get_text (KATZE_ITEM (array)));
g_string_append (markup, metadata ? metadata : "");
KATZE_ARRAY_FOREACH_ITEM_L (item, array, list)
string_append_item (markup, item);
g_string_append (markup, "</xbel>\n");
g_free (metadata);
g_list_free (list);
return g_string_free (markup, FALSE);
}
static gchar*
katze_array_to_netscape_html (KatzeArray* array,
GError** error)
{
KatzeItem* item;
GList* list;
/* The header, including the text, is the same as used in other browsers,
see http://msdn.microsoft.com/en-us/library/aa753582(v=vs.85).aspx */
GString* markup = g_string_new (
"<!DOCTYPE NETSCAPE-Bookmark-file-1>\n"
"<!--This is an automatically generated file.\n"
"It will be read and overwritten.\n"
"Do Not Edit! -->\n"
"<META HTTP-EQUIV=\"Content-Type\" CONTENT=\"text/html; charset=UTF-8\">\n"
"<Title>Bookmarks</Title>\n"
"<H1>Bookmarks</H1>\n"
"\n");
g_string_append (markup, "<DL><P>\n");
KATZE_ARRAY_FOREACH_ITEM_L (item, array, list)
string_append_netscape_item (markup, item);
g_string_append (markup, "</DL><P>\n");
g_list_free (list);
return g_string_free (markup, FALSE);
}
static gboolean
midori_array_to_file_format (KatzeArray* array,
const gchar* filename,
const gchar* format,
GError** error)
{
gchar* data;
FILE* fp;
if (!g_strcmp0 (format, "xbel"))
data = katze_array_to_xbel (array, error);
else if (!g_strcmp0 (format, "netscape"))
data = katze_array_to_netscape_html (array, error);
else
return FALSE;
if (!(fp = fopen (filename, "w")))
{
*error = g_error_new_literal (G_FILE_ERROR, G_FILE_ERROR_ACCES,
_("Writing failed."));
return FALSE;
}
fputs (data, fp);
fclose (fp);
g_free (data);
return TRUE;
}
/**
* midori_array_to_file:
* @array: a #KatzeArray
* @filename: a filename to load from
* @format: the desired format
* @error: a #GError or %NULL
*
* Saves the contents to a file in the specified format.
*
* Return value: %TRUE on success, %FALSE otherwise
*
* Since: 0.1.6
**/
gboolean
midori_array_to_file (KatzeArray* array,
const gchar* filename,
const gchar* format,
GError** error)
{
g_return_val_if_fail (katze_array_is_a (array, KATZE_TYPE_ITEM), FALSE);
g_return_val_if_fail (filename, FALSE);
g_return_val_if_fail (!error || !*error, FALSE);
if (!g_strcmp0 (format, "xbel")
|| !g_strcmp0 (format, "netscape"))
return midori_array_to_file_format (array, filename, format, error);
g_critical ("Cannot write KatzeArray to unknown format '%s'.", format);
return FALSE;
}
static void
katze_item_set_value_from_column (sqlite3_stmt* stmt,
gint column,
KatzeItem* item)
{
const gchar* name;
name = sqlite3_column_name (stmt, column);
g_return_if_fail (name != NULL);
if (g_str_equal (name, "uri"))
{
const unsigned char* uri;
uri = sqlite3_column_text (stmt, column);
if (uri && uri[0] && uri[0] != '(')
item->uri = g_strdup ((gchar*)uri);
}
else if (g_str_equal (name, "title") || g_str_equal (name, "name"))
{
const unsigned char* title;
title = sqlite3_column_text (stmt, column);
item->name = g_strdup ((gchar*)title);
}
else if (g_str_equal (name, "date"))
{
gint date;
date = sqlite3_column_int64 (stmt, column);
item->added = date;
}
else if (g_str_equal (name, "day") || g_str_equal (name, "app")
|| g_str_equal (name, "toolbar") || g_str_equal (name, "id")
|| g_str_equal (name, "parentid") || g_str_equal (name, "seq")
|| g_str_equal (name, "pos_panel") || g_str_equal (name, "pos_bar"))
{
gint value;
value = sqlite3_column_int64 (stmt, column);
katze_item_set_meta_integer (item, name, value);
}
else if (g_str_equal (name, "desc"))
{
const unsigned char* text;
text = sqlite3_column_text (stmt, column);
item->text = g_strdup ((gchar*)text);
}
else if (g_str_equal (name, "sql"))
{
const unsigned char* sql;
sql = sqlite3_column_text (stmt, column);
katze_item_set_meta_string (item, name, (gchar*)sql);
}
else
g_warn_if_reached ();
}
/**
* midori_array_from_statement:
* @stmt: prepared statement
*
* Stores the result in a #KatzeArray.
*
* Return value: a #KatzeArray on success, %NULL otherwise
*
* Since: 0.2.7
**/
KatzeArray*
katze_array_from_statement (sqlite3_stmt* stmt)
{
KatzeArray *array;
gint result;
gint cols;
array = katze_array_new (KATZE_TYPE_ITEM);
cols = sqlite3_column_count (stmt);
while ((result = sqlite3_step (stmt)) == SQLITE_ROW)
{
gint i;
KatzeItem* item;
item = katze_item_new ();
for (i = 0; i < cols; i++)
katze_item_set_value_from_column (stmt, i, item);
katze_array_add_item (array, item);
}
sqlite3_clear_bindings (stmt);
sqlite3_reset (stmt);
return array;
}
/**
* midori_array_from_sqlite:
* @db: opened database handler
* @sqlcmd: SQL query
*
* Stores the result in a #KatzeArray.
*
* Return value: a #KatzeArray on success, %NULL otherwise
*
* Since: 0.2.7
**/
KatzeArray*
katze_array_from_sqlite (sqlite3* db,
const gchar* sqlcmd)
{
sqlite3_stmt* stmt;
gint result;
result = sqlite3_prepare_v2 (db, sqlcmd, -1, &stmt, NULL);
if (result != SQLITE_OK)
return NULL;
return katze_array_from_statement (stmt);
}
/**
* midori_array_query_recursive:
* @array: the main bookmark array
* @fields: comma separated list of fields
* @condition: condition, like "folder = '%q'"
* @value: a value to be inserted if @condition contains %q
* @recursive: if %TRUE include children
*
* Stores the result in a #KatzeArray.
*
* Return value: a #KatzeArray on success, %NULL otherwise
*
* Since: 0.4.4
**/
KatzeArray*
midori_array_query_recursive (KatzeArray* bookmarks,
const gchar* fields,
const gchar* condition,
const gchar* value,
gboolean recursive)
{
sqlite3* db;
gchar* sqlcmd;
char* sqlcmd_value;
KatzeArray* array;
KatzeItem* item;
GList* list;
g_return_val_if_fail (KATZE_IS_ARRAY (bookmarks), NULL);
g_return_val_if_fail (fields, NULL);
g_return_val_if_fail (condition, NULL);
db = g_object_get_data (G_OBJECT (bookmarks), "db");
if (db == NULL)
return NULL;
sqlcmd = g_strdup_printf ("SELECT %s FROM bookmarks WHERE %s "
"ORDER BY (uri='') ASC, title DESC", fields, condition);
if (strstr (condition, "%q"))
{
sqlcmd_value = sqlite3_mprintf (sqlcmd, value ? value : "");
array = katze_array_from_sqlite (db, sqlcmd_value);
sqlite3_free (sqlcmd_value);
}
else
array = katze_array_from_sqlite (db, sqlcmd);
g_free (sqlcmd);
if (!recursive)
return array;
KATZE_ARRAY_FOREACH_ITEM_L (item, array, list)
{
if (KATZE_ITEM_IS_FOLDER (item))
{
gchar* parentid = g_strdup_printf ("%" G_GINT64_FORMAT,
katze_item_get_meta_integer (item, "id"));
KatzeArray* subarray = midori_array_query_recursive (bookmarks,
fields, "parentid=%q", parentid, TRUE);
katze_item_set_name (KATZE_ITEM (subarray), item->name);
katze_array_add_item (array, subarray);
g_free (parentid);
}
}
g_list_free (list);
return array;
}
/**
* midori_array_query:
* @array: the main bookmark array
* @fields: comma separated list of fields
* @condition: condition, like "folder = '%q'"
* @value: a value to be inserted if @condition contains %q
*
* Stores the result in a #KatzeArray.
*
* Return value: a #KatzeArray on success, %NULL otherwise
*
* Since: 0.4.3
*
* Deprecated: 0.4.4: Use midori_array_query_recursive() instead.
**/
KatzeArray*
midori_array_query (KatzeArray* bookmarks,
const gchar* fields,
const gchar* condition,
const gchar* value)
{
return midori_array_query_recursive (bookmarks, fields, condition, value, FALSE);
}