[geary] Moved DOM function into Util.DOM namespace



commit 591dd2620c70012b64837e6e33074d5f9f479df8
Author: Niels De Graef <nielsdegraef gmail com>
Date:   Sun May 8 22:41:51 2016 +0200

    Moved DOM function into Util.DOM namespace
    
    Signed-off-by: Niels De Graef <nielsdegraef gmail com>
    
    Bug 766100

 src/client/accounts/add-edit-page.vala             |    2 +-
 src/client/composer/composer-widget.vala           |   12 +-
 .../conversation-viewer/conversation-viewer.vala   |   77 ++-
 .../conversation-viewer/conversation-web-view.vala |    4 +-
 src/client/util/util-webkit.vala                   |  669 ++++++++++----------
 5 files changed, 390 insertions(+), 374 deletions(-)
---
diff --git a/src/client/accounts/add-edit-page.vala b/src/client/accounts/add-edit-page.vala
index e285a48..49f6b2b 100644
--- a/src/client/accounts/add-edit-page.vala
+++ b/src/client/accounts/add-edit-page.vala
@@ -590,7 +590,7 @@ public class AddEditPage : Gtk.Box {
     
     private void on_signature_stack_changed() {
         if (signature_stack.visible_child_name == "preview_window")
-            preview_webview.load_html_string(smart_escape(email_signature, true), "");
+            preview_webview.load_html_string(Util.DOM.smart_escape(email_signature, true), "");
     }
 
     private uint16 get_default_smtp_port() {
diff --git a/src/client/composer/composer-widget.vala b/src/client/composer/composer-widget.vala
index db571c1..18af0e5 100644
--- a/src/client/composer/composer-widget.vala
+++ b/src/client/composer/composer-widget.vala
@@ -804,7 +804,7 @@ public class ComposerWidget : Gtk.EventBox {
         // Ensure the editor is in correct mode re HTML
         on_compose_as_html();
 
-        bind_event(editor,"a", "click", (Callback) on_link_clicked, this);
+        Util.DOM.bind_event(editor,"a", "click", (Callback) on_link_clicked, this);
         update_actions();
         on_show_extended();
         
@@ -1051,7 +1051,7 @@ public class ComposerWidget : Gtk.EventBox {
                     set_cursor();
                     return;
                 }
-                signature = smart_escape(signature, false);
+                signature = Util.DOM.smart_escape(signature, false);
             } catch (Error error) {
                 debug("Error reading signature file %s: %s", signature_file.get_path(), error.message);
                 set_cursor();
@@ -1063,7 +1063,7 @@ public class ComposerWidget : Gtk.EventBox {
                 set_cursor();
                 return;
             }
-            signature = smart_escape(signature, true);
+            signature = Util.DOM.smart_escape(signature, true);
         }
         
         if (body_html == null)
@@ -1287,7 +1287,7 @@ public class ComposerWidget : Gtk.EventBox {
         container.vanish();
         is_closing = true;
         
-        linkify_document(editor.get_dom_document());
+        Util.DOM.linkify_document(editor.get_dom_document());
         
         // Perform send.
         try {
@@ -2000,7 +2000,7 @@ public class ComposerWidget : Gtk.EventBox {
         dialog.destroy();
         
         // Re-bind to anchor links.  This must be done every time link have changed.
-        bind_event(editor,"a", "click", (Callback) on_link_clicked, this);
+        Util.DOM.bind_event(editor,"a", "click", (Callback) on_link_clicked, this);
     }
     
     private string get_html() {
@@ -2009,7 +2009,7 @@ public class ComposerWidget : Gtk.EventBox {
     }
     
     private string get_text() {
-        return html_to_flowed_text((WebKit.DOM.HTMLElement) editor.get_dom_document()
+        return Util.DOM.html_to_flowed_text((WebKit.DOM.HTMLElement) editor.get_dom_document()
             .get_element_by_id(BODY_ID));
     }
     
diff --git a/src/client/conversation-viewer/conversation-viewer.vala 
b/src/client/conversation-viewer/conversation-viewer.vala
index 3c1d9f0..2d03754 100644
--- a/src/client/conversation-viewer/conversation-viewer.vala
+++ b/src/client/conversation-viewer/conversation-viewer.vala
@@ -294,7 +294,7 @@ public class ConversationViewer : Gtk.Box {
                     ancestor = ancestor_node.get_parent_element();
                 // If the selection is part of a plain text message, we have to stick it in
                 // an appropriately styled div, so that new lines are preserved.
-                if (is_descendant_of(ancestor, ".plaintext")) {
+                if (Util.DOM.is_descendant_of(ancestor, ".plaintext")) {
                     dummy.get_class_list().add("plaintext");
                     dummy.set_attribute("style", "white-space: pre-wrap;");
                     include_dummy = true;
@@ -750,23 +750,40 @@ public class ConversationViewer : Gtk.Box {
         });
         
         // Attach to the click events for hiding/showing quotes, opening the menu, and so forth.
-        bind_event(web_view, ".email", "contextmenu", (Callback) on_context_menu, this);
-        bind_event(web_view, ".quote_container > .hider", "click", (Callback) on_hide_quote_clicked);
-        bind_event(web_view, ".quote_container > .shower", "click", (Callback) on_show_quote_clicked);
-        bind_event(web_view, ".email_container .menu", "click", (Callback) on_menu_clicked, this);
-        bind_event(web_view, ".email_container .starred", "click", (Callback) on_unstar_clicked, this);
-        bind_event(web_view, ".email_container .unstarred", "click", (Callback) on_star_clicked, this);
-        bind_event(web_view, ".email_container .draft_edit .button", "click", (Callback) on_draft_edit_menu, 
this);
-        bind_event(web_view, ".header .field .value", "click", (Callback) on_value_clicked, this);
-        bind_event(web_view, ".email:not(:only-of-type) .header_container, .email .email 
.header_container","click", (Callback) on_body_toggle_clicked, this);
-        bind_event(web_view, ".email .compressed_note", "click", (Callback) on_body_toggle_clicked, this);
-        bind_event(web_view, ".attachment_container .attachment", "click", (Callback) on_attachment_clicked, 
this);
-        bind_event(web_view, ".attachment_container .attachment", "contextmenu", (Callback) 
on_attachment_menu, this);
-        bind_event(web_view, "." + DATA_IMAGE_CLASS, "contextmenu", (Callback) on_data_image_menu_handler, 
this);
-        bind_event(web_view, ".remote_images .show_images", "click", (Callback) on_show_images, this);
-        bind_event(web_view, ".remote_images .show_from", "click", (Callback) on_show_images_from, this);
-        bind_event(web_view, ".remote_images .close_show_images", "click", (Callback) on_close_show_images, 
this);
-        bind_event(web_view, ".body a", "click", (Callback) on_link_clicked, this);
+        Util.DOM.bind_event(web_view, ".email", "contextmenu",
+                (Callback) on_context_menu, this);
+        Util.DOM.bind_event(web_view, ".quote_container > .hider", "click",
+                (Callback) on_hide_quote_clicked);
+        Util.DOM.bind_event(web_view, ".quote_container > .shower", "click",
+                (Callback) on_show_quote_clicked);
+        Util.DOM.bind_event(web_view, ".email_container .menu", "click",
+                (Callback) on_menu_clicked, this);
+        Util.DOM.bind_event(web_view, ".email_container .starred", "click",
+                (Callback) on_unstar_clicked, this);
+        Util.DOM.bind_event(web_view, ".email_container .unstarred", "click",
+                (Callback) on_star_clicked, this);
+        Util.DOM.bind_event(web_view, ".email_container .draft_edit .button", "click",
+                (Callback) on_draft_edit_menu, this);
+        Util.DOM.bind_event(web_view, ".header .field .value", "click",
+                (Callback) on_value_clicked, this);
+        Util.DOM.bind_event(web_view, ".email:not(:only-of-type) .header_container, .email .email 
.header_container","click",
+                (Callback) on_body_toggle_clicked, this);
+        Util.DOM.bind_event(web_view, ".email .compressed_note", "click",
+                (Callback) on_body_toggle_clicked, this);
+        Util.DOM.bind_event(web_view, ".attachment_container .attachment", "click",
+                (Callback) on_attachment_clicked, this);
+        Util.DOM.bind_event(web_view, ".attachment_container .attachment", "contextmenu",
+                (Callback) on_attachment_menu, this);
+        Util.DOM.bind_event(web_view, "." + DATA_IMAGE_CLASS, "contextmenu",
+                (Callback) on_data_image_menu_handler, this);
+        Util.DOM.bind_event(web_view, ".remote_images .show_images", "click",
+                (Callback) on_show_images, this);
+        Util.DOM.bind_event(web_view, ".remote_images .show_from", "click",
+                (Callback) on_show_images_from, this);
+        Util.DOM.bind_event(web_view, ".remote_images .close_show_images", "click",
+                (Callback) on_close_show_images, this);
+        Util.DOM.bind_event(web_view, ".body a", "click",
+                (Callback) on_link_clicked, this);
         
         // Update the search results
         if (conversation_find_bar.visible)
@@ -897,7 +914,7 @@ public class ConversationViewer : Gtk.Box {
             for (int i = 0; i < style_sheets.length; i++) {
                 WebKit.DOM.StyleSheet style_sheet = style_sheets.item(i);
                 WebKit.DOM.Element style_element = (WebKit.DOM.Element) style_sheet.owner_node;
-                if (closest_ancestor(style_element, ".email") == div_message)
+                if (Util.DOM.closest_ancestor(style_element, ".email") == div_message)
                     style_element.set_text_content(scope_style_sheet(style_sheet, div_id));
             }
         } catch (Error error) {
@@ -1010,7 +1027,7 @@ public class ConversationViewer : Gtk.Box {
         return "<img alt=\"%s\" class=\"%s %s\" src=\"%s\" replaced-id=\"%s\" %s />".printf(
             Geary.HTML.escape_markup(filename),
             DATA_IMAGE_CLASS, REPLACED_IMAGE_CLASS,
-            assemble_data_uri(mime_type, rotated_image),
+            Util.DOM.assemble_data_uri(mime_type, rotated_image),
             Geary.HTML.escape_markup(replaced_image.id),
             escaped_content_id != null ? @"cid=\"$escaped_content_id\"" : "");
     }
@@ -1185,7 +1202,7 @@ public class ConversationViewer : Gtk.Box {
             if (element.webkit_matches_selector(".email")) {
                 email_element = element;
             } else {
-                email_element = closest_ancestor(element, ".email");
+                email_element = Util.DOM.closest_ancestor(element, ".email");
             }
         } catch (Error error) {
             debug("Failed to find div.email from element: %s", error.message);
@@ -1385,7 +1402,7 @@ public class ConversationViewer : Gtk.Box {
 
     private bool is_hidden_email(WebKit.DOM.Element element) {
         try {
-            WebKit.DOM.HTMLElement? email_element = closest_ancestor(element, ".email");
+            WebKit.DOM.HTMLElement? email_element = Util.DOM.closest_ancestor(element, ".email");
             if (email_element == null)
                 return false;
             
@@ -1407,7 +1424,7 @@ public class ConversationViewer : Gtk.Box {
             if (web_view.get_dom_document().get_body().get_class_list().contains("nohide"))
                 return;
             
-            WebKit.DOM.HTMLElement? email_element = closest_ancestor(element, ".email");
+            WebKit.DOM.HTMLElement? email_element = Util.DOM.closest_ancestor(element, ".email");
             if (email_element == null)
                 return;
             
@@ -1429,7 +1446,7 @@ public class ConversationViewer : Gtk.Box {
 
     private static void on_show_images(WebKit.DOM.Element element, WebKit.DOM.Event event,
         ConversationViewer conversation_viewer) {
-        WebKit.DOM.HTMLElement? email_element = closest_ancestor(element, ".email");
+        WebKit.DOM.HTMLElement? email_element = Util.DOM.closest_ancestor(element, ".email");
         if (email_element != null)
             conversation_viewer.show_images_email(email_element, true);
     }
@@ -1519,7 +1536,7 @@ public class ConversationViewer : Gtk.Box {
     
     private static void on_close_show_images(WebKit.DOM.Element element, WebKit.DOM.Event event,
         ConversationViewer conversation_viewer) {
-        WebKit.DOM.HTMLElement? remote_images = closest_ancestor(element, ".remote_images");
+        WebKit.DOM.HTMLElement? remote_images = Util.DOM.closest_ancestor(element, ".remote_images");
         if (remote_images != null) {
             try {
                 remote_images.get_class_list().remove("show");
@@ -1567,7 +1584,7 @@ public class ConversationViewer : Gtk.Box {
         } catch (Error error) {
             warning("Error showing link warning dialog: %s", error.message);
         }
-        bind_event(web_view, ".link_warning .close_link_warning, .link_warning a", "click",
+        Util.DOM.bind_event(web_view, ".link_warning .close_link_warning, .link_warning a", "click",
             (Callback) on_close_link_warning, this);
         return true;
     }
@@ -1655,7 +1672,7 @@ public class ConversationViewer : Gtk.Box {
     private static void on_close_link_warning(WebKit.DOM.Element element, WebKit.DOM.Event event,
         ConversationViewer conversation_viewer) {
         try {
-            WebKit.DOM.Element warning_div = closest_ancestor(element, ".link_warning");
+            WebKit.DOM.Element warning_div = Util.DOM.closest_ancestor(element, ".link_warning");
             WebKit.DOM.Element link = (WebKit.DOM.Element) warning_div.get_next_sibling();
             link.remove_attribute("warning");
             warning_div.parent_node.remove_child(warning_div);
@@ -1960,7 +1977,7 @@ public class ConversationViewer : Gtk.Box {
                 WebKit.DOM.Node parent = blockquote_node.get_parent_node();
 
                 // Make sure this is a top level blockquote.
-                if (node_is_child_of(blockquote_node, "BLOCKQUOTE")) {
+                if (Util.DOM.node_is_child_of(blockquote_node, "BLOCKQUOTE")) {
                     continue;
                 }
 
@@ -2019,7 +2036,7 @@ public class ConversationViewer : Gtk.Box {
                     // Replace the SRC to a data URI, the class to a known label for the popup menu,
                     // and the ALT to its filename, if supplied
                     img.remove_attribute("src");  // Work around a WebKitGTK+ crash. Bug 764152
-                    img.set_attribute("src", assemble_data_uri(mimetype, image_content));
+                    img.set_attribute("src", Util.DOM.assemble_data_uri(mimetype, image_content));
                     img.set_attribute("class", DATA_IMAGE_CLASS);
                     if (!Geary.String.is_empty(filename))
                         img.set_attribute("alt", filename);
@@ -2069,7 +2086,7 @@ public class ConversationViewer : Gtk.Box {
             WebKit.DOM.HTMLElement div = div_list.item(i) as WebKit.DOM.HTMLElement;
             string inner_html = div.get_inner_html();
             if ((sig_regex.match(inner_html) || alternate_sig_regex.match(inner_html)) &&
-                !node_is_child_of(div, "BLOCKQUOTE")) {
+                !Util.DOM.node_is_child_of(div, "BLOCKQUOTE")) {
                 break;
             }
         }
diff --git a/src/client/conversation-viewer/conversation-web-view.vala 
b/src/client/conversation-viewer/conversation-web-view.vala
index 50ed18c..57dcbd0 100644
--- a/src/client/conversation-viewer/conversation-web-view.vala
+++ b/src/client/conversation-viewer/conversation-web-view.vala
@@ -247,7 +247,7 @@ public class ConversationWebView : StylishWebView {
             // Then set the source to a data url.
             WebKit.DOM.HTMLImageElement img = Util.DOM.select(get_dom_document(), selector)
                 as WebKit.DOM.HTMLImageElement;
-            img.set_attribute("src", assemble_data_uri("image/png", buffer));
+            img.set_attribute("src", Util.DOM.assemble_data_uri("image/png", buffer));
         } catch (Error error) {
             warning("Failed to load icon '%s': %s", icon_name, error.message);
         }
@@ -289,7 +289,7 @@ public class ConversationWebView : StylishWebView {
             Geary.Memory.Buffer buffer = new Geary.Memory.ByteBuffer.take((owned) content,
                 content_length);
             
-            img.set_attribute("src", assemble_data_uri("image/png", buffer));
+            img.set_attribute("src", Util.DOM.assemble_data_uri("image/png", buffer));
         } catch (Error error) {
             warning("Failed to load image '%s': %s", filename, error.message);
         }
diff --git a/src/client/util/util-webkit.vala b/src/client/util/util-webkit.vala
index 98b06ac..b542449 100644
--- a/src/client/util/util-webkit.vala
+++ b/src/client/util/util-webkit.vala
@@ -15,7 +15,6 @@ public const string PROTOCOL_REGEX = "^(aim|apt|bitcoin|cvs|ed2k|ftp|file|finger
 public const string QUOTE_START = "‘";
 public const string QUOTE_END = "’";
 
-// TODO Move these other functions and variables into this namespace.
 namespace Util.DOM {
     public WebKit.DOM.HTMLElement? select(WebKit.DOM.Node node, string selector) {
         try {
@@ -46,7 +45,7 @@ namespace Util.DOM {
             class_list.remove(clas);
         }
     }
-    
+
     // Returns the text contained in the DOM document, after ignoring tags of type "exclude"
     // and padding newlines where appropriate. Used to scan for attachment keywords.
     public string get_text_representation(WebKit.DOM.Document doc, string exclude) {
@@ -54,14 +53,14 @@ namespace Util.DOM {
         if (copy == null) {
             return "";
         }
-        
+
         // Keep deleting the next excluded element until there are none left
         while (true) {
             WebKit.DOM.HTMLElement? current = Util.DOM.select(copy, exclude);
             if (current == null) {
                 break;
             }
-            
+
             WebKit.DOM.Node parent = current.get_parent_node();
             try {
                 parent.remove_child(current);
@@ -70,7 +69,7 @@ namespace Util.DOM {
                 break;
             }
         }
-        
+
         WebKit.DOM.NodeList node_list;
         try {
             node_list = copy.query_selector_all("br");
@@ -78,7 +77,7 @@ namespace Util.DOM {
             debug("Error finding <br>s: %s", error.message);
             return copy.get_inner_text();
         }
-        
+
         // Replace <br> tags with newlines
         for (int i = 0; i < node_list.length; ++i) {
             WebKit.DOM.Node br = node_list.item(i);
@@ -89,14 +88,14 @@ namespace Util.DOM {
                 debug("Error replacing <br>: %s", error.message);
             }
         }
-        
+
         try {
             node_list = copy.query_selector_all("div");
         } catch (Error error) {
             debug("Error finding <div>s: %s", error.message);
             return copy.get_inner_text();
         }
-        
+
         // Pad each <div> with newlines
         for (int i = 0; i < node_list.length; ++i) {
             WebKit.DOM.Node div = node_list.item(i);
@@ -109,378 +108,378 @@ namespace Util.DOM {
         }
         return copy.get_inner_text();
     }
-}
 
-public void bind_event(WebKit.WebView view, string selector, string event, Callback callback,
-    Object? extra = null) {
-    try {
-        WebKit.DOM.NodeList node_list = view.get_dom_document().query_selector_all(selector);
-        for (int i = 0; i < node_list.length; ++i) {
-            WebKit.DOM.EventTarget node = node_list.item(i) as WebKit.DOM.EventTarget;
-            node.remove_event_listener(event, callback, false);
-            node.add_event_listener(event, callback, false, extra);
+    public void bind_event(WebKit.WebView view, string selector, string event, Callback callback,
+        Object? extra = null) {
+        try {
+            WebKit.DOM.NodeList node_list = view.get_dom_document().query_selector_all(selector);
+            for (int i = 0; i < node_list.length; ++i) {
+                WebKit.DOM.EventTarget node = node_list.item(i) as WebKit.DOM.EventTarget;
+                node.remove_event_listener(event, callback, false);
+                node.add_event_listener(event, callback, false, extra);
+            }
+        } catch (Error error) {
+            warning("Error setting up click handlers: %s", error.message);
         }
-    } catch (Error error) {
-        warning("Error setting up click handlers: %s", error.message);
     }
-}
-
-// Linkifies plain text links in an HTML document.
-public void linkify_document(WebKit.DOM.Document document) {
-    linkify_recurse(document, document.get_body());
-}
 
-// Validates a URL.
-// Ensures the URL begins with a valid protocol specifier.  (If not, we don't
-// want to linkify it.)
-// Note that the output of this will place '\01' chars before and after a link,
-// which we use to split the string in linkify()
-private bool pre_split_urls(MatchInfo match_info, StringBuilder result) {
-    try {
-        string? url = match_info.fetch(0);
-        Regex r = new Regex(PROTOCOL_REGEX, RegexCompileFlags.CASELESS);
-        result.append(r.match(url) ? "\01%s\01".printf(url) : url);
-    } catch (Error e) {
-        debug("URL parsing error: %s\n", e.message);
+    // Linkifies plain text links in an HTML document.
+    public void linkify_document(WebKit.DOM.Document document) {
+        linkify_recurse(document, document.get_body());
     }
-    return false; // False to continue processing.
-}
 
-// Linkifies "plain text" links in the HTML doc.  If you want to do this
-// for the entire document, use the get_dom_document().get_body() for the
-// node param and leave _in_link as false.
-private void linkify_recurse(WebKit.DOM.Document document, WebKit.DOM.Node node,
-    bool _in_link = false) {
-    
-    bool in_link = _in_link;
-    if (node is WebKit.DOM.HTMLAnchorElement)
-        in_link = true;
-    
-    string input = node.get_node_value();
-    if (!in_link && !Geary.String.is_empty(input)) {
+    // Validates a URL.
+    // Ensures the URL begins with a valid protocol specifier.  (If not, we don't
+    // want to linkify it.)
+    // Note that the output of this will place '\01' chars before and after a link,
+    // which we use to split the string in linkify()
+    private bool pre_split_urls(MatchInfo match_info, StringBuilder result) {
         try {
-            Regex r = new Regex(URL_REGEX, RegexCompileFlags.CASELESS);
-            string output = r.replace_eval(input, -1, 0, 0, pre_split_urls);
-            if (input != output) {
-                // We got one!  Now split the text and swap out the node.
-                Regex tester = new Regex(PROTOCOL_REGEX, RegexCompileFlags.CASELESS);
-                string[] pieces = output.split("\01");
-                Gee.ArrayList<WebKit.DOM.Node> new_nodes = new Gee.ArrayList<WebKit.DOM.Node>();
-                
-                for(int i = 0; i < pieces.length; i++) {
-                    //WebKit.DOM.Node new_node;
-                    if (tester.match(pieces[i])) {
-                        // Link part.
-                        WebKit.DOM.HTMLAnchorElement anchor = document.create_element("a")
-                            as WebKit.DOM.HTMLAnchorElement;
-                        anchor.href = pieces[i];
-                        anchor.set_inner_text(pieces[i]);
-                        new_nodes.add(anchor);
-                    } else {
-                        // Text part.
-                        WebKit.DOM.Node new_node = node.clone_node(false);
-                        new_node.set_node_value(pieces[i]);
-                        new_nodes.add(new_node);
+            string? url = match_info.fetch(0);
+            Regex r = new Regex(PROTOCOL_REGEX, RegexCompileFlags.CASELESS);
+            result.append(r.match(url) ? "\01%s\01".printf(url) : url);
+        } catch (Error e) {
+            debug("URL parsing error: %s\n", e.message);
+        }
+        return false; // False to continue processing.
+    }
+
+    // Linkifies "plain text" links in the HTML doc.  If you want to do this
+    // for the entire document, use the get_dom_document().get_body() for the
+    // node param and leave _in_link as false.
+    private void linkify_recurse(WebKit.DOM.Document document, WebKit.DOM.Node node,
+        bool _in_link = false) {
+
+        bool in_link = _in_link;
+        if (node is WebKit.DOM.HTMLAnchorElement)
+            in_link = true;
+
+        string input = node.get_node_value();
+        if (!in_link && !Geary.String.is_empty(input)) {
+            try {
+                Regex r = new Regex(URL_REGEX, RegexCompileFlags.CASELESS);
+                string output = r.replace_eval(input, -1, 0, 0, pre_split_urls);
+                if (input != output) {
+                    // We got one!  Now split the text and swap out the node.
+                    Regex tester = new Regex(PROTOCOL_REGEX, RegexCompileFlags.CASELESS);
+                    string[] pieces = output.split("\01");
+                    Gee.ArrayList<WebKit.DOM.Node> new_nodes = new Gee.ArrayList<WebKit.DOM.Node>();
+
+                    for(int i = 0; i < pieces.length; i++) {
+                        //WebKit.DOM.Node new_node;
+                        if (tester.match(pieces[i])) {
+                            // Link part.
+                            WebKit.DOM.HTMLAnchorElement anchor = document.create_element("a")
+                                as WebKit.DOM.HTMLAnchorElement;
+                            anchor.href = pieces[i];
+                            anchor.set_inner_text(pieces[i]);
+                            new_nodes.add(anchor);
+                        } else {
+                            // Text part.
+                            WebKit.DOM.Node new_node = node.clone_node(false);
+                            new_node.set_node_value(pieces[i]);
+                            new_nodes.add(new_node);
+                        }
                     }
+
+                    // Add our new nodes.
+                    WebKit.DOM.Node? sibling = node.get_next_sibling();
+                    for (int i = 0; i < new_nodes.size; i++) {
+                        WebKit.DOM.Node new_node = new_nodes.get(i);
+                        if (sibling == null)
+                            node.get_parent_node().append_child(new_node);
+                        else
+                            node.get_parent_node().insert_before(new_node, sibling);
+                    }
+
+                    // Remove the original node's text.
+                    node.set_node_value("");
                 }
-                
-                // Add our new nodes.
-                WebKit.DOM.Node? sibling = node.get_next_sibling();
-                for (int i = 0; i < new_nodes.size; i++) {
-                    WebKit.DOM.Node new_node = new_nodes.get(i);
-                    if (sibling == null)
-                        node.get_parent_node().append_child(new_node);
-                    else
-                        node.get_parent_node().insert_before(new_node, sibling);
-                }
-                
-                // Remove the original node's text.
-                node.set_node_value("");
+            } catch (Error e) {
+                debug("Error linkifying outgoing mail: %s", e.message);
             }
-        } catch (Error e) {
-            debug("Error linkifying outgoing mail: %s", e.message);
         }
-    }
-    
-    // Visit children.
-    WebKit.DOM.NodeList list = node.get_child_nodes();
-    for (int i = 0; i < list.length; i++) {
-        linkify_recurse(document, list.item(i), in_link);
-    }
-}
 
-// Validates a URL.  Intended to be used as a RegexEvalCallback.
-// Ensures the URL begins with a valid protocol specifier.  (If not, we don't
-// want to linkify it.)
-public bool is_valid_url(MatchInfo match_info, StringBuilder result) {
-    try {
-        string? url = match_info.fetch(0);
-        Regex r = new Regex(PROTOCOL_REGEX, RegexCompileFlags.CASELESS);
-        
-        result.append(r.match(url) ? "<a href=\"%s\">%s</a>".printf(url, url) : url);
-    } catch (Error e) {
-        debug("URL parsing error: %s\n", e.message);
+        // Visit children.
+        WebKit.DOM.NodeList list = node.get_child_nodes();
+        for (int i = 0; i < list.length; i++) {
+            linkify_recurse(document, list.item(i), in_link);
+        }
     }
-    return false; // False to continue processing.
-}
 
-// Converts plain text emails to something safe and usable in HTML.
-public string linkify_and_escape_plain_text(string input) throws Error {
-    // Convert < and > into non-printable characters, and change & to &amp;.
-    string output = input.replace("<", " \01 ").replace(">", " \02 ").replace("&", "&amp;");
-    
-    // Converts text links into HTML hyperlinks.
-    Regex r = new Regex(URL_REGEX, RegexCompileFlags.CASELESS);
-    
-    output = r.replace_eval(output, -1, 0, 0, is_valid_url);
-    return output.replace(" \01 ", "&lt;").replace(" \02 ", "&gt;");
-}
+    // Validates a URL.  Intended to be used as a RegexEvalCallback.
+    // Ensures the URL begins with a valid protocol specifier.  (If not, we don't
+    // want to linkify it.)
+    public bool is_valid_url(MatchInfo match_info, StringBuilder result) {
+        try {
+            string? url = match_info.fetch(0);
+            Regex r = new Regex(PROTOCOL_REGEX, RegexCompileFlags.CASELESS);
 
-public bool node_is_child_of(WebKit.DOM.Node node, string ancestor_tag) {
-    WebKit.DOM.Element? ancestor = node.get_parent_element();
-    for (; ancestor != null; ancestor = ancestor.get_parent_element()) {
-        if (ancestor.get_tag_name() == ancestor_tag) {
-            return true;
+            result.append(r.match(url) ? "<a href=\"%s\">%s</a>".printf(url, url) : url);
+        } catch (Error e) {
+            debug("URL parsing error: %s\n", e.message);
         }
+        return false; // False to continue processing.
     }
-    return false;
-}
 
-public WebKit.DOM.HTMLElement? closest_ancestor(WebKit.DOM.Element element, string selector) {
-    try {
-        WebKit.DOM.Element? parent = element.get_parent_element();
-        while (parent != null && !parent.webkit_matches_selector(selector)) {
-            parent = parent.get_parent_element();
-        }
-        return parent as WebKit.DOM.HTMLElement;
-    } catch (Error error) {
-        warning("Failed to find ancestor: %s", error.message);
-        return null;
+    // Converts plain text emails to something safe and usable in HTML.
+    public string linkify_and_escape_plain_text(string input) throws Error {
+        // Convert < and > into non-printable characters, and change & to &amp;.
+        string output = input.replace("<", " \01 ").replace(">", " \02 ").replace("&", "&amp;");
+
+        // Converts text links into HTML hyperlinks.
+        Regex r = new Regex(URL_REGEX, RegexCompileFlags.CASELESS);
+
+        output = r.replace_eval(output, -1, 0, 0, is_valid_url);
+        return output.replace(" \01 ", "&lt;").replace(" \02 ", "&gt;");
     }
-}
 
-public bool is_descendant_of(WebKit.DOM.Element? element, string selector) {
-    try {
-        while (element != null) {
-            if (element.webkit_matches_selector(selector))
+    public bool node_is_child_of(WebKit.DOM.Node node, string ancestor_tag) {
+        WebKit.DOM.Element? ancestor = node.get_parent_element();
+        for (; ancestor != null; ancestor = ancestor.get_parent_element()) {
+            if (ancestor.get_tag_name() == ancestor_tag) {
                 return true;
-            element = element.get_parent_element();
+            }
         }
-    } catch (Error error) {
-        warning("Problem traversing DOM: %s", error.message);
+        return false;
     }
-    return false;
-}
 
-public string decorate_quotes(string text) throws Error {
-    int level = 0;
-    string outtext = "";
-    Regex quote_leader = new Regex("^(&gt;)* ?");  // Some &gt; followed by optional space
-    
-    foreach (string line in text.split("\n")) {
-        MatchInfo match_info;
-        if (quote_leader.match_all(line, 0, out match_info)) {
-            int start, end, new_level;
-            match_info.fetch_pos(0, out start, out end);
-            new_level = end / 4;  // Cast to int removes 0.25 from space at end, if present
-            while (new_level > level) {
-                outtext += "<blockquote>";
-                level += 1;
-            }
-            while (new_level < level) {
-                outtext += "</blockquote>";
-                level -= 1;
+    public WebKit.DOM.HTMLElement? closest_ancestor(WebKit.DOM.Element element, string selector) {
+        try {
+            WebKit.DOM.Element? parent = element.get_parent_element();
+            while (parent != null && !parent.webkit_matches_selector(selector)) {
+                parent = parent.get_parent_element();
             }
-            outtext += line.substring(end);
-        } else {
-            debug("This line didn't match the quote regex: %s", line);
-            outtext += line;
+            return parent as WebKit.DOM.HTMLElement;
+        } catch (Error error) {
+            warning("Failed to find ancestor: %s", error.message);
+            return null;
         }
     }
-    // Close any remaining blockquotes.
-    while (level > 0) {
-        outtext += "</blockquote>";
-        level -= 1;
+
+    public bool is_descendant_of(WebKit.DOM.Element? element, string selector) {
+        try {
+            while (element != null) {
+                if (element.webkit_matches_selector(selector))
+                    return true;
+                element = element.get_parent_element();
+            }
+        } catch (Error error) {
+            warning("Problem traversing DOM: %s", error.message);
+        }
+        return false;
     }
-    return outtext;
-}
 
-// This will modify/reset the DOM
-public string html_to_flowed_text(WebKit.DOM.HTMLElement el) {
-    string saved_doc = el.get_inner_html();
-    WebKit.DOM.NodeList blockquotes;
-    try {
-        blockquotes = el.query_selector_all("blockquote");
-    } catch (Error error) {
-        debug("Error selecting blockquotes: %s", error.message);
-        return "";
+    public string decorate_quotes(string text) throws Error {
+        int level = 0;
+        string outtext = "";
+        Regex quote_leader = new Regex("^(&gt;)* ?");  // Some &gt; followed by optional space
+
+        foreach (string line in text.split("\n")) {
+            MatchInfo match_info;
+            if (quote_leader.match_all(line, 0, out match_info)) {
+                int start, end, new_level;
+                match_info.fetch_pos(0, out start, out end);
+                new_level = end / 4;  // Cast to int removes 0.25 from space at end, if present
+                while (new_level > level) {
+                    outtext += "<blockquote>";
+                    level += 1;
+                }
+                while (new_level < level) {
+                    outtext += "</blockquote>";
+                    level -= 1;
+                }
+                outtext += line.substring(end);
+            } else {
+                debug("This line didn't match the quote regex: %s", line);
+                outtext += line;
+            }
+        }
+        // Close any remaining blockquotes.
+        while (level > 0) {
+            outtext += "</blockquote>";
+            level -= 1;
+        }
+        return outtext;
     }
-    
-    int nbq = (int) blockquotes.length;
-    string[] bqtexts = new string[nbq];
-    
-    // Get text of blockquotes and pull them out of DOM.  They are replaced with tokens deliminated
-    // with the characters QUOTE_START and QUOTE_END (from a unicode private use block).  We need to
-    // get the text while they're  still in the DOM to get newlines at appropriate places.  We go
-    // through the list of blockquotes from the end so that we get the innermost ones first.
-    for (int i = nbq - 1; i >= 0; i--) {
-        WebKit.DOM.HTMLElement bq = (WebKit.DOM.HTMLElement) blockquotes.item(i);
-        bqtexts[i] = bq.get_inner_text();
-        if (bqtexts[i].length > 0 && bqtexts[i].substring(-1, 1) == "\n")
-            bqtexts[i] = bqtexts[i].slice(0, -1);
-        else
-            debug("Did not find expected newline at end of quote.");
-        
+
+    // This will modify/reset the DOM
+    public string html_to_flowed_text(WebKit.DOM.HTMLElement el) {
+        string saved_doc = el.get_inner_html();
+        WebKit.DOM.NodeList blockquotes;
+        try {
+            blockquotes = el.query_selector_all("blockquote");
+        } catch (Error error) {
+            debug("Error selecting blockquotes: %s", error.message);
+            return "";
+        }
+
+        int nbq = (int) blockquotes.length;
+        string[] bqtexts = new string[nbq];
+
+        // Get text of blockquotes and pull them out of DOM.  They are replaced with tokens deliminated
+        // with the characters QUOTE_START and QUOTE_END (from a unicode private use block).  We need to
+        // get the text while they're  still in the DOM to get newlines at appropriate places.  We go
+        // through the list of blockquotes from the end so that we get the innermost ones first.
+        for (int i = nbq - 1; i >= 0; i--) {
+            WebKit.DOM.HTMLElement bq = (WebKit.DOM.HTMLElement) blockquotes.item(i);
+            bqtexts[i] = bq.get_inner_text();
+            if (bqtexts[i].length > 0 && bqtexts[i].substring(-1, 1) == "\n")
+                bqtexts[i] = bqtexts[i].slice(0, -1);
+            else
+                debug("Did not find expected newline at end of quote.");
+
+            try {
+                bq.set_inner_text(@"$QUOTE_START$i$QUOTE_END");
+            } catch (Error error) {
+                debug("Error manipulating DOM: %s", error.message);
+            }
+        }
+
+        // Reassemble plain text out of parts, replace non-breaking space with regular space
+        string doctext = resolve_nesting(el.get_inner_text(), bqtexts).replace("\xc2\xa0", " ");
+
+        // Reassemble DOM
         try {
-            bq.set_inner_text(@"$QUOTE_START$i$QUOTE_END");
+            el.set_inner_html(saved_doc);
         } catch (Error error) {
-            debug("Error manipulating DOM: %s", error.message);
+            debug("Error resetting DOM: %s", error.message);
+        }
+
+        // Wrap, space stuff, quote
+        string[] lines = doctext.split("\n");
+        GLib.StringBuilder flowed = new GLib.StringBuilder.sized(doctext.length);
+        foreach (string line in lines) {
+            // Strip trailing whitespace, so it doesn't look like a flowed line.  But the
+            // signature separator "-- " is special, so leave that alone.
+            if (line != "-- ")
+                line = line.chomp();
+            int quote_level = 0;
+            while (line[quote_level] == Geary.RFC822.Utils.QUOTE_MARKER)
+                quote_level += 1;
+            line = line[quote_level:line.length];
+            string prefix = quote_level > 0 ? string.nfill(quote_level, '>') + " " : "";
+            int max_len = 72 - prefix.length;
+
+            do {
+                if (quote_level == 0 && (line.has_prefix(">") || line.has_prefix("From")))
+                    line = " " + line;
+
+                int cut_ind = line.length;
+                if (cut_ind > max_len) {
+                    string beg = line[0:max_len];
+                    cut_ind = beg.last_index_of(" ") + 1;
+                    if (cut_ind == 0) {
+                        cut_ind = line.index_of(" ") + 1;
+                        if (cut_ind == 0)
+                            cut_ind = line.length;
+                        if (cut_ind > 998 - prefix.length)
+                            cut_ind = 998 - prefix.length;
+                    }
+                }
+                flowed.append(prefix + line[0:cut_ind] + "\n");
+                line = line[cut_ind:line.length];
+            } while (line.length > 0);
         }
+
+        return flowed.str;
     }
-    
-    // Reassemble plain text out of parts, replace non-breaking space with regular space
-    string doctext = resolve_nesting(el.get_inner_text(), bqtexts).replace("\xc2\xa0", " ");
-    
-    // Reassemble DOM
-    try {
-        el.set_inner_html(saved_doc);
-    } catch (Error error) {
-        debug("Error resetting DOM: %s", error.message);
+
+    public string quote_lines(string text) {
+        string[] lines = text.split("\n");
+        for (int i=0; i<lines.length; i++)
+            lines[i] = @"$(Geary.RFC822.Utils.QUOTE_MARKER)" + lines[i];
+        return string.joinv("\n", lines);
     }
-    
-    // Wrap, space stuff, quote
-    string[] lines = doctext.split("\n");
-    GLib.StringBuilder flowed = new GLib.StringBuilder.sized(doctext.length);
-    foreach (string line in lines) {
-        // Strip trailing whitespace, so it doesn't look like a flowed line.  But the
-        // signature separator "-- " is special, so leave that alone.
-        if (line != "-- ")
-            line = line.chomp();
-        int quote_level = 0;
-        while (line[quote_level] == Geary.RFC822.Utils.QUOTE_MARKER)
-            quote_level += 1;
-        line = line[quote_level:line.length];
-        string prefix = quote_level > 0 ? string.nfill(quote_level, '>') + " " : "";
-        int max_len = 72 - prefix.length;
-        
-        do {
-            if (quote_level == 0 && (line.has_prefix(">") || line.has_prefix("From")))
-                line = " " + line;
-            
-            int cut_ind = line.length;
-            if (cut_ind > max_len) {
-                string beg = line[0:max_len];
-                cut_ind = beg.last_index_of(" ") + 1;
-                if (cut_ind == 0) {
-                    cut_ind = line.index_of(" ") + 1;
-                    if (cut_ind == 0)
-                        cut_ind = line.length;
-                    if (cut_ind > 998 - prefix.length)
-                        cut_ind = 998 - prefix.length;
+
+    public string resolve_nesting(string text, string[] values) {
+        try {
+            GLib.Regex tokenregex = new GLib.Regex(@"(.?)$QUOTE_START([0-9]*)$QUOTE_END(?=(.?))");
+            return tokenregex.replace_eval(text, -1, 0, 0, (info, res) => {
+                int key = int.parse(info.fetch(2));
+                string prev_char = info.fetch(1), next_char = info.fetch(3), insert_next = "";
+                // Make sure there's a newline before and after the quote.
+                if (prev_char != "" && prev_char != "\n")
+                    prev_char = prev_char + "\n";
+                if (next_char != "" && next_char != "\n")
+                    insert_next = "\n";
+                if (key >= 0 && key < values.length) {
+                    res.append(prev_char + quote_lines(resolve_nesting(values[key], values)) + insert_next);
+                } else {
+                    debug("Regex error in denesting blockquotes: Invalid key");
+                    res.append("");
                 }
-            }
-            flowed.append(prefix + line[0:cut_ind] + "\n");
-            line = line[cut_ind:line.length];
-        } while (line.length > 0);
+                return false;
+            });
+        } catch (Error error) {
+            debug("Regex error in denesting blockquotes: %s", error.message);
+            return "";
+        }
     }
-    
-    return flowed.str;
-}
 
-public string quote_lines(string text) {
-    string[] lines = text.split("\n");
-    for (int i=0; i<lines.length; i++)
-        lines[i] = @"$(Geary.RFC822.Utils.QUOTE_MARKER)" + lines[i];
-    return string.joinv("\n", lines);
-}
+    // Returns a URI suitable for an IMG SRC attribute (or elsewhere, potentially) that is the
+    // memory buffer unpacked into a Base-64 encoded data: URI
+    public string assemble_data_uri(string mimetype, Geary.Memory.Buffer buffer) {
+        // attempt to use UnownedBytesBuffer to avoid memcpying a potentially huge buffer only to
+        // free it when the encoding operation is completed
+        string base64;
+        Geary.Memory.UnownedBytesBuffer? unowned_bytes = buffer as Geary.Memory.UnownedBytesBuffer;
+        if (unowned_bytes != null)
+            base64 = Base64.encode(unowned_bytes.to_unowned_uint8_array());
+        else
+            base64 = Base64.encode(buffer.get_uint8_array());
 
-public string resolve_nesting(string text, string[] values) {
-    try {
-        GLib.Regex tokenregex = new GLib.Regex(@"(.?)$QUOTE_START([0-9]*)$QUOTE_END(?=(.?))");
-        return tokenregex.replace_eval(text, -1, 0, 0, (info, res) => {
-            int key = int.parse(info.fetch(2));
-            string prev_char = info.fetch(1), next_char = info.fetch(3), insert_next = "";
-            // Make sure there's a newline before and after the quote.
-            if (prev_char != "" && prev_char != "\n")
-                prev_char = prev_char + "\n";
-            if (next_char != "" && next_char != "\n")
-                insert_next = "\n";
-            if (key >= 0 && key < values.length) {
-                res.append(prev_char + quote_lines(resolve_nesting(values[key], values)) + insert_next);
-            } else {
-                debug("Regex error in denesting blockquotes: Invalid key");
-                res.append("");
-            }
-            return false;
-        });
-    } catch (Error error) {
-        debug("Regex error in denesting blockquotes: %s", error.message);
-        return "";
+        return "data:%s;base64,%s".printf(mimetype, base64);
     }
-}
 
-// Returns a URI suitable for an IMG SRC attribute (or elsewhere, potentially) that is the
-// memory buffer unpacked into a Base-64 encoded data: URI
-public string assemble_data_uri(string mimetype, Geary.Memory.Buffer buffer) {
-    // attempt to use UnownedBytesBuffer to avoid memcpying a potentially huge buffer only to
-    // free it when the encoding operation is completed
-    string base64;
-    Geary.Memory.UnownedBytesBuffer? unowned_bytes = buffer as Geary.Memory.UnownedBytesBuffer;
-    if (unowned_bytes != null)
-        base64 = Base64.encode(unowned_bytes.to_unowned_uint8_array());
-    else
-        base64 = Base64.encode(buffer.get_uint8_array());
-    
-    return "data:%s;base64,%s".printf(mimetype, base64);
-}
+    // Turns the data: URI created by assemble_data_uri() back into its components.  The returned
+    // buffer is decoded.
+    //
+    // TODO: Return mimetype
+    public bool disassemble_data_uri(string uri, out Geary.Memory.Buffer? buffer) {
+        buffer = null;
 
-// Turns the data: URI created by assemble_data_uri() back into its components.  The returned
-// buffer is decoded.
-//
-// TODO: Return mimetype
-public bool dissasemble_data_uri(string uri, out Geary.Memory.Buffer? buffer) {
-    buffer = null;
-    
-    if (!uri.has_prefix("data:"))
-        return false;
-    
-    // count from semicolon past encoding type specifier
-    int start_index = uri.index_of(";");
-    if (start_index <= 0)
-        return false;
-    
-    // watch for string termination to avoid overflow
-    int base64_len = "base64,".length;
-    for (int ctr = 0; ctr < base64_len; ctr++) {
-        if (uri[start_index++] == Geary.String.EOS)
+        if (!uri.has_prefix("data:"))
             return false;
+
+        // count from semicolon past encoding type specifier
+        int start_index = uri.index_of(";");
+        if (start_index <= 0)
+            return false;
+
+        // watch for string termination to avoid overflow
+        int base64_len = "base64,".length;
+        for (int ctr = 0; ctr < base64_len; ctr++) {
+            if (uri[start_index++] == Geary.String.EOS)
+                return false;
+        }
+
+        // avoid a memory copy of the substring by manually calculating the start address
+        uint8[] bytes = Base64.decode((string) (((char *) uri) + start_index));
+
+        // transfer ownership of the byte array directly to the Buffer; this prevents an
+        // unnecessary copy ... save length before transferring ownership (which frees the array)
+        int bytes_length = bytes.length;
+        buffer = new Geary.Memory.ByteBuffer.take((owned) bytes, bytes_length);
+
+        return true;
     }
-    
-    // avoid a memory copy of the substring by manually calculating the start address
-    uint8[] bytes = Base64.decode((string) (((char *) uri) + start_index));
-    
-    // transfer ownership of the byte array directly to the Buffer; this prevents an
-    // unnecessary copy ... save length before transferring ownership (which frees the array)
-    int bytes_length = bytes.length;
-    buffer = new Geary.Memory.ByteBuffer.take((owned) bytes, bytes_length);
-    
-    return true;
-}
 
-// Escape reserved HTML entities if the string does not have HTML tags.  If there are no tags,
-// or if preserve_whitespace_in_html is true, wrap the string a div to preserve whitespace.
-public string smart_escape(string? text, bool preserve_whitespace_in_html) {
-    if (text == null)
-        return text;
-    
-    string res = text;
-    if (!Regex.match_simple("<([A-Z]*)(?: [^>]*)?>.*</(\\1)>|<[A-Z]*(?: [^>]*)?/>", res,
-        RegexCompileFlags.CASELESS)) {
-        res = Geary.HTML.escape_markup(res);
-        preserve_whitespace_in_html = true;
+    // Escape reserved HTML entities if the string does not have HTML tags.  If there are no tags,
+    // or if preserve_whitespace_in_html is true, wrap the string a div to preserve whitespace.
+    public string smart_escape(string? text, bool preserve_whitespace_in_html) {
+        if (text == null)
+            return text;
+
+        string res = text;
+        if (!Regex.match_simple("<([A-Z]*)(?: [^>]*)?>.*</(\\1)>|<[A-Z]*(?: [^>]*)?/>", res,
+            RegexCompileFlags.CASELESS)) {
+            res = Geary.HTML.escape_markup(res);
+            preserve_whitespace_in_html = true;
+        }
+        if (preserve_whitespace_in_html)
+            res = @"<div style='white-space: pre;'>$res</div>";
+        return res;
     }
-    if (preserve_whitespace_in_html)
-        res = @"<div style='white-space: pre;'>$res</div>";
-    return res;
 }
 


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]