[libgdata] [core] Abstract XML parsing from GDataFeed and GDataEntry into GDataParsable



commit c1c6885e2cf997175e8a71470e74e2deddcb6c2f
Author: Philip Withnall <philip tecnocode co uk>
Date:   Sun May 17 18:03:26 2009 +0100

    [core] Abstract XML parsing from GDataFeed and GDataEntry into GDataParsable
    
    Adds a new abstract class, GDataParsable, which adds extensible XML parsing
    functionality to inheriting classes. A little documentation is included, but
    there is no real public API for the class.
---
 docs/reference/gdata-docs.xml                     |    1 +
 docs/reference/gdata-sections.txt                 |   17 +
 gdata/Makefile.am                                 |    6 +-
 gdata/gdata-access-rule.c                         |   16 +-
 gdata/gdata-entry.c                               |  440 ++++++--------
 gdata/gdata-entry.h                               |    8 +-
 gdata/gdata-feed.c                                |  692 +++++++++------------
 gdata/gdata-feed.h                                |    5 +-
 gdata/gdata-parsable.c                            |  210 +++++++
 gdata/gdata-parsable.h                            |   74 +++
 gdata/gdata-private.h                             |    9 +-
 gdata/gdata.h                                     |    1 +
 gdata/gdata.symbols                               |    1 +
 gdata/services/calendar/gdata-calendar-calendar.c |   16 +-
 gdata/services/calendar/gdata-calendar-event.c    |   16 +-
 gdata/services/contacts/gdata-contacts-contact.c  |   16 +-
 gdata/services/youtube/gdata-youtube-service.c    |    2 +-
 gdata/services/youtube/gdata-youtube-video.c      |   16 +-
 po/POTFILES.in                                    |    1 +
 19 files changed, 858 insertions(+), 689 deletions(-)

diff --git a/docs/reference/gdata-docs.xml b/docs/reference/gdata-docs.xml
index f14206f..9959050 100644
--- a/docs/reference/gdata-docs.xml
+++ b/docs/reference/gdata-docs.xml
@@ -23,6 +23,7 @@
 		<xi:include href="xml/gdata-feed.xml"/>
 		<xi:include href="xml/gdata-entry.xml"/>
 		<xi:include href="xml/gdata-types.xml"/>
+		<xi:include href="xml/gdata-parsable.xml"/>
 	</chapter>
 
 	<chapter>
diff --git a/docs/reference/gdata-sections.txt b/docs/reference/gdata-sections.txt
index c49ecc4..6e9df3c 100644
--- a/docs/reference/gdata-sections.txt
+++ b/docs/reference/gdata-sections.txt
@@ -669,3 +669,20 @@ GDATA_TYPE_ACCESS_RULE
 <SUBSECTION Private>
 GDataAccessRulePrivate
 </SECTION>
+
+<SECTION>
+<FILE>gdata-parsable</FILE>
+<TITLE>GDataParsable</TITLE>
+GDataParsable
+GDataParsableClass
+<SUBSECTION Standard>
+gdata_parsable_get_type
+GDATA_IS_PARSABLE
+GDATA_IS_PARSABLE_CLASS
+GDATA_PARSABLE
+GDATA_PARSABLE_CLASS
+GDATA_PARSABLE_GET_CLASS
+GDATA_TYPE_PARSABLE
+<SUBSECTION Private>
+GDataParsablePrivate
+</SECTION>
diff --git a/gdata/Makefile.am b/gdata/Makefile.am
index 03f0660..5b73ee2 100644
--- a/gdata/Makefile.am
+++ b/gdata/Makefile.am
@@ -52,7 +52,8 @@ gdata_headers = \
 	gdata-gdata.h		\
 	gdata-parser.h		\
 	gdata-access-handler.h	\
-	gdata-access-rule.h
+	gdata-access-rule.h	\
+	gdata-parsable.h
 
 gdataincludedir = $(pkgincludedir)/gdata
 gdatainclude_HEADERS = \
@@ -74,7 +75,8 @@ libgdata_la_SOURCES = \
 	gdata-parser.c		\
 	gdata-access-handler.c	\
 	gdata-access-rule.c	\
-	gdata-private.h
+	gdata-private.h		\
+	gdata-parsable.c
 
 libgdata_la_CPPFLAGS = \
 	-I$(top_srcdir)			\
diff --git a/gdata/gdata-access-rule.c b/gdata/gdata-access-rule.c
index cd2a50f..a979477 100644
--- a/gdata/gdata-access-rule.c
+++ b/gdata/gdata-access-rule.c
@@ -45,7 +45,7 @@ static void get_namespaces (GDataEntry *entry, GHashTable *namespaces);
 static void get_xml (GDataEntry *entry, GString *xml_string);
 static void gdata_access_rule_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec);
 static void gdata_access_rule_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec);
-static gboolean parse_xml (GDataEntry *entry, xmlDoc *doc, xmlNode *node, GError **error);
+static gboolean parse_xml (GDataParsable *parsable, xmlDoc *doc, xmlNode *node, gpointer user_data, GError **error);
 
 struct _GDataAccessRulePrivate {
 	gchar *role;
@@ -66,6 +66,7 @@ static void
 gdata_access_rule_class_init (GDataAccessRuleClass *klass)
 {
 	GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+	GDataParsableClass *parsable_class = GDATA_PARSABLE_CLASS (klass);
 	GDataEntryClass *entry_class = GDATA_ENTRY_CLASS (klass);
 
 	g_type_class_add_private (klass, sizeof (GDataAccessRulePrivate));
@@ -74,8 +75,9 @@ gdata_access_rule_class_init (GDataAccessRuleClass *klass)
 	gobject_class->set_property = gdata_access_rule_set_property; 
 	gobject_class->get_property = gdata_access_rule_get_property; 
 
+	parsable_class->parse_xml = parse_xml;
+
 	entry_class->get_xml = get_xml;
-	entry_class->parse_xml = parse_xml;
 	entry_class->get_namespaces = get_namespaces;
 
 	/**
@@ -224,14 +226,16 @@ gdata_access_rule_finalize (GObject *object)
 }
 
 static gboolean
-parse_xml (GDataEntry *entry, xmlDoc *doc, xmlNode *node, GError **error)
+parse_xml (GDataParsable *parsable, xmlDoc *doc, xmlNode *node, gpointer user_data, GError **error)
 {
-	GDataAccessRule *self = GDATA_ACCESS_RULE (entry);
+	GDataAccessRule *self;
 
-	g_return_val_if_fail (GDATA_IS_ACCESS_RULE (self), FALSE);
+	g_return_val_if_fail (GDATA_IS_ACCESS_RULE (parsable), FALSE);
 	g_return_val_if_fail (doc != NULL, FALSE);
 	g_return_val_if_fail (node != NULL, FALSE);
 
+	self = GDATA_ACCESS_RULE (parsable);
+
 	if (xmlStrcmp (node->name, (xmlChar*) "role") == 0) {
 		/* gAcl:role */
 		xmlChar *role = xmlGetProp (node, (xmlChar*) "value");
@@ -251,7 +255,7 @@ parse_xml (GDataEntry *entry, xmlDoc *doc, xmlNode *node, GError **error)
 		gdata_access_rule_set_scope (self, (gchar*) scope_type, (gchar*) scope_value);
 		xmlFree (scope_type);
 		xmlFree (scope_value);
-	} else if (GDATA_ENTRY_CLASS (gdata_access_rule_parent_class)->parse_xml (entry, doc, node, error) == FALSE) {
+	} else if (GDATA_PARSABLE_CLASS (gdata_access_rule_parent_class)->parse_xml (parsable, doc, node, user_data, error) == FALSE) {
 		/* Error! */
 		return FALSE;
 	}
diff --git a/gdata/gdata-entry.c b/gdata/gdata-entry.c
index 337b1ce..c46f8f0 100644
--- a/gdata/gdata-entry.c
+++ b/gdata/gdata-entry.c
@@ -39,13 +39,14 @@
 #include "gdata-service.h"
 #include "gdata-private.h"
 #include "gdata-atom.h"
-#include "gdata-parser.h"
 
 static void gdata_entry_finalize (GObject *object);
 static void gdata_entry_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec);
 static void gdata_entry_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec);
