[libgdata] [core] Move all element object parsing into two functions



commit c18152c862c7311c21293a6840ef8e20a77daa6a
Author: Philip Withnall <philip tecnocode co uk>
Date:   Tue Mar 23 16:40:53 2010 +0000

    [core] Move all element object parsing into two functions

 docs/reference/gdata-sections.txt                |    1 +
 gdata/gd/gdata-gd-organization.c                 |   23 +---
 gdata/gd/gdata-gd-when.c                         |   40 ++++--
 gdata/gd/gdata-gd-when.h                         |    2 +
 gdata/gdata-entry.c                              |   29 +---
 gdata/gdata-feed.c                               |   66 ++++-----
 gdata/gdata-parser.c                             |  161 ++++++++++++++++++++--
 gdata/gdata-parser.h                             |   22 ++-
 gdata/gdata.symbols                              |    1 +
 gdata/media/gdata-media-group.c                  |   53 ++-----
 gdata/media/gdata-media-group.h                  |    2 +
 gdata/services/calendar/gdata-calendar-event.c   |   29 +---
 gdata/services/contacts/gdata-contacts-contact.c |   61 ++-------
 gdata/services/documents/gdata-documents-entry.c |   17 +--
 gdata/services/picasaweb/gdata-picasaweb-album.c |   34 ++----
 gdata/services/picasaweb/gdata-picasaweb-file.c  |   52 ++-----
 gdata/services/youtube/gdata-youtube-control.c   |   16 +--
 gdata/services/youtube/gdata-youtube-group.c     |   11 +-
 gdata/services/youtube/gdata-youtube-video.c     |   31 +----
 gdata/tests/general.c                            |    7 +
 20 files changed, 329 insertions(+), 329 deletions(-)
---
diff --git a/docs/reference/gdata-sections.txt b/docs/reference/gdata-sections.txt
index 2c9810e..a627a7a 100644
--- a/docs/reference/gdata-sections.txt
+++ b/docs/reference/gdata-sections.txt
@@ -804,6 +804,7 @@ gdata_gd_when_set_is_date
 gdata_gd_when_get_value_string
 gdata_gd_when_set_value_string
 gdata_gd_when_get_reminders
+gdata_gd_when_add_reminder
 <SUBSECTION Standard>
 gdata_gd_when_get_type
 GDATA_GD_WHEN
diff --git a/gdata/gd/gdata-gd-organization.c b/gdata/gd/gdata-gd-organization.c
index ebb024f..8fa64e3 100644
--- a/gdata/gd/gdata-gd-organization.c
+++ b/gdata/gd/gdata-gd-organization.c
@@ -391,26 +391,13 @@ parse_xml (GDataParsable *parsable, xmlDoc *doc, xmlNode *node, gpointer user_da
 	    gdata_parser_string_from_element (node, "orgTitle", P_NO_DUPES, &(priv->title), &success, error) == TRUE ||
 	    gdata_parser_string_from_element (node, "orgDepartment", P_NO_DUPES, &(priv->department), &success, error) == TRUE ||
 	    gdata_parser_string_from_element (node, "orgJobDescription", P_NO_DUPES, &(priv->job_description), &success, error) == TRUE ||
-	    gdata_parser_string_from_element (node, "orgSymbol", P_NO_DUPES, &(priv->symbol), &success, error) == TRUE) {
+	    gdata_parser_string_from_element (node, "orgSymbol", P_NO_DUPES, &(priv->symbol), &success, error) == TRUE ||
+	    gdata_parser_object_from_element (node, "where", P_REQUIRED | P_NO_DUPES, GDATA_TYPE_GD_WHERE,
+	                                      &(priv->location), &success, error) == TRUE) {
 		return success;
-	} else if (xmlStrcmp (node->name, (xmlChar*) "where") == 0) {
-		/* gd:where */
-		GDataGDWhere *location = GDATA_GD_WHERE (_gdata_parsable_new_from_xml_node (GDATA_TYPE_GD_WHERE, doc, node, NULL, error));
-		if (location == NULL)
-			return FALSE;
-
-		if (priv->location != NULL) {
-			g_object_unref (location);
-			return gdata_parser_error_duplicate_element (node, error);
-		}
-
-		priv->location = location;
-	} else if (GDATA_PARSABLE_CLASS (gdata_gd_organization_parent_class)->parse_xml (parsable, doc, node, user_data, error) == FALSE) {
-		/* Error! */
-		return FALSE;
+	} else {
+		return GDATA_PARSABLE_CLASS (gdata_gd_organization_parent_class)->parse_xml (parsable, doc, node, user_data, error);
 	}
-
-	return TRUE;
 }
 
 static void
