summaryrefslogtreecommitdiff
path: root/desktop/tree_url_node.c
diff options
context:
space:
mode:
authorJohn Mark Bell <jmb@netsurf-browser.org>2010-10-05 19:14:46 +0000
committerJohn Mark Bell <jmb@netsurf-browser.org>2010-10-05 19:14:46 +0000
commit6173bb0e6c3bf51cd463f7bc4f725429d9087b2b (patch)
treede3e013699742960b97ee4a5eda240908d0ea8e6 /desktop/tree_url_node.c
parent195c1ea3193f169c6825eca1fc6207e138126e98 (diff)
downloadnetsurf-6173bb0e6c3bf51cd463f7bc4f725429d9087b2b.tar.gz
netsurf-6173bb0e6c3bf51cd463f7bc4f725429d9087b2b.tar.bz2
Merge treeview-redux to trunk
svn path=/trunk/netsurf/; revision=10865
Diffstat (limited to 'desktop/tree_url_node.c')
-rw-r--r--desktop/tree_url_node.c846
1 files changed, 846 insertions, 0 deletions
diff --git a/desktop/tree_url_node.c b/desktop/tree_url_node.c
new file mode 100644
index 000000000..3bc9f90fc
--- /dev/null
+++ b/desktop/tree_url_node.c
@@ -0,0 +1,846 @@
+/*
+ * Copyright 2005 Richard Wilson <info@tinct.net>
+ * Copyright 2009 Paul Blokus <paul_pl@users.sourceforge.net>
+ *
+ * This file is part of NetSurf, http://www.netsurf-browser.org/
+ *
+ * NetSurf is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * NetSurf is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/** \file
+ * Creation of URL nodes with use of trees (implementation)
+ */
+
+
+#include <assert.h>
+#include <ctype.h>
+#include <libxml/HTMLparser.h>
+#include <libxml/HTMLtree.h>
+
+#include "content/content.h"
+#include "content/hlcache.h"
+#include "content/urldb.h"
+#include "desktop/browser.h"
+#include "desktop/options.h"
+#include "desktop/tree_url_node.h"
+#include "utils/log.h"
+#include "utils/messages.h"
+#include "utils/url.h"
+#include "utils/utils.h"
+
+/** Flags for each type of url tree node. */
+enum tree_element_url {
+ TREE_ELEMENT_URL = 0x01,
+ TREE_ELEMENT_LAST_VISIT = 0x02,
+ TREE_ELEMENT_VISITS = 0x03,
+ TREE_ELEMENT_THUMBNAIL = 0x04,
+};
+
+#define MAX_ICON_NAME_LEN 256
+
+static bool initialised = false;
+
+static hlcache_handle *folder_icon;
+
+struct icon_entry {
+ content_type type;
+ hlcache_handle *icon;
+};
+
+struct icon_entry icon_table[] = {
+ {CONTENT_HTML, NULL},
+ {CONTENT_TEXTPLAIN, NULL},
+ {CONTENT_CSS, NULL},
+#if defined(WITH_MNG) || defined(WITH_PNG)
+ {CONTENT_PNG, NULL},
+#endif
+#ifdef WITH_MNG
+ {CONTENT_JNG, NULL},
+ {CONTENT_MNG, NULL},
+#endif
+#ifdef WITH_JPEG
+ {CONTENT_JPEG, NULL},
+#endif
+#ifdef WITH_GIF
+ {CONTENT_GIF, NULL},
+#endif
+#ifdef WITH_BMP
+ {CONTENT_BMP, NULL},
+ {CONTENT_ICO, NULL},
+#endif
+#ifdef WITH_SPRITE
+ {CONTENT_SPRITE, NULL},
+#endif
+#ifdef WITH_DRAW
+ {CONTENT_DRAW, NULL},
+#endif
+#ifdef WITH_ARTWORKS
+ {CONTENT_ARTWORKS, NULL},
+#endif
+#ifdef WITH_NS_SVG
+ {CONTENT_SVG, NULL},
+#endif
+ {CONTENT_UNKNOWN, NULL},
+
+ /* this serves as a sentinel */
+ {CONTENT_HTML, NULL}
+};
+
+
+void tree_url_node_init(void)
+{
+ struct icon_entry *entry;
+ char icon_name[MAX_ICON_NAME_LEN];
+
+ if (initialised || option_tree_icons_dir == NULL)
+ return;
+ initialised = true;
+
+ folder_icon = tree_load_icon(tree_directory_icon_name);
+
+ entry = icon_table;
+ do {
+
+ tree_icon_name_from_content_type(icon_name, entry->type);
+ entry->icon = tree_load_icon(icon_name);
+
+ ++entry;
+ } while (entry->type != CONTENT_HTML);
+
+}
+
+
+/**
+ * Creates a tree entry for a URL, and links it into the tree
+ *
+ * \param parent the node to link to
+ * \param url the URL (copied)
+ * \param data the URL data to use
+ * \param title the custom title to use
+ * \return the node created, or NULL for failure
+ */
+struct node *tree_create_URL_node(struct tree *tree, struct node *parent,
+ const char *url, const char *title,
+ tree_node_user_callback user_callback, void *callback_data)
+{
+ struct node *node;
+ struct node_element *element;
+ char *text_cp, *squashed;
+
+ squashed = squash_whitespace(title ? title : url);
+ text_cp = strdup(squashed);
+ if (text_cp == NULL) {
+ LOG(("malloc failed"));
+ warn_user("NoMemory", 0);
+ return NULL;
+ }
+ free(squashed);
+ node = tree_create_leaf_node(tree, parent, text_cp, true, false,
+ false);
+ if (node == NULL) {
+ free(text_cp);
+ return NULL;
+ }
+
+ if (user_callback != NULL)
+ tree_set_node_user_callback(node, user_callback,
+ callback_data);
+
+ tree_create_node_element(node, NODE_ELEMENT_BITMAP,
+ TREE_ELEMENT_THUMBNAIL, false);
+ tree_create_node_element(node, NODE_ELEMENT_TEXT, TREE_ELEMENT_VISITS,
+ false);
+ tree_create_node_element(node, NODE_ELEMENT_TEXT,
+ TREE_ELEMENT_LAST_VISIT, false);
+ element = tree_create_node_element(node, NODE_ELEMENT_TEXT,
+ TREE_ELEMENT_URL, true);
+ if (element != NULL) {
+ text_cp = strdup(url);
+ if (text_cp == NULL) {
+ tree_delete_node(tree, node, false);
+ LOG(("malloc failed"));
+ warn_user("NoMemory", 0);
+ return NULL;
+ }
+ tree_update_node_element(tree, element, text_cp, NULL);
+ }
+
+ return node;
+}
+
+
+/**
+ * Creates a tree entry for a URL, and links it into the tree.
+ *
+ * All information is used directly from the url_data, and as such cannot be
+ * edited and should never be freed.
+ *
+ * \param parent the node to link to
+ * \param url the URL
+ * \param data the URL data to use
+ * \return the node created, or NULL for failure
+ */
+struct node *tree_create_URL_node_shared(struct tree *tree, struct node *parent,
+ const char *url, const struct url_data *data,
+ tree_node_user_callback user_callback, void *callback_data)
+{
+ struct node *node;
+ struct node_element *element;
+ const char *title;
+
+ assert(url && data);
+
+ if (data->title != NULL) {
+ title = data->title;
+ } else {
+ title = url;
+ }
+
+ node = tree_create_leaf_node(tree, parent, title, false, false, false);
+ if (node == NULL)
+ return NULL;
+
+ if (user_callback != NULL) {
+ tree_set_node_user_callback(node, user_callback,
+ callback_data);
+ }
+
+ tree_create_node_element(node, NODE_ELEMENT_BITMAP,
+ TREE_ELEMENT_THUMBNAIL, false);
+ tree_create_node_element(node, NODE_ELEMENT_TEXT, TREE_ELEMENT_VISITS,
+ false);
+ tree_create_node_element(node, NODE_ELEMENT_TEXT,
+ TREE_ELEMENT_LAST_VISIT, false);
+ element = tree_create_node_element(node, NODE_ELEMENT_TEXT,
+ TREE_ELEMENT_URL, false);
+ if (element != NULL) {
+ tree_update_node_element(tree, element, url, NULL);
+ }
+
+ tree_update_URL_node(tree, node, url, data, true);
+ return node;
+}
+
+
+/**
+ * Updates the node details for a URL node.
+ *
+ * \param node the node to update
+ */
+void tree_update_URL_node(struct tree *tree, struct node *node,
+ const char *url, const struct url_data *data, bool shared)
+{
+ struct node_element *element;
+ struct bitmap *bitmap = NULL;
+ struct icon_entry *entry;
+ char *text_cp;
+
+ assert(node != NULL);
+
+ element = tree_node_find_element(node, TREE_ELEMENT_URL, NULL);
+ if (element == NULL)
+ return;
+
+ if (data != NULL) {
+ if (data->title == NULL)
+ urldb_set_url_title(url, url);
+
+ if (data->title == NULL)
+ return;
+
+ element = tree_node_find_element(node, TREE_ELEMENT_TITLE,
+ NULL);
+ if (shared)
+ tree_update_node_element(tree, element, data->title,
+ NULL);
+ else {
+ text_cp = strdup(data->title);
+ if (text_cp == NULL) {
+ LOG(("malloc failed"));
+ warn_user("NoMemory", 0);
+ return;
+ }
+ tree_update_node_element(tree, element, text_cp, NULL);
+ }
+ } else {
+ data = urldb_get_url_data(url);
+ if (data == NULL)
+ return;
+ }
+
+ entry = icon_table;
+ do {
+ if (entry->type == data->type) {
+ if (entry->icon != NULL)
+ tree_set_node_icon(tree, node, entry->icon);
+ break;
+ }
+ ++entry;
+ } while (entry->type != CONTENT_HTML);
+
+ /* update last visit text */
+ element = tree_node_find_element(node, TREE_ELEMENT_LAST_VISIT, element);
+ tree_update_element_text(tree,
+ element,
+ messages_get_buff("TreeLast",
+ (data->last_visit > 0) ?
+ ctime((time_t *)&data->last_visit) :
+ messages_get("TreeUnknown")));
+
+
+ /* update number of visits text */
+ element = tree_node_find_element(node, TREE_ELEMENT_VISITS, element);
+ tree_update_element_text(tree,
+ element,
+ messages_get_buff("TreeVisits", data->visits));
+
+
+ /* update thumbnail */
+ element = tree_node_find_element(node, TREE_ELEMENT_THUMBNAIL, element);
+ if (element != NULL) {
+ bitmap = urldb_get_thumbnail(url);
+
+ if (bitmap != NULL) {
+ tree_update_node_element(tree, element, NULL, bitmap);
+ }
+ }
+}
+
+
+const char *tree_url_node_get_title(struct node *node)
+{
+ struct node_element *element;
+ element = tree_node_find_element(node, TREE_ELEMENT_TITLE, NULL);
+ if (element == NULL)
+ return NULL;
+ return tree_node_element_get_text(element);
+}
+
+
+const char *tree_url_node_get_url(struct node *node)
+{
+ struct node_element *element;
+ element = tree_node_find_element(node, TREE_ELEMENT_URL, NULL);
+ if (element == NULL)
+ return NULL;
+ return tree_node_element_get_text(element);
+}
+
+void tree_url_node_edit_title(struct tree *tree, struct node *node)
+{
+ struct node_element *element;
+ element = tree_node_find_element(node, TREE_ELEMENT_TITLE, NULL);
+ tree_start_edit(tree, element);
+}
+
+void tree_url_node_edit_url(struct tree *tree, struct node *node)
+{
+ struct node_element *element;
+ element = tree_node_find_element(node, TREE_ELEMENT_URL, NULL);
+ tree_start_edit(tree, element);
+}
+
+node_callback_resp tree_url_node_callback(void *user_data,
+ struct node_msg_data *msg_data)
+{
+ struct tree *tree;
+ struct node_element *element;
+ url_func_result res;
+ const char *text;
+ char *norm_text, *escaped_text;
+ const struct url_data *data;
+
+ /** @todo memory leaks on non-shared folder deletion. */
+ switch (msg_data->msg) {
+ case NODE_DELETE_ELEMENT_TXT:
+ switch (msg_data->flag) {
+ /* only history is using non-editable url
+ * elements so only history deletion will run
+ * this code
+ */
+ case TREE_ELEMENT_URL:
+ /* reset URL characteristics */
+ urldb_reset_url_visit_data(
+ msg_data->data.text);
+ return NODE_CALLBACK_HANDLED;
+ case TREE_ELEMENT_TITLE:
+ return NODE_CALLBACK_HANDLED;
+ }
+ break;
+ case NODE_DELETE_ELEMENT_IMG:
+ if (msg_data->flag == TREE_ELEMENT_THUMBNAIL ||
+ msg_data->flag == TREE_ELEMENT_TITLE)
+ return NODE_CALLBACK_HANDLED;
+ break;
+ case NODE_LAUNCH:
+ element = tree_node_find_element(msg_data->node,
+ TREE_ELEMENT_URL, NULL);
+ if (element != NULL) {
+ text = tree_node_element_get_text(element);
+ browser_window_create(text, NULL, 0,
+ true, false);
+ return NODE_CALLBACK_HANDLED;
+ }
+ break;
+ case NODE_ELEMENT_EDIT_FINISHING:
+
+ text = msg_data->data.text;
+
+ if (msg_data->flag == TREE_ELEMENT_URL) {
+ res = url_escape(text, 0, false, NULL,
+ &escaped_text);
+ if (res == URL_FUNC_OK)
+ res = url_normalize(escaped_text,
+ &norm_text);
+ if (res != URL_FUNC_OK) {
+ if (res == URL_FUNC_FAILED) {
+ warn_user("NoURLError", 0);
+ return NODE_CALLBACK_CONTINUE;
+ }
+ else {
+ warn_user("NoMemory", 0);
+ return NODE_CALLBACK_REJECT;
+ }
+
+ }
+ msg_data->data.text = norm_text;
+
+ data = urldb_get_url_data(norm_text);
+ if (data == NULL) {
+ urldb_add_url(norm_text);
+ urldb_set_url_persistence(norm_text,
+ true);
+ data = urldb_get_url_data(norm_text);
+ if (data == NULL)
+ return NODE_CALLBACK_REJECT;
+ }
+ tree = user_data;
+ tree_update_URL_node(tree, msg_data->node,
+ norm_text, NULL, false);
+ }
+ else if (msg_data->flag == TREE_ELEMENT_TITLE) {
+ while (isspace(*text))
+ text++;
+ norm_text = strdup(text);
+ if (norm_text == NULL) {
+ LOG(("malloc failed"));
+ warn_user("NoMemory", 0);
+ return NODE_CALLBACK_REJECT;
+ }
+ /* don't allow zero length entry text, return
+ false */
+ if (norm_text[0] == '\0') {
+ warn_user("NoNameError", 0);
+ msg_data->data.text = NULL;
+ return NODE_CALLBACK_CONTINUE;
+ }
+ msg_data->data.text = norm_text;
+ }
+
+ return NODE_CALLBACK_HANDLED;
+ default:
+ break;
+ }
+ return NODE_CALLBACK_NOT_HANDLED;
+}
+
+/**
+ * Search the children of an xmlNode for an element.
+ *
+ * \param node xmlNode to search children of, or 0
+ * \param name name of element to find
+ * \return first child of node which is an element and matches name, or
+ * 0 if not found or parameter node is 0
+ */
+static xmlNode *tree_url_find_xml_element(xmlNode *node, const char *name)
+{
+ xmlNode *xmlnode;
+ if (node == NULL)
+ return NULL;
+
+ for (xmlnode = node->children;
+ xmlnode && !(xmlnode->type == XML_ELEMENT_NODE &&
+ strcmp((const char *) xmlnode->name, name) == 0);
+ xmlnode = xmlnode->next)
+ ;
+
+ return xmlnode;
+}
+
+/**
+ * Parse an entry represented as a li.
+ *
+ * \param li xmlNode for parsed li
+ * \param directory directory to add this entry to
+ */
+static void tree_url_load_entry(xmlNode *li, struct tree *tree,
+ struct node *directory, tree_node_user_callback callback,
+ void *callback_data)
+{
+ char *url = NULL, *url1 = NULL;
+ char *title = NULL;
+ struct node *entry;
+ xmlNode *xmlnode;
+ const struct url_data *data;
+ url_func_result res;
+
+ for (xmlnode = li->children; xmlnode; xmlnode = xmlnode->next) {
+ /* The li must contain an "a" element */
+ if (xmlnode->type == XML_ELEMENT_NODE &&
+ strcmp((const char *)xmlnode->name, "a") == 0) {
+ url1 = (char *)xmlGetProp(xmlnode, (const xmlChar *) "href");
+ title = (char *)xmlNodeGetContent(xmlnode);
+ }
+ }
+
+ if ((url1 == NULL) || (title == NULL)) {
+ warn_user("TreeLoadError", "(Missing <a> in <li> or "
+ "memory exhausted.)");
+ return;
+ }
+
+ /* We're loading external input.
+ * This may be garbage, so attempt to normalise
+ */
+ res = url_normalize(url1, &url);
+ if (res != URL_FUNC_OK) {
+ LOG(("Failed normalising '%s'", url1));
+
+ if (res == URL_FUNC_NOMEM)
+ warn_user("NoMemory", NULL);
+
+ xmlFree(url1);
+ xmlFree(title);
+
+ return;
+ }
+
+ /* No longer need this */
+ xmlFree(url1);
+
+ data = urldb_get_url_data(url);
+ if (data == NULL) {
+ /* No entry in database, so add one */
+ urldb_add_url(url);
+ /* now attempt to get url data */
+ data = urldb_get_url_data(url);
+ }
+ if (data == NULL) {
+ xmlFree(title);
+ free(url);
+
+ return;
+ }
+
+ /* Make this URL persistent */
+ urldb_set_url_persistence(url, true);
+
+ if (data->title == NULL)
+ urldb_set_url_title(url, title);
+
+ entry = tree_create_URL_node(tree, directory, url, title,
+ callback, callback_data);
+
+ if (entry == NULL) {
+ /** \todo why isn't this fatal? */
+ warn_user("NoMemory", 0);
+ } else {
+ tree_update_URL_node(tree, entry, url, data, false);
+ }
+
+
+ xmlFree(title);
+ free(url);
+}
+
+/**
+ * Parse a directory represented as a ul.
+ *
+ * \param ul xmlNode for parsed ul
+ * \param directory directory to add this directory to
+ */
+static void tree_url_load_directory(xmlNode *ul, struct tree *tree,
+ struct node *directory, tree_node_user_callback callback,
+ void *callback_data)
+{
+ char *title;
+ struct node *dir;
+ xmlNode *xmlnode;
+
+ assert(ul != NULL);
+ assert(directory != NULL);
+
+ for (xmlnode = ul->children; xmlnode; xmlnode = xmlnode->next) {
+ /* The ul may contain entries as a li, or directories as
+ * an h4 followed by a ul. Non-element nodes may be present
+ * (eg. text, comments), and are ignored. */
+
+ if (xmlnode->type != XML_ELEMENT_NODE)
+ continue;
+
+ if (strcmp((const char *)xmlnode->name, "li") == 0) {
+ /* entry */
+ tree_url_load_entry(xmlnode, tree, directory, callback,
+ callback_data);
+
+ } else if (strcmp((const char *)xmlnode->name, "h4") == 0) {
+ /* directory */
+ title = (char *) xmlNodeGetContent(xmlnode );
+ if (!title) {
+ warn_user("TreeLoadError", "(Empty <h4> "
+ "or memory exhausted.)");
+ return;
+ }
+
+ for (xmlnode = xmlnode->next;
+ xmlnode && xmlnode->type != XML_ELEMENT_NODE;
+ xmlnode = xmlnode->next)
+ ;
+ if ((xmlnode == NULL) ||
+ strcmp((const char *)xmlnode->name, "ul") != 0) {
+ /* next element isn't expected ul */
+ free(title);
+ warn_user("TreeLoadError", "(Expected "
+ "<ul> not present.)");
+ return;
+ }
+
+ dir = tree_create_folder_node(tree, directory, title,
+ true, false, false);
+ if (dir == NULL) {
+ free(title);
+ return;
+ }
+
+ if (callback != NULL)
+ tree_set_node_user_callback(dir, callback,
+ callback_data);
+
+ if (folder_icon != NULL)
+ tree_set_node_icon(tree, dir, folder_icon);
+
+ tree_url_load_directory(xmlnode, tree, dir, callback,
+ callback_data);
+ }
+ }
+}
+
+/**
+ * Loads an url tree from a specified file.
+ *
+ * \param filename name of file to read
+ * \param tree empty tree which data will be read into
+ * \return the file represented as a tree, or NULL on failure
+ */
+bool tree_urlfile_load(const char *filename, struct tree *tree,
+ tree_node_user_callback callback, void *callback_data)
+{
+ xmlDoc *doc;
+ xmlNode *html, *body, *ul;
+ struct node *root;
+ FILE *fp = NULL;
+
+ if (filename == NULL) {
+ return false;
+ }
+
+ fp = fopen(filename, "r");
+ if (fp == NULL) {
+ return false;
+ }
+ fclose(fp);
+
+ doc = htmlParseFile(filename, "iso-8859-1");
+ if (doc == NULL) {
+ warn_user("TreeLoadError", messages_get("ParsingFail"));
+ return false;
+ }
+
+ html = tree_url_find_xml_element((xmlNode *) doc, "html");
+ body = tree_url_find_xml_element(html, "body");
+ ul = tree_url_find_xml_element(body, "ul");
+ if (ul == NULL) {
+ xmlFreeDoc(doc);
+ warn_user("TreeLoadError",
+ "(<html>...<body>...<ul> not found.)");
+ return false;
+ }
+
+ root = tree_get_root(tree);
+ tree_url_load_directory(ul, tree, root, callback, callback_data);
+ tree_set_node_expanded(tree, root, true, false, false);
+
+ xmlFreeDoc(doc);
+ return true;
+}
+
+/**
+ * Add an entry to the HTML tree for saving.
+ *
+ * The node must contain a sequence of node_elements in the following order:
+ *
+ * \param entry hotlist entry to add
+ * \param node node to add li to
+ * \return true on success, false on memory exhaustion
+ */
+static bool tree_url_save_entry(struct node *entry, xmlNode *node)
+{
+ xmlNode *li, *a;
+ xmlAttr *href;
+ const char *text;
+
+ li = xmlNewChild(node, NULL, (const xmlChar *) "li", NULL);
+ if (li == NULL)
+ return false;
+
+
+ text = tree_url_node_get_title(entry);
+ if (text == NULL)
+ return false;
+ a = xmlNewTextChild(li, NULL, (const xmlChar *) "a",
+ (const xmlChar *) text);
+ if (a == NULL)
+ return false;
+
+ text = tree_url_node_get_url(entry);
+ if (text == NULL)
+ return false;
+
+ href = xmlNewProp(a, (const xmlChar *) "href", (const xmlChar *) text);
+ if (href == NULL)
+ return false;
+ return true;
+}
+
+/**
+ * Add a directory to the HTML tree for saving.
+ *
+ * \param directory hotlist directory to add
+ * \param node node to add ul to
+ * \return true on success, false on memory exhaustion
+ */
+static bool tree_url_save_directory(struct node *directory, xmlNode *node)
+{
+ struct node *child;
+ xmlNode *ul, *h4;
+ const char *text;
+
+ ul = xmlNewChild(node, NULL, (const xmlChar *)"ul", NULL);
+ if (ul == NULL)
+ return false;
+
+ for (child = tree_node_get_child(directory); child;
+ child = tree_node_get_next(child)) {
+ if (!tree_node_is_folder(child)) {
+ /* entry */
+ if (!tree_url_save_entry(child, ul))
+ return false;
+ } else {
+ /* directory */
+ /* invalid HTML */
+
+ text = tree_url_node_get_title(child);
+ if (text == NULL)
+ return false;
+
+ h4 = xmlNewTextChild(ul, NULL,
+ (const xmlChar *) "h4",
+ (const xmlChar *) text);
+ if (h4 == NULL)
+ return false;
+
+ if (!tree_url_save_directory(child, ul))
+ return false;
+ } }
+
+ return true;
+}
+
+
+
+
+
+
+
+
+/**
+ * Perform a save to a specified file in the form of a html page
+ *
+ * \param filename the file to save to
+ * \param page_title title of the page
+ */
+bool tree_urlfile_save(struct tree *tree, const char *filename,
+ const char *page_title)
+{
+ int res;
+ xmlDoc *doc;
+ xmlNode *html, *head, *title, *body;
+
+ /* Unfortunately the Browse Hotlist format is invalid HTML,
+ * so this is a lie.
+ */
+ doc = htmlNewDoc(
+ (const xmlChar *) "http://www.w3.org/TR/html4/strict.dtd",
+ (const xmlChar *) "-//W3C//DTD HTML 4.01//EN");
+ if (doc == NULL) {
+ warn_user("NoMemory", 0);
+ return false;
+ }
+
+ html = xmlNewNode(NULL, (const xmlChar *) "html");
+ if (html == NULL) {
+ warn_user("NoMemory", 0);
+ xmlFreeDoc(doc);
+ return false;
+ }
+ xmlDocSetRootElement(doc, html);
+
+ head = xmlNewChild(html, NULL, (const xmlChar *) "head", NULL);
+ if (head == NULL) {
+ warn_user("NoMemory", 0);
+ xmlFreeDoc(doc);
+ return false;
+ }
+
+ title = xmlNewTextChild(head, NULL, (const xmlChar *) "title",
+ (const xmlChar *) page_title);
+ if (title == NULL) {
+ warn_user("NoMemory", 0);
+ xmlFreeDoc(doc);
+ return false;
+ }
+
+ body = xmlNewChild(html, NULL, (const xmlChar *) "body", NULL);
+ if (body == NULL) {
+ warn_user("NoMemory", 0);
+ xmlFreeDoc(doc);
+ return false;
+ }
+
+ if (!tree_url_save_directory(tree_get_root(tree), body)) {
+ warn_user("NoMemory", 0);
+ xmlFreeDoc(doc);
+ return false;
+ }
+
+ doc->charset = XML_CHAR_ENCODING_UTF8;
+ res = htmlSaveFileEnc(filename, doc, "iso-8859-1");
+ if (res == -1) {
+ warn_user("HotlistSaveError", 0);
+ xmlFreeDoc(doc);
+ return false;
+ }
+
+ xmlFreeDoc(doc);
+ return true;
+}