/*
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) version 3.
 *
 * 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with the program; if not, see <http://www.gnu.org/licenses/>
 *
 *
 * Authors:
 *		Jeffrey Stedfast <fejj@ximian.com>
 *
 * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
 *
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <string.h>

#include <glib/gi18n.h>
#include <gconf/gconf-client.h>

#include "em-inline-filter.h"
#include "em-stripsig-filter.h"
#include "em-format-quote.h"

#define EM_FORMAT_QUOTE_GET_PRIVATE(obj) \
	(G_TYPE_INSTANCE_GET_PRIVATE \
	((obj), EM_TYPE_FORMAT_QUOTE, EMFormatQuotePrivate))

struct _EMFormatQuotePrivate {
	gchar *credits;
	CamelStream *stream;
	EMFormatQuoteFlags flags;
	guint32 text_html_flags;
};

static void emfq_builtin_init (EMFormatQuoteClass *efhc);

static gpointer parent_class;

static void
emfq_dispose (GObject *object)
{
	EMFormatQuotePrivate *priv;

	priv = EM_FORMAT_QUOTE_GET_PRIVATE (object);

	if (priv->stream != NULL) {
		g_object_unref (priv->stream);
		priv->stream = NULL;
	}

	/* Chain up to parent's dispose() method. */
	G_OBJECT_CLASS (parent_class)->dispose (object);
}

static void
emfq_finalize (GObject *object)
{
	EMFormatQuotePrivate *priv;

	priv = EM_FORMAT_QUOTE_GET_PRIVATE (object);

	g_free (priv->credits);

	/* Chain up to parent's finalize() method. */
	G_OBJECT_CLASS (parent_class)->finalize (object);
}

static void
emfq_format_clone (EMFormat *emf,
                   CamelFolder *folder,
                   const gchar *uid,
                   CamelMimeMessage *msg,
                   EMFormat *src,
                   GCancellable *cancellable)
{
	EMFormatQuote *emfq = (EMFormatQuote *) emf;
	const EMFormatHandler *handle;
	GConfClient *gconf;

	/* Chain up to parent's format_clone() method. */
	EM_FORMAT_CLASS (parent_class)->format_clone (
		emf, folder, uid, msg, src, cancellable);

	g_seekable_seek (
		G_SEEKABLE (emfq->priv->stream),
		0, G_SEEK_SET, NULL, NULL);

	gconf = gconf_client_get_default ();
	if (gconf_client_get_bool (
		gconf, "/apps/evolution/mail/composer/top_signature", NULL))
		camel_stream_write_string (
			emfq->priv->stream, "<br>\n", cancellable, NULL);
	g_object_unref (gconf);
	handle = em_format_find_handler(emf, "x-evolution/message/prefix");
	if (handle)
		handle->handler (
			emf, emfq->priv->stream,
			CAMEL_MIME_PART (msg),
			handle, cancellable, FALSE);
	handle = em_format_find_handler(emf, "x-evolution/message/rfc822");
	if (handle)
		handle->handler (
			emf, emfq->priv->stream,
			CAMEL_MIME_PART (msg),
			handle, cancellable, FALSE);

	camel_stream_flush (emfq->priv->stream, cancellable, NULL);

	g_signal_emit_by_name(emf, "complete");
}

static void
emfq_format_error (EMFormat *emf,
                   CamelStream *stream,
                   const gchar *errmsg)
{
	/* Nothing to do. */
}

static void
emfq_format_source (EMFormat *emf,
                    CamelStream *stream,
                    CamelMimePart *part,
                    GCancellable *cancellable)
{
	CamelStream *filtered_stream;
	CamelMimeFilter *html_filter;

	filtered_stream = camel_stream_filter_new (stream);
	html_filter = camel_mime_filter_tohtml_new (
		CAMEL_MIME_FILTER_TOHTML_CONVERT_NL |
		CAMEL_MIME_FILTER_TOHTML_CONVERT_SPACES |
		CAMEL_MIME_FILTER_TOHTML_ESCAPE_8BIT, 0);
	camel_stream_filter_add (
		CAMEL_STREAM_FILTER (filtered_stream), html_filter);
	g_object_unref (html_filter);

	em_format_format_text (
		emf, filtered_stream,
		CAMEL_DATA_WRAPPER (part), cancellable);

	g_object_unref (filtered_stream);
}