+static gboolean pre_parse_xml (GDataParsable *parsable, xmlDoc *doc, xmlNode *root_node, gpointer user_data, GError **error);
+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 real_get_xml (GDataEntry *self, GString *xml_string);
-static gboolean real_parse_xml (GDataEntry *self, xmlDoc *doc, xmlNode *node, GError **error);
 static void real_get_namespaces (GDataEntry *self, GHashTable *namespaces);
 
 struct _GDataEntryPrivate {
@@ -58,9 +59,6 @@ struct _GDataEntryPrivate {
 	gchar *content;
 	GList *links;
 	GList *authors;
-
-	GString *extra_xml;
-	GHashTable *extra_namespaces;
 };
 
 enum {
@@ -73,13 +71,14 @@ enum {
 	PROP_IS_INSERTED
 };
 
-G_DEFINE_TYPE (GDataEntry, gdata_entry, G_TYPE_OBJECT)
+G_DEFINE_TYPE (GDataEntry, gdata_entry, GDATA_TYPE_PARSABLE)
 #define GDATA_ENTRY_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GDATA_TYPE_ENTRY, GDataEntryPrivate))
 
 static void
 gdata_entry_class_init (GDataEntryClass *klass)
 {
 	GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+	GDataParsableClass *parsable_class = GDATA_PARSABLE_CLASS (klass);
 
 	g_type_class_add_private (klass, sizeof (GDataEntryPrivate));
 
@@ -87,8 +86,11 @@ gdata_entry_class_init (GDataEntryClass *klass)
 	gobject_class->get_property = gdata_entry_get_property;
 	gobject_class->finalize = gdata_entry_finalize;
 
+	parsable_class->pre_parse_xml = pre_parse_xml;
+	parsable_class->parse_xml = parse_xml;
+	parsable_class->post_parse_xml = post_parse_xml;
+
 	klass->get_xml = real_get_xml;
-	klass->parse_xml = real_parse_xml;
 	klass->get_namespaces = real_get_namespaces;
 
 	g_object_class_install_property (gobject_class, PROP_TITLE,
@@ -133,8 +135,6 @@ static void
 gdata_entry_init (GDataEntry *self)
 {
 	self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GDATA_TYPE_ENTRY, GDataEntryPrivate);
-	self->priv->extra_xml = g_string_new ("");
-	self->priv->extra_namespaces = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
 }
 
 static void
@@ -153,9 +153,6 @@ gdata_entry_finalize (GObject *object)
 	g_list_foreach (priv->authors, (GFunc) gdata_author_free, NULL);
 	g_list_free (priv->authors);
 
-	g_string_free (priv->extra_xml, TRUE);
-	g_hash_table_destroy (priv->extra_namespaces);
-
 	/* Chain up to the parent class */
 	G_OBJECT_CLASS (gdata_entry_parent_class)->finalize (object);
 }
@@ -221,6 +218,187 @@ gdata_entry_set_property (GObject *object, guint property_id, const GValue *valu
 	}
 }
 
+static gboolean
+pre_parse_xml (GDataParsable *parsable, xmlDoc *doc, xmlNode *root_node, gpointer user_data, GError **error)
+{
+	g_return_val_if_fail (GDATA_IS_ENTRY (parsable), FALSE);
+	g_return_val_if_fail (doc != NULL, FALSE);
+	g_return_val_if_fail (root_node != NULL, FALSE);
+
+	/* Extract the ETag */
+	GDATA_ENTRY (parsable)->priv->etag = (gchar*) xmlGetProp (root_node, (xmlChar*) "etag");
+
+	return TRUE;
+}
+
+static gboolean
+parse_xml (GDataParsable *parsable, xmlDoc *doc, xmlNode *node, gpointer user_data, GError **error)
+{
+	GDataEntry *self;
+
+	g_return_val_if_fail (GDATA_IS_ENTRY (parsable), FALSE);
+	g_return_val_if_fail (doc != NULL, FALSE);
+	g_return_val_if_fail (node != NULL, FALSE);
+
+	self = GDATA_ENTRY (parsable);
+
+	if (xmlStrcmp (node->name, (xmlChar*) "title") == 0) {
+		/* atom:title */
+		xmlChar *title = xmlNodeListGetString (doc, node->children, TRUE);
+
+		/* Title can be empty */
+		if (title == NULL)
+			gdata_entry_set_title (self, "");
+		else
+			gdata_entry_set_title (self, (gchar*) title);
+		xmlFree (title);
+	} else if (xmlStrcmp (node->name, (xmlChar*) "id") == 0) {
+		/* atom:id */
+		xmlFree (self->priv->id);
+		self->priv->id = (gchar*) xmlNodeListGetString (doc, node->children, TRUE);
+	} else if (xmlStrcmp (node->name, (xmlChar*) "updated") == 0) {
+		/* atom:updated */
+		xmlChar *updated;
+
+		updated = xmlNodeListGetString (doc, node->children, TRUE);
+		if (g_time_val_from_iso8601 ((gchar*) updated, &(self->priv->updated)) == FALSE) {
+			/* Error */
+			gdata_parser_error_not_iso8601_format ("updated", "entry", (gchar*) updated, error);
+			xmlFree (updated);
+			return FALSE;
+		}
+		xmlFree (updated);
+	} else if (xmlStrcmp (node->name, (xmlChar*) "published") == 0) {
+		/* atom:published */
+		xmlChar *published;
+
+		published = xmlNodeListGetString (doc, node->children, TRUE);
+		if (g_time_val_from_iso8601 ((gchar*) published, &(self->priv->published)) == FALSE) {
+			/* Error */
+			gdata_parser_error_not_iso8601_format ("published", "entry", (gchar*) published, error);
+			xmlFree (published);
+			return FALSE;
+		}
+		xmlFree (published);
+	} else if (xmlStrcmp (node->name, (xmlChar*) "category") == 0) {
+		/* atom:category */
+		xmlChar *scheme, *term, *label;
+		GDataCategory *category;
+
+		scheme = xmlGetProp (node, (xmlChar*) "scheme");
+		term = xmlGetProp (node, (xmlChar*) "term");
+		label = xmlGetProp (node, (xmlChar*) "label");
+
+		category = gdata_category_new ((gchar*) term, (gchar*) scheme, (gchar*) label);
+		self->priv->categories = g_list_prepend (self->priv->categories, category);
+
+		xmlFree (scheme);
+		xmlFree (term);
+		xmlFree (label);
+	} 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");
+		gdata_entry_set_content (self, (gchar*) content);
+		xmlFree (content);
+	} else if (xmlStrcmp (node->name, (xmlChar*) "link") == 0) {
+		/* atom:link */
+		xmlChar *href, *rel, *type, *hreflang, *title, *length;
+		gint length_int;
+		GDataLink *link;
+
+		href = xmlGetProp (node, (xmlChar*) "href");
+		rel = xmlGetProp (node, (xmlChar*) "rel");
+		type = xmlGetProp (node, (xmlChar*) "type");
+		hreflang = xmlGetProp (node, (xmlChar*) "hreflang");
+		title = xmlGetProp (node, (xmlChar*) "title");
+		length = xmlGetProp (node, (xmlChar*) "length");
+
+		if (length == NULL)
+			length_int = -1;
+		else
+			length_int = strtoul ((gchar*) length, NULL, 10);
+
+		link = gdata_link_new ((gchar*) href, (gchar*) rel, (gchar*) type, (gchar*) hreflang, (gchar*) title, length_int);
+		self->priv->links = g_list_prepend (self->priv->links, link);
+
+		xmlFree (href);
+		xmlFree (rel);
+		xmlFree (type);
+		xmlFree (hreflang);
+		xmlFree (title);
+		xmlFree (length);
+	} else if (xmlStrcmp (node->name, (xmlChar*) "author") == 0) {
+		/* atom:author */
+		GDataAuthor *author;
+		xmlNode *author_node;
+		xmlChar *name = NULL, *uri = NULL, *email = NULL;
+
+		author_node = node->children;
+		while (author_node != NULL) {
+			if (xmlStrcmp (author_node->name, (xmlChar*) "name") == 0) {
+				name = xmlNodeListGetString (doc, author_node->children, TRUE);
+			} else if (xmlStrcmp (author_node->name, (xmlChar*) "uri") == 0) {
+				uri = xmlNodeListGetString (doc, author_node->children, TRUE);
+			} else if (xmlStrcmp (author_node->name, (xmlChar*) "email") == 0) {
+				email = xmlNodeListGetString (doc, author_node->children, TRUE);
+			} else {
+				gdata_parser_error_unhandled_element ((gchar*) author_node->ns->prefix, (gchar*) author_node->name, "author", error);
+				xmlFree (name);
+				xmlFree (uri);
+				xmlFree (email);
+				return FALSE;
+			}
+
+			author_node = author_node->next;
+		}
+
+		author = gdata_author_new ((gchar*) name, (gchar*) uri, (gchar*) email);
+		self->priv->authors = g_list_prepend (self->priv->authors, author);
+
+		xmlFree (name);
+		xmlFree (uri);
+		xmlFree (email);
+	} else if (GDATA_PARSABLE_CLASS (gdata_entry_parent_class)->parse_xml (parsable, doc, node, user_data, error) == FALSE) {
+		/* Error! */
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+static gboolean
+post_parse_xml (GDataParsable *parsable, gpointer user_data, GError **error)
+{
+	GDataEntryPrivate *priv = GDATA_ENTRY (parsable)->priv;
+
+	/* Check for missing required elements */
+	/* Can't uncomment it, as things like access rules break the Atom standard */
+	/*if (priv->title == NULL)
+		return gdata_parser_error_required_element_missing ("title", "entry", error);
+	if (priv->id == NULL)
+		return gdata_parser_error_required_element_missing ("id", "entry", error);
+	if (priv->updated.tv_sec == 0 && priv->updated.tv_usec == 0)
+		return gdata_parser_error_required_element_missing ("updated", "entry", error);*/
+
+	/* Reverse our lists of stuff */
+	priv->categories = g_list_reverse (priv->categories);
+	priv->links = g_list_reverse (priv->links);
+	priv->authors = g_list_reverse (priv->authors);
+
+	return TRUE;
+}
+
+GDataEntry *
+_gdata_entry_new_from_xml (GType entry_type, const gchar *xml, gint length, GError **error)
+{
+	g_return_val_if_fail (xml != NULL, NULL);
+	g_return_val_if_fail (g_type_is_a (entry_type, GDATA_TYPE_ENTRY) == TRUE, FALSE);
+
+	return GDATA_ENTRY (_gdata_parsable_new_from_xml (entry_type, "entry", xml, length, NULL, error));
+}
+
 static void
 real_get_xml (GDataEntry *self, GString *xml_string)
 {
@@ -314,8 +492,8 @@ real_get_xml (GDataEntry *self, GString *xml_string)
 		g_string_append (xml_string, "</author>");
 	}
 
-	if (priv->extra_xml != NULL && priv->extra_xml->str != NULL)
-		g_string_append (xml_string, priv->extra_xml->str);
+	if (_gdata_parsable_get_extra_xml (GDATA_PARSABLE (self)) != NULL)
+		g_string_append (xml_string, _gdata_parsable_get_extra_xml (GDATA_PARSABLE (self)));
 }
 
 static void
@@ -338,48 +516,6 @@ gdata_entry_new (const gchar *id)
 	return g_object_new (GDATA_TYPE_ENTRY, "id", id, NULL);
 }
 
