~ubuntu-branches/ubuntu/saucy/geary/saucy-updates

« back to all changes in this revision

Viewing changes to src/client/util/util-webkit.vala

  • Committer: Package Import Robot
  • Author(s): Sebastien Bacher
  • Date: 2013-03-14 13:48:23 UTC
  • mfrom: (1.1.3)
  • Revision ID: package-import@ubuntu.com-20130314134823-gyk5av1g508zyj8a
Tags: 0.3.0~pr1-0ubuntu1
New upstream version (FFE lp: #1154316), supports multiple account as
well as full conversation views with inline replies

Show diffs side-by-side

added added

removed removed

Lines of Context:
142
142
    }
143
143
}
144
144
 
 
145
// Validates a URL.  Intended to be used as a RegexEvalCallback.
 
146
// Ensures the URL begins with a valid protocol specifier.  (If not, we don't
 
147
// want to linkify it.)
 
148
public bool is_valid_url(MatchInfo match_info, StringBuilder result) {
 
149
    try {
 
150
        string? url = match_info.fetch(0);
 
151
        Regex r = new Regex(PROTOCOL_REGEX, RegexCompileFlags.CASELESS);
 
152
        
 
153
        result.append(r.match(url) ? "<a href=\"%s\">%s</a>".printf(url, url) : url);
 
154
    } catch (Error e) {
 
155
        debug("URL parsing error: %s\n", e.message);
 
156
    }
 
157
    return false; // False to continue processing.
 
158
}
 
159
 
 
160
// Converts plain text emails to something safe and usable in HTML.
 
161
public string linkify_and_escape_plain_text(string input) throws Error {
 
162
    // Convert < and > into non-printable characters, and change & to &amp;.
 
163
    string output = input.replace("<", " \01 ").replace(">", " \02 ").replace("&", "&amp;");
 
164
    
 
165
    // Converts text links into HTML hyperlinks.
 
166
    Regex r = new Regex(URL_REGEX, RegexCompileFlags.CASELESS);
 
167
    
 
168
    output = r.replace_eval(output, -1, 0, 0, is_valid_url);
 
169
    return output.replace(" \01 ", "&lt;").replace(" \02 ", "&gt;");
 
170
}
 
171
 
 
172
public bool node_is_child_of(WebKit.DOM.Node node, string ancestor_tag) {
 
173
    WebKit.DOM.Element? ancestor = node.get_parent_element();
 
174
    for (; ancestor != null; ancestor = ancestor.get_parent_element()) {
 
175
        if (ancestor.get_tag_name() == ancestor_tag) {
 
176
            return true;
 
177
        }
 
178
    }
 
179
    return false;
 
180
}
 
181
 
 
182
public WebKit.DOM.HTMLElement? closest_ancestor(WebKit.DOM.Element element, string selector) {
 
183
    try {
 
184
        WebKit.DOM.Element? parent = element.get_parent_element();
 
185
        while (parent != null && !parent.webkit_matches_selector(selector)) {
 
186
            parent = parent.get_parent_element();
 
187
        }
 
188
        return parent as WebKit.DOM.HTMLElement;
 
189
    } catch (Error error) {
 
190
        warning("Failed to find ancestor: %s", error.message);
 
191
        return null;
 
192
    }
 
193
}
 
194
 
 
195
public string decorate_quotes(string text) throws Error {
 
196
    int level = 0;
 
197
    string outtext = "";
 
198
    Regex quote_leader = new Regex("^(&gt;)* ?");  // Some &gt; followed by optional space
 
199
    
 
200
    foreach (string line in text.split("\n")) {
 
201
        MatchInfo match_info;
 
202
        if (quote_leader.match_all(line, 0, out match_info)) {
 
203
            int start, end, new_level;
 
204
            match_info.fetch_pos(0, out start, out end);
 
205
            new_level = end / 4;  // Cast to int removes 0.25 from space at end, if present
 
206
            while (new_level > level) {
 
207
                outtext += "<blockquote>";
 
208
                level += 1;
 
209
            }
 
210
            while (new_level < level) {
 
211
                outtext += "</blockquote>";
 
212
                level -= 1;
 
213
            }
 
214
            outtext += line.substring(end);
 
215
        } else {
 
216
            debug("This line didn't match the quote regex: %s", line);
 
217
            outtext += line;
 
218
        }
 
219
    }
 
220
    // Close any remaining blockquotes.
 
221
    while (level > 0) {
 
222
        outtext += "</blockquote>";
 
223
        level -= 1;
 
224
    }
 
225
    return outtext;
 
226
}
 
