~tuxator/midori/speeddial-refresh

« back to all changes in this revision

Viewing changes to src/xbel.c

  • Committer: Christian Dywan
  • Date: 2007-12-16 22:20:24 UTC
  • Revision ID: git-v1:3bbd273a4f9e85a1d8380cb0924c875683fa3ad1
Tags: v0.0.14
Initial commit

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
 Copyright (C) 2007 Christian Dywan <christian@twotoasts.de>
 
3
 
 
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.
 
8
 
 
9
 See the file COPYING for the full license text.
 
10
*/
 
11
 
 
12
/**
 
13
 * This is an implementation of XBEL based on Glib and libXML2.
 
14
 *
 
15
 * Design Goals:
 
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.
 
20
 *
 
21
 * TODO:
 
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
 
26
 *  - XML Indentation
 
27
 *  - Export and import to other formats
 
28
 **/
 
29
 
 
30
#include "xbel.h"
 
31
 
 
32
#include <stdio.h>
 
33
#include <string.h>
 
34
#include <libxml/parser.h>
 
35
#include <libxml/tree.h>
 
36
 
 
37
// Private: Create a new item of a certain type
 
38
static XbelItem* xbel_item_new(XbelItemKind kind)
 
39
{
 
40
    XbelItem* item = g_new(XbelItem, 1);
 
41
    item->parent = NULL;
 
42
    item->kind = kind;
 
43
    if(kind == XBEL_ITEM_FOLDER)
 
44
    {
 
45
        item->items = NULL;
 
46
        item->folded = TRUE;
 
47
    }
 
48
    if(kind != XBEL_ITEM_SEPARATOR)
 
49
    {
 
50
        item->title = NULL;
 
51
        item->desc  = NULL;
 
52
    }
 
53
    if(kind == XBEL_ITEM_BOOKMARK)
 
54
        item->href = g_strdup("");
 
55
    return item;
 
56
}
 
57
 
 
58
/**
 
59
 * xbel_bookmark_new:
 
60
 *
 
61
 * Create a new empty bookmark.
 
62
 *
 
63
 * Return value: a newly allocated bookmark
 
64
 **/
 
65
XbelItem* xbel_bookmark_new(void)
 
66
{
 
67
    return xbel_item_new(XBEL_ITEM_BOOKMARK);
 
68
}
 
69
 
 
70
static XbelItem* xbel_bookmark_from_xmlNodePtr(xmlNodePtr cur)
 
71
{
 
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;
 
77
    while(cur != NULL)
 
78
    {
 
79
        if(!xmlStrcmp(cur->name, (const xmlChar*)"title"))
 
80
        {
 
81
         xmlChar* key = xmlNodeGetContent(cur);
 
82
         bookmark->title = (gchar*)g_strstrip((gchar*)key);
 
83
        }
 
84
        else if(!xmlStrcmp(cur->name, (const xmlChar*)"desc"))
 
85
        {
 
86
         xmlChar* key = xmlNodeGetContent(cur);
 
87
         bookmark->desc = (gchar*)g_strstrip((gchar*)key);
 
88
        }
 
89
        cur = cur->next;
 
90
    }
 
91
    return bookmark;
 
92
}
 
93
 
 
94
/**
 
95
 * xbel_separator_new:
 
96
 *
 
97
 * Create a new separator.
 
98
 *
 
99
 * The returned item must be freed eventually.
 
100
 *
 
101
 * Return value: a newly allocated separator.
 
102
 **/
 
103
XbelItem* xbel_separator_new(void)
 
104
{
 
105
    return xbel_item_new(XBEL_ITEM_SEPARATOR);
 
106
}
 
107
 
 
108
/**
 
109
 * xbel_folder_new:
 
110
 *
 
111
 * Create a new empty folder.
 
112
 *
 
113
 * The returned item must be freed eventually.
 
114
 *
 
115
 * Return value: a newly allocated folder.
 
116
 **/
 
117
XbelItem* xbel_folder_new(void)
 
118
{
 
119
    return xbel_item_new(XBEL_ITEM_FOLDER);
 
120
}
 
