X-Git-Url: http://git.maemo.org/git/?p=modest;a=blobdiff_plain;f=src%2Fmodest-text-utils.c;h=5cf47bc0c711a4d6a5c889be6a8de51855bbf6ae;hp=2f28c91ef87dc23b08a38f46a245802ff8021fab;hb=b09daad954384d34091b51a04eda7fd3a57a5f22;hpb=4c17eac51757809148b84e229bede6323aac270a diff --git a/src/modest-text-utils.c b/src/modest-text-utils.c index 2f28c91..5cf47bc 100644 --- a/src/modest-text-utils.c +++ b/src/modest-text-utils.c @@ -28,12 +28,11 @@ */ -/* modest-ui.c */ - -#include +#include #include #include #include +#include #include "modest-text-utils.h" @@ -41,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) @@ -197,31 +460,21 @@ get_breakpoint (const gchar * s, const gint indent, const gint limit) } } - - -/* just to prevent warnings: - * warning: `%x' yields only last 2 digits of year in some locales - */ -static size_t -my_strftime(char *s, size_t max, const char *fmt, const - struct tm *tm) { - return strftime(s, max, fmt, tm); -} - static gchar * -cite (const time_t sent_date, const gchar *from) { - gchar *str; +cite (const time_t sent_date, const gchar *from) +{ gchar sent_str[101]; /* format sent_date */ - my_strftime (sent_str, 100, "%c", localtime (&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); } -gchar * -modest_text_utils_quote (const gchar * to_quote, const gchar * from, - const time_t sent_date, const int limit) +static gchar * +modest_text_utils_quote_plain_text (const gchar *text, + const gchar *cite, + int limit) { const gchar *iter; gint indent, breakpoint, rem_indent = 0; @@ -229,18 +482,14 @@ modest_text_utils_quote (const gchar * to_quote, const gchar * from, gsize len; gchar *tmp; - /* format sent_date */ - tmp = cite (sent_date, from); - q = g_string_new (tmp); - g_free (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); @@ -275,134 +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 * -create_derivated_subject (const gchar *subject, const gchar *prefix) +static gchar* +modest_text_utils_quote_html (const gchar *text, + const gchar *cite, + int limit) { - gchar *tmp, *buffer; + const gchar *format = \ + "\n" \ + "\n" \ + "\n" \ + "%s" \ + "
\n%s\n
\n" \ + "\n" \ + "\n"; + + return g_strdup_printf (format, cite, text); +} - if (!subject) - return g_strdup_printf ("%s ", prefix); +static gint +cmp_offsets_reverse (const url_match_t *match1, const url_match_t *match2) +{ + return match2->offset - match1->offset; +} - 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); - } -} -/** - * modest_text_utils_create_reply_subject: - * @subject: - * - * creates a new subject with a reply prefix if not present before - * - * Returns: a new subject with the reply prefix - **/ -gchar * -modest_text_utils_create_reply_subject (const gchar *subject) +/* + * check if the match is inside an existing match... */ +static void +chk_partial_match (const url_match_t *match, guint* offset) { - return create_derivated_subject (subject, _("Re:")); + if (*offset >= match->offset && *offset < match->offset + match->len) + *offset = -1; } -/** - * modest_text_utils_create_forward_subject: - * @subject: - * - * creates a new subject with a forward prefix if not present before - * - * Returns: a new subject with the forward prefix - **/ -gchar * -modest_text_utils_create_forward_subject (const gchar *subject) +static GSList* +get_url_matches (GString *txt) { - return create_derivated_subject (subject, _("Fw:")); + 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; } -gchar * -modest_text_utils_create_cited_text (const gchar *from, - time_t sent_date, - const gchar *text) + + +static void +hyperlinkify_plain_text (GString *txt) { - gchar *tmp, *retval; + GSList *cursor; + GSList *match_list = get_url_matches (txt); - tmp = cite (sent_date, from); - retval = g_strdup_printf ("%s%s\n", tmp, text); - g_free (tmp); + /* we will work backwards, so the offsets stay valid */ + for (cursor = match_list; cursor; cursor = cursor->next) { - return retval; -} + url_match_t *match = (url_match_t*) cursor->data; + gchar *url = g_strndup (txt->str + match->offset, match->len); + gchar *repl = NULL; /* replacement */ -/** - * modest_text_utils_create_inlined_text: - * @text: the original text - * - * creates a new string with the "Original message" text prepended to - * the text passed as argument and some data of the header - * - * Returns: a newly allocated text - **/ -gchar * -modest_text_utils_create_inlined_text (const gchar *from, - time_t sent_date, - const gchar *to, - const gchar *subject, - const gchar *text) -{ - gchar sent_str[101]; + /* the prefix is NULL: use the one that is already there */ + repl = g_strdup_printf ("%s", + match->prefix ? match->prefix : "", url, url); - my_strftime (sent_str, 100, "%c", localtime (&sent_date)); + /* 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); - return g_strdup_printf ("%s\n%s %s\n%s %s\n%s %s\n%s %s\n\n%s", - _("-----Forwarded Message-----"), - _("From:"), from, - _("Sent:"), sent_str, - _("To:"), to, - _("Subject:"), subject, - text); + g_free (cursor->data); + } + + g_slist_free (match_list); } -gchar * -modest_text_utils_remove_mail_from_mail_list (const gchar *emails, - const gchar *email) -{ - char *dup, *token, *ptr, *result; - GString *filtered_emails; - if (!emails) + +gchar* +modest_text_utils_display_address (gchar *address) +{ + gchar *cursor; + + if (!address) return NULL; - /* Search for substring */ - if (!strstr ((const char *) emails, (const char *) email)) - return g_strdup (emails); + g_return_val_if_fail (g_utf8_validate (address, -1, NULL), NULL); - dup = g_strdup (emails); - filtered_emails = g_string_new (NULL); + g_strchug (address); /* remove leading whitespace */ - token = strtok_r (dup, ",", &ptr); + /* 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'; - while (token != NULL) { - /* Add to list if not found */ - if (!strstr ((const char *) token, (const char *) email)) { - if (G_UNLIKELY (filtered_emails->len) == 0) - g_string_append_printf (filtered_emails, "%s", token); - else - g_string_append_printf (filtered_emails, ",%s", token); - } - token = strtok_r (NULL, ",", &ptr); - } - result = filtered_emails->str; + /* 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'; - /* Clean */ - g_free (dup); - g_string_free (filtered_emails, FALSE); + g_strchomp (address); /* remove trailing whitespace */ - return result; + return address; } +
%s%s
%s%s
%s%s
%s%s