-GDataEntry *
-_gdata_entry_new_from_xml (GType entry_type, const gchar *xml, gint length, GError **error)
-{
-	xmlDoc *doc;
-	xmlNode *node;
-
-	g_return_val_if_fail (xml != NULL, NULL);
-
-	if (length == -1)
-		length = strlen (xml);
-
-	/* Parse the XML */
-	doc = xmlReadMemory (xml, length, "entry.xml", NULL, 0);
-	if (doc == NULL) {
-		xmlError *xml_error = xmlGetLastError ();
-		g_set_error (error, GDATA_PARSER_ERROR, GDATA_PARSER_ERROR_PARSING_STRING,
-			     _("Error parsing XML: %s"),
-			     xml_error->message);
-		return NULL;
-	}
-
-	/* Get the root element */
-	node = xmlDocGetRootElement (doc);
-	if (node == NULL) {
-		/* XML document's empty */
-		xmlFreeDoc (doc);
-		g_set_error (error, GDATA_PARSER_ERROR, GDATA_PARSER_ERROR_EMPTY_DOCUMENT,
-			     _("Error parsing XML: %s"),
-			     _("Empty document."));
-		return NULL;
-	}
-
-	if (xmlStrcmp (node->name, (xmlChar*) "entry") != 0) {
-		/* No <entry> element (required) */
-		xmlFreeDoc (doc);
-		gdata_parser_error_required_element_missing ("entry", "root", error);
-		return NULL;
-	}
-
-	return _gdata_entry_new_from_xml_node (entry_type, doc, node, error);
-}
-
 /**
  * gdata_entry_new_from_xml:
  * @xml: the XML for just the entry, with full namespace declarations
@@ -397,190 +533,7 @@ _gdata_entry_new_from_xml (GType entry_type, const gchar *xml, gint length, GErr
 GDataEntry *
 gdata_entry_new_from_xml (const gchar *xml, gint length, GError **error)
 {
-	return _gdata_entry_new_from_xml (GDATA_TYPE_ENTRY, xml, length, error);
-}
-
-GDataEntry *
-_gdata_entry_new_from_xml_node (GType entry_type, xmlDoc *doc, xmlNode *node, GError **error)
-{
-	GDataEntry *entry;
-	GDataEntryClass *klass;
-
-	g_return_val_if_fail (g_type_is_a (entry_type, GDATA_TYPE_ENTRY) == TRUE, FALSE);
-	g_return_val_if_fail (doc != NULL, FALSE);
-	g_return_val_if_fail (node != NULL, FALSE);
-	g_return_val_if_fail (xmlStrcmp (node->name, (xmlChar*) "entry") == 0, FALSE);
-
-	entry = g_object_new (entry_type, NULL);
-
-	klass = GDATA_ENTRY_GET_CLASS (entry);
-	if (klass->parse_xml == NULL)
-		return FALSE;
-
-	/* Get the ETag first */
-	entry->priv->etag = (gchar*) xmlGetProp (node, (xmlChar*) "etag");
-
-	node = node->children;
-	while (node != NULL) {
-		if (klass->parse_xml (entry, doc, node, error) == FALSE) {
-			g_object_unref (entry);
-			return NULL;
-		}
-		node = node->next;
-	}
-
-	return entry;
-}
-
-static gboolean
-real_parse_xml (GDataEntry *self, xmlDoc *doc, xmlNode *node, GError **error)
-{
-	g_return_val_if_fail (GDATA_IS_ENTRY (self), FALSE);
-	g_return_val_if_fail (doc != NULL, FALSE);
-	g_return_val_if_fail (node != NULL, FALSE);
-
-	if (xmlStrcmp (node->name, (xmlChar*) "title") == 0) {
-		/* atom:title */
-		xmlChar *title = xmlNodeListGetString (doc, node->children, TRUE);
-
-		/* Title can be empty */
-		if (title == NULL)
-			gdata_entry_set_title (self, "");
-		else
-			gdata_entry_set_title (self, (gchar*) title);
-		xmlFree (title);
-	} else if (xmlStrcmp (node->name, (xmlChar*) "id") == 0) {
-		/* atom:id */
-		xmlFree (self->priv->id);
-		self->priv->id = (gchar*) xmlNodeListGetString (doc, node->children, TRUE);
-	} else if (xmlStrcmp (node->name, (xmlChar*) "updated") == 0) {
-		/* atom:updated */
-		xmlChar *updated;
-
-		updated = xmlNodeListGetString (doc, node->children, TRUE);
-		if (g_time_val_from_iso8601 ((gchar*) updated, &(self->priv->updated)) == FALSE) {
-			/* Error */
-			gdata_parser_error_not_iso8601_format ("updated", "entry", (gchar*) updated, error);
-			xmlFree (updated);
-			return FALSE;
-		}
-		xmlFree (updated);
-	} else if (xmlStrcmp (node->name, (xmlChar*) "published") == 0) {
-		/* atom:published */
-		xmlChar *published;
-
-		published = xmlNodeListGetString (doc, node->children, TRUE);
-		if (g_time_val_from_iso8601 ((gchar*) published, &(self->priv->published)) == FALSE) {
-			/* Error */
-			gdata_parser_error_not_iso8601_format ("published", "entry", (gchar*) published, error);
-			xmlFree (published);
-			return FALSE;
-		}
-		xmlFree (published);
-	} else if (xmlStrcmp (node->name, (xmlChar*) "category") == 0) {
-		/* atom:category */
-		xmlChar *scheme, *term, *label;
-		GDataCategory *category;
-
-		scheme = xmlGetProp (node, (xmlChar*) "scheme");
-		term = xmlGetProp (node, (xmlChar*) "term");
-		label = xmlGetProp (node, (xmlChar*) "label");
-
-		category = gdata_category_new ((gchar*) term, (gchar*) scheme, (gchar*) label);
-		gdata_entry_add_category (self, category);
-
-		xmlFree (scheme);
-		xmlFree (term);
-		xmlFree (label);
-	} 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");
-		gdata_entry_set_content (self, (gchar*) content);
-		xmlFree (content);
-	} else if (xmlStrcmp (node->name, (xmlChar*) "link") == 0) {
-		/* atom:link */
-		xmlChar *href, *rel, *type, *hreflang, *title, *length;
-		gint length_int;
-		GDataLink *link;
-
-		href = xmlGetProp (node, (xmlChar*) "href");
-		rel = xmlGetProp (node, (xmlChar*) "rel");
-		type = xmlGetProp (node, (xmlChar*) "type");
-		hreflang = xmlGetProp (node, (xmlChar*) "hreflang");
-		title = xmlGetProp (node, (xmlChar*) "title");
-		length = xmlGetProp (node, (xmlChar*) "length");
-
-		if (length == NULL)
-			length_int = -1;
-		else
-			length_int = strtoul ((gchar*) length, NULL, 10);
-
-		link = gdata_link_new ((gchar*) href, (gchar*) rel, (gchar*) type, (gchar*) hreflang, (gchar*) title, length_int);
-		gdata_entry_add_link (self, link);
-
-		xmlFree (href);
-		xmlFree (rel);
-		xmlFree (type);
-		xmlFree (hreflang);
-		xmlFree (title);
-		xmlFree (length);
-	} else if (xmlStrcmp (node->name, (xmlChar*) "author") == 0) {
-		/* atom:author */
-		GDataAuthor *author;
-		xmlNode *author_node;
-		xmlChar *name = NULL, *uri = NULL, *email = NULL;
-
-		author_node = node->children;
-		while (author_node != NULL) {
-			if (xmlStrcmp (author_node->name, (xmlChar*) "name") == 0) {
-				name = xmlNodeListGetString (doc, author_node->children, TRUE);
-			} else if (xmlStrcmp (author_node->name, (xmlChar*) "uri") == 0) {
-				uri = xmlNodeListGetString (doc, author_node->children, TRUE);
-			} else if (xmlStrcmp (author_node->name, (xmlChar*) "email") == 0) {
-				email = xmlNodeListGetString (doc, author_node->children, TRUE);
-			} else {
-				gdata_parser_error_unhandled_element ((gchar*) author_node->ns->prefix, (gchar*) author_node->name, "author", error);
-				xmlFree (name);
-				xmlFree (uri);
-				xmlFree (email);
-				return FALSE;
-			}
-
-			author_node = author_node->next;
-		}
-
-		author = gdata_author_new ((gchar*) name, (gchar*) uri, (gchar*) email);
-		gdata_entry_add_author (self, author);
-
-		xmlFree (name);
-		xmlFree (uri);
-		xmlFree (email);
-	} else {
-		xmlBuffer *buffer;
-		xmlNs **namespaces, **namespace;
-
-		/* Unhandled XML */
-		buffer = xmlBufferCreate ();
-		xmlNodeDump (buffer, doc, node, 0, 0);
-		g_string_append (self->priv->extra_xml, (gchar*) xmlBufferContent (buffer));
-		g_message ("Unhandled XML in <entry>: %s", (gchar*) xmlBufferContent (buffer));
-		xmlBufferFree (buffer);
-
-		/* Get the namespaces */
-		namespaces = xmlGetNsList (doc, node);
-		for (namespace = namespaces; *namespace != NULL; namespace++) {
-			if ((*namespace)->prefix != NULL) {
-				g_hash_table_insert (self->priv->extra_namespaces,
-						     g_strdup ((gchar*) ((*namespace)->prefix)),
-						     g_strdup ((gchar*) ((*namespace)->href)));
-			}
-		}
-		xmlFree (namespaces);
-	}
-
-	return TRUE;
+	return GDATA_ENTRY (_gdata_parsable_new_from_xml (GDATA_TYPE_ENTRY, "entry", xml, length, NULL, error));
 }
 
 /**
@@ -856,7 +809,7 @@ gdata_entry_get_xml (GDataEntry *self)
 {
 	GDataEntryClass *klass;
 	GString *xml_string;
-	GHashTable *namespaces;
+	GHashTable *namespaces, *extra_namespaces;
 
 	klass = GDATA_ENTRY_GET_CLASS (self);
 	g_assert (klass->get_xml != NULL);
@@ -865,14 +818,15 @@ gdata_entry_get_xml (GDataEntry *self)
 	/* Get the namespaces the class uses */
 	namespaces = g_hash_table_new (g_str_hash, g_str_equal);
 	klass->get_namespaces (self, namespaces);
+	extra_namespaces = _gdata_parsable_get_extra_namespaces (GDATA_PARSABLE (self));
 
 	/* Remove any duplicate extra namespaces */
-	g_hash_table_foreach_remove (self->priv->extra_namespaces, (GHRFunc) filter_namespaces_cb, namespaces);
+	g_hash_table_foreach_remove (extra_namespaces, (GHRFunc) filter_namespaces_cb, namespaces);
 
 	/* Build up the namespace list */
 	xml_string = g_string_new ("<entry xmlns='http://www.w3.org/2005/Atom'");
 	g_hash_table_foreach (namespaces, (GHFunc) build_namespaces_cb, xml_string);
-	g_hash_table_foreach (self->priv->extra_namespaces, (GHFunc) build_namespaces_cb, xml_string);
+	g_hash_table_foreach (extra_namespaces, (GHFunc) build_namespaces_cb, xml_string);
 
 	/* Add the entry's ETag, if available */
 	if (self->priv->etag != NULL)
diff --git a/gdata/gdata-entry.h b/gdata/gdata-entry.h
index 11bf995..eb754bd 100644
--- a/gdata/gdata-entry.h
+++ b/gdata/gdata-entry.h
@@ -22,9 +22,9 @@
 
 #include <glib.h>
 #include <glib-object.h>
-#include <libxml/parser.h>
 
 #include <gdata/gdata-atom.h>
+#include <gdata/gdata-parsable.h>
 
 G_BEGIN_DECLS
 
@@ -43,7 +43,7 @@ typedef struct _GDataEntryPrivate	GDataEntryPrivate;
  * All the fields in the #GDataEntry structure are private and should never be accessed directly.
  **/
 typedef struct {
-	GObject parent;
+	GDataParsable parent;
 	GDataEntryPrivate *priv;
 } GDataEntry;
 
@@ -52,7 +52,6 @@ typedef struct {
  * @parent: the parent class
  * @get_xml: a function to build an XML representation of the #GDataEntry in its current state, appending it to the provided
  * #GString
- * @parse_xml: a function to parse an XML representation of the #GDataEntry to set the properties of the @entry
  * @get_namespaces: a function to return a string containing the namespace declarations used by the entry when represented
  * in XML form
  *
@@ -65,10 +64,9 @@ typedef struct {
  **/
 typedef struct {
 	/*< private >*/
-	GObjectClass parent;
+	GDataParsableClass parent;
 
 	void (*get_xml) (GDataEntry *self, GString *xml_string);
-	gboolean (*parse_xml) (GDataEntry *self, xmlDoc *doc, xmlNode *node, GError **error);
 	void (*get_namespaces) (GDataEntry *self, GHashTable *namespaces);
 } GDataEntryClass;
 
diff --git a/gdata/gdata-feed.c b/gdata/gdata-feed.c
index 2c6a66e..0745cba 100644
--- a/gdata/gdata-feed.c
+++ b/gdata/gdata-feed.c
@@ -42,12 +42,14 @@
 #include "gdata-types.h"
 #include "gdata-private.h"
 #include "gdata-service.h"
-#include "gdata-parser.h"
+#include "gdata-parsable.h"
 
 static void gdata_feed_dispose (GObject *object);
 static void gdata_feed_finalize (GObject *object);
 static void gdata_feed_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec);
-static void gdata_feed_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec);
+static gboolean pre_parse_xml (GDataParsable *parsable, xmlDoc *doc, xmlNode *root_node, gpointer user_data, GError **error);
+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);
 
 struct _GDataFeedPrivate {
 	GList *entries;
@@ -64,7 +66,6 @@ struct _GDataFeedPrivate {
 	guint items_per_page;
 	guint start_index;
 	guint total_results;
-	gchar *extra_xml;
 };
 
 enum {
@@ -80,21 +81,25 @@ enum {
 	PROP_TOTAL_RESULTS
 };
 
-G_DEFINE_TYPE (GDataFeed, gdata_feed, G_TYPE_OBJECT)
+G_DEFINE_TYPE (GDataFeed, gdata_feed, GDATA_TYPE_PARSABLE)
 #define GDATA_FEED_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GDATA_TYPE_FEED, GDataFeedPrivate))
 
 static void
 gdata_feed_class_init (GDataFeedClass *klass)
 {
 	GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+	GDataParsableClass *parsable_class = GDATA_PARSABLE_CLASS (klass);
 
 	g_type_class_add_private (klass, sizeof (GDataFeedPrivate));
 
-	gobject_class->set_property = gdata_feed_set_property;
 	gobject_class->get_property = gdata_feed_get_property;
 	gobject_class->dispose = gdata_feed_dispose;
 	gobject_class->finalize = gdata_feed_finalize;
 
+	parsable_class->pre_parse_xml = pre_parse_xml;
+	parsable_class->parse_xml = parse_xml;
+	parsable_class->post_parse_xml = post_parse_xml;
+
 	/**
 	 * GDataFeed:title:
 	 *
@@ -106,7 +111,7 @@ gdata_feed_class_init (GDataFeedClass *klass)
 				g_param_spec_string ("title",
 					"Title", "The title of the feed.",
 					NULL,
-					G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+					G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
 
 	/**
 	 * GDataFeed:subtitle:
@@ -119,7 +124,7 @@ gdata_feed_class_init (GDataFeedClass *klass)
 				g_param_spec_string ("subtitle",
 					"Subtitle", "The subtitle of the feed.",
 					NULL,
-					G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+					G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
 
 	/**
 	 * GDataFeed:id:
@@ -132,7 +137,7 @@ gdata_feed_class_init (GDataFeedClass *klass)
 				g_param_spec_string ("id",
 					"ID", "The unique and permanent URN ID for the feed.",
 					NULL,
-					G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+					G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
 
 	/**
 	 * GDataFeed:etag:
@@ -147,7 +152,7 @@ gdata_feed_class_init (GDataFeedClass *klass)
 				g_param_spec_string ("etag",
 					"ETag", "The unique ETag for this version of the feed.",
 					NULL,
-					G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+					G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
 
 
 	/**
@@ -162,7 +167,7 @@ gdata_feed_class_init (GDataFeedClass *klass)
 				g_param_spec_boxed ("updated",
 					"Updated", "The time the feed was last updated.",
 					GDATA_TYPE_G_TIME_VAL,
-					G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+					G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
 
 	/**
 	 * GDataFeed:logo:
@@ -175,7 +180,7 @@ gdata_feed_class_init (GDataFeedClass *klass)
 				g_param_spec_string ("logo",
 					"Logo", "The URI of a logo for the feed.",
 					NULL,
-					G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+					G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
 
 	/**
 	 * GDataFeed:generator:
@@ -188,7 +193,7 @@ gdata_feed_class_init (GDataFeedClass *klass)
 	g_object_class_install_property (gobject_class, PROP_GENERATOR,
 				g_param_spec_pointer ("generator",
 					"Generator", "Details of the software used to generate the feed.",
-					G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+					G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
 
 	/**
 	 * GDataFeed:items-per-page:
@@ -202,7 +207,7 @@ gdata_feed_class_init (GDataFeedClass *klass)
 				g_param_spec_uint ("items-per-page",
 					"Items per page", "The number of items per results page feed.",
 					0, G_MAXINT, 0,
-					G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+					G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
 
 	/**
 	 * GDataFeed:start-index:
@@ -219,7 +224,7 @@ gdata_feed_class_init (GDataFeedClass *klass)
 				g_param_spec_uint ("start-index",
 					"Start index", "The one-based index of the first item in the results feed.",
 					1, G_MAXINT, 1,
-					G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+					G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
 
 	/**
 	 * GDataFeed:total-results:
@@ -236,7 +241,7 @@ gdata_feed_class_init (GDataFeedClass *klass)
 				g_param_spec_uint ("total-results",
 					"Total results", "The total number of results in the feed.",
 					0, 1000000, 0,
-					G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+					G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
 }
 
 static void
@@ -256,7 +261,6 @@ gdata_feed_dispose (GObject *object)
 	}
 	priv->entries = NULL;
 
-
 	/* Chain up to the parent class */
 	G_OBJECT_CLASS (gdata_feed_parent_class)->dispose (object);
 }
@@ -266,21 +270,19 @@ gdata_feed_finalize (GObject *object)
 {
 	GDataFeedPrivate *priv = GDATA_FEED_GET_PRIVATE (object);
 
-	g_free (priv->title);
-	g_free (priv->subtitle);
-	g_free (priv->id);
-	g_free (priv->etag);
+	xmlFree (priv->title);
+	xmlFree (priv->subtitle);
+	xmlFree (priv->id);
+	xmlFree (priv->etag);
 	g_list_foreach (priv->categories, (GFunc) gdata_category_free, NULL);
 	g_list_free (priv->categories);
-	g_free (priv->logo);
+	xmlFree (priv->logo);
 	g_list_foreach (priv->links, (GFunc) gdata_link_free, NULL);
 	g_list_free (priv->links);
 	g_list_foreach (priv->authors, (GFunc) gdata_author_free, NULL);
 	g_list_free (priv->authors);
 	gdata_generator_free (priv->generator);
 
-	g_free (priv->extra_xml);
-
 	/* Chain up to the parent class */
 	G_OBJECT_CLASS (gdata_feed_parent_class)->finalize (object);
 }
@@ -328,49 +330,24 @@ gdata_feed_get_property (GObject *object, guint property_id, GValue *value, GPar
 	}
 }
 
-static void
-gdata_feed_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec)
+typedef struct {
+	GType entry_type;
+	GDataQueryProgressCallback progress_callback;
+	gpointer progress_user_data;
+	guint entry_i;
+} ParseData;
+
+static gboolean
+pre_parse_xml (GDataParsable *parsable, xmlDoc *doc, xmlNode *root_node, gpointer user_data, GError **error)
 {
-	GDataFeedPrivate *priv = GDATA_FEED_GET_PRIVATE (object);
-	GTimeVal *timeval;
+	g_return_val_if_fail (GDATA_IS_FEED (parsable), FALSE);
+	g_return_val_if_fail (doc != NULL, FALSE);
+	g_return_val_if_fail (root_node != NULL, FALSE);
 
-	switch (property_id) {
-		case PROP_TITLE:
-			priv->title = g_value_dup_string (value);
-			break;
-		case PROP_SUBTITLE:
-			priv->subtitle = g_value_dup_string (value);
-			break;
-		case PROP_ID:
-			priv->id = g_value_dup_string (value);
-			break;
-		case PROP_ETAG:
-			priv->etag = g_value_dup_string (value);
-			break;
-		case PROP_UPDATED:
-			timeval = g_value_get_boxed (value);
-			priv->updated = *timeval;
-			break;
-		case PROP_LOGO:
-			priv->logo = g_value_dup_string (value);
-			break;
-		case PROP_GENERATOR:
-			priv->generator = g_value_get_pointer (value);
-			break;
-		case PROP_ITEMS_PER_PAGE:
-			priv->items_per_page = g_value_get_uint (value);
-			break;
-		case PROP_START_INDEX:
-			priv->start_index = g_value_get_uint (value);
-			break;
-		case PROP_TOTAL_RESULTS:
-			priv->total_results = g_value_get_uint (value);
-			break;
-		default:
-			/* We don't have any other property... */
-			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
-			break;
-	}
+	/* Extract the ETag */
+	GDATA_FEED (parsable)->priv->etag = (gchar*) xmlGetProp (root_node, (xmlChar*) "etag");
+
+	return TRUE;
 }
 
 typedef struct {
@@ -390,364 +367,269 @@ progress_callback_idle (ProgressCallbackData *data)
 	return FALSE;
 }
 