121
 
 
122
// Private: Create a folder from an xmlNodePtr
 
123
static XbelItem* xbel_folder_from_xmlNodePtr(xmlNodePtr cur)
 
124
{
 
125
    g_return_val_if_fail(cur != NULL, NULL);
 
126
    XbelItem* folder = xbel_folder_new();
 
127
    xmlChar* key = xmlGetProp(cur, (xmlChar*)"folded");
 
128
    if(key)
 
129
    {
 
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;
 
134
        else
 
135
            g_warning("XBEL: Unknown value for folded.");
 
136
        xmlFree(key);
 
137
    }
 
138
    cur = cur->xmlChildrenNode;
 
139
    while(cur)
 
140
    {
 
141
        if(!xmlStrcmp(cur->name, (const xmlChar*)"title"))
 
142
        {
 
143
            xmlChar* key = xmlNodeGetContent(cur);
 
144
            folder->title = (gchar*)g_strstrip((gchar*)key);
 
145
        }
 
146
        else if(!xmlStrcmp(cur->name, (const xmlChar*)"desc"))
 
147
        {
 
148
            xmlChar* key = xmlNodeGetContent(cur);
 
149
            folder->desc = (gchar*)g_strstrip((gchar*)key);
 
150
        }
 
151
        else if(!xmlStrcmp(cur->name, (const xmlChar*)"folder"))
 
152
        {
 
153
            XbelItem* item = xbel_folder_from_xmlNodePtr(cur);
 
154
            item->parent = (struct XbelItem*)folder;
 
155
            folder->items = g_list_prepend(folder->items, item);
 
156
        }
 
157
     else if(!xmlStrcmp(cur->name, (const xmlChar*)"bookmark"))
 
158
     {
 
159
         XbelItem* item = xbel_bookmark_from_xmlNodePtr(cur);
 
160
         item->parent = (struct XbelItem*)folder;
 
161
         folder->items = g_list_prepend(folder->items, item);
 
162
     }
 
163
     else if(!xmlStrcmp(cur->name, (const xmlChar*)"separator"))
 
164
     {
 
165
         XbelItem* item = xbel_separator_new();
 
166
         item->parent = (struct XbelItem*)folder;
 
167
         folder->items = g_list_prepend(folder->items, item);
 
168
     }
 
169
        cur = cur->next;
 
170
    }
 
171
    // Prepending and reversing is faster than appending
 
172
    folder->items = g_list_reverse(folder->items);
 
173
    return folder;
 
174
}
 
175
 
 
176
// Private: Loads the contents from an xmlNodePtr into a folder.
 
177
static gboolean xbel_folder_from_xmlDocPtr(XbelItem* folder, xmlDocPtr doc)
 
178
{
 
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.");
 
185
    xmlFree(version);
 
186
    folder->title = (gchar*)xmlGetProp(cur, (xmlChar*)"title");
 
187
    folder->desc = (gchar*)xmlGetProp(cur, (xmlChar*)"desc");
 
188
    if((cur = xmlDocGetRootElement(doc)) == NULL)
 
189
    {
 
190
        // Empty document
 
191
        return FALSE;
 
192
    }
 
193
    if(xmlStrcmp(cur->name, (const xmlChar*)"xbel"))
 
194
    {
 
195
        // Wrong document kind
 
196
        return FALSE;
 
197
    }
 
198
    cur = cur->xmlChildrenNode;
 
199
    while(cur != NULL)
 
200
    {
 
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);*/
 
210
        if(item != NULL)
 
211
        {
 
212
            item->parent = (struct XbelItem*)folder;
 
213
            folder->items = g_list_prepend(folder->items, item);
 
214
        }
 
215
        cur = cur->next;
 
216
    }
 
217
    // Prepending and reversing is faster than appending
 
218
    folder->items = g_list_reverse(folder->items);
 
219
    return TRUE;
 
220
}
 
221
 
 
222
/**
 
223
 * xbel_item_free:
 
224
 * @item: a valid item
 
225
 *
 
226
 * Free an XbelItem. If @item is a folder all of its children will also
 
227
 *  be freed automatically.
 
228
 *
 
229
 * The item must not be contained in a folder or attempting to free it will fail.
 
230
 **/
 