static void
emfq_format_attachment (EMFormat *emf,
                        CamelStream *stream,
                        CamelMimePart *part,
                        const gchar *mime_type,
                        const EMFormatHandler *handle,
                        GCancellable *cancellable)
{
	EMFormatQuote *emfq = EM_FORMAT_QUOTE (emf);
	gchar *text, *html;

	if (!em_format_is_inline (emf, emf->part_id->str, part, handle))
		return;

	camel_stream_write_string (
		stream, "<table border=1 cellspacing=0 cellpadding=0>"
		"<tr><td><font size=-1>\n", cancellable, NULL);

	/* output some info about it */
	text = em_format_describe_part (part, mime_type);
	html = camel_text_to_html (
		text, emfq->priv->text_html_flags &
		CAMEL_MIME_FILTER_TOHTML_CONVERT_URLS, 0);
	camel_stream_write_string (stream, html, cancellable, NULL);
	g_free (html);
	g_free (text);

	camel_stream_write_string (
		stream, "</font></td></tr></table>", cancellable, NULL);

	handle->handler (emf, stream, part, handle, cancellable, FALSE);
}

static void
emfq_base_init (EMFormatQuoteClass *class)
{
	emfq_builtin_init (class);
}

static void
emfq_class_init (EMFormatQuoteClass *class)
{
	GObjectClass *object_class;
	EMFormatClass *format_class;

	parent_class = g_type_class_peek_parent (class);
	g_type_class_add_private (class, sizeof (EMFormatQuotePrivate));

	object_class = G_OBJECT_CLASS (class);
	object_class->dispose = emfq_dispose;
	object_class->finalize = emfq_finalize;

	format_class = EM_FORMAT_CLASS (class);
	format_class->format_clone = emfq_format_clone;
	format_class->format_error = emfq_format_error;
	format_class->format_source = emfq_format_source;
	format_class->format_attachment = emfq_format_attachment;
}

static void
emfq_init (EMFormatQuote *emfq)
{
	emfq->priv = EM_FORMAT_QUOTE_GET_PRIVATE (emfq);

	/* we want to convert url's etc */
	emfq->priv->text_html_flags =
		CAMEL_MIME_FILTER_TOHTML_PRE |
		CAMEL_MIME_FILTER_TOHTML_CONVERT_URLS |
		CAMEL_MIME_FILTER_TOHTML_CONVERT_ADDRESSES;
}

GType
em_format_quote_get_type (void)
{
	static GType type = 0;

	if (G_UNLIKELY (type == 0)) {
		static const GTypeInfo type_info = {
			sizeof (EMFormatQuoteClass),
			(GBaseInitFunc) emfq_base_init,
			(GBaseFinalizeFunc) NULL,
			(GClassInitFunc) emfq_class_init,
			(GClassFinalizeFunc) NULL,
			NULL,  /* class_data */
			sizeof (EMFormatQuote),
			0,     /* n_preallocs */
			(GInstanceInitFunc) emfq_init,
			NULL   /* value_table */
		};

		type = g_type_register_static (
			EM_TYPE_FORMAT, "EMFormatQuote", &type_info, 0);
	}

	return type;
}

EMFormatQuote *
em_format_quote_new (const gchar *credits,
                     CamelStream *stream,
                     EMFormatQuoteFlags flags)
{
	EMFormatQuote *emfq;

	g_return_val_if_fail (CAMEL_IS_STREAM (stream), NULL);

	/* Steam must also be seekable so we can reset its position. */
	g_return_val_if_fail (G_IS_SEEKABLE (stream), NULL);

	emfq = g_object_new (EM_TYPE_FORMAT_QUOTE, NULL);

	emfq->priv->credits = g_strdup (credits);
	emfq->priv->stream = g_object_ref (stream);
	emfq->priv->flags = flags;

	return emfq;
}

