Subversion Repositories Kolibri OS

Rev

Go to most recent revision | Blame | Last modification | View Log | Download | RSS feed

  1. /*
  2.  * Copyright 2005 Richard Wilson <info@tinct.net>
  3.  * Copyright 2009 Paul Blokus <paul_pl@users.sourceforge.net>
  4.  *
  5.  * This file is part of NetSurf, http://www.netsurf-browser.org/
  6.  *
  7.  * NetSurf is free software; you can redistribute it and/or modify
  8.  * it under the terms of the GNU General Public License as published by
  9.  * the Free Software Foundation; version 2 of the License.
  10.  *
  11.  * NetSurf is distributed in the hope that it will be useful,
  12.  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13.  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  14.  * GNU General Public License for more details.
  15.  *
  16.  * You should have received a copy of the GNU General Public License
  17.  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  18.  */
  19.  
  20. /** \file
  21.  * Creation of URL nodes with use of trees (implementation)
  22.  */
  23.  
  24.  
  25. #include <assert.h>
  26. #include <ctype.h>
  27.  
  28. #include <dom/dom.h>
  29. #include <dom/bindings/hubbub/parser.h>
  30.  
  31. #include "content/content.h"
  32. #include "content/hlcache.h"
  33. #include "content/urldb.h"
  34. #include "desktop/browser.h"
  35. #include "desktop/options.h"
  36. #include "desktop/tree_url_node.h"
  37. #include "utils/corestrings.h"
  38. #include "utils/libdom.h"
  39. #include "utils/log.h"
  40. #include "utils/messages.h"
  41. #include "utils/url.h"
  42. #include "utils/utf8.h"
  43. #include "utils/utils.h"
  44.  
  45. /** Flags for each type of url tree node. */
  46. enum tree_element_url {
  47.         TREE_ELEMENT_URL = 0x01,
  48.         TREE_ELEMENT_LAST_VISIT = 0x02,
  49.         TREE_ELEMENT_VISITS = 0x03,
  50.         TREE_ELEMENT_THUMBNAIL = 0x04,
  51. };
  52.  
  53. #define MAX_ICON_NAME_LEN 256
  54.  
  55. static bool initialised = false;
  56.  
  57. static hlcache_handle *folder_icon;
  58.  
  59. struct icon_entry {
  60.         content_type type;
  61.         hlcache_handle *icon;
  62. };
  63.  
  64. struct icon_entry icon_table[] = {
  65.         {CONTENT_HTML, NULL},
  66.         {CONTENT_TEXTPLAIN, NULL},
  67.         {CONTENT_CSS, NULL},
  68.         {CONTENT_IMAGE, NULL},
  69.         {CONTENT_NONE, NULL},
  70.  
  71.         /* this serves as a sentinel */
  72.         {CONTENT_HTML, NULL}
  73. };
  74.  
  75. static uint32_t tun_users = 0;
  76.  
  77. void tree_url_node_init(const char *folder_icon_name)
  78. {
  79.         struct icon_entry *entry;
  80.         char icon_name[MAX_ICON_NAME_LEN];
  81.        
  82.         tun_users++;
  83.        
  84.         if (initialised)
  85.                 return;
  86.         initialised = true;
  87.  
  88.         folder_icon = tree_load_icon(folder_icon_name);
  89.  
  90.         entry = icon_table;
  91.         do {
  92.  
  93.                 tree_icon_name_from_content_type(icon_name, entry->type);
  94.                 entry->icon = tree_load_icon(icon_name);
  95.  
  96.                 ++entry;
  97.         } while (entry->type != CONTENT_HTML);
  98. }
  99.  
  100.  
  101. void tree_url_node_cleanup()
  102. {
  103.         struct icon_entry *entry;
  104.        
  105.         tun_users--;
  106.        
  107.         if (tun_users > 0)
  108.                 return;
  109.        
  110.         if (!initialised)
  111.                 return;
  112.         initialised = false;
  113.        
  114.         hlcache_handle_release(folder_icon);
  115.        
  116.         entry = icon_table;
  117.         do {
  118.                 hlcache_handle_release(entry->icon);
  119.                 ++entry;
  120.         } while (entry->type != CONTENT_HTML);
  121. }
  122.  
  123. /**
  124.  * Creates a tree entry for a URL, and links it into the tree
  125.  *
  126.  * \param parent     the node to link to
  127.  * \param url        the URL (copied)
  128.  * \param data       the URL data to use
  129.  * \param title      the custom title to use
  130.  * \return the node created, or NULL for failure
  131.  */
  132. struct node *tree_create_URL_node(struct tree *tree, struct node *parent,
  133.                 nsurl *url, const char *title,
  134.                 tree_node_user_callback user_callback, void *callback_data)
  135. {
  136.         struct node *node;
  137.         struct node_element *element;
  138.         char *text_cp, *squashed;
  139.  
  140.         squashed = squash_whitespace(title ? title : nsurl_access(url));
  141.         text_cp = strdup(squashed);
  142.         if (text_cp == NULL) {
  143.                 LOG(("malloc failed"));
  144.                 warn_user("NoMemory", 0);
  145.                 return NULL;
  146.         }
  147.         free(squashed);
  148.         node = tree_create_leaf_node(tree, parent, text_cp, true, false,
  149.                                      false);
  150.         if (node == NULL) {
  151.                 free(text_cp);
  152.                 return NULL;
  153.         }
  154.  
  155.         if (user_callback != NULL)
  156.                 tree_set_node_user_callback(node, user_callback,
  157.                                             callback_data);
  158.  
  159.         tree_create_node_element(node, NODE_ELEMENT_BITMAP,
  160.                                  TREE_ELEMENT_THUMBNAIL, false);
  161.         tree_create_node_element(node, NODE_ELEMENT_TEXT, TREE_ELEMENT_VISITS,
  162.                                  false);
  163.         tree_create_node_element(node, NODE_ELEMENT_TEXT,
  164.                                  TREE_ELEMENT_LAST_VISIT, false);
  165.         element = tree_create_node_element(node, NODE_ELEMENT_TEXT,
  166.                                            TREE_ELEMENT_URL, true);
  167.         if (element != NULL) {
  168.                 text_cp = strdup(nsurl_access(url));
  169.                 if (text_cp == NULL) {
  170.                         tree_delete_node(tree, node, false);
  171.                         LOG(("malloc failed"));
  172.                         warn_user("NoMemory", 0);
  173.                         return NULL;
  174.                 }
  175.                 tree_update_node_element(tree, element, text_cp, NULL);
  176.         }
  177.  
  178.         return node;
  179. }
  180.  
  181.  
  182. /**
  183.  * Creates a read only tree entry for a URL, and links it into the tree.
  184.  *
  185.  * \param parent      the node to link to
  186.  * \param url         the URL
  187.  * \param data        the URL data to use
  188.  * \return the node created, or NULL for failure
  189.  */
  190. struct node *tree_create_URL_node_readonly(struct tree *tree,
  191.                 struct node *parent, nsurl *url,
  192.                 const struct url_data *data,
  193.                 tree_node_user_callback user_callback, void *callback_data)
  194. {
  195.         struct node *node;
  196.         struct node_element *element;
  197.         char *title;
  198.  
  199.         assert(url && data);
  200.  
  201.         if (data->title != NULL) {
  202.                 title = strdup(data->title);
  203.         } else {
  204.                 title = strdup(nsurl_access(url));
  205.         }
  206.  
  207.         if (title == NULL)
  208.                 return NULL;
  209.  
  210.         node = tree_create_leaf_node(tree, parent, title, false, false, false);
  211.         if (node == NULL) {
  212.                 free(title);
  213.                 return NULL;
  214.         }
  215.  
  216.         if (user_callback != NULL) {
  217.                 tree_set_node_user_callback(node, user_callback,
  218.                                             callback_data);
  219.         }
  220.  
  221.         tree_create_node_element(node, NODE_ELEMENT_BITMAP,
  222.                                  TREE_ELEMENT_THUMBNAIL, false);
  223.         tree_create_node_element(node, NODE_ELEMENT_TEXT, TREE_ELEMENT_VISITS,
  224.                                  false);
  225.         tree_create_node_element(node, NODE_ELEMENT_TEXT,
  226.                                  TREE_ELEMENT_LAST_VISIT, false);
  227.         element = tree_create_node_element(node, NODE_ELEMENT_TEXT,
  228.                                            TREE_ELEMENT_URL, false);
  229.         if (element != NULL) {
  230.                 tree_update_node_element(tree, element, nsurl_access(url),
  231.                                 NULL);
  232.         }
  233.  
  234.         tree_update_URL_node(tree, node, url, data);
  235.  
  236.         return node;
  237. }
  238.  
  239.  
  240. /**
  241.  * Updates the node details for a URL node.
  242.  *
  243.  * \param node  the node to update
  244.  */
  245. void tree_update_URL_node(struct tree *tree, struct node *node,
  246.                 nsurl *url, const struct url_data *data)
  247. {
  248.         struct node_element *element;
  249.         struct bitmap *bitmap = NULL;
  250.         struct icon_entry *entry;
  251.         char *text_cp;
  252.  
  253.         assert(node != NULL);
  254.  
  255.         element = tree_node_find_element(node, TREE_ELEMENT_URL, NULL);
  256.         if (element == NULL)
  257.                 return;
  258.  
  259.         if (data != NULL) {
  260.                 if (data->title == NULL)
  261.                         urldb_set_url_title(url, nsurl_access(url));
  262.  
  263.                 if (data->title == NULL)
  264.                         return;
  265.  
  266.                 element = tree_node_find_element(node, TREE_ELEMENT_TITLE,
  267.                                                  NULL);
  268.                        
  269.                 text_cp = strdup(data->title);
  270.                 if (text_cp == NULL) {
  271.                         LOG(("malloc failed"));
  272.                         warn_user("NoMemory", 0);
  273.                         return;
  274.                 }
  275.                 tree_update_node_element(tree, element, text_cp, NULL);
  276.         } else {
  277.                 data = urldb_get_url_data(url);
  278.                 if (data == NULL)
  279.                         return;
  280.         }
  281.  
  282.         entry = icon_table;
  283.         do {
  284.                 if (entry->type == data->type) {
  285.                         if (entry->icon != NULL)
  286.                                 tree_set_node_icon(tree, node, entry->icon);
  287.                         break;
  288.                 }
  289.                 ++entry;
  290.         } while (entry->type != CONTENT_HTML);
  291.  
  292.         /* update last visit text */
  293.         element = tree_node_find_element(node, TREE_ELEMENT_LAST_VISIT, element);
  294.         tree_update_element_text(tree,
  295.                 element,
  296.                 messages_get_buff("TreeLast",
  297.                         (data->last_visit > 0) ?
  298.                         ctime((time_t *)&data->last_visit) :
  299.                         messages_get("TreeUnknown")));
  300.  
  301.  
  302.         /* update number of visits text */
  303.         element = tree_node_find_element(node, TREE_ELEMENT_VISITS, element);
  304.         tree_update_element_text(tree,
  305.                 element,
  306.                 messages_get_buff("TreeVisits", data->visits));
  307.  
  308.  
  309.         /* update thumbnail */
  310.         element = tree_node_find_element(node, TREE_ELEMENT_THUMBNAIL, element);
  311.         if (element != NULL) {
  312.                 bitmap = urldb_get_thumbnail(url);
  313.  
  314.                 if (bitmap != NULL) {
  315.                         tree_update_node_element(tree, element, NULL, bitmap);
  316.                 }
  317.         }
  318. }
  319.  
  320.  
  321. const char *tree_url_node_get_title(struct node *node)
  322. {
  323.         struct node_element *element;
  324.         element = tree_node_find_element(node, TREE_ELEMENT_TITLE, NULL);
  325.         if (element == NULL)
  326.                 return NULL;
  327.         return tree_node_element_get_text(element);
  328. }
  329.  
  330.  
  331. const char *tree_url_node_get_url(struct node *node)
  332. {
  333.         struct node_element *element;
  334.         element = tree_node_find_element(node, TREE_ELEMENT_URL, NULL);
  335.         if (element == NULL)
  336.                 return NULL;
  337.         return tree_node_element_get_text(element);
  338. }
  339.  
  340. void tree_url_node_edit_title(struct tree *tree, struct node *node)
  341. {
  342.         struct node_element *element;
  343.         element = tree_node_find_element(node, TREE_ELEMENT_TITLE, NULL);
  344.         tree_start_edit(tree, element);
  345. }
  346.  
  347. void tree_url_node_edit_url(struct tree *tree, struct node *node)
  348. {
  349.         struct node_element *element;
  350.         element = tree_node_find_element(node, TREE_ELEMENT_URL, NULL);
  351.         tree_start_edit(tree, element);
  352. }
  353.  
  354. node_callback_resp tree_url_node_callback(void *user_data,
  355.                                           struct node_msg_data *msg_data)
  356. {
  357.         struct tree *tree;
  358.         struct node_element *element;
  359.         nsurl *nsurl;
  360.         nserror error;
  361.         const char *text;
  362.         char *norm_text;
  363.         const struct url_data *data;
  364.  
  365.         /** @todo memory leaks on non-shared folder deletion. */
  366.         switch (msg_data->msg) {
  367.         case NODE_DELETE_ELEMENT_TXT:
  368.                 switch (msg_data->flag) {
  369.                         /* only history is using non-editable url
  370.                          * elements so only history deletion will run
  371.                          * this code
  372.                          */
  373.                 case TREE_ELEMENT_URL:
  374.                         /* reset URL characteristics */
  375.                         error = nsurl_create(msg_data->data.text, &nsurl);
  376.                         if (error != NSERROR_OK) {
  377.                                 warn_user("NoMemory", 0);
  378.                                 return NODE_CALLBACK_REJECT;
  379.                         }
  380.                         urldb_reset_url_visit_data(nsurl);
  381.                         nsurl_unref(nsurl);
  382.                         return NODE_CALLBACK_HANDLED;
  383.                 case TREE_ELEMENT_TITLE:
  384.                         return NODE_CALLBACK_HANDLED;
  385.                 }
  386.                 break;
  387.         case NODE_DELETE_ELEMENT_IMG:
  388.                 if (msg_data->flag == TREE_ELEMENT_THUMBNAIL ||
  389.                     msg_data->flag == TREE_ELEMENT_TITLE)
  390.                         return NODE_CALLBACK_HANDLED;
  391.                 break;
  392.         case NODE_LAUNCH:
  393.                 element = tree_node_find_element(msg_data->node,
  394.                                                  TREE_ELEMENT_URL, NULL);
  395.                 if (element != NULL) {
  396.                         text = tree_node_element_get_text(element);
  397.                         if (msg_data->flag == TREE_ELEMENT_LAUNCH_IN_TABS) {
  398.                                 msg_data->data.bw = browser_window_create(text,
  399.                                                 msg_data->data.bw, 0, true, true);
  400.                         } else {
  401.                                 browser_window_create(text, NULL, 0,
  402.                                                       true, false);
  403.                         }
  404.                         return NODE_CALLBACK_HANDLED;
  405.                 }
  406.                 break;
  407.         case NODE_ELEMENT_EDIT_FINISHING:
  408.  
  409.                 text = msg_data->data.text;
  410.  
  411.                 if (msg_data->flag == TREE_ELEMENT_URL) {
  412.                         size_t len;
  413.                         error = nsurl_create(text, &nsurl);
  414.                         if (error != NSERROR_OK) {
  415.                                 warn_user("NoMemory", 0);
  416.                                 return NODE_CALLBACK_REJECT;
  417.                         }
  418.                         error = nsurl_get(nsurl, NSURL_WITH_FRAGMENT,
  419.                                         &norm_text, &len);
  420.                         if (error != NSERROR_OK) {
  421.                                 warn_user("NoMemory", 0);
  422.                                 return NODE_CALLBACK_REJECT;
  423.                         }
  424.  
  425.                         msg_data->data.text = norm_text;
  426.  
  427.                         data = urldb_get_url_data(nsurl);
  428.                         if (data == NULL) {
  429.                                 urldb_add_url(nsurl);
  430.                                 urldb_set_url_persistence(nsurl, true);
  431.                                 data = urldb_get_url_data(nsurl);
  432.                                 if (data == NULL) {
  433.                                         nsurl_unref(nsurl);
  434.                                         return NODE_CALLBACK_REJECT;
  435.                                 }
  436.                         }
  437.                         tree = user_data;
  438.                         tree_update_URL_node(tree, msg_data->node,
  439.                                              nsurl, NULL);
  440.                         nsurl_unref(nsurl);
  441.                 }
  442.                 else if (msg_data->flag == TREE_ELEMENT_TITLE) {
  443.                         while (isspace(*text))
  444.                                 text++;
  445.                         norm_text = strdup(text);
  446.                         if (norm_text == NULL) {
  447.                                 LOG(("malloc failed"));
  448.                                 warn_user("NoMemory", 0);
  449.                                 return NODE_CALLBACK_REJECT;
  450.                         }
  451.                         /* don't allow zero length entry text, return
  452.                            false */
  453.                         if (norm_text[0] == '\0') {
  454.                                 warn_user("NoNameError", 0);
  455.                                 msg_data->data.text = NULL;
  456.                                 return NODE_CALLBACK_CONTINUE;
  457.                         }
  458.                         msg_data->data.text = norm_text;
  459.                 }
  460.  
  461.                 return NODE_CALLBACK_HANDLED;
  462.         default:
  463.                 break;
  464.         }
  465.         return NODE_CALLBACK_NOT_HANDLED;
  466. }
  467.  
  468. typedef struct {
  469.         struct tree *tree;
  470.         struct node *directory;
  471.         tree_node_user_callback callback;
  472.         void *callback_data;
  473.         bool last_was_h4;
  474.         dom_string *title;
  475. } tree_url_load_ctx;
  476.  
  477. static void tree_url_load_directory(dom_node *ul, tree_url_load_ctx *ctx);
  478.  
  479. /**
  480.  * Parse an entry represented as a li.
  481.  *
  482.  * \param  li         DOM node for parsed li
  483.  * \param  directory  directory to add this entry to
  484.  */
  485. static void tree_url_load_entry(dom_node *li, tree_url_load_ctx *ctx)
  486. {
  487.         dom_node *a;
  488.         dom_string *title1;
  489.         dom_string *url1;
  490.         char *title, *url2;
  491.         nsurl *url;
  492.         const struct url_data *data;
  493.         struct node *entry;
  494.         dom_exception derror;
  495.         nserror error;
  496.  
  497.         /* The li must contain an "a" element */
  498.         a = libdom_find_first_element(li, corestring_lwc_a);
  499.         if (a == NULL) {
  500.                 warn_user("TreeLoadError", "(Missing <a> in <li>)");
  501.                 return;
  502.         }
  503.  
  504.         derror = dom_node_get_text_content(a, &title1);
  505.         if (derror != DOM_NO_ERR) {
  506.                 warn_user("TreeLoadError", "(No title)");
  507.                 dom_node_unref(a);
  508.                 return;
  509.         }
  510.  
  511.         derror = dom_element_get_attribute(a, corestring_dom_href, &url1);
  512.         if (derror != DOM_NO_ERR || url1 == NULL) {
  513.                 warn_user("TreeLoadError", "(No URL)");
  514.                 dom_string_unref(title1);
  515.                 dom_node_unref(a);
  516.                 return;
  517.         }
  518.  
  519.         if (title1 != NULL) {
  520.                 title = strndup(dom_string_data(title1),
  521.                                 dom_string_byte_length(title1));
  522.                 dom_string_unref(title1);
  523.         } else {
  524.                 title = strdup("");
  525.         }
  526.         if (title == NULL) {
  527.                 warn_user("NoMemory", NULL);
  528.                 dom_string_unref(url1);
  529.                 dom_node_unref(a);
  530.                 return;
  531.         }
  532.  
  533.         /* We're loading external input.
  534.          * This may be garbage, so attempt to normalise via nsurl
  535.          */
  536.         url2 = strndup(dom_string_data(url1), dom_string_byte_length(url1));
  537.         if (url2 == NULL) {
  538.                 warn_user("NoMemory", NULL);
  539.                 free(title);
  540.                 dom_string_unref(url1);
  541.                 dom_node_unref(a);
  542.                 return;
  543.         }
  544.  
  545.         dom_string_unref(url1);
  546.  
  547.         error = nsurl_create(url2, &url);
  548.  
  549.         free(url2);
  550.  
  551.         if (error != NSERROR_OK) {
  552.                 LOG(("Failed normalising '%s'", url2));
  553.  
  554.                 warn_user("NoMemory", NULL);
  555.  
  556.                 free(title);
  557.                 dom_node_unref(a);
  558.  
  559.                 return;
  560.         }
  561.  
  562.         data = urldb_get_url_data(url);
  563.         if (data == NULL) {
  564.                 /* No entry in database, so add one */
  565.                 urldb_add_url(url);
  566.                 /* now attempt to get url data */
  567.                 data = urldb_get_url_data(url);
  568.         }
  569.         if (data == NULL) {
  570.                 nsurl_unref(url);
  571.                 free(title);
  572.                 dom_node_unref(a);
  573.  
  574.                 return;
  575.         }
  576.  
  577.         /* Make this URL persistent */
  578.         urldb_set_url_persistence(url, true);
  579.  
  580.         /* Force the title in the hotlist */
  581.         urldb_set_url_title(url, title);
  582.  
  583.         entry = tree_create_URL_node(ctx->tree, ctx->directory, url, title,
  584.                                      ctx->callback, ctx->callback_data);
  585.  
  586.         if (entry == NULL) {
  587.                 /** \todo why isn't this fatal? */
  588.                 warn_user("NoMemory", 0);
  589.         } else {
  590.                 tree_update_URL_node(ctx->tree, entry, url, data);
  591.         }
  592.  
  593.         nsurl_unref(url);
  594.         free(title);
  595.         dom_node_unref(a);
  596. }
  597.  
  598. static bool tree_url_load_directory_cb(dom_node *node, void *ctx)
  599. {
  600.         tree_url_load_ctx *tctx = ctx;
  601.         dom_string *name;
  602.         dom_exception error;
  603.  
  604.         /* The ul may contain entries as a li, or directories as
  605.          * an h4 followed by a ul. Non-element nodes may be present
  606.          * (eg. text, comments), and are ignored. */
  607.  
  608.         error = dom_node_get_node_name(node, &name);
  609.         if (error != DOM_NO_ERR || name == NULL)
  610.                 return false;
  611.  
  612.         if (dom_string_caseless_lwc_isequal(name, corestring_lwc_li)) {
  613.                 /* entry */
  614.                 tree_url_load_entry(node, tctx);
  615.                 tctx->last_was_h4 = false;
  616.         } else if (dom_string_caseless_lwc_isequal(name, corestring_lwc_h4)) {
  617.                 /* directory (a) */
  618.                 dom_string *title;
  619.  
  620.                 error = dom_node_get_text_content(node, &title);
  621.                 if (error != DOM_NO_ERR || title == NULL) {
  622.                         warn_user("TreeLoadError", "(Empty <h4> "
  623.                                         "or memory exhausted.)");
  624.                         dom_string_unref(name);
  625.                         return false;
  626.                 }
  627.  
  628.                 if (tctx->title != NULL)
  629.                         dom_string_unref(tctx->title);
  630.                 tctx->title = title;
  631.                 tctx->last_was_h4 = true;
  632.         } else if (tctx->last_was_h4 && dom_string_caseless_lwc_isequal(name,
  633.                         corestring_lwc_ul)) {
  634.                 /* directory (b) */
  635.                 dom_string *id;
  636.                 bool dir_is_default;
  637.                 struct node *dir;
  638.                 char *title;
  639.                 tree_url_load_ctx new_ctx;
  640.  
  641.                 error = dom_element_get_attribute(node, corestring_dom_id, &id);
  642.                 if (error != DOM_NO_ERR) {
  643.                         dom_string_unref(name);
  644.                         return false;
  645.                 }
  646.  
  647.                 if (id != NULL) {
  648.                         dir_is_default = dom_string_caseless_lwc_isequal(id,
  649.                                         corestring_lwc_default);
  650.  
  651.                         dom_string_unref(id);
  652.                 } else {
  653.                         dir_is_default = false;
  654.                 }
  655.  
  656.                 title = strndup(dom_string_data(tctx->title),
  657.                                 dom_string_byte_length(tctx->title));
  658.                 if (title == NULL) {
  659.                         dom_string_unref(name);
  660.                         return false;
  661.                 }
  662.  
  663.                 dir = tree_create_folder_node(tctx->tree, tctx->directory,
  664.                                 title, true, false, false);
  665.                 if (dir == NULL) {
  666.                         dom_string_unref(name);
  667.                         return false;
  668.                 }
  669.  
  670.                 if (dir_is_default)
  671.                         tree_set_default_folder_node(tctx->tree, dir);
  672.  
  673.                 if (tctx->callback != NULL)
  674.                         tree_set_node_user_callback(dir, tctx->callback,
  675.                                                     tctx->callback_data);
  676.  
  677.                 if (folder_icon != NULL)
  678.                         tree_set_node_icon(tctx->tree, dir, folder_icon);
  679.  
  680.                 new_ctx.tree = tctx->tree;
  681.                 new_ctx.directory = dir;
  682.                 new_ctx.callback = tctx->callback;
  683.                 new_ctx.callback_data = tctx->callback_data;
  684.                 new_ctx.last_was_h4 = false;
  685.                 new_ctx.title = NULL;
  686.  
  687.                 tree_url_load_directory(node, &new_ctx);
  688.  
  689.                 if (new_ctx.title != NULL) {
  690.                         dom_string_unref(new_ctx.title);
  691.                         new_ctx.title = NULL;
  692.                 }
  693.                 tctx->last_was_h4 = false;
  694.         } else {
  695.                 tctx->last_was_h4 = false;
  696.         }
  697.  
  698.         dom_string_unref(name);
  699.  
  700.         return true;
  701. }
  702.  
  703. /**
  704.  * Parse a directory represented as a ul.
  705.  *
  706.  * \param  ul         DOM node for parsed ul
  707.  * \param  directory  directory to add this directory to
  708.  */
  709. static void tree_url_load_directory(dom_node *ul, tree_url_load_ctx *ctx)
  710. {
  711.         assert(ul != NULL);
  712.         assert(ctx != NULL);
  713.         assert(ctx->directory != NULL);
  714.  
  715.         libdom_iterate_child_elements(ul, tree_url_load_directory_cb, ctx);
  716. }
  717.  
  718. /**
  719.  * Loads an url tree from a specified file.
  720.  *
  721.  * \param  filename     name of file to read
  722.  * \param  tree         empty tree which data will be read into
  723.  * \return the file represented as a tree, or NULL on failure
  724.  */
  725. bool tree_urlfile_load(const char *filename, struct tree *tree,
  726.                        tree_node_user_callback callback, void *callback_data)
  727. {
  728.         dom_document *document;
  729.         dom_node *html, *body, *ul;
  730.         struct node *root;
  731.         nserror error;
  732.         tree_url_load_ctx ctx;
  733.  
  734.         if (filename == NULL) {
  735.                 return false;
  736.         }
  737.  
  738.         error = libdom_parse_file(filename, "iso-8859-1", &document);
  739.         if (error != NSERROR_OK) {
  740.                 if (error != NSERROR_NOT_FOUND) {
  741.                         warn_user("TreeLoadError", messages_get("ParsingFail"));
  742.                 }
  743.                 return false;
  744.         }
  745.  
  746.         html = libdom_find_first_element((dom_node *) document,
  747.                         corestring_lwc_html);
  748.         if (html == NULL) {
  749.                 dom_node_unref(document);
  750.                 warn_user("TreeLoadError", "(<html> not found)");
  751.                 return false;
  752.         }
  753.  
  754.         body = libdom_find_first_element(html, corestring_lwc_body);
  755.         if (body == NULL) {
  756.                 dom_node_unref(html);
  757.                 dom_node_unref(document);
  758.                 warn_user("TreeLoadError", "(<html>...<body> not found)");
  759.                 return false;
  760.         }
  761.  
  762.         ul = libdom_find_first_element(body, corestring_lwc_ul);
  763.         if (ul == NULL) {
  764.                 dom_node_unref(body);
  765.                 dom_node_unref(html);
  766.                 dom_node_unref(document);
  767.                 warn_user("TreeLoadError",
  768.                           "(<html>...<body>...<ul> not found.)");
  769.                 return false;
  770.         }
  771.  
  772.         root = tree_get_root(tree);
  773.  
  774.         ctx.tree = tree;
  775.         ctx.directory = root;
  776.         ctx.callback = callback;
  777.         ctx.callback_data = callback_data;
  778.         ctx.last_was_h4 = false;
  779.         ctx.title = NULL;
  780.  
  781.         tree_url_load_directory(ul, &ctx);
  782.         tree_set_node_expanded(tree, root, true, false, false);
  783.  
  784.         if (ctx.title != NULL) {
  785.                 dom_string_unref(ctx.title);
  786.                 ctx.title = NULL;
  787.         }
  788.  
  789.         dom_node_unref(ul);
  790.         dom_node_unref(body);
  791.         dom_node_unref(html);
  792.         dom_node_unref(document);
  793.  
  794.         return true;
  795. }
  796.  
  797. /**
  798.  * Add an entry to the HTML tree for saving.
  799.  *
  800.  * The node must contain a sequence of node_elements in the following order:
  801.  *
  802.  * \param  entry  hotlist entry to add
  803.  * \param  fp     File to write to
  804.  * \return  true on success, false on memory exhaustion
  805.  */
  806. static bool tree_url_save_entry(struct node *entry, FILE *fp)
  807. {
  808.         const char *href, *text;
  809.         char *latin1_href, *latin1_text;
  810.         utf8_convert_ret ret;
  811.  
  812.         text = tree_url_node_get_title(entry);
  813.         if (text == NULL)
  814.                 return false;
  815.  
  816.         href = tree_url_node_get_url(entry);
  817.         if (href == NULL)
  818.                 return false;
  819.  
  820.         ret = utf8_to_html(text, "iso-8859-1", strlen(text), &latin1_text);
  821.         if (ret != UTF8_CONVERT_OK)
  822.                 return false;
  823.  
  824.         ret = utf8_to_html(href, "iso-8859-1", strlen(href), &latin1_href);
  825.         if (ret != UTF8_CONVERT_OK) {
  826.                 free(latin1_text);
  827.                 return false;
  828.         }
  829.  
  830.         fprintf(fp, "<li><a href=\"%s\">%s</a></li>",
  831.                         latin1_href, latin1_text);
  832.  
  833.         free(latin1_href);
  834.         free(latin1_text);
  835.  
  836.         return true;
  837. }
  838.  
  839. /**
  840.  * Add a directory to the HTML tree for saving.
  841.  *
  842.  * \param  directory  hotlist directory to add
  843.  * \param  fp         File to write to
  844.  * \return  true on success, false on memory exhaustion
  845.  */
  846. static bool tree_url_save_directory(struct node *directory, FILE *fp)
  847. {
  848.         struct node *child;
  849.  
  850.         fputs("<ul", fp);
  851.         if (tree_node_is_default(directory))
  852.                 fputs(" id=\"default\"", fp);
  853.         fputc('>', fp);
  854.  
  855.         if (tree_node_get_child(directory) != NULL)
  856.                 fputc('\n', fp);
  857.  
  858.         for (child = tree_node_get_child(directory); child != NULL;
  859.              child = tree_node_get_next(child)) {
  860.                 if (tree_node_is_folder(child) == false) {
  861.                         /* entry */
  862.                         if (tree_url_save_entry(child, fp) == false)
  863.                                 return false;
  864.                 } else {
  865.                         /* directory */
  866.                         /* invalid HTML */
  867.                         const char *text;
  868.                         char *latin1_text;
  869.                         utf8_convert_ret ret;
  870.  
  871.                         text = tree_url_node_get_title(child);
  872.                         if (text == NULL)
  873.                                 return false;
  874.  
  875.                         ret = utf8_to_html(text, "iso-8859-1",
  876.                                         strlen(text), &latin1_text);
  877.                         if (ret != UTF8_CONVERT_OK)
  878.                                 return false;
  879.  
  880.                         fprintf(fp, "<h4>%s</h4>\n", latin1_text);
  881.  
  882.                         free(latin1_text);
  883.  
  884.                         if (tree_url_save_directory(child, fp) == false)
  885.                                 return false;
  886.                 }
  887.  
  888.                 fputc('\n', fp);
  889.         }
  890.  
  891.         fputs("</ul>", fp);
  892.  
  893.         return true;
  894. }
  895.  
  896.  
  897. /**
  898.  * Perform a save to a specified file in the form of a html page
  899.  *
  900.  * \param filename      the file to save to
  901.  * \param page_title    title of the page
  902.  */
  903. bool tree_urlfile_save(struct tree *tree, const char *filename,
  904.                        const char *page_title)
  905. {
  906.         FILE *fp;
  907.  
  908.         fp = fopen(filename, "w");
  909.         if (fp == NULL)
  910.                 return NULL;
  911.  
  912.         /* Unfortunately the Browse Hotlist format is invalid HTML,
  913.          * so this is a lie.
  914.          */
  915.         fputs("<!DOCTYPE html "
  916.                 "PUBLIC \"//W3C/DTD HTML 4.01//EN\" "
  917.                 "\"http://www.w3.org/TR/html4/strict.dtd\">\n", fp);
  918.         fputs("<html>\n<head>\n", fp);
  919.         fputs("<meta http-equiv=\"Content-Type\" "
  920.                 "content=\"text/html; charset=iso-8859-1\">\n", fp);
  921.         fprintf(fp, "<title>%s</title>\n", page_title);
  922.         fputs("</head>\n<body>", fp);
  923.  
  924.         if (tree_url_save_directory(tree_get_root(tree), fp) == false) {
  925.                 warn_user("HotlistSaveError", 0);
  926.                 fclose(fp);
  927.                 return false;
  928.         }
  929.  
  930.         fputs("</body>\n</html>\n", fp);
  931.  
  932.         fclose(fp);
  933.  
  934.         return true;
  935. }
  936.  
  937.