231
void xbel_item_free(XbelItem* item)
 
232
{
 
233
    g_return_if_fail(item);
 
234
    g_return_if_fail(!xbel_item_get_parent(item));
 
235
    if(xbel_item_is_folder(item))
 
236
    {
 
237
        guint n = xbel_folder_get_n_items(item);
 
238
        guint i;
 
239
        for(i = 0; i < n; i++)
 
240
        {
 
241
            XbelItem* _item = xbel_folder_get_nth_item(item, i);
 
242
            _item->parent = NULL;
 
243
            xbel_item_free(_item);
 
244
        }
 
245
        g_list_free(item->items);
 
246
    }
 
247
    if(item->kind != XBEL_ITEM_SEPARATOR)
 
248
    {
 
249
        g_free(item->title);
 
250
        g_free(item->desc);
 
251
    }
 
252
    if(item->kind == XBEL_ITEM_BOOKMARK)
 
253
        g_free(item->href);
 
254
    g_free(item);
 
255
}
 
256
 
 
257
/**
 
258
 * xbel_item_copy:
 
259
 * @item: the item to copy
 
260
 *
 
261
 * Copy an XbelItem.
 
262
 *
 
263
 * The returned item must be freed eventually.
 
264
 *
 
265
 * Return value: a copy of @item
 
266
 **/
 
267
XbelItem* xbel_item_copy(XbelItem* item)
 
268
{
 
269
    g_return_val_if_fail(item, NULL);
 
270
    XbelItem* copy = xbel_folder_new();
 
271
    if(xbel_item_is_folder(item))
 
272
    {
 
273
        guint n = xbel_folder_get_n_items(item);
 
274
        guint i;
 
275
        for(i = 0; i < n; i++)
 
276
        {
 
277
            XbelItem* _item = xbel_folder_get_nth_item(item, i);
 
278
            xbel_folder_append_item(copy, xbel_item_copy(_item));
 
279
        }
 
280
    }
 
281
    if(item->kind != XBEL_ITEM_SEPARATOR)
 
282
    {
 
283
        xbel_item_set_title(copy, item->title);
 
284
        xbel_item_set_desc(copy, item->desc);
 
285
    }
 
286
    if(item->kind == XBEL_ITEM_BOOKMARK)
 
287
        xbel_bookmark_set_href(copy, item->href);
 
288
    return copy;
 
289
}
 
290
 
 
291
GType xbel_item_get_type()
 
292
{
 
293
    static GType type = 0;
 
294
    if(!type)
 
295
        type = g_pointer_type_register_static("xbel_item");
 
296
    return type;
 
297
}
 
298
 
 
299
/**
 
300
 * xbel_folder_append_item:
 
301
 * @folder: a folder
 
302
 * @item: the item to append
 
303
 *
 
304
 * Append the given item to a folder.
 
305
 *
 
306
 * The item is actually moved and must not be contained in another folder.
 
307
 *
 
308
 **/
 
309
void xbel_folder_append_item(XbelItem* folder, XbelItem* item)
 
310
{
 
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);
 
315
}
 
316
 
 
317
/**
 
318
 * xbel_folder_prepend_item:
 
319
 * @folder: a folder
 
320
 * @item: the item to prepend
 
321
 *
 
322
 * Prepend the given item to a folder.
 
323
 *
 
324
 * The item is actually moved and must not be contained in another folder.
 
325
 *
 
326
 **/
 
327
void xbel_folder_prepend_item(XbelItem* folder, XbelItem* item)
 
328
{
 
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);
 
333
}
 
334
 
 
335
/**
 
336
 * xbel_folder_remove_item:
 
337
 * @folder: a folder
 
338
 * @item:   the item to remove
 
339
 *
 
340
 * Remove the given @item from a @folder.
 
341
 **/
 
342
void xbel_folder_remove_item(XbelItem* folder, XbelItem* item)
 
