X-Git-Url: http://git.maemo.org/git/?p=modest;a=blobdiff_plain;f=src%2Fmodest-text-utils.c;h=5cf47bc0c711a4d6a5c889be6a8de51855bbf6ae;hp=ddf2476e0f224b7551888e379cef1e30c4fda327;hb=9af35d848c6bd939c607c9c8293ad7e475ecaa0c;hpb=5fc7538688f5e00d27fca03b79744e86a072bb1a diff --git a/src/modest-text-utils.c b/src/modest-text-utils.c index ddf2476..5cf47bc 100644 --- a/src/modest-text-utils.c +++ b/src/modest-text-utils.c @@ -1,10 +1,38 @@ -/* modest-ui.c */ +/* Copyright (c) 2006, Nokia Corporation + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the Nokia Corporation nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER + * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ -/* insert (c)/licensing information) */ -#include +#include #include - +#include +#include +#include #include "modest-text-utils.h" @@ -12,18 +40,282 @@ #include #endif /*HAVE_CONFIG_H */ +/* defines */ +#define FORWARD_STRING _("-----Forwarded Message-----") +#define FROM_STRING _("From:") +#define SENT_STRING _("Sent:") +#define TO_STRING _("To:") +#define SUBJECT_STRING _("Subject:") + +/* + * we need these regexps to find URLs in plain text e-mails + */ +typedef struct _url_match_pattern_t url_match_pattern_t; +struct _url_match_pattern_t { + gchar *regex; + regex_t *preg; + gchar *prefix; +}; + +typedef struct _url_match_t url_match_t; +struct _url_match_t { + guint offset; + guint len; + const gchar* prefix; +}; + +#define MAIL_VIEWER_URL_MATCH_PATTERNS { \ + { "(file|rtsp|http|ftp|https)://[-A-Za-z0-9_$.+!*(),;:@%&=?/~#]+[-A-Za-z0-9_$%&=?/~#]",\ + NULL, NULL },\ + { "www\\.[-a-z0-9.]+[-a-z0-9](:[0-9]*)?(/[-A-Za-z0-9_$.+!*(),;:@%&=?/~#]*[^]}\\),?!;:\"]?)?",\ + NULL, "http://" },\ + { "ftp\\.[-a-z0-9.]+[-a-z0-9](:[0-9]*)?(/[-A-Za-z0-9_$.+!*(),;:@%&=?/~#]*[^]}\\),?!;:\"]?)?",\ + NULL, "ftp://" },\ + { "(voipto|callto|chatto|jabberto|xmpp):[-_a-z@0-9.\\+]+", \ + NULL, NULL}, \ + { "mailto:[-_a-z0-9.\\+]+@[-_a-z0-9.]+", \ + NULL, NULL},\ + { "[-_a-z0-9.\\+]+@[-_a-z0-9.]+",\ + NULL, "mailto:"}\ + } + /* private */ -static GString *get_next_line (const char *b, const gsize blen, const gchar * iter); -static int get_indent_level (const char *l); -static void unquote_line (GString * l); -static void append_quoted (GString * buf, const int indent, - const GString * str, const int cutpoint); -static int get_breakpoint_utf8 (const gchar * s, const gint indent, - const gint limit); -static int get_breakpoint_ascii (const gchar * s, const gint indent, - const gint limit); -static int get_breakpoint (const gchar * s, const gint indent, - const gint limit); +static gchar* cite (const time_t sent_date, const gchar *from); +static void hyperlinkify_plain_text (GString *txt); +static gint cmp_offsets_reverse (const url_match_t *match1, const url_match_t *match2); +static void chk_partial_match (const url_match_t *match, guint* offset); +static GSList* get_url_matches (GString *txt); + +static GString* get_next_line (const char *b, const gsize blen, const gchar * iter); +static int get_indent_level (const char *l); +static void unquote_line (GString * l); +static void append_quoted (GString * buf, const int indent, const GString * str, + const int cutpoint); +static int get_breakpoint_utf8 (const gchar * s, const gint indent, const gint limit); +static int get_breakpoint_ascii (const gchar * s, const gint indent, const gint limit); +static int get_breakpoint (const gchar * s, const gint indent, const gint limit); + +static gchar* modest_text_utils_quote_plain_text (const gchar *text, + const gchar *cite, + int limit); + +static gchar* modest_text_utils_quote_html (const gchar *text, + const gchar *cite, + int limit); + + +/* ******************************************************************* */ +/* ************************* PUBLIC FUNCTIONS ************************ */ +/* ******************************************************************* */ + +gchar * +modest_text_utils_quote (const gchar *text, + const gchar *content_type, + const gchar *from, + const time_t sent_date, + int limit) +{ + gchar *retval, *cited; + + cited = cite (sent_date, from); + + if (!strcmp (content_type, "text/html")) + /* TODO: extract the of the HTML and pass it to + the function */ + retval = modest_text_utils_quote_html (text, cited, limit); + else + retval = modest_text_utils_quote_plain_text (text, cited, limit); + + g_free (cited); + + return retval; +} + + +gchar * +modest_text_utils_cite (const gchar *text, + const gchar *content_type, + const gchar *from, + time_t sent_date) +{ + gchar *tmp, *retval; + + tmp = cite (sent_date, from); + retval = g_strdup_printf ("%s%s\n", tmp, text); + g_free (tmp); + + return retval; +} + +gchar * +modest_text_utils_inline (const gchar *text, + const gchar *content_type, + const gchar *from, + time_t sent_date, + const gchar *to, + const gchar *subject) +{ + gchar sent_str[101]; + const gchar *plain_format = "%s\n%s %s\n%s %s\n%s %s\n%s %s\n\n%s"; + const gchar *html_format = \ + "%s
\n\n" \ + "\n" \ + "\n" \ + "\n" \ + "\n" \ + "

