diff options
-rw-r--r-- | libempathy-gtk/empathy-chat-text-view.c | 144 | ||||
-rw-r--r-- | libempathy-gtk/empathy-smiley-manager.c | 158 | ||||
-rw-r--r-- | libempathy-gtk/empathy-smiley-manager.h | 15 | ||||
-rw-r--r-- | libempathy-gtk/empathy-theme-adium.c | 230 | ||||
-rw-r--r-- | libempathy-gtk/empathy-ui-utils.c | 122 | ||||
-rw-r--r-- | libempathy-gtk/empathy-ui-utils.h | 45 | ||||
-rw-r--r-- | libempathy/empathy-debug.c | 1 | ||||
-rw-r--r-- | libempathy/empathy-debug.h | 1 | ||||
-rw-r--r-- | tests/.gitignore | 1 | ||||
-rw-r--r-- | tests/Makefile.am | 6 | ||||
-rw-r--r-- | tests/empathy-parser-test.c | 145 | ||||
-rw-r--r-- | tests/test-helper.c | 6 |
12 files changed, 607 insertions, 267 deletions
diff --git a/libempathy-gtk/empathy-chat-text-view.c b/libempathy-gtk/empathy-chat-text-view.c index de777f2fb..a46f6a4c5 100644 --- a/libempathy-gtk/empathy-chat-text-view.c +++ b/libempathy-gtk/empathy-chat-text-view.c @@ -1256,109 +1256,99 @@ empathy_chat_text_view_set_only_if_date (EmpathyChatTextView *view, } static void -chat_text_view_insert_text_with_emoticons (EmpathyChatTextView *view, - GtkTextIter *iter, - const gchar *str) +chat_text_view_replace_link (const gchar *text, + gssize len, + gpointer match_data, + gpointer user_data) { - EmpathyChatTextViewPriv *priv = GET_PRIV (view); - gboolean use_smileys = FALSE; - GSList *smileys, *l; + GtkTextBuffer *buffer = GTK_TEXT_BUFFER (user_data); + GtkTextIter iter; - empathy_conf_get_bool (empathy_conf_get (), - EMPATHY_PREFS_CHAT_SHOW_SMILEYS, - &use_smileys); + gtk_text_buffer_get_end_iter (buffer, &iter); + gtk_text_buffer_insert_with_tags_by_name (buffer, &iter, + text, len, + EMPATHY_CHAT_TEXT_VIEW_TAG_LINK, + NULL); +} - if (!use_smileys) { - gtk_text_buffer_insert (priv->buffer, iter, str, -1); - return; - } +static void +chat_text_view_replace_smiley (const gchar *text, + gssize len, + gpointer match_data, + gpointer user_data) +{ + EmpathySmileyHit *hit = match_data; + GtkTextBuffer *buffer = GTK_TEXT_BUFFER (user_data); + GtkTextIter iter; - smileys = empathy_smiley_manager_parse (priv->smiley_manager, str); - for (l = smileys; l; l = l->next) { - EmpathySmiley *smiley; + gtk_text_buffer_get_end_iter (buffer, &iter); + gtk_text_buffer_insert_pixbuf (buffer, &iter, hit->pixbuf); +} - smiley = l->data; - if (smiley->pixbuf) { - gtk_text_buffer_insert_pixbuf (priv->buffer, iter, smiley->pixbuf); - } else { - gtk_text_buffer_insert (priv->buffer, iter, smiley->str, -1); - } - empathy_smiley_free (smiley); - } - g_slist_free (smileys); +static void +chat_text_view_replace_verbatim (const gchar *text, + gssize len, + gpointer match_data, + gpointer user_data) +{ + GtkTextBuffer *buffer = GTK_TEXT_BUFFER (user_data); + GtkTextIter iter; + + gtk_text_buffer_get_end_iter (buffer, &iter); + gtk_text_buffer_insert (buffer, &iter, text, len); } +static EmpathyStringParser string_parsers[] = { + {empathy_string_match_link, chat_text_view_replace_link}, + {empathy_string_match_all, chat_text_view_replace_verbatim}, + {NULL, NULL} +}; + +static EmpathyStringParser string_parsers_with_smiley[] = { + {empathy_string_match_link, chat_text_view_replace_link}, + {empathy_string_match_smiley, chat_text_view_replace_smiley}, + {empathy_string_match_all, chat_text_view_replace_verbatim}, + {NULL, NULL} +}; + void empathy_chat_text_view_append_body (EmpathyChatTextView *view, const gchar *body, const gchar *tag) { EmpathyChatTextViewPriv *priv = GET_PRIV (view); - GtkTextIter start_iter, end_iter; - GtkTextMark *mark; + EmpathyStringParser *parsers; + gboolean use_smileys; + GtkTextIter start_iter; GtkTextIter iter; - GRegex *uri_regex; - GMatchInfo *match_info; - gboolean match; - gint last = 0; - gint s = 0, e = 0; - gchar *tmp; + GtkTextMark *mark; - priv = GET_PRIV (view); + /* Check if we have to parse smileys */ + empathy_conf_get_bool (empathy_conf_get (), + EMPATHY_PREFS_CHAT_SHOW_SMILEYS, + &use_smileys); + if (use_smileys) + parsers = string_parsers_with_smiley; + else + parsers = string_parsers; + /* Create a mark at the place we'll start inserting */ gtk_text_buffer_get_end_iter (priv->buffer, &start_iter); mark = gtk_text_buffer_create_mark (priv->buffer, NULL, &start_iter, TRUE); - uri_regex = empathy_uri_regex_dup_singleton (); - for (match = g_regex_match (uri_regex, body, 0, &match_info); match; - match = g_match_info_next (match_info, NULL)) { - if (!g_match_info_fetch_pos (match_info, 0, &s, &e)) - continue; - - if (s > last) { - tmp = empathy_substring (body, last, s); - - gtk_text_buffer_get_end_iter (priv->buffer, &iter); - chat_text_view_insert_text_with_emoticons (view, - &iter, - tmp); - g_free (tmp); - } - - tmp = empathy_substring (body, s, e); - - gtk_text_buffer_get_end_iter (priv->buffer, &iter); - gtk_text_buffer_insert_with_tags_by_name (priv->buffer, - &iter, - tmp, - -1, - EMPATHY_CHAT_TEXT_VIEW_TAG_LINK, - NULL); - - g_free (tmp); - last = e; - } - g_match_info_free (match_info); - g_regex_unref (uri_regex); - - if (last < (gint) strlen (body)) { - gtk_text_buffer_get_end_iter (priv->buffer, &iter); - chat_text_view_insert_text_with_emoticons (view, - &iter, - body + last); - } + /* Parse text for links/smileys and insert in the buffer */ + empathy_string_parser_substr (body, -1, parsers, priv->buffer); + /* Insert a newline after the text inserted */ gtk_text_buffer_get_end_iter (priv->buffer, &iter); gtk_text_buffer_insert (priv->buffer, &iter, "\n", 1); /* Apply the style to the inserted text. */ gtk_text_buffer_get_iter_at_mark (priv->buffer, &start_iter, mark); - gtk_text_buffer_get_end_iter (priv->buffer, &end_iter); - - gtk_text_buffer_apply_tag_by_name (priv->buffer, - tag, + gtk_text_buffer_get_end_iter (priv->buffer, &iter); + gtk_text_buffer_apply_tag_by_name (priv->buffer, tag, &start_iter, - &end_iter); + &iter); gtk_text_buffer_delete_mark (priv->buffer, mark); } diff --git a/libempathy-gtk/empathy-smiley-manager.c b/libempathy-gtk/empathy-smiley-manager.c index cf7a70ada..71cb5065f 100644 --- a/libempathy-gtk/empathy-smiley-manager.c +++ b/libempathy-gtk/empathy-smiley-manager.c @@ -81,32 +81,22 @@ smiley_manager_tree_free (SmileyManagerTree *tree) g_slice_free (SmileyManagerTree, tree); } -/* Note: This function takes the ownership of str */ static EmpathySmiley * -smiley_new (GdkPixbuf *pixbuf, gchar *str, const gchar *path) +smiley_new (GdkPixbuf *pixbuf, const gchar *str) { EmpathySmiley *smiley; smiley = g_slice_new0 (EmpathySmiley); - if (pixbuf) { - smiley->pixbuf = g_object_ref (pixbuf); - } - smiley->str = str; - smiley->path = path; + smiley->pixbuf = g_object_ref (pixbuf); + smiley->str = g_strdup (str); return smiley; } -void -empathy_smiley_free (EmpathySmiley *smiley) +static void +smiley_free (EmpathySmiley *smiley) { - if (!smiley) { - return; - } - - if (smiley->pixbuf) { - g_object_unref (smiley->pixbuf); - } + g_object_unref (smiley->pixbuf); g_free (smiley->str); g_slice_free (EmpathySmiley, smiley); } @@ -115,16 +105,9 @@ static void smiley_manager_finalize (GObject *object) { EmpathySmileyManagerPriv *priv = GET_PRIV (object); - GSList *l; smiley_manager_tree_free (priv->tree); - for (l = priv->smileys; l; l = l->next) { - EmpathySmiley *smiley = l->data; - - /* The smiley got the ownership of the path */ - g_free ((gchar *) smiley->path); - empathy_smiley_free (smiley); - } + g_slist_foreach (priv->smileys, (GFunc) smiley_free, NULL); g_slist_free (priv->smileys); } @@ -247,7 +230,7 @@ smiley_manager_add_valist (EmpathySmileyManager *manager, /* We give the ownership of path to the smiley */ g_object_set_data_full (G_OBJECT (pixbuf), "smiley_str", g_strdup (first_str), g_free); - smiley = smiley_new (pixbuf, g_strdup (first_str), path); + smiley = smiley_new (pixbuf, first_str); priv->smileys = g_slist_prepend (priv->smileys, smiley); } @@ -305,70 +288,119 @@ empathy_smiley_manager_load (EmpathySmileyManager *manager) empathy_smiley_manager_add (manager, "face-worried", ":-S", ":S", ":-s", ":s", NULL); } +static EmpathySmileyHit * +smiley_hit_new (SmileyManagerTree *tree, + guint start, + guint end) +{ + EmpathySmileyHit *hit; + + hit = g_slice_new (EmpathySmileyHit); + hit->pixbuf = tree->pixbuf; + hit->path = tree->path; + hit->start = start; + hit->end = end; + + return hit; +} + +void +empathy_smiley_hit_free (EmpathySmileyHit *hit) +{ + g_return_if_fail (hit != NULL); + + g_slice_free (EmpathySmileyHit, hit); +} + GSList * -empathy_smiley_manager_parse (EmpathySmileyManager *manager, - const gchar *text) +empathy_smiley_manager_parse_len (EmpathySmileyManager *manager, + const gchar *text, + gssize len) { EmpathySmileyManagerPriv *priv = GET_PRIV (manager); - EmpathySmiley *smiley; + EmpathySmileyHit *hit; + GSList *hits = NULL; SmileyManagerTree *cur_tree = priv->tree; - const gchar *t; - const gchar *cur_str = text; - GSList *smileys = NULL; + const gchar *cur_str; + const gchar *start = NULL; g_return_val_if_fail (EMPATHY_IS_SMILEY_MANAGER (manager), NULL); g_return_val_if_fail (text != NULL, NULL); - for (t = text; *t; t = g_utf8_next_char (t)) { + /* If len is negative, parse the string until we find '\0' */ + if (len < 0) { + len = G_MAXSSIZE; + } + + /* Parse the len first bytes of text to find smileys. Each time a smiley + * is detected, append a EmpathySmileyHit struct to the returned list, + * containing the smiley pixbuf and the position of the text to be + * replaced by it. + * cur_str is a pointer in the text showing the current position + * of the parsing. It is always at the begining of an UTF-8 character, + * because we support unicode smileys! For example we could want to + * replace ™ by an image. */ + + for (cur_str = text; + *cur_str != '\0' && cur_str - text < len; + cur_str = g_utf8_next_char (cur_str)) { SmileyManagerTree *child; gunichar c; - c = g_utf8_get_char (t); + c = g_utf8_get_char (cur_str); child = smiley_manager_tree_find_child (cur_tree, c); - if (cur_tree == priv->tree) { - if (child) { - if (t > cur_str) { - smiley = smiley_new (NULL, - g_strndup (cur_str, t - cur_str), - NULL); - smileys = g_slist_prepend (smileys, smiley); - } - cur_str = t; - cur_tree = child; - } - - continue; - } - + /* If we have a child it means c is part of a smiley */ if (child) { + if (cur_tree == priv->tree) { + /* c is the first char of some smileys, keep + * the begining position */ + start = cur_str; + } cur_tree = child; continue; } - smiley = smiley_new (cur_tree->pixbuf, - g_strndup (cur_str, t - cur_str), - cur_tree->path); - smileys = g_slist_prepend (smileys, smiley); - if (cur_tree->pixbuf) { - cur_str = t; - cur_tree = smiley_manager_tree_find_child (priv->tree, c); + /* c is not part of a smiley. let's check if we found a smiley + * before it. */ + if (cur_tree->pixbuf != NULL) { + /* found! */ + hit = smiley_hit_new (cur_tree, start - text, + cur_str - text); + hits = g_slist_prepend (hits, hit); - if (!cur_tree) { + /* c was not part of this smiley, check if a new smiley + * start with it. */ + cur_tree = smiley_manager_tree_find_child (priv->tree, c); + if (cur_tree) { + start = cur_str; + } else { cur_tree = priv->tree; } - } else { - cur_str = t; + } else if (cur_tree != priv->tree) { + /* We searched a smiley starting at 'start' but we ended + * with no smiley. Look again starting from next char. + * + * For example ">:)" and ":(" are both valid smileys, + * when parsing text ">:(" we first see '>' which could + * be the start of a smiley. 'start' variable is set to + * that position and we parse next char which is ':' and + * is still potential smiley. Then we see '(' which is + * NOT part of the smiley, ">:(" does not exist, so we + * have to start again from ':' to find ":(" which is + * correct smiley. */ + cur_str = start; cur_tree = priv->tree; } } - smiley = smiley_new (cur_tree->pixbuf, - g_strndup (cur_str, t - cur_str), - cur_tree->path); - smileys = g_slist_prepend (smileys, smiley); + /* Check if last char of the text was the end of a smiley */ + if (cur_tree->pixbuf != NULL) { + hit = smiley_hit_new (cur_tree, start - text, cur_str - text); + hits = g_slist_prepend (hits, hit); + } - return g_slist_reverse (smileys); + return g_slist_reverse (hits); } GSList * diff --git a/libempathy-gtk/empathy-smiley-manager.h b/libempathy-gtk/empathy-smiley-manager.h index dc7428c3b..1d6eaac54 100644 --- a/libempathy-gtk/empathy-smiley-manager.h +++ b/libempathy-gtk/empathy-smiley-manager.h @@ -50,9 +50,15 @@ struct _EmpathySmileyManagerClass { typedef struct { GdkPixbuf *pixbuf; gchar *str; - const gchar *path; } EmpathySmiley; +typedef struct { + GdkPixbuf *pixbuf; /* Pixbuf of the smiley */ + const gchar *path; /* Filename of the smiley image */ + guint start; /* text[start:end] should be replaced by pixbuf */ + guint end; +} EmpathySmileyHit; + typedef void (*EmpathySmileyMenuFunc) (EmpathySmileyManager *manager, EmpathySmiley *smiley, gpointer user_data); @@ -65,12 +71,13 @@ void empathy_smiley_manager_add (EmpathySmileyManag const gchar *first_str, ...); GSList * empathy_smiley_manager_get_all (EmpathySmileyManager *manager); -GSList * empathy_smiley_manager_parse (EmpathySmileyManager *manager, - const gchar *text); +GSList * empathy_smiley_manager_parse_len (EmpathySmileyManager *manager, + const gchar *text, + gssize len); GtkWidget * empathy_smiley_menu_new (EmpathySmileyManager *manager, EmpathySmileyMenuFunc func, gpointer user_data); -void empathy_smiley_free (EmpathySmiley *smiley); +void empathy_smiley_hit_free (EmpathySmileyHit *hit); G_END_DECLS diff --git a/libempathy-gtk/empathy-theme-adium.c b/libempathy-gtk/empathy-theme-adium.c index c1365579b..4e70fedb2 100644 --- a/libempathy-gtk/empathy-theme-adium.c +++ b/libempathy-gtk/empathy-theme-adium.c @@ -191,122 +191,126 @@ theme_adium_open_address_cb (GtkMenuItem *menuitem, g_free (uri); } -static gchar * -theme_adium_parse_body (EmpathyThemeAdium *theme, - const gchar *text) +static void +theme_adium_match_newline (const gchar *text, + gssize len, + EmpathyStringReplace replace_func, + EmpathyStringParser *sub_parsers, + gpointer user_data) { - EmpathyThemeAdiumPriv *priv = GET_PRIV (theme); - gboolean use_smileys = FALSE; - GSList *smileys, *l; - GString *string; - gint i; - GRegex *uri_regex; - GMatchInfo *match_info; - gboolean match; - gchar *ret = NULL; - gint prev; - - empathy_conf_get_bool (empathy_conf_get (), - EMPATHY_PREFS_CHAT_SHOW_SMILEYS, - &use_smileys); - - /* Add <a href></a> arround links */ - uri_regex = empathy_uri_regex_dup_singleton (); - match = g_regex_match (uri_regex, text, 0, &match_info); - if (match) { - gint last = 0; - gint s = 0, e = 0; - - string = g_string_sized_new (strlen (text)); - do { - gchar *real_url; - - g_match_info_fetch_pos (match_info, 0, &s, &e); - - if (s > last) { - /* Append the text between last link (or the - * start of the message) and this link */ - gchar *str; - str = g_markup_escape_text (text + last, s - last); - g_string_append (string, str); - g_free (str); - } - - /* Append the link inside <a href=""></a> tag */ - real_url = empathy_make_absolute_url_len (text + s, e - s); - - g_string_append (string, "<a href=\""); - g_string_append (string, real_url); - g_string_append (string, "\">"); - g_string_append_len (string, text + s, e - s); - g_string_append (string, "</a>"); - - g_free (real_url); - last = e; - } while (g_match_info_next (match_info, NULL)); - - if (e < (gint) strlen (text)) { - /* Append the text after the last link */ - gchar *str; - str = g_markup_escape_text (text + e, strlen (text) - e); - g_string_append (string, str); - g_free (str); - } - - g_free (ret); - text = ret = g_string_free (string, FALSE); - } else if (use_smileys) { - /* Replace smileys by a <img/> tag */ - string = g_string_sized_new (strlen (text)); - smileys = empathy_smiley_manager_parse (priv->smiley_manager, text); - for (l = smileys; l; l = l->next) { - EmpathySmiley *smiley; - - smiley = l->data; - if (smiley->path) { - g_string_append_printf (string, - "<abbr title='%s'><img src=\"%s\"/ alt=\"%s\"/></abbr>", - smiley->str, smiley->path, smiley->str); - } else { - gchar *str; + GString *string = user_data; + gint i; + gint prev = 0; - str = g_markup_escape_text (smiley->str, -1); - g_string_append (string, str); - g_free (str); - } - empathy_smiley_free (smiley); - } - g_slist_free (smileys); - - g_free (ret); - text = ret = g_string_free (string, FALSE); - } else { - text = ret = g_markup_escape_text (text, -1); + if (len < 0) { + len = G_MAXSSIZE; } - g_match_info_free (match_info); - g_regex_unref (uri_regex); - /* Replace \n by <br/> */ - string = NULL; - prev = 0; - for (i = 0; text[i] != '\0'; i++) { + for (i = 0; i < len && text[i] != '\0'; i++) { if (text[i] == '\n') { - if (!string ) { - string = g_string_sized_new (strlen (text)); - } - g_string_append_len (string, text + prev, i - prev); + empathy_string_parser_substr (text + prev, + i - prev, sub_parsers, + user_data); g_string_append (string, "<br/>"); prev = i + 1; } } - if (string) { - g_string_append (string, text + prev); - g_free (ret); - text = ret = g_string_free (string, FALSE); - } + empathy_string_parser_substr (text + prev, i - prev, + sub_parsers, user_data); +} - return ret; +static void +theme_adium_replace_link (const gchar *text, + gssize len, + gpointer match_data, + gpointer user_data) +{ + GString *string = user_data; + gchar *real_url; + gchar *escaped; + + real_url = empathy_make_absolute_url_len (text, len); + + /* The thing we are making a link of may contain + * characters which need escaping */ + escaped = g_markup_escape_text (text, len); + + /* Append the link inside <a href=""></a> tag */ + g_string_append_printf (string, "<a href=\"%s\">%s</a>", + real_url, escaped); + + g_free (real_url); + g_free (escaped); +} + +static void +theme_adium_replace_smiley (const gchar *text, + gssize len, + gpointer match_data, + gpointer user_data) +{ + EmpathySmileyHit *hit = match_data; + GString *string = user_data; + + /* Replace smiley by a <img/> tag */ + g_string_append_printf (string, + "<img src=\"%s\" alt=\"%.*s\" title=\"%.*s\"/>", + hit->path, len, text, len, text); +} + +static void +theme_adium_replace_escaped (const gchar *text, + gssize len, + gpointer match_data, + gpointer user_data) +{ + GString *string = user_data; + gchar *escaped; + + escaped = g_markup_escape_text (text, len); + g_string_append (string, escaped); + g_free (escaped); +} + +static EmpathyStringParser string_parsers[] = { + {empathy_string_match_link, theme_adium_replace_link}, + {theme_adium_match_newline, NULL}, + {empathy_string_match_all, theme_adium_replace_escaped}, + {NULL, NULL} +}; + +static EmpathyStringParser string_parsers_with_smiley[] = { + {empathy_string_match_link, theme_adium_replace_link}, + {empathy_string_match_smiley, theme_adium_replace_smiley}, + {theme_adium_match_newline, NULL}, + {empathy_string_match_all, theme_adium_replace_escaped}, + {NULL, NULL} +}; + +static gchar * +theme_adium_parse_body (const gchar *text) +{ + EmpathyStringParser *parsers; + GString *string; + gboolean use_smileys; + + /* Check if we have to parse smileys */ + empathy_conf_get_bool (empathy_conf_get (), + EMPATHY_PREFS_CHAT_SHOW_SMILEYS, + &use_smileys); + if (use_smileys) + parsers = string_parsers_with_smiley; + else + parsers = string_parsers; + + /* Parse text and construct string with links and smileys replaced + * by html tags. Also escape text to make sure html code is + * displayed verbatim. */ + string = g_string_sized_new (strlen (text)); + empathy_string_parser_substr (text, -1, parsers, string); + + return g_string_free (string, FALSE); } static void @@ -464,7 +468,7 @@ theme_adium_append_message (EmpathyChatView *view, EmpathyThemeAdiumPriv *priv = GET_PRIV (theme); EmpathyContact *sender; TpAccount *account; - gchar *dup_body = NULL; + gchar *body_escaped; const gchar *body; const gchar *name; const gchar *contact_id; @@ -494,10 +498,7 @@ theme_adium_append_message (EmpathyChatView *view, service_name = tp_account_get_protocol (account); timestamp = empathy_message_get_timestamp (msg); body = empathy_message_get_body (msg); - dup_body = theme_adium_parse_body (theme, body); - if (dup_body) { - body = dup_body; - } + body_escaped = theme_adium_parse_body (body); name = empathy_contact_get_name (sender); contact_id = empathy_contact_get_id (sender); @@ -505,10 +506,11 @@ theme_adium_append_message (EmpathyChatView *view, if (empathy_message_get_tptype (msg) == TP_CHANNEL_TEXT_MESSAGE_TYPE_ACTION) { gchar *str; - str = g_strdup_printf ("%s %s", name, body); + str = g_strdup_printf ("%s %s", name, body_escaped); theme_adium_append_event_escaped (view, str); + g_free (str); - g_free (dup_body); + g_free (body_escaped); return; } @@ -629,7 +631,7 @@ theme_adium_append_message (EmpathyChatView *view, } if (html != NULL) { - theme_adium_append_html (theme, func, html, len, body, + theme_adium_append_html (theme, func, html, len, body_escaped, avatar_filename, name, contact_id, service_name, message_classes->str, timestamp); @@ -645,7 +647,7 @@ theme_adium_append_message (EmpathyChatView *view, priv->last_timestamp = timestamp; priv->last_is_backlog = is_backlog; - g_free (dup_body); + g_free (body_escaped); g_string_free (message_classes, TRUE); } diff --git a/libempathy-gtk/empathy-ui-utils.c b/libempathy-gtk/empathy-ui-utils.c index ce5ec419e..c737873d1 100644 --- a/libempathy-gtk/empathy-ui-utils.c +++ b/libempathy-gtk/empathy-ui-utils.c @@ -40,6 +40,7 @@ #include "empathy-ui-utils.h" #include "empathy-images.h" +#include "empathy-smiley-manager.h" #include "empathy-conf.h" #define DEBUG_FLAG EMPATHY_DEBUG_OTHER @@ -49,13 +50,15 @@ #include <libempathy/empathy-idle.h> #include <libempathy/empathy-ft-factory.h> -#define SCHEMES "(https?|s?ftps?|nntp|news|javascript|about|ghelp|apt|telnet|"\ - "file|webcal|mailto)" -#define BODY "([^\\ \\n\"]+)" -#define END_BODY "([^\\ \\n\"]*[^,;\?><()\\ \"\\.\\n])" -#define URI_REGEX "("SCHEMES"://"END_BODY")" \ - "|((mailto:)?"BODY"@"BODY"\\."END_BODY")"\ - "|((www|ftp)\\."END_BODY")" +#define SCHEMES "([a-zA-Z\\+]+)" +#define INVALID_CHARS " \n\"'" +#define INVALID_CHARS_EXT INVALID_CHARS "\\[\\]<>(){},;:?" +#define BODY "([^"INVALID_CHARS"]+)" +#define BODY_END "([^"INVALID_CHARS"]*)[^"INVALID_CHARS_EXT".]" +#define BODY_STRICT "([^"INVALID_CHARS_EXT"]+)" +#define URI_REGEX "("SCHEMES"://"BODY_END")" \ + "|((www|ftp)\\."BODY_END")" \ + "|((mailto:)?"BODY_STRICT"@"BODY"\\."BODY_END")" void empathy_gtk_init (void) @@ -1569,3 +1572,108 @@ empathy_receive_file_with_file_chooser (EmpathyFTHandler *handler) gtk_widget_show (widget); } + +void +empathy_string_parser_substr (const gchar *text, + gssize len, + EmpathyStringParser *parsers, + gpointer user_data) +{ + if (parsers != NULL && parsers[0].match_func != NULL) { + parsers[0].match_func (text, len, + parsers[0].replace_func, parsers + 1, + user_data); + } +} + +void +empathy_string_match_link (const gchar *text, + gssize len, + EmpathyStringReplace replace_func, + EmpathyStringParser *sub_parsers, + gpointer user_data) +{ + GRegex *uri_regex; + GMatchInfo *match_info; + gboolean match; + gint last = 0; + + uri_regex = empathy_uri_regex_dup_singleton (); + match = g_regex_match_full (uri_regex, text, len, 0, 0, &match_info, NULL); + if (match) { + gint s = 0, e = 0; + + do { + g_match_info_fetch_pos (match_info, 0, &s, &e); + + if (s > last) { + /* Append the text between last link (or the + * start of the message) and this link */ + empathy_string_parser_substr (text + last, + s - last, + sub_parsers, + user_data); + } + + replace_func (text + s, e - s, NULL, user_data); + + last = e; + } while (g_match_info_next (match_info, NULL)); + } + + empathy_string_parser_substr (text + last, len - last, + sub_parsers, user_data); + + g_match_info_free (match_info); + g_regex_unref (uri_regex); +} + +void +empathy_string_match_smiley (const gchar *text, + gssize len, + EmpathyStringReplace replace_func, + EmpathyStringParser *sub_parsers, + gpointer user_data) +{ + guint last = 0; + EmpathySmileyManager *smiley_manager; + GSList *hits, *l; + + smiley_manager = empathy_smiley_manager_dup_singleton (); + hits = empathy_smiley_manager_parse_len (smiley_manager, text, len); + + for (l = hits; l; l = l->next) { + EmpathySmileyHit *hit = l->data; + + if (hit->start > last) { + /* Append the text between last smiley (or the + * start of the message) and this smiley */ + empathy_string_parser_substr (text + last, + hit->start - last, + sub_parsers, user_data); + } + + replace_func (text + hit->start, hit->end - hit->start, + hit, user_data); + + last = hit->end; + + empathy_smiley_hit_free (hit); + } + g_slist_free (hits); + g_object_unref (smiley_manager); + + empathy_string_parser_substr (text + last, len - last, + sub_parsers, user_data); +} + +void +empathy_string_match_all (const gchar *text, + gssize len, + EmpathyStringReplace replace_func, + EmpathyStringParser *sub_parsers, + gpointer user_data) +{ + replace_func (text, len, NULL, user_data); +} + diff --git a/libempathy-gtk/empathy-ui-utils.h b/libempathy-gtk/empathy-ui-utils.h index 7bec0884e..65fc9ac0e 100644 --- a/libempathy-gtk/empathy-ui-utils.h +++ b/libempathy-gtk/empathy-ui-utils.h @@ -117,6 +117,51 @@ gchar * empathy_make_absolute_url (const gchar *url); gchar * empathy_make_absolute_url_len (const gchar *url, guint len); +/* String parser */ +typedef struct _EmpathyStringParser EmpathyStringParser; + +typedef void (*EmpathyStringReplace) (const gchar *text, + gssize len, + gpointer match_data, + gpointer user_data); +typedef void (*EmpathyStringMatch) (const gchar *text, + gssize len, + EmpathyStringReplace replace_func, + EmpathyStringParser *sub_parsers, + gpointer user_data); + +struct _EmpathyStringParser { + EmpathyStringMatch match_func; + EmpathyStringReplace replace_func; +}; + +void +empathy_string_parser_substr (const gchar *text, + gssize len, + EmpathyStringParser *parsers, + gpointer user_data); + +void +empathy_string_match_link (const gchar *text, + gssize len, + EmpathyStringReplace replace_func, + EmpathyStringParser *sub_parsers, + gpointer user_data); + +void +empathy_string_match_smiley (const gchar *text, + gssize len, + EmpathyStringReplace replace_func, + EmpathyStringParser *sub_parsers, + gpointer user_data); + +void +empathy_string_match_all (const gchar *text, + gssize len, + EmpathyStringReplace replace_func, + EmpathyStringParser *sub_parsers, + gpointer user_data); + G_END_DECLS #endif /* __EMPATHY_UI_UTILS_H__ */ diff --git a/libempathy/empathy-debug.c b/libempathy/empathy-debug.c index 141340024..3f22c8969 100644 --- a/libempathy/empathy-debug.c +++ b/libempathy/empathy-debug.c @@ -51,6 +51,7 @@ static GDebugKey keys[] = { { "Other", EMPATHY_DEBUG_OTHER }, { "Connectivity", EMPATHY_DEBUG_CONNECTIVITY }, { "ImportMc4Accounts", EMPATHY_DEBUG_IMPORT_MC4_ACCOUNTS }, + { "Tests", EMPATHY_DEBUG_TESTS }, { 0, } }; diff --git a/libempathy/empathy-debug.h b/libempathy/empathy-debug.h index cc8eca0a3..fa80d403a 100644 --- a/libempathy/empathy-debug.h +++ b/libempathy/empathy-debug.h @@ -44,6 +44,7 @@ typedef enum EMPATHY_DEBUG_SHARE_DESKTOP = 1 << 10, EMPATHY_DEBUG_CONNECTIVITY = 1 << 11, EMPATHY_DEBUG_IMPORT_MC4_ACCOUNTS = 1 << 11, + EMPATHY_DEBUG_TESTS = 1 << 12, } EmpathyDebugFlags; gboolean empathy_debug_flag_is_set (EmpathyDebugFlags flag); diff --git a/tests/.gitignore b/tests/.gitignore index 73a19245d..f3af9c7fe 100644 --- a/tests/.gitignore +++ b/tests/.gitignore @@ -5,4 +5,5 @@ empathy-irc-network-test empathy-irc-network-manager-test empathy-chatroom-test empathy-chatroom-manager-test +empathy-parser-test test-report.xml diff --git a/tests/Makefile.am b/tests/Makefile.am index 25e399406..936761533 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -27,7 +27,8 @@ TEST_PROGS = \ empathy-irc-network-test \ empathy-irc-network-manager-test \ empathy-chatroom-test \ - empathy-chatroom-manager-test + empathy-chatroom-manager-test \ + empathy-parser-test empathy_utils_test_SOURCES = empathy-utils-test.c \ test-helper.c test-helper.h @@ -50,6 +51,9 @@ empathy_chatroom_test_SOURCES = empathy-chatroom-test.c \ empathy_chatroom_manager_test_SOURCES = empathy-chatroom-manager-test.c \ test-helper.c test-helper.h +empathy_parser_test_SOURCES = empathy-parser-test.c \ + test-helper.c test-helper.h + check_PROGRAMS = $(TEST_PROGS) TESTS_ENVIRONMENT = EMPATHY_SRCDIR=@abs_top_srcdir@ \ diff --git a/tests/empathy-parser-test.c b/tests/empathy-parser-test.c new file mode 100644 index 000000000..001882863 --- /dev/null +++ b/tests/empathy-parser-test.c @@ -0,0 +1,145 @@ +#include <stdlib.h> +#include <stdio.h> +#include <string.h> + +#include "test-helper.h" + +#include <telepathy-glib/util.h> + +#define DEBUG_FLAG EMPATHY_DEBUG_TESTS +#include <libempathy/empathy-debug.h> + +#include <libempathy-gtk/empathy-ui-utils.h> + +static void +test_replace_match (const gchar *text, + gssize len, + gpointer match_data, + gpointer user_data) +{ + GString *string = user_data; + + g_string_append_c (string, '['); + g_string_append_len (string, text, len); + g_string_append_c (string, ']'); +} + +static void +test_replace_verbatim (const gchar *text, + gssize len, + gpointer match_data, + gpointer user_data) +{ + GString *string = user_data; + + g_string_append_len (string, text, len); +} + +static void +test_parsers (void) +{ + gchar *tests[] = + { + /* Basic link matches */ + "http://foo.com", "[http://foo.com]", + "http://foo.com\nhttp://bar.com", "[http://foo.com]\n[http://bar.com]", + "http://foo.com/test?id=bar?", "[http://foo.com/test?id=bar]?", + "git://foo.com", "[git://foo.com]", + "git+ssh://foo.com", "[git+ssh://foo.com]", + "mailto:user@server.com", "[mailto:user@server.com]", + "www.foo.com", "[www.foo.com]", + "ftp.foo.com", "[ftp.foo.com]", + "user@server.com", "[user@server.com]", + "first.last@server.com", "[first.last@server.com]", + "http://foo.com. bar", "[http://foo.com]. bar", + "http://foo.com; bar", "[http://foo.com]; bar", + "http://foo.com: bar", "[http://foo.com]: bar", + "http://foo.com:bar", "[http://foo.com:bar]", + + /* They are not links! */ + "http://", "http[:/]/", /* Hm... */ + "www.", "www.", + "w.foo.com", "w.foo.com", + "@server.com", "@server.com", + "mailto:user@", "mailto:user@", + "mailto:user@.com", "mailto:user@.com", + "user@.com", "user@.com", + + /* Links inside (), {}, [], <> or "" */ + /* FIXME: How to test if the ending ] is matched or not? */ + "Foo (www.foo.com)", "Foo ([www.foo.com])", + "Foo {www.foo.com}", "Foo {[www.foo.com]}", + "Foo [www.foo.com]", "Foo [[www.foo.com]]", + "Foo <www.foo.com>", "Foo <[www.foo.com]>", + "Foo \"www.foo.com\"", "Foo \"[www.foo.com]\"", + "Foo (www.foo.com/bar(123)baz)", "Foo ([www.foo.com/bar(123)baz])", + "<a href=\"http://foo.com\">bar</a>", "<a href=\"[http://foo.com]\">bar</a>", + "Foo (user@server.com)", "Foo ([user@server.com])", + "Foo {user@server.com}", "Foo {[user@server.com]}", + "Foo [user@server.com]", "Foo [[user@server.com]]", + "Foo <user@server.com>", "Foo <[user@server.com]>", + "Foo \"user@server.com\"", "Foo \"[user@server.com]\"", + + /* Basic smileys */ + "a:)b", "a[:)]b", + ">:)", "[>:)]", + ">:(", ">[:(]", + + /* Smileys and links mixed */ + ":)http://foo.com", "[:)][http://foo.com]", + "a :) b http://foo.com c :( d www.test.com e", "a [:)] b [http://foo.com] c [:(] d [www.test.com] e", + + /* FIXME: Known issue: Brackets should be counted by the parser */ + //"Foo www.bar.com/test(123)", "Foo [www.bar.com/test(123)]", + //"Foo (www.bar.com/test(123))", "Foo ([www.bar.com/test(123)])", + //"Foo www.bar.com/test{123}", "Foo [www.bar.com/test{123}]", + //"Foo (:))", "Foo ([:)])", + //"Foo <a href=\"http://foo.com\">:)</a>", "Foo <a href=\"[http://foo.com]\">[:)]</a>", + + NULL, NULL + }; + EmpathyStringParser parsers[] = + { + {empathy_string_match_link, test_replace_match}, + {empathy_string_match_smiley, test_replace_match}, + {empathy_string_match_all, test_replace_verbatim}, + {NULL, NULL} + }; + guint i; + gboolean failed = FALSE; + + DEBUG ("Started"); + for (i = 0; tests[i] != NULL; i += 2) + { + GString *string; + gboolean ok; + + string = g_string_new (NULL); + empathy_string_parser_substr (tests[i], -1, parsers, string); + + ok = !tp_strdiff (tests[i + 1], string->str); + DEBUG ("'%s' => '%s': %s", tests[i], string->str, ok ? "OK" : "FAILED"); + if (!ok) + failed = TRUE; + + g_string_free (string, TRUE); + } + + g_assert (!failed); +} + +int +main (int argc, + char **argv) +{ + int result; + + test_init (argc, argv); + + g_test_add_func ("/parsers", test_parsers); + + result = g_test_run (); + test_deinit (); + + return result; +} diff --git a/tests/test-helper.c b/tests/test-helper.c index 8b6cc2937..d51d22a52 100644 --- a/tests/test-helper.c +++ b/tests/test-helper.c @@ -20,6 +20,9 @@ #include <stdlib.h> #include <glib.h> #include <glib-object.h> +#include <gtk/gtk.h> + +#include <libempathy-gtk/empathy-ui-utils.h> #include "test-helper.h" @@ -28,7 +31,8 @@ test_init (int argc, char **argv) { g_test_init (&argc, &argv, NULL); - g_type_init (); + gtk_init (&argc, &argv); + empathy_gtk_init (); } void |