343
{
 
344
    g_return_if_fail(item);
 
345
    g_return_if_fail(xbel_item_get_parent(folder) != item);
 
346
    item->parent = NULL;
 
347
    // Fortunately we know that items are unique
 
348
    folder->items = g_list_remove(folder->items, item);
 
349
}
 
350
 
 
351
/**
 
352
 * xbel_folder_get_n_items:
 
353
 * @folder: a folder
 
354
 *
 
355
 * Retrieve the number of items contained in the given @folder.
 
356
 *
 
357
 * Return value: number of items
 
358
 **/
 
359
guint xbel_folder_get_n_items(XbelItem* folder)
 
360
{
 
361
    g_return_val_if_fail(xbel_item_is_folder(folder), 0);
 
362
    return g_list_length(folder->items);
 
363
}
 
364
 
 
365
/**
 
366
 * xbel_folder_get_nth_item:
 
367
 * @folder: a folder
 
368
 * @n: the index of an item
 
369
 *
 
370
 * Retrieve an item contained in the given @folder by its index.
 
371
 *
 
372
 * Return value: the item at the given index or %NULL
 
373
 **/
 
374
XbelItem* xbel_folder_get_nth_item(XbelItem* folder, guint n)
 
375
{
 
376
    g_return_val_if_fail(xbel_item_is_folder(folder), NULL);
 
377
    return (XbelItem*)g_list_nth_data(folder->items, n);
 
378
}
 
379
 
 
380
/**
 
381
 * xbel_folder_is_empty:
 
382
 * @folder: A folder.
 
383
 *
 
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.
 
386
 *
 
387
 * Return value: Wether the given @folder is folded.
 
388
 **/
 
389
gboolean xbel_folder_is_empty(XbelItem* folder)
 
390
{
 
391
    return !xbel_folder_get_nth_item(folder, 0);
 
392
}
 
393
 
 
394
/**
 
395
 * xbel_folder_get_folded:
 
396
 * @folder: A folder.
 
397
 *
 
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.
 
400
 *
 
401
 * New folders are folded by default.
 
402
 *
 
403
 * Return value: Wether the given @folder is folded.
 
404
 **/
 
405
gboolean xbel_folder_get_folded(XbelItem* folder)
 
406
{
 
407
    g_return_val_if_fail(xbel_item_is_folder(folder), TRUE);
 
408
    return folder->folded;
 
409
}
 
410
 
 
411
/**
 
412
 * xbel_item_get_kind:
 
413
 * @item: A item.
 
414
 *
 
415
 * Determines the kind of an item.
 
416
 *
 
417
 * Return value: The kind of the given @item.
 
418
 **/
 
419
XbelItemKind xbel_item_get_kind(XbelItem* item)
 
420
{
 
421
    return item->kind;
 
422
}
 
423
 
 
424
/**
 
425
 * xbel_item_get_parent:
 
426
 * @item: A valid item.
 
427
 *
 
428
 * Retrieves the parent folder of an item.
 
429
 *
 
430
 * Return value: The parent folder of the given @item or %NULL.
 
431
 **/
 
432
XbelItem* xbel_item_get_parent(XbelItem* item)
 
433
{
 
434
    g_return_val_if_fail(item, NULL);
 
435
    return (XbelItem*)item->parent;
 
436
}
 
437
 
 
438
/**
 
439
 * xbel_item_get_title:
 
440
 * @item: A valid item.
 
441
 *
 
442
 * Retrieves the title of an item.
 
443
 *
 
444
 * Return value: The title of the given @item or %NULL.
 
445
 **/
 
446
G_CONST_RETURN gchar* xbel_item_get_title(XbelItem* item)
 
447
{
 
448
    g_return_val_if_fail(!xbel_item_is_separator(item), NULL);
 
449
    return item->title;
 
450
}
 
451
 
 
452
/**
 
453
 * xbel_item_get_desc:
 
454
 * @item: A valid item.
 
455
 *
 
456
 * Retrieves the description of an item.
 
457
 *
 
458
 * Return value: The description of the @item or %NULL.
 
459
 **/
 
460
G_CONST_RETURN gchar* xbel_item_get_desc(XbelItem* item)
 
