mirror of
https://github.com/netsurf-browser/netsurf
synced 2024-11-24 15:29:45 +03:00
6dfcd35580
svn path=/trunk/netsurf/; revision=12383
839 lines
20 KiB
C
839 lines
20 KiB
C
/*
|
|
* 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},
|
|
{CONTENT_IMAGE, NULL},
|
|
{CONTENT_NONE, NULL},
|
|
|
|
/* this serves as a sentinel */
|
|
{CONTENT_HTML, NULL}
|
|
};
|
|
|
|
static uint32_t tun_users = 0;
|
|
|
|
void tree_url_node_init(const char *folder_icon_name)
|
|
{
|
|
struct icon_entry *entry;
|
|
char icon_name[MAX_ICON_NAME_LEN];
|
|
|
|
tun_users++;
|
|
|
|
if (initialised)
|
|
return;
|
|
initialised = true;
|
|
|
|
folder_icon = tree_load_icon(folder_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);
|
|
}
|
|
|
|
|
|
void tree_url_node_cleanup()
|
|
{
|
|
struct icon_entry *entry;
|
|
|
|
tun_users--;
|
|
|
|
if (tun_users > 0)
|
|
return;
|
|
|
|
if (!initialised)
|
|
return;
|
|
initialised = false;
|
|
|
|
hlcache_handle_release(folder_icon);
|
|
|
|
entry = icon_table;
|
|
do {
|
|
hlcache_handle_release(entry->icon);
|
|
++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;
|
|
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_normalize(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);
|
|
|
|
/* Force the title in the hotlist */
|
|
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;
|
|
}
|