static void
emfq_format_text_header (EMFormatQuote *emfq,
                         GString *buffer,
                         const gchar *label,
                         const gchar *value,
                         guint32 flags,
                         gint is_html)
{
	const gchar *html;
	gchar *mhtml = NULL;

	if (value == NULL)
		return;

	while (*value == ' ')
		value++;

	if (!is_html)
		html = mhtml = camel_text_to_html (value, 0, 0);
	else
		html = value;

	if (flags & EM_FORMAT_HEADER_BOLD)
		g_string_append_printf (
			buffer, "<b>%s</b>: %s<br>", label, html);
	else
		g_string_append_printf (
			buffer, "%s: %s<br>", label, html);

	g_free (mhtml);
}

static const gchar *addrspec_hdrs[] = {
	"Sender", "From", "Reply-To", "To", "Cc", "Bcc",
	"Resent-Sender", "Resent-from", "Resent-Reply-To",
	"Resent-To", "Resent-cc", "Resent-Bcc", NULL
};

#if 0
/* FIXME: include Sender and Resent-* headers too? */
/* For Translators only: The following strings are
 * used in the header table in the preview pane. */
static gchar *i18n_hdrs[] = {
	N_("From"), N_("Reply-To"), N_("To"), N_("Cc"), N_("Bcc")
};
#endif

static void
emfq_format_address (GString *out, struct _camel_header_address *a)
{
	guint32 flags = CAMEL_MIME_FILTER_TOHTML_CONVERT_SPACES;
	gchar *name, *mailto, *addr;

	while (a) {
		if (a->name)
			name = camel_text_to_html (a->name, flags, 0);
		else
			name = NULL;

		switch (a->type) {
		case CAMEL_HEADER_ADDRESS_NAME:
			if (name && *name) {
				gchar *real, *mailaddr;

				g_string_append_printf (out, "%s &lt;", name);
				/* rfc2368 for mailto syntax and url encoding extras */
				if ((real = camel_header_encode_phrase ((guchar *) a->name))) {
					mailaddr = g_strdup_printf ("%s <%s>", real, a->v.addr);
					g_free (real);
					mailto = camel_url_encode (mailaddr, "?=&()");
					g_free (mailaddr);
				} else {
					mailto = camel_url_encode (a->v.addr, "?=&()");
				}
			} else {
				mailto = camel_url_encode (a->v.addr, "?=&()");
			}
			addr = camel_text_to_html (a->v.addr, flags, 0);
			g_string_append_printf (out, "<a href=\"mailto:%s\">%s</a>", mailto, addr);
			g_free (mailto);
			g_free (addr);

			if (name && *name)
				g_string_append (out, "&gt;");
			break;
		case CAMEL_HEADER_ADDRESS_GROUP:
			g_string_append_printf (out, "%s: ", name);
			emfq_format_address (out, a->v.members);
			g_string_append_printf (out, ";");
			break;
		default:
			g_warning ("Invalid address type");
			break;
		}

		g_free (name);

		a = a->next;
		if (a)
			g_string_append (out, ", ");
	}
}

static void
canon_header_name (gchar *name)
{
	gchar *inptr = name;

	/* canonicalise the header name... first letter is
	 * capitalised and any letter following a '-' also gets
	 * capitalised */

	if (g_ascii_islower (*inptr))
		*inptr = g_ascii_toupper (*inptr);

	inptr++;

	while (*inptr) {
		if (inptr[-1] == '-' && g_ascii_islower (*inptr))
			*inptr = g_ascii_toupper (*inptr);
		else if (g_ascii_isupper (*inptr))
			*inptr = g_ascii_tolower (*inptr);

		inptr++;
	}
}