461
{
 
462
    g_return_val_if_fail(!xbel_item_is_separator(item), NULL);
 
463
    return item->desc;
 
464
}
 
465
 
 
466
/**
 
467
 * xbel_bookmark_get_href:
 
468
 * @bookmark: A bookmark.
 
469
 *
 
470
 * Retrieves the uri of a bookmark. The value is guaranteed to not be %NULL.
 
471
 *
 
472
 * Return value: The uri of the @bookmark.
 
473
 **/
 
474
G_CONST_RETURN gchar* xbel_bookmark_get_href(XbelItem* bookmark)
 
475
{
 
476
    g_return_val_if_fail(xbel_item_is_bookmark(bookmark), NULL);
 
477
    return bookmark->href;
 
478
}
 
479
 
 
480
/**
 
481
 * xbel_item_is_bookmark:
 
482
 * @item: A valid item.
 
483
 *
 
484
 * Determines wether or not an item is a bookmark.
 
485
 *
 
486
 * Return value: %TRUE if the @item is a bookmark.
 
487
 **/
 
488
gboolean xbel_item_is_bookmark(XbelItem* item)
 
489
{
 
490
    g_return_val_if_fail(item, FALSE);
 
491
    return item->kind == XBEL_ITEM_BOOKMARK;
 
492
}
 
493
 
 
494
/**
 
495
 * xbel_item_is_separator:
 
496
 * @item: A valid item.
 
497
 *
 
498
 * Determines wether or not an item is a separator.
 
499
 *
 
500
 * Return value: %TRUE if the @item is a separator.
 
501
 **/
 
502
gboolean xbel_item_is_separator(XbelItem* item)
 
503
{
 
504
    g_return_val_if_fail(item, FALSE);
 
505
    return item->kind == XBEL_ITEM_SEPARATOR;
 
506
}
 
507
 
 
508
/**
 
509
 * xbel_item_is_folder:
 
510
 * @item: A valid item.
 
511
 *
 
512
 * Determines wether or not an item is a folder.
 
513
 *
 
514
 * Return value: %TRUE if the item is a folder.
 
515
 **/
 
516
gboolean xbel_item_is_folder(XbelItem* item)
 
517
{
 
518
    g_return_val_if_fail(item, FALSE);
 
519
    return item->kind == XBEL_ITEM_FOLDER;
 
520
}
 
521
 
 
522
/**
 
523
 * xbel_folder_set_folded:
 
524
 * @folder: A folder.
 
525
 * @folded: TRUE if the folder is folded.
 
526
 *
 
527
 * Sets the foldedness of the @folder.
 
528
 **/
 
529
void xbel_folder_set_folded(XbelItem* folder, gboolean folded)
 
530
{
 
531
    g_return_if_fail(xbel_item_is_folder(folder));
 
532
    folder->folded = folded;
 
533
}
 
534
 
 
535
/**
 
536
 * xbel_item_set_title:
 
537
 * @item: A valid item.
 
538
 * @title: A string to use for the title.
 
539
 *
 
540
 * Sets the title of the @item.
 
541
 **/
 
542
void xbel_item_set_title(XbelItem* item, const gchar* title)
 
543
{
 
544
    g_return_if_fail(!xbel_item_is_separator(item));
 
545
    g_free(item->title);
 
546
    item->title = g_strdup(title);
 
547
}
 
548
 
 
549
/**
 
550
 * xbel_item_set_desc:
 
551
 * @item: A valid item.
 
552
 * @title: A string to use for the description.
 
553
 *
 
554
 * Sets the description of the @item.
 
555
 **/
 
556
void xbel_item_set_desc(XbelItem* item, const gchar* desc)
 
557
{
 
558
    g_return_if_fail(!xbel_item_is_separator(item));
 
559
    g_free(item->desc);
 
560
    item->desc = g_strdup(desc);
 
561
}
 
562
 
 
563
/**
 
564
 * xbel_bookmark_set_href:
 
565
 * @bookmark: A bookmark.
 
566
 * @href: A string containing a valid uri.
 
567
 *
 
568
 * Sets the uri of the bookmark.
 
569
 *
 
570
 * The uri must not be %NULL.
 
571
 *
 
572
 * This uri is not currently validated in any way. This may change in the future.
 
573
 **/
 