-GDataFeed *
-_gdata_feed_new_from_xml (const gchar *xml, gint length, GType entry_type,
-			  GDataQueryProgressCallback progress_callback, gpointer progress_user_data, GError **error)
+static gboolean
+parse_xml (GDataParsable *parsable, xmlDoc *doc, xmlNode *node, gpointer user_data, GError **error)
 {
-	GDataFeed *feed = NULL;
-	xmlDoc *doc;
-	xmlNode *node;
-	xmlChar *title = NULL, *subtitle = NULL, *id = NULL, *logo = NULL, *etag = NULL;
-	GTimeVal updated = { 0, };
-	GDataGenerator *generator = NULL;
-	guint entry_i = 0, total_results = 0, start_index = 0, items_per_page = 0;
-	GList *entries = NULL, *categories = NULL, *links = NULL, *authors = NULL;
-	GString *extra_xml;
-
-	g_return_val_if_fail (xml != NULL, NULL);
-
-	if (length == -1)
-		length = strlen (xml);
-
-	/* Parse the XML */
-	doc = xmlReadMemory (xml, length, "feed.xml", NULL, 0);
-	if (doc == NULL) {
-		xmlError *xml_error = xmlGetLastError ();
-		g_set_error (error, GDATA_PARSER_ERROR, GDATA_PARSER_ERROR_PARSING_STRING,
-			     _("Error parsing XML: %s"),
-			     xml_error->message);
-		return NULL;
-	}
-
-	/* Get the root element */
-	node = xmlDocGetRootElement (doc);
-	if (node == NULL) {
-		/* XML document's empty */
-		xmlFreeDoc (doc);
-		g_set_error (error, GDATA_PARSER_ERROR, GDATA_PARSER_ERROR_EMPTY_DOCUMENT,
-			     _("Error parsing XML: %s"),
-			     _("Empty document."));
-		return NULL;
-	}
-
-	if (xmlStrcmp (node->name, (xmlChar*) "feed") != 0) {
-		/* No <feed> element (required) */
-		xmlFreeDoc (doc);
-		gdata_parser_error_required_element_missing ("feed", "root", error);
-		return NULL;
-	}
-
-	/* Get the ETag first */
-	etag = xmlGetProp (node, (xmlChar*) "etag");
-
-	extra_xml = g_string_new ("");
-	node = node->children;
-	while (node != NULL) {
-		if (xmlStrcmp (node->name, (xmlChar*) "entry") == 0) {
-			/* atom:entry */
-			GDataEntry *entry = _gdata_entry_new_from_xml_node (entry_type, doc, node, error);
-			if (entry == NULL)
-				goto error;
-
-			/* Call the progress callback in the main thread */
-			if (progress_callback != NULL) {
-				ProgressCallbackData *data;
-
-				/* Build the data for the callback */
-				data = g_slice_new (ProgressCallbackData);
-				data->progress_callback = progress_callback;
-				data->progress_user_data = progress_user_data;
-				data->entry = g_object_ref (entry);
-				data->entry_i = entry_i;
-				data->total_results = MIN (items_per_page, total_results);
-
-				/* Send the callback; use G_PRIORITY_DEFAULT rather than G_PRIORITY_DEFAULT_IDLE
-				 * to contend with the priorities used by the callback functions in GAsyncResult */
-				g_idle_add_full (G_PRIORITY_DEFAULT, (GSourceFunc) progress_callback_idle, data, NULL);
-			}
-
-			entry_i++;
-			entries = g_list_prepend (entries, entry);
-		} else if (xmlStrcmp (node->name, (xmlChar*) "title") == 0) {
-			/* atom:title */
-			if (title != NULL) {
-				gdata_parser_error_duplicate_element ("title", "feed", error);
-				goto error;
-			}
-
-			title = xmlNodeListGetString (doc, node->children, TRUE);
-		} else if (xmlStrcmp (node->name, (xmlChar*) "subtitle") == 0) {
-			/* atom:subtitle */
-			if (subtitle != NULL) {
-				gdata_parser_error_duplicate_element ("subtitle", "feed", error);
-				goto error;
-			}
-
-			subtitle = xmlNodeListGetString (doc, node->children, TRUE);
-		} else if (xmlStrcmp (node->name, (xmlChar*) "id") == 0) {
-			/* atom:id */
-			if (id != NULL) {
-				gdata_parser_error_duplicate_element ("id", "feed", error);
-				goto error;
-			}
-
-			id = xmlNodeListGetString (doc, node->children, TRUE);
-		} else if (xmlStrcmp (node->name, (xmlChar*) "updated") == 0) {
-			/* atom:updated */
-			xmlChar *updated_string;
-
-			/* Duplicate checking */
-			if (updated.tv_sec != 0 || updated.tv_usec != 0) {
-				gdata_parser_error_duplicate_element ("updated", "feed", error);
-				goto error;
-			}
-
-			/* Parse the string */
-			updated_string = xmlNodeListGetString (doc, node->children, TRUE);
-			if (g_time_val_from_iso8601 ((gchar*) updated_string, &updated) == FALSE) {
-				gdata_parser_error_not_iso8601_format ("updated", "feed", (gchar*) updated_string, error);
-				xmlFree (updated_string);
-				goto error;
-			}
+	GDataFeed *self;
+	ParseData *data = user_data;
+
+	g_return_val_if_fail (GDATA_IS_FEED (parsable), FALSE);
+	g_return_val_if_fail (doc != NULL, FALSE);
+	g_return_val_if_fail (node != NULL, FALSE);
+
+	self = GDATA_FEED (parsable);
+
+	if (xmlStrcmp (node->name, (xmlChar*) "entry") == 0) {
+		/* atom:entry */
+		GDataEntry *entry = GDATA_ENTRY (_gdata_parsable_new_from_xml_node (data->entry_type, "entry", doc, node, NULL, error));
+		if (entry == NULL)
+			return FALSE;
+
+		/* Call the progress callback in the main thread */
+		if (data->progress_callback != NULL) {
+			ProgressCallbackData *progress_data;
+
+			/* Build the data for the callback */
+			progress_data = g_slice_new (ProgressCallbackData);
+			progress_data->progress_callback = data->progress_callback;
+			progress_data->progress_user_data = data->progress_user_data;
+			progress_data->entry = g_object_ref (entry);
+			progress_data->entry_i = data->entry_i;
+			progress_data->total_results = MIN (self->priv->items_per_page, self->priv->total_results);
+
+			/* Send the callback; use G_PRIORITY_DEFAULT rather than G_PRIORITY_DEFAULT_IDLE
+			 * to contend with the priorities used by the callback functions in GAsyncResult */
+			g_idle_add_full (G_PRIORITY_DEFAULT, (GSourceFunc) progress_callback_idle, progress_data, NULL);
+		}
 
+		data->entry_i++;
+		self->priv->entries = g_list_prepend (self->priv->entries, entry);
+	} else if (xmlStrcmp (node->name, (xmlChar*) "title") == 0) {
+		/* atom:title */
+		if (self->priv->title != NULL)
+			return gdata_parser_error_duplicate_element ("title", "feed", error);
+
+		self->priv->title = (gchar*) xmlNodeListGetString (doc, node->children, TRUE);
+	} else if (xmlStrcmp (node->name, (xmlChar*) "subtitle") == 0) {
+		/* atom:subtitle */
+		if (self->priv->subtitle != NULL)
+			return gdata_parser_error_duplicate_element ("subtitle", "feed", error);
+
+		self->priv->subtitle = (gchar*) xmlNodeListGetString (doc, node->children, TRUE);
+	} else if (xmlStrcmp (node->name, (xmlChar*) "id") == 0) {
+		/* atom:id */
+		if (self->priv->id != NULL)
+			return gdata_parser_error_duplicate_element ("id", "feed", error);
+
+		self->priv->id = (gchar*) xmlNodeListGetString (doc, node->children, TRUE);
+	} else if (xmlStrcmp (node->name, (xmlChar*) "updated") == 0) {
+		/* atom:updated */
+		xmlChar *updated_string;
+
+		/* Duplicate checking */
+		if (self->priv->updated.tv_sec != 0 || self->priv->updated.tv_usec != 0)
+			return gdata_parser_error_duplicate_element ("updated", "feed", error);
+
+		/* Parse the string */
+		updated_string = xmlNodeListGetString (doc, node->children, TRUE);
+		if (g_time_val_from_iso8601 ((gchar*) updated_string, &(self->priv->updated)) == FALSE) {
+			gdata_parser_error_not_iso8601_format ("updated", "feed", (gchar*) updated_string, error);
 			xmlFree (updated_string);
-		} else if (xmlStrcmp (node->name, (xmlChar*) "category") == 0) {
-			/* atom:category */
-			xmlChar *scheme, *term, *label;
-			GDataCategory *category;
-
-			scheme = xmlGetProp (node, (xmlChar*) "scheme");
-			term = xmlGetProp (node, (xmlChar*) "term");
-			label = xmlGetProp (node, (xmlChar*) "label");
-
-			category = gdata_category_new ((gchar*) term, (gchar*) scheme, (gchar*) label);
-			categories = g_list_prepend (categories, category);
-
-			xmlFree (scheme);
-			xmlFree (term);
-			xmlFree (label);
-		} else if (xmlStrcmp (node->name, (xmlChar*) "logo") == 0) {
-			/* atom:logo */
-			if (logo != NULL) {
-				gdata_parser_error_duplicate_element ("logo", "feed", error);
-				goto error;
-			}
-
-			logo = xmlNodeListGetString (doc, node->children, TRUE);
-		} else if (xmlStrcmp (node->name, (xmlChar*) "link") == 0) {
-			/* atom:link */
-			xmlChar *href, *rel, *type, *hreflang, *link_title, *link_length;
-			gint length_int;
-			GDataLink *link;
-
-			href = xmlGetProp (node, (xmlChar*) "href");
-			rel = xmlGetProp (node, (xmlChar*) "rel");
-			type = xmlGetProp (node, (xmlChar*) "type");
-			hreflang = xmlGetProp (node, (xmlChar*) "hreflang");
-			link_title = xmlGetProp (node, (xmlChar*) "title");
-			link_length = xmlGetProp (node, (xmlChar*) "length");
-
-			if (link_length == NULL)
-				length_int = -1;
-			else
-				length_int = strtoul ((gchar*) link_length, NULL, 10);
-
-			link = gdata_link_new ((gchar*) href, (gchar*) rel, (gchar*) type, (gchar*) hreflang, (gchar*) link_title, length_int);
-			links = g_list_prepend (links, link);
-
-			xmlFree (href);
-			xmlFree (rel);
-			xmlFree (type);
-			xmlFree (hreflang);
-			xmlFree (link_title);
-			xmlFree (link_length);
-		} else if (xmlStrcmp (node->name, (xmlChar*) "author") == 0) {
-			/* atom:author */
-			GDataAuthor *author;
-			xmlNode *author_node;
-			xmlChar *name = NULL, *uri = NULL, *email = NULL;
-
-			author_node = node->children;
-			while (author_node != NULL) {
-				if (xmlStrcmp (author_node->name, (xmlChar*) "name") == 0) {
-					name = xmlNodeListGetString (doc, author_node->children, TRUE);
-				} else if (xmlStrcmp (author_node->name, (xmlChar*) "uri") == 0) {
-					uri = xmlNodeListGetString (doc, author_node->children, TRUE);
-				} else if (xmlStrcmp (author_node->name, (xmlChar*) "email") == 0) {
-					email = xmlNodeListGetString (doc, author_node->children, TRUE);
-				} else {
-					gdata_parser_error_unhandled_element ((gchar*) author_node->ns->prefix, (gchar*) author_node->name, "author", error);
-					xmlFree (name);
-					xmlFree (uri);
-					xmlFree (email);
-					goto error;
-				}
-
-				author_node = author_node->next;
-			}
-
-			author = gdata_author_new ((gchar*) name, (gchar*) uri, (gchar*) email);
-			authors = g_list_prepend (authors, author);
-
-			xmlFree (name);
-			xmlFree (uri);
-			xmlFree (email);
-		} else if (xmlStrcmp (node->name, (xmlChar*) "generator") == 0) {
-			/* atom:generator */
-			xmlChar *name, *uri, *version;
-
-			/* Duplicate checking */
-			if (generator != NULL) {
-				gdata_parser_error_duplicate_element ("generator", "feed", error);
-				goto error;
-			}
-
-			/* Parse the element's parameters */
-			name = xmlNodeListGetString (doc, node->children, TRUE);
-			uri = xmlGetProp (node, (xmlChar*) "uri");
-			version = xmlGetProp (node, (xmlChar*) "version");
-
-			generator = gdata_generator_new ((gchar*) name, (gchar*) uri, (gchar*) version);
-
-			xmlFree (name);
-			xmlFree (uri);
-			xmlFree (version);
-		} else if (xmlStrcmp (node->name, (xmlChar*) "totalResults") == 0) {
-			/* openSearch:totalResults */
-			xmlChar *total_results_string;
+			return FALSE;
+		}
 
-			/* Duplicate checking */
-			if (total_results != 0) {
-				gdata_parser_error_duplicate_element ("totalResults", "feed", error);
-				goto error;
+		xmlFree (updated_string);
+	} else if (xmlStrcmp (node->name, (xmlChar*) "category") == 0) {
+		/* atom:category */
+		xmlChar *scheme, *term, *label;
+		GDataCategory *category;
+
+		scheme = xmlGetProp (node, (xmlChar*) "scheme");
+		term = xmlGetProp (node, (xmlChar*) "term");
+		label = xmlGetProp (node, (xmlChar*) "label");
+
+		category = gdata_category_new ((gchar*) term, (gchar*) scheme, (gchar*) label);
+		self->priv->categories = g_list_prepend (self->priv->categories, category);
+
+		xmlFree (scheme);
+		xmlFree (term);
+		xmlFree (label);
+	} else if (xmlStrcmp (node->name, (xmlChar*) "logo") == 0) {
+		/* atom:logo */
+		if (self->priv->logo != NULL)
+			return gdata_parser_error_duplicate_element ("logo", "feed", error);
+
+		self->priv->logo = (gchar*) xmlNodeListGetString (doc, node->children, TRUE);
+	} else if (xmlStrcmp (node->name, (xmlChar*) "link") == 0) {
+		/* atom:link */
+		xmlChar *href, *rel, *type, *hreflang, *link_title, *link_length;
+		gint length_int;
+		GDataLink *link;
+
+		href = xmlGetProp (node, (xmlChar*) "href");
+		rel = xmlGetProp (node, (xmlChar*) "rel");
+		type = xmlGetProp (node, (xmlChar*) "type");
+		hreflang = xmlGetProp (node, (xmlChar*) "hreflang");
+		link_title = xmlGetProp (node, (xmlChar*) "title");
+		link_length = xmlGetProp (node, (xmlChar*) "length");
+
+		if (link_length == NULL)
+			length_int = -1;
+		else
+			length_int = strtoul ((gchar*) link_length, NULL, 10);
+
+		link = gdata_link_new ((gchar*) href, (gchar*) rel, (gchar*) type, (gchar*) hreflang, (gchar*) link_title, length_int);
+		self->priv->links = g_list_prepend (self->priv->links, link);
+
+		xmlFree (href);
+		xmlFree (rel);
+		xmlFree (type);
+		xmlFree (hreflang);
+		xmlFree (link_title);
+		xmlFree (link_length);
+	} else if (xmlStrcmp (node->name, (xmlChar*) "author") == 0) {
+		/* atom:author */
+		GDataAuthor *author;
+		xmlNode *author_node;
+		xmlChar *name = NULL, *uri = NULL, *email = NULL;
+
+		author_node = node->children;
+		while (author_node != NULL) {
+			if (xmlStrcmp (author_node->name, (xmlChar*) "name") == 0) {
+				name = xmlNodeListGetString (doc, author_node->children, TRUE);
+			} else if (xmlStrcmp (author_node->name, (xmlChar*) "uri") == 0) {
+				uri = xmlNodeListGetString (doc, author_node->children, TRUE);
+			} else if (xmlStrcmp (author_node->name, (xmlChar*) "email") == 0) {
+				email = xmlNodeListGetString (doc, author_node->children, TRUE);
+			} else {
+				gdata_parser_error_unhandled_element ((gchar*) author_node->ns->prefix, (gchar*) author_node->name, "author", error);
+				xmlFree (name);
+				xmlFree (uri);
+				xmlFree (email);
+				return FALSE;
 			}
 
-			/* Parse the number */
-			total_results_string = xmlNodeListGetString (doc, node->children, TRUE);
-			if (total_results_string == NULL) {
-				gdata_parser_error_required_content_missing ("openSearch:totalResults", error);
-				goto error;
-			}
+			author_node = author_node->next;
+		}
 
-			total_results = strtoul ((gchar*) total_results_string, NULL, 10);
-			xmlFree (total_results_string);
-		} else if (xmlStrcmp (node->name, (xmlChar*) "startIndex") == 0) {
-			/* openSearch:startIndex */
-			xmlChar *start_index_string;
+		author = gdata_author_new ((gchar*) name, (gchar*) uri, (gchar*) email);
+		self->priv->authors = g_list_prepend (self->priv->authors, author);
+
+		xmlFree (name);
+		xmlFree (uri);
+		xmlFree (email);
+	} else if (xmlStrcmp (node->name, (xmlChar*) "generator") == 0) {
+		/* atom:generator */
+		xmlChar *name, *uri, *version;
+
+		/* Duplicate checking */
+		if (self->priv->generator != NULL)
+			return gdata_parser_error_duplicate_element ("generator", "feed", error);
+
+		/* Parse the element's parameters */
+		name = xmlNodeListGetString (doc, node->children, TRUE);
+		uri = xmlGetProp (node, (xmlChar*) "uri");
+		version = xmlGetProp (node, (xmlChar*) "version");
+
+		self->priv->generator = gdata_generator_new ((gchar*) name, (gchar*) uri, (gchar*) version);
+
+		xmlFree (name);
+		xmlFree (uri);
+		xmlFree (version);
+	} else if (xmlStrcmp (node->name, (xmlChar*) "totalResults") == 0) {
+		/* openSearch:totalResults */
+		xmlChar *total_results_string;
+
+		/* Duplicate checking */
+		if (self->priv->total_results != 0)
+			return gdata_parser_error_duplicate_element ("totalResults", "feed", error);
+
+		/* Parse the number */
+		total_results_string = xmlNodeListGetString (doc, node->children, TRUE);
+		if (total_results_string == NULL)
+			return gdata_parser_error_required_content_missing ("openSearch:totalResults", error);
+
+		self->priv->total_results = strtoul ((gchar*) total_results_string, NULL, 10);
+		xmlFree (total_results_string);
+	} else if (xmlStrcmp (node->name, (xmlChar*) "startIndex") == 0) {
+		/* openSearch:startIndex */
+		xmlChar *start_index_string;
+
+		/* Duplicate checking */
+		if (self->priv->start_index != 0)
+			return gdata_parser_error_duplicate_element ("startIndex", "feed", error);
+
+		/* Parse the number */
+		start_index_string = xmlNodeListGetString (doc, node->children, TRUE);
+		if (start_index_string == NULL)
+			return gdata_parser_error_required_content_missing ("openSearch:startIndex", error);
+
+		self->priv->start_index = strtoul ((gchar*) start_index_string, NULL, 10);
+		xmlFree (start_index_string);
+	} else if (xmlStrcmp (node->name, (xmlChar*) "itemsPerPage") == 0) {
+		/* openSearch:itemsPerPage */
+		xmlChar *items_per_page_string;
+
+		/* Duplicate checking */
+		if (self->priv->items_per_page != 0)
+			return gdata_parser_error_duplicate_element ("itemsPerPage", "feed", error);
+
+		/* Parse the number */
+		items_per_page_string = xmlNodeListGetString (doc, node->children, TRUE);
+		if (items_per_page_string == NULL)
+			return gdata_parser_error_required_content_missing ("openSearch:itemsPerPage", error);
+
+		self->priv->items_per_page = strtoul ((gchar*) items_per_page_string, NULL, 10);
+		xmlFree (items_per_page_string);
+	} else if (GDATA_PARSABLE_CLASS (gdata_feed_parent_class)->parse_xml (parsable, doc, node, user_data, error) == FALSE) {
+		/* Error! */
+		return FALSE;
+	}
 
-			/* Duplicate checking */
-			if (start_index != 0) {
-				gdata_parser_error_duplicate_element ("startIndex", "feed", error);
-				goto error;
-			}
+	return TRUE;
+}
 
-			/* Parse the number */
+static gboolean
+post_parse_xml (GDataParsable *parsable, gpointer user_data, GError **error)
+{
+	GDataFeedPrivate *priv = GDATA_FEED (parsable)->priv;
 
-			start_index_string = xmlNodeListGetString (doc, node->children, TRUE);
-			if (start_index_string == NULL) {
-				gdata_parser_error_required_content_missing ("openSearch:startIndex", error);
-				goto error;
-			}
+	/* Check for missing required elements */
+	if (priv->title == NULL)
+		return gdata_parser_error_required_element_missing ("title", "feed", error);
+	if (priv->id == NULL)
+		return gdata_parser_error_required_element_missing ("id", "feed", error);
+	if (priv->updated.tv_sec == 0 && priv->updated.tv_usec == 0)
+		return gdata_parser_error_required_element_missing ("updated", "feed", error);
 
-			start_index = strtoul ((gchar*) start_index_string, NULL, 10);
-			xmlFree (start_index_string);
-		} else if (xmlStrcmp (node->name, (xmlChar*) "itemsPerPage") == 0) {
-			/* openSearch:itemsPerPage */
-			xmlChar *items_per_page_string;
+	/* Reverse our lists of stuff */
+	priv->entries = g_list_reverse (priv->entries);
+	priv->categories = g_list_reverse (priv->categories);
+	priv->links = g_list_reverse (priv->links);
+	priv->authors = g_list_reverse (priv->authors);
 
-			/* Duplicate checking */
-			if (items_per_page != 0) {
-				gdata_parser_error_duplicate_element ("itemsPerPage", "feed", error);
-				goto error;
-			}
+	return TRUE;
+}
 
-			/* Parse the number */
-			items_per_page_string = xmlNodeListGetString (doc, node->children, TRUE);
-			if (items_per_page_string == NULL) {
-				gdata_parser_error_required_content_missing ("openSearch:itemsPerPage", error);
-				goto error;
-			}
+GDataFeed *
+_gdata_feed_new_from_xml (const gchar *xml, gint length, GType entry_type,
+			  GDataQueryProgressCallback progress_callback, gpointer progress_user_data, GError **error)
+{
+	ParseData *data;
+	GDataFeed *feed;
 
-			items_per_page = strtoul ((gchar*) items_per_page_string, NULL, 10);
-			xmlFree (items_per_page_string);
-		} else {
-			/* Unhandled XML */
-			xmlBuffer *buffer = xmlBufferCreate ();
-			xmlNodeDump (buffer, doc, node, 0, 0);
-			g_string_append (extra_xml, (gchar*) xmlBufferContent (buffer));
-			g_message ("Unhandled XML in <feed>: %s", (gchar*) xmlBufferContent (buffer));
-			xmlBufferFree (buffer);
-		}
+	g_return_val_if_fail (xml != NULL, NULL);
+	g_return_val_if_fail (g_type_is_a (entry_type, GDATA_TYPE_ENTRY) == TRUE, FALSE);
 
-		node = node->next;
-	}
+	data = g_slice_new (ParseData);
+	data->entry_type = entry_type;
+	data->progress_callback = progress_callback;
+	data->progress_user_data = progress_user_data;
+	data->entry_i = 0;
 
-	/* Check for missing required elements */
-	if (title == NULL) {
-		gdata_parser_error_required_element_missing ("title", "feed", error);
-		goto error;
-	}
-	if (id == NULL) {
-		gdata_parser_error_required_element_missing ("id", "feed", error);
-		goto error;
-	}
-	if (updated.tv_sec == 0 && updated.tv_usec == 0) {
-		gdata_parser_error_required_element_missing ("updated", "feed", error);
-		goto error;
-	}
+	feed = GDATA_FEED (_gdata_parsable_new_from_xml (GDATA_TYPE_FEED, "feed", xml, length, data, error));
 
-	/* Reverse our lists of stuff */
-	entries = g_list_reverse (entries);
-	categories = g_list_reverse (categories);
-	links = g_list_reverse (links);
-	authors = g_list_reverse (authors);
-
-	/* Create the feed */
-	feed = g_object_new (GDATA_TYPE_FEED,
-			     "title", (gchar*) title,
-			     "subtitle", (gchar*) subtitle,
-			     "id", (gchar*) id,
-			     "etag", (gchar*) etag,
-			     "updated", &updated,
-			     "logo", (gchar*) logo,
-			     "generator", generator,
-			     "total-results", total_results,
-			     "start-index", start_index,
-			     "items-per-page", items_per_page,
-			     NULL);
-
-	feed->priv->entries = entries;
-	feed->priv->categories = categories;
-	feed->priv->links = links;
-	feed->priv->authors = authors;
-
-	/* Set the lists and generator to NULL so they aren't freed --- the memory belongs to the GDataFeed now */
-	entries = categories = links = authors = NULL;
-	generator = NULL;
-
-error:
-	xmlFree (title);
-	xmlFree (subtitle);
-	xmlFree (id);
-	xmlFree (etag);
-	xmlFree (logo);
-	gdata_generator_free (generator);
-	g_list_foreach (entries, (GFunc) g_object_unref, NULL);
-	g_list_free (entries);
-	g_list_foreach (categories, (GFunc) gdata_category_free, NULL);
-	g_list_free (categories);
-	g_list_foreach (links, (GFunc) gdata_link_free, NULL);
-	g_list_free (links);
-	g_list_foreach (authors, (GFunc) gdata_author_free, NULL);
-	g_list_free (authors);
-
-	/* Store any unhandled XML for future use */
-	if (feed != NULL)
-		feed->priv->extra_xml = g_string_free (extra_xml, FALSE);
-	else
-		g_string_free (extra_xml, TRUE);
-
-	xmlFreeDoc (doc);
+	g_slice_free (ParseData, data);
 
 	return feed;
 }
diff --git a/gdata/gdata-feed.h b/gdata/gdata-feed.h
index caa2c8f..3eae443 100644
--- a/gdata/gdata-feed.h
+++ b/gdata/gdata-feed.h
@@ -25,6 +25,7 @@
 
 #include <gdata/gdata-entry.h>
 #include <gdata/gdata-atom.h>
+#include <gdata/gdata-parsable.h>
 
 G_BEGIN_DECLS
 
@@ -44,7 +45,7 @@ typedef struct _GDataFeedPrivate	GDataFeedPrivate;
  **/
 typedef struct {
 	/*< private >*/
-	GObject parent;
+	GDataParsable parent;
 	GDataFeedPrivate *priv;
 } GDataFeed;
 
@@ -55,7 +56,7 @@ typedef struct {
  **/
 typedef struct {
 	/*< private >*/
-	GObjectClass parent;
+	GDataParsableClass parent;
 } GDataFeedClass;
 
 GType gdata_feed_get_type (void) G_GNUC_CONST;
diff --git a/gdata/gdata-parsable.c b/gdata/gdata-parsable.c
new file mode 100644
index 0000000..7d6ac47
--- /dev/null
+++ b/gdata/gdata-parsable.c
@@ -0,0 +1,210 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/*
+ * GData Client
+ * Copyright (C) Philip Withnall 2009 <philip tecnocode co uk>
+ * 
+ * GData Client is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GData Client is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GData Client.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * SECTION:gdata-parsable
+ * @short_description: GData parsable object
+ * @stability: Unstable
+ * @include: gdata/gdata-parsable.h
+ *
+ * #GDataParsable is an abstract class allowing easy implementation of an extensible parser. It is primarily extended by #GDataFeed and #GDataEntry,
+ * both of which require XML parsing which can be extended by subclassing.
+ *
+ * It allows methods to be defined for handling the root XML node, each of its child nodes, and a method to be called after parsing is complete.
+ **/
+
+#include <config.h>
+#include <glib.h>
+#include <glib/gi18n-lib.h>
+#include <string.h>
+#include <libxml/parser.h>
+
+#include "gdata-parsable.h"
+#include "gdata-private.h"
+#include "gdata-parser.h"
+
+static void gdata_parsable_finalize (GObject *object);
+static gboolean real_parse_xml (GDataParsable *parsable, xmlDoc *doc, xmlNode *node, gpointer user_data, GError **error);
+
+struct _GDataParsablePrivate {
+	GString *extra_xml;
+	GHashTable *extra_namespaces;
+};
+
+G_DEFINE_ABSTRACT_TYPE (GDataParsable, gdata_parsable, G_TYPE_OBJECT)
+#define GDATA_PARSABLE_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GDATA_TYPE_PARSABLE, GDataParsablePrivate))
+
+static void
+gdata_parsable_class_init (GDataParsableClass *klass)
+{
+	GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+	g_type_class_add_private (klass, sizeof (GDataParsablePrivate));
+	gobject_class->finalize = gdata_parsable_finalize;
+	klass->parse_xml = real_parse_xml;
+}
+
+static void
+gdata_parsable_init (GDataParsable *self)
+{
+	self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GDATA_TYPE_PARSABLE, GDataParsablePrivate);
+	self->priv->extra_xml = g_string_new ("");
+	self->priv->extra_namespaces = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
+}
+
+static void
+gdata_parsable_finalize (GObject *object)
+{
+	GDataParsablePrivate *priv = GDATA_PARSABLE_GET_PRIVATE (object);
+
+	g_string_free (priv->extra_xml, TRUE);
+	g_hash_table_destroy (priv->extra_namespaces);
+
+	/* Chain up to the parent class */
+	G_OBJECT_CLASS (gdata_parsable_parent_class)->finalize (object);
+}
+
+static gboolean
+real_parse_xml (GDataParsable *parsable, xmlDoc *doc, xmlNode *node, gpointer user_data, GError **error)
+{
+	xmlBuffer *buffer;
+	xmlNs **namespaces, **namespace;
+
+	/* Unhandled XML */
+	buffer = xmlBufferCreate ();
+	xmlNodeDump (buffer, doc, node, 0, 0);
+	g_string_append (parsable->priv->extra_xml, (gchar*) xmlBufferContent (buffer));
+	g_message ("Unhandled XML in %s: %s", G_OBJECT_TYPE_NAME (parsable), (gchar*) xmlBufferContent (buffer));
+	xmlBufferFree (buffer);
+
+	/* Get the namespaces */
+	namespaces = xmlGetNsList (doc, node);
+	for (namespace = namespaces; *namespace != NULL; namespace++) {
+		if ((*namespace)->prefix != NULL) {
+			g_hash_table_insert (parsable->priv->extra_namespaces,
+					     g_strdup ((gchar*) ((*namespace)->prefix)),
+					     g_strdup ((gchar*) ((*namespace)->href)));
+		}
+	}
+	xmlFree (namespaces);
+
+	return TRUE;
+}
+
+GDataParsable *
+_gdata_parsable_new_from_xml (GType parsable_type, const gchar *first_element, const gchar *xml, gint length, gpointer user_data, GError **error)
+{
+	xmlDoc *doc;
+	xmlNode *node;
+
+	g_return_val_if_fail (g_type_is_a (parsable_type, GDATA_TYPE_PARSABLE) == TRUE, FALSE);
+	g_return_val_if_fail (first_element != NULL, NULL);
+	g_return_val_if_fail (xml != NULL, NULL);
+
+	if (length == -1)
+		length = strlen (xml);
+
+	/* Parse the XML */
+	doc = xmlReadMemory (xml, length, "/dev/null", NULL, 0);
+	if (doc == NULL) {
+		xmlError *xml_error = xmlGetLastError ();
+		g_set_error (error, GDATA_PARSER_ERROR, GDATA_PARSER_ERROR_PARSING_STRING,
+			     _("Error parsing XML: %s"),
+			     xml_error->message);
+		return NULL;
+	}
+
+	/* Get the root element */
+	node = xmlDocGetRootElement (doc);
+	if (node == NULL) {
+		/* XML document's empty */
+		xmlFreeDoc (doc);
+		g_set_error (error, GDATA_PARSER_ERROR, GDATA_PARSER_ERROR_EMPTY_DOCUMENT,
+			     _("Error parsing XML: %s"),
+			     _("Empty document."));
+		return NULL;
+	}
+
+	if (xmlStrcmp (node->name, (xmlChar*) first_element) != 0) {
+		/* No <entry> element (required) */
+		xmlFreeDoc (doc);
+		gdata_parser_error_required_element_missing (first_element, "root", error);
+		return NULL;
+	}
+
+	return _gdata_parsable_new_from_xml_node (parsable_type, first_element, doc, node, user_data, error);
+}
+
+GDataParsable *
+_gdata_parsable_new_from_xml_node (GType parsable_type, const gchar *first_element, xmlDoc *doc, xmlNode *node, gpointer user_data, GError **error)
+{
+	GDataParsable *parsable;
+	GDataParsableClass *klass;
+
+	g_return_val_if_fail (g_type_is_a (parsable_type, GDATA_TYPE_PARSABLE) == TRUE, FALSE);
+	g_return_val_if_fail (doc != NULL, FALSE);
+	g_return_val_if_fail (node != NULL, FALSE);
+	g_return_val_if_fail (xmlStrcmp (node->name, (xmlChar*) first_element) == 0, FALSE);
+
+	parsable = g_object_new (parsable_type, NULL);
+
+	klass = GDATA_PARSABLE_GET_CLASS (parsable);
+	if (klass->parse_xml == NULL)
+		return FALSE;
+
+	/* Call the pre-parse function first */
+	if (klass->pre_parse_xml != NULL &&
+	    klass->pre_parse_xml (parsable, doc, node, user_data, error) == FALSE) {
+		g_object_unref (parsable);
+		return NULL;
+	}	
+
+	/* Parse each child element */
+	node = node->children;
+	while (node != NULL) {
+		if (klass->parse_xml (parsable, doc, node, user_data, error) == FALSE) {
+			g_object_unref (parsable);
+			return NULL;
+		}
+		node = node->next;
+	}
+
+	/* Call the post-parse function */
+	if (klass->post_parse_xml != NULL &&
+	    klass->post_parse_xml (parsable, user_data, error) == FALSE) {
+		g_object_unref (parsable);
+		return NULL;
+	}
+
+	return parsable;
+}
+
+const gchar *
+_gdata_parsable_get_extra_xml (GDataParsable *self)
+{
+	g_return_val_if_fail (GDATA_IS_PARSABLE (self), NULL);
+	return self->priv->extra_xml->str;
+}
+
+GHashTable *
+_gdata_parsable_get_extra_namespaces (GDataParsable *self)
+{
+	g_return_val_if_fail (GDATA_IS_PARSABLE (self), NULL);
+	return self->priv->extra_namespaces;
+}
diff --git a/gdata/gdata-parsable.h b/gdata/gdata-parsable.h
new file mode 100644
index 0000000..20c742d
--- /dev/null
+++ b/gdata/gdata-parsable.h
@@ -0,0 +1,74 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/*
+ * GData Client
+ * Copyright (C) Philip Withnall 2009 <philip tecnocode co uk>
+ * 
+ * GData Client is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GData Client is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GData Client.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef GDATA_PARSABLE_H
+#define GDATA_PARSABLE_H
+
+#include <glib.h>
+#include <glib-object.h>
+#include <libxml/parser.h>
+
+G_BEGIN_DECLS
+
+#define GDATA_TYPE_PARSABLE		(gdata_parsable_get_type ())
+#define GDATA_PARSABLE(o)		(G_TYPE_CHECK_INSTANCE_CAST ((o), GDATA_TYPE_PARSABLE, GDataParsable))
+#define GDATA_PARSABLE_CLASS(k)		(G_TYPE_CHECK_CLASS_CAST((k), GDATA_TYPE_PARSABLE, GDataParsableClass))
+#define GDATA_IS_PARSABLE(o)		(G_TYPE_CHECK_INSTANCE_TYPE ((o), GDATA_TYPE_PARSABLE))
+#define GDATA_IS_PARSABLE_CLASS(k)	(G_TYPE_CHECK_CLASS_TYPE ((k), GDATA_TYPE_PARSABLE))
+#define GDATA_PARSABLE_GET_CLASS(o)	(G_TYPE_INSTANCE_GET_CLASS ((o), GDATA_TYPE_PARSABLE, GDataParsableClass))
+
+typedef struct _GDataParsablePrivate	GDataParsablePrivate;
+
+/**
+ * GDataParsable:
+ *
+ * All the fields in the #GDataParsable structure are private and should never be accessed directly.
+ *
+ * Since: 0.3.0
+ **/
+typedef struct {
+	GObject parent;
+	GDataParsablePrivate *priv;
+} GDataParsable;
+
+/**
+ * GDataParsableClass:
+ * @parent: the parent class
+ * @pre_parse_xml: a function called before parsing of an XML tree is started, which allows properties from the root node to be extracted
+ * and used in @parsable
+ * @parse_xml: a function to parse an XML representation of the #GDataParsable to set the properties of the @parsable
+ * @post_parse_xml: a function called after parsing an XML tree, to allow the @parsable to validate the parsed properties
+ *
+ * The class structure for the #GDataParsable class.
+ *
+ * Since: 0.3.0
+ **/
+typedef struct {
+	GObjectClass parent;
+
+	gboolean (*pre_parse_xml) (GDataParsable *parsable, xmlDoc *doc, xmlNode *root_node, gpointer user_data, GError **error);
+	gboolean (*parse_xml) (GDataParsable *parsable, xmlDoc *doc, xmlNode *node, gpointer user_data, GError **error);
+	gboolean (*post_parse_xml) (GDataParsable *parsable, gpointer user_data, GError **error);
+} GDataParsableClass;
+
+GType gdata_parsable_get_type (void);
+
+G_END_DECLS
+
+#endif /* !GDATA_PARSABLE_H */
diff --git a/gdata/gdata-private.h b/gdata/gdata-private.h
index 6bb994d..cc2b78a 100644
--- a/gdata/gdata-private.h
+++ b/gdata/gdata-private.h
@@ -36,13 +36,20 @@ guint _gdata_service_send_message (GDataService *self, SoupMessage *message, GEr
 void _gdata_query_set_next_uri (GDataQuery *self, const gchar *next_uri);
 void _gdata_query_set_previous_uri (GDataQuery *self, const gchar *previous_uri);
 
+#include "gdata-parsable.h"
+GDataParsable *_gdata_parsable_new_from_xml (GType parsable_type, const gchar *first_element, const gchar *xml, gint length, gpointer user_data,
+					     GError **error) G_GNUC_WARN_UNUSED_RESULT;
+GDataParsable *_gdata_parsable_new_from_xml_node (GType parsable_type, const gchar *first_element, xmlDoc *doc, xmlNode *node, gpointer user_data,
+						  GError **error) G_GNUC_WARN_UNUSED_RESULT;
+const gchar *_gdata_parsable_get_extra_xml (GDataParsable *self);
+GHashTable *_gdata_parsable_get_extra_namespaces (GDataParsable *self);
+
 #include "gdata-feed.h"
 GDataFeed *_gdata_feed_new_from_xml (const gchar *xml, gint length, GType entry_type,
 				     GDataQueryProgressCallback progress_callback, gpointer progress_user_data, GError **error) G_GNUC_WARN_UNUSED_RESULT;
 
 #include "gdata-entry.h"
 GDataEntry *_gdata_entry_new_from_xml (GType entry_type, const gchar *xml, gint length, GError **error) G_GNUC_WARN_UNUSED_RESULT;
-GDataEntry *_gdata_entry_new_from_xml_node (GType entry_type, xmlDoc *doc, xmlNode *node, GError **error) G_GNUC_WARN_UNUSED_RESULT;
 
 #include "gdata-parser.h"
 gboolean gdata_parser_error_required_content_missing (const gchar *element_name, GError **error);
diff --git a/gdata/gdata.h b/gdata/gdata.h
index b51d3c5..a0054a4 100644
--- a/gdata/gdata.h
+++ b/gdata/gdata.h
@@ -30,6 +30,7 @@
 #include <gdata/gdata-enums.h>
 #include <gdata/gdata-access-handler.h>
 #include <gdata/gdata-access-rule.h>
+#include <gdata/gdata-parsable.h>
 
 /* Namespaces */
 #include <gdata/gdata-atom.h>
diff --git a/gdata/gdata.symbols b/gdata/gdata.symbols
index 30ccb08..6d2a9a5 100644
--- a/gdata/gdata.symbols
+++ b/gdata/gdata.symbols
@@ -347,3 +347,4 @@ gdata_access_rule_get_role
 gdata_access_rule_set_role
 gdata_access_rule_get_scope
 gdata_access_rule_set_scope
+gdata_parsable_get_type
diff --git a/gdata/services/calendar/gdata-calendar-calendar.c b/gdata/services/calendar/gdata-calendar-calendar.c
index fefa1b4..2c9a28f 100644
--- a/gdata/services/calendar/gdata-calendar-calendar.c
+++ b/gdata/services/calendar/gdata-calendar-calendar.c
@@ -48,7 +48,7 @@ static void gdata_calendar_calendar_finalize (GObject *object);
 static void gdata_calendar_calendar_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec);
 static void gdata_calendar_calendar_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec);
 static void get_xml (GDataEntry *entry, GString *xml_string);