static void
emfq_format_header (EMFormat *emf,
                    GString *buffer,
                    CamelMedium *part,
                    const gchar *namein,
                    guint32 flags,
                    const gchar *charset)
{
	CamelMimeMessage *msg = (CamelMimeMessage *) part;
	EMFormatQuote *emfq = (EMFormatQuote *) emf;
	gchar *name, *buf, *value = NULL;
	const gchar *txt, *label;
	gboolean addrspec = FALSE;
	gint is_html = FALSE;
	gint i;

	name = g_alloca (strlen (namein) + 1);
	strcpy (name, namein);
	canon_header_name (name);

	/* Never quote Bcc headers */
	if (g_str_equal (name, "Bcc") || g_str_equal (name, "Resent-Bcc"))
		return;

	for (i = 0; addrspec_hdrs[i]; i++) {
		if (!strcmp (name, addrspec_hdrs[i])) {
			addrspec = TRUE;
			break;
		}
	}

	label = _(name);

	if (addrspec) {
		struct _camel_header_address *addrs;
		GString *html;

		if (!(txt = camel_medium_get_header (part, name)))
			return;

		buf = camel_header_unfold (txt);
		addrs = camel_header_address_decode (
			txt, emf->charset ?
			emf->charset : emf->default_charset);
		if (addrs == NULL) {
			g_free (buf);
			return;
		}

		g_free (buf);

		html = g_string_new ("");
		emfq_format_address (html, addrs);
		camel_header_address_unref (addrs);
		txt = value = html->str;
		g_string_free (html, FALSE);
		flags |= EM_FORMAT_HEADER_BOLD;
		is_html = TRUE;
	} else if (!strcmp (name, "Subject")) {
		txt = camel_mime_message_get_subject (msg);
		label = _("Subject");
		flags |= EM_FORMAT_HEADER_BOLD;
	} else if (!strcmp (name, "X-Evolution-Mailer")) { /* pseudo-header */
		if (!(txt = camel_medium_get_header (part, "x-mailer")))
			if (!(txt = camel_medium_get_header (part, "user-agent")))
				if (!(txt = camel_medium_get_header (part, "x-newsreader")))
					if (!(txt = camel_medium_get_header (part, "x-mimeole")))
						return;

		txt = value = camel_header_format_ctext (txt, charset);

		label = _("Mailer");
		flags |= EM_FORMAT_HEADER_BOLD;
	} else if (!strcmp (name, "Date") || !strcmp (name, "Resent-Date")) {
		if (!(txt = camel_medium_get_header (part, name)))
			return;

		flags |= EM_FORMAT_HEADER_BOLD;
	} else {
		txt = camel_medium_get_header (part, name);
		buf = camel_header_unfold (txt);
		txt = value = camel_header_decode_string (txt, charset);
		g_free (buf);
	}

	emfq_format_text_header (emfq, buffer, label, txt, flags, is_html);

	g_free (value);
}

static void
emfq_format_headers (EMFormatQuote *emfq,
                     GString *buffer,
                     CamelMedium *part)
{
	EMFormat *emf = (EMFormat *) emfq;
	CamelContentType *ct;
	const gchar *charset;
	GList *link;

	if (!part)
		return;

	ct = camel_mime_part_get_content_type ((CamelMimePart *) part);
	charset = camel_content_type_param (ct, "charset");
	charset = camel_iconv_charset_name (charset);

	/* dump selected headers */
	link = g_queue_peek_head_link (&emf->header_list);
	while (link != NULL) {
		EMFormatHeader *h = link->data;
		emfq_format_header (
			emf, buffer, part, h->name, h->flags, charset);
		link = g_list_next (link);
	}

	g_string_append (buffer, "<br>\n");
}

static void
emfq_format_message_prefix (EMFormat *emf,
                            CamelStream *stream,
                            CamelMimePart *part,
                            const EMFormatHandler *info,
                            GCancellable *cancellable,
                            gboolean is_fallback)
{
	EMFormatQuote *emfq = (EMFormatQuote *) emf;

	if (emfq->priv->credits != NULL) {
		camel_stream_write_string (
			stream, emfq->priv->credits, NULL, NULL);
		camel_stream_write_string (
			stream, "<br>\n", NULL, NULL);
	}
}

static void
emfq_format_message (EMFormat *emf,
                     CamelStream *stream,
                     CamelMimePart *part,
                     const EMFormatHandler *info,
                     GCancellable *cancellable,
                     gboolean is_fallback)
{
	EMFormatQuote *emfq = (EMFormatQuote *) emf;
	GString *buffer;

	buffer = g_string_sized_new (1024);

	if (emfq->priv->flags & EM_FORMAT_QUOTE_CITE)
		g_string_append (
			buffer,
			"<!--+GtkHTML:<DATA class=\"ClueFlow\" "
			"key=\"orig\" value=\"1\">-->\n"
			"<blockquote type=cite>\n");

	if (((CamelMimePart *) emf->message) != part) {
		g_string_append_printf (
			buffer,
			"%s</br>\n",
			_("-------- Forwarded Message --------"));
		emfq_format_headers (emfq, buffer, (CamelMedium *) part);
	} else if (emfq->priv->flags & EM_FORMAT_QUOTE_HEADERS)
		emfq_format_headers (emfq, buffer, (CamelMedium *) part);

	camel_stream_write (
		stream, buffer->str, buffer->len, cancellable, NULL);

	em_format_part (emf, stream, part, cancellable);

	if (emfq->priv->flags & EM_FORMAT_QUOTE_CITE)
		camel_stream_write_string (
			stream, "</blockquote><!--+GtkHTML:"
			"<DATA class=\"ClueFlow\" clear=\"orig\">-->",
			cancellable, NULL);
}