574
void xbel_bookmark_set_href(XbelItem* bookmark, const gchar* href)
 
575
{
 
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);
 
580
}
 
581
 
 
582
gboolean xbel_folder_from_data(XbelItem* folder, const gchar* data, GError** error)
 
583
{
 
584
    g_return_val_if_fail(xbel_folder_is_empty(folder), FALSE);
 
585
    g_return_val_if_fail(data, FALSE);
 
586
    xmlDocPtr doc;
 
587
    if((doc = xmlParseMemory(data, strlen(data))) == NULL)
 
588
    {
 
589
        // No valid xml or broken encoding
 
590
        *error = g_error_new(XBEL_ERROR, XBEL_ERROR_READ
 
591
        , "Malformed document.");
 
592
        return FALSE;
 
593
    }
 
594
    if(!xbel_folder_from_xmlDocPtr(folder, doc))
 
595
    {
 
596
        // Parsing failed
 
597
        xmlFreeDoc(doc);
 
598
        *error = g_error_new(XBEL_ERROR, XBEL_ERROR_READ
 
599
         , "Malformed document.");
 
600
        return FALSE;
 
601
    }
 
602
    xmlFreeDoc(doc);
 
603
    return TRUE;
 
604
}
 
605
 
 
606
/**
 
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
 
611
 *
 
612
 * Tries to load @file.
 
613
 *
 
614
 * Return value: %TRUE on success or %FALSE when an error occured.
 
615
 **/
 
616
gboolean xbel_folder_from_file(XbelItem* folder, const gchar* file, GError** error)
 
617
{
 
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))
 
621
    {
 
622
        // File doesn't exist
 
623
        *error = g_error_new(G_FILE_ERROR, G_FILE_ERROR_NOENT
 
624
         , "File not found.");
 
625
        return FALSE;
 
626
    }
 
627
    xmlDocPtr doc;
 
628
    if((doc = xmlParseFile(file)) == NULL)
 
629
    {
 
630
        // No valid xml or broken encoding
 
631
        *error = g_error_new(XBEL_ERROR, XBEL_ERROR_READ
 
632
         , "Malformed document.");
 
633
        return FALSE;
 
634
    }
 
635
    if(!xbel_folder_from_xmlDocPtr(folder, doc))
 
636
    {
 
637
        // Parsing failed
 
638
        xmlFreeDoc(doc);
 
639
        *error = g_error_new(XBEL_ERROR, XBEL_ERROR_READ
 
640
         , "Malformed document.");
 
641
        return FALSE;
 
642
    }
 
643
    xmlFreeDoc(doc);
 
644
    return TRUE;
 
645
}
 
646
 
 
647
/**
 
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
 
653
 *
 
654
 * Tries to load @file from the user data dir or any of the system data dirs.
 
655
 *
 
656
 * Return value: %TRUE on success or %FALSE when an error occured.
 
657
 **/
 
658
gboolean xbel_folder_from_data_dirs(XbelItem* folder, const gchar* file
 
659
 , gchar** full_path, GError** error)
 
660
{
 
661
    g_return_val_if_fail(xbel_folder_is_empty(folder), FALSE);
 
662
    g_return_val_if_fail(file, FALSE);
 
663
    // FIXME: Essentially unimplemented
 
664
 
 
665
    *error = g_error_new(XBEL_ERROR, XBEL_ERROR_READ
 
666
     , "Malformed document.");
 
667
    return FALSE;
 
668
}
 
669
 
 
670
static gchar* xbel_xml_element(const gchar* name, const gchar* value)
 
671
{
 
672
    if(!value)
 
673
        return g_strdup("");
 
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);
 
677
    return XML;
 
678
}
 
679
 
 
680
static gchar* xbel_item_to_data(XbelItem* item)
 