-static gboolean parse_xml (GDataEntry *entry, xmlDoc *doc, xmlNode *node, GError **error);
+static gboolean parse_xml (GDataParsable *parsable, xmlDoc *doc, xmlNode *node, gpointer user_data, GError **error);
 static void get_namespaces (GDataEntry *entry, GHashTable *namespaces);
 
 struct _GDataCalendarCalendarPrivate {
@@ -80,6 +80,7 @@ static void
 gdata_calendar_calendar_class_init (GDataCalendarCalendarClass *klass)
 {
 	GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+	GDataParsableClass *parsable_class = GDATA_PARSABLE_CLASS (klass);
 	GDataEntryClass *entry_class = GDATA_ENTRY_CLASS (klass);
 
 	g_type_class_add_private (klass, sizeof (GDataCalendarCalendarPrivate));
@@ -88,8 +89,9 @@ gdata_calendar_calendar_class_init (GDataCalendarCalendarClass *klass)
 	gobject_class->get_property = gdata_calendar_calendar_get_property;
 	gobject_class->finalize = gdata_calendar_calendar_finalize;
 
+	parsable_class->parse_xml = parse_xml;
+
 	entry_class->get_xml = get_xml;
-	entry_class->parse_xml = parse_xml;
 	entry_class->get_namespaces = get_namespaces;
 
 	/**
@@ -300,14 +302,16 @@ gdata_calendar_calendar_new_from_xml (const gchar *xml, gint length, GError **er
 }
 
 static gboolean
-parse_xml (GDataEntry *entry, xmlDoc *doc, xmlNode *node, GError **error)
+parse_xml (GDataParsable *parsable, xmlDoc *doc, xmlNode *node, gpointer user_data, GError **error)
 {
-	GDataCalendarCalendar *self = GDATA_CALENDAR_CALENDAR (entry);
+	GDataCalendarCalendar *self;
 
-	g_return_val_if_fail (GDATA_IS_CALENDAR_CALENDAR (self), FALSE);
+	g_return_val_if_fail (GDATA_IS_CALENDAR_CALENDAR (parsable), FALSE);
 	g_return_val_if_fail (doc != NULL, FALSE);
 	g_return_val_if_fail (node != NULL, FALSE);
 
+	self = GDATA_CALENDAR_CALENDAR (parsable);
+
 	if (xmlStrcmp (node->name, (xmlChar*) "timezone") == 0) {
 		/* gCal:timezone */
 		xmlChar *_timezone = xmlGetProp (node, (xmlChar*) "value");
@@ -377,7 +381,7 @@ parse_xml (GDataEntry *entry, xmlDoc *doc, xmlNode *node, GError **error)
 			return FALSE;
 		}
 		xmlFree (edited);
-	} else if (GDATA_ENTRY_CLASS (gdata_calendar_calendar_parent_class)->parse_xml (entry, doc, node, error) == FALSE) {
+	} else if (GDATA_PARSABLE_CLASS (gdata_calendar_calendar_parent_class)->parse_xml (parsable, doc, node, user_data, error) == FALSE) {
 		/* Error! */
 		return FALSE;
 	}