/* Decodes inline encoded parts of 'part'. The returned pointer,
 * if not NULL, should be unreffed with g_object_unref(). */
static CamelMimePart *
decode_inline_parts (CamelMimePart *part, GCancellable *cancellable)
{
	CamelMultipart *mp;
	CamelStream *null;
	CamelStream *filtered_stream;
	EMInlineFilter *inline_filter;

	g_return_val_if_fail (part != NULL, NULL);

	null = camel_stream_null_new ();
	filtered_stream = camel_stream_filter_new (null);
	g_object_unref (null);

	inline_filter = em_inline_filter_new (
		camel_mime_part_get_encoding (part),
		camel_mime_part_get_content_type (part));
	camel_stream_filter_add (
		CAMEL_STREAM_FILTER (filtered_stream),
		CAMEL_MIME_FILTER (inline_filter));
	camel_data_wrapper_decode_to_stream_sync (
		camel_medium_get_content (CAMEL_MEDIUM (part)),
		filtered_stream, cancellable, NULL);
	camel_stream_close (filtered_stream, cancellable, NULL);
	g_object_unref (filtered_stream);

	if (!em_inline_filter_found_any (inline_filter)) {
		g_object_unref (inline_filter);
		return NULL;
	}

	mp = em_inline_filter_get_multipart (inline_filter);

	g_object_unref (inline_filter);

	if (mp) {
		part = camel_mime_part_new ();
		camel_medium_set_content (
			CAMEL_MEDIUM (part), CAMEL_DATA_WRAPPER (mp));
		g_object_unref (mp);
	} else {
		g_object_ref (part);
	}

	return part;
}

static void
emfq_text_plain (EMFormat *emf,
                 CamelStream *stream,
                 CamelMimePart *part,
                 const EMFormatHandler *info,
                 GCancellable *cancellable,
                 gboolean is_fallback)
{
	EMFormatQuote *emfq = EM_FORMAT_QUOTE (emf);
	CamelStream *filtered_stream;
	CamelMimeFilter *html_filter;
	CamelMimeFilter *sig_strip;
	CamelMimePart *mp;
	CamelContentType *type;
	const gchar *format;
	guint32 rgb = 0x737373, flags;

	if (!part)
		return;

	mp = decode_inline_parts (part, cancellable);
	if (mp) {
		if (CAMEL_IS_MULTIPART (camel_medium_get_content (CAMEL_MEDIUM (mp)))) {
			em_format_part (emf, stream, mp, cancellable);
			g_object_unref (mp);

			return;
		}

		g_object_unref (mp);
	}

	flags = emfq->priv->text_html_flags;

	/* Check for RFC 2646 flowed text. */
	type = camel_mime_part_get_content_type (part);
	if (camel_content_type_is(type, "text", "plain")
	    && (format = camel_content_type_param(type, "format"))
	    && !g_ascii_strcasecmp(format, "flowed"))
		flags |= CAMEL_MIME_FILTER_TOHTML_FORMAT_FLOWED;

	filtered_stream = camel_stream_filter_new (stream);

	if ((emfq->priv->flags & EM_FORMAT_QUOTE_KEEP_SIG) == 0) {
		sig_strip = em_stripsig_filter_new (TRUE);
		camel_stream_filter_add (
			CAMEL_STREAM_FILTER (filtered_stream), sig_strip);
		g_object_unref (sig_strip);
	}

	html_filter = camel_mime_filter_tohtml_new (flags, rgb);
	camel_stream_filter_add (
		CAMEL_STREAM_FILTER (filtered_stream), html_filter);
	g_object_unref (html_filter);

	em_format_format_text (
		EM_FORMAT (emfq), filtered_stream,
		CAMEL_DATA_WRAPPER (part), cancellable);

	camel_stream_flush (filtered_stream, cancellable, NULL);
	g_object_unref (filtered_stream);
}