%s"; + const gchar *format; + + modest_text_utils_strftime (sent_str, 100, "%c", localtime (&sent_date)); + + if (!strcmp (content_type, "text/html")) + /* TODO: extract the of the HTML and pass it to + the function */ + format = html_format; + else + format = plain_format; + + return g_strdup_printf (format, + FORWARD_STRING, + FROM_STRING, from, + SENT_STRING, sent_str, + TO_STRING, to, + SUBJECT_STRING, subject, + text); +} + +/* just to prevent warnings: + * warning: `%x' yields only last 2 digits of year in some locales + */ +size_t +modest_text_utils_strftime(char *s, size_t max, const char *fmt, const struct tm *tm) +{ + return strftime(s, max, fmt, tm); +} + +gchar * +modest_text_utils_derived_subject (const gchar *subject, const gchar *prefix) +{ + gchar *tmp; + + g_return_val_if_fail (prefix, NULL); + + if (!subject) + return g_strdup (prefix); + + tmp = g_strchug (g_strdup (subject)); + + if (!strncmp (tmp, prefix, strlen (prefix))) { + return tmp; + } else { + g_free (tmp); + return g_strdup_printf ("%s %s", prefix, subject); + } +} + +gchar* +modest_text_utils_remove_address (const gchar *address_list, const gchar *address) +{ + gchar *dup, *token, *ptr, *result; + GString *filtered_emails; + + g_return_val_if_fail (address_list, NULL); + + if (!address) + return g_strdup (address_list); + + /* search for substring */ + if (!strstr ((const char *) address_list, (const char *) address)) + return g_strdup (address_list); + + dup = g_strdup (address_list); + filtered_emails = g_string_new (NULL); + + token = strtok_r (dup, ",", &ptr); + + while (token != NULL) { + /* Add to list if not found */ + if (!strstr ((const char *) token, (const char *) address)) { + if (filtered_emails->len == 0) + g_string_append_printf (filtered_emails, "%s", g_strstrip (token)); + else + g_string_append_printf (filtered_emails, ",%s", g_strstrip (token)); + } + token = strtok_r (NULL, ",", &ptr); + } + result = filtered_emails->str; + + /* Clean */ + g_free (dup); + g_string_free (filtered_emails, FALSE); + + return result; +} + +gchar* +modest_text_utils_convert_to_html (const gchar *data) +{ + guint i; + gboolean first_space = TRUE; + GString *html; + gsize len; + + if (!data) + return NULL; + + len = strlen (data); + html = g_string_sized_new (len + 100); /* just a guess... */ + + g_string_append_printf (html, + "" + "" + "" + "" + ""); + + /* replace with special html chars where needed*/ + for (i = 0; i != len; ++i) { + char kar = data[i]; + switch (kar) { + + case 0: break; /* ignore embedded \0s */ + case '<' : g_string_append (html, "<"); break; + case '>' : g_string_append (html, ">"); break; + case '&' : g_string_append (html, """); break; + case '\n': g_string_append (html, "
\n"); break; + default: + if (kar == ' ') { + g_string_append (html, first_space ? " " : " "); + first_space = FALSE; + } else if (kar == '\t') + g_string_append (html, "    "); + else { + int charnum = 0; + first_space = TRUE; + /* optimization trick: accumulate 'normal' chars, then copy */ + do { + kar = data [++charnum + i]; + + } while ((i + charnum < len) && + (kar > '>' || (kar != '<' && kar != '>' + && kar != '&' && kar != ' ' + && kar != '\n' && kar != '\t'))); + g_string_append_len (html, &data[i], charnum); + i += (charnum - 1); + } + } + } + + g_string_append (html, "
"); + hyperlinkify_plain_text (html); + + return g_string_free (html, FALSE); +} + +/* ******************************************************************* */ +/* ************************* UTILIY FUNCTIONS ************************ */ +/* ******************************************************************* */ static GString * get_next_line (const gchar * b, const gsize blen, const gchar * iter) @@ -74,7 +366,6 @@ get_indent_level (const char *l) static void unquote_line (GString * l) { - GString *r; gchar *p; p = l->str; @@ -169,29 +460,36 @@ get_breakpoint (const gchar * s, const gint indent, const gint limit) } } +static gchar * +cite (const time_t sent_date, const gchar *from) +{ + gchar sent_str[101]; -gchar * -modest_text_utils_quote (const gchar * to_quote, const gchar * from, - const time_t sent_date, const int limit) + /* format sent_date */ + modest_text_utils_strftime (sent_str, 100, "%c", localtime (&sent_date)); + return g_strdup_printf (N_("On %s, %s wrote:\n"), sent_str, from); +} + + +static gchar * +modest_text_utils_quote_plain_text (const gchar *text, + const gchar *cite, + int limit) { const gchar *iter; - gint indent, breakpoint, rem_indent; - gchar sent_str[101]; + gint indent, breakpoint, rem_indent = 0; GString *q, *l, *remaining; gsize len; - - /* format sent_date */ - strftime (sent_str, 100, "%c", localtime (&sent_date)); - q = g_string_new (""); - g_string_printf (q, "On %s, %s wrote:\n", sent_str, from); + gchar *tmp; /* remaining will store the rest of the line if we have to break it */ + q = g_string_new (cite); remaining = g_string_new (""); - iter = to_quote; - len = strlen(to_quote); + iter = text; + len = strlen(text); do { - l = get_next_line (to_quote, len, iter); + l = get_next_line (text, len, iter); iter = iter + l->len + 1; indent = get_indent_level (l->str); unquote_line (l); @@ -226,7 +524,168 @@ modest_text_utils_quote (const gchar * to_quote, const gchar * from, rem_indent = indent; append_quoted (q, indent, l, breakpoint); g_string_free (l, TRUE); - } while ((iter < to_quote + len) || (remaining->str[0])); + } while ((iter < text + len) || (remaining->str[0])); return g_string_free (q, FALSE); } + +static gchar* +modest_text_utils_quote_html (const gchar *text, + const gchar *cite, + int limit) +{ + const gchar *format = \ + "\n" \ + "\n" \ + "\n" \ + "%s" \ + "
\n%s\n
\n" \ + "\n" \ + "\n"; + + return g_strdup_printf (format, cite, text); +} + +static gint +cmp_offsets_reverse (const url_match_t *match1, const url_match_t *match2) +{ + return match2->offset - match1->offset; +} + + + +/* + * check if the match is inside an existing match... */ +static void +chk_partial_match (const url_match_t *match, guint* offset) +{ + if (*offset >= match->offset && *offset < match->offset + match->len) + *offset = -1; +} + +static GSList* +get_url_matches (GString *txt) +{ + regmatch_t rm; + guint rv, i, offset = 0; + GSList *match_list = NULL; + + static url_match_pattern_t patterns[] = MAIL_VIEWER_URL_MATCH_PATTERNS; + const size_t pattern_num = sizeof(patterns)/sizeof(url_match_pattern_t); + + /* initalize the regexps */ + for (i = 0; i != pattern_num; ++i) { + patterns[i].preg = g_new0 (regex_t,1); + g_assert(regcomp (patterns[i].preg, patterns[i].regex, + REG_ICASE|REG_EXTENDED|REG_NEWLINE) == 0); + } + /* find all the matches */ + for (i = 0; i != pattern_num; ++i) { + offset = 0; + while (1) { + int test_offset; + if ((rv = regexec (patterns[i].preg, txt->str + offset, 1, &rm, 0)) != 0) { + g_assert (rv == REG_NOMATCH); /* this should not happen */ + break; /* try next regexp */ + } + if (rm.rm_so == -1) + break; + + /* FIXME: optimize this */ + /* to avoid partial matches on something that was already found... */ + /* check_partial_match will put -1 in the data ptr if that is the case */ + test_offset = offset + rm.rm_so; + g_slist_foreach (match_list, (GFunc)chk_partial_match, &test_offset); + + /* make a list of our matches ( tupels)*/ + if (test_offset != -1) { + url_match_t *match = g_new (url_match_t,1); + match->offset = offset + rm.rm_so; + match->len = rm.rm_eo - rm.rm_so; + match->prefix = patterns[i].prefix; + match_list = g_slist_prepend (match_list, match); + } + offset += rm.rm_eo; + } + } + + for (i = 0; i != pattern_num; ++i) { + regfree (patterns[i].preg); + g_free (patterns[i].preg); + } /* don't free patterns itself -- it's static */ + + /* now sort the list, so the matches are in reverse order of occurence. + * that way, we can do the replacements starting from the end, so we don't need + * to recalculate the offsets + */ + match_list = g_slist_sort (match_list, + (GCompareFunc)cmp_offsets_reverse); + return match_list; +} + + + +static void +hyperlinkify_plain_text (GString *txt) +{ + GSList *cursor; + GSList *match_list = get_url_matches (txt); + + /* we will work backwards, so the offsets stay valid */ + for (cursor = match_list; cursor; cursor = cursor->next) { + + url_match_t *match = (url_match_t*) cursor->data; + gchar *url = g_strndup (txt->str + match->offset, match->len); + gchar *repl = NULL; /* replacement */ + + /* the prefix is NULL: use the one that is already there */ + repl = g_strdup_printf ("%s", + match->prefix ? match->prefix : "", url, url); + + /* replace the old thing with our hyperlink + * replacement thing */ + g_string_erase (txt, match->offset, match->len); + g_string_insert (txt, match->offset, repl); + + g_free (url); + g_free (repl); + + g_free (cursor->data); + } + + g_slist_free (match_list); +} + + + +gchar* +modest_text_utils_display_address (gchar *address) +{ + gchar *cursor; + + if (!address) + return NULL; + + g_return_val_if_fail (g_utf8_validate (address, -1, NULL), NULL); + + g_strchug (address); /* remove leading whitespace */ + + /* from display name */ + cursor = g_strstr_len (address, strlen(address), "<"); + if (cursor == address) /* there's nothing else? leave it */ + return address; + if (cursor) + cursor[0]='\0'; + + /* remove (bla bla) from display name */ + cursor = g_strstr_len (address, strlen(address), "("); + if (cursor == address) /* there's nothing else? leave it */ + return address; + if (cursor) + cursor[0]='\0'; + + g_strchomp (address); /* remove trailing whitespace */ + + return address; +} +
%s%s
%s%s
%s%s
%s%s