# HG changeset patch
# Node ID a5314f6a6dd6cc9b6d4d737ab3c0c0055647f804
# Parent  bc94d447483213075b596be0b2b502df973523c9
diff --git a/xeme/meson.build b/xeme/meson.build
--- a/xeme/meson.build
+++ b/xeme/meson.build
@@ -128,7 +128,7 @@
 # Library Dependency Object
 ###############################################################################
 xeme_dep = declare_dependency(
-  dependencies : [gio_dep, glib_dep, gobject_dep],
+  dependencies : [birb_dep, gio_dep, glib_dep, gobject_dep],
   include_directories : [toplevel_inc, xeme_inc],
   link_with : xeme_lib,
   sources : XEME_BUILT_HEADERS + XEME_GENERATED_TARGETS)
diff --git a/xeme/tests/testmessage.c b/xeme/tests/testmessage.c
--- a/xeme/tests/testmessage.c
+++ b/xeme/tests/testmessage.c
@@ -22,6 +22,8 @@
 
 #include <glib.h>
 
+#include <birb.h>
+
 #include <xeme.h>
 
 /******************************************************************************
@@ -33,7 +35,7 @@
 	XemeStanza *message = NULL;
 
 	message = xeme_message_new();
-	g_assert_true(XEME_IS_MESSAGE(message));
+	birb_assert_type(message, XEME_TYPE_MESSAGE);
 
 	message_type = xeme_message_get_message_type(XEME_MESSAGE(message));
 	g_assert_cmpuint(message_type, ==, XEME_MESSAGE_TYPE_NORMAL);
@@ -45,6 +47,8 @@
 test_xeme_message_properties(void) {
 	XemeMessage *message = NULL;
 	XemeMessageType message_type = XEME_MESSAGE_TYPE_UNKNOWN;
+	GListModel *bodies = NULL;
+	GListModel *subjects = NULL;
 	char *thread = NULL;
 	char *thread_parent = NULL;
 
@@ -55,15 +59,23 @@
 		"thread-parent", "thread-parent1",
 		NULL);
 
-	g_assert_true(XEME_IS_MESSAGE(message));
+	birb_assert_type(message, XEME_TYPE_MESSAGE);
 
 	g_object_get(
 		message,
+		"bodies", &bodies,
 		"message-type", &message_type,
+		"subjects", &subjects,
 		"thread", &thread,
 		"thread-parent", &thread_parent,
 		NULL);
 
+	birb_assert_type(bodies, G_TYPE_LIST_STORE);
+	g_clear_object(&bodies);
+
+	birb_assert_type(subjects, G_TYPE_LIST_STORE);
+	g_clear_object(&subjects);
+
 	g_assert_cmpuint(message_type, ==, XEME_MESSAGE_TYPE_HEADLINE);
 
 	g_assert_cmpstr(thread, ==, "thread1");
@@ -76,6 +88,135 @@
 }
 
 static void
+test_xeme_message_body_add_remove(void) {
+	XemeStanza *message = NULL;
+	gboolean result = FALSE;
+
+	message = xeme_message_new();
+
+	/* Make sure we can't remove a body that doesn't exist. */
+	result = xeme_message_remove_body(XEME_MESSAGE(message), NULL);
+	g_assert_false(result);
+
+	/* Add a body twice and remove it. */
+	xeme_message_add_body(XEME_MESSAGE(message), NULL, "ping");
+	xeme_message_add_body(XEME_MESSAGE(message), NULL, "pong");
+	result = xeme_message_remove_body(XEME_MESSAGE(message), NULL);
+	g_assert_true(result);
+
+	/* Try and remove it again. */
+	result = xeme_message_remove_body(XEME_MESSAGE(message), NULL);
+	g_assert_false(result);
+
+	g_assert_finalize_object(XEME_MESSAGE(message));
+}
+
+static void
+text_xeme_message_body_get(void) {
+	XemeStanza *message = NULL;
+	XemeLocalizedString *l10nstr = NULL;
+	const char *content = NULL;
+
+	message = xeme_message_new();
+
+	l10nstr = xeme_message_get_body(XEME_MESSAGE(message), NULL);
+	g_assert_null(l10nstr);
+
+	/* Add a body and make sure we can retrieve it. */
+	xeme_message_add_body(XEME_MESSAGE(message), NULL, "hello");
+	l10nstr = xeme_message_get_body(XEME_MESSAGE(message), NULL);
+	birb_assert_type(l10nstr, XEME_TYPE_LOCALIZED_STRING);
+
+	content = xeme_localized_string_get_string(l10nstr);
+	g_assert_cmpstr(content, ==, "hello");
+	g_clear_object(&l10nstr);
+
+	/* Add another body for a different language and make sure both can be
+	 * retrieved.
+	 */
+	xeme_message_add_body(XEME_MESSAGE(message), "es", "hola");
+	l10nstr = xeme_message_get_body(XEME_MESSAGE(message), "es");
+	birb_assert_type(l10nstr, XEME_TYPE_LOCALIZED_STRING);
+	content = xeme_localized_string_get_string(l10nstr);
+	g_assert_cmpstr(content, ==, "hola");
+	g_clear_object(&l10nstr);
+
+	l10nstr = xeme_message_get_body(XEME_MESSAGE(message), NULL);
+	birb_assert_type(l10nstr, XEME_TYPE_LOCALIZED_STRING);
+	content = xeme_localized_string_get_string(l10nstr);
+	g_assert_cmpstr(content, ==, "hello");
+	g_clear_object(&l10nstr);
+
+	g_assert_finalize_object(message);
+}
+
+static void
+test_xeme_message_subject_add_remove(void) {
+	XemeStanza *message = NULL;
+	gboolean result = FALSE;
+
+	message = xeme_message_new();
+
+	/* Make sure we can't remove a subject that doesn't exist. */
+	result = xeme_message_remove_subject(XEME_MESSAGE(message), NULL);
+	g_assert_false(result);
+
+	/* Add a subject twice and remove it. */
+	xeme_message_add_subject(XEME_MESSAGE(message), NULL, "math");
+	xeme_message_add_subject(XEME_MESSAGE(message), NULL, "science");
+	result = xeme_message_remove_subject(XEME_MESSAGE(message), NULL);
+	g_assert_true(result);
+
+	/* Try and remove it again. */
+	result = xeme_message_remove_subject(XEME_MESSAGE(message), NULL);
+	g_assert_false(result);
+
+	g_assert_finalize_object(XEME_MESSAGE(message));
+}
+
+static void
+text_xeme_message_subject_get(void) {
+	XemeStanza *message = NULL;
+	XemeLocalizedString *l10nstr = NULL;
+	const char *content = NULL;
+
+	message = xeme_message_new();
+
+	l10nstr = xeme_message_get_subject(XEME_MESSAGE(message), NULL);
+	g_assert_null(l10nstr);
+
+	/* Add a subject and make sure we can retrieve it. */
+	xeme_message_add_subject(XEME_MESSAGE(message), NULL, "hello");
+	l10nstr = xeme_message_get_subject(XEME_MESSAGE(message), NULL);
+	birb_assert_type(l10nstr, XEME_TYPE_LOCALIZED_STRING);
+
+	content = xeme_localized_string_get_string(l10nstr);
+	g_assert_cmpstr(content, ==, "hello");
+	g_clear_object(&l10nstr);
+
+	/* Add another subject for a different language and make sure both can be
+	 * retrieved.
+	 */
+	xeme_message_add_subject(XEME_MESSAGE(message), "es", "hola");
+	l10nstr = xeme_message_get_subject(XEME_MESSAGE(message), "es");
+	birb_assert_type(l10nstr, XEME_TYPE_LOCALIZED_STRING);
+	content = xeme_localized_string_get_string(l10nstr);
+	g_assert_cmpstr(content, ==, "hola");
+	g_clear_object(&l10nstr);
+
+	l10nstr = xeme_message_get_subject(XEME_MESSAGE(message), NULL);
+	birb_assert_type(l10nstr, XEME_TYPE_LOCALIZED_STRING);
+	content = xeme_localized_string_get_string(l10nstr);
+	g_assert_cmpstr(content, ==, "hello");
+	g_clear_object(&l10nstr);
+
+	g_assert_finalize_object(message);
+}
+
+/******************************************************************************
+ * Marshal Tests
+ *****************************************************************************/
+static void
 test_xeme_message_marshal_empty(void) {
 	XemeStanza *message = NULL;
 	GError *error = NULL;
@@ -314,6 +455,15 @@
 	g_test_add_func("/xeme/message/new", test_xeme_message_new);
 	g_test_add_func("/xeme/message/properties", test_xeme_message_properties);
 
+	g_test_add_func("/xeme/message/body/add-remove",
+	                test_xeme_message_body_add_remove);
+	g_test_add_func("/xeme/message/body/get", text_xeme_message_body_get);
+
+	g_test_add_func("/xeme/message/subject/add-remove",
+	                test_xeme_message_subject_add_remove);
+	g_test_add_func("/xeme/message/subject/get",
+	                text_xeme_message_subject_get);
+
 	g_test_add_func("/xeme/message/marshal/empty",
 	                test_xeme_message_marshal_empty);
 	g_test_add_func("/xeme/message/marshal/all-attributes",
diff --git a/xeme/xememessage.c b/xeme/xememessage.c
--- a/xeme/xememessage.c
+++ b/xeme/xememessage.c
@@ -27,7 +27,9 @@
 
 enum {
 	PROP_0,
+	PROP_BODIES,
 	PROP_MESSAGE_TYPE,
+	PROP_SUBJECTS,
 	PROP_THREAD,
 	PROP_THREAD_PARENT,
 	N_PROPERTIES,
@@ -39,11 +41,8 @@
 
 	XemeMessageType message_type;
 
-	GHashTable *bodies;
-	GPtrArray *bodies_order;
-
-	GHashTable *subjects;
-	GPtrArray *subjects_order;
+	GListStore *bodies;
+	GListStore *subjects;
 
 	char *thread;
 	char *thread_parent;
@@ -52,69 +51,51 @@
 /******************************************************************************
  * Helpers
  *****************************************************************************/
-static void
-xeme_message_add_element_with_language(XemeMessage *message,
-                                       GHashTable *collection,
-                                       GPtrArray *order,
-                                       const char *language,
-                                       const char *content)
+static gboolean
+xeme_message_localized_string_language_equal(gconstpointer a,
+                                             gconstpointer b)
 {
-	char *lang = NULL;
-	gboolean found = FALSE;
-	gpointer key = NULL;
+	const char *lang1 = NULL;
+	const char *lang2 = NULL;
 
-	g_return_if_fail(XEME_IS_MESSAGE(message));
-	g_return_if_fail(content != NULL);
+	if(XEME_IS_LOCALIZED_STRING((gpointer)a)) {
+		XemeLocalizedString *l10nstr = (gpointer)a;
 
-	/* If the caller passes in NULL, we just convert it to empty string. */
-	if(!xeme_str_is_empty(language)) {
-		lang = g_strdup(language);
-	} else {
-		lang = g_strdup("");
+		lang1 = xeme_localized_string_get_language(l10nstr);
 	}
 
-	/* Look if the language already exists, and if so, remove it from our
-	 * ordered list of languages.
-	 */
-	found = g_hash_table_lookup_extended(collection, lang, &key, NULL);
-	if(found) {
-		g_ptr_array_remove(order, key);
+	if(XEME_IS_LOCALIZED_STRING((gpointer)b)) {
+		XemeLocalizedString *l10nstr = (gpointer)b;
+
+		lang2 = xeme_localized_string_get_language(l10nstr);
 	}
 
-	/* Add the language to our list of ordered languages. This is used to look
-	 * up keys in the bodies hash table, so we just keep a pointer to that
-	 * value rather than creating a new copy and requiring a strcmp to find it
-	 * again.
-	 */
-	g_ptr_array_add(order, lang);
-
-	/* We use g_hash_table_replace because we want to free the old key and
-	 * have it use the new one we pass in, which is the opposite of what
-	 * g_hash_table_insert does.
-	 */
-	g_hash_table_replace(collection, lang, g_strdup(content));
+	return xeme_str_equal(lang1, lang2);
 }
 
 static void
-xeme_message_marshal_language_collection(GHashTable *collection,
-                                         GPtrArray *order,
+xeme_message_marshal_language_collection(GListModel *collection,
                                          GString *str,
                                          const char *element)
 {
-	for(guint i = 0; i < order->len; i++) {
+	for(guint i = 0; i < g_list_model_get_n_items(collection); i++) {
+		XemeLocalizedString *l10nstr = NULL;
 		const char *lang = NULL;
 		const char *content = NULL;
 
-		lang = g_ptr_array_index(order, i);
-		content = g_hash_table_lookup(collection, lang);
+		l10nstr = g_list_model_get_item(collection, i);
+		lang = xeme_localized_string_get_language(l10nstr);
+		content = xeme_localized_string_get_string(l10nstr);
 
-		g_string_append_printf(str, "<%s", element);
 		if(xeme_str_is_empty(lang)) {
-			g_string_append_printf(str, ">%s", content);
+			g_string_append_printf(str, "<%s>%s</%s>", element, content,
+			                       element);
 		} else {
-			g_string_append_printf(str, " xml:lang=\"%s\">%s", lang, content);
+			g_string_append_printf(str, "<%s xml:lang=\"%s\">%s</%s>",
+			                       element, lang, content, element);
 		}
-		g_string_append_printf(str, "</%s>", element);
+
+		g_clear_object(&l10nstr);
 	}
 }
 
@@ -138,12 +119,10 @@
 	g_clear_pointer(&attrs, g_free);
 
 	/* Now run through the collections. */
-	xeme_message_marshal_language_collection(message->subjects,
-	                                         message->subjects_order, str,
-	                                         "subject");
-	xeme_message_marshal_language_collection(message->bodies,
-	                                         message->bodies_order, str,
-	                                         "body");
+	xeme_message_marshal_language_collection(G_LIST_MODEL(message->bodies),
+	                                         str, "body");
+	xeme_message_marshal_language_collection(G_LIST_MODEL(message->subjects),
+	                                         str, "subject");
 
 	if(!xeme_str_is_empty(message->thread)) {
 		if(!xeme_str_is_empty(message->thread_parent)) {
@@ -170,13 +149,8 @@
 xeme_message_finalize(GObject *obj) {
 	XemeMessage *message = XEME_MESSAGE(obj);
 
-	g_ptr_array_free(message->subjects_order, TRUE);
-	message->subjects_order = NULL;
-	g_clear_pointer(&message->subjects, g_hash_table_destroy);
-
-	g_ptr_array_free(message->bodies_order, TRUE);
-	message->bodies_order = NULL;
-	g_clear_pointer(&message->bodies, g_hash_table_destroy);
+	g_clear_object(&message->bodies);
+	g_clear_object(&message->subjects);
 
 	g_clear_pointer(&message->thread, g_free);
 	g_clear_pointer(&message->thread_parent, g_free);
@@ -191,9 +165,15 @@
 	XemeMessage *message = XEME_MESSAGE(obj);
 
 	switch(param_id) {
+	case PROP_BODIES:
+		g_value_set_object(value, xeme_message_get_bodies(message));
+		break;
 	case PROP_MESSAGE_TYPE:
 		g_value_set_enum(value, xeme_message_get_message_type(message));
 		break;
+	case PROP_SUBJECTS:
+		g_value_set_object(value, xeme_message_get_subjects(message));
+		break;
 	case PROP_THREAD:
 		g_value_set_string(value, xeme_message_get_thread(message));
 		break;
@@ -232,13 +212,8 @@
 xeme_message_init(XemeMessage *message) {
 	message->message_type = XEME_MESSAGE_TYPE_NORMAL;
 
-	message->subjects = g_hash_table_new_full(g_str_hash, g_str_equal, g_free,
-	                                          g_free);
-	message->subjects_order = g_ptr_array_new();
-
-	message->bodies = g_hash_table_new_full(g_str_hash, g_str_equal, g_free,
-	                                        g_free);
-	message->bodies_order = g_ptr_array_new();
+	message->bodies = g_list_store_new(XEME_TYPE_LOCALIZED_STRING);
+	message->subjects = g_list_store_new(XEME_TYPE_LOCALIZED_STRING);
 }
 
 static void
@@ -253,6 +228,18 @@
 	stanza_class->marshal = xeme_message_marshal;
 
 	/**
+	 * XemeMessage:bodies:
+	 *
+	 * All of the bodies for this message.
+	 *
+	 * Since: 0.1
+	 */
+	properties[PROP_BODIES] = g_param_spec_object(
+		"bodies", NULL, NULL,
+		G_TYPE_LIST_MODEL,
+		G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+	/**
 	 * XemeMessage:message-type:
 	 *
 	 * The type of the message.
@@ -266,6 +253,18 @@
 		G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
 
 	/**
+	 * XemeMessage:subjects:
+	 *
+	 * All of the subjects for this message.
+	 *
+	 * Since: 0.1
+	 */
+	properties[PROP_SUBJECTS] = g_param_spec_object(
+		"subjects", NULL, NULL,
+		G_TYPE_LIST_MODEL,
+		G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+
+	/**
 	 * XemeMessage:thread:
 	 *
 	 * The thread identifier for this message.
@@ -295,9 +294,120 @@
 /******************************************************************************
  * Public API
  *****************************************************************************/
-XemeStanza *
-xeme_message_new(void) {
-	return g_object_new(XEME_TYPE_MESSAGE, NULL);
+void
+xeme_message_add_body(XemeMessage *message, const char *language,
+                      const char *content)
+{
+	XemeLocalizedString *new_body = NULL;
+	gboolean found = FALSE;
+	guint position = 0;
+
+	g_return_if_fail(XEME_IS_MESSAGE(message));
+	g_return_if_fail(!xeme_str_is_empty(content));
+
+	new_body = xeme_localized_string_new(language, content);
+	found = g_list_store_find_with_equal_func(message->bodies, new_body,
+	                                          xeme_message_localized_string_language_equal,
+	                                          &position);
+
+	/* We found an existing string for the language so we need to remove it. */
+	if(found) {
+		g_list_store_remove(message->bodies, position);
+	}
+
+	/* Add our new body. */
+	g_list_store_append(message->bodies, new_body);
+
+	/* The append adds a reference so we need to clear ours. */
+	g_clear_object(&new_body);
+}
+
+void
+xeme_message_add_subject(XemeMessage *message, const char *language,
+                         const char *content)
+{
+	XemeLocalizedString *new_subject = NULL;
+	gboolean found = FALSE;
+	guint position = 0;
+
+	g_return_if_fail(XEME_IS_MESSAGE(message));
+	g_return_if_fail(!xeme_str_is_empty(content));
+
+	new_subject = xeme_localized_string_new(language, content);
+	found = g_list_store_find_with_equal_func(message->subjects, new_subject,
+	                                          xeme_message_localized_string_language_equal,
+	                                          &position);
+
+	/* We found an existing string for the language so we need to remove it. */
+	if(found) {
+		g_list_store_remove(message->subjects, position);
+	}
+
+	/* Add our new subject. */
+	g_list_store_append(message->subjects, new_subject);
+
+	/* The append adds a reference so we need to clear ours. */
+	g_clear_object(&new_subject);
+}
+
+GListModel *
+xeme_message_get_bodies(XemeMessage *message) {
+	g_return_val_if_fail(XEME_IS_MESSAGE(message), NULL);
+
+	return G_LIST_MODEL(message->bodies);
+}
+
+XemeLocalizedString *
+xeme_message_get_body(XemeMessage *message, const char *language) {
+	GListModel *model = NULL;
+
+	g_return_val_if_fail(XEME_IS_MESSAGE(message), NULL);
+
+	model = G_LIST_MODEL(message->bodies);
+	for(guint i = 0; i < g_list_model_get_n_items(model); i++) {
+		XemeLocalizedString *l10nstr = NULL;
+		const char *language1 = NULL;
+
+		l10nstr = g_list_model_get_item(model, i);
+		language1 = xeme_localized_string_get_language(l10nstr);
+		if(xeme_str_equal(language1, language)) {
+			return l10nstr;
+		}
+
+		g_clear_object(&l10nstr);
+	}
+
+	return NULL;
+}
+
+XemeLocalizedString *
+xeme_message_get_subject(XemeMessage *message, const char *language) {
+	GListModel *model = NULL;
+
+	g_return_val_if_fail(XEME_IS_MESSAGE(message), NULL);
+
+	model = G_LIST_MODEL(message->subjects);
+	for(guint i = 0; i < g_list_model_get_n_items(model); i++) {
+		XemeLocalizedString *l10nstr = NULL;
+		const char *language1 = NULL;
+
+		l10nstr = g_list_model_get_item(model, i);
+		language1 = xeme_localized_string_get_language(l10nstr);
+		if(xeme_str_equal(language1, language)) {
+			return l10nstr;
+		}
+
+		g_clear_object(&l10nstr);
+	}
+
+	return NULL;
+}
+
+GListModel *
+xeme_message_get_subjects(XemeMessage *message) {
+	g_return_val_if_fail(XEME_IS_MESSAGE(message), NULL);
+
+	return G_LIST_MODEL(message->subjects);
 }
 
 XemeMessageType
@@ -327,6 +437,64 @@
 	return message->thread;
 }
 
+const char *
+xeme_message_get_thread_parent(XemeMessage *message) {
+	g_return_val_if_fail(XEME_IS_MESSAGE(message), NULL);
+
+	return message->thread_parent;
+}
+
+XemeStanza *
+xeme_message_new(void) {
+	return g_object_new(XEME_TYPE_MESSAGE, NULL);
+}
+
+gboolean
+xeme_message_remove_body(XemeMessage *message, const char *language) {
+	XemeLocalizedString *needle = NULL;
+	gboolean found = FALSE;
+	guint position = 0;
+
+	g_return_val_if_fail(XEME_IS_MESSAGE(message), FALSE);
+
+	needle = xeme_localized_string_new(language, "needle");
+
+	found = g_list_store_find_with_equal_func(message->bodies, needle,
+	                                          xeme_message_localized_string_language_equal,
+	                                          &position);
+
+	if(found) {
+		g_list_store_remove(message->bodies, position);
+	}
+
+	g_clear_object(&needle);
+
+	return found;
+}
+
+gboolean
+xeme_message_remove_subject(XemeMessage *message, const char *language) {
+	XemeLocalizedString *needle = NULL;
+	gboolean found = FALSE;
+	guint position = 0;
+
+	g_return_val_if_fail(XEME_IS_MESSAGE(message), FALSE);
+
+	needle = xeme_localized_string_new(language, "needle");
+
+	found = g_list_store_find_with_equal_func(message->subjects, needle,
+	                                          xeme_message_localized_string_language_equal,
+	                                          &position);
+
+	if(found) {
+		g_list_store_remove(message->subjects, position);
+	}
+
+	g_clear_object(&needle);
+
+	return found;
+}
+
 void
 xeme_message_set_thread(XemeMessage *message, const char *thread) {
 	g_return_if_fail(XEME_IS_MESSAGE(message));
@@ -336,13 +504,6 @@
 	}
 }
 
-const char *
-xeme_message_get_thread_parent(XemeMessage *message) {
-	g_return_val_if_fail(XEME_IS_MESSAGE(message), NULL);
-
-	return message->thread_parent;
-}
-
 void
 xeme_message_set_thread_parent(XemeMessage *message,
                                const char *thread_parent)
@@ -354,27 +515,3 @@
 		                         properties[PROP_THREAD_PARENT]);
 	}
 }
-
-void
-xeme_message_add_body(XemeMessage *message, const char *language,
-                      const char *content)
-{
-	g_return_if_fail(XEME_IS_MESSAGE(message));
-	g_return_if_fail(content != NULL);
-
-	xeme_message_add_element_with_language(message, message->bodies,
-	                                       message->bodies_order, language,
-	                                       content);
-}
-
-void
-xeme_message_add_subject(XemeMessage *message, const char *language,
-                         const char *content)
-{
-	g_return_if_fail(XEME_IS_MESSAGE(message));
-	g_return_if_fail(content != NULL);
-
-	xeme_message_add_element_with_language(message, message->subjects,
-	                                       message->subjects_order, language,
-	                                       content);
-}
diff --git a/xeme/xememessage.h b/xeme/xememessage.h
--- a/xeme/xememessage.h
+++ b/xeme/xememessage.h
@@ -26,6 +26,9 @@
 #include <glib.h>
 #include <glib-object.h>
 
+#include <gio/gio.h>
+
+#include "xemelocalizedstring.h"
 #include "xemestanza.h"
 #include "xemeversion.h"
 
@@ -70,16 +73,84 @@
 G_DECLARE_FINAL_TYPE(XemeMessage, xeme_message, XEME, MESSAGE, XemeStanza)
 
 /**
- * xeme_message_new:
+ * xeme_message_add_body:
+ * @language: (nullable): the language
+ * @content: the content
+ *
+ * Add a body to the message.
+ *
+ * If a body already exists with @language, it will be replaced with the new
+ * @content.
  *
- * Creates a new [class@Message] instance.
+ * Since: 0.1
+ */
+XEME_AVAILABLE_IN_0_1
+void xeme_message_add_body(XemeMessage *message, const char *language, const char *content);
+
+/**
+ * xeme_message_add_subject:
+ * @language: (nullable): the language
+ * @content: the content
+ *
+ * Add a subject to the message.
  *
- * returns: (transfer full) (type XemeMessage): The new message.
+ * If a subject already exists with @language, it will be replaced with the new
+ * @content.
+ *
+ * Since: 0.1
+ */
+XEME_AVAILABLE_IN_0_1
+void xeme_message_add_subject(XemeMessage *message, const char *language, const char *content);
+
+/**
+ * xeme_message_get_bodies:
+ *
+ * Gets all of the bodies for the message.
+ *
+ * Returns: (transfer none): the bodies.
  *
  * Since: 0.1
  */
 XEME_AVAILABLE_IN_0_1
-XemeStanza *xeme_message_new(void);
+GListModel *xeme_message_get_bodies(XemeMessage *message);
+
+/**
+ * xeme_message_get_body:
+ * @language: (nullable): the language
+ *
+ * Gets the body matching the given language.
+ *
+ * Returns: (transfer full) (nullable): the body, if found.
+ *
+ * Since: 0.1
+ */
+XEME_AVAILABLE_IN_0_1
+XemeLocalizedString *xeme_message_get_body(XemeMessage *message, const char *language);
+
+/**
+ * xeme_message_get_subject:
+ * @language: (nullable): the language
+ *
+ * Gets the subject matching the given language.
+ *
+ * Returns: (transfer full) (nullable): the subject, if found.
+ *
+ * Since: 0.1
+ */
+XEME_AVAILABLE_IN_0_1
+XemeLocalizedString *xeme_message_get_subject(XemeMessage *message, const char *language);
+
+/**
+ * xeme_message_get_subjects:
+ *
+ * Gets all of the subjects for the message.
+ *
+ * Returns: (transfer none): the subjects.
+ *
+ * Since: 0.1
+ */
+XEME_AVAILABLE_IN_0_1
+GListModel *xeme_message_get_subjects(XemeMessage *message);
 
 /**
  * xeme_message_get_message_type:
@@ -118,18 +189,6 @@
 const char *xeme_message_get_thread(XemeMessage *message);
 
 /**
- * xeme_message_set_thread:
- * @message: The instance.
- * @thread: (nullable): The new thread identifier.
- *
- * Sets the thread identifier of @message.
- *
- * Since: 0.1
- */
-XEME_AVAILABLE_IN_0_1
-void xeme_message_set_thread(XemeMessage *message, const char *thread);
-
-/**
  * xeme_message_get_thread_parent:
  * @message: The instance.
  *
@@ -143,6 +202,56 @@
 const char *xeme_message_get_thread_parent(XemeMessage *message);
 
 /**
+ * xeme_message_new:
+ *
+ * Creates a new [class@Message] instance.
+ *
+ * returns: (transfer full) (type XemeMessage): The new message.
+ *
+ * Since: 0.1
+ */
+XEME_AVAILABLE_IN_0_1
+XemeStanza *xeme_message_new(void);
+
+/**
+ * xeme_message_remove_body:
+ * @language: the language
+ *
+ * Removes the body with given language.
+ *
+ * Returns: true if a body was found and removed.
+ *
+ * Since: 0.1
+ */
+XEME_AVAILABLE_IN_0_1
+gboolean xeme_message_remove_body(XemeMessage *message, const char *language);
+
+/**
+ * xeme_message_remove_subject:
+ * @language: the language
+ *
+ * Removes the subject with given language.
+ *
+ * Returns: true if a subject was found and removed.
+ *
+ * Since: 0.1
+ */
+XEME_AVAILABLE_IN_0_1
+gboolean xeme_message_remove_subject(XemeMessage *message, const char *language);
+
+/**
+ * xeme_message_set_thread:
+ * @message: The instance.
+ * @thread: (nullable): The new thread identifier.
+ *
+ * Sets the thread identifier of @message.
+ *
+ * Since: 0.1
+ */
+XEME_AVAILABLE_IN_0_1
+void xeme_message_set_thread(XemeMessage *message, const char *thread);
+
+/**
  * xeme_message_set_thread_parent:
  * @message: The instance.
  * @thread_parent: (nullable): The new thread parent identifier.
@@ -154,34 +263,6 @@
 XEME_AVAILABLE_IN_0_1
 void xeme_message_set_thread_parent(XemeMessage *message, const char *thread_parent);
 
-/**
- * xeme_message_add_subject:
- * @message: The instance.
- * @language: (nullable): The value for the xml:lang tag.
- * @content: The content.
- *
- * Add @content as a subject to @message. If a subject already exists with
- * @language, it will be replaced by the new @content.
- *
- * Since: 0.1
- */
-XEME_AVAILABLE_IN_0_1
-void xeme_message_add_subject(XemeMessage *message, const char *language, const char *content);
-
-/**
- * xeme_message_add_body:
- * @message: The instance.
- * @language: (nullable): The value for the xml:lang tag.
- * @content: The content.
- *
- * Add @content as a body to @message. If a body already exists with @language,
- * it will be replaced by the new @content.
- *
- * Since: 0.1
- */
-XEME_AVAILABLE_IN_0_1
-void xeme_message_add_body(XemeMessage *message, const char *language, const char *content);
-
 G_END_DECLS
 
 #endif /* XEME_MESSAGE_H */