static void
emfq_text_enriched (EMFormat *emf,
                    CamelStream *stream,
                    CamelMimePart *part,
                    const EMFormatHandler *info,
                    GCancellable *cancellable,
                    gboolean is_fallback)
{
	CamelStream *filtered_stream;
	CamelMimeFilter *enriched;
	guint32 flags = 0;

	if (g_strcmp0 (info->mime_type, "text/richtext") == 0) {
		flags = CAMEL_MIME_FILTER_ENRICHED_IS_RICHTEXT;
		camel_stream_write_string (
			stream, "\n<!-- text/richtext -->\n",
			cancellable, NULL);
	} else {
		camel_stream_write_string (
			stream, "\n<!-- text/enriched -->\n",
			cancellable, NULL);
	}

	enriched = camel_mime_filter_enriched_new (flags);
	filtered_stream = camel_stream_filter_new (stream);
	camel_stream_filter_add (
		CAMEL_STREAM_FILTER (filtered_stream), enriched);
	g_object_unref (enriched);

	camel_stream_write_string (stream, "<br><hr><br>", cancellable, NULL);
	em_format_format_text (
		emf, filtered_stream, CAMEL_DATA_WRAPPER (part), cancellable);
	camel_stream_flush (filtered_stream, cancellable, NULL);
	g_object_unref (filtered_stream);
}

static void
emfq_text_html (EMFormat *emf,
                CamelStream *stream,
                CamelMimePart *part,
                const EMFormatHandler *info,
                GCancellable *cancellable,
                gboolean is_fallback)
{
	EMFormatQuotePrivate *priv;

	priv = EM_FORMAT_QUOTE_GET_PRIVATE (emf);

	camel_stream_write_string (
		stream, "\n<!-- text/html -->\n", cancellable, NULL);

	if ((priv->flags & EM_FORMAT_QUOTE_KEEP_SIG) == 0) {
		CamelMimeFilter *sig_strip;
		CamelStream *filtered_stream;

		filtered_stream = camel_stream_filter_new (stream);

		sig_strip = em_stripsig_filter_new (FALSE);
		camel_stream_filter_add (
			CAMEL_STREAM_FILTER (filtered_stream), sig_strip);
		g_object_unref (sig_strip);

		em_format_format_text (
			emf, filtered_stream,
			(CamelDataWrapper *) part, cancellable);
		camel_stream_flush (filtered_stream, cancellable, NULL);
		g_object_unref (filtered_stream);
	} else {
		em_format_format_text (
			emf, stream,
			(CamelDataWrapper *) part, cancellable);
	}
}

static void
emfq_ignore (EMFormat *emf,
             CamelStream *stream,
             CamelMimePart *part,
             const EMFormatHandler *info,
             GCancellable *cancellable,
             gboolean is_fallback)
{
	/* NOOP */
}

static EMFormatHandler type_builtin_table[] = {
	{ (gchar *) "text/plain", emfq_text_plain },
	{ (gchar *) "text/enriched", emfq_text_enriched },
	{ (gchar *) "text/richtext", emfq_text_enriched },
	{ (gchar *) "text/html", emfq_text_html },
	{ (gchar *) "text/*", emfq_text_plain },
	{ (gchar *) "message/external-body", emfq_ignore },
	{ (gchar *) "multipart/appledouble", emfq_ignore },

	/* internal evolution types */
	{ (gchar *) "x-evolution/evolution-rss-feed", emfq_text_html },
	{ (gchar *) "x-evolution/message/rfc822", emfq_format_message },
	{ (gchar *) "x-evolution/message/prefix", emfq_format_message_prefix },
};

static void
emfq_builtin_init (EMFormatQuoteClass *efhc)
{
	gint ii;

	for (ii = 0; ii < G_N_ELEMENTS (type_builtin_table); ii++)
		em_format_class_add_handler (
			EM_FORMAT_CLASS (efhc), &type_builtin_table[ii]);
}
