mirror of
https://github.com/rkd77/elinks.git
synced 2024-12-04 14:46:47 -05:00
e325b748be
Previously, print_xml_entities did look up the charset, but did not save the result anywhere and just used 0, leading to further lookups in subsequent calls. It worked by accident though, because the codepage index of us-ascii currently is 0.
538 lines
12 KiB
C
538 lines
12 KiB
C
/* Internal bookmarks XBEL bookmarks basic support */
|
|
|
|
/*
|
|
* TODO: Decent XML output.
|
|
* TODO: Validation of the document (with librxp?). An invalid document can
|
|
* crash elinks.
|
|
* TODO: Support all the XBEL elements. */
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif /* HAVE_CONFIG_H */
|
|
|
|
#include <ctype.h>
|
|
#include <expat.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#ifdef HAVE_UNISTD_H
|
|
#include <unistd.h>
|
|
#endif
|
|
|
|
#include "elinks.h"
|
|
|
|
#include "bfu/dialog.h"
|
|
#include "bookmarks/bookmarks.h"
|
|
#include "bookmarks/backend/common.h"
|
|
#include "bookmarks/backend/xbel.h"
|
|
#include "intl/charsets.h"
|
|
#include "intl/gettext/libintl.h"
|
|
#include "util/conv.h"
|
|
#include "util/lists.h"
|
|
#include "util/string.h"
|
|
|
|
#define BOOKMARKS_XBEL_FILENAME "bookmarks.xbel"
|
|
|
|
|
|
/* Elements' attributes */
|
|
struct attributes {
|
|
LIST_HEAD(struct attributes);
|
|
|
|
unsigned char *name;
|
|
};
|
|
|
|
/* Prototypes */
|
|
static void on_element_open(void *data, const char *name, const char **attr);
|
|
static void on_element_close(void *data, const char *name);
|
|
static void on_text(void *data, const XML_Char *text, int len);
|
|
|
|
static struct tree_node *new_node(struct tree_node *parent);
|
|
static void free_node(struct tree_node *node);
|
|
static void free_xbeltree(struct tree_node *node);
|
|
static struct tree_node *get_child(struct tree_node *node, unsigned char *name);
|
|
static unsigned char *get_attribute_value(struct tree_node *node,
|
|
unsigned char *name);
|
|
|
|
|
|
static void read_bookmarks_xbel(FILE *f);
|
|
static unsigned char * filename_bookmarks_xbel(int writing);
|
|
static int xbeltree_to_bookmarks_list(struct tree_node *root,
|
|
struct bookmark *current_parent);
|
|
static void write_bookmarks_list(struct secure_save_info *ssi,
|
|
struct list_head *bookmarks_list,
|
|
int n, int folder_state);
|
|
static void write_bookmarks_xbel(struct secure_save_info *ssi,
|
|
struct list_head *bookmarks_list);
|
|
|
|
/* Element */
|
|
struct tree_node {
|
|
unsigned char *name; /* Name of the element */
|
|
unsigned char *text; /* Text inside the element */
|
|
struct list_head attrs; /* {struct attributes} */
|
|
struct tree_node *parent;
|
|
struct tree_node *children;
|
|
|
|
struct tree_node *prev;
|
|
struct tree_node *next;
|
|
};
|
|
|
|
static struct tree_node *root_node = NULL;
|
|
static struct tree_node *current_node = NULL;
|
|
|
|
/* This is 1 so that we won't fail miserably if we read bookmarks in a
|
|
* different format. */
|
|
static int readok = 1;
|
|
|
|
static void
|
|
read_bookmarks_xbel(FILE *f)
|
|
{
|
|
unsigned char in_buffer[BUFSIZ];
|
|
XML_Parser p;
|
|
int done = 0;
|
|
int err = 0;
|
|
|
|
readok = 0;
|
|
|
|
p = XML_ParserCreate(NULL);
|
|
if (!p) {
|
|
ERROR(gettext("read_bookmarks_xbel(): Error in XML_ParserCreate()"));
|
|
return;
|
|
}
|
|
|
|
XML_SetElementHandler(p, on_element_open, on_element_close);
|
|
XML_SetCharacterDataHandler(p, on_text);
|
|
|
|
while (!done && !err) {
|
|
size_t len = fread(in_buffer, 1, BUFSIZ, f);
|
|
|
|
if (ferror(f)) {
|
|
ERROR(gettext("read_bookmarks_xbel(): Error reading %s"),
|
|
filename_bookmarks_xbel(0));
|
|
err = 1;
|
|
} else {
|
|
|
|
done = feof(f);
|
|
|
|
if (!err && !XML_Parse(p, in_buffer, len, done)) {
|
|
usrerror(gettext("Parse error while processing "
|
|
"XBEL bookmarks in %s at line %d "
|
|
"column %d:\n%s"),
|
|
filename_bookmarks_xbel(0),
|
|
XML_GetCurrentLineNumber(p),
|
|
XML_GetCurrentColumnNumber(p),
|
|
XML_ErrorString(XML_GetErrorCode(p)));
|
|
err = 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!err) readok = xbeltree_to_bookmarks_list(root_node->children, NULL); /* Top node is xbel */
|
|
|
|
XML_ParserFree(p);
|
|
free_xbeltree(root_node);
|
|
|
|
}
|
|
|
|
static void
|
|
write_bookmarks_xbel(struct secure_save_info *ssi,
|
|
struct list_head *bookmarks_list)
|
|
{
|
|
int folder_state = get_opt_bool("bookmarks.folder_state");
|
|
/* We check for readok in filename_bookmarks_xbel(). */
|
|
|
|
secure_fputs(ssi,
|
|
"<?xml version=\"1.0\"?>\n"
|
|
"<!DOCTYPE xbel PUBLIC \"+//IDN python.org//DTD XML "
|
|
"Bookmark Exchange Language 1.0//EN//XML\"\n"
|
|
" "
|
|
"\"http://www.python.org/topics/xml/dtds/xbel-1.0.dtd\">\n\n"
|
|
"<xbel>\n\n\n");
|
|
|
|
|
|
write_bookmarks_list(ssi, bookmarks_list, 0, folder_state);
|
|
secure_fputs(ssi, "\n</xbel>\n");
|
|
}
|
|
|
|
static unsigned char *
|
|
filename_bookmarks_xbel(int writing)
|
|
{
|
|
if (writing && !readok) return NULL;
|
|
return BOOKMARKS_XBEL_FILENAME;
|
|
}
|
|
|
|
static void
|
|
indentation(struct secure_save_info *ssi, int num)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < num; i++)
|
|
secure_fputs(ssi, " ");
|
|
}
|
|
|
|
/* FIXME This is totally broken, we should use the Unicode value in
|
|
* numeric entities.
|
|
* Additionally it is slow, not elegant, incomplete and
|
|
* if you pay enough attention you can smell the unmistakable
|
|
* odor of doom coming from it. --fabio */
|
|
static void
|
|
print_xml_entities(struct secure_save_info *ssi, const unsigned char *str)
|
|
{
|
|
#define accept_char(x) (isident((x)) || (x) == ' ' || (x) == '.' \
|
|
|| (x) == ':' || (x) == ';' \
|
|
|| (x) == '/' || (x) == '(' \
|
|
|| (x) == ')' || (x) == '}' \
|
|
|| (x) == '{' || (x) == '%' \
|
|
|| (x) == '+')
|
|
|
|
static int cp = -1;
|
|
|
|
if (cp == -1) cp = get_cp_index("us-ascii");
|
|
|
|
for (; *str; str++) {
|
|
if (accept_char(*str))
|
|
secure_fputc(ssi, *str);
|
|
else {
|
|
if (isascii(*str)) {
|
|
secure_fprintf(ssi, "&#%i;", (int) *str);
|
|
}
|
|
else {
|
|
const unsigned char *s = u2cp_no_nbsp(*str, cp);
|
|
|
|
if (s) print_xml_entities(ssi, s);
|
|
}
|
|
}
|
|
}
|
|
|
|
#undef accept_char
|
|
|
|
}
|
|
|
|
static void
|
|
write_bookmarks_list(struct secure_save_info *ssi,
|
|
struct list_head *bookmarks_list,
|
|
int n, int folder_state)
|
|
{
|
|
struct bookmark *bm;
|
|
|
|
foreach (bm, *bookmarks_list) {
|
|
indentation(ssi, n + 1);
|
|
|
|
if (bm->box_item->type == BI_FOLDER) {
|
|
int expanded = folder_state && bm->box_item->expanded;
|
|
|
|
secure_fputs(ssi, "<folder folded=\"");
|
|
secure_fputs(ssi, expanded ? "no" : "yes");
|
|
secure_fputs(ssi, "\">\n");
|
|
|
|
indentation(ssi, n + 2);
|
|
secure_fputs(ssi, "<title>");
|
|
print_xml_entities(ssi, bm->title);
|
|
secure_fputs(ssi, "</title>\n");
|
|
|
|
if (!list_empty(bm->child))
|
|
write_bookmarks_list(ssi, &bm->child, n + 2, folder_state);
|
|
|
|
indentation(ssi, n + 1);
|
|
secure_fputs(ssi, "</folder>\n\n");
|
|
|
|
} else if (bm->box_item->type == BI_LEAF) {
|
|
|
|
secure_fputs(ssi, "<bookmark href=\"");
|
|
print_xml_entities(ssi, bm->url);
|
|
secure_fputs(ssi, "\">\n");
|
|
|
|
indentation(ssi, n + 2);
|
|
secure_fputs(ssi, "<title>");
|
|
print_xml_entities(ssi, bm->title);
|
|
secure_fputs(ssi, "</title>\n");
|
|
|
|
indentation(ssi, n + 1);
|
|
secure_fputs(ssi, "</bookmark>\n\n");
|
|
|
|
} else if (bm->box_item->type == BI_SEPARATOR) {
|
|
secure_fputs(ssi, "<separator/>\n\n");
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
on_element_open(void *data, const char *name, const char **attr)
|
|
{
|
|
struct attributes *attribute;
|
|
struct tree_node *node;
|
|
|
|
node = new_node(current_node);
|
|
if (!node) return;
|
|
|
|
if (root_node) {
|
|
if (current_node->children) {
|
|
struct tree_node *tmp;
|
|
|
|
tmp = current_node->children;
|
|
current_node->children = node;
|
|
current_node->children->next = tmp;
|
|
current_node->children->prev = NULL;
|
|
}
|
|
else current_node->children = node;
|
|
}
|
|
else root_node = node;
|
|
|
|
current_node = node;
|
|
|
|
current_node->name = stracpy((unsigned char *) name);
|
|
if (!current_node->name) {
|
|
mem_free(current_node);
|
|
return;
|
|
}
|
|
|
|
while (*attr) {
|
|
unsigned char *tmp = stracpy((unsigned char *) *attr);
|
|
|
|
if (!tmp) {
|
|
free_node(current_node);
|
|
return;
|
|
}
|
|
|
|
attribute = mem_calloc(1, sizeof(*attribute));
|
|
if (!attribute) {
|
|
mem_free(tmp);
|
|
free_node(current_node);
|
|
return;
|
|
}
|
|
|
|
attribute->name = tmp;
|
|
|
|
add_to_list(current_node->attrs, attribute);
|
|
|
|
++attr;
|
|
}
|
|
|
|
}
|
|
|
|
static void
|
|
on_element_close(void *data, const char *name)
|
|
{
|
|
current_node = current_node->parent;
|
|
}
|
|
|
|
static unsigned char *
|
|
delete_whites(unsigned char *s)
|
|
{
|
|
unsigned char *r;
|
|
int count = 0, c = 0, i;
|
|
int len = strlen(s);
|
|
|
|
r = mem_alloc(len + 1);
|
|
if (!r) return NULL;
|
|
|
|
for (i = 0; i < len; i++) {
|
|
if (isspace(s[i])) {
|
|
if (count == 1) continue;
|
|
else count = 1;
|
|
}
|
|
else count = 0;
|
|
|
|
if (s[i] == '\n' || s[i] == '\t')
|
|
r[c++] = ' ';
|
|
else r[c++] = s[i];
|
|
}
|
|
|
|
r[c] = '\0';
|
|
|
|
/* XXX This should never return NULL, right? wrong! --fabio */
|
|
/* r = mem_realloc(r, strlen(r + 1)); */
|
|
|
|
return r;
|
|
|
|
}
|
|
|
|
static void
|
|
on_text(void *data, const XML_Char *text, int len)
|
|
{
|
|
char *tmp;
|
|
int len2 = 0;
|
|
|
|
if (len) {
|
|
len2 = current_node->text ? strlen(current_node->text) : 0;
|
|
|
|
tmp = mem_realloc(current_node->text, (size_t) (len + 1 + len2));
|
|
|
|
/* Out of memory */
|
|
if (!tmp) return;
|
|
|
|
strncpy(tmp + len2, text, len);
|
|
tmp[len + len2] = '\0';
|
|
current_node->text = delete_whites(tmp);
|
|
|
|
mem_free(tmp);
|
|
}
|
|
}
|
|
|
|
/* xbel_tree_to_bookmarks_list: returns 0 on fail,
|
|
* 1 on success */
|
|
static int
|
|
xbeltree_to_bookmarks_list(struct tree_node *node,
|
|
struct bookmark *current_parent)
|
|
{
|
|
struct bookmark *tmp;
|
|
struct tree_node *title;
|
|
static struct bookmark *lastbm;
|
|
|
|
while (node) {
|
|
if (!strcmp(node->name, "bookmark")) {
|
|
unsigned char *href;
|
|
|
|
title = get_child(node, "title");
|
|
href = get_attribute_value(node, "href");
|
|
|
|
tmp = add_bookmark(current_parent, 0,
|
|
/* The <title> element is optional */
|
|
title && title->text ? title->text
|
|
: (unsigned char *) gettext("No title"),
|
|
/* XXX: The href attribute isn't optional but
|
|
* we don't validate the source XML yet, so
|
|
* we can't always assume a non NULL value for
|
|
* get_attribute_value() */
|
|
href ? href
|
|
: (unsigned char *) gettext("No URL"));
|
|
|
|
/* Out of memory */
|
|
if (!tmp) return 0;
|
|
|
|
tmp->root = current_parent;
|
|
lastbm = tmp;
|
|
|
|
} else if (!strcmp(node->name, "folder")) {
|
|
unsigned char *folded;
|
|
|
|
title = get_child(node, "title");
|
|
|
|
tmp = add_bookmark(current_parent, 0,
|
|
title && title->text ? title->text
|
|
: (unsigned char *) gettext("No title"),
|
|
NULL);
|
|
|
|
/* Out of memory */
|
|
if (!tmp) return 0;
|
|
|
|
folded = get_attribute_value(node, "folded");
|
|
if (folded && !strcmp(folded, "no"))
|
|
tmp->box_item->expanded = 1;
|
|
|
|
lastbm = tmp;
|
|
|
|
} else if (!strcmp(node->name, "separator")) {
|
|
tmp = add_bookmark(current_parent, 0, "-", "");
|
|
|
|
/* Out of memory */
|
|
if (!tmp) return 0;
|
|
tmp->root = current_parent;
|
|
lastbm = tmp;
|
|
}
|
|
|
|
if (node->children) {
|
|
int ret;
|
|
|
|
/* If this node is a <folder> element, current parent
|
|
* changes */
|
|
ret = (!strcmp(node->name, "folder") ?
|
|
xbeltree_to_bookmarks_list(node->children,
|
|
lastbm) :
|
|
xbeltree_to_bookmarks_list(node->children,
|
|
current_parent));
|
|
/* Out of memory */
|
|
if (!ret) return 0;
|
|
}
|
|
|
|
node = node->next;
|
|
}
|
|
|
|
/* Success */
|
|
return 1;
|
|
}
|
|
|
|
static void
|
|
free_xbeltree(struct tree_node *node)
|
|
{
|
|
struct tree_node *next_node;
|
|
|
|
while (node) {
|
|
|
|
if (node->children)
|
|
free_xbeltree(node->children);
|
|
|
|
next_node = node->next;
|
|
free_node(node);
|
|
|
|
node = next_node;
|
|
}
|
|
}
|
|
|
|
static struct tree_node *
|
|
get_child(struct tree_node *node, unsigned char *name)
|
|
{
|
|
struct tree_node *ret;
|
|
|
|
if (!node) return NULL;
|
|
|
|
ret = node->children;
|
|
|
|
while (ret) {
|
|
if (!strcmp(name, ret->name)) {
|
|
return ret;
|
|
}
|
|
ret = ret->next;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static unsigned char *
|
|
get_attribute_value(struct tree_node *node, unsigned char *name)
|
|
{
|
|
struct attributes *attribute;
|
|
|
|
foreachback (attribute, node->attrs) {
|
|
if (!strcmp(attribute->name, name)) {
|
|
return attribute->prev->name;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static struct tree_node *
|
|
new_node(struct tree_node *parent)
|
|
{
|
|
struct tree_node *node;
|
|
|
|
node = mem_calloc(1, sizeof(*node));
|
|
if (!node) return NULL;
|
|
|
|
node->parent = parent ? parent : node;
|
|
init_list(node->attrs);
|
|
|
|
return node;
|
|
}
|
|
|
|
static void
|
|
free_node(struct tree_node *node)
|
|
{
|
|
struct attributes *attribute;
|
|
|
|
foreachback (attribute, node->attrs)
|
|
mem_free_if(attribute->name);
|
|
free_list(node->attrs); /* Don't free list during traversal */
|
|
|
|
mem_free_if(node->name);
|
|
mem_free_if(node->text);
|
|
|
|
mem_free(node);
|
|
}
|
|
|
|
/* Read and write functions for the XBEL backend */
|
|
struct bookmarks_backend xbel_bookmarks_backend = {
|
|
filename_bookmarks_xbel,
|
|
read_bookmarks_xbel,
|
|
write_bookmarks_xbel,
|
|
};
|