diff --git a/gdata/services/calendar/gdata-calendar-event.c b/gdata/services/calendar/gdata-calendar-event.c
index 653ed74..eb61e69 100644
--- a/gdata/services/calendar/gdata-calendar-event.c
+++ b/gdata/services/calendar/gdata-calendar-event.c
@@ -46,7 +46,7 @@ static void gdata_calendar_event_finalize (GObject *object);
 static void gdata_calendar_event_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec);
 static void gdata_calendar_event_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec);
 static void get_xml (GDataEntry *entry, GString *xml_string);
-static gboolean parse_xml (GDataEntry *entry, xmlDoc *doc, xmlNode *node, GError **error);
+static gboolean parse_xml (GDataParsable *parsable, xmlDoc *doc, xmlNode *node, gpointer user_data, GError **error);
 static void get_namespaces (GDataEntry *entry, GHashTable *namespaces);
 
 struct _GDataCalendarEventPrivate {
@@ -92,6 +92,7 @@ static void
 gdata_calendar_event_class_init (GDataCalendarEventClass *klass)
 {
 	GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+	GDataParsableClass *parsable_class = GDATA_PARSABLE_CLASS (klass);
 	GDataEntryClass *entry_class = GDATA_ENTRY_CLASS (klass);
 
 	g_type_class_add_private (klass, sizeof (GDataCalendarEventPrivate));
@@ -100,8 +101,9 @@ gdata_calendar_event_class_init (GDataCalendarEventClass *klass)
 	gobject_class->get_property = gdata_calendar_event_get_property;
 	gobject_class->finalize = gdata_calendar_event_finalize;
 
+	parsable_class->parse_xml = parse_xml;
+
 	entry_class->get_xml = get_xml;
-	entry_class->parse_xml = parse_xml;
 	entry_class->get_namespaces = get_namespaces;
 
 	/**
@@ -442,14 +444,16 @@ gdata_calendar_event_new_from_xml (const gchar *xml, gint length, GError **error
 }
 
 static gboolean
-parse_xml (GDataEntry *entry, xmlDoc *doc, xmlNode *node, GError **error)
+parse_xml (GDataParsable *parsable, xmlDoc *doc, xmlNode *node, gpointer user_data, GError **error)
 {
-	GDataCalendarEvent *self = GDATA_CALENDAR_EVENT (entry);
+	GDataCalendarEvent *self;
 
-	g_return_val_if_fail (GDATA_IS_CALENDAR_EVENT (self), FALSE);
+	g_return_val_if_fail (GDATA_IS_CALENDAR_EVENT (parsable), FALSE);
 	g_return_val_if_fail (doc != NULL, FALSE);
 	g_return_val_if_fail (node != NULL, FALSE);
 
+	self = GDATA_CALENDAR_EVENT (parsable);
+
 	if (xmlStrcmp (node->name, (xmlChar*) "edited") == 0) {
 		/* app:edited */
 		xmlChar *edited = xmlNodeListGetString (doc, node->children, TRUE);
@@ -645,7 +649,7 @@ parse_xml (GDataEntry *entry, xmlDoc *doc, xmlNode *node, GError **error)
 		self->priv->original_event_uri = (gchar*) xmlGetProp (node, (xmlChar*) "href");
 		g_object_notify (G_OBJECT (self), "original-event-uri");
 		g_object_thaw_notify (G_OBJECT (self));
-	} else if (GDATA_ENTRY_CLASS (gdata_calendar_event_parent_class)->parse_xml (entry, doc, node, error) == FALSE) {
+	} else if (GDATA_PARSABLE_CLASS (gdata_calendar_event_parent_class)->parse_xml (parsable, doc, node, user_data, error) == FALSE) {
 		/* Error! */
 		return FALSE;
 	}
diff --git a/gdata/services/contacts/gdata-contacts-contact.c b/gdata/services/contacts/gdata-contacts-contact.c
index d65b577..ee72486 100644
--- a/gdata/services/contacts/gdata-contacts-contact.c
+++ b/gdata/services/contacts/gdata-contacts-contact.c
@@ -50,7 +50,7 @@
 static void gdata_contacts_contact_finalize (GObject *object);
 static void gdata_contacts_contact_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec);
 static void get_xml (GDataEntry *entry, GString *xml_string);
