2
Copyright (C) 2007 Christian Dywan <christian@twotoasts.de>
4
This library is free software; you can redistribute it and/or
5
modify it under the terms of the GNU Lesser General Public
6
License as published by the Free Software Foundation; either
7
version 2.1 of the License, or (at your option) any later version.
9
See the file COPYING for the full license text.
13
* This is an implementation of XBEL based on Glib and libXML2.
16
* - XbelItem is the only opaque public data structure.
17
* - The interface should be intuitive and familiar to Glib users.
18
* - There should be no public exposure of libXML2 specific code.
19
* - Bookmarks should actually be easily exchangeable between programs.
22
* - Support info > metadata, alias, added, modified, visited
23
* - Compatibility: The Nokia 770 *requires* metadata and folder
24
* - Compatibility: Kazehakase's bookmarks
25
* - Compatibility: Epiphany's bookmarks
27
* - Export and import to other formats
34
#include <libxml/parser.h>
35
#include <libxml/tree.h>
37
// Private: Create a new item of a certain type
38
static XbelItem* xbel_item_new(XbelItemKind kind)
40
XbelItem* item = g_new(XbelItem, 1);
43
if(kind == XBEL_ITEM_FOLDER)
48
if(kind != XBEL_ITEM_SEPARATOR)
53
if(kind == XBEL_ITEM_BOOKMARK)
54
item->href = g_strdup("");
61
* Create a new empty bookmark.
63
* Return value: a newly allocated bookmark
65
XbelItem* xbel_bookmark_new(void)
67
return xbel_item_new(XBEL_ITEM_BOOKMARK);
70
static XbelItem* xbel_bookmark_from_xmlNodePtr(xmlNodePtr cur)
72
g_return_val_if_fail(cur != NULL, NULL);
73
XbelItem* bookmark = xbel_bookmark_new();
74
xmlChar* key = xmlGetProp(cur, (xmlChar*)"href");
75
xbel_bookmark_set_href(bookmark, (gchar*)key);
76
cur = cur->xmlChildrenNode;
79
if(!xmlStrcmp(cur->name, (const xmlChar*)"title"))
81
xmlChar* key = xmlNodeGetContent(cur);
82
bookmark->title = (gchar*)g_strstrip((gchar*)key);
84
else if(!xmlStrcmp(cur->name, (const xmlChar*)"desc"))
86
xmlChar* key = xmlNodeGetContent(cur);
87
bookmark->desc = (gchar*)g_strstrip((gchar*)key);
97
* Create a new separator.
99
* The returned item must be freed eventually.
101
* Return value: a newly allocated separator.
103
XbelItem* xbel_separator_new(void)
105
return xbel_item_new(XBEL_ITEM_SEPARATOR);
111
* Create a new empty folder.
113
* The returned item must be freed eventually.
115
* Return value: a newly allocated folder.
117
XbelItem* xbel_folder_new(void)
119
return xbel_item_new(XBEL_ITEM_FOLDER);
122
// Private: Create a folder from an xmlNodePtr
123
static XbelItem* xbel_folder_from_xmlNodePtr(xmlNodePtr cur)
125
g_return_val_if_fail(cur != NULL, NULL);
126
XbelItem* folder = xbel_folder_new();
127
xmlChar* key = xmlGetProp(cur, (xmlChar*)"folded");
130
if(!g_ascii_strncasecmp((gchar*)key, "yes", 3))
131
folder->folded = TRUE;
132
else if(!g_ascii_strncasecmp((gchar*)key, "no", 2))
133
folder->folded = FALSE;
135
g_warning("XBEL: Unknown value for folded.");
138
cur = cur->xmlChildrenNode;
141
if(!xmlStrcmp(cur->name, (const xmlChar*)"title"))
143
xmlChar* key = xmlNodeGetContent(cur);
144
folder->title = (gchar*)g_strstrip((gchar*)key);
146
else if(!xmlStrcmp(cur->name, (const xmlChar*)"desc"))
148
xmlChar* key = xmlNodeGetContent(cur);
149
folder->desc = (gchar*)g_strstrip((gchar*)key);
151
else if(!xmlStrcmp(cur->name, (const xmlChar*)"folder"))
153
XbelItem* item = xbel_folder_from_xmlNodePtr(cur);
154
item->parent = (struct XbelItem*)folder;
155
folder->items = g_list_prepend(folder->items, item);
157
else if(!xmlStrcmp(cur->name, (const xmlChar*)"bookmark"))
159
XbelItem* item = xbel_bookmark_from_xmlNodePtr(cur);
160
item->parent = (struct XbelItem*)folder;
161
folder->items = g_list_prepend(folder->items, item);
163
else if(!xmlStrcmp(cur->name, (const xmlChar*)"separator"))
165
XbelItem* item = xbel_separator_new();
166
item->parent = (struct XbelItem*)folder;
167
folder->items = g_list_prepend(folder->items, item);
171
// Prepending and reversing is faster than appending
172
folder->items = g_list_reverse(folder->items);
176
// Private: Loads the contents from an xmlNodePtr into a folder.
177
static gboolean xbel_folder_from_xmlDocPtr(XbelItem* folder, xmlDocPtr doc)
179
g_return_val_if_fail(xbel_folder_is_empty(folder), FALSE);
180
g_return_val_if_fail(doc != NULL, FALSE);
181
xmlNodePtr cur = xmlDocGetRootElement(doc);
182
xmlChar* version = xmlGetProp(cur, (xmlChar*)"version");
183
if(xmlStrcmp(version, (xmlChar*)"1.0"))
184
g_warning("XBEL version is not 1.0.");
186
folder->title = (gchar*)xmlGetProp(cur, (xmlChar*)"title");
187
folder->desc = (gchar*)xmlGetProp(cur, (xmlChar*)"desc");
188
if((cur = xmlDocGetRootElement(doc)) == NULL)
193
if(xmlStrcmp(cur->name, (const xmlChar*)"xbel"))
195
// Wrong document kind
198
cur = cur->xmlChildrenNode;
201
XbelItem* item = NULL;
202
if(!xmlStrcmp(cur->name, (const xmlChar*)"folder"))
203
item = xbel_folder_from_xmlNodePtr(cur);
204
else if(!xmlStrcmp(cur->name, (const xmlChar*)"bookmark"))
205
item = xbel_bookmark_from_xmlNodePtr(cur);
206
else if(!xmlStrcmp(cur->name, (const xmlChar*)"separator"))
207
item = xbel_separator_new();
208
/*else if(!xmlStrcmp(cur->name, (const xmlChar*)"info"))
209
item = xbel_parse_info(xbel, cur);*/
212
item->parent = (struct XbelItem*)folder;
213
folder->items = g_list_prepend(folder->items, item);
217
// Prepending and reversing is faster than appending
218
folder->items = g_list_reverse(folder->items);
224
* @item: a valid item
226
* Free an XbelItem. If @item is a folder all of its children will also
227
* be freed automatically.
229
* The item must not be contained in a folder or attempting to free it will fail.
231
void xbel_item_free(XbelItem* item)
233
g_return_if_fail(item);
234
g_return_if_fail(!xbel_item_get_parent(item));
235
if(xbel_item_is_folder(item))
237
guint n = xbel_folder_get_n_items(item);
239
for(i = 0; i < n; i++)
241
XbelItem* _item = xbel_folder_get_nth_item(item, i);
242
_item->parent = NULL;
243
xbel_item_free(_item);
245
g_list_free(item->items);
247
if(item->kind != XBEL_ITEM_SEPARATOR)
252
if(item->kind == XBEL_ITEM_BOOKMARK)
259
* @item: the item to copy
263
* The returned item must be freed eventually.
265
* Return value: a copy of @item
267
XbelItem* xbel_item_copy(XbelItem* item)
269
g_return_val_if_fail(item, NULL);
270
XbelItem* copy = xbel_folder_new();
271
if(xbel_item_is_folder(item))
273
guint n = xbel_folder_get_n_items(item);
275
for(i = 0; i < n; i++)
277
XbelItem* _item = xbel_folder_get_nth_item(item, i);
278
xbel_folder_append_item(copy, xbel_item_copy(_item));
281
if(item->kind != XBEL_ITEM_SEPARATOR)
283
xbel_item_set_title(copy, item->title);
284
xbel_item_set_desc(copy, item->desc);
286
if(item->kind == XBEL_ITEM_BOOKMARK)
287
xbel_bookmark_set_href(copy, item->href);
291
GType xbel_item_get_type()
293
static GType type = 0;
295
type = g_pointer_type_register_static("xbel_item");
300
* xbel_folder_append_item:
302
* @item: the item to append
304
* Append the given item to a folder.
306
* The item is actually moved and must not be contained in another folder.
309
void xbel_folder_append_item(XbelItem* folder, XbelItem* item)
311
g_return_if_fail(!xbel_item_get_parent(item));
312
g_return_if_fail(xbel_item_is_folder(folder));
313
item->parent = (struct XbelItem*)folder;
314
folder->items = g_list_append(folder->items, item);
318
* xbel_folder_prepend_item:
320
* @item: the item to prepend
322
* Prepend the given item to a folder.
324
* The item is actually moved and must not be contained in another folder.
327
void xbel_folder_prepend_item(XbelItem* folder, XbelItem* item)
329
g_return_if_fail(!xbel_item_get_parent(item));
330
g_return_if_fail(xbel_item_is_folder(folder));
331
item->parent = (struct XbelItem*)folder;
332
folder->items = g_list_prepend(folder->items, item);
336
* xbel_folder_remove_item:
338
* @item: the item to remove
340
* Remove the given @item from a @folder.
342
void xbel_folder_remove_item(XbelItem* folder, XbelItem* item)
344
g_return_if_fail(item);
345
g_return_if_fail(xbel_item_get_parent(folder) != item);
347
// Fortunately we know that items are unique
348
folder->items = g_list_remove(folder->items, item);
352
* xbel_folder_get_n_items:
355
* Retrieve the number of items contained in the given @folder.
357
* Return value: number of items
359
guint xbel_folder_get_n_items(XbelItem* folder)
361
g_return_val_if_fail(xbel_item_is_folder(folder), 0);
362
return g_list_length(folder->items);
366
* xbel_folder_get_nth_item:
368
* @n: the index of an item
370
* Retrieve an item contained in the given @folder by its index.
372
* Return value: the item at the given index or %NULL
374
XbelItem* xbel_folder_get_nth_item(XbelItem* folder, guint n)
376
g_return_val_if_fail(xbel_item_is_folder(folder), NULL);
377
return (XbelItem*)g_list_nth_data(folder->items, n);
381
* xbel_folder_is_empty:
384
* Determines wether or not a folder contains no items. This is significantly
385
* faster than xbel_folder_get_n_items for this particular purpose.
387
* Return value: Wether the given @folder is folded.
389
gboolean xbel_folder_is_empty(XbelItem* folder)
391
return !xbel_folder_get_nth_item(folder, 0);
395
* xbel_folder_get_folded:
398
* Determines wether or not a folder is folded. If a folder is not folded
399
* it should not be exposed in a user interface.
401
* New folders are folded by default.
403
* Return value: Wether the given @folder is folded.
405
gboolean xbel_folder_get_folded(XbelItem* folder)
407
g_return_val_if_fail(xbel_item_is_folder(folder), TRUE);
408
return folder->folded;
412
* xbel_item_get_kind:
415
* Determines the kind of an item.
417
* Return value: The kind of the given @item.
419
XbelItemKind xbel_item_get_kind(XbelItem* item)
425
* xbel_item_get_parent:
426
* @item: A valid item.
428
* Retrieves the parent folder of an item.
430
* Return value: The parent folder of the given @item or %NULL.
432
XbelItem* xbel_item_get_parent(XbelItem* item)
434
g_return_val_if_fail(item, NULL);
435
return (XbelItem*)item->parent;
439
* xbel_item_get_title:
440
* @item: A valid item.
442
* Retrieves the title of an item.
444
* Return value: The title of the given @item or %NULL.
446
G_CONST_RETURN gchar* xbel_item_get_title(XbelItem* item)
448
g_return_val_if_fail(!xbel_item_is_separator(item), NULL);
453
* xbel_item_get_desc:
454
* @item: A valid item.
456
* Retrieves the description of an item.
458
* Return value: The description of the @item or %NULL.
460
G_CONST_RETURN gchar* xbel_item_get_desc(XbelItem* item)
462
g_return_val_if_fail(!xbel_item_is_separator(item), NULL);
467
* xbel_bookmark_get_href:
468
* @bookmark: A bookmark.
470
* Retrieves the uri of a bookmark. The value is guaranteed to not be %NULL.
472
* Return value: The uri of the @bookmark.
474
G_CONST_RETURN gchar* xbel_bookmark_get_href(XbelItem* bookmark)
476
g_return_val_if_fail(xbel_item_is_bookmark(bookmark), NULL);
477
return bookmark->href;
481
* xbel_item_is_bookmark:
482
* @item: A valid item.
484
* Determines wether or not an item is a bookmark.
486
* Return value: %TRUE if the @item is a bookmark.
488
gboolean xbel_item_is_bookmark(XbelItem* item)
490
g_return_val_if_fail(item, FALSE);
491
return item->kind == XBEL_ITEM_BOOKMARK;
495
* xbel_item_is_separator:
496
* @item: A valid item.
498
* Determines wether or not an item is a separator.
500
* Return value: %TRUE if the @item is a separator.
502
gboolean xbel_item_is_separator(XbelItem* item)
504
g_return_val_if_fail(item, FALSE);
505
return item->kind == XBEL_ITEM_SEPARATOR;
509
* xbel_item_is_folder:
510
* @item: A valid item.
512
* Determines wether or not an item is a folder.
514
* Return value: %TRUE if the item is a folder.
516
gboolean xbel_item_is_folder(XbelItem* item)
518
g_return_val_if_fail(item, FALSE);
519
return item->kind == XBEL_ITEM_FOLDER;
523
* xbel_folder_set_folded:
525
* @folded: TRUE if the folder is folded.
527
* Sets the foldedness of the @folder.
529
void xbel_folder_set_folded(XbelItem* folder, gboolean folded)
531
g_return_if_fail(xbel_item_is_folder(folder));
532
folder->folded = folded;
536
* xbel_item_set_title:
537
* @item: A valid item.
538
* @title: A string to use for the title.
540
* Sets the title of the @item.
542
void xbel_item_set_title(XbelItem* item, const gchar* title)
544
g_return_if_fail(!xbel_item_is_separator(item));
546
item->title = g_strdup(title);
550
* xbel_item_set_desc:
551
* @item: A valid item.
552
* @title: A string to use for the description.
554
* Sets the description of the @item.
556
void xbel_item_set_desc(XbelItem* item, const gchar* desc)
558
g_return_if_fail(!xbel_item_is_separator(item));
560
item->desc = g_strdup(desc);
564
* xbel_bookmark_set_href:
565
* @bookmark: A bookmark.
566
* @href: A string containing a valid uri.
568
* Sets the uri of the bookmark.
570
* The uri must not be %NULL.
572
* This uri is not currently validated in any way. This may change in the future.
574
void xbel_bookmark_set_href(XbelItem* bookmark, const gchar* href)
576
g_return_if_fail(xbel_item_is_bookmark(bookmark));
577
g_return_if_fail(href);
578
g_free(bookmark->href);
579
bookmark->href = g_strdup(href);
582
gboolean xbel_folder_from_data(XbelItem* folder, const gchar* data, GError** error)
584
g_return_val_if_fail(xbel_folder_is_empty(folder), FALSE);
585
g_return_val_if_fail(data, FALSE);
587
if((doc = xmlParseMemory(data, strlen(data))) == NULL)
589
// No valid xml or broken encoding
590
*error = g_error_new(XBEL_ERROR, XBEL_ERROR_READ
591
, "Malformed document.");
594
if(!xbel_folder_from_xmlDocPtr(folder, doc))
598
*error = g_error_new(XBEL_ERROR, XBEL_ERROR_READ
599
, "Malformed document.");
607
* xbel_folder_from_file:
608
* @folder: An empty folder.
609
* @file: A relative path to a file.
610
* @error: return location for a GError or %NULL
612
* Tries to load @file.
614
* Return value: %TRUE on success or %FALSE when an error occured.
616
gboolean xbel_folder_from_file(XbelItem* folder, const gchar* file, GError** error)
618
g_return_val_if_fail(xbel_folder_is_empty(folder), FALSE);
619
g_return_val_if_fail(file, FALSE);
620
if(!g_file_test(file, G_FILE_TEST_EXISTS))
622
// File doesn't exist
623
*error = g_error_new(G_FILE_ERROR, G_FILE_ERROR_NOENT
624
, "File not found.");
628
if((doc = xmlParseFile(file)) == NULL)
630
// No valid xml or broken encoding
631
*error = g_error_new(XBEL_ERROR, XBEL_ERROR_READ
632
, "Malformed document.");
635
if(!xbel_folder_from_xmlDocPtr(folder, doc))
639
*error = g_error_new(XBEL_ERROR, XBEL_ERROR_READ
640
, "Malformed document.");
648
* xbel_folder_from_data_dirs:
649
* @folder: An empty folder.
650
* @file: A relative path to a file.
651
* @full_path: return location for the full path of the file or %NULL
652
* @error: return location for a GError or %NULL
654
* Tries to load @file from the user data dir or any of the system data dirs.
656
* Return value: %TRUE on success or %FALSE when an error occured.
658
gboolean xbel_folder_from_data_dirs(XbelItem* folder, const gchar* file
659
, gchar** full_path, GError** error)
661
g_return_val_if_fail(xbel_folder_is_empty(folder), FALSE);
662
g_return_val_if_fail(file, FALSE);
663
// FIXME: Essentially unimplemented
665
*error = g_error_new(XBEL_ERROR, XBEL_ERROR_READ
666
, "Malformed document.");
670
static gchar* xbel_xml_element(const gchar* name, const gchar* value)
674
gchar* valueEscaped = g_markup_escape_text(value, -1);
675
gchar* XML = g_strdup_printf("<%s>%s</%s>\n", name, valueEscaped, name);
676
g_free(valueEscaped);
680
static gchar* xbel_item_to_data(XbelItem* item)
682
g_return_val_if_fail(item, NULL);
684
switch(xbel_item_get_kind(item))
686
case XBEL_ITEM_FOLDER:
688
GString* _XML = g_string_new(NULL);
689
guint n = xbel_folder_get_n_items(item);
691
for(i = 0; i < n; i++)
693
XbelItem* _item = xbel_folder_get_nth_item(item, i);
694
gchar* itemXML = xbel_item_to_data(_item);
695
g_string_append(_XML, itemXML);
698
gchar* folded = item->folded ? NULL : g_strdup_printf(" folded=\"no\"");
699
gchar* title = xbel_xml_element("title", item->title);
700
gchar* desc = xbel_xml_element("desc", item->desc);
701
XML = g_strdup_printf("<folder%s>\n%s%s%s</folder>\n"
702
, folded ? folded : ""
705
, g_string_free(_XML, FALSE));
711
case XBEL_ITEM_BOOKMARK:
713
gchar* hrefEscaped = g_markup_escape_text(item->href, -1);
714
gchar* href = g_strdup_printf(" href=\"%s\"", hrefEscaped);
716
gchar* title = xbel_xml_element("title", item->title);
717
gchar* desc = xbel_xml_element("desc", item->desc);
718
XML = g_strdup_printf("<bookmark%s>\n%s%s%s</bookmark>\n"
728
case XBEL_ITEM_SEPARATOR:
729
XML = g_strdup("<separator/>\n");
732
g_warning("XBEL: Unknown item kind");
738
* xbel_folder_to_data:
740
* @length: return location for the length of the created string or %NULL
741
* @error: return location for a GError or %NULL
743
* Retrieve the contents of @folder as a string.
745
* Return value: %TRUE on success or %FALSE when an error occured.
747
gchar* xbel_folder_to_data(XbelItem* folder, gsize* length, GError** error)
749
g_return_val_if_fail(xbel_item_is_folder(folder), FALSE);
750
// FIXME: length is never filled
751
GString* innerXML = g_string_new(NULL);
752
guint n = xbel_folder_get_n_items(folder);
754
for(i = 0; i < n; i++)
756
gchar* sItem = xbel_item_to_data(xbel_folder_get_nth_item(folder, i));
757
g_string_append(innerXML, sItem);
760
gchar* title = xbel_xml_element("title", folder->title);
761
gchar* desc = xbel_xml_element("desc", folder->desc);
763
outerXML = g_strdup_printf("%s%s<xbel version=\"1.0\">\n%s%s%s</xbel>\n"
764
, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
765
, "<!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"
768
, g_string_free(innerXML, FALSE));
775
* xbel_folder_to_file:
777
* @file: The destination filename.
778
* @error: return location for a GError or %NULL
780
* Write the contents of @folder to the specified file, creating it if necessary.
782
* Return value: %TRUE on success or %FALSE when an error occured.
784
gboolean xbel_folder_to_file(XbelItem* folder, const gchar* file, GError** error)
786
g_return_val_if_fail(file, FALSE);
788
if(!(data = xbel_folder_to_data(folder, NULL, error)))
791
if(!(fp = fopen(file, "w")))
793
*error = g_error_new(G_FILE_ERROR, G_FILE_ERROR_ACCES
794
, "Writing failed.");