227
 
 
228
public string html_to_flowed_text(WebKit.DOM.Document doc) {
 
229
    WebKit.DOM.NodeList blockquotes;
 
230
    try {
 
231
        blockquotes = doc.query_selector_all("blockquote");
 
232
    } catch (Error error) {
 
233
        debug("Error selecting blockquotes: %s", error.message);
 
234
        return "";
 
235
    }
 
236
    
 
237
    int nbq = (int) blockquotes.length;
 
238
    WebKit.DOM.Text[] tokens = new WebKit.DOM.Text[nbq];
 
239
    string[] bqtexts = new string[nbq];
 
240
    
 
241
    // Get text of blockquotes and pull them out of DOM.  We need to get the text while they're
 
242
    // still in the DOM to get newlines at appropriate places.  We go through the list of blockquotes
 
243
    // from the end so that we get the innermost ones first.
 
244
    for (int i = nbq - 1; i >= 0; i--) {
 
245
        WebKit.DOM.Node bq = blockquotes.item(i);
 
246
        WebKit.DOM.Node parent = bq.get_parent_node();
 
247
        bqtexts[i] = ((WebKit.DOM.HTMLElement) bq).get_inner_text();
 
248
        tokens[i] = doc.create_text_node(@"‘$i’");
 
249
        try {
 
250
            parent.replace_child(tokens[i], bq);
 
251
        } catch (Error error) {
 
252
            debug("Error manipulating DOM: %s", error.message);
 
253
        }
 
254
    }
 
255
    
 
256
    // Reassemble plain text out of parts
 
257
    string doctext = resolve_nesting(doc.get_body().get_inner_text(), bqtexts);
 
258
    
 
259
    // Reassemble DOM
 
260
    for (int i = 0; i < nbq; i++) {
 
261
        WebKit.DOM.Node parent = tokens[i].get_parent_node();
 
262
        try {
 
263
            parent.replace_child(blockquotes.item(i), tokens[i]);
 
264
        } catch (Error error) {
 
265
            debug("Error manipulating DOM: %s", error.message);
 
266
        }
 
267
    }
 
268
    
 
269
    // Wrap, space stuff, quote
 
270
    string[] lines = doctext.split("\n");
 
271
    GLib.StringBuilder flowed = new GLib.StringBuilder.sized(doctext.length);
 
272
    foreach (string line in lines) {
 
273
        int quote_level = 0;
 
274
        while (line[quote_level] == Geary.RFC822.Utils.QUOTE_MARKER)
 
275
            quote_level += 1;
 
276
        line = line[quote_level:line.length];
 
277
        string prefix = quote_level > 0 ? string.nfill(quote_level, '>') + " " : "";
 
278
        int max_len = 72 - prefix.length;
 
279
        
 
280
        do {
 
281
            if (quote_level == 0 && (line.has_prefix(">") || line.has_prefix("From")))
 
282
                line = " " + line;
 
283
            
 
284
            int cut_ind = line.length;
 
285
            if (cut_ind > max_len) {
 
286
                string beg = line[0:max_len];
 
287
                cut_ind = beg.last_index_of(" ") + 1;
 
288
                if (cut_ind == 0) {
 
289
                    cut_ind = line.index_of(" ") + 1;
 
290
                    if (cut_ind == 0)
 
291
                        cut_ind = line.length;
 
292
                    if (cut_ind > 998 - prefix.length)
 
293
                        cut_ind = 998 - prefix.length;
 
294
                }
 
295
            }
 
296
            flowed.append(prefix + line[0:cut_ind] + "\n");
 
297
            line = line[cut_ind:line.length];
 
298
        } while (line.length > 0);
 
299
    }
 
300
    
 
301
    return flowed.str;
 
302
}
 
303
 
 
304
public string quote_lines(string text) {
 
305
    string[] lines = text.split("\n");
 
306
    for (int i=0; i<lines.length; i++)
 
307
        lines[i] = @"$(Geary.RFC822.Utils.QUOTE_MARKER)" + lines[i];
 
308
    return string.joinv("\n", lines);
 
309
}
 
310
 
 
311
public string resolve_nesting(string text, string[] values) {
 
312
    try {
 
313
        GLib.Regex tokenregex = new GLib.Regex("‘([0-9]*)’(.?)");
 
314
        return tokenregex.replace_eval(text, -1, 0, 0, (info, res) => {
 
315
            int key = int.parse(info.fetch(1));
 
316
            string next_char = info.fetch(2);
 
317
            // If there is a next character, and it's not a newline, insert a newline
 
318
            // before it.  Otherwise, that text will become part of the inserted quote.
 
319
            if (next_char != "" && next_char != "\n")
 
320
                next_char = "\n" + next_char;
 
321
            if (key >= 0 && key < values.length) {
 
322
                res.append(quote_lines(resolve_nesting(values[key], values)) + next_char);
 
323
            } else {
 
324
                debug("Regex error in denesting blockquotes: Invalid key");
 
325
                res.append("");
 
326
            }
 
327
            return false;
 
328
        });
 
329
    } catch (Error error) {
 
330
        debug("Regex error in denesting blockquotes: %s", error.message);
 
331
        return "";
 
332
    }
 
333
}
 
334