-static gboolean parse_xml (GDataEntry *entry, xmlDoc *doc, xmlNode *node, GError **error);
+static gboolean parse_xml (GDataParsable *parsable, xmlDoc *doc, xmlNode *node, gpointer user_data, GError **error);
 static void get_namespaces (GDataEntry *entry, GHashTable *namespaces);
 
 struct _GDataContactsContactPrivate {
@@ -77,6 +77,7 @@ static void
 gdata_contacts_contact_class_init (GDataContactsContactClass *klass)
 {
 	GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+	GDataParsableClass *parsable_class = GDATA_PARSABLE_CLASS (klass);
 	GDataEntryClass *entry_class = GDATA_ENTRY_CLASS (klass);
 
 	g_type_class_add_private (klass, sizeof (GDataContactsContactPrivate));
@@ -84,8 +85,9 @@ gdata_contacts_contact_class_init (GDataContactsContactClass *klass)
 	gobject_class->get_property = gdata_contacts_contact_get_property;
 	gobject_class->finalize = gdata_contacts_contact_finalize;
 
+	parsable_class->parse_xml = parse_xml;
+
 	entry_class->get_xml = get_xml;
-	entry_class->parse_xml = parse_xml;
 	entry_class->get_namespaces = get_namespaces;
 
 	/**
@@ -205,14 +207,16 @@ gdata_contacts_contact_new_from_xml (const gchar *xml, gint length, GError **err
 }
 
 static gboolean
-parse_xml (GDataEntry *entry, xmlDoc *doc, xmlNode *node, GError **error)
+parse_xml (GDataParsable *parsable, xmlDoc *doc, xmlNode *node, gpointer user_data, GError **error)
 {
-	GDataContactsContact *self = GDATA_CONTACTS_CONTACT (entry);
+	GDataContactsContact *self;
 
-	g_return_val_if_fail (GDATA_IS_CONTACTS_CONTACT (self), FALSE);
+	g_return_val_if_fail (GDATA_IS_CONTACTS_CONTACT (parsable), FALSE);
 	g_return_val_if_fail (doc != NULL, FALSE);
 	g_return_val_if_fail (node != NULL, FALSE);
 
+	self = GDATA_CONTACTS_CONTACT (parsable);
+
 	if (xmlStrcmp (node->name, (xmlChar*) "edited") == 0) {
 		/* app:edited */
 		/* TODO: Should be in GDataEntry? */
@@ -484,7 +488,7 @@ parse_xml (GDataEntry *entry, xmlDoc *doc, xmlNode *node, GError **error)
 	} else if (xmlStrcmp (node->name, (xmlChar*) "deleted") == 0) {
 		/* gd:deleted */
 		self->priv->deleted = TRUE;
-	} else if (GDATA_ENTRY_CLASS (gdata_contacts_contact_parent_class)->parse_xml (entry, doc, node, error) == FALSE) {
+	} else if (GDATA_PARSABLE_CLASS (gdata_contacts_contact_parent_class)->parse_xml (parsable, doc, node, user_data, error) == FALSE) {
 		/* Error! */
 		return FALSE;
 	}
diff --git a/gdata/services/youtube/gdata-youtube-service.c b/gdata/services/youtube/gdata-youtube-service.c
index 1a0e344..6e69ba9 100644
--- a/gdata/services/youtube/gdata-youtube-service.c
+++ b/gdata/services/youtube/gdata-youtube-service.c
@@ -235,7 +235,7 @@ parse_error_response (GDataService *self, GDataServiceError error_type, guint st
 		length = strlen (response_body);
 
 	/* Parse the XML */
-	doc = xmlReadMemory (response_body, length, "error.xml", NULL, 0);
+	doc = xmlReadMemory (response_body, length, "/dev/null", NULL, 0);
 	if (doc == NULL)
 		goto parent;
 
diff --git a/gdata/services/youtube/gdata-youtube-video.c b/gdata/services/youtube/gdata-youtube-video.c
index 5d35280..0a3b0f4 100644
--- a/gdata/services/youtube/gdata-youtube-video.c
+++ b/gdata/services/youtube/gdata-youtube-video.c
@@ -47,7 +47,7 @@ static void gdata_youtube_video_finalize (GObject *object);
 static void gdata_youtube_video_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec);
 static void gdata_youtube_video_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec);
 static void get_xml (GDataEntry *entry, GString *xml_string);
