view libpurple/smiley-parser.c @ 38351:d18a2256f3c5

Merged in CMaiku/pidgin (pull request #202) gnomekeyring: Quiet Gnome Keyring function deprecations Approved-by: Eion Robb <eionrobb@gmail.com> Approved-by: Gary Kramlich <grim@reaperworld.com> Approved-by: dx <dx@dxzone.com.ar>
author Gary Kramlich <grim@reaperworld.com>
date Thu, 15 Jun 2017 03:29:49 +0000
parents 61b755f852a7
children
line wrap: on
line source

/* purple
 *
 * Purple is the legal property of its developers, whose names are too numerous
 * to list here.  Please refer to the COPYRIGHT file distributed with this
 * source distribution.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
 */

#include "smiley-parser.h"

#include "smiley-custom.h"
#include "smiley-theme.h"

#define DISPLAY_OUR_CUSTOM_SMILEYS_FOR_INCOMING_MESSAGES 1

typedef struct
{
	union {
		struct {
			PurpleConversation *conv;
			PurpleSmileyParseCb cb;
			gpointer ui_data;
		} replace;
		struct {
			GHashTable *found_smileys;
		} find;
	} job;

	gboolean in_html_tag;
} PurpleSmileyParseData;

static PurpleTrie *html_sentry;

static inline void
purple_smiley_parse_check_html(const gchar *word,
	PurpleSmileyParseData *parse_data)
{
	if (G_LIKELY(word[0] == '<'))
		parse_data->in_html_tag = TRUE;
	else if (G_LIKELY(word[0] == '>'))
		parse_data->in_html_tag = FALSE;
	else
		g_return_if_reached();
	g_warn_if_fail(word[1] == '\0');
}

static gboolean
purple_smiley_parse_cb(GString *out, const gchar *word, gpointer _smiley,
	gpointer _parse_data)
{
	PurpleSmileyParseData *parse_data = _parse_data;
	PurpleSmiley *smiley = _smiley;

	/* a special-case for html_sentry */
	if (smiley == NULL) {
		purple_smiley_parse_check_html(word, parse_data);
		return FALSE;
	}

	if (parse_data->in_html_tag)
		return FALSE;

	return parse_data->job.replace.cb(out, smiley,
		parse_data->job.replace.conv, parse_data->job.replace.ui_data);
}

/* XXX: this shouldn't be a conv for incoming messages - see
 * PurpleConversationPrivate.remote_smileys.
 * For outgoing messages, we could pass conv in ui_data (or something).
 * Or maybe we should use two distinct functions?
 * To be reconsidered when we had PurpleDude-like objects.
 */
gchar *
purple_smiley_parser_smileify(PurpleConversation *conv, const gchar *html_message,
	gboolean use_remote_smileys, PurpleSmileyParseCb cb, gpointer ui_data)
{
	PurpleSmileyTheme *theme;
	PurpleSmileyList *theme_smileys = NULL, *remote_smileys = NULL;
	PurpleTrie *theme_trie = NULL, *custom_trie = NULL, *remote_trie = NULL;
	GSList *tries = NULL;
	GSList tries_sentry, tries_theme, tries_custom, tries_remote;
	PurpleSmileyParseData parse_data;

	if (html_message == NULL || html_message[0] == '\0')
		return g_strdup(html_message);

	/* get remote smileys */
	if (use_remote_smileys)
		remote_smileys = purple_conversation_get_remote_smileys(conv);
	if (remote_smileys)
		remote_trie = purple_smiley_list_get_trie(remote_smileys);
	if (remote_trie && purple_trie_get_size(remote_trie) == 0)
		remote_trie = NULL;

	/* get custom smileys */
	if (purple_conversation_get_features(conv) &
		PURPLE_CONNECTION_FLAG_ALLOW_CUSTOM_SMILEY)
	{
		custom_trie = purple_smiley_list_get_trie(
			purple_smiley_custom_get_list());
#if !DISPLAY_OUR_CUSTOM_SMILEYS_FOR_INCOMING_MESSAGES
		if (use_remote_smileys)
			custom_trie = NULL;
#endif
	}
	if (custom_trie && purple_trie_get_size(custom_trie) == 0)
		custom_trie = NULL;

	/* get theme smileys */
	theme = purple_smiley_theme_get_current();
	if (theme != NULL)
		theme_smileys = purple_smiley_theme_get_smileys(theme, ui_data);
	if (theme_smileys != NULL)
		theme_trie = purple_smiley_list_get_trie(theme_smileys);

	/* we have absolutely no smileys */
	if (theme_trie == NULL && custom_trie == NULL && remote_trie == NULL)
		return g_strdup(html_message);

	/* Create a tries list on the stack. */
	tries_sentry.data = html_sentry;
	tries_theme.data = theme_trie;
	tries_custom.data = custom_trie;
	tries_remote.data = remote_trie;
	tries_sentry.next = NULL;
	tries_theme.next = tries_custom.next = tries_remote.next = NULL;
	tries = &tries_sentry;
	if (remote_trie != NULL)
		g_slist_last(tries)->next = &tries_remote;
	if (custom_trie != NULL)
		g_slist_last(tries)->next = &tries_custom;
	if (theme_trie != NULL)
		g_slist_last(tries)->next = &tries_theme;

	parse_data.job.replace.conv = conv;
	parse_data.job.replace.cb = cb;
	parse_data.job.replace.ui_data = ui_data;
	parse_data.in_html_tag = FALSE;

	/* TODO: parse greedily (as much as possible) when PurpleTrie
	 * provides support for it. */
	return purple_trie_multi_replace(tries, html_message,
		purple_smiley_parse_cb, &parse_data);
}

gchar *
purple_smiley_parser_replace(PurpleSmileyList *smileys,
	const gchar *html_message, PurpleSmileyParseCb cb, gpointer ui_data)
{
	PurpleTrie *smileys_trie = NULL;
	GSList trie_1, trie_2;
	PurpleSmileyParseData parse_data;

	if (html_message == NULL || html_message[0] == '\0')
		return g_strdup(html_message);

	if (smileys == NULL || purple_smiley_list_is_empty(smileys))
		return g_strdup(html_message);

	smileys_trie = purple_smiley_list_get_trie(smileys);
	g_return_val_if_fail(smileys_trie != NULL, NULL);

	trie_1.data = html_sentry;
	trie_2.data = smileys_trie;
	trie_1.next = &trie_2;
	trie_2.next = NULL;

	parse_data.job.replace.conv = NULL;
	parse_data.job.replace.cb = cb;
	parse_data.job.replace.ui_data = ui_data;
	parse_data.in_html_tag = FALSE;

	return purple_trie_multi_replace(&trie_1, html_message,
		purple_smiley_parse_cb, &parse_data);
}

static gboolean
smiley_find_cb(const gchar *word, gpointer _smiley, gpointer _parse_data)
{
	PurpleSmiley *smiley = _smiley;
	PurpleSmileyParseData *parse_data = _parse_data;

	/* a special-case for html_sentry */
	if (smiley == NULL) {
		purple_smiley_parse_check_html(word, parse_data);
		return FALSE;
	}

	if (parse_data->in_html_tag)
		return FALSE;

	g_hash_table_insert(parse_data->job.find.found_smileys, smiley, smiley);

	return TRUE;
}

GList *
purple_smiley_parser_find(PurpleSmileyList *smileys, const gchar *message,
	gboolean is_html)
{
	PurpleTrie *smileys_trie;
	GList *found_list;
	gchar *escaped_message = NULL;
	PurpleSmileyParseData parse_data;
	GSList trie_1, trie_2;

	if (message == NULL || message[0] == '\0')
		return NULL;

	if (smileys == NULL || purple_smiley_list_is_empty(smileys))
		return NULL;

	smileys_trie = purple_smiley_list_get_trie(smileys);
	g_return_val_if_fail(smileys_trie != NULL, NULL);

	if (!is_html)
		message = escaped_message = g_markup_escape_text(message, -1);

	parse_data.job.find.found_smileys =
		g_hash_table_new(g_direct_hash, g_direct_equal);
	parse_data.in_html_tag = FALSE;

	trie_1.data = html_sentry;
	trie_2.data = smileys_trie;
	trie_1.next = &trie_2;
	trie_2.next = NULL;
	purple_trie_multi_find(&trie_1, message, smiley_find_cb, &parse_data);

	g_free(escaped_message);

	found_list = g_hash_table_get_values(parse_data.job.find.found_smileys);
	g_hash_table_destroy(parse_data.job.find.found_smileys);

	return found_list;
}

void
_purple_smiley_parser_init(void)
{
	html_sentry = purple_trie_new();
	purple_trie_add(html_sentry, "<", NULL);
	purple_trie_add(html_sentry, ">", NULL);
}

void
_purple_smiley_parser_uninit(void)
{
	g_object_unref(html_sentry);
}