Declare element types of lists.
[elinks.git] / src / bookmarks / backend / xbel.c
blobd78a76842926c40a23e4783ee90be77b3324ed91
1 /* Internal bookmarks XBEL bookmarks basic support */
3 /*
4 * TODO: Decent XML output.
5 * TODO: Validation of the document (with librxp?). An invalid document can
6 * crash elinks.
7 * TODO: Support all the XBEL elements. */
9 #ifdef HAVE_CONFIG_H
10 #include "config.h"
11 #endif /* HAVE_CONFIG_H */
13 #include <ctype.h>
14 #include <expat.h>
15 #include <stdio.h>
16 #include <stdlib.h>
17 #ifdef HAVE_UNISTD_H
18 #include <unistd.h>
19 #endif
21 #include "elinks.h"
23 #include "bfu/dialog.h"
24 #include "bookmarks/bookmarks.h"
25 #include "bookmarks/backend/common.h"
26 #include "bookmarks/backend/xbel.h"
27 #include "intl/charsets.h"
28 #include "intl/gettext/libintl.h"
29 #include "util/conv.h"
30 #include "util/lists.h"
31 #include "util/string.h"
33 #define BOOKMARKS_XBEL_FILENAME "bookmarks.xbel"
36 /* Elements' attributes */
37 struct attributes {
38 LIST_HEAD(struct attributes);
40 unsigned char *name;
43 /* Prototypes */
44 static void on_element_open(void *data, const char *name, const char **attr);
45 static void on_element_close(void *data, const char *name);
46 static void on_text(void *data, const XML_Char *text, int len);
48 static struct tree_node *new_node(struct tree_node *parent);
49 static void free_node(struct tree_node *node);
50 static void free_xbeltree(struct tree_node *node);
51 static struct tree_node *get_child(struct tree_node *node, unsigned char *name);
52 static unsigned char *get_attribute_value(struct tree_node *node,
53 unsigned char *name);
56 static void read_bookmarks_xbel(FILE *f);
57 static unsigned char * filename_bookmarks_xbel(int writing);
58 static int xbeltree_to_bookmarks_list(struct tree_node *root,
59 struct bookmark *current_parent);
60 static void write_bookmarks_list(struct secure_save_info *ssi,
61 LIST_OF(struct bookmark) *bookmarks_list,
62 int n, int folder_state);
63 static void write_bookmarks_xbel(struct secure_save_info *ssi,
64 LIST_OF(struct bookmark) *bookmarks_list);
66 /* Element */
67 struct tree_node {
68 unsigned char *name; /* Name of the element */
69 unsigned char *text; /* Text inside the element */
70 LIST_OF(struct attributes) attrs;
71 struct tree_node *parent;
72 struct tree_node *children;
74 struct tree_node *prev;
75 struct tree_node *next;
78 static struct tree_node *root_node = NULL;
79 static struct tree_node *current_node = NULL;
81 /* This is 1 so that we won't fail miserably if we read bookmarks in a
82 * different format. */
83 static int readok = 1;
85 static void
86 read_bookmarks_xbel(FILE *f)
88 unsigned char in_buffer[BUFSIZ];
89 XML_Parser p;
90 int done = 0;
91 int err = 0;
93 readok = 0;
95 p = XML_ParserCreate(NULL);
96 if (!p) {
97 ERROR(gettext("read_bookmarks_xbel(): Error in XML_ParserCreate()"));
98 return;
101 XML_SetElementHandler(p, on_element_open, on_element_close);
102 XML_SetCharacterDataHandler(p, on_text);
104 while (!done && !err) {
105 size_t len = fread(in_buffer, 1, BUFSIZ, f);
107 if (ferror(f)) {
108 ERROR(gettext("read_bookmarks_xbel(): Error reading %s"),
109 filename_bookmarks_xbel(0));
110 err = 1;
111 } else {
113 done = feof(f);
115 if (!err && !XML_Parse(p, in_buffer, len, done)) {
116 usrerror(gettext("Parse error while processing "
117 "XBEL bookmarks in %s at line %d "
118 "column %d:\n%s"),
119 filename_bookmarks_xbel(0),
120 XML_GetCurrentLineNumber(p),
121 XML_GetCurrentColumnNumber(p),
122 XML_ErrorString(XML_GetErrorCode(p)));
123 err = 1;
128 if (!err) readok = xbeltree_to_bookmarks_list(root_node->children, NULL); /* Top node is xbel */
130 XML_ParserFree(p);
131 free_xbeltree(root_node);
135 static void
136 write_bookmarks_xbel(struct secure_save_info *ssi,
137 LIST_OF(struct bookmarks) *bookmarks_list)
139 int folder_state = get_opt_bool("bookmarks.folder_state");
140 /* We check for readok in filename_bookmarks_xbel(). */
142 secure_fputs(ssi,
143 "<?xml version=\"1.0\"?>\n"
144 "<!DOCTYPE xbel PUBLIC \"+//IDN python.org//DTD XML "
145 "Bookmark Exchange Language 1.0//EN//XML\"\n"
147 "\"http://www.python.org/topics/xml/dtds/xbel-1.0.dtd\">\n\n"
148 "<xbel>\n\n\n");
151 write_bookmarks_list(ssi, bookmarks_list, 0, folder_state);
152 secure_fputs(ssi, "\n</xbel>\n");
155 static unsigned char *
156 filename_bookmarks_xbel(int writing)
158 if (writing && !readok) return NULL;
159 return BOOKMARKS_XBEL_FILENAME;
162 static void
163 indentation(struct secure_save_info *ssi, int num)
165 int i;
167 for (i = 0; i < num; i++)
168 secure_fputs(ssi, " ");
171 /* FIXME This is totally broken, we should use the Unicode value in
172 * numeric entities.
173 * Additionally it is slow, not elegant, incomplete and
174 * if you pay enough attention you can smell the unmistakable
175 * odor of doom coming from it. --fabio */
176 static void
177 print_xml_entities(struct secure_save_info *ssi, const unsigned char *str)
179 #define accept_char(x) (isident((x)) || (x) == ' ' || (x) == '.' \
180 || (x) == ':' || (x) == ';' \
181 || (x) == '/' || (x) == '(' \
182 || (x) == ')' || (x) == '}' \
183 || (x) == '{' || (x) == '%' \
184 || (x) == '+')
186 static int cp = -1;
188 if (cp == -1) cp = get_cp_index("us-ascii");
190 for (; *str; str++) {
191 if (accept_char(*str))
192 secure_fputc(ssi, *str);
193 else {
194 if (isascii(*str)) {
195 secure_fprintf(ssi, "&#%i;", (int) *str);
197 else {
198 const unsigned char *s = u2cp_no_nbsp(*str, cp);
200 if (s) print_xml_entities(ssi, s);
205 #undef accept_char
209 static void
210 write_bookmarks_list(struct secure_save_info *ssi,
211 LIST_OF(struct bookmark) *bookmarks_list,
212 int n, int folder_state)
214 struct bookmark *bm;
216 foreach (bm, *bookmarks_list) {
217 indentation(ssi, n + 1);
219 if (bm->box_item->type == BI_FOLDER) {
220 int expanded = folder_state && bm->box_item->expanded;
222 secure_fputs(ssi, "<folder folded=\"");
223 secure_fputs(ssi, expanded ? "no" : "yes");
224 secure_fputs(ssi, "\">\n");
226 indentation(ssi, n + 2);
227 secure_fputs(ssi, "<title>");
228 print_xml_entities(ssi, bm->title);
229 secure_fputs(ssi, "</title>\n");
231 if (!list_empty(bm->child))
232 write_bookmarks_list(ssi, &bm->child, n + 2, folder_state);
234 indentation(ssi, n + 1);
235 secure_fputs(ssi, "</folder>\n\n");
237 } else if (bm->box_item->type == BI_LEAF) {
239 secure_fputs(ssi, "<bookmark href=\"");
240 print_xml_entities(ssi, bm->url);
241 secure_fputs(ssi, "\">\n");
243 indentation(ssi, n + 2);
244 secure_fputs(ssi, "<title>");
245 print_xml_entities(ssi, bm->title);
246 secure_fputs(ssi, "</title>\n");
248 indentation(ssi, n + 1);
249 secure_fputs(ssi, "</bookmark>\n\n");
251 } else if (bm->box_item->type == BI_SEPARATOR) {
252 secure_fputs(ssi, "<separator/>\n\n");
257 static void
258 on_element_open(void *data, const char *name, const char **attr)
260 struct attributes *attribute;
261 struct tree_node *node;
263 node = new_node(current_node);
264 if (!node) return;
266 if (root_node) {
267 if (current_node->children) {
268 struct tree_node *tmp;
270 tmp = current_node->children;
271 current_node->children = node;
272 current_node->children->next = tmp;
273 current_node->children->prev = NULL;
275 else current_node->children = node;
277 else root_node = node;
279 current_node = node;
281 current_node->name = stracpy((unsigned char *) name);
282 if (!current_node->name) {
283 mem_free(current_node);
284 return;
287 while (*attr) {
288 unsigned char *tmp = stracpy((unsigned char *) *attr);
290 if (!tmp) {
291 free_node(current_node);
292 return;
295 attribute = mem_calloc(1, sizeof(*attribute));
296 if (!attribute) {
297 mem_free(tmp);
298 free_node(current_node);
299 return;
302 attribute->name = tmp;
304 add_to_list(current_node->attrs, attribute);
306 ++attr;
311 static void
312 on_element_close(void *data, const char *name)
314 current_node = current_node->parent;
317 static unsigned char *
318 delete_whites(unsigned char *s)
320 unsigned char *r;
321 int count = 0, c = 0, i;
322 int len = strlen(s);
324 r = mem_alloc(len + 1);
325 if (!r) return NULL;
327 for (i = 0; i < len; i++) {
328 if (isspace(s[i])) {
329 if (count == 1) continue;
330 else count = 1;
332 else count = 0;
334 if (s[i] == '\n' || s[i] == '\t')
335 r[c++] = ' ';
336 else r[c++] = s[i];
339 r[c] = '\0';
341 /* XXX This should never return NULL, right? wrong! --fabio */
342 /* r = mem_realloc(r, strlen(r + 1)); */
344 return r;
348 static void
349 on_text(void *data, const XML_Char *text, int len)
351 char *tmp;
352 int len2 = 0;
354 if (len) {
355 len2 = current_node->text ? strlen(current_node->text) : 0;
357 tmp = mem_realloc(current_node->text, (size_t) (len + 1 + len2));
359 /* Out of memory */
360 if (!tmp) return;
362 strncpy(tmp + len2, text, len);
363 tmp[len + len2] = '\0';
364 current_node->text = delete_whites(tmp);
366 mem_free(tmp);
370 /* xbel_tree_to_bookmarks_list: returns 0 on fail,
371 * 1 on success */
372 static int
373 xbeltree_to_bookmarks_list(struct tree_node *node,
374 struct bookmark *current_parent)
376 struct bookmark *tmp;
377 struct tree_node *title;
378 static struct bookmark *lastbm;
380 while (node) {
381 if (!strcmp(node->name, "bookmark")) {
382 unsigned char *href;
384 title = get_child(node, "title");
385 href = get_attribute_value(node, "href");
387 tmp = add_bookmark(current_parent, 0,
388 /* The <title> element is optional */
389 title && title->text ? title->text
390 : (unsigned char *) gettext("No title"),
391 /* XXX: The href attribute isn't optional but
392 * we don't validate the source XML yet, so
393 * we can't always assume a non NULL value for
394 * get_attribute_value() */
395 href ? href
396 : (unsigned char *) gettext("No URL"));
398 /* Out of memory */
399 if (!tmp) return 0;
401 tmp->root = current_parent;
402 lastbm = tmp;
404 } else if (!strcmp(node->name, "folder")) {
405 unsigned char *folded;
407 title = get_child(node, "title");
409 tmp = add_bookmark(current_parent, 0,
410 title && title->text ? title->text
411 : (unsigned char *) gettext("No title"),
412 NULL);
414 /* Out of memory */
415 if (!tmp) return 0;
417 folded = get_attribute_value(node, "folded");
418 if (folded && !strcmp(folded, "no"))
419 tmp->box_item->expanded = 1;
421 lastbm = tmp;
423 } else if (!strcmp(node->name, "separator")) {
424 tmp = add_bookmark(current_parent, 0, "-", "");
426 /* Out of memory */
427 if (!tmp) return 0;
428 tmp->root = current_parent;
429 lastbm = tmp;
432 if (node->children) {
433 int ret;
435 /* If this node is a <folder> element, current parent
436 * changes */
437 ret = (!strcmp(node->name, "folder") ?
438 xbeltree_to_bookmarks_list(node->children,
439 lastbm) :
440 xbeltree_to_bookmarks_list(node->children,
441 current_parent));
442 /* Out of memory */
443 if (!ret) return 0;
446 node = node->next;
449 /* Success */
450 return 1;
453 static void
454 free_xbeltree(struct tree_node *node)
456 struct tree_node *next_node;
458 while (node) {
460 if (node->children)
461 free_xbeltree(node->children);
463 next_node = node->next;
464 free_node(node);
466 node = next_node;
470 static struct tree_node *
471 get_child(struct tree_node *node, unsigned char *name)
473 struct tree_node *ret;
475 if (!node) return NULL;
477 ret = node->children;
479 while (ret) {
480 if (!strcmp(name, ret->name)) {
481 return ret;
483 ret = ret->next;
486 return NULL;
489 static unsigned char *
490 get_attribute_value(struct tree_node *node, unsigned char *name)
492 struct attributes *attribute;
494 foreachback (attribute, node->attrs) {
495 if (!strcmp(attribute->name, name)) {
496 return attribute->prev->name;
500 return NULL;
503 static struct tree_node *
504 new_node(struct tree_node *parent)
506 struct tree_node *node;
508 node = mem_calloc(1, sizeof(*node));
509 if (!node) return NULL;
511 node->parent = parent ? parent : node;
512 init_list(node->attrs);
514 return node;
517 static void
518 free_node(struct tree_node *node)
520 struct attributes *attribute;
522 foreachback (attribute, node->attrs)
523 mem_free_if(attribute->name);
524 free_list(node->attrs); /* Don't free list during traversal */
526 mem_free_if(node->name);
527 mem_free_if(node->text);
529 mem_free(node);
532 /* Read and write functions for the XBEL backend */
533 struct bookmarks_backend xbel_bookmarks_backend = {
534 filename_bookmarks_xbel,
535 read_bookmarks_xbel,
536 write_bookmarks_xbel,