diff --git a/gdata/gd/gdata-gd-when.c b/gdata/gd/gdata-gd-when.c
index ac19cb7..1ca161b 100644
--- a/gdata/gd/gdata-gd-when.c
+++ b/gdata/gd/gdata-gd-when.c
@@ -291,21 +291,14 @@ pre_parse_xml (GDataParsable *parsable, xmlDoc *doc, xmlNode *root_node, gpointe
 static gboolean
 parse_xml (GDataParsable *parsable, xmlDoc *doc, xmlNode *node, gpointer user_data, GError **error)
 {
-	GDataGDWhenPrivate *priv = GDATA_GD_WHEN (parsable)->priv;
-
-	if (xmlStrcmp (node->name, (xmlChar*) "reminder") == 0) {
-		/* gd:reminder */
-		GDataGDReminder *reminder = GDATA_GD_REMINDER (_gdata_parsable_new_from_xml_node (GDATA_TYPE_GD_REMINDER, doc, node, NULL, error));
-		if (reminder == NULL)
-			return FALSE;
+	gboolean success;
 
-		priv->reminders = g_list_prepend (priv->reminders, reminder);
-	} else if (GDATA_PARSABLE_CLASS (gdata_gd_when_parent_class)->parse_xml (parsable, doc, node, user_data, error) == FALSE) {
-		/* Error! */
-		return FALSE;
+	if (gdata_parser_object_from_element_setter (node, "reminder", P_REQUIRED, GDATA_TYPE_GD_REMINDER,
+	                                             gdata_gd_when_add_reminder, parsable, &success, error) == TRUE) {
+		return success;
+	} else {
+		return GDATA_PARSABLE_CLASS (gdata_gd_when_parent_class)->parse_xml (parsable, doc, node, user_data, error);
 	}
-
-	return TRUE;
 }
 
 static gboolean
@@ -592,3 +585,24 @@ gdata_gd_when_get_reminders (GDataGDWhen *self)
 	g_return_val_if_fail (GDATA_IS_GD_WHEN (self), NULL);
 	return self->priv->reminders;
 }
+
+/**
+ * gdata_gd_when_add_reminder:
+ * @self: a #GDataGDWhen
+ * @reminder: a #GDataGDReminder to add
+ *
+ * Adds a reminder to the #GDataGDWhen's list of reminders and increments its reference count.
+ *
+ * Duplicate reminders will not be added to the list.
+ *
+ * Since: 0.7.0
+ **/
+void
+gdata_gd_when_add_reminder (GDataGDWhen *self, GDataGDReminder *reminder)
+{
+	g_return_if_fail (GDATA_IS_GD_WHEN (self));
+	g_return_if_fail (GDATA_IS_GD_REMINDER (reminder));
+
+	if (g_list_find_custom (self->priv->reminders, reminder, (GCompareFunc) gdata_gd_reminder_compare) == NULL)
+		self->priv->reminders = g_list_append (self->priv->reminders, g_object_ref (reminder));
+}
diff --git a/gdata/gd/gdata-gd-when.h b/gdata/gd/gdata-gd-when.h
index cf6f143..2f2e1f4 100644
--- a/gdata/gd/gdata-gd-when.h
+++ b/gdata/gd/gdata-gd-when.h
@@ -24,6 +24,7 @@
 #include <glib-object.h>
 
 #include <gdata/gdata-parsable.h>
+#include <gdata/gd/gdata-gd-reminder.h>
 
 G_BEGIN_DECLS
 
@@ -78,6 +79,7 @@ const gchar *gdata_gd_when_get_value_string (GDataGDWhen *self);
 void gdata_gd_when_set_value_string (GDataGDWhen *self, const gchar *value_string);
 
 GList *gdata_gd_when_get_reminders (GDataGDWhen *self);
+void gdata_gd_when_add_reminder (GDataGDWhen *self, GDataGDReminder *reminder);
 /* TODO: More reminder API */
 
 G_END_DECLS
diff --git a/gdata/gdata-entry.c b/gdata/gdata-entry.c
index 4f0e4f1..98ec252 100644
--- a/gdata/gdata-entry.c
+++ b/gdata/gdata-entry.c
@@ -372,6 +372,7 @@ pre_parse_xml (GDataParsable *parsable, xmlDoc *doc, xmlNode *root_node, gpointe
 static gboolean
 parse_xml (GDataParsable *parsable, xmlDoc *doc, xmlNode *node, gpointer user_data, GError **error)
 {
+	gboolean success;
 	GDataEntry *self;
 
 	g_return_val_if_fail (GDATA_IS_ENTRY (parsable), FALSE);
@@ -414,33 +415,19 @@ parse_xml (GDataParsable *parsable, xmlDoc *doc, xmlNode *node, gpointer user_da
 			return FALSE;
 		}
 		xmlFree (published);
-	} else if (xmlStrcmp (node->name, (xmlChar*) "category") == 0) {
-		/* atom:category */
-		GDataCategory *category = GDATA_CATEGORY (_gdata_parsable_new_from_xml_node (GDATA_TYPE_CATEGORY, doc, node, NULL, error));
-		if (category == NULL)
-			return FALSE;
-
-		self->priv->categories = g_list_prepend (self->priv->categories, category);
+	} else if (gdata_parser_object_from_element_setter (node, "category", P_REQUIRED, GDATA_TYPE_CATEGORY,
+	                                                    gdata_entry_add_category, self, &success, error) == TRUE ||
+	           gdata_parser_object_from_element_setter (node, "link", P_REQUIRED, GDATA_TYPE_LINK,
+	                                                    gdata_entry_add_link, self, &success, error) == TRUE ||
+	           gdata_parser_object_from_element_setter (node, "author", P_REQUIRED, GDATA_TYPE_AUTHOR,
+	                                                    gdata_entry_add_author, self, &success, error) == TRUE) {
+		return success;
 	} else if (xmlStrcmp (node->name, (xmlChar*) "content") == 0) {
 		/* atom:content */
 		xmlChar *content = xmlNodeListGetString (doc, node->children, TRUE);
 		if (content == NULL)
 			content = xmlGetProp (node, (xmlChar*) "src");
 		self->priv->content = (gchar*) content;
-	} else if (xmlStrcmp (node->name, (xmlChar*) "link") == 0) {
-		/* atom:link */
-		GDataLink *link = GDATA_LINK (_gdata_parsable_new_from_xml_node (GDATA_TYPE_LINK, doc, node, NULL, error));
-		if (link == NULL)
-			return FALSE;
-
-		self->priv->links = g_list_prepend (self->priv->links, link);
-	} else if (xmlStrcmp (node->name, (xmlChar*) "author") == 0) {
-		/* atom:author */
-		GDataAuthor *author = GDATA_AUTHOR (_gdata_parsable_new_from_xml_node (GDATA_TYPE_AUTHOR, doc, node, NULL, error));
-		if (author == NULL)
-			return FALSE;
-
-		self->priv->authors = g_list_prepend (self->priv->authors, author);
 	} else if (xmlStrcmp (node->name, (xmlChar*) "summary") == 0) {
 		/* atom:summary */
 		self->priv->summary = (gchar*) xmlNodeListGetString (doc, node->children, TRUE);
diff --git a/gdata/gdata-feed.c b/gdata/gdata-feed.c
index a071cee..e0740c1 100644
--- a/gdata/gdata-feed.c
+++ b/gdata/gdata-feed.c
@@ -51,6 +51,10 @@ static gboolean pre_parse_xml (GDataParsable *parsable, xmlDoc *doc, xmlNode *ro
 static gboolean parse_xml (GDataParsable *parsable, xmlDoc *doc, xmlNode *node, gpointer user_data, GError **error);
 static gboolean post_parse_xml (GDataParsable *parsable, gpointer user_data, GError **error);
 
+static void _gdata_feed_add_category (GDataFeed *self, GDataCategory *category);
+static void _gdata_feed_add_link (GDataFeed *self, GDataLink *link);
+static void _gdata_feed_add_author (GDataFeed *self, GDataAuthor *author);
+
 struct _GDataFeedPrivate {
 	GList *entries;
 	gchar *title;
@@ -428,7 +432,15 @@ parse_xml (GDataParsable *parsable, xmlDoc *doc, xmlNode *node, gpointer user_da
 	           gdata_parser_string_from_element (node, "subtitle", P_NO_DUPES, &(self->priv->subtitle), &success, error) == TRUE ||
 	           gdata_parser_string_from_element (node, "id", P_NO_DUPES, &(self->priv->id), &success, error) == TRUE ||
 	           gdata_parser_string_from_element (node, "logo", P_NO_DUPES, &(self->priv->logo), &success, error) == TRUE ||
-	           gdata_parser_string_from_element (node, "icon", P_NO_DUPES, &(self->priv->icon), &success, error) == TRUE) {
+	           gdata_parser_string_from_element (node, "icon", P_NO_DUPES, &(self->priv->icon), &success, error) == TRUE ||
+	           gdata_parser_object_from_element_setter (node, "category", P_REQUIRED, GDATA_TYPE_CATEGORY,
+	                                                    _gdata_feed_add_category, self, &success, error) == TRUE ||
+	           gdata_parser_object_from_element_setter (node, "link", P_REQUIRED, GDATA_TYPE_LINK,
+	                                                    _gdata_feed_add_link, self, &success, error) == TRUE ||
+	           gdata_parser_object_from_element_setter (node, "author", P_REQUIRED, GDATA_TYPE_AUTHOR,
+	                                                    _gdata_feed_add_author, self, &success, error) == TRUE ||
+	           gdata_parser_object_from_element (node, "generator", P_REQUIRED | P_NO_DUPES, GDATA_TYPE_GENERATOR,
+	                                             &(self->priv->generator), &success, error) == TRUE) {
 		return success;
 	/*TODO for atom:id: xmlStrcmp (node->ns->href, (xmlChar*) "http://www.w3.org/2005/Atom";) == 0) {*/
 	} else if (xmlStrcmp (node->name, (xmlChar*) "updated") == 0) {
@@ -447,40 +459,6 @@ parse_xml (GDataParsable *parsable, xmlDoc *doc, xmlNode *node, gpointer user_da
 			return FALSE;
 		}
 		xmlFree (updated_string);
-	} else if (xmlStrcmp (node->name, (xmlChar*) "category") == 0) {
-		/* atom:category */
-		GDataCategory *category = GDATA_CATEGORY (_gdata_parsable_new_from_xml_node (GDATA_TYPE_CATEGORY, doc, node, NULL, error));
-		if (category == NULL)
-			return FALSE;
-
-		self->priv->categories = g_list_prepend (self->priv->categories, category);
-	} else if (xmlStrcmp (node->name, (xmlChar*) "link") == 0) {
-		/* atom:link */
-		GDataLink *link = GDATA_LINK (_gdata_parsable_new_from_xml_node (GDATA_TYPE_LINK, doc, node, NULL, error));
-		if (link == NULL)
-			return FALSE;
-
-		self->priv->links = g_list_prepend (self->priv->links, link);
-	} else if (xmlStrcmp (node->name, (xmlChar*) "author") == 0) {
-		/* atom:author */
-		GDataAuthor *author = GDATA_AUTHOR (_gdata_parsable_new_from_xml_node (GDATA_TYPE_AUTHOR, doc, node, NULL, error));
-		if (author == NULL)
-			return FALSE;
-
-		self->priv->authors = g_list_prepend (self->priv->authors, author);
-	} else if (xmlStrcmp (node->name, (xmlChar*) "generator") == 0) {
-		/* atom:generator */
-		GDataGenerator *generator;
-
-		/* Duplicate checking */
-		if (self->priv->generator != NULL)
-			return gdata_parser_error_duplicate_element (node, error);
-
-		generator = GDATA_GENERATOR (_gdata_parsable_new_from_xml_node (GDATA_TYPE_GENERATOR, doc, node, NULL, error));
-		if (generator == NULL)
-			return FALSE;
-
-		self->priv->generator = generator;
 	} else if (xmlStrcmp (node->name, (xmlChar*) "totalResults") == 0) {
 		/* openSearch:totalResults */
 		xmlChar *total_results_string;
@@ -636,6 +614,12 @@ gdata_feed_get_categories (GDataFeed *self)
 	return self->priv->categories;
 }
 
+static void
+_gdata_feed_add_category (GDataFeed *self, GDataCategory *category)
+{
+	self->priv->categories = g_list_prepend (self->priv->categories, g_object_ref (category));
+}
+
 /**
  * gdata_feed_get_links:
  * @self: a #GDataFeed
@@ -682,6 +666,12 @@ gdata_feed_look_up_link (GDataFeed *self, const gchar *rel)
 	return GDATA_LINK (element->data);
 }
 
+static void
+_gdata_feed_add_link (GDataFeed *self, GDataLink *link)
+{
+		self->priv->links = g_list_prepend (self->priv->links, g_object_ref (link));
+}
+
 /**
  * gdata_feed_get_authors:
  * @self: a #GDataFeed
@@ -697,6 +687,12 @@ gdata_feed_get_authors (GDataFeed *self)
 	return self->priv->authors;
 }
 
+static void
+_gdata_feed_add_author (GDataFeed *self, GDataAuthor *author)
+{
+		self->priv->authors = g_list_prepend (self->priv->authors, g_object_ref (author));
+}
+
 /**
  * gdata_feed_get_title:
  * @self: a #GDataFeed
diff --git a/gdata/gdata-parser.c b/gdata/gdata-parser.c
index 6536285..039d44e 100644
--- a/gdata/gdata-parser.c
+++ b/gdata/gdata-parser.c
@@ -26,6 +26,7 @@
 
 #include "gdata-parser.h"
 #include "gdata-service.h"
+#include "gdata-private.h"
 
 static gchar *
 print_element (xmlNode *node)
@@ -73,8 +74,8 @@ gdata_parser_error_not_iso8601_format (xmlNode *element, const gchar *actual_val
 {
 	gchar *element_string = print_element (element);
 	g_set_error (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_PROTOCOL_ERROR,
-		     /* Translators: the first parameter is the name of an XML element (including the angle brackets ("<" and ">")), and the second parameter is
-		      * the erroneous value (which was not in ISO 8601 format).
+		     /* Translators: the first parameter is the name of an XML element (including the angle brackets ("<" and ">")),
+		      * and the second parameter is the erroneous value (which was not in ISO 8601 format).
 		      *
 		      * For example:
 		      *  The content of a <media:group/media:uploaded> element ("2009-05-06 26:30Z") was not in ISO 8601 format. */
@@ -253,7 +254,7 @@ gdata_parser_boolean_from_property (xmlNode *element, const gchar *property_name
  * gdata_parser_string_from_element:
  * @element: the element to check against
  * @element_name: the name of the element to parse
- * @options: a bitwise combination of parsing options from #GDataParserStringOptions, or %P_NONE
+ * @options: a bitwise combination of parsing options from #GDataParserOptions, or %P_NONE
  * @output: the return location for the parsed string content
  * @success: the return location for a value which is %TRUE if the string was parsed successfully, %FALSE if an error was encountered,
  * and undefined if @element didn't match @element_name
@@ -278,22 +279,19 @@ gdata_parser_boolean_from_property (xmlNode *element, const gchar *property_name
  * Since: 0.7.0
  */
 gboolean
-gdata_parser_string_from_element (xmlNode *element, const gchar *element_name, GDataParserStringOptions options,
+gdata_parser_string_from_element (xmlNode *element, const gchar *element_name, GDataParserOptions options,
                                   gchar **output, gboolean *success, GError **error)
 {
 	xmlChar *text;
 
+	/* Check it's the right element */
 	if (xmlStrcmp (element->name, (xmlChar*) element_name) != 0)
 		return FALSE;
 
 	/* Check if the output string has already been set */
-	if (*output != NULL) {
-		if (options & P_NO_DUPES) {
-			*success = gdata_parser_error_duplicate_element (element, error);
-			return TRUE;
-		} else {
-			g_free (*output);
-		}
+	if (options & P_NO_DUPES && *output != NULL) {
+		*success = gdata_parser_error_duplicate_element (element, error);
+		return TRUE;
 	}
 
 	/* Get the string and check it for NULLness or emptiness */
@@ -304,12 +302,153 @@ gdata_parser_string_from_element (xmlNode *element, const gchar *element_name, G
 		return TRUE;
 	}
 
+	/* Success! */
+	g_free (*output);
 	*output = (gchar*) text;
 	*success = TRUE;
 
 	return TRUE;
 }
 
+/*
+ * gdata_parser_object_from_element_setter:
+ * @element: the element to check against
+ * @element_name: the name of the element to parse
+ * @options: a bitwise combination of parsing options from #GDataParserOptions, or %P_NONE
+ * @object_type: the type of the object to parse
+ * @_setter: a function to call once parsing's finished to return the object (#GDataParserSetterFunc)
+ * @_parent_parsable: the first parameter to pass to @_setter (of type #GDataParsable)
+ * @success: the return location for a value which is %TRUE if the object was parsed successfully, %FALSE if an error was encountered,
+ * and undefined if @element didn't match @element_name
+ * @error: a #GError, or %NULL
+ *
+ * Gets the object content of @element if its name is @element_name, subject to various checks specified by @options. If @element matches @element_name,
+ * @element will be parsed as an @object_type, which must extend #GDataParsable. If parsing is successful, @_setter will be called with its first
+ * parameter as @_parent_parsable, and its second as the parsed object of type @object_type. @_setter must reference the parsed object it's passed if
+ * it wants to keep it, as gdata_parser_object_from_element_setter will unreference it before returning.
+ *
+ * If @element doesn't match @element_name, %FALSE will be returned, @error will be unset and @success will be unset.
+ *
+ * If @element matches @element_name but one of the checks specified by @options fails, %TRUE will be returned, @error will be set to a
+ * %GDATA_SERVICE_ERROR_PROTOCOL_ERROR error and @success will be set to %FALSE.
+ *
+ * If @element matches @element_name and all of the checks specified by @options pass, %TRUE will be returned, @error will be unset and
+ * @success will be set to %TRUE.
+ *
+ * The reason for returning the success of the parsing in @success is so that calls to gdata_parser_object_from_element_setter() can be chained
+ * together in a large "or" statement based on their return values, for the purposes of determining whether any of the calls matched
+ * a given @element. If any of the calls to gdata_parser_object_from_element_setter() return %TRUE, the value of @success can be examined.
+ *
+ * @_setter and @_parent_parsable are both #gpointer<!-- -->s to avoid casts having to be put into calls to gdata_parser_object_from_element_setter().
+ * @_setter is actually of type #GDataParserSetterFunc, and @_parent_parsable should be a subclass of #GDataParsable. Neither parameter should be %NULL.
+ * No checks are implemented against these conditions (for efficiency reasons), so calling code must be correct.
+ *
+ * Return value: %TRUE if @element matched @element_name, %FALSE otherwise
+ *
+ * Since: 0.7.0
+ */
+gboolean
+gdata_parser_object_from_element_setter (xmlNode *element, const gchar *element_name, GDataParserOptions options, GType object_type,
+                                         gpointer /* GDataParserSetterFunc */ _setter, gpointer /* GDataParsable * */ _parent_parsable,
+                                         gboolean *success, GError **error)
+{
+	GDataParsable *parsable, *parent_parsable;
+	GDataParserSetterFunc setter;
+
+	/* We're lax on the types so that we don't have to do loads of casting when calling the function, which makes the parsing code more legible */
+	setter = (GDataParserSetterFunc) _setter;
+	parent_parsable = (GDataParsable*) _parent_parsable;
+
+	/* Check it's the right element */
+	if (xmlStrcmp (element->name, (xmlChar*) element_name) != 0)
+		return FALSE;
+
+	/* Get the object and check for instantiation failure */
+	parsable = _gdata_parsable_new_from_xml_node (object_type, element->doc, element, NULL, error);
+	if (options & P_REQUIRED && parsable == NULL) {
+		/* The error has already been set by _gdata_parsable_new_from_xml_node() */
+		*success = FALSE;
+		return TRUE;
+	}
+
+	/* Success! */
+	setter (parent_parsable, parsable);
+	g_object_unref (parsable);
+	*success = TRUE;
+
+	return TRUE;
+}
+
+/*
+ * gdata_parser_object_from_element:
+ * @element: the element to check against
+ * @element_name: the name of the element to parse
+ * @options: a bitwise combination of parsing options from #GDataParserOptions, or %P_NONE
+ * @object_type: the type of the object to parse
+ * @_output: the return location for the parsed object (of type #GDataParsable)
+ * @success: the return location for a value which is %TRUE if the object was parsed successfully, %FALSE if an error was encountered,
+ * and undefined if @element didn't match @element_name
+ * @error: a #GError, or %NULL
+ *
+ * Gets the object content of @element if its name is @element_name, subject to various checks specified by @options. If @element matches @element_name,
+ * @element will be parsed as an @object_type, which must extend #GDataParsable. If parsing is successful, the parsed object will be returned in
+ * @_output, which must be of type #GDataParsable (or a subclass). Ownership of the parsed object will pass to the calling code.
+ *
+ * If @element doesn't match @element_name, %FALSE will be returned, @error will be unset and @success will be unset.
+ *
+ * If @element matches @element_name but one of the checks specified by @options fails, %TRUE will be returned, @error will be set to a
+ * %GDATA_SERVICE_ERROR_PROTOCOL_ERROR error and @success will be set to %FALSE.
+ *
+ * If @element matches @element_name and all of the checks specified by @options pass, %TRUE will be returned, @error will be unset and
+ * @success will be set to %TRUE.
+ *
+ * The reason for returning the success of the parsing in @success is so that calls to gdata_parser_object_from_element_setter() can be chained
+ * together in a large "or" statement based on their return values, for the purposes of determining whether any of the calls matched
+ * a given @element. If any of the calls to gdata_parser_object_from_element_setter() return %TRUE, the value of @success can be examined.
+ *
+ * @_object is a #gpointer to avoid casts having to be put into calls to gdata_parser_object_from_element(). It is actually of type #GDataParsable
+ * and must not be %NULL. No check is implemented against this condition (for efficiency reasons), so calling code must be correct.
+ *
+ * Return value: %TRUE if @element matched @element_name, %FALSE otherwise
+ *
+ * Since: 0.7.0
+ */
+gboolean
+gdata_parser_object_from_element (xmlNode *element, const gchar *element_name, GDataParserOptions options, GType object_type,
+                                  gpointer /* GDataParsable ** */ _output, gboolean *success, GError **error)
+{
+	GDataParsable *parsable, **output;
+
+	/* We're lax on the types so that we don't have to do loads of casting when calling the function, which makes the parsing code more legible */
+	output = (GDataParsable**) _output;
+
+	/* Check it's the right element */
+	if (xmlStrcmp (element->name, (xmlChar*) element_name) != 0)
+		return FALSE;
+
+	/* If we're not using a setter, check if the output already exists */
+	if (options & P_NO_DUPES && *output != NULL) {
+		*success = gdata_parser_error_duplicate_element (element, error);
+		return TRUE;
+	}
+
+	/* Get the object and check for instantiation failure */
+	parsable = _gdata_parsable_new_from_xml_node (object_type, element->doc, element, NULL, error);
+	if (options & P_REQUIRED && parsable == NULL) {
+		/* The error has already been set by _gdata_parsable_new_from_xml_node() */
+		*success = FALSE;
+		return TRUE;
+	}
+
+	/* Success! */
+	if (*output != NULL)
+		g_object_unref (*output);
+	*output = parsable;
+	*success = TRUE;
+
+	return TRUE;
+}
+
 void
 gdata_parser_string_append_escaped (GString *xml_string, const gchar *pre, const gchar *element_content, const gchar *post)
 {
diff --git a/gdata/gdata-parser.h b/gdata/gdata-parser.h
index 4147e23..c8d9692 100644
--- a/gdata/gdata-parser.h
+++ b/gdata/gdata-parser.h
@@ -19,6 +19,8 @@
 
 #include <glib.h>
 
+#include "gdata-parsable.h"
+
 #ifndef GDATA_PARSER_H
 #define GDATA_PARSER_H
 
@@ -36,14 +38,15 @@ gboolean gdata_parser_time_val_from_date (const gchar *date, GTimeVal *_time);
 gchar *gdata_parser_date_from_time_val (const GTimeVal *_time) G_GNUC_WARN_UNUSED_RESULT;
 
 /*
- * GDataParserStringOptions:
+ * GDataParserOptions:
  * @P_NONE: no special options; the content of the element will be outputted directly without any checks
  * @P_NO_DUPES: the element must be encountered at most once; if encountered more than once, an error will be returned
  * @P_REQUIRED: the element content must not be %NULL if the element exists
- * @P_NON_EMPTY: the element content must not be empty (i.e. a zero-length string) if the element exists
+ * @P_NON_EMPTY: the element content must not be empty (i.e. a zero-length string) if the element exists;
+ * this only applies to gdata_parser_string_from_element()
  *
- * Parsing options to be passed in a bitwise fashion to gdata_parser_string_from_element(). Their names aren't namespaced as they
- * aren't public, and brevity is important, since they're used frequently in the parsing code.
+ * Parsing options to be passed in a bitwise fashion to gdata_parser_string_from_element() or gdata_parser_object_from_element().
+ * Their names aren't namespaced as they aren't public, and brevity is important, since they're used frequently in the parsing code.
  *
  * Since: 0.7.0
  */
@@ -52,11 +55,18 @@ typedef enum {
 	P_NO_DUPES = 1 << 0,
 	P_REQUIRED = 1 << 1,
 	P_NON_EMPTY = 1 << 2
-} GDataParserStringOptions;
+} GDataParserOptions;
+
+typedef void (*GDataParserSetterFunc) (GDataParsable *parent_parsable, GDataParsable *parsable);
 
 gboolean gdata_parser_boolean_from_property (xmlNode *element, const gchar *property_name, gboolean *output, gint default_output, GError **error);
-gboolean gdata_parser_string_from_element (xmlNode *element, const gchar *element_name, GDataParserStringOptions options,
+gboolean gdata_parser_string_from_element (xmlNode *element, const gchar *element_name, GDataParserOptions options,
                                            gchar **output, gboolean *success, GError **error);
+gboolean gdata_parser_object_from_element_setter (xmlNode *element, const gchar *element_name, GDataParserOptions options, GType object_type,
+                                                  gpointer /* GDataParserSetterFunc */ _setter, gpointer /* GDataParsable * */ _parent_parsable,
+                                                  gboolean *success, GError **error);
+gboolean gdata_parser_object_from_element (xmlNode *element, const gchar *element_name, GDataParserOptions options, GType object_type,
+                                           gpointer /* GDataParsable ** */ _output, gboolean *success, GError **error);
 
 void gdata_parser_string_append_escaped (GString *xml_string, const gchar *pre, const gchar *element_content, const gchar *post);
 gchar *gdata_parser_utf8_trim_whitespace (const gchar *s) G_GNUC_WARN_UNUSED_RESULT;
diff --git a/gdata/gdata.symbols b/gdata/gdata.symbols
index 2d1a764..ec7cda6 100644
--- a/gdata/gdata.symbols
+++ b/gdata/gdata.symbols
@@ -393,6 +393,7 @@ gdata_gd_when_set_is_date
 gdata_gd_when_get_value_string
 gdata_gd_when_set_value_string
 gdata_gd_when_get_reminders
+gdata_gd_when_add_reminder
 gdata_gd_who_get_type
 gdata_gd_who_new
 gdata_gd_who_compare
diff --git a/gdata/media/gdata-media-group.c b/gdata/media/gdata-media-group.c
index 346c996..065154b 100644
--- a/gdata/media/gdata-media-group.c
+++ b/gdata/media/gdata-media-group.c
@@ -143,35 +143,16 @@ parse_xml (GDataParsable *parsable, xmlDoc *doc, xmlNode *node, gpointer user_da
 
 	if (gdata_parser_string_from_element (node, "title", P_NONE, &(self->priv->title), &success, error) == TRUE ||
 	    gdata_parser_string_from_element (node, "description", P_NONE, &(self->priv->description), &success, error) == TRUE ||
-	    gdata_parser_string_from_element (node, "keywords", P_NONE, &(self->priv->keywords), &success, error) == TRUE) {
+	    gdata_parser_string_from_element (node, "keywords", P_NONE, &(self->priv->keywords), &success, error) == TRUE ||
+	    gdata_parser_object_from_element_setter (node, "category", P_REQUIRED, GDATA_TYPE_MEDIA_CATEGORY,
+	                                             gdata_media_group_set_category, self, &success, error) == TRUE ||
+	    gdata_parser_object_from_element_setter (node, "content", P_REQUIRED, GDATA_TYPE_MEDIA_CONTENT,
+	                                             _gdata_media_group_add_content, self, &success, error) == TRUE ||
+	    gdata_parser_object_from_element_setter (node, "thumbnail", P_REQUIRED, GDATA_TYPE_MEDIA_THUMBNAIL,
+	                                             _gdata_media_group_add_thumbnail, self, &success, error) == TRUE ||
+	    gdata_parser_object_from_element (node, "credit", P_REQUIRED | P_NO_DUPES, GDATA_TYPE_MEDIA_CREDIT,
+	                                      &(self->priv->credit), &success, error) == TRUE) {
 		return success;
-	} else if (xmlStrcmp (node->name, (xmlChar*) "category") == 0) {
-		/* media:category */
-		GDataMediaCategory *category = GDATA_MEDIA_CATEGORY (_gdata_parsable_new_from_xml_node (GDATA_TYPE_MEDIA_CATEGORY, doc,
-													node, NULL, error));
-		if (category == NULL)
-			return FALSE;
-
-		gdata_media_group_set_category (self, category);
-	} else if (xmlStrcmp (node->name, (xmlChar*) "content") == 0) {
-		/* media:content */
-		GDataMediaContent *content = GDATA_MEDIA_CONTENT (_gdata_parsable_new_from_xml_node (GDATA_TYPE_MEDIA_CONTENT, doc, node, NULL, error));
-		if (content == NULL)
-			return FALSE;
-
-		_gdata_media_group_add_content (self, content);
-	} else if (xmlStrcmp (node->name, (xmlChar*) "credit") == 0) {
-		/* media:credit */
-		GDataMediaCredit *credit = GDATA_MEDIA_CREDIT (_gdata_parsable_new_from_xml_node (GDATA_TYPE_MEDIA_CREDIT, doc, node, NULL, error));
-		if (credit == NULL)
-			return FALSE;
-
-		if (self->priv->credit != NULL) {
-			g_object_unref (credit);
-			return gdata_parser_error_duplicate_element (node, error);
-		}
-
-		_gdata_media_group_set_credit (self, credit);
 	} else if (xmlStrcmp (node->name, (xmlChar*) "player") == 0) {
 		/* media:player */
 		xmlChar *player_uri = xmlGetProp (node, (xmlChar*) "url");
@@ -229,14 +210,6 @@ parse_xml (GDataParsable *parsable, xmlDoc *doc, xmlNode *node, gpointer user_da
 		for (country = country_list; *country != NULL; country++)
 			g_hash_table_insert (self->priv->restricted_countries, *country, GUINT_TO_POINTER (relationship_bool));
 		g_free (country_list);
-	} else if (xmlStrcmp (node->name, (xmlChar*) "thumbnail") == 0) {
-		/* media:thumbnail */
-		GDataMediaThumbnail *thumb = GDATA_MEDIA_THUMBNAIL (_gdata_parsable_new_from_xml_node (GDATA_TYPE_MEDIA_THUMBNAIL, doc,
-												       node, NULL, error));
-		if (thumb == NULL)
-			return FALSE;
-
-		self->priv->thumbnails = g_list_prepend (self->priv->thumbnails, thumb);
 	} else if (GDATA_PARSABLE_CLASS (gdata_media_group_parent_class)->parse_xml (parsable, doc, node, user_data, error) == FALSE) {
 		/* Error! */
 		return FALSE;
@@ -460,7 +433,7 @@ gdata_media_group_get_contents (GDataMediaGroup *self)
 void
 _gdata_media_group_add_content (GDataMediaGroup *self, GDataMediaContent *content)
 {
-	self->priv->contents = g_list_prepend (self->priv->contents, content);
+	self->priv->contents = g_list_prepend (self->priv->contents, g_object_ref (content));
 }
 
 /**
@@ -535,3 +508,9 @@ gdata_media_group_get_thumbnails (GDataMediaGroup *self)
 	g_return_val_if_fail (GDATA_IS_MEDIA_GROUP (self), NULL);
 	return self->priv->thumbnails;
 }
+
+void
+_gdata_media_group_add_thumbnail (GDataMediaGroup *self, GDataMediaThumbnail *thumbnail)
+{
+	self->priv->thumbnails = g_list_prepend (self->priv->thumbnails, g_object_ref (thumbnail));
+}
diff --git a/gdata/media/gdata-media-group.h b/gdata/media/gdata-media-group.h
index ab54970..c6e69bc 100644
--- a/gdata/media/gdata-media-group.h
+++ b/gdata/media/gdata-media-group.h
@@ -27,6 +27,7 @@
 #include <gdata/media/gdata-media-category.h>
 #include <gdata/media/gdata-media-content.h>
 #include <gdata/media/gdata-media-credit.h>
+#include <gdata/media/gdata-media-thumbnail.h>
 
 G_BEGIN_DECLS
 
@@ -79,6 +80,7 @@ void _gdata_media_group_set_credit (GDataMediaGroup *self, GDataMediaCredit *cre
 const gchar *gdata_media_group_get_player_uri (GDataMediaGroup *self);
 gboolean gdata_media_group_is_restricted_in_country (GDataMediaGroup *self, const gchar *country);
 GList *gdata_media_group_get_thumbnails (GDataMediaGroup *self);
+void _gdata_media_group_add_thumbnail (GDataMediaGroup *self, GDataMediaThumbnail *thumbnail);
 
 G_END_DECLS
 
diff --git a/gdata/services/calendar/gdata-calendar-event.c b/gdata/services/calendar/gdata-calendar-event.c
index b1ca713..4c38582 100644
--- a/gdata/services/calendar/gdata-calendar-event.c
+++ b/gdata/services/calendar/gdata-calendar-event.c
@@ -447,6 +447,7 @@ gdata_calendar_event_new (const gchar *id)
 static gboolean
 parse_xml (GDataParsable *parsable, xmlDoc *doc, xmlNode *node, gpointer user_data, GError **error)
 {
+	gboolean success;
 	GDataCalendarEvent *self;
 
 	g_return_val_if_fail (GDATA_IS_CALENDAR_EVENT (parsable), FALSE);
@@ -528,13 +529,13 @@ parse_xml (GDataParsable *parsable, xmlDoc *doc, xmlNode *node, gpointer user_da
 		xmlFree (value);
 
 		gdata_calendar_event_set_sequence (self, value_uint);
-	} else if (xmlStrcmp (node->name, (xmlChar*) "when") == 0) {
-		/* gd:when */
-		GDataGDWhen *when = GDATA_GD_WHEN (_gdata_parsable_new_from_xml_node (GDATA_TYPE_GD_WHEN, doc, node, NULL, error));
-		if (when == NULL)
-			return FALSE;
-
-		gdata_calendar_event_add_time (self, when);
+	} else if (gdata_parser_object_from_element_setter (node, "when", P_REQUIRED, GDATA_TYPE_GD_WHEN,
+	                                                    gdata_calendar_event_add_time, self, &success, error) == TRUE ||
+	           gdata_parser_object_from_element_setter (node, "who", P_REQUIRED, GDATA_TYPE_GD_WHO,
+	                                                    gdata_calendar_event_add_person, self, &success, error) == TRUE ||
+	           gdata_parser_object_from_element_setter (node, "where", P_REQUIRED, GDATA_TYPE_GD_WHERE,
+	                                                    gdata_calendar_event_add_place, self, &success, error) == TRUE) {
+		return success;
 	} else if (xmlStrcmp (node->name, (xmlChar*) "guestsCanModify") == 0) {
 		/* gCal:guestsCanModify */
 		gboolean guests_can_modify;
@@ -559,20 +560,6 @@ parse_xml (GDataParsable *parsable, xmlDoc *doc, xmlNode *node, gpointer user_da
 		if (gdata_parser_boolean_from_property (node, "value", &anyone_can_add_self, -1, error) == FALSE)
 			return FALSE;
 		gdata_calendar_event_set_anyone_can_add_self (self, anyone_can_add_self);
-	} else if (xmlStrcmp (node->name, (xmlChar*) "who") == 0) {
-		/* gd:who */
-		GDataGDWho *who = GDATA_GD_WHO (_gdata_parsable_new_from_xml_node (GDATA_TYPE_GD_WHO, doc, node, NULL, error));
-		if (who == NULL)
-			return FALSE;
-
-		gdata_calendar_event_add_person (self, who);
-	} else if (xmlStrcmp (node->name, (xmlChar*) "where") == 0) {
-		/* gd:where */
-		GDataGDWhere *where = GDATA_GD_WHERE (_gdata_parsable_new_from_xml_node (GDATA_TYPE_GD_WHERE, doc, node, NULL, error));
-		if (where == NULL)
-			return FALSE;
-
-		gdata_calendar_event_add_place (self, where);
 	} else if (xmlStrcmp (node->name, (xmlChar*) "recurrence") == 0) {
 		/* gd:recurrence */
 		self->priv->recurrence = (gchar*) xmlNodeListGetString (doc, node->children, TRUE);
diff --git a/gdata/services/contacts/gdata-contacts-contact.c b/gdata/services/contacts/gdata-contacts-contact.c
index 207db82..7e71a98 100644
--- a/gdata/services/contacts/gdata-contacts-contact.c
+++ b/gdata/services/contacts/gdata-contacts-contact.c
@@ -290,6 +290,7 @@ gdata_contacts_contact_new (const gchar *id)
 static gboolean
 parse_xml (GDataParsable *parsable, xmlDoc *doc, xmlNode *node, gpointer user_data, GError **error)
 {
+	gboolean success;
 	GDataContactsContact *self;
 
 	g_return_val_if_fail (GDATA_IS_CONTACTS_CONTACT (parsable), FALSE);
@@ -309,54 +310,18 @@ parse_xml (GDataParsable *parsable, xmlDoc *doc, xmlNode *node, gpointer user_da
 			return FALSE;
 		}
 		g_free (edited);
-	} else if (xmlStrcmp (node->name, (xmlChar*) "name") == 0) {
-		/* gd:name */
-		GDataGDName *name = GDATA_GD_NAME (_gdata_parsable_new_from_xml_node (GDATA_TYPE_GD_NAME, doc, node, NULL, error));
-		if (name == NULL)
-			return FALSE;
-
-		if (self->priv->name != NULL)
-			g_object_unref (self->priv->name);
-		self->priv->name = name;
-	} else if (xmlStrcmp (node->name, (xmlChar*) "email") == 0) {
-		/* gd:email */
-		GDataGDEmailAddress *email = GDATA_GD_EMAIL_ADDRESS (_gdata_parsable_new_from_xml_node (GDATA_TYPE_GD_EMAIL_ADDRESS, doc,
-													node, NULL, error));
-		if (email == NULL)
-			return FALSE;
-
-		gdata_contacts_contact_add_email_address (self, email);
-	} else if (xmlStrcmp (node->name, (xmlChar*) "im") == 0) {
-		/* gd:im */
-		GDataGDIMAddress *im = GDATA_GD_IM_ADDRESS (_gdata_parsable_new_from_xml_node (GDATA_TYPE_GD_IM_ADDRESS, doc, node, NULL, error));
-		if (im == NULL)
-			return FALSE;
-
-		gdata_contacts_contact_add_im_address (self, im);
-	} else if (xmlStrcmp (node->name, (xmlChar*) "phoneNumber") == 0) {
-		/* gd:phoneNumber */
-		GDataGDPhoneNumber *number = GDATA_GD_PHONE_NUMBER (_gdata_parsable_new_from_xml_node (GDATA_TYPE_GD_PHONE_NUMBER, doc,
-												       node, NULL, error));
-		if (number == NULL)
-			return FALSE;
-
-		gdata_contacts_contact_add_phone_number (self, number);
-	} else if (xmlStrcmp (node->name, (xmlChar*) "structuredPostalAddress") == 0) {
-		/* gd:structuredPostalAddress â?? deprecates gd:postalAddress */
-		GDataGDPostalAddress *address = GDATA_GD_POSTAL_ADDRESS (_gdata_parsable_new_from_xml_node (GDATA_TYPE_GD_POSTAL_ADDRESS,
-													    doc, node, NULL, error));
-		if (address == NULL)
-			return FALSE;
-
-		gdata_contacts_contact_add_postal_address (self, address);
-	} else if (xmlStrcmp (node->name, (xmlChar*) "organization") == 0) {
-		/* gd:organization */
-		GDataGDOrganization *organization = GDATA_GD_ORGANIZATION (_gdata_parsable_new_from_xml_node (GDATA_TYPE_GD_ORGANIZATION,
-													      doc, node, NULL, error));
-		if (organization == NULL)
-			return FALSE;
-
-		gdata_contacts_contact_add_organization (self, organization);
+	} else if (gdata_parser_object_from_element_setter (node, "email", P_REQUIRED, GDATA_TYPE_GD_EMAIL_ADDRESS,
+	                                                    gdata_contacts_contact_add_email_address, self, &success, error) == TRUE ||
+	           gdata_parser_object_from_element_setter (node, "im", P_REQUIRED, GDATA_TYPE_GD_IM_ADDRESS,
+	                                                    gdata_contacts_contact_add_im_address, self, &success, error) == TRUE ||
+	           gdata_parser_object_from_element_setter (node, "phoneNumber", P_REQUIRED, GDATA_TYPE_GD_PHONE_NUMBER,
+	                                                    gdata_contacts_contact_add_phone_number, self, &success, error) == TRUE ||
+	           gdata_parser_object_from_element_setter (node, "structuredPostalAddress", P_REQUIRED, GDATA_TYPE_GD_POSTAL_ADDRESS,
+	                                                    gdata_contacts_contact_add_postal_address, self, &success, error) == TRUE ||
+	           gdata_parser_object_from_element_setter (node, "organization", P_REQUIRED, GDATA_TYPE_GD_ORGANIZATION,
+	                                                    gdata_contacts_contact_add_organization, self, &success, error) == TRUE ||
+	           gdata_parser_object_from_element (node, "name", P_REQUIRED, GDATA_TYPE_GD_NAME, &(self->priv->name), &success, error) == TRUE) {
+		return success;
 	} else if (xmlStrcmp (node->name, (xmlChar*) "extendedProperty") == 0) {
 		/* gd:extendedProperty */
 		xmlChar *name, *value;
diff --git a/gdata/services/documents/gdata-documents-entry.c b/gdata/services/documents/gdata-documents-entry.c
index 1bcc4f1..e31467f 100644
--- a/gdata/services/documents/gdata-documents-entry.c
+++ b/gdata/services/documents/gdata-documents-entry.c
@@ -201,6 +201,7 @@ gdata_documents_entry_access_handler_init (GDataAccessHandlerIface *iface)
 static gboolean
 parse_xml (GDataParsable *parsable, xmlDoc *doc, xmlNode *node, gpointer user_data, GError **error)
 {
+	gboolean success;
 	GDataDocumentsEntry *self = GDATA_DOCUMENTS_ENTRY (parsable);
 
 	if (xmlStrcmp (node->name, (xmlChar*) "edited") == 0) {
@@ -249,17 +250,11 @@ parse_xml (GDataParsable *parsable, xmlDoc *doc, xmlNode *node, gpointer user_da
 
 		self->priv->document_id = g_strdup (document_id_parts[1]);
 		g_strfreev (document_id_parts);
-	} else if (xmlStrcmp (node->name, (xmlChar*) "feedLink") ==  0) {
-		GDataLink *link = GDATA_LINK (_gdata_parsable_new_from_xml_node (GDATA_TYPE_LINK, doc, node, NULL, error));
-		if (link == NULL)
-			return FALSE;
-		gdata_entry_add_link (GDATA_ENTRY (self), link);
-		g_object_unref (link);
-	} else if (xmlStrcmp (node->name, (xmlChar*) "lastModifiedBy") ==  0) {
-		GDataAuthor *last_modified_by = GDATA_AUTHOR (_gdata_parsable_new_from_xml_node (GDATA_TYPE_AUTHOR, doc, node, NULL, error));
-		if (last_modified_by == NULL)
-			return FALSE;
-		self->priv->last_modified_by = last_modified_by;
+	} else if (gdata_parser_object_from_element_setter (node, "feedLink", P_REQUIRED, GDATA_TYPE_LINK,
+	                                                    gdata_entry_add_link, self,  &success, error) == TRUE ||
+	           gdata_parser_object_from_element (node, "lastModifiedBy", P_REQUIRED, GDATA_TYPE_AUTHOR,
+	                                             &(self->priv->last_modified_by), &success, error) == TRUE) {
+		return success;
 	} else if (GDATA_PARSABLE_CLASS (gdata_documents_entry_parent_class)->parse_xml (parsable, doc, node, user_data, error) == FALSE) {
 		/* Error! */
 		return FALSE;
diff --git a/gdata/services/picasaweb/gdata-picasaweb-album.c b/gdata/services/picasaweb/gdata-picasaweb-album.c
index 9b705fb..96341bf 100644
--- a/gdata/services/picasaweb/gdata-picasaweb-album.c
+++ b/gdata/services/picasaweb/gdata-picasaweb-album.c
@@ -550,31 +550,15 @@ parse_xml (GDataParsable *parsable, xmlDoc *doc, xmlNode *node, gpointer user_da
 	gboolean success;
 	GDataPicasaWebAlbum *self = GDATA_PICASAWEB_ALBUM (parsable);
 
-	if (xmlStrcmp (node->name, (xmlChar*) "group") == 0) {
-		/* media:group */
-		GDataMediaGroup *group = GDATA_MEDIA_GROUP (_gdata_parsable_new_from_xml_node (GDATA_TYPE_MEDIA_GROUP, doc, node, NULL, error));
-		if (group == NULL)
-			return FALSE;
-
-		if (self->priv->media_group != NULL)
-			/* We should really error here, but we can't, as priv->media_group has to be pre-populated
-			 * in order for things like gdata_picasaweb_album_get_tags() to work. */
-			g_object_unref (self->priv->media_group);
-
-		self->priv->media_group = group;
-	} else if (xmlStrcmp (node->name, (xmlChar*) "where") == 0) {
-		/* georss:where */
-		GDataGeoRSSWhere *where = GDATA_GEORSS_WHERE (_gdata_parsable_new_from_xml_node (GDATA_TYPE_GEORSS_WHERE, doc, node, NULL, error));
-		if (where == NULL)
-			return FALSE;
-
-		if (self->priv->georss_where != NULL)
-			g_object_unref (self->priv->georss_where);
-
-		self->priv->georss_where = where;
-	} else if (gdata_parser_string_from_element (node, "user", P_REQUIRED | P_NON_EMPTY, &(self->priv->user), &success, error) == TRUE ||
-	           gdata_parser_string_from_element (node, "nickname", P_REQUIRED | P_NON_EMPTY, &(self->priv->nickname), &success, error) == TRUE ||
-	           gdata_parser_string_from_element (node, "location", P_NONE, &(self->priv->location), &success, error) == TRUE) {
+	/* TODO: This should also be P_NO_DUPES, but we can't, as priv->media_group has to be pre-populated
+	 * in order for things like gdata_picasaweb_album_get_tags() to work. */
+	if (gdata_parser_object_from_element (node, "group", P_REQUIRED, GDATA_TYPE_MEDIA_GROUP,
+	                                      &(self->priv->media_group), &success, error) == TRUE ||
+	    gdata_parser_object_from_element (node, "where", P_REQUIRED, GDATA_TYPE_GEORSS_WHERE,
+	                                      &(self->priv->georss_where), &success, error) == TRUE ||
+	    gdata_parser_string_from_element (node, "user", P_REQUIRED | P_NON_EMPTY, &(self->priv->user), &success, error) == TRUE ||
+	    gdata_parser_string_from_element (node, "nickname", P_REQUIRED | P_NON_EMPTY, &(self->priv->nickname), &success, error) == TRUE ||
+	    gdata_parser_string_from_element (node, "location", P_NONE, &(self->priv->location), &success, error) == TRUE) {
 		return success;
 	} else if (xmlStrcmp (node->name, (xmlChar*) "edited") == 0) {
 		/* app:edited */
diff --git a/gdata/services/picasaweb/gdata-picasaweb-file.c b/gdata/services/picasaweb/gdata-picasaweb-file.c
index c4466ed..9cea5a1 100644
--- a/gdata/services/picasaweb/gdata-picasaweb-file.c
+++ b/gdata/services/picasaweb/gdata-picasaweb-file.c
@@ -796,38 +796,20 @@ parse_xml (GDataParsable *parsable, xmlDoc *doc, xmlNode *node, gpointer user_da
 	gboolean success;
 	GDataPicasaWebFile *self = GDATA_PICASAWEB_FILE (parsable);
 
-	if (xmlStrcmp (node->name, (xmlChar*) "group") == 0) {
-		/* media:group */
-		GDataMediaGroup *group = GDATA_MEDIA_GROUP (_gdata_parsable_new_from_xml_node (GDATA_TYPE_MEDIA_GROUP, doc, node, NULL, error));
-		if (group == NULL)
-			return FALSE;
-
-		if (self->priv->media_group != NULL)
-			/* We should really error here, but we can't, as priv->media_group has to be pre-populated
-			 * in order for things like gdata_picasaweb_file_set_description() to work. */
-			g_object_unref (self->priv->media_group);
-
-		self->priv->media_group = group;
-	} else if (xmlStrcmp (node->name, (xmlChar*) "where") == 0) {
-		/* georss:where */
-		GDataGeoRSSWhere *where = GDATA_GEORSS_WHERE (_gdata_parsable_new_from_xml_node (GDATA_TYPE_GEORSS_WHERE, doc, node, NULL, error));
-		if (where == NULL)
-			return FALSE;
-
-		if (self->priv->georss_where != NULL)
-			g_object_unref (self->priv->georss_where);
-
-		self->priv->georss_where = where;
-	} else if (xmlStrcmp (node->name, (xmlChar*) "tags") == 0) {
-		/* exif:tags */
-		GDataExifTags *tags = GDATA_EXIF_TAGS (_gdata_parsable_new_from_xml_node (GDATA_TYPE_EXIF_TAGS, doc, node, NULL, error));
-		if (tags == NULL)
-			return FALSE;
-
-		if (self->priv->exif_tags != NULL)
-			g_object_unref (self->priv->exif_tags);
-
-		self->priv->exif_tags = tags;
+	/* TODO: This should also be P_NO_DUPES, but we can't, as priv->media_group has to be pre-populated
+	 * in order for things like gdata_picasaweb_file_set_description() to work. */
+	if (gdata_parser_object_from_element (node, "group", P_REQUIRED, GDATA_TYPE_MEDIA_GROUP,
+	                                      &(self->priv->media_group), &success, error) == TRUE ||
+	    gdata_parser_object_from_element (node, "where", P_REQUIRED, GDATA_TYPE_GEORSS_WHERE,
+	                                      &(self->priv->georss_where), &success, error) == TRUE ||
+	    gdata_parser_object_from_element (node, "tags", P_REQUIRED, GDATA_TYPE_EXIF_TAGS,
+	                                      &(self->priv->exif_tags), &success, error) == TRUE ||
+	    gdata_parser_string_from_element (node, "videostatus", P_NO_DUPES, &(self->priv->video_status), &success, error) == TRUE ||
+	    gdata_parser_string_from_element (node, "imageVersion", P_NONE, &(self->priv->version), &success, error) == TRUE ||
+	    gdata_parser_string_from_element (node, "albumid", P_NONE, &(self->priv->album_id), &success, error) == TRUE ||
+	    gdata_parser_string_from_element (node, "client", P_NONE, &(self->priv->client), &success, error) == TRUE ||
+	    gdata_parser_string_from_element (node, "checksum", P_NONE, &(self->priv->client), &success, error) == TRUE) {
+		return success;
 	} else if (xmlStrcmp (node->name, (xmlChar*) "edited") == 0) {
 		/* app:edited */
 		xmlChar *edited = xmlNodeListGetString (doc, node->children, TRUE);
@@ -884,12 +866,6 @@ parse_xml (GDataParsable *parsable, xmlDoc *doc, xmlNode *node, gpointer user_da
 		xmlChar *comment_count = xmlNodeListGetString (doc, node->children, TRUE);
 		self->priv->comment_count = strtoul ((gchar*) comment_count, NULL, 10);
 		xmlFree (comment_count);
-	} else if (gdata_parser_string_from_element (node, "videostatus", P_NO_DUPES, &(self->priv->video_status), &success, error) == TRUE ||
-	           gdata_parser_string_from_element (node, "imageVersion", P_NONE, &(self->priv->version), &success, error) == TRUE ||
-	           gdata_parser_string_from_element (node, "albumid", P_NONE, &(self->priv->album_id), &success, error) == TRUE ||
-	           gdata_parser_string_from_element (node, "client", P_NONE, &(self->priv->client), &success, error) == TRUE ||
-	           gdata_parser_string_from_element (node, "checksum", P_NONE, &(self->priv->client), &success, error) == TRUE) {
-		return success;
 	} else if (xmlStrcmp (node->name, (xmlChar*) "access") == 0) {
 		/* gphoto:access */
 		/* Visibility is already obtained through the album. When PicasaWeb supports per-file access restrictions,
diff --git a/gdata/services/youtube/gdata-youtube-control.c b/gdata/services/youtube/gdata-youtube-control.c
index d909729..9937d65 100644
--- a/gdata/services/youtube/gdata-youtube-control.c
+++ b/gdata/services/youtube/gdata-youtube-control.c
@@ -96,6 +96,7 @@ gdata_youtube_control_dispose (GObject *object)
 static gboolean
 parse_xml (GDataParsable *parsable, xmlDoc *doc, xmlNode *node, gpointer user_data, GError **error)
 {
+	gboolean success;
 	GDataYouTubeControl *self = GDATA_YOUTUBE_CONTROL (parsable);
 
 	if (xmlStrcmp (node->name, (xmlChar*) "draft") == 0) {
@@ -106,18 +107,9 @@ parse_xml (GDataParsable *parsable, xmlDoc *doc, xmlNode *node, gpointer user_da
 		else
 			self->priv->is_draft = TRUE;
 		xmlFree (draft);
-	} else if (xmlStrcmp (node->name, (xmlChar*) "state") == 0) {
-		/* yt:state */
-		GDataYouTubeState *state = GDATA_YOUTUBE_STATE (_gdata_parsable_new_from_xml_node (GDATA_TYPE_YOUTUBE_STATE, doc, node, NULL, error));
-		if (state == NULL)
-			return FALSE;
-
-		if (self->priv->state != NULL) {
-			g_object_unref (state);
-			return gdata_parser_error_duplicate_element (node, error);
-		}
-
-		self->priv->state = state;
+	} else if (gdata_parser_object_from_element (node, "state", P_REQUIRED | P_NO_DUPES, GDATA_TYPE_YOUTUBE_STATE,
+	                                             &(self->priv->state), &success, error) == TRUE) {
+		return success;
 	} else if (GDATA_PARSABLE_CLASS (gdata_youtube_control_parent_class)->parse_xml (parsable, doc, node, user_data, error) == FALSE) {
 		/* Error! */
 		return FALSE;
diff --git a/gdata/services/youtube/gdata-youtube-group.c b/gdata/services/youtube/gdata-youtube-group.c
index 4792b96..e5da07e 100644
--- a/gdata/services/youtube/gdata-youtube-group.c
+++ b/gdata/services/youtube/gdata-youtube-group.c
@@ -93,14 +93,9 @@ parse_xml (GDataParsable *parsable, xmlDoc *doc, xmlNode *node, gpointer user_da
 	gboolean success;
 	GDataYouTubeGroup *self = GDATA_YOUTUBE_GROUP (parsable);
 
-	if (xmlStrcmp (node->name, (xmlChar*) "content") == 0) {
-		/* media:content */
-		GDataYouTubeContent *content = GDATA_YOUTUBE_CONTENT (_gdata_parsable_new_from_xml_node (GDATA_TYPE_YOUTUBE_CONTENT, doc,
-													 node, NULL, error));
-		if (content == NULL)
-			return FALSE;
-
-		_gdata_media_group_add_content (GDATA_MEDIA_GROUP (self), GDATA_MEDIA_CONTENT (content));
+	if (gdata_parser_object_from_element_setter (node, "content", P_REQUIRED, GDATA_TYPE_YOUTUBE_CONTENT,
+	                                             _gdata_media_group_add_content, self, &success, error) == TRUE) {
+		return success;
 	} else if (xmlStrcmp (node->name, (xmlChar*) "credit") == 0) {
 		/* media:credit */
 		GDataYouTubeCredit *credit = GDATA_YOUTUBE_CREDIT (_gdata_parsable_new_from_xml_node (GDATA_TYPE_YOUTUBE_CREDIT, doc,
diff --git a/gdata/services/youtube/gdata-youtube-video.c b/gdata/services/youtube/gdata-youtube-video.c
index 097c905..390482e 100644
--- a/gdata/services/youtube/gdata-youtube-video.c
+++ b/gdata/services/youtube/gdata-youtube-video.c
@@ -624,20 +624,14 @@ gdata_youtube_video_new (const gchar *id)
 static gboolean
 parse_xml (GDataParsable *parsable, xmlDoc *doc, xmlNode *node, gpointer user_data, GError **error)
 {
+	gboolean success;
 	GDataYouTubeVideo *self = GDATA_YOUTUBE_VIDEO (parsable);
 
-	if (xmlStrcmp (node->name, (xmlChar*) "group") == 0) {
-		/* media:group */
-		GDataMediaGroup *group = GDATA_MEDIA_GROUP (_gdata_parsable_new_from_xml_node (GDATA_TYPE_YOUTUBE_GROUP, doc, node, NULL, error));
-		if (group == NULL)
-			return FALSE;
-
-		if (self->priv->media_group != NULL) {
-			g_object_unref (group);
-			return gdata_parser_error_duplicate_element (node, error);
-		}
-
-		self->priv->media_group = group;
+	if (gdata_parser_object_from_element (node, "group", P_REQUIRED | P_NO_DUPES, GDATA_TYPE_YOUTUBE_GROUP,
+	                                      &(self->priv->media_group), &success, error) == TRUE ||
+	    gdata_parser_object_from_element (node, "control", P_REQUIRED | P_NO_DUPES, GDATA_TYPE_YOUTUBE_CONTROL,
+	                                      &(self->priv->youtube_control), &success, error) == TRUE) {
+		return success;
 	} else if (xmlStrcmp (node->name, (xmlChar*) "rating") == 0) {
 		/* gd:rating */
 		xmlChar *min, *max, *num_raters, *average;
@@ -767,19 +761,6 @@ parse_xml (GDataParsable *parsable, xmlDoc *doc, xmlNode *node, gpointer user_da
 		}
 		xmlFree (recorded);
 		gdata_youtube_video_set_recorded (self, &recorded_timeval);
-	} else if (xmlStrcmp (node->name, (xmlChar*) "control") == 0) {
-		/* app:control */
-		GDataYouTubeControl *control = GDATA_YOUTUBE_CONTROL (_gdata_parsable_new_from_xml_node (GDATA_TYPE_YOUTUBE_CONTROL, doc,
-													 node, NULL, error));
-		if (control == NULL)
-			return FALSE;
-
-		if (self->priv->youtube_control != NULL) {
-			g_object_unref (control);
-			return gdata_parser_error_duplicate_element (node, error);
-		}
-
-		self->priv->youtube_control = control;
 	} else if (GDATA_PARSABLE_CLASS (gdata_youtube_video_parent_class)->parse_xml (parsable, doc, node, user_data, error) == FALSE) {
 		/* Error! */
 		return FALSE;
diff --git a/gdata/tests/general.c b/gdata/tests/general.c
index 73e01e7..0a92ed1 100644
--- a/gdata/tests/general.c
+++ b/gdata/tests/general.c
@@ -1800,6 +1800,7 @@ static void
 test_gd_when (void)
 {
 	GDataGDWhen *when, *when2;
+	GDataGDReminder *reminder;
 	gchar *xml;
 	GList *reminders;
 	GTimeVal time_val, time_val2;
@@ -1874,12 +1875,18 @@ test_gd_when (void)
 	g_assert (gdata_gd_reminder_is_absolute_time (GDATA_GD_REMINDER (reminders->data)) == FALSE);
 	g_assert_cmpint (gdata_gd_reminder_get_relative_time (GDATA_GD_REMINDER (reminders->data)), ==, 15);
 
+	/* Add another reminder */
+	reminder = gdata_gd_reminder_new ("alert", &time_val, -1);
+	gdata_gd_when_add_reminder (when, reminder);
+	g_object_unref (reminder);
+
 	/* Check the outputted XML is correct */
 	xml = gdata_parsable_get_xml (GDATA_PARSABLE (when));
 	g_assert_cmpstr (xml, ==,
 			 "<gd:when xmlns='http://www.w3.org/2005/Atom' xmlns:gd='http://schemas.google.com/g/2005' startTime='2005-06-06' "
 				"endTime='2005-06-08' valueString='This weekend'>"
 				"<gd:reminder minutes='15'/>"
+				"<gd:reminder absoluteTime='2005-06-06T00:00:00Z' method='alert'/>"
 				"<foobar/>"
 			 "</gd:when>");
 	g_free (xml);



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]