-static gboolean parse_xml (GDataEntry *entry, xmlDoc *doc, xmlNode *node, GError **error);
+static gboolean parse_xml (GDataParsable *parsable, xmlDoc *doc, xmlNode *node, gpointer user_data, GError **error);
 static void get_namespaces (GDataEntry *entry, GHashTable *namespaces);
 
 struct _GDataYouTubeVideoPrivate {
@@ -113,6 +113,7 @@ static void
 gdata_youtube_video_class_init (GDataYouTubeVideoClass *klass)
 {
 	GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+	GDataParsableClass *parsable_class = GDATA_PARSABLE_CLASS (klass);
 	GDataEntryClass *entry_class = GDATA_ENTRY_CLASS (klass);
 
 	g_type_class_add_private (klass, sizeof (GDataYouTubeVideoPrivate));
@@ -121,8 +122,9 @@ gdata_youtube_video_class_init (GDataYouTubeVideoClass *klass)
 	gobject_class->get_property = gdata_youtube_video_get_property;
 	gobject_class->finalize = gdata_youtube_video_finalize;
 
+	parsable_class->parse_xml = parse_xml;
+
 	entry_class->get_xml = get_xml;
-	entry_class->parse_xml = parse_xml;
 	entry_class->get_namespaces = get_namespaces;
 
 	/**
@@ -860,14 +862,16 @@ parse_media_group_xml_node (GDataYouTubeVideo *self, xmlDoc *doc, xmlNode *node,
 }
 
 static gboolean
-parse_xml (GDataEntry *entry, xmlDoc *doc, xmlNode *node, GError **error)
+parse_xml (GDataParsable *parsable, xmlDoc *doc, xmlNode *node, gpointer user_data, GError **error)
 {
-	GDataYouTubeVideo *self = GDATA_YOUTUBE_VIDEO (entry);
+	GDataYouTubeVideo *self;
 
-	g_return_val_if_fail (GDATA_IS_YOUTUBE_VIDEO (self), FALSE);
+	g_return_val_if_fail (GDATA_IS_YOUTUBE_VIDEO (parsable), FALSE);
 	g_return_val_if_fail (doc != NULL, FALSE);
 	g_return_val_if_fail (node != NULL, FALSE);
 
+	self = GDATA_YOUTUBE_VIDEO (parsable);
+
 	if (xmlStrcmp (node->name, (xmlChar*) "group") == 0) {
 		/* media:group */
 		xmlNode *child_node;
@@ -1024,7 +1028,7 @@ parse_xml (GDataEntry *entry, xmlDoc *doc, xmlNode *node, GError **error)
 
 			child_node = child_node->next;
 		}
-	} else if (GDATA_ENTRY_CLASS (gdata_youtube_video_parent_class)->parse_xml (entry, doc, node, error) == FALSE) {
+	} 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/po/POTFILES.in b/po/POTFILES.in
index 180770d..56a2470 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -4,6 +4,7 @@
 gdata/gdata-access-handler.c
 gdata/gdata-entry.c
 gdata/gdata-feed.c
+gdata/gdata-parsable.c
 gdata/gdata-parser.c
 gdata/gdata-service.c
 gdata/services/calendar/gdata-calendar-calendar.c



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