681
{
 
682
    g_return_val_if_fail(item, NULL);
 
683
    gchar* XML = NULL;
 
684
    switch(xbel_item_get_kind(item))
 
685
    {
 
686
    case XBEL_ITEM_FOLDER:
 
687
    {
 
688
        GString* _XML = g_string_new(NULL);
 
689
        guint n = xbel_folder_get_n_items(item);
 
690
        guint i;
 
691
        for(i = 0; i < n; i++)
 
692
        {
 
693
            XbelItem* _item = xbel_folder_get_nth_item(item, i);
 
694
            gchar* itemXML = xbel_item_to_data(_item);
 
695
            g_string_append(_XML, itemXML);
 
696
            g_free(itemXML);
 
697
        }
 
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 : ""
 
703
         , title
 
704
         , desc
 
705
         , g_string_free(_XML, FALSE));
 
706
        g_free(folded);
 
707
        g_free(title);
 
708
        g_free(desc);
 
709
        break;
 
710
        }
 
711
    case XBEL_ITEM_BOOKMARK:
 
712
    {
 
713
        gchar* hrefEscaped = g_markup_escape_text(item->href, -1);
 
714
        gchar* href = g_strdup_printf(" href=\"%s\"", hrefEscaped);
 
715
        g_free(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"
 
719
         , href
 
720
         , title
 
721
         , desc
 
722
         , "");
 
723
        g_free(href);
 
724
        g_free(title);
 
725
        g_free(desc);
 
726
        break;
 
727
    }
 
728
    case XBEL_ITEM_SEPARATOR:
 
729
        XML = g_strdup("<separator/>\n");
 
730
        break;
 
731
    default:
 
732
        g_warning("XBEL: Unknown item kind");
 
733
    }
 
734
    return XML;
 
735
}
 
736
 
 
737
/**
 
738
 * xbel_folder_to_data:
 
739
 * @folder: A folder.
 
740
 * @length: return location for the length of the created string or %NULL
 
741
 * @error: return location for a GError or %NULL
 
742
 *
 
743
 * Retrieve the contents of @folder as a string.
 
744
 *
 
745
 * Return value: %TRUE on success or %FALSE when an error occured.
 
746
 **/
 
747
gchar* xbel_folder_to_data(XbelItem* folder, gsize* length, GError** error)
 
748
{
 
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);
 
753
    guint i;
 
754
    for(i = 0; i < n; i++)
 
755
    {
 
756
        gchar* sItem = xbel_item_to_data(xbel_folder_get_nth_item(folder, i));
 
757
        g_string_append(innerXML, sItem);
 
758
        g_free(sItem);
 
759
    }
 
760
    gchar* title = xbel_xml_element("title", folder->title);
 
761
    gchar* desc = xbel_xml_element("desc", folder->desc);
 
762
    gchar* outerXML;
 
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"
 
766
     , title
 
767
     , desc
 
768
     , g_string_free(innerXML, FALSE));
 
769
    g_free(title);
 
770
    g_free(desc);
 
771
    return outerXML;
 
772
}
 
773
 
 
774
/**
 
775
 * xbel_folder_to_file:
 
776
 * @folder: A folder.
 
777
 * @file: The destination filename.
 
778
 * @error: return location for a GError or %NULL
 
779
 *
 
780
 * Write the contents of @folder to the specified file, creating it if necessary.
 
781
 *
 
782
 * Return value: %TRUE on success or %FALSE when an error occured.
 
783
 **/
 
784
gboolean xbel_folder_to_file(XbelItem* folder, const gchar* file, GError** error)
 
785
{
 
786
    g_return_val_if_fail(file, FALSE);
 
787
    gchar* data;
 
788
    if(!(data = xbel_folder_to_data(folder, NULL, error)))
 
789
        return FALSE;
 
790
    FILE* fp;
 
791
    if(!(fp = fopen(file, "w")))
 
792
    {
 
793
        *error = g_error_new(G_FILE_ERROR, G_FILE_ERROR_ACCES
 
794
         , "Writing failed.");
 
795
        return FALSE;
 
796
    }
 
797
    fputs(data, fp);
 
798
    fclose(fp);
 
799
    g_free(data);
 
800
    return TRUE;
 
801
}