[libgdata] [core] Converted Atom structs to GObjects



commit 05d2280925c8492764b2bdc8d9a2f7957dafc877
Author: Philip Withnall <philip tecnocode co uk>
Date:   Thu Jun 4 11:34:25 2009 +0100

    [core] Converted Atom structs to GObjects
    
    Converted all the Atom structs to GObjects, and moved them to an "atom"
    directory. This entailed moving get_xml and get_namespaces to GDataParsable,
    which simplifies things anyway. The documentation for the new Atom objects
    is mostly complete, but no new test cases have been added. Helps: bgo#579170

 configure.in                                      |    1 +
 docs/reference/Makefile.am                        |    7 +-
 docs/reference/gdata-docs.xml                     |    5 +-
 docs/reference/gdata-sections.txt                 |  123 ++++-
 gdata/Makefile.am                                 |   15 +-
 gdata/atom/Makefile.am                            |   36 ++
 gdata/atom/gdata-author.c                         |  422 ++++++++++++++
 gdata/atom/gdata-author.h                         |   77 +++
 gdata/atom/gdata-category.c                       |  381 +++++++++++++
 gdata/atom/gdata-category.h                       |   77 +++
 gdata/atom/gdata-generator.c                      |  262 +++++++++
 gdata/atom/gdata-generator.h                      |   71 +++
 gdata/atom/gdata-link.c                           |  616 +++++++++++++++++++++
 gdata/atom/gdata-link.h                           |   86 +++
 gdata/gdata-access-handler.c                      |   10 +-
 gdata/gdata-access-rule.c                         |   26 +-
 gdata/gdata-atom.c                                |  329 -----------
 gdata/gdata-atom.h                                |  120 ----
 gdata/gdata-entry.c                               |  287 +++-------
 gdata/gdata-entry.h                               |   17 +-
 gdata/gdata-feed.c                                |  149 ++----
 gdata/gdata-feed.h                                |    5 +-
 gdata/gdata-parsable.c                            |   82 +++-
 gdata/gdata-parsable.h                            |   27 +-
 gdata/gdata-parser.c                              |    9 +-
 gdata/gdata-parser.h                              |   25 +-
 gdata/gdata-private.h                             |   12 +-
 gdata/gdata-service.c                             |    8 +-
 gdata/gdata.h                                     |    9 +-
 gdata/gdata.symbols                               |   51 ++-
 gdata/services/calendar/gdata-calendar-calendar.c |   20 +-
 gdata/services/calendar/gdata-calendar-event.c    |   20 +-
 gdata/services/contacts/gdata-contacts-contact.c  |   26 +-
 gdata/services/youtube/gdata-youtube-service.c    |    5 +-
 gdata/services/youtube/gdata-youtube-video.c      |   20 +-
 gdata/tests/calendar.c                            |    6 +-
 gdata/tests/general.c                             |   11 +-
 37 files changed, 2519 insertions(+), 934 deletions(-)
---
diff --git a/configure.in b/configure.in
index ef1cd30..7d0d688 100644
--- a/configure.in
+++ b/configure.in
@@ -85,6 +85,7 @@ AC_OUTPUT([
 Makefile
 libgdata.pc
 gdata/Makefile
+gdata/atom/Makefile
 gdata/services/Makefile
 gdata/services/calendar/Makefile
 gdata/services/contacts/Makefile
diff --git a/docs/reference/Makefile.am b/docs/reference/Makefile.am
index 113d4da..b2b8ae9 100644
--- a/docs/reference/Makefile.am
+++ b/docs/reference/Makefile.am
@@ -50,7 +50,12 @@ CFILE_GLOB=$(top_srcdir)/gdata/*.c $(top_srcdir)/gdata/services/*/*.c
 
 # Header files to ignore when scanning.
 # e.g. IGNORE_HFILES=gtkdebug.h gtkintl.h
-IGNORE_HFILES=common.h gdata-private.h gdata-marshal.h gdata-enums.h
+IGNORE_HFILES = \
+	common.h	\
+	gdata-private.h	\
+	gdata-parser.h	\
+	gdata-marshal.h	\
+	gdata-enums.h
 
 # Images to copy into HTML directory.
 # e.g. HTML_IMAGES=$(top_srcdir)/gtk/stock-icons/stock_about_24.png
diff --git a/docs/reference/gdata-docs.xml b/docs/reference/gdata-docs.xml
index 41368e4..a82a617 100644
--- a/docs/reference/gdata-docs.xml
+++ b/docs/reference/gdata-docs.xml
@@ -34,7 +34,10 @@
 
 	<chapter>
 		<title>Namespace API</title>
-		<xi:include href="xml/gdata-atom.xml"/>
+		<xi:include href="xml/gdata-author.xml"/>
+		<xi:include href="xml/gdata-category.xml"/>
+		<xi:include href="xml/gdata-generator.xml"/>
+		<xi:include href="xml/gdata-link.xml"/>
 		<xi:include href="xml/gdata-gdata.xml"/>
 		<xi:include href="xml/gdata-media.xml"/>
 		<xi:include href="xml/gdata-youtube.xml"/>
diff --git a/docs/reference/gdata-sections.txt b/docs/reference/gdata-sections.txt
index a26c4c1..2e4b86d 100644
--- a/docs/reference/gdata-sections.txt
+++ b/docs/reference/gdata-sections.txt
@@ -161,27 +161,6 @@ GDataEntryPrivate
 </SECTION>
 
 <SECTION>
-<FILE>gdata-atom</FILE>
-<TITLE>Atom API</TITLE>
-GDataAuthor
-gdata_author_new
-gdata_author_compare
-gdata_author_free
-GDataCategory
-gdata_category_new
-gdata_category_compare
-gdata_category_free
-GDataLink
-gdata_link_new
-gdata_link_compare
-gdata_link_free
-GDataGenerator
-gdata_generator_new
-gdata_generator_compare
-gdata_generator_free
-</SECTION>
-
-<SECTION>
 <FILE>gdata-gdata</FILE>
 <TITLE>GData API</TITLE>
 GDataGDEmailAddress
@@ -724,3 +703,105 @@ GDATA_TYPE_CALENDAR_FEED
 <SUBSECTION Private>
 GDataCalendarFeedPrivate
 </SECTION>
+
+<SECTION>
+<FILE>gdata-category</FILE>
+<TITLE>GDataCategory</TITLE>
+GDataCategory
+GDataCategoryClass
+gdata_category_new
+gdata_category_compare
+gdata_category_get_term
+gdata_category_set_term
+gdata_category_get_scheme
+gdata_category_set_scheme
+gdata_category_get_label
+gdata_category_set_label
+<SUBSECTION Standard>
+gdata_category_get_type
+GDATA_CATEGORY
+GDATA_CATEGORY_CLASS
+GDATA_CATEGORY_GET_CLASS
+GDATA_IS_CATEGORY
+GDATA_IS_CATEGORY_CLASS
+GDATA_TYPE_CATEGORY
+<SUBSECTION Private>
+GDataCategoryPrivate
+</SECTION>
+
+<SECTION>
+<FILE>gdata-link</FILE>
+<TITLE>GDataLink</TITLE>
+GDataLink
+GDataLinkClass
+gdata_link_new
+gdata_link_compare
+gdata_link_get_uri
+gdata_link_set_uri
+gdata_link_get_relation_type
+gdata_link_set_relation_type
+gdata_link_get_content_type
+gdata_link_set_content_type
+gdata_link_get_language
+gdata_link_set_language
+gdata_link_get_title
+gdata_link_set_title
+gdata_link_get_length
+gdata_link_set_length
+<SUBSECTION Standard>
+gdata_link_get_type
+GDATA_IS_LINK
+GDATA_IS_LINK_CLASS
+GDATA_LINK
+GDATA_LINK_CLASS
+GDATA_LINK_GET_CLASS
+GDATA_TYPE_LINK
+<SUBSECTION Private>
+GDataLinkPrivate
+</SECTION>
+
+<SECTION>
+<FILE>gdata-author</FILE>
+<TITLE>GDataAuthor</TITLE>
+GDataAuthor
+GDataAuthorClass
+gdata_author_new
+gdata_author_compare
+gdata_author_get_name
+gdata_author_set_name
+gdata_author_get_uri
+gdata_author_set_uri
+gdata_author_get_email_address
+gdata_author_set_email_address
+<SUBSECTION Standard>
+gdata_author_get_type
+GDATA_AUTHOR
+GDATA_AUTHOR_CLASS
+GDATA_AUTHOR_GET_CLASS
+GDATA_IS_AUTHOR
+GDATA_IS_AUTHOR_CLASS
+GDATA_TYPE_AUTHOR
+<SUBSECTION Private>
+GDataAuthorPrivate
+</SECTION>
+
+<SECTION>
+<FILE>gdata-generator</FILE>
+<TITLE>GDataGenerator</TITLE>
+GDataGenerator
+GDataGeneratorClass
+gdata_generator_compare
+gdata_generator_get_name
+gdata_generator_get_uri
+gdata_generator_get_version
+<SUBSECTION Standard>
+gdata_generator_get_type
+GDATA_GENERATOR
+GDATA_GENERATOR_CLASS
+GDATA_GENERATOR_GET_CLASS
+GDATA_IS_GENERATOR
+GDATA_IS_GENERATOR_CLASS
+GDATA_TYPE_GENERATOR
+<SUBSECTION Private>
+GDataGeneratorPrivate
+</SECTION>
diff --git a/gdata/Makefile.am b/gdata/Makefile.am
index 5b73ee2..d8f76a8 100644
--- a/gdata/Makefile.am
+++ b/gdata/Makefile.am
@@ -1,5 +1,5 @@
-SUBDIRS = services . tests
-DIST_SUBDIRS = services tests
+SUBDIRS = atom services . tests
+DIST_SUBDIRS = atom services tests
 
 # Marshalling
 GDATA_MARSHAL_FILES = \
@@ -47,10 +47,8 @@ gdata_headers = \
 	gdata-feed.h		\
 	gdata-service.h		\
 	gdata-query.h		\
-	gdata-atom.h		\
 	gdata-media-rss.h	\
 	gdata-gdata.h		\
-	gdata-parser.h		\
 	gdata-access-handler.h	\
 	gdata-access-rule.h	\
 	gdata-parsable.h
@@ -69,14 +67,14 @@ libgdata_la_SOURCES = \
 	gdata-service.c		\
 	gdata-types.c		\
 	gdata-query.c		\
-	gdata-atom.c		\
 	gdata-media-rss.c	\
 	gdata-gdata.c		\
 	gdata-parser.c		\
 	gdata-access-handler.c	\
 	gdata-access-rule.c	\
+	gdata-parsable.c	\
 	gdata-private.h		\
-	gdata-parsable.c
+	gdata-parser.h
 
 libgdata_la_CPPFLAGS = \
 	-I$(top_srcdir)			\
@@ -92,8 +90,9 @@ libgdata_la_CFLAGS = \
 	-D_GNU_SOURCE
 
 libgdata_la_LIBADD = \
-	$(GDATA_LIBS)	\
-	$(GNOME_LIBS)	\
+	$(GDATA_LIBS)				\
+	$(GNOME_LIBS)				\
+	atom/libgdataatom.la			\
 	services/youtube/libgdatayoutube.la	\
 	services/calendar/libgdatacalendar.la	\
 	services/contacts/libgdatacontacts.la
diff --git a/gdata/atom/Makefile.am b/gdata/atom/Makefile.am
new file mode 100644
index 0000000..d73f3a9
--- /dev/null
+++ b/gdata/atom/Makefile.am
@@ -0,0 +1,36 @@
+gdataatomincludedir = $(pkgincludedir)/gdata/atom
+gdataatominclude_HEADERS = \
+	gdata-author.h		\
+	gdata-category.h	\
+	gdata-generator.h	\
+	gdata-link.h
+
+noinst_LTLIBRARIES = libgdataatom.la
+
+libgdataatom_la_SOURCES = \
+	gdata-author.c		\
+	gdata-category.c	\
+	gdata-generator.c	\
+	gdata-link.c
+
+libgdataatom_la_CPPFLAGS = \
+	-I$(top_srcdir)			\
+	-I$(top_srcdir)/gdata		\
+	-I$(top_srcdir)/gdata/atom	\
+	$(DISABLE_DEPRECATED)		\
+	$(AM_CPPFLAGS)
+
+libgdataatom_la_CFLAGS = \
+	$(GDATA_CFLAGS)	\
+	$(WARN_CFLAGS)	\
+	$(AM_CFLAGS)	\
+	-D_GNU_SOURCE
+
+libgdataatom_la_LIBADD = \
+	$(GDATA_LIBS)
+
+libgdataatom_la_LDFLAGS = \
+	-no-undefined	\
+	$(AM_LDFLAGS)
+
+-include $(top_srcdir)/git.mk
diff --git a/gdata/atom/gdata-author.c b/gdata/atom/gdata-author.c
new file mode 100644
index 0000000..d28ace6
--- /dev/null
+++ b/gdata/atom/gdata-author.c
@@ -0,0 +1,422 @@
+/* -*- 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-author
+ * @short_description: Atom author element
+ * @stability: Unstable
+ * @include: gdata/gdata-author.h
+ *
+ * #GDataAuthor represents a "author" element from the
+ * <ulink type="http" url="http://www.atomenabled.org/developers/syndication/atom-format-spec.php";>Atom specification</ulink>.
+ **/
+
+#include <glib.h>
+#include <libxml/parser.h>
+
+#include "gdata-author.h"
+#include "gdata-parsable.h"
+#include "gdata-parser.h"
+
+static void gdata_author_finalize (GObject *object);
+static void gdata_author_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec);
+static void gdata_author_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec);
+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 get_xml (GDataParsable *parsable, GString *xml_string);
+
+struct _GDataAuthorPrivate {
+	gchar *name;
+	gchar *uri;
+	gchar *email_address;
+};
+
+enum {
+	PROP_NAME = 1,
+	PROP_URI,
+	PROP_EMAIL_ADDRESS
+};
+
+G_DEFINE_TYPE (GDataAuthor, gdata_author, GDATA_TYPE_PARSABLE)
+#define GDATA_AUTHOR_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GDATA_TYPE_AUTHOR, GDataAuthorPrivate))
+
+static void
+gdata_author_class_init (GDataAuthorClass *klass)
+{
+	GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+	GDataParsableClass *parsable_class = GDATA_PARSABLE_CLASS (klass);
+
+	g_type_class_add_private (klass, sizeof (GDataAuthorPrivate));
+
+	gobject_class->set_property = gdata_author_set_property;
+	gobject_class->get_property = gdata_author_get_property;
+	gobject_class->finalize = gdata_author_finalize;
+
+	parsable_class->parse_xml = parse_xml;
+	parsable_class->post_parse_xml = post_parse_xml;
+	parsable_class->get_xml = get_xml;
+
+	/**
+	 * GDataAuthor:name:
+	 *
+	 * A human-readable name for the person.
+	 *
+	 * For more information, see the
+	 * <ulink type="http" url="http://www.atomenabled.org/developers/syndication/atom-format-spec.php#element.author";>Atom specification</ulink>.
+	 *
+	 * Since: 0.4.0
+	 **/
+	g_object_class_install_property (gobject_class, PROP_NAME,
+				g_param_spec_string ("name",
+					"Name", "A human-readable name for the person.",
+					NULL,
+					G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+	/**
+	 * GDataAuthor:uri:
+	 *
+	 * An IRI associated with the person.
+	 *
+	 * For more information, see the
+	 * <ulink type="http" url="http://www.atomenabled.org/developers/syndication/atom-format-spec.php#element.author";>Atom specification</ulink>.
+	 *
+	 * Since: 0.4.0
+	 **/
+	g_object_class_install_property (gobject_class, PROP_URI,
+				g_param_spec_string ("uri",
+					"URI", "An IRI associated with the person.",
+					NULL,
+					G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+	/**
+	 * GDataAuthor:email-address:
+	 *
+	 * An e-mail address associated with the person.
+	 *
+	 * For more information, see the
+	 * <ulink type="http" url="http://www.atomenabled.org/developers/syndication/atom-format-spec.php#element.author";>Atom specification</ulink>.
+	 *
+	 * Since: 0.4.0
+	 **/
+	g_object_class_install_property (gobject_class, PROP_EMAIL_ADDRESS,
+				g_param_spec_string ("email-address",
+					"E-mail address", "An e-mail address associated with the person.",
+					NULL,
+					G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+}
+
+static void
+gdata_author_init (GDataAuthor *self)
+{
+	self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GDATA_TYPE_AUTHOR, GDataAuthorPrivate);
+}
+
+static void
+gdata_author_finalize (GObject *object)
+{
+	GDataAuthorPrivate *priv = GDATA_AUTHOR (object)->priv;
+
+	g_free (priv->name);
+	g_free (priv->uri);
+	g_free (priv->email_address);
+
+	/* Chain up to the parent class */
+	G_OBJECT_CLASS (gdata_author_parent_class)->finalize (object);
+}
+
+static void
+gdata_author_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec)
+{
+	GDataAuthorPrivate *priv = GDATA_AUTHOR (object)->priv;
+
+	switch (property_id) {
+		case PROP_NAME:
+			g_value_set_string (value, priv->name);
+			break;
+		case PROP_URI:
+			g_value_set_string (value, priv->uri);
+			break;
+		case PROP_EMAIL_ADDRESS:
+			g_value_set_string (value, priv->email_address);
+			break;
+		default:
+			/* We don't have any other property... */
+			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+			break;
+	}
+}
+
+static void
+gdata_author_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec)
+{
+	GDataAuthor *self = GDATA_AUTHOR (object);
+
+	switch (property_id) {
+		case PROP_NAME:
+			gdata_author_set_name (self, g_value_get_string (value));
+			break;
+		case PROP_URI:
+			gdata_author_set_uri (self, g_value_get_string (value));
+			break;
+		case PROP_EMAIL_ADDRESS:
+			gdata_author_set_email_address (self, g_value_get_string (value));
+			break;
+		default:
+			/* We don't have any other property... */
+			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+			break;
+	}
+}
+
+static gboolean
+parse_xml (GDataParsable *parsable, xmlDoc *doc, xmlNode *node, gpointer user_data, GError **error)
+{
+	GDataAuthorPrivate *priv = GDATA_AUTHOR (parsable)->priv;
+
+	if (xmlStrcmp (node->name, (xmlChar*) "name") == 0) {
+		/* atom:name */
+		xmlChar *name;
+
+		if (priv->name != NULL)
+			return gdata_parser_error_duplicate_element (node, error);
+
+		name = xmlNodeListGetString (doc, node->children, TRUE);
+		if (name == NULL || *name == '\0')
+			return gdata_parser_error_required_content_missing (node, error);
+		priv->name = g_strdup ((gchar*) name);
+		xmlFree (name);
+	} else if (xmlStrcmp (node->name, (xmlChar*) "uri") == 0) {
+		/* atom:uri */
+		xmlChar *uri;
+
+		if (priv->uri != NULL)
+			return gdata_parser_error_duplicate_element (node, error);
+
+		uri = xmlNodeListGetString (doc, node->children, TRUE);
+		priv->uri = g_strdup ((gchar*) uri);
+		xmlFree (uri);
+	} else if (xmlStrcmp (node->name, (xmlChar*) "email") == 0) {
+		/* atom:email */
+		xmlChar *email_address;
+
+		if (priv->email_address != NULL)
+			return gdata_parser_error_duplicate_element (node, error);
+
+		email_address = xmlNodeListGetString (doc, node->children, TRUE);
+		priv->email_address = g_strdup ((gchar*) email_address);
+		xmlFree (email_address);
+	} else if (GDATA_PARSABLE_CLASS (gdata_author_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)
+{
+	GDataAuthorPrivate *priv = GDATA_AUTHOR (parsable)->priv;
+
+	if (priv->name == NULL || *(priv->name) == '\0')
+		return gdata_parser_error_required_element_missing ("name", "author", error);
+
+	return TRUE;
+}
+
+static void
+get_xml (GDataParsable *parsable, GString *xml_string)
+{
+	GDataAuthorPrivate *priv = GDATA_AUTHOR (parsable)->priv;
+	gchar *name;
+
+	name = g_markup_escape_text (priv->name, -1);
+	g_string_append_printf (xml_string, "<name>%s</name>", name);
+	g_free (name);
+
+	if (priv->uri != NULL) {
+		gchar *uri = g_markup_escape_text (priv->uri, -1);
+		g_string_append_printf (xml_string, "<uri>%s</uri>", uri);
+		g_free (uri);
+	}
+
+	if (priv->email_address != NULL) {
+		gchar *email = g_markup_escape_text (priv->email_address, -1);
+		g_string_append_printf (xml_string, "<email>%s</email>", email);
+		g_free (email);
+	}
+}
+
+/**
+ * gdata_author_new:
+ * @name: the author's name
+ * @uri: an IRI describing the author, or %NULL
+ * @email_address: the author's e-mail address, or %NULL
+ *
+ * Creates a new #GDataAuthor. More information is available in the <ulink type="http"
+ * url="http://www.atomenabled.org/developers/syndication/atom-format-spec.php#element.author";>Atom specification</ulink>.
+ *
+ * Return value: a new #GDataAuthor, or %NULL; unref with g_object_unref()
+ **/
+GDataAuthor *
+gdata_author_new (const gchar *name, const gchar *uri, const gchar *email_address)
+{
+	g_return_val_if_fail (name != NULL && *name != '\0', NULL);
+	return g_object_new (GDATA_TYPE_AUTHOR, "name", name, "uri", uri, "email-address", email_address, NULL);
+}
+
+/**
+ * gdata_author_compare:
+ * @a: a #GDataAuthor, or %NULL
+ * @b: another #GDataAuthor, or %NULL
+ *
+ * Compares the two authors in a strcmp() fashion. %NULL values are handled gracefully, with
+ * %0 returned if both @a and @b are %NULL, %-1 if @a is %NULL and %1 if @b is %NULL.
+ *
+ * The comparison of non-%NULL values is done on the basis of the @name property of the #GDataAuthor<!-- -->s.
+ *
+ * Return value: %0 if @a equals @b, %-1 or %1 as appropriate otherwise
+ *
+ * Since: 0.4.0
+ **/
+gint
+gdata_author_compare (const GDataAuthor *a, const GDataAuthor *b)
+{
+	if (a == NULL && b != NULL)
+		return -1;
+	else if (b == NULL)
+		return 1;
+
+	if (a == b)
+		return 0;
+	return g_strcmp0 (a->priv->name, b->priv->name);
+}
+
+/**
+ * gdata_author_get_name:
+ * @self: a #GDataAuthor
+ *
+ * Gets the #GDataAuthor:name property.
+ *
+ * Return value: the author's name
+ *
+ * Since: 0.4.0
+ **/
+const gchar *
+gdata_author_get_name (GDataAuthor *self)
+{
+	g_return_val_if_fail (GDATA_IS_AUTHOR (self), NULL);
+	return self->priv->name;
+}
+
+/**
+ * gdata_author_set_name:
+ * @self: a #GDataAuthor
+ * @name: the new name for the author
+ *
+ * Sets the #GDataAuthor:name property to @name.
+ *
+ * Since: 0.4.0
+ **/
+void
+gdata_author_set_name (GDataAuthor *self, const gchar *name)
+{
+	g_return_if_fail (GDATA_IS_AUTHOR (self));
+	g_return_if_fail (name != NULL && *name != '\0');
+
+	g_free (self->priv->name);
+	self->priv->name = g_strdup (name);
+	g_object_notify (G_OBJECT (self), "name");
+}
+
+/**
+ * gdata_author_get_uri:
+ * @self: a #GDataAuthor
+ *
+ * Gets the #GDataAuthor:uri property.
+ *
+ * Return value: the author's URI, or %NULL
+ *
+ * Since: 0.4.0
+ **/
+const gchar *
+gdata_author_get_uri (GDataAuthor *self)
+{
+	g_return_val_if_fail (GDATA_IS_AUTHOR (self), NULL);
+	return self->priv->uri;
+}
+
+/**
+ * gdata_author_set_uri:
+ * @self: a #GDataAuthor
+ * @uri: the new URI for the author, or %NULL
+ *
+ * Sets the #GDataAuthor:uri property to @uri.
+ *
+ * Set @uri to %NULL to unset the property in the author.
+ *
+ * Since: 0.4.0
+ **/
+void
+gdata_author_set_uri (GDataAuthor *self, const gchar *uri)
+{
+	g_return_if_fail (GDATA_IS_AUTHOR (self));
+
+	g_free (self->priv->uri);
+	self->priv->uri = g_strdup (uri);
+	g_object_notify (G_OBJECT (self), "uri");
+}
+
+/**
+ * gdata_author_get_email_address:
+ * @self: a #GDataAuthor
+ *
+ * Gets the #GDataAuthor:email-address property.
+ *
+ * Return value: the author's e-mail address, or %NULL
+ *
+ * Since: 0.4.0
+ **/
+const gchar *
+gdata_author_get_email_address (GDataAuthor *self)
+{
+	g_return_val_if_fail (GDATA_IS_AUTHOR (self), NULL);
+	return self->priv->email_address;
+}
+
+/**
+ * gdata_author_set_email_address:
+ * @self: a #GDataAuthor
+ * @email_address: the new e-mail address for the author, or %NULL
+ *
+ * Sets the #GDataAuthor:email-address property to @email_address.
+ *
+ * Set @email_address to %NULL to unset the property in the author.
+ *
+ * Since: 0.4.0
+ **/
+void
+gdata_author_set_email_address (GDataAuthor *self, const gchar *email_address)
+{
+	g_return_if_fail (GDATA_IS_AUTHOR (self));
+
+	g_free (self->priv->email_address);
+	self->priv->email_address = g_strdup (email_address);
+	g_object_notify (G_OBJECT (self), "email-address");
+}
diff --git a/gdata/atom/gdata-author.h b/gdata/atom/gdata-author.h
new file mode 100644
index 0000000..a7d0988
--- /dev/null
+++ b/gdata/atom/gdata-author.h
@@ -0,0 +1,77 @@
+/* -*- 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_AUTHOR_H
+#define GDATA_AUTHOR_H
+
+#include <glib.h>
+#include <glib-object.h>
+
+#include <gdata/gdata-parsable.h>
+
+G_BEGIN_DECLS
+
+#define GDATA_TYPE_AUTHOR		(gdata_author_get_type ())
+#define GDATA_AUTHOR(o)			(G_TYPE_CHECK_INSTANCE_CAST ((o), GDATA_TYPE_AUTHOR, GDataAuthor))
+#define GDATA_AUTHOR_CLASS(k)		(G_TYPE_CHECK_CLASS_CAST((k), GDATA_TYPE_AUTHOR, GDataAuthorClass))
+#define GDATA_IS_AUTHOR(o)		(G_TYPE_CHECK_INSTANCE_TYPE ((o), GDATA_TYPE_AUTHOR))
+#define GDATA_IS_AUTHOR_CLASS(k)	(G_TYPE_CHECK_CLASS_TYPE ((k), GDATA_TYPE_AUTHOR))
+#define GDATA_AUTHOR_GET_CLASS(o)	(G_TYPE_INSTANCE_GET_CLASS ((o), GDATA_TYPE_AUTHOR, GDataAuthorClass))
+
+typedef struct _GDataAuthorPrivate	GDataAuthorPrivate;
+
+/**
+ * GDataAuthor:
+ *
+ * All the fields in the #GDataAuthor structure are private and should never be accessed directly.
+ **/
+typedef struct {
+	GDataParsable parent;
+	GDataAuthorPrivate *priv;
+} GDataAuthor;
+
+/**
+ * GDataAuthorClass:
+ *
+ * All the fields in the #GDataAuthorClass structure are private and should never be accessed directly.
+ *
+ * Since: 0.4.0
+ **/
+typedef struct {
+	/*< private >*/
+	GDataParsableClass parent;
+} GDataAuthorClass;
+
+GType gdata_author_get_type (void) G_GNUC_CONST;
+
+GDataAuthor *gdata_author_new (const gchar *name, const gchar *uri, const gchar *email_address) G_GNUC_WARN_UNUSED_RESULT;
+gint gdata_author_compare (const GDataAuthor *a, const GDataAuthor *b);
+
+const gchar *gdata_author_get_name (GDataAuthor *self);
+void gdata_author_set_name (GDataAuthor *self, const gchar *name);
+
+const gchar *gdata_author_get_uri (GDataAuthor *self);
+void gdata_author_set_uri (GDataAuthor *self, const gchar *uri);
+
+const gchar *gdata_author_get_email_address (GDataAuthor *self);
+void gdata_author_set_email_address (GDataAuthor *self, const gchar *email_address);
+
+G_END_DECLS
+
+#endif /* !GDATA_AUTHOR_H */
diff --git a/gdata/atom/gdata-category.c b/gdata/atom/gdata-category.c
new file mode 100644
index 0000000..6fdfef3
--- /dev/null
+++ b/gdata/atom/gdata-category.c
@@ -0,0 +1,381 @@
+/* -*- 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-category
+ * @short_description: Atom category element
+ * @stability: Unstable
+ * @include: gdata/gdata-category.h
+ *
+ * #GDataCategory represents a "category" element from the
+ * <ulink type="http" url="http://www.atomenabled.org/developers/syndication/atom-format-spec.php";>Atom specification</ulink>.
+ **/
+
+#include <glib.h>
+#include <libxml/parser.h>
+
+#include "gdata-category.h"
+#include "gdata-parsable.h"
+#include "gdata-parser.h"
+
+static void gdata_category_finalize (GObject *object);
+static void gdata_category_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec);
+static void gdata_category_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 void pre_get_xml (GDataParsable *parsable, GString *xml_string);
+
+struct _GDataCategoryPrivate {
+	gchar *term;
+	gchar *scheme;
+	gchar *label;
+};
+
+enum {
+	PROP_TERM = 1,
+	PROP_SCHEME,
+	PROP_LABEL
+};
+
+G_DEFINE_TYPE (GDataCategory, gdata_category, GDATA_TYPE_PARSABLE)
+#define GDATA_CATEGORY_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GDATA_TYPE_CATEGORY, GDataCategoryPrivate))
+
+static void
+gdata_category_class_init (GDataCategoryClass *klass)
+{
+	GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+	GDataParsableClass *parsable_class = GDATA_PARSABLE_CLASS (klass);
+
+	g_type_class_add_private (klass, sizeof (GDataCategoryPrivate));
+
+	gobject_class->set_property = gdata_category_set_property;
+	gobject_class->get_property = gdata_category_get_property;
+	gobject_class->finalize = gdata_category_finalize;
+
+	parsable_class->pre_parse_xml = pre_parse_xml;
+	parsable_class->pre_get_xml = pre_get_xml;
+
+	/**
+	 * GDataCategory:term:
+	 *
+	 * Identifies the category to which the entry or feed belongs.
+	 *
+	 * For more information, see the
+	 * <ulink type="http" url="http://www.atomenabled.org/developers/syndication/atom-format-spec.php#element.category";>Atom specification</ulink>.
+	 *
+	 * Since: 0.4.0
+	 **/
+	g_object_class_install_property (gobject_class, PROP_TERM,
+				g_param_spec_string ("term",
+					"Term", "Identifies the category to which the entry or feed belongs.",
+					NULL,
+					G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+	/**
+	 * GDataCategory:scheme:
+	 *
+	 * An IRI that identifies a categorization scheme.
+	 *
+	 * For more information, see the
+	 * <ulink type="http" url="http://www.atomenabled.org/developers/syndication/atom-format-spec.php#element.category";>Atom specification</ulink>.
+	 *
+	 * Since: 0.4.0
+	 **/
+	g_object_class_install_property (gobject_class, PROP_SCHEME,
+				g_param_spec_string ("scheme",
+					"Scheme", "An IRI that identifies a categorization scheme.",
+					NULL,
+					G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+	/**
+	 * GDataCategory:label:
+	 *
+	 * A human-readable label for display in end-user applications.
+	 *
+	 * For more information, see the
+	 * <ulink type="http" url="http://www.atomenabled.org/developers/syndication/atom-format-spec.php#element.category";>Atom specification</ulink>.
+	 *
+	 * Since: 0.4.0
+	 **/
+	g_object_class_install_property (gobject_class, PROP_LABEL,
+				g_param_spec_string ("label",
+					"Label", "A human-readable label for display in end-user applications.",
+					NULL,
+					G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+}
+
+static void
+gdata_category_init (GDataCategory *self)
+{
+	self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GDATA_TYPE_CATEGORY, GDataCategoryPrivate);
+}
+
+static void
+gdata_category_finalize (GObject *object)
+{
+	GDataCategoryPrivate *priv = GDATA_CATEGORY (object)->priv;
+
+	g_free (priv->term);
+	g_free (priv->scheme);
+	g_free (priv->label);
+
+	/* Chain up to the parent class */
+	G_OBJECT_CLASS (gdata_category_parent_class)->finalize (object);
+}
+
+static void
+gdata_category_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec)
+{
+	GDataCategoryPrivate *priv = GDATA_CATEGORY (object)->priv;
+
+	switch (property_id) {
+		case PROP_TERM:
+			g_value_set_string (value, priv->term);
+			break;
+		case PROP_SCHEME:
+			g_value_set_string (value, priv->scheme);
+			break;
+		case PROP_LABEL:
+			g_value_set_string (value, priv->label);
+			break;
+		default:
+			/* We don't have any other property... */
+			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+			break;
+	}
+}
+
+static void
+gdata_category_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec)
+{
+	GDataCategory *self = GDATA_CATEGORY (object);
+
+	switch (property_id) {
+		case PROP_TERM:
+			gdata_category_set_term (self, g_value_get_string (value));
+			break;
+		case PROP_SCHEME:
+			gdata_category_set_scheme (self, g_value_get_string (value));
+			break;
+		case PROP_LABEL:
+			gdata_category_set_label (self, g_value_get_string (value));
+			break;
+		default:
+			/* We don't have any other property... */
+			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+			break;
+	}
+}
+
+static gboolean
+pre_parse_xml (GDataParsable *parsable, xmlDoc *doc, xmlNode *root_node, gpointer user_data, GError **error)
+{
+	xmlChar *term, *scheme, *label;
+	GDataCategory *self = GDATA_CATEGORY (parsable);
+
+	term = xmlGetProp (root_node, (xmlChar*) "term");
+	if (term == NULL || *term == '\0')
+		return gdata_parser_error_required_property_missing (root_node, "term", error);
+	self->priv->term = g_strdup ((gchar*) term);
+	xmlFree (term);
+
+	scheme = xmlGetProp (root_node, (xmlChar*) "scheme");
+	self->priv->scheme = g_strdup ((gchar*) scheme);
+	xmlFree (scheme);
+
+	label = xmlGetProp (root_node, (xmlChar*) "label");
+	self->priv->label = g_strdup ((gchar*) label);
+	xmlFree (label);
+
+	return TRUE;
+}
+
+static void
+pre_get_xml (GDataParsable *parsable, GString *xml_string)
+{
+	GDataCategoryPrivate *priv = GDATA_CATEGORY (parsable)->priv;
+
+	g_string_append_printf (xml_string, " term='%s'", priv->term);
+
+	if (priv->scheme != NULL)
+		g_string_append_printf (xml_string, " scheme='%s'", priv->scheme);
+
+	if (priv->label != NULL) {
+		gchar *label = g_markup_escape_text (priv->label, -1);
+		g_string_append_printf (xml_string, " label='%s'", label);
+		g_free (label);
+	}
+}
+
+/**
+ * gdata_category_new:
+ * @term: a category identifier
+ * @scheme: an IRI to define the categorisation scheme, or %NULL
+ * @label: a human-readable label for the category, or %NULL
+ *
+ * Creates a new #GDataCategory. More information is available in the <ulink type="http"
+ * url="http://www.atomenabled.org/developers/syndication/atom-format-spec.php#element.category";>Atom specification</ulink>.
+ *
+ * Return value: a new #GDataCategory, or %NULL; unref with g_object_unref()
+ **/
+GDataCategory *
+gdata_category_new (const gchar *term, const gchar *scheme, const gchar *label)
+{
+	g_return_val_if_fail (term != NULL && *term != '\0', NULL);
+	return g_object_new (GDATA_TYPE_CATEGORY, "term", term, "scheme", scheme, "label", label, NULL);
+}
+
+/**
+ * gdata_category_compare:
+ * @a: a #GDataCategory, or %NULL
+ * @b: another #GDataCategory, or %NULL
+ *
+ * Compares the two categories in a strcmp() fashion. %NULL values are handled gracefully, with
+ * %0 returned if both @a and @b are %NULL, %-1 if @a is %NULL and %1 if @b is %NULL.
+ *
+ * The comparison of non-%NULL values is done on the basis of the @term property of the #GDataCategory<!-- -->s.
+ *
+ * Return value: %0 if @a equals @b, %-1 or %1 as appropriate otherwise
+ *
+ * Since: 0.4.0
+ **/
+gint
+gdata_category_compare (const GDataCategory *a, const GDataCategory *b)
+{
+	if (a == NULL && b != NULL)
+		return -1;
+	else if (b == NULL)
+		return 1;
+
+	if (a == b)
+		return 0;
+	return g_strcmp0 (a->priv->term, b->priv->term);
+}
+
+/**
+ * gdata_category_get_term:
+ * @self: a #GDataCategory
+ *
+ * Gets the #GDataCategory:term property.
+ *
+ * Return value: the category's term
+ *
+ * Since: 0.4.0
+ **/
+const gchar *
+gdata_category_get_term (GDataCategory *self)
+{
+	g_return_val_if_fail (GDATA_IS_CATEGORY (self), NULL);
+	return self->priv->term;
+}
+
+/**
+ * gdata_category_set_term:
+ * @self: a #GDataCategory
+ * @term: the new term for the category
+ *
+ * Sets the #GDataCategory:term property to @term.
+ *
+ * Since: 0.4.0
+ **/
+void
+gdata_category_set_term (GDataCategory *self, const gchar *term)
+{
+	g_return_if_fail (GDATA_IS_CATEGORY (self));
+	g_return_if_fail (term != NULL && *term != '\0');
+
+	g_free (self->priv->term);
+	self->priv->term = g_strdup (term);
+	g_object_notify (G_OBJECT (self), "term");
+}
+
+/**
+ * gdata_category_get_scheme:
+ * @self: a #GDataCategory
+ *
+ * Gets the #GDataCategory:scheme property.
+ *
+ * Return value: the category's scheme, or %NULL
+ *
+ * Since: 0.4.0
+ **/
+const gchar *
+gdata_category_get_scheme (GDataCategory *self)
+{
+	g_return_val_if_fail (GDATA_IS_CATEGORY (self), NULL);
+	return self->priv->scheme;
+}
+
+/**
+ * gdata_category_set_scheme:
+ * @self: a #GDataCategory
+ * @scheme: the new scheme for the category, or %NULL
+ *
+ * Sets the #GDataCategory:scheme property to @scheme.
+ *
+ * Set @scheme to %NULL to unset the property in the category.
+ *
+ * Since: 0.4.0
+ **/
+void
+gdata_category_set_scheme (GDataCategory *self, const gchar *scheme)
+{
+	g_return_if_fail (GDATA_IS_CATEGORY (self));
+
+	g_free (self->priv->scheme);
+	self->priv->scheme = g_strdup (scheme);
+	g_object_notify (G_OBJECT (self), "scheme");
+}
+
+/**
+ * gdata_category_get_label:
+ * @self: a #GDataCategory
+ *
+ * Gets the #GDataCategory:label property.
+ *
+ * Return value: the category's label, or %NULL
+ *
+ * Since: 0.4.0
+ **/
+const gchar *
+gdata_category_get_label (GDataCategory *self)
+{
+	g_return_val_if_fail (GDATA_IS_CATEGORY (self), NULL);
+	return self->priv->label;
+}
+
+/**
+ * gdata_category_set_label:
+ * @self: a #GDataCategory
+ * @label: the new label for the category, or %NULL
+ *
+ * Sets the #GDataCategory:label property to @label.
+ *
+ * Set @label to %NULL to unset the property in the category.
+ *
+ * Since: 0.4.0
+ **/
+void
+gdata_category_set_label (GDataCategory *self, const gchar *label)
+{
+	g_return_if_fail (GDATA_IS_CATEGORY (self));
+
+	g_free (self->priv->label);
+	self->priv->label = g_strdup (label);
+	g_object_notify (G_OBJECT (self), "label");
+}
diff --git a/gdata/atom/gdata-category.h b/gdata/atom/gdata-category.h
new file mode 100644
index 0000000..02d5058
--- /dev/null
+++ b/gdata/atom/gdata-category.h
@@ -0,0 +1,77 @@
+/* -*- 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_CATEGORY_H
+#define GDATA_CATEGORY_H
+
+#include <glib.h>
+#include <glib-object.h>
+
+#include <gdata/gdata-parsable.h>
+
+G_BEGIN_DECLS
+
+#define GDATA_TYPE_CATEGORY		(gdata_category_get_type ())
+#define GDATA_CATEGORY(o)		(G_TYPE_CHECK_INSTANCE_CAST ((o), GDATA_TYPE_CATEGORY, GDataCategory))
+#define GDATA_CATEGORY_CLASS(k)		(G_TYPE_CHECK_CLASS_CAST((k), GDATA_TYPE_CATEGORY, GDataCategoryClass))
+#define GDATA_IS_CATEGORY(o)		(G_TYPE_CHECK_INSTANCE_TYPE ((o), GDATA_TYPE_CATEGORY))
+#define GDATA_IS_CATEGORY_CLASS(k)	(G_TYPE_CHECK_CLASS_TYPE ((k), GDATA_TYPE_CATEGORY))
+#define GDATA_CATEGORY_GET_CLASS(o)	(G_TYPE_INSTANCE_GET_CLASS ((o), GDATA_TYPE_CATEGORY, GDataCategoryClass))
+
+typedef struct _GDataCategoryPrivate	GDataCategoryPrivate;
+
+/**
+ * GDataCategory:
+ *
+ * All the fields in the #GDataCategory structure are private and should never be accessed directly.
+ **/
+typedef struct {
+	GDataParsable parent;
+	GDataCategoryPrivate *priv;
+} GDataCategory;
+
+/**
+ * GDataCategoryClass:
+ *
+ * All the fields in the #GDataCategoryClass structure are private and should never be accessed directly.
+ *
+ * Since: 0.4.0
+ **/
+typedef struct {
+	/*< private >*/
+	GDataParsableClass parent;
+} GDataCategoryClass;
+
+GType gdata_category_get_type (void) G_GNUC_CONST;
+
+GDataCategory *gdata_category_new (const gchar *term, const gchar *scheme, const gchar *label) G_GNUC_WARN_UNUSED_RESULT;
+gint gdata_category_compare (const GDataCategory *a, const GDataCategory *b);
+
+const gchar *gdata_category_get_term (GDataCategory *self);
+void gdata_category_set_term (GDataCategory *self, const gchar *term);
+
+const gchar *gdata_category_get_scheme (GDataCategory *self);
+void gdata_category_set_scheme (GDataCategory *self, const gchar *scheme);
+
+const gchar *gdata_category_get_label (GDataCategory *self);
+void gdata_category_set_label (GDataCategory *self, const gchar *label);
+
+G_END_DECLS
+
+#endif /* !GDATA_CATEGORY_H */
diff --git a/gdata/atom/gdata-generator.c b/gdata/atom/gdata-generator.c
new file mode 100644
index 0000000..717e814
--- /dev/null
+++ b/gdata/atom/gdata-generator.c
@@ -0,0 +1,262 @@
+/* -*- 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-generator
+ * @short_description: Atom generator element
+ * @stability: Unstable
+ * @include: gdata/gdata-generator.h
+ *
+ * #GDataGenerator represents a "generator" element from the
+ * <ulink type="http" url="http://www.atomenabled.org/developers/syndication/atom-format-spec.php";>Atom specification</ulink>.
+ **/
+
+#include <glib.h>
+#include <libxml/parser.h>
+
+#include "gdata-generator.h"
+#include "gdata-parsable.h"
+#include "gdata-parser.h"
+
+static void gdata_generator_finalize (GObject *object);
+static void gdata_generator_get_property (GObject *object, guint property_id, 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 *root_node, gpointer user_data, GError **error);
+
+struct _GDataGeneratorPrivate {
+	gchar *name;
+	gchar *uri;
+	gchar *version;
+};
+
+enum {
+	PROP_NAME = 1,
+	PROP_URI,
+	PROP_VERSION
+};
+
+G_DEFINE_TYPE (GDataGenerator, gdata_generator, GDATA_TYPE_PARSABLE)
+#define GDATA_GENERATOR_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GDATA_TYPE_GENERATOR, GDataGeneratorPrivate))
+
+static void
+gdata_generator_class_init (GDataGeneratorClass *klass)
+{
+	GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+	GDataParsableClass *parsable_class = GDATA_PARSABLE_CLASS (klass);
+
+	g_type_class_add_private (klass, sizeof (GDataGeneratorPrivate));
+
+	gobject_class->get_property = gdata_generator_get_property;
+	gobject_class->finalize = gdata_generator_finalize;
+
+	parsable_class->pre_parse_xml = pre_parse_xml;
+	parsable_class->parse_xml = parse_xml;
+
+	/**
+	 * GDataGenerator:name:
+	 *
+	 * A human-readable name for the generating agent.
+	 *
+	 * For more information, see the
+	 * <ulink type="http" url="http://www.atomenabled.org/developers/syndication/atom-format-spec.php#element.generator";>Atom specification</ulink>.
+	 *
+	 * Since: 0.4.0
+	 **/
+	g_object_class_install_property (gobject_class, PROP_NAME,
+				g_param_spec_string ("name",
+					"Name", "A human-readable name for the generating agent.",
+					NULL,
+					G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+	/**
+	 * GDataGenerator:uri:
+	 *
+	 * An IRI reference that is relevant to the agent.
+	 *
+	 * For more information, see the
+	 * <ulink type="http" url="http://www.atomenabled.org/developers/syndication/atom-format-spec.php#element.generator";>Atom specification</ulink>.
+	 *
+	 * Since: 0.4.0
+	 **/
+	g_object_class_install_property (gobject_class, PROP_URI,
+				g_param_spec_string ("uri",
+					"URI", "An IRI reference that is relevant to the agent.",
+					NULL,
+					G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+	/**
+	 * GDataGenerator:version:
+	 *
+	 * Indicates the version of the generating agent.
+	 *
+	 * For more information, see the
+	 * <ulink type="http" url="http://www.atomenabled.org/developers/syndication/atom-format-spec.php#element.generator";>Atom specification</ulink>.
+	 *
+	 * Since: 0.4.0
+	 **/
+	g_object_class_install_property (gobject_class, PROP_VERSION,
+				g_param_spec_string ("version",
+					"Version", "Indicates the version of the generating agent.",
+					NULL,
+					G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+}
+
+static void
+gdata_generator_init (GDataGenerator *self)
+{
+	self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GDATA_TYPE_GENERATOR, GDataGeneratorPrivate);
+}
+
+static void
+gdata_generator_finalize (GObject *object)
+{
+	GDataGeneratorPrivate *priv = GDATA_GENERATOR (object)->priv;
+
+	xmlFree (priv->name);
+	xmlFree (priv->uri);
+	xmlFree (priv->version);
+
+	/* Chain up to the parent class */
+	G_OBJECT_CLASS (gdata_generator_parent_class)->finalize (object);
+}
+
+static void
+gdata_generator_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec)
+{
+	GDataGeneratorPrivate *priv = GDATA_GENERATOR (object)->priv;
+
+	switch (property_id) {
+		case PROP_NAME:
+			g_value_set_string (value, priv->name);
+			break;
+		case PROP_URI:
+			g_value_set_string (value, priv->uri);
+			break;
+		case PROP_VERSION:
+			g_value_set_string (value, priv->version);
+			break;
+		default:
+			/* We don't have any other property... */
+			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+			break;
+	}
+}
+
+static gboolean
+pre_parse_xml (GDataParsable *parsable, xmlDoc *doc, xmlNode *root_node, gpointer user_data, GError **error)
+{
+	GDataGeneratorPrivate *priv = GDATA_GENERATOR (parsable)->priv;
+
+	priv->uri = (gchar*) xmlGetProp (root_node, (xmlChar*) "uri");
+	priv->version = (gchar*) xmlGetProp (root_node, (xmlChar*) "version");
+
+	return TRUE;
+}
+
+static gboolean
+parse_xml (GDataParsable *parsable, xmlDoc *doc, xmlNode *node, gpointer user_data, GError **error)
+{
+	xmlChar *name = xmlNodeListGetString (doc, node->children, TRUE);
+	if (name != NULL && *name == '\0') {
+		xmlFree (name);
+		return gdata_parser_error_required_content_missing (node, error);
+	}
+
+	GDATA_GENERATOR (parsable)->priv->name = (gchar*) name;
+
+	return TRUE;
+}
+
+/**
+ * gdata_generator_compare:
+ * @a: a #GDataGenerator, or %NULL
+ * @b: another #GDataGenerator, or %NULL
+ *
+ * Compares the two generators in a strcmp() fashion. %NULL values are handled gracefully, with
+ * %0 returned if both @a and @b are %NULL, %-1 if @a is %NULL and %1 if @b is %NULL.
+ *
+ * The comparison of non-%NULL values is done on the basis of the @name property of the #GDataGenerator<!-- -->s.
+ *
+ * Return value: %0 if @a equals @b, %-1 or %1 as appropriate otherwise
+ *
+ * Since: 0.4.0
+ **/
+gint
+gdata_generator_compare (const GDataGenerator *a, const GDataGenerator *b)
+{
+	if (a == NULL && b != NULL)
+		return -1;
+	else if (b == NULL)
+		return 1;
+
+	if (a == b)
+		return 0;
+	return g_strcmp0 (a->priv->name, b->priv->name);
+}
+
+/**
+ * gdata_generator_get_name:
+ * @self: a #GDataGenerator
+ *
+ * Gets the #GDataGenerator:name property.
+ *
+ * Return value: the generator's name
+ *
+ * Since: 0.4.0
+ **/
+const gchar *
+gdata_generator_get_name (GDataGenerator *self)
+{
+	g_return_val_if_fail (GDATA_IS_GENERATOR (self), NULL);
+	return self->priv->name;
+}
+
+/**
+ * gdata_generator_get_uri:
+ * @self: a #GDataGenerator
+ *
+ * Gets the #GDataGenerator:uri property.
+ *
+ * Return value: the generator's URI, or %NULL
+ *
+ * Since: 0.4.0
+ **/
+const gchar *
+gdata_generator_get_uri (GDataGenerator *self)
+{
+	g_return_val_if_fail (GDATA_IS_GENERATOR (self), NULL);
+	return self->priv->uri;
+}
+
+/**
+ * gdata_generator_get_version:
+ * @self: a #GDataGenerator
+ *
+ * Gets the #GDataGenerator:version property.
+ *
+ * Return value: the generator's version, or %NULL
+ *
+ * Since: 0.4.0
+ **/
+const gchar *
+gdata_generator_get_version (GDataGenerator *self)
+{
+	g_return_val_if_fail (GDATA_IS_GENERATOR (self), NULL);
+	return self->priv->version;
+}
diff --git a/gdata/atom/gdata-generator.h b/gdata/atom/gdata-generator.h
new file mode 100644
index 0000000..dd1f795
--- /dev/null
+++ b/gdata/atom/gdata-generator.h
@@ -0,0 +1,71 @@
+/* -*- 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_GENERATOR_H
+#define GDATA_GENERATOR_H
+
+#include <glib.h>
+#include <glib-object.h>
+
+#include <gdata/gdata-parsable.h>
+
+G_BEGIN_DECLS
+
+#define GDATA_TYPE_GENERATOR		(gdata_generator_get_type ())
+#define GDATA_GENERATOR(o)		(G_TYPE_CHECK_INSTANCE_CAST ((o), GDATA_TYPE_GENERATOR, GDataGenerator))
+#define GDATA_GENERATOR_CLASS(k)	(G_TYPE_CHECK_CLASS_CAST((k), GDATA_TYPE_GENERATOR, GDataGeneratorClass))
+#define GDATA_IS_GENERATOR(o)		(G_TYPE_CHECK_INSTANCE_TYPE ((o), GDATA_TYPE_GENERATOR))
+#define GDATA_IS_GENERATOR_CLASS(k)	(G_TYPE_CHECK_CLASS_TYPE ((k), GDATA_TYPE_GENERATOR))
+#define GDATA_GENERATOR_GET_CLASS(o)	(G_TYPE_INSTANCE_GET_CLASS ((o), GDATA_TYPE_GENERATOR, GDataGeneratorClass))
+
+typedef struct _GDataGeneratorPrivate	GDataGeneratorPrivate;
+
+/**
+ * GDataGenerator:
+ *
+ * All the fields in the #GDataGenerator structure are private and should never be accessed directly.
+ **/
+typedef struct {
+	GDataParsable parent;
+	GDataGeneratorPrivate *priv;
+} GDataGenerator;
+
+/**
+ * GDataGeneratorClass:
+ *
+ * All the fields in the #GDataGeneratorClass structure are private and should never be accessed directly.
+ *
+ * Since: 0.4.0
+ **/
+typedef struct {
+	/*< private >*/
+	GDataParsableClass parent;
+} GDataGeneratorClass;
+
+GType gdata_generator_get_type (void) G_GNUC_CONST;
+
+gint gdata_generator_compare (const GDataGenerator *a, const GDataGenerator *b);
+
+const gchar *gdata_generator_get_name (GDataGenerator *self);
+const gchar *gdata_generator_get_uri (GDataGenerator *self);
+const gchar *gdata_generator_get_version (GDataGenerator *self);
+
+G_END_DECLS
+
+#endif /* !GDATA_GENERATOR_H */
diff --git a/gdata/atom/gdata-link.c b/gdata/atom/gdata-link.c
new file mode 100644
index 0000000..aba08d2
--- /dev/null
+++ b/gdata/atom/gdata-link.c
@@ -0,0 +1,616 @@
+/* -*- 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-link
+ * @short_description: Atom link element
+ * @stability: Unstable
+ * @include: gdata/gdata-link.h
+ *
+ * #GDataLink represents a "link" element from the
+ * <ulink type="http" url="http://www.atomenabled.org/developers/syndication/atom-format-spec.php";>Atom specification</ulink>.
+ **/
+
+#include <glib.h>
+#include <libxml/parser.h>
+
+#include "gdata-link.h"
+#include "gdata-parsable.h"
+#include "gdata-parser.h"
+
+static void gdata_link_finalize (GObject *object);
+static void gdata_link_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec);
+static void gdata_link_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 void pre_get_xml (GDataParsable *parsable, GString *xml_string);
+
+struct _GDataLinkPrivate {
+	gchar *uri;
+	gchar *relation_type;
+	gchar *content_type;
+	gchar *language;
+	gchar *title;
+	gint length;
+};
+
+enum {
+	PROP_URI = 1,
+	PROP_RELATION_TYPE,
+	PROP_CONTENT_TYPE,
+	PROP_LANGUAGE,
+	PROP_TITLE,
+	PROP_LENGTH
+};
+
+G_DEFINE_TYPE (GDataLink, gdata_link, GDATA_TYPE_PARSABLE)
+#define GDATA_LINK_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GDATA_TYPE_LINK, GDataLinkPrivate))
+
+static void
+gdata_link_class_init (GDataLinkClass *klass)
+{
+	GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+	GDataParsableClass *parsable_class = GDATA_PARSABLE_CLASS (klass);
+
+	g_type_class_add_private (klass, sizeof (GDataLinkPrivate));
+
+	gobject_class->set_property = gdata_link_set_property;
+	gobject_class->get_property = gdata_link_get_property;
+	gobject_class->finalize = gdata_link_finalize;
+
+	parsable_class->pre_parse_xml = pre_parse_xml;
+	parsable_class->pre_get_xml = pre_get_xml;
+
+	/**
+	 * GDataLink:uri:
+	 *
+	 * The link's IRI.
+	 *
+	 * For more information, see the
+	 * <ulink type="http" url="http://www.atomenabled.org/developers/syndication/atom-format-spec.php#element.link";>Atom specification</ulink>.
+	 *
+	 * Since: 0.4.0
+	 **/
+	g_object_class_install_property (gobject_class, PROP_URI,
+				g_param_spec_string ("uri",
+					"URI", "The link's IRI.",
+					NULL,
+					G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+	/**
+	 * GDataLink:relation-type:
+	 *
+	 * The link relation type.
+	 *
+	 * For more information, see the
+	 * <ulink type="http" url="http://www.atomenabled.org/developers/syndication/atom-format-spec.php#element.link";>Atom specification</ulink>.
+	 *
+	 * Since: 0.4.0
+	 **/
+	g_object_class_install_property (gobject_class, PROP_RELATION_TYPE,
+				g_param_spec_string ("relation-type",
+					"Relation type", "The link relation type.",
+					"alternate",
+					G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+	/**
+	 * GDataLink:content-type:
+	 *
+	 * An advisory media type: it is a hint about the type of the representation that is expected to be returned when the value
+	 * of the #GDataLink:uri property is dereferenced.
+	 *
+	 * For more information, see the
+	 * <ulink type="http" url="http://www.atomenabled.org/developers/syndication/atom-format-spec.php#element.link";>Atom specification</ulink>.
+	 *
+	 * Since: 0.4.0
+	 **/
+	g_object_class_install_property (gobject_class, PROP_CONTENT_TYPE,
+				g_param_spec_string ("content-type",
+					"Content type", "An advisory media type.",
+					NULL,
+					G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+	/**
+	 * GDataLink:language:
+	 *
+	 * Describes the language of the resource pointed to by the #GDataLink:uri property.
+	 *
+	 * For more information, see the
+	 * <ulink type="http" url="http://www.atomenabled.org/developers/syndication/atom-format-spec.php#element.link";>Atom specification</ulink>.
+	 *
+	 * Since: 0.4.0
+	 **/
+	g_object_class_install_property (gobject_class, PROP_LANGUAGE,
+				g_param_spec_string ("language",
+					"Language", "Describes the language of the resource pointed to by the #GDataLink:uri property.",
+					NULL,
+					G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+	/**
+	 * GDataLink:title:
+	 *
+	 * Conveys human-readable information about the link.
+	 *
+	 * For more information, see the
+	 * <ulink type="http" url="http://www.atomenabled.org/developers/syndication/atom-format-spec.php#element.link";>Atom specification</ulink>.
+	 *
+	 * Since: 0.4.0
+	 **/
+	g_object_class_install_property (gobject_class, PROP_TITLE,
+				g_param_spec_string ("title",
+					"Title", "Conveys human-readable information about the link.",
+					NULL,
+					G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+	/**
+	 * GDataLink:length:
+	 *
+	 * Indicates an advisory length of the linked content in octets. %-1 means the length is unspecified.
+	 *
+	 * For more information, see the
+	 * <ulink type="http" url="http://www.atomenabled.org/developers/syndication/atom-format-spec.php#element.link";>Atom specification</ulink>.
+	 *
+	 * Since: 0.4.0
+	 **/
+	g_object_class_install_property (gobject_class, PROP_LENGTH,
+				g_param_spec_int ("length",
+					"Length", "Indicates an advisory length of the linked content in octets.",
+					-1, G_MAXINT, -1,
+					G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+}
+
+static void
+gdata_link_init (GDataLink *self)
+{
+	self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GDATA_TYPE_LINK, GDataLinkPrivate);
+	self->priv->length = -1;
+	self->priv->relation_type = g_strdup ("alternate");
+}
+
+static void
+gdata_link_finalize (GObject *object)
+{
+	GDataLinkPrivate *priv = GDATA_LINK (object)->priv;
+
+	g_free (priv->uri);
+	g_free (priv->relation_type);
+	g_free (priv->content_type);
+	g_free (priv->language);
+	g_free (priv->title);
+
+	/* Chain up to the parent class */
+	G_OBJECT_CLASS (gdata_link_parent_class)->finalize (object);
+}
+
+static void
+gdata_link_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec)
+{
+	GDataLinkPrivate *priv = GDATA_LINK (object)->priv;
+
+	switch (property_id) {
+		case PROP_URI:
+			g_value_set_string (value, priv->uri);
+			break;
+		case PROP_RELATION_TYPE:
+			g_value_set_string (value, priv->relation_type);
+			break;
+		case PROP_CONTENT_TYPE:
+			g_value_set_string (value, priv->content_type);
+			break;
+		case PROP_LANGUAGE:
+			g_value_set_string (value, priv->language);
+			break;
+		case PROP_TITLE:
+			g_value_set_string (value, priv->title);
+			break;
+		case PROP_LENGTH:
+			g_value_set_int (value, priv->length);
+			break;
+		default:
+			/* We don't have any other property... */
+			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+			break;
+	}
+}
+
+static void
+gdata_link_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec)
+{
+	GDataLink *self = GDATA_LINK (object);
+
+	switch (property_id) {
+		case PROP_URI:
+			gdata_link_set_uri (self, g_value_get_string (value));
+			break;
+		case PROP_RELATION_TYPE:
+			gdata_link_set_relation_type (self, g_value_get_string (value));
+			break;
+		case PROP_CONTENT_TYPE:
+			gdata_link_set_content_type (self, g_value_get_string (value));
+			break;
+		case PROP_LANGUAGE:
+			gdata_link_set_language (self, g_value_get_string (value));
+			break;
+		case PROP_TITLE:
+			gdata_link_set_title (self, g_value_get_string (value));
+			break;
+		case PROP_LENGTH:
+			gdata_link_set_length (self, g_value_get_int (value));
+			break;
+		default:
+			/* We don't have any other property... */
+			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+			break;
+	}
+}
+
+static gboolean
+pre_parse_xml (GDataParsable *parsable, xmlDoc *doc, xmlNode *root_node, gpointer user_data, GError **error)
+{
+	xmlChar *uri, *relation_type, *content_type, *language, *title, *length;
+	GDataLink *self = GDATA_LINK (parsable);
+
+	/* href */
+	uri = xmlGetProp (root_node, (xmlChar*) "href");
+	if (uri == NULL && *uri != '\0')
+		return gdata_parser_error_required_property_missing (root_node, "href", error);
+	self->priv->uri = g_strdup ((gchar*) uri);
+	xmlFree (uri);
+
+	/* rel */
+	relation_type = xmlGetProp (root_node, (xmlChar*) "rel");
+	if (relation_type != NULL && *relation_type == '\0')
+		return gdata_parser_error_required_property_missing (root_node, "rel", error);
+
+	if (relation_type == NULL)
+		self->priv->relation_type = g_strdup ("alternate");
+	else
+		self->priv->relation_type = g_strdup ((gchar*) relation_type);
+	xmlFree (relation_type);
+
+	/* type */
+	content_type = xmlGetProp (root_node, (xmlChar*) "type");
+	if (content_type != NULL && *content_type == '\0')
+		return gdata_parser_error_required_property_missing (root_node, "type", error);
+	self->priv->content_type = g_strdup ((gchar*) content_type);
+	xmlFree (content_type);
+
+	/* hreflang */
+	language = xmlGetProp (root_node, (xmlChar*) "hreflang");
+	if (language != NULL && *language == '\0')
+		return gdata_parser_error_required_property_missing (root_node, "hreflang", error);
+	self->priv->language = g_strdup ((gchar*) language);
+	xmlFree (language);
+
+	/* title */
+	title = xmlGetProp (root_node, (xmlChar*) "title");
+	self->priv->title = g_strdup ((gchar*) title);
+	xmlFree (title);
+
+	/* length */
+	length = xmlGetProp (root_node, (xmlChar*) "length");
+	if (length == NULL)
+		self->priv->length = -1;
+	else
+		self->priv->length = strtoul ((gchar*) length, NULL, 10);
+
+	return TRUE;
+}
+
+static void
+pre_get_xml (GDataParsable *parsable, GString *xml_string)
+{
+	GDataLinkPrivate *priv = GDATA_LINK (parsable)->priv;
+
+	g_string_append_printf (xml_string, " href='%s'", priv->uri);
+
+	if (priv->title != NULL) {
+		gchar *link_title = g_markup_escape_text (priv->title, -1);
+		g_string_append_printf (xml_string, " title='%s'", link_title);
+		g_free (link_title);
+	}
+
+	if (priv->relation_type != NULL)
+		g_string_append_printf (xml_string, " rel='%s'", priv->relation_type);
+	if (priv->content_type != NULL)
+		g_string_append_printf (xml_string, " type='%s'", priv->content_type);
+	if (priv->language != NULL)
+		g_string_append_printf (xml_string, " hreflang='%s'", priv->language);
+	if (priv->length != -1)
+		g_string_append_printf (xml_string, " length='%i'", priv->length);
+}
+
+/**
+ * gdata_link_new:
+ * @uri: the link's IRI
+ * @relation_type: the relationship of the link to the current document, or %NULL
+ *
+ * Creates a new #GDataLink. More information is available in the <ulink type="http"
+ * url="http://www.atomenabled.org/developers/syndication/atom-format-spec.php#element.link";>Atom specification</ulink>.
+ *
+ * Return value: a new #GDataLink, or %NULL; unref with g_object_unref()
+ **/
+GDataLink *
+gdata_link_new (const gchar *uri, const gchar *relation_type)
+{
+	g_return_val_if_fail (uri != NULL && *uri != '\0', NULL);
+	g_return_val_if_fail (relation_type == NULL || *relation_type != '\0', NULL);
+
+	return g_object_new (GDATA_TYPE_LINK, "uri", uri, "relation-type", relation_type, NULL);
+}
+
+/**
+ * gdata_link_compare:
+ * @a: a #GDataLink, or %NULL
+ * @b: another #GDataLink, or %NULL
+ *
+ * Compares the two links in a strcmp() fashion. %NULL values are handled gracefully, with
+ * %0 returned if both @a and @b are %NULL, %-1 if @a is %NULL and %1 if @b is %NULL.
+ *
+ * The comparison of non-%NULL values is done on the basis of the @uri property of the #GDataLink<!-- -->s.
+ *
+ * Return value: %0 if @a equals @b, %-1 or %1 as appropriate otherwise
+ *
+ * Since: 0.4.0
+ **/
+gint
+gdata_link_compare (const GDataLink *a, const GDataLink *b)
+{
+	if (a == NULL && b != NULL)
+		return -1;
+	else if (b == NULL)
+		return 1;
+
+	if (a == b)
+		return 0;
+	return g_strcmp0 (a->priv->uri, b->priv->uri);
+}
+
+/**
+ * gdata_link_get_uri:
+ * @self: a #GDataLink
+ *
+ * Gets the #GDataLink:uri property.
+ *
+ * Return value: the link's URI
+ *
+ * Since: 0.4.0
+ **/
+const gchar *
+gdata_link_get_uri (GDataLink *self)
+{
+	g_return_val_if_fail (GDATA_IS_LINK (self), NULL);
+	return self->priv->uri;
+}
+
+/**
+ * gdata_link_set_uri:
+ * @self: a #GDataLink
+ * @uri: the new URI for the link
+ *
+ * Sets the #GDataLink:uri property to @uri.
+ *
+ * Since: 0.4.0
+ **/
+void
+gdata_link_set_uri (GDataLink *self, const gchar *uri)
+{
+	g_return_if_fail (GDATA_IS_LINK (self));
+	g_return_if_fail (uri != NULL && *uri != '\0');
+
+	g_free (self->priv->uri);
+	self->priv->uri = g_strdup (uri);
+	g_object_notify (G_OBJECT (self), "uri");
+}
+
+/**
+ * gdata_link_get_relation_type:
+ * @self: a #GDataLink
+ *
+ * Gets the #GDataLink:relation-type property.
+ *
+ * Return value: the link's relation type
+ *
+ * Since: 0.4.0
+ **/
+const gchar *
+gdata_link_get_relation_type (GDataLink *self)
+{
+	g_return_val_if_fail (GDATA_IS_LINK (self), NULL);
+	return self->priv->relation_type;
+}
+
+/**
+ * gdata_link_set_relation_type:
+ * @self: a #GDataLink
+ * @relation_type: the new relation type for the link, or %NULL
+ *
+ * Sets the #GDataLink:relation-type property to @relation_type.
+ *
+ * Set @relation_type to %NULL to unset the property in the link.
+ *
+ * Since: 0.4.0
+ **/
+void
+gdata_link_set_relation_type (GDataLink *self, const gchar *relation_type)
+{
+	g_return_if_fail (GDATA_IS_LINK (self));
+	g_return_if_fail (relation_type == NULL || *relation_type != '\0');
+
+	/* "If the "rel" attribute is not present, the link element MUST be interpreted as if the link relation type is "alternate"." */
+	if (relation_type == NULL)
+		relation_type = "alternate";
+
+	g_free (self->priv->relation_type);
+	self->priv->relation_type = g_strdup (relation_type);
+	g_object_notify (G_OBJECT (self), "relation-type");
+}
+
+/**
+ * gdata_link_get_content_type:
+ * @self: a #GDataLink
+ *
+ * Gets the #GDataLink:content-type property.
+ *
+ * Return value: the link's content type, or %NULL
+ *
+ * Since: 0.4.0
+ **/
+const gchar *
+gdata_link_get_content_type (GDataLink *self)
+{
+	g_return_val_if_fail (GDATA_IS_LINK (self), NULL);
+	return self->priv->content_type;
+}
+
+/**
+ * gdata_link_set_content_type:
+ * @self: a #GDataLink
+ * @content_type: the new content type for the link, or %NULL
+ *
+ * Sets the #GDataLink:content-type property to @content_type.
+ *
+ * Set @content_type to %NULL to unset the property in the link.
+ *
+ * Since: 0.4.0
+ **/
+void
+gdata_link_set_content_type (GDataLink *self, const gchar *content_type)
+{
+	g_return_if_fail (GDATA_IS_LINK (self));
+	g_return_if_fail (content_type == NULL || *content_type != '\0');
+
+	g_free (self->priv->content_type);
+	self->priv->content_type = g_strdup (content_type);
+	g_object_notify (G_OBJECT (self), "content-type");
+}
+
+/**
+ * gdata_link_get_language:
+ * @self: a #GDataLink
+ *
+ * Gets the #GDataLink:language property.
+ *
+ * Return value: the link's language, or %NULL
+ *
+ * Since: 0.4.0
+ **/
+const gchar *
+gdata_link_get_language (GDataLink *self)
+{
+	g_return_val_if_fail (GDATA_IS_LINK (self), NULL);
+	return self->priv->language;
+}
+
+/**
+ * gdata_link_set_language:
+ * @self: a #GDataLink
+ * @language: the new language for the link, or %NULL
+ *
+ * Sets the #GDataLink:language property to @language.
+ *
+ * Set @language to %NULL to unset the property in the link.
+ *
+ * Since: 0.4.0
+ **/
+void
+gdata_link_set_language (GDataLink *self, const gchar *language)
+{
+	g_return_if_fail (GDATA_IS_LINK (self));
+	g_return_if_fail (language == NULL || *language != '\0');
+
+	g_free (self->priv->language);
+	self->priv->language = g_strdup (language);
+	g_object_notify (G_OBJECT (self), "language");
+}
+
+/**
+ * gdata_link_get_title:
+ * @self: a #GDataLink
+ *
+ * Gets the #GDataLink:title property.
+ *
+ * Return value: the link's title, or %NULL
+ *
+ * Since: 0.4.0
+ **/
+const gchar *
+gdata_link_get_title (GDataLink *self)
+{
+	g_return_val_if_fail (GDATA_IS_LINK (self), NULL);
+	return self->priv->title;
+}
+
+/**
+ * gdata_link_set_title:
+ * @self: a #GDataLink
+ * @title: the new title for the link, or %NULL
+ *
+ * Sets the #GDataLink:title property to @title.
+ *
+ * Set @title to %NULL to unset the property in the link.
+ *
+ * Since: 0.4.0
+ **/
+void
+gdata_link_set_title (GDataLink *self, const gchar *title)
+{
+	g_return_if_fail (GDATA_IS_LINK (self));
+
+	g_free (self->priv->title);
+	self->priv->title = g_strdup (title);
+	g_object_notify (G_OBJECT (self), "title");
+}
+
+/**
+ * gdata_link_get_length:
+ * @self: a #GDataLink
+ *
+ * Gets the #GDataLink:length property.
+ *
+ * Return value: the link's length, or %-1
+ *
+ * Since: 0.4.0
+ **/
+gint
+gdata_link_get_length (GDataLink *self)
+{
+	g_return_val_if_fail (GDATA_IS_LINK (self), -1);
+	return self->priv->length;
+}
+
+/**
+ * gdata_link_set_length:
+ * @self: a #GDataLink
+ * @length: the new length for the link, or %-1
+ *
+ * Sets the #GDataLink:length property to @length.
+ *
+ * Set @length to %-1 to unset the property in the link.
+ *
+ * Since: 0.4.0
+ **/
+void
+gdata_link_set_length (GDataLink *self, gint length)
+{
+	g_return_if_fail (GDATA_IS_LINK (self));
+	g_return_if_fail (length >= -1);
+
+	self->priv->length = length;
+	g_object_notify (G_OBJECT (self), "length");
+}
diff --git a/gdata/atom/gdata-link.h b/gdata/atom/gdata-link.h
new file mode 100644
index 0000000..40b22ca
--- /dev/null
+++ b/gdata/atom/gdata-link.h
@@ -0,0 +1,86 @@
+/* -*- 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_LINK_H
+#define GDATA_LINK_H
+
+#include <glib.h>
+#include <glib-object.h>
+
+#include <gdata/gdata-parsable.h>
+
+G_BEGIN_DECLS
+
+#define GDATA_TYPE_LINK			(gdata_link_get_type ())
+#define GDATA_LINK(o)			(G_TYPE_CHECK_INSTANCE_CAST ((o), GDATA_TYPE_LINK, GDataLink))
+#define GDATA_LINK_CLASS(k)		(G_TYPE_CHECK_CLASS_CAST((k), GDATA_TYPE_LINK, GDataLinkClass))
+#define GDATA_IS_LINK(o)		(G_TYPE_CHECK_INSTANCE_TYPE ((o), GDATA_TYPE_LINK))
+#define GDATA_IS_LINK_CLASS(k)		(G_TYPE_CHECK_CLASS_TYPE ((k), GDATA_TYPE_LINK))
+#define GDATA_LINK_GET_CLASS(o)		(G_TYPE_INSTANCE_GET_CLASS ((o), GDATA_TYPE_LINK, GDataLinkClass))
+
+typedef struct _GDataLinkPrivate	GDataLinkPrivate;
+
+/**
+ * GDataLink:
+ *
+ * All the fields in the #GDataLink structure are private and should never be accessed directly.
+ **/
+typedef struct {
+	GDataParsable parent;
+	GDataLinkPrivate *priv;
+} GDataLink;
+
+/**
+ * GDataLinkClass:
+ *
+ * All the fields in the #GDataLinkClass structure are private and should never be accessed directly.
+ *
+ * Since: 0.4.0
+ **/
+typedef struct {
+	/*< private >*/
+	GDataParsableClass parent;
+} GDataLinkClass;
+
+GType gdata_link_get_type (void) G_GNUC_CONST;
+
+GDataLink *gdata_link_new (const gchar *uri, const gchar *relation_type) G_GNUC_WARN_UNUSED_RESULT;
+gint gdata_link_compare (const GDataLink *a, const GDataLink *b);
+
+const gchar *gdata_link_get_uri (GDataLink *self);
+void gdata_link_set_uri (GDataLink *self, const gchar *uri);
+
+const gchar *gdata_link_get_relation_type (GDataLink *self);
+void gdata_link_set_relation_type (GDataLink *self, const gchar *relation_type);
+
+const gchar *gdata_link_get_content_type (GDataLink *self);
+void gdata_link_set_content_type (GDataLink *self, const gchar *content_type);
+
+const gchar *gdata_link_get_language (GDataLink *self);
+void gdata_link_set_language (GDataLink *self, const gchar *language);
+
+const gchar *gdata_link_get_title (GDataLink *self);
+void gdata_link_set_title (GDataLink *self, const gchar *title);
+
+gint gdata_link_get_length (GDataLink *self);
+void gdata_link_set_length (GDataLink *self, gint length);
+
+G_END_DECLS
+
+#endif /* !GDATA_LINK_H */
diff --git a/gdata/gdata-access-handler.c b/gdata/gdata-access-handler.c
index 4045f31..49f8421 100644
--- a/gdata/gdata-access-handler.c
+++ b/gdata/gdata-access-handler.c
@@ -95,7 +95,7 @@ gdata_access_handler_get_rules (GDataAccessHandler *self, GDataService *service,
 	/* Get the ACL URI */
 	link = gdata_entry_look_up_link (GDATA_ENTRY (self), "http://schemas.google.com/acl/2007#accessControlList";);
 	g_assert (link != NULL);
-	message = soup_message_new (SOUP_METHOD_GET, link->href);
+	message = soup_message_new (SOUP_METHOD_GET, gdata_link_get_uri (link));
 
 	/* Make sure subclasses set their headers */
 	klass = GDATA_SERVICE_GET_CLASS (service);
@@ -180,7 +180,7 @@ gdata_access_handler_insert_rule (GDataAccessHandler *self, GDataService *servic
 	/* Get the ACL URI */
 	link = gdata_entry_look_up_link (GDATA_ENTRY (self), "http://schemas.google.com/acl/2007#accessControlList";);
 	g_assert (link != NULL);
-	message = soup_message_new (SOUP_METHOD_POST, link->href);
+	message = soup_message_new (SOUP_METHOD_POST, gdata_link_get_uri (link));
 
 	/* Make sure subclasses set their headers */
 	klass = GDATA_SERVICE_GET_CLASS (service);
@@ -236,15 +236,15 @@ get_soup_message (GDataAccessHandler *access_handler, GDataAccessRule *rule, con
 	/* Get the edit URI */
 	link = gdata_entry_look_up_link (GDATA_ENTRY (rule), "edit");
 	if (link != NULL)
-		return soup_message_new (method, link->href);
+		return soup_message_new (method, gdata_link_get_uri (link));
 
 	/* Try building the URI instead */
 	link = gdata_entry_look_up_link (GDATA_ENTRY (access_handler), "http://schemas.google.com/acl/2007#accessControlList";);
 	g_assert (link != NULL);
 	gdata_access_rule_get_scope (rule, &scope_type, &scope_value);
 
-	uri_string = g_string_sized_new (strlen (link->href) + 30);
-	g_string_append_printf (uri_string, "%s/", link->href);
+	uri_string = g_string_sized_new (strlen (gdata_link_get_uri (link)) + 30);
+	g_string_append_printf (uri_string, "%s/", gdata_link_get_uri (link));
 	g_string_append_uri_escaped (uri_string, scope_type, NULL, TRUE);
 	if (scope_value != NULL) {
 		g_string_append (uri_string, "%3A");
diff --git a/gdata/gdata-access-rule.c b/gdata/gdata-access-rule.c
index 2df4fe5..e9bdb7c 100644
--- a/gdata/gdata-access-rule.c
+++ b/gdata/gdata-access-rule.c
@@ -41,8 +41,8 @@
 #include "gdata-private.h"
 
 static void gdata_access_rule_finalize (GObject *object);
-static void get_namespaces (GDataEntry *entry, GHashTable *namespaces);
-static void get_xml (GDataEntry *entry, GString *xml_string);
+static void get_namespaces (GDataParsable *parsable, GHashTable *namespaces);
+static void get_xml (GDataParsable *parsable, 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 (GDataParsable *parsable, xmlDoc *doc, xmlNode *node, gpointer user_data, GError **error);
@@ -67,7 +67,6 @@ 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));
 
@@ -76,9 +75,8 @@ gdata_access_rule_class_init (GDataAccessRuleClass *klass)
 	gobject_class->get_property = gdata_access_rule_get_property; 
 
 	parsable_class->parse_xml = parse_xml;
-
-	entry_class->get_xml = get_xml;
-	entry_class->get_namespaces = get_namespaces;
+	parsable_class->get_xml = get_xml;
+	parsable_class->get_namespaces = get_namespaces;
 
 	/**
 	 * GDataAccessRule:role:
@@ -264,21 +262,21 @@ parse_xml (GDataParsable *parsable, xmlDoc *doc, xmlNode *node, gpointer user_da
 }
 
 static void
-get_xml (GDataEntry *entry, GString *xml_string)
+get_xml (GDataParsable *parsable, GString *xml_string)
 {
 	GDataCategory *category;
-	GDataAccessRulePrivate *priv = GDATA_ACCESS_RULE (entry)->priv;
+	GDataAccessRulePrivate *priv = GDATA_ACCESS_RULE (parsable)->priv;
 
 	/* Ensure we have the correct category/kind */
 	category = gdata_category_new ("http://schemas.google.com/acl/2007#accessRule";, "http://schemas.google.com/g/2005#kind";, NULL);
-	gdata_entry_add_category (entry, category);
+	gdata_entry_add_category (GDATA_ENTRY (parsable), category);
 
 	/* So it's valid Atom, set the title if one doesn't already exist */
-	if (gdata_entry_get_title (entry) == NULL)
-		gdata_entry_set_title (entry, priv->role);
+	if (gdata_entry_get_title (GDATA_ENTRY (parsable)) == NULL)
+		gdata_entry_set_title (GDATA_ENTRY (parsable), priv->role);
 
 	/* Chain up to the parent class */
-	GDATA_ENTRY_CLASS (gdata_access_rule_parent_class)->get_xml (entry, xml_string);
+	GDATA_PARSABLE_CLASS (gdata_access_rule_parent_class)->get_xml (parsable, xml_string);
 
 	if (priv->role != NULL)
 		/* gAcl:role */
@@ -294,10 +292,10 @@ get_xml (GDataEntry *entry, GString *xml_string)
 }
 
 static void
-get_namespaces (GDataEntry *entry, GHashTable *namespaces)
+get_namespaces (GDataParsable *parsable, GHashTable *namespaces)
 {
 	/* Chain up to the parent class */
-	GDATA_ENTRY_CLASS (gdata_access_rule_parent_class)->get_namespaces (entry, namespaces);
+	GDATA_PARSABLE_CLASS (gdata_access_rule_parent_class)->get_namespaces (parsable, namespaces);
 
 	g_hash_table_insert (namespaces, (gchar*) "gAcl", (gchar*) "http://schemas.google.com/acl/2007";); 
 }
diff --git a/gdata/gdata-entry.c b/gdata/gdata-entry.c
index 880438f..e1a15bb 100644
--- a/gdata/gdata-entry.c
+++ b/gdata/gdata-entry.c
@@ -38,16 +38,20 @@
 #include "gdata-types.h"
 #include "gdata-service.h"
 #include "gdata-private.h"
-#include "gdata-atom.h"
+#include "atom/gdata-category.h"
+#include "atom/gdata-link.h"
+#include "atom/gdata-author.h"
 
+static void gdata_entry_dispose (GObject *object);
 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 void real_get_namespaces (GDataEntry *self, GHashTable *namespaces);
+static void pre_get_xml (GDataParsable *parsable, GString *xml_string);
+static void get_xml (GDataParsable *parsable, GString *xml_string);
+static void get_namespaces (GDataParsable *parsable, GHashTable *namespaces);
 
 struct _GDataEntryPrivate {
 	gchar *title;
@@ -55,10 +59,10 @@ struct _GDataEntryPrivate {
 	gchar *etag;
 	GTimeVal updated;
 	GTimeVal published;
-	GList *categories;
+	GList *categories; /* GDataCategory */
 	gchar *content;
-	GList *links;
-	GList *authors;
+	GList *links; /* GDataLink */
+	GList *authors; /* GDataAuthor */
 };
 
 enum {
@@ -84,14 +88,15 @@ gdata_entry_class_init (GDataEntryClass *klass)
 
 	gobject_class->set_property = gdata_entry_set_property;
 	gobject_class->get_property = gdata_entry_get_property;
+	gobject_class->dispose = gdata_entry_dispose;
 	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->get_namespaces = real_get_namespaces;
+	parsable_class->pre_get_xml = pre_get_xml;
+	parsable_class->get_xml = get_xml;
+	parsable_class->get_namespaces = get_namespaces;
 
 	g_object_class_install_property (gobject_class, PROP_TITLE,
 				g_param_spec_string ("title",
@@ -138,6 +143,33 @@ gdata_entry_init (GDataEntry *self)
 }
 
 static void
+gdata_entry_dispose (GObject *object)
+{
+	GDataEntryPrivate *priv = GDATA_ENTRY (object)->priv;
+
+	if (priv->categories != NULL) {
+		g_list_foreach (priv->categories, (GFunc) g_object_unref, NULL);
+		g_list_free (priv->categories);
+	}
+	priv->categories = NULL;
+
+	if (priv->links != NULL) {
+		g_list_foreach (priv->links, (GFunc) g_object_unref, NULL);
+		g_list_free (priv->links);
+	}
+	priv->links = NULL;
+
+	if (priv->authors != NULL) {
+		g_list_foreach (priv->authors, (GFunc) g_object_unref, NULL);
+		g_list_free (priv->authors);
+	}
+	priv->authors = NULL;
+
+	/* Chain up to the parent class */
+	G_OBJECT_CLASS (gdata_entry_parent_class)->dispose (object);
+}
+
+static void
 gdata_entry_finalize (GObject *object)
 {
 	GDataEntryPrivate *priv = GDATA_ENTRY_GET_PRIVATE (object);
@@ -145,13 +177,7 @@ gdata_entry_finalize (GObject *object)
 	g_free (priv->title);
 	xmlFree (priv->id);
 	xmlFree (priv->etag);
-	g_list_foreach (priv->categories, (GFunc) gdata_category_free, NULL);
-	g_list_free (priv->categories);
 	g_free (priv->content);
-	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);
 
 	/* Chain up to the parent class */
 	G_OBJECT_CLASS (gdata_entry_parent_class)->finalize (object);
@@ -282,19 +308,11 @@ parse_xml (GDataParsable *parsable, xmlDoc *doc, xmlNode *node, gpointer user_da
 		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");
+		GDataCategory *category = GDATA_CATEGORY (_gdata_parsable_new_from_xml_node (GDATA_TYPE_CATEGORY, "category", doc, node, NULL, error));
+		if (category == NULL)
+			return FALSE;
 
-		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);
@@ -304,62 +322,18 @@ parse_xml (GDataParsable *parsable, xmlDoc *doc, xmlNode *node, gpointer user_da
 		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);
+		GDataLink *link = GDATA_LINK (_gdata_parsable_new_from_xml_node (GDATA_TYPE_LINK, "link", doc, node, NULL, error));
+		if (link == NULL)
+			return FALSE;
 
-		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 (author_node, error);
-				xmlFree (name);
-				xmlFree (uri);
-				xmlFree (email);
-				return FALSE;
-			}
-
-			author_node = author_node->next;
-		}
+		GDataAuthor *author = GDATA_AUTHOR (_gdata_parsable_new_from_xml_node (GDATA_TYPE_AUTHOR, "author", doc, node, NULL, error));
+		if (author == NULL)
+			return FALSE;
 
-		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;
@@ -400,9 +374,19 @@ _gdata_entry_new_from_xml (GType entry_type, const gchar *xml, gint length, GErr
 }
 
 static void
-real_get_xml (GDataEntry *self, GString *xml_string)
+pre_get_xml (GDataParsable *parsable, GString *xml_string)
+{
+	GDataEntryPrivate *priv = GDATA_ENTRY (parsable)->priv;
+
+	/* Add the entry's ETag, if available */
+	if (priv->etag != NULL)
+		g_string_append_printf (xml_string, " gd:etag='%s'", priv->etag);
+}
+
+static void
+get_xml (GDataParsable *parsable, GString *xml_string)
 {
-	GDataEntryPrivate *priv = self->priv;
+	GDataEntryPrivate *priv = GDATA_ENTRY (parsable)->priv;
 	gchar *title;
 	GList *categories, *links, *authors;
 
@@ -431,73 +415,18 @@ real_get_xml (GDataEntry *self, GString *xml_string)
 		g_free (content);
 	}
 
-	for (categories = priv->categories; categories != NULL; categories = categories->next) {
-		GDataCategory *category = (GDataCategory*) categories->data;
-
-		g_string_append_printf (xml_string, "<category term='%s'", category->term);
-
-		if (G_LIKELY (category->scheme != NULL))
-			g_string_append_printf (xml_string, " scheme='%s'", category->scheme);
-
-		if (G_UNLIKELY (category->label != NULL)) {
-			gchar *label = g_markup_escape_text (category->label, -1);
-			g_string_append_printf (xml_string, " label='%s'", label);
-			g_free (label);
-		}
-
-		g_string_append (xml_string, "/>");
-	}
-
-	for (links = priv->links; links != NULL; links = links->next) {
-		GDataLink *link = (GDataLink*) links->data;
-
-		g_string_append_printf (xml_string, "<link href='%s'", link->href);
-
-		if (G_UNLIKELY (link->title != NULL)) {
-			gchar *link_title = g_markup_escape_text (link->title, -1);
-			g_string_append_printf (xml_string, " title='%s'", link_title);
-			g_free (link_title);
-		}
-
-		if (G_LIKELY (link->rel != NULL))
-			g_string_append_printf (xml_string, " rel='%s'", link->rel);
-		if (G_LIKELY (link->type != NULL))
-			g_string_append_printf (xml_string, " type='%s'", link->type);
-		if (G_UNLIKELY (link->hreflang != NULL))
-			g_string_append_printf (xml_string, " hreflang='%s'", link->hreflang);
-		if (G_UNLIKELY (link->length != -1))
-			g_string_append_printf (xml_string, " length='%i'", link->length);
-		g_string_append (xml_string, "/>");
-	}
-
-	for (authors = priv->authors; authors != NULL; authors = authors->next) {
-		GDataAuthor *author = (GDataAuthor*) authors->data;
-
-		gchar *name = g_markup_escape_text (author->name, -1);
-		g_string_append_printf (xml_string, "<author><name>%s</name>", name);
-		g_free (name);
+	for (categories = priv->categories; categories != NULL; categories = categories->next)
+		g_string_append (xml_string, _gdata_parsable_get_xml (GDATA_PARSABLE (categories->data), "category", FALSE));
 
-		if (G_LIKELY (author->uri != NULL)) {
-			gchar *uri = g_markup_escape_text (author->uri, -1);
-			g_string_append_printf (xml_string, "<uri>%s</uri>", uri);
-			g_free (uri);
-		}
-
-		if (G_UNLIKELY (author->email != NULL)) {
-			gchar *email = g_markup_escape_text (author->email, -1);
-			g_string_append_printf (xml_string, "<email>%s</email>", email);
-			g_free (email);
-		}
-
-		g_string_append (xml_string, "</author>");
-	}
+	for (links = priv->links; links != NULL; links = links->next)
+		g_string_append (xml_string, _gdata_parsable_get_xml (GDATA_PARSABLE (links->data), "link", FALSE));
 
-	if (_gdata_parsable_get_extra_xml (GDATA_PARSABLE (self)) != NULL)
-		g_string_append (xml_string, _gdata_parsable_get_extra_xml (GDATA_PARSABLE (self)));
+	for (authors = priv->authors; authors != NULL; authors = authors->next)
+		g_string_append (xml_string, _gdata_parsable_get_xml (GDATA_PARSABLE (authors->data), "author", FALSE));
 }
 
 static void
-real_get_namespaces (GDataEntry *self, GHashTable *namespaces)
+get_namespaces (GDataParsable *parsable, GHashTable *namespaces)
 {
 	g_hash_table_insert (namespaces, (gchar*) "gd", (gchar*) "http://schemas.google.com/g/2005";);
 }
@@ -635,8 +564,7 @@ gdata_entry_get_published (GDataEntry *self, GTimeVal *published)
  * @self: a #GDataEntry
  * @category: a #GDataCategory to add
  *
- * Adds @category to the list of categories in the given #GDataEntry. The #GDataEntry takes
- * ownership of @category, so it must not be freed after being added.
+ * Adds @category to the list of categories in the given #GDataEntry, and increments its reference count.
  *
  * Duplicate categories will not be added to the list.
  **/
@@ -644,12 +572,10 @@ void
 gdata_entry_add_category (GDataEntry *self, GDataCategory *category)
 {
 	g_return_if_fail (GDATA_IS_ENTRY (self));
-	g_return_if_fail (category != NULL);
+	g_return_if_fail (GDATA_IS_CATEGORY (category));
 
 	if (g_list_find_custom (self->priv->categories, category, (GCompareFunc) gdata_category_compare) == NULL)
-		self->priv->categories = g_list_prepend (self->priv->categories, category);
-	else
-		gdata_category_free (category);
+		self->priv->categories = g_list_prepend (self->priv->categories, g_object_ref (category));
 }
 
 /**
@@ -706,8 +632,7 @@ gdata_entry_set_content (GDataEntry *self, const gchar *content)
  * @self: a #GDataEntry
  * @link: a #GDataLink to add
  *
- * Adds @link to the list of links in the given #GDataEntry. The #GDataEntry takes
- * ownership of @link, so it must not be freed after being added.
+ * Adds @link to the list of links in the given #GDataEntry and increments its reference count.
  *
  * Duplicate links will not be added to the list.
  **/
@@ -716,18 +641,16 @@ gdata_entry_add_link (GDataEntry *self, GDataLink *link)
 {
 	/* TODO: More link API */
 	g_return_if_fail (GDATA_IS_ENTRY (self));
-	g_return_if_fail (link != NULL);
+	g_return_if_fail (GDATA_IS_LINK (link));
 
 	if (g_list_find_custom (self->priv->links, link, (GCompareFunc) gdata_link_compare) == NULL)
-		self->priv->links = g_list_prepend (self->priv->links, link);
-	else
-		gdata_link_free (link);
+		self->priv->links = g_list_prepend (self->priv->links, g_object_ref (link));
 }
 
 static gint
 link_compare_cb (const GDataLink *link, const gchar *rel)
 {
-	return strcmp (link->rel, rel);
+	return strcmp (gdata_link_get_relation_type ((GDataLink*) link), rel);
 }
 
 /**
@@ -752,7 +675,7 @@ gdata_entry_look_up_link (GDataEntry *self, const gchar *rel)
 	element = g_list_find_custom (self->priv->links, rel, (GCompareFunc) link_compare_cb);
 	if (element == NULL)
 		return NULL;
-	return (GDataLink*) (element->data);
+	return GDATA_LINK (element->data);
 }
 
 /**
@@ -760,8 +683,7 @@ gdata_entry_look_up_link (GDataEntry *self, const gchar *rel)
  * @self: a #GDataEntry
  * @author: a #GDataAuthor to add
  *
- * Adds @author to the list of authors in the given #GDataEntry. The #GDataEntry takes
- * ownership of @author, so it must not be freed after being added.
+ * Adds @author to the list of authors in the given #GDataEntry and increments its reference count.
  *
  * Duplicate authors will not be added to the list.
  **/
@@ -773,9 +695,7 @@ gdata_entry_add_author (GDataEntry *self, GDataAuthor *author)
 	g_return_if_fail (author != NULL);
 
 	if (g_list_find_custom (self->priv->authors, author, (GCompareFunc) gdata_author_compare) == NULL)
-		self->priv->authors = g_list_prepend (self->priv->authors, author);
-	else
-		gdata_author_free (author);
+		self->priv->authors = g_list_prepend (self->priv->authors, g_object_ref (author));
 }
 
 /**
@@ -798,20 +718,6 @@ gdata_entry_is_inserted (GDataEntry *self)
 	return FALSE;
 }
 
-static void
-build_namespaces_cb (gchar *prefix, gchar *href, GString *output)
-{
-	g_string_append_printf (output, " xmlns:%s='%s'", prefix, href);
-}
-
-static gboolean
-filter_namespaces_cb (gchar *prefix, gchar *href, GHashTable *canonical_namespaces)
-{
-	if (g_hash_table_lookup (canonical_namespaces, prefix) != NULL)
-		return TRUE;
-	return FALSE;
-}
-
 /**
  * gdata_entry_get_xml:
  * @self: a #GDataEntry
@@ -825,38 +731,5 @@ filter_namespaces_cb (gchar *prefix, gchar *href, GHashTable *canonical_namespac
 gchar *
 gdata_entry_get_xml (GDataEntry *self)
 {
-	GDataEntryClass *klass;
-	GString *xml_string;
-	GHashTable *namespaces, *extra_namespaces;
-
-	klass = GDATA_ENTRY_GET_CLASS (self);
-	g_assert (klass->get_xml != NULL);
-	g_assert (klass->get_namespaces != NULL);
-
-	/* 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 (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 (extra_namespaces, (GHFunc) build_namespaces_cb, xml_string);
-
-	/* Add the entry's ETag, if available */
-	if (self->priv->etag != NULL)
-		g_string_append_printf (xml_string, " gd:etag='%s'>", self->priv->etag);
-	else
-		g_string_append_c (xml_string, '>');
-
-	g_hash_table_destroy (namespaces);
-
-	/* Add the rest of the XML */
-	klass->get_xml (self, xml_string);
-	g_string_append (xml_string, "</entry>");
-
-	return g_string_free (xml_string, FALSE);
+	return _gdata_parsable_get_xml (GDATA_PARSABLE (self), "entry", TRUE);
 }
diff --git a/gdata/gdata-entry.h b/gdata/gdata-entry.h
index eb754bd..d8d190d 100644
--- a/gdata/gdata-entry.h
+++ b/gdata/gdata-entry.h
@@ -23,8 +23,10 @@
 #include <glib.h>
 #include <glib-object.h>
 
-#include <gdata/gdata-atom.h>
 #include <gdata/gdata-parsable.h>
+#include <gdata/atom/gdata-category.h>
+#include <gdata/atom/gdata-link.h>
+#include <gdata/atom/gdata-author.h>
 
 G_BEGIN_DECLS
 
@@ -47,16 +49,6 @@ typedef struct {
 	GDataEntryPrivate *priv;
 } GDataEntry;
 
-/*
- * GDataEntryClass:
- * @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
- * @get_namespaces: a function to return a string containing the namespace declarations used by the entry when represented
- * in XML form
- *
- * The class structure for the #GDataEntry type.
- */
 /**
  * GDataEntryClass:
  *
@@ -65,9 +57,6 @@ typedef struct {
 typedef struct {
 	/*< private >*/
 	GDataParsableClass parent;
-
-	void (*get_xml) (GDataEntry *self, GString *xml_string);
-	void (*get_namespaces) (GDataEntry *self, GHashTable *namespaces);
 } GDataEntryClass;
 
 GType gdata_entry_get_type (void) G_GNUC_CONST;
diff --git a/gdata/gdata-feed.c b/gdata/gdata-feed.c
index 260e248..d096453 100644
--- a/gdata/gdata-feed.c
+++ b/gdata/gdata-feed.c
@@ -58,10 +58,10 @@ struct _GDataFeedPrivate {
 	gchar *id;
 	gchar *etag;
 	GTimeVal updated;
-	GList *categories;
+	GList *categories; /* GDataCategory */
 	gchar *logo;
-	GList *links;
-	GList *authors;
+	GList *links; /* GDataLink */
+	GList *authors; /* GDataAuthor */
 	GDataGenerator *generator;
 	guint items_per_page;
 	guint start_index;
@@ -191,8 +191,9 @@ gdata_feed_class_init (GDataFeedClass *klass)
 	 * atom:generator</ulink>
 	 **/
 	g_object_class_install_property (gobject_class, PROP_GENERATOR,
-				g_param_spec_pointer ("generator",
+				g_param_spec_object ("generator",
 					"Generator", "Details of the software used to generate the feed.",
+					GDATA_TYPE_GENERATOR,
 					G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
 
 	/**
@@ -253,7 +254,7 @@ gdata_feed_init (GDataFeed *self)
 static void
 gdata_feed_dispose (GObject *object)
 {
-	GDataFeedPrivate *priv = GDATA_FEED_GET_PRIVATE (object);
+	GDataFeedPrivate *priv = GDATA_FEED (object)->priv;
 
 	if (priv->entries != NULL) {
 		g_list_foreach (priv->entries, (GFunc) g_object_unref, NULL);
@@ -261,6 +262,28 @@ gdata_feed_dispose (GObject *object)
 	}
 	priv->entries = NULL;
 
+	if (priv->categories != NULL) {
+		g_list_foreach (priv->categories, (GFunc) g_object_unref, NULL);
+		g_list_free (priv->categories);
+	}
+	priv->categories = NULL;
+
+	if (priv->links != NULL) {
+		g_list_foreach (priv->links, (GFunc) g_object_unref, NULL);
+		g_list_free (priv->links);
+	}
+	priv->links = NULL;
+
+	if (priv->authors != NULL) {
+		g_list_foreach (priv->authors, (GFunc) g_object_unref, NULL);
+		g_list_free (priv->authors);
+	}
+	priv->authors = NULL;
+
+	if (priv->generator != NULL)
+		g_object_unref (priv->generator);
+	priv->generator = NULL;
+
 	/* Chain up to the parent class */
 	G_OBJECT_CLASS (gdata_feed_parent_class)->dispose (object);
 }
@@ -268,20 +291,13 @@ gdata_feed_dispose (GObject *object)
 static void
 gdata_feed_finalize (GObject *object)
 {
-	GDataFeedPrivate *priv = GDATA_FEED_GET_PRIVATE (object);
+	GDataFeedPrivate *priv = GDATA_FEED (object)->priv;
 
 	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);
 	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);
 
 	/* Chain up to the parent class */
 	G_OBJECT_CLASS (gdata_feed_parent_class)->finalize (object);
@@ -290,7 +306,7 @@ gdata_feed_finalize (GObject *object)
 static void
 gdata_feed_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec)
 {
-	GDataFeedPrivate *priv = GDATA_FEED_GET_PRIVATE (object);
+	GDataFeedPrivate *priv = GDATA_FEED (object)->priv;
 
 	switch (property_id) {
 		case PROP_TITLE:
@@ -312,7 +328,7 @@ gdata_feed_get_property (GObject *object, guint property_id, GValue *value, GPar
 			g_value_set_string (value, priv->logo);
 			break;
 		case PROP_GENERATOR:
-			g_value_set_pointer (value, priv->generator);
+			g_value_set_object (value, priv->generator);
 			break;
 		case PROP_ITEMS_PER_PAGE:
 			g_value_set_uint (value, priv->items_per_page);
@@ -441,19 +457,11 @@ parse_xml (GDataParsable *parsable, xmlDoc *doc, xmlNode *node, gpointer user_da
 		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");
+		GDataCategory *category = GDATA_CATEGORY (_gdata_parsable_new_from_xml_node (GDATA_TYPE_CATEGORY, "category", doc, node, NULL, error));
+		if (category == NULL)
+			return FALSE;
 
-		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)
@@ -462,80 +470,31 @@ parse_xml (GDataParsable *parsable, xmlDoc *doc, xmlNode *node, gpointer user_da
 		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);
+		GDataLink *link = GDATA_LINK (_gdata_parsable_new_from_xml_node (GDATA_TYPE_LINK, "link", doc, node, NULL, error));
+		if (link == NULL)
+			return FALSE;
 
-		xmlFree (href);
-		xmlFree (rel);
-		xmlFree (type);
-		xmlFree (hreflang);
-		xmlFree (link_title);
-		xmlFree (link_length);
+		self->priv->links = g_list_prepend (self->priv->links, link);
 	} 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 (author_node, error);
-				xmlFree (name);
-				xmlFree (uri);
-				xmlFree (email);
-				return FALSE;
-			}
-
-			author_node = author_node->next;
-		}
+		GDataAuthor *author = GDATA_AUTHOR (_gdata_parsable_new_from_xml_node (GDATA_TYPE_AUTHOR, "author", doc, node, NULL, error));
+		if (author == NULL)
+			return FALSE;
 
-		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;
+		GDataGenerator *generator;
 
 		/* Duplicate checking */
 		if (self->priv->generator != NULL)
 			return gdata_parser_error_duplicate_element (node, 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);
+		generator = GDATA_GENERATOR (_gdata_parsable_new_from_xml_node (GDATA_TYPE_GENERATOR, "generator", doc, node, NULL, error));
+		if (generator == NULL)
+			return FALSE;
 
-		xmlFree (name);
-		xmlFree (uri);
-		xmlFree (version);
+		self->priv->generator = generator;
 	} else if (xmlStrcmp (node->name, (xmlChar*) "totalResults") == 0) {
 		/* openSearch:totalResults */
 		xmlChar *total_results_string;
@@ -641,7 +600,7 @@ _gdata_feed_new_from_xml (GType feed_type, const gchar *xml, gint length, GType
  *
  * Returns a list of the entries contained in this feed.
  *
- * Return value: a #GList of #GDataEntry<!-- -->s; free the list with g_list_free(), but do not unref the entries
+ * Return value: a #GList of #GDataEntry<!-- -->s
  **/
 GList *
 gdata_feed_get_entries (GDataFeed *self)
@@ -687,7 +646,7 @@ gdata_feed_look_up_entry (GDataFeed *self, const gchar *id)
  *
  * Returns a list of the categories listed in this feed.
  *
- * Return value: a #GList of #GDataCategory<!-- -->s; free the list with g_list_free(), but do not free the categories
+ * Return value: a #GList of #GDataCategory<!-- -->s
  **/
 GList *
 gdata_feed_get_categories (GDataFeed *self)
@@ -702,7 +661,7 @@ gdata_feed_get_categories (GDataFeed *self)
  *
  * Returns a list of the links listed in this feed.
  *
- * Return value: a #GList of #GDataLink<!-- -->s; free the list with g_list_free(), but do not free the links
+ * Return value: a #GList of #GDataLink<!-- -->s
  **/
 GList *
 gdata_feed_get_links (GDataFeed *self)
@@ -714,15 +673,15 @@ gdata_feed_get_links (GDataFeed *self)
 static gint
 link_compare_cb (const GDataLink *link, const gchar *rel)
 {
-	return strcmp (link->rel, rel);
+	return strcmp (gdata_link_get_relation_type ((GDataLink*) link), rel);
 }
 
 /**
  * gdata_feed_look_up_link:
  * @self: a #GDataFeed
- * @rel: the value of the <structfield>rel</structfield> attribute of the desired link
+ * @rel: the value of the #GDataLink:relation-type property of the desired link
  *
- * Looks up a link by <structfield>rel</structfield> value from the list of links in the feed.
+ * Looks up a link by #GDataLink:relation-type value from the list of links in the feed.
  *
  * Return value: a #GDataLink, or %NULL if one was not found
  *
@@ -739,7 +698,7 @@ gdata_feed_look_up_link (GDataFeed *self, const gchar *rel)
 	element = g_list_find_custom (self->priv->links, rel, (GCompareFunc) link_compare_cb);
 	if (element == NULL)
 		return NULL;
-	return (GDataLink*) (element->data);
+	return GDATA_LINK (element->data);
 }
 
 /**
@@ -748,7 +707,7 @@ gdata_feed_look_up_link (GDataFeed *self, const gchar *rel)
  *
  * Returns a list of the authors listed in this feed.
  *
- * Return value: a #GList of #GDataAuthor<!-- -->s; free the list with g_list_free(), but do not free the authors
+ * Return value: a #GList of #GDataAuthor<!-- -->s
  **/
 GList *
 gdata_feed_get_authors (GDataFeed *self)
diff --git a/gdata/gdata-feed.h b/gdata/gdata-feed.h
index 3eae443..15adf85 100644
--- a/gdata/gdata-feed.h
+++ b/gdata/gdata-feed.h
@@ -23,9 +23,10 @@
 #include <glib.h>
 #include <glib-object.h>
 
-#include <gdata/gdata-entry.h>
-#include <gdata/gdata-atom.h>
 #include <gdata/gdata-parsable.h>
+#include <gdata/gdata-entry.h>
+#include <gdata/atom/gdata-link.h>
+#include <gdata/atom/gdata-generator.h>
 
 G_BEGIN_DECLS
 
diff --git a/gdata/gdata-parsable.c b/gdata/gdata-parsable.c
index 05cc6ec..520e74e 100644
--- a/gdata/gdata-parsable.c
+++ b/gdata/gdata-parsable.c
@@ -39,6 +39,12 @@
 #include "gdata-private.h"
 #include "gdata-parser.h"
 
+GQuark
+gdata_parser_error_quark (void)
+{
+	return g_quark_from_static_string ("gdata-parser-error-quark");
+}
+
 static void gdata_parsable_finalize (GObject *object);
 static gboolean real_parse_xml (GDataParsable *parsable, xmlDoc *doc, xmlNode *node, gpointer user_data, GError **error);
 
@@ -197,16 +203,76 @@ _gdata_parsable_new_from_xml_node (GType parsable_type, const gchar *first_eleme
 	return parsable;
 }
 
-const gchar *
-_gdata_parsable_get_extra_xml (GDataParsable *self)
+static void
+build_namespaces_cb (gchar *prefix, gchar *href, GString *output)
 {
-	g_return_val_if_fail (GDATA_IS_PARSABLE (self), NULL);
-	return self->priv->extra_xml->str;
+	g_string_append_printf (output, " xmlns:%s='%s'", prefix, href);
 }
 
-GHashTable *
-_gdata_parsable_get_extra_namespaces (GDataParsable *self)
+static gboolean
+filter_namespaces_cb (gchar *prefix, gchar *href, GHashTable *canonical_namespaces)
 {
-	g_return_val_if_fail (GDATA_IS_PARSABLE (self), NULL);
-	return self->priv->extra_namespaces;
+	if (g_hash_table_lookup (canonical_namespaces, prefix) != NULL)
+		return TRUE;
+	return FALSE;
+}
+
+gchar *
+_gdata_parsable_get_xml (GDataParsable *self, const gchar *first_element, gboolean at_top_level)
+{
+	GDataParsableClass *klass;
+	GString *xml_string;
+	GHashTable *namespaces = NULL; /* shut up, gcc */
+
+	klass = GDATA_PARSABLE_GET_CLASS (self);
+
+	/* Get the namespaces the class uses */
+	if (at_top_level == TRUE && klass->get_namespaces != NULL) {
+		namespaces = g_hash_table_new (g_str_hash, g_str_equal);
+		klass->get_namespaces (self, namespaces);
+
+		/* Remove any duplicate extra namespaces */
+		g_hash_table_foreach_remove (self->priv->extra_namespaces, (GHRFunc) filter_namespaces_cb, namespaces);
+	}
+
+	/* Build up the namespace list */
+	xml_string = g_string_sized_new (100);
+	g_string_append_printf (xml_string, "<%s", first_element);
+
+	/* We only include the normal namespaces if we're not at the top level of XML building */
+	if (at_top_level == TRUE) {
+		g_string_append (xml_string, " xmlns='http://www.w3.org/2005/Atom'");
+		g_hash_table_foreach (namespaces, (GHFunc) build_namespaces_cb, xml_string);
+		g_hash_table_destroy (namespaces);
+	}
+
+	g_hash_table_foreach (self->priv->extra_namespaces, (GHFunc) build_namespaces_cb, xml_string);
+
+	/* Add anything the class thinks is suitable */
+	if (klass->pre_get_xml != NULL)
+		klass->pre_get_xml (self, xml_string);
+
+	/* We can self-close in certain circumstances */
+	if (klass->get_xml == NULL &&
+	    (self->priv->extra_xml == NULL || self->priv->extra_xml->str == NULL || *(self->priv->extra_xml->str) == '\0')) {
+		/* Self-closing */
+		g_string_append (xml_string, "/>");
+		goto finish;
+	}
+
+	/* We have some content */
+	g_string_append_c (xml_string, '>');
+
+	/* Add the rest of the XML */
+	if (klass->get_xml != NULL)
+		klass->get_xml (self, xml_string);
+
+	/* Any extra XML? */
+	if (self->priv->extra_xml != NULL && self->priv->extra_xml->str != NULL)
+		g_string_append (xml_string, self->priv->extra_xml->str);
+
+	g_string_append_printf (xml_string, "</%s>", first_element);
+
+finish:
+	return g_string_free (xml_string, FALSE);
 }
diff --git a/gdata/gdata-parsable.h b/gdata/gdata-parsable.h
index 20c742d..76dd671 100644
--- a/gdata/gdata-parsable.h
+++ b/gdata/gdata-parsable.h
@@ -26,6 +26,23 @@
 
 G_BEGIN_DECLS
 
+/**
+ * GDataParserError:
+ * @GDATA_PARSER_ERROR_PARSING_STRING: Error parsing the XML syntax itself
+ * @GDATA_PARSER_ERROR_EMPTY_DOCUMENT: Empty document
+ * @GDATA_PARSER_ERROR_UNHANDLED_XML_ELEMENT: Unknown or unhandled XML element (fatal error)
+ *
+ * Error codes for XML parsing operations.
+ **/
+typedef enum {
+	GDATA_PARSER_ERROR_PARSING_STRING = 1,
+	GDATA_PARSER_ERROR_EMPTY_DOCUMENT,
+	GDATA_PARSER_ERROR_UNHANDLED_XML_ELEMENT
+} GDataParserError;
+
+#define GDATA_PARSER_ERROR gdata_parser_error_quark ()
+GQuark gdata_parser_error_quark (void) G_GNUC_CONST;
+
 #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))
@@ -54,6 +71,10 @@ typedef struct {
  * 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
+ * @pre_get_xml: a function called before building the XML representation of the children of the #GDataParsable, which allows attributes of the root
+ * XML node to be added to @xml_string
+ * @get_xml: a function to build an XML representation of the #GDataParsable in its current state, appending it to the provided #GString
+ * @get_namespaces: a function to return a string containing the namespace declarations used by the @parsable when represented in XML form
  *
  * The class structure for the #GDataParsable class.
  *
@@ -65,9 +86,13 @@ typedef struct {
 	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);
+
+	void (*pre_get_xml) (GDataParsable *parsable, GString *xml_string);
+	void (*get_xml) (GDataParsable *parsable, GString *xml_string);
+	void (*get_namespaces) (GDataParsable *parsable, GHashTable *namespaces);
 } GDataParsableClass;
 
-GType gdata_parsable_get_type (void);
+GType gdata_parsable_get_type (void) G_GNUC_CONST;
 
 G_END_DECLS
 
diff --git a/gdata/gdata-parser.c b/gdata/gdata-parser.c
index f5059f9..b577f99 100644
--- a/gdata/gdata-parser.c
+++ b/gdata/gdata-parser.c
@@ -24,15 +24,8 @@
 #include <time.h>
 #include <libxml/parser.h>
 
-#include "gdata-service.h"
 #include "gdata-parser.h"
-#include "gdata-private.h"
-
-GQuark
-gdata_parser_error_quark (void)
-{
-	return g_quark_from_static_string ("gdata-parser-error-quark");
-}
+#include "gdata-service.h"
 
 static gchar *
 print_element (xmlNode *node)
diff --git a/gdata/gdata-parser.h b/gdata/gdata-parser.h
index d932a59..194baed 100644
--- a/gdata/gdata-parser.h
+++ b/gdata/gdata-parser.h
@@ -24,22 +24,15 @@
 
 G_BEGIN_DECLS
 
-/**
- * GDataParserError:
- * @GDATA_PARSER_ERROR_PARSING_STRING: Error parsing the XML syntax itself
- * @GDATA_PARSER_ERROR_EMPTY_DOCUMENT: Empty document
- * @GDATA_PARSER_ERROR_UNHANDLED_XML_ELEMENT: Unknown or unhandled XML element (fatal error)
- *
- * Error codes for XML parsing operations.
- **/
-typedef enum {
-	GDATA_PARSER_ERROR_PARSING_STRING = 1,
-	GDATA_PARSER_ERROR_EMPTY_DOCUMENT,
-	GDATA_PARSER_ERROR_UNHANDLED_XML_ELEMENT
-} GDataParserError;
-
-#define GDATA_PARSER_ERROR gdata_parser_error_quark ()
-GQuark gdata_parser_error_quark (void) G_GNUC_CONST;
+gboolean gdata_parser_error_required_content_missing (xmlNode *element, GError **error);
+gboolean gdata_parser_error_not_iso8601_format (xmlNode *element, const gchar *actual_value, GError **error);
+gboolean gdata_parser_error_unhandled_element (xmlNode *element, GError **error);
+gboolean gdata_parser_error_unknown_property_value (xmlNode *element, const gchar *property_name, const gchar *actual_value, GError **error);
+gboolean gdata_parser_error_required_property_missing (xmlNode *element, const gchar *property_name, GError **error);
+gboolean gdata_parser_error_required_element_missing (const gchar *element_name, const gchar *parent_element_name, GError **error);
+gboolean gdata_parser_error_duplicate_element (xmlNode *element, GError **error);
+gboolean gdata_parser_time_val_from_date (const gchar *date, GTimeVal *_time);
+gchar *gdata_parser_date_from_time_val (GTimeVal *_time) G_GNUC_WARN_UNUSED_RESULT;
 
 G_END_DECLS
 
diff --git a/gdata/gdata-private.h b/gdata/gdata-private.h
index 290b70b..80faa5c 100644
--- a/gdata/gdata-private.h
+++ b/gdata/gdata-private.h
@@ -41,8 +41,7 @@ GDataParsable *_gdata_parsable_new_from_xml (GType parsable_type, const gchar *f
 					     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);
+gchar *_gdata_parsable_get_xml (GDataParsable *self, const gchar *first_element, gboolean at_top_level) G_GNUC_WARN_UNUSED_RESULT;
 
 #include "gdata-feed.h"
 GDataFeed *_gdata_feed_new_from_xml (GType feed_type, const gchar *xml, gint length, GType entry_type,
@@ -52,15 +51,6 @@ GDataFeed *_gdata_feed_new_from_xml (GType feed_type, const gchar *xml, gint len
 GDataEntry *_gdata_entry_new_from_xml (GType entry_type, const gchar *xml, gint length, GError **error) G_GNUC_WARN_UNUSED_RESULT;
 
 #include "gdata-parser.h"
-gboolean gdata_parser_error_required_content_missing (xmlNode *element, GError **error);
-gboolean gdata_parser_error_not_iso8601_format (xmlNode *element, const gchar *actual_value, GError **error);
-gboolean gdata_parser_error_unhandled_element (xmlNode *element, GError **error);
-gboolean gdata_parser_error_unknown_property_value (xmlNode *element, const gchar *property_name, const gchar *actual_value, GError **error);
-gboolean gdata_parser_error_required_property_missing (xmlNode *element, const gchar *property_name, GError **error);
-gboolean gdata_parser_error_required_element_missing (const gchar *element_name, const gchar *parent_element_name, GError **error);
-gboolean gdata_parser_error_duplicate_element (xmlNode *element, GError **error);
-gboolean gdata_parser_time_val_from_date (const gchar *date, GTimeVal *_time);
-gchar *gdata_parser_date_from_time_val (GTimeVal *_time) G_GNUC_WARN_UNUSED_RESULT;
 
 G_END_DECLS
 
diff --git a/gdata/gdata-service.c b/gdata/gdata-service.c
index 8f298e9..48114fc 100644
--- a/gdata/gdata-service.c
+++ b/gdata/gdata-service.c
@@ -1039,10 +1039,10 @@ gdata_service_query (GDataService *self, const gchar *feed_uri, GDataQuery *quer
 	if (query != NULL && feed != NULL) {
 		link = gdata_feed_look_up_link (feed, "next");
 		if (link != NULL)
-			_gdata_query_set_next_uri (query, link->href);
+			_gdata_query_set_next_uri (query, gdata_link_get_uri (link));
 		link = gdata_feed_look_up_link (feed, "previous");
 		if (link != NULL)
-			_gdata_query_set_previous_uri (query, link->href);
+			_gdata_query_set_previous_uri (query, gdata_link_get_uri (link));
 	}
 
 	return feed;
@@ -1379,7 +1379,7 @@ gdata_service_update_entry (GDataService *self, GDataEntry *entry, GCancellable
 	/* Get the edit URI */
 	link = gdata_entry_look_up_link (entry, "edit");
 	g_assert (link != NULL);
-	message = soup_message_new (SOUP_METHOD_PUT, link->href);
+	message = soup_message_new (SOUP_METHOD_PUT, gdata_link_get_uri (link));
 
 	/* Make sure subclasses set their headers */
 	klass = GDATA_SERVICE_GET_CLASS (self);
@@ -1546,7 +1546,7 @@ gdata_service_delete_entry (GDataService *self, GDataEntry *entry, GCancellable
 	/* Get the edit URI */
 	link = gdata_entry_look_up_link (entry, "edit");
 	g_assert (link != NULL);
-	message = soup_message_new (SOUP_METHOD_DELETE, link->href);
+	message = soup_message_new (SOUP_METHOD_DELETE, gdata_link_get_uri (link));
 
 	/* Make sure subclasses set their headers */
 	klass = GDATA_SERVICE_GET_CLASS (self);
diff --git a/gdata/gdata.h b/gdata/gdata.h
index 05e9f9d..54a2ece 100644
--- a/gdata/gdata.h
+++ b/gdata/gdata.h
@@ -33,7 +33,14 @@
 #include <gdata/gdata-parsable.h>
 
 /* Namespaces */
-#include <gdata/gdata-atom.h>
+
+/* Atom */
+#include <gdata/atom/gdata-author.h>
+#include <gdata/atom/gdata-category.h>
+#include <gdata/atom/gdata-generator.h>
+#include <gdata/atom/gdata-link.h>
+
+/* Others */
 #include <gdata/gdata-gdata.h>
 #include <gdata/gdata-media-rss.h>
 
diff --git a/gdata/gdata.symbols b/gdata/gdata.symbols
index 349e904..2bfd775 100644
--- a/gdata/gdata.symbols
+++ b/gdata/gdata.symbols
@@ -92,19 +92,6 @@ gdata_query_set_entry_id
 gdata_query_get_etag
 gdata_query_set_etag
 gdata_g_time_val_get_type
-gdata_soup_uri_get_type
-gdata_category_new
-gdata_category_compare
-gdata_category_free
-gdata_link_new
-gdata_link_compare
-gdata_link_free
-gdata_author_new
-gdata_author_compare
-gdata_author_free
-gdata_generator_new
-gdata_generator_compare
-gdata_generator_free
 gdata_media_rating_new
 gdata_media_rating_free
 gdata_media_restriction_new
@@ -370,3 +357,41 @@ gdata_parsable_get_type
 gdata_calendar_feed_get_type
 gdata_calendar_feed_get_timezone
 gdata_calendar_feed_get_times_cleaned
+gdata_category_get_type
+gdata_category_new
+gdata_category_compare
+gdata_category_get_term
+gdata_category_set_term
+gdata_category_get_scheme
+gdata_category_set_scheme
+gdata_category_get_label
+gdata_category_set_label
+gdata_link_get_type
+gdata_link_new
+gdata_link_compare
+gdata_link_get_uri
+gdata_link_set_uri
+gdata_link_get_relation_type
+gdata_link_set_relation_type
+gdata_link_get_content_type
+gdata_link_set_content_type
+gdata_link_get_language
+gdata_link_set_language
+gdata_link_get_title
+gdata_link_set_title
+gdata_link_get_length
+gdata_link_set_length
+gdata_author_get_type
+gdata_author_new
+gdata_author_compare
+gdata_author_get_name
+gdata_author_set_name
+gdata_author_get_uri
+gdata_author_set_uri
+gdata_author_get_email_address
+gdata_author_set_email_address
+gdata_generator_get_type
+gdata_generator_compare
+gdata_generator_get_name
+gdata_generator_get_uri
+gdata_generator_get_version
diff --git a/gdata/services/calendar/gdata-calendar-calendar.c b/gdata/services/calendar/gdata-calendar-calendar.c
index e9c72e4..b179bc6 100644
--- a/gdata/services/calendar/gdata-calendar-calendar.c
+++ b/gdata/services/calendar/gdata-calendar-calendar.c
@@ -47,9 +47,9 @@ static void gdata_calendar_calendar_access_handler_init (GDataAccessHandlerIface
 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 void get_xml (GDataParsable *parsable, GString *xml_string);
 static gboolean parse_xml (GDataParsable *parsable, xmlDoc *doc, xmlNode *node, gpointer user_data, GError **error);
-static void get_namespaces (GDataEntry *entry, GHashTable *namespaces);
+static void get_namespaces (GDataParsable *parsable, GHashTable *namespaces);
 
 struct _GDataCalendarCalendarPrivate {
 	gchar *timezone;
@@ -81,7 +81,6 @@ 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));
 
@@ -90,9 +89,8 @@ gdata_calendar_calendar_class_init (GDataCalendarCalendarClass *klass)
 	gobject_class->finalize = gdata_calendar_calendar_finalize;
 
 	parsable_class->parse_xml = parse_xml;
-
-	entry_class->get_xml = get_xml;
-	entry_class->get_namespaces = get_namespaces;
+	parsable_class->get_xml = get_xml;
+	parsable_class->get_namespaces = get_namespaces;
 
 	/**
 	 * GDataCalendarCalendar:timezone:
@@ -388,13 +386,13 @@ parse_xml (GDataParsable *parsable, xmlDoc *doc, xmlNode *node, gpointer user_da
 }
 
 static void
-get_xml (GDataEntry *entry, GString *xml_string)
+get_xml (GDataParsable *parsable, GString *xml_string)
 {
 	gchar *colour;
-	GDataCalendarCalendarPrivate *priv = GDATA_CALENDAR_CALENDAR (entry)->priv;
+	GDataCalendarCalendarPrivate *priv = GDATA_CALENDAR_CALENDAR (parsable)->priv;
 
 	/* Chain up to the parent class */
-	GDATA_ENTRY_CLASS (gdata_calendar_calendar_parent_class)->get_xml (entry, xml_string);
+	GDATA_PARSABLE_CLASS (gdata_calendar_calendar_parent_class)->get_xml (parsable, xml_string);
 
 	/* Add all the Calendar-specific XML */
 	if (priv->timezone != NULL) {
@@ -419,10 +417,10 @@ get_xml (GDataEntry *entry, GString *xml_string)
 }
 
 static void
-get_namespaces (GDataEntry *entry, GHashTable *namespaces)
+get_namespaces (GDataParsable *parsable, GHashTable *namespaces)
 {
 	/* Chain up to the parent class */
-	GDATA_ENTRY_CLASS (gdata_calendar_calendar_parent_class)->get_namespaces (entry, namespaces);
+	GDATA_PARSABLE_CLASS (gdata_calendar_calendar_parent_class)->get_namespaces (parsable, namespaces);
 
 	g_hash_table_insert (namespaces, (gchar*) "gCal", (gchar*) "http://schemas.google.com/gCal/2005";);
 	g_hash_table_insert (namespaces, (gchar*) "app", (gchar*) "http://www.w3.org/2007/app";);
diff --git a/gdata/services/calendar/gdata-calendar-event.c b/gdata/services/calendar/gdata-calendar-event.c
index 0dda59e..da7ee30 100644
--- a/gdata/services/calendar/gdata-calendar-event.c
+++ b/gdata/services/calendar/gdata-calendar-event.c
@@ -45,9 +45,9 @@
 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 void get_xml (GDataParsable *parsable, GString *xml_string);
 static gboolean parse_xml (GDataParsable *parsable, xmlDoc *doc, xmlNode *node, gpointer user_data, GError **error);
-static void get_namespaces (GDataEntry *entry, GHashTable *namespaces);
+static void get_namespaces (GDataParsable *parsable, GHashTable *namespaces);
 
 struct _GDataCalendarEventPrivate {
 	GTimeVal edited;
@@ -93,7 +93,6 @@ 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));
 
@@ -102,9 +101,8 @@ gdata_calendar_event_class_init (GDataCalendarEventClass *klass)
 	gobject_class->finalize = gdata_calendar_event_finalize;
 
 	parsable_class->parse_xml = parse_xml;
-
-	entry_class->get_xml = get_xml;
-	entry_class->get_namespaces = get_namespaces;
+	parsable_class->get_xml = get_xml;
+	parsable_class->get_namespaces = get_namespaces;
 
 	/**
 	 * GDataCalendarEvent:edited:
@@ -658,13 +656,13 @@ parse_xml (GDataParsable *parsable, xmlDoc *doc, xmlNode *node, gpointer user_da
 }
 
 static void
-get_xml (GDataEntry *entry, GString *xml_string)
+get_xml (GDataParsable *parsable, GString *xml_string)
 {
-	GDataCalendarEventPrivate *priv = GDATA_CALENDAR_EVENT (entry)->priv;
+	GDataCalendarEventPrivate *priv = GDATA_CALENDAR_EVENT (parsable)->priv;
 	GList *i;
 
 	/* Chain up to the parent class */
-	GDATA_ENTRY_CLASS (gdata_calendar_event_parent_class)->get_xml (entry, xml_string);
+	GDATA_PARSABLE_CLASS (gdata_calendar_event_parent_class)->get_xml (parsable, xml_string);
 
 	/* Add all the Calendar-specific XML */
 
@@ -780,10 +778,10 @@ get_xml (GDataEntry *entry, GString *xml_string)
 }
 
 static void
-get_namespaces (GDataEntry *entry, GHashTable *namespaces)
+get_namespaces (GDataParsable *parsable, GHashTable *namespaces)
 {
 	/* Chain up to the parent class */
-	GDATA_ENTRY_CLASS (gdata_calendar_event_parent_class)->get_namespaces (entry, namespaces);
+	GDATA_PARSABLE_CLASS (gdata_calendar_event_parent_class)->get_namespaces (parsable, namespaces);
 
 	g_hash_table_insert (namespaces, (gchar*) "gd", (gchar*) "http://schemas.google.com/g/2005";);
 	g_hash_table_insert (namespaces, (gchar*) "gCal", (gchar*) "http://schemas.google.com/gCal/2005";);
diff --git a/gdata/services/contacts/gdata-contacts-contact.c b/gdata/services/contacts/gdata-contacts-contact.c
index c5bbc96..26a2eee 100644
--- a/gdata/services/contacts/gdata-contacts-contact.c
+++ b/gdata/services/contacts/gdata-contacts-contact.c
@@ -49,9 +49,9 @@
 
 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 void get_xml (GDataParsable *parsable, GString *xml_string);
 static gboolean parse_xml (GDataParsable *parsable, xmlDoc *doc, xmlNode *node, gpointer user_data, GError **error);
-static void get_namespaces (GDataEntry *entry, GHashTable *namespaces);
+static void get_namespaces (GDataParsable *parsable, GHashTable *namespaces);
 
 struct _GDataContactsContactPrivate {
 	GTimeVal edited;
@@ -80,7 +80,6 @@ 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));
 
@@ -88,9 +87,8 @@ gdata_contacts_contact_class_init (GDataContactsContactClass *klass)
 	gobject_class->finalize = gdata_contacts_contact_finalize;
 
 	parsable_class->parse_xml = parse_xml;
-
-	entry_class->get_xml = get_xml;
-	entry_class->get_namespaces = get_namespaces;
+	parsable_class->get_xml = get_xml;
+	parsable_class->get_namespaces = get_namespaces;
 
 	/**
 	 * GDataContactsContact:edited:
@@ -544,13 +542,13 @@ get_group_xml_cb (const gchar *href, gpointer deleted, GString *xml_string)
 }
 
 static void
-get_xml (GDataEntry *entry, GString *xml_string)
+get_xml (GDataParsable *parsable, GString *xml_string)
 {
-	GDataContactsContactPrivate *priv = GDATA_CONTACTS_CONTACT (entry)->priv;
+	GDataContactsContactPrivate *priv = GDATA_CONTACTS_CONTACT (parsable)->priv;
 	GList *i;
 
 	/* Chain up to the parent class */
-	GDATA_ENTRY_CLASS (gdata_contacts_contact_parent_class)->get_xml (entry, xml_string);
+	GDATA_PARSABLE_CLASS (gdata_contacts_contact_parent_class)->get_xml (parsable, xml_string);
 
 	/* E-mail addresses */
 	for (i = priv->email_addresses; i != NULL; i = i->next) {
@@ -678,10 +676,10 @@ get_xml (GDataEntry *entry, GString *xml_string)
 }
 
 static void
-get_namespaces (GDataEntry *entry, GHashTable *namespaces)
+get_namespaces (GDataParsable *parsable, GHashTable *namespaces)
 {
 	/* Chain up to the parent class */
-	GDATA_ENTRY_CLASS (gdata_contacts_contact_parent_class)->get_namespaces (entry, namespaces);
+	GDATA_PARSABLE_CLASS (gdata_contacts_contact_parent_class)->get_namespaces (parsable, namespaces);
 
 	g_hash_table_insert (namespaces, (gchar*) "gd", (gchar*) "http://schemas.google.com/g/2005";);
 	g_hash_table_insert (namespaces, (gchar*) "gContact", (gchar*) "http://schemas.google.com/contact/2008";);
@@ -1307,7 +1305,7 @@ gdata_contacts_contact_get_photo (GDataContactsContact *self, GDataContactsServi
 	/* Get the photo URI */
 	link = gdata_entry_look_up_link (GDATA_ENTRY (self), "http://schemas.google.com/contacts/2008/rel#photo";);
 	g_assert (link != NULL);
-	message = soup_message_new (SOUP_METHOD_GET, link->href);
+	message = soup_message_new (SOUP_METHOD_GET, gdata_link_get_uri (link));
 
 	/* Make sure the headers are set */
 	klass = GDATA_SERVICE_GET_CLASS (service);
@@ -1395,9 +1393,9 @@ gdata_contacts_contact_set_photo (GDataContactsContact *self, GDataService *serv
 	link = gdata_entry_look_up_link (GDATA_ENTRY (self), "http://schemas.google.com/contacts/2008/rel#photo";);
 	g_assert (link != NULL);
 	if (deleting_photo == TRUE)
-		message = soup_message_new (SOUP_METHOD_DELETE, link->href);
+		message = soup_message_new (SOUP_METHOD_DELETE, gdata_link_get_uri (link));
 	else
-		message = soup_message_new (SOUP_METHOD_PUT, link->href);
+		message = soup_message_new (SOUP_METHOD_PUT, gdata_link_get_uri (link));
 
 	/* Make sure the headers are set */
 	klass = GDATA_SERVICE_GET_CLASS (service);
diff --git a/gdata/services/youtube/gdata-youtube-service.c b/gdata/services/youtube/gdata-youtube-service.c
index 8db2405..5f5b946 100644
--- a/gdata/services/youtube/gdata-youtube-service.c
+++ b/gdata/services/youtube/gdata-youtube-service.c
@@ -40,6 +40,7 @@
 #include "gdata-service.h"
 #include "gdata-private.h"
 #include "gdata-parser.h"
+#include "atom/gdata-link.h"
 
 /* Standards reference here: http://code.google.com/apis/youtube/2.0/reference.html */
 
@@ -543,7 +544,7 @@ gdata_youtube_service_query_related (GDataYouTubeService *self, GDataYouTubeVide
 	}
 
 	/* Execute the query */
-	return gdata_service_query (GDATA_SERVICE (self), related_link->href, query,
+	return gdata_service_query (GDATA_SERVICE (self), gdata_link_get_uri (related_link), query,
 				    GDATA_TYPE_YOUTUBE_VIDEO, cancellable, progress_callback, progress_user_data, error);
 }
 
@@ -583,7 +584,7 @@ gdata_youtube_service_query_related_async (GDataYouTubeService *self, GDataYouTu
 		return;
 	}
 
-	gdata_service_query_async (GDATA_SERVICE (self), related_link->href, query,
+	gdata_service_query_async (GDATA_SERVICE (self), gdata_link_get_uri (related_link), query,
 				   GDATA_TYPE_YOUTUBE_VIDEO, cancellable, progress_callback, progress_user_data, callback, user_data);
 }
 
diff --git a/gdata/services/youtube/gdata-youtube-video.c b/gdata/services/youtube/gdata-youtube-video.c
index f498731..b540798 100644
--- a/gdata/services/youtube/gdata-youtube-video.c
+++ b/gdata/services/youtube/gdata-youtube-video.c
@@ -46,9 +46,9 @@
 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 void get_xml (GDataParsable *parsable, GString *xml_string);
 static gboolean parse_xml (GDataParsable *parsable, xmlDoc *doc, xmlNode *node, gpointer user_data, GError **error);
-static void get_namespaces (GDataEntry *entry, GHashTable *namespaces);
+static void get_namespaces (GDataParsable *parsable, GHashTable *namespaces);
 
 struct _GDataYouTubeVideoPrivate {
 	guint view_count;
@@ -114,7 +114,6 @@ 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));
 
@@ -123,9 +122,8 @@ gdata_youtube_video_class_init (GDataYouTubeVideoClass *klass)
 	gobject_class->finalize = gdata_youtube_video_finalize;
 
 	parsable_class->parse_xml = parse_xml;
-
-	entry_class->get_xml = get_xml;
-	entry_class->get_namespaces = get_namespaces;
+	parsable_class->get_xml = get_xml;
+	parsable_class->get_namespaces = get_namespaces;
 
 	/**
 	 * GDataYouTubeVideo:view-count:
@@ -1034,13 +1032,13 @@ parse_xml (GDataParsable *parsable, xmlDoc *doc, xmlNode *node, gpointer user_da
 }
 
 static void
-get_xml (GDataEntry *entry, GString *xml_string)
+get_xml (GDataParsable *parsable, GString *xml_string)
 {
-	GDataYouTubeVideoPrivate *priv = GDATA_YOUTUBE_VIDEO (entry)->priv;
+	GDataYouTubeVideoPrivate *priv = GDATA_YOUTUBE_VIDEO (parsable)->priv;
 	gchar *category;
 
 	/* Chain up to the parent class */
-	GDATA_ENTRY_CLASS (gdata_youtube_video_parent_class)->get_xml (entry, xml_string);
+	GDATA_PARSABLE_CLASS (gdata_youtube_video_parent_class)->get_xml (parsable, xml_string);
 
 	/* Add all the YouTube-specific XML */
 	g_string_append (xml_string, "<media:group><media:category");
@@ -1099,10 +1097,10 @@ get_xml (GDataEntry *entry, GString *xml_string)
 }
 
 static void
-get_namespaces (GDataEntry *entry, GHashTable *namespaces)
+get_namespaces (GDataParsable *parsable, GHashTable *namespaces)
 {
 	/* Chain up to the parent class */
-	GDATA_ENTRY_CLASS (gdata_youtube_video_parent_class)->get_namespaces (entry, namespaces);
+	GDATA_PARSABLE_CLASS (gdata_youtube_video_parent_class)->get_namespaces (parsable, namespaces);
 
 	g_hash_table_insert (namespaces, (gchar*) "media", (gchar*) "http://search.yahoo.com/mrss/";);
 	g_hash_table_insert (namespaces, (gchar*) "yt", (gchar*) "http://gdata.youtube.com/schemas/2007";);
diff --git a/gdata/tests/calendar.c b/gdata/tests/calendar.c
index ba7e8b5..b7b00d8 100644
--- a/gdata/tests/calendar.c
+++ b/gdata/tests/calendar.c
@@ -595,9 +595,9 @@ test_acls_insert_rule (void)
 	g_assert (categories != NULL);
 	g_assert_cmpuint (g_list_length (categories), ==, 1);
 	category = categories->data;
-	g_assert_cmpstr (category->term, ==, "http://schemas.google.com/acl/2007#accessRule";);
-	g_assert_cmpstr (category->scheme, ==, "http://schemas.google.com/g/2005#kind";);
-	g_assert (category->label == NULL);
+	g_assert_cmpstr (gdata_category_get_term (category), ==, "http://schemas.google.com/acl/2007#accessRule";);
+	g_assert_cmpstr (gdata_category_get_scheme (category), ==, "http://schemas.google.com/g/2005#kind";);
+	g_assert (gdata_category_get_label (category) == NULL);
 
 	/* TODO: Check more properties? */
 
diff --git a/gdata/tests/general.c b/gdata/tests/general.c
index e00f2ad..4fc44ce 100644
--- a/gdata/tests/general.c
+++ b/gdata/tests/general.c
@@ -52,11 +52,16 @@ test_entry_get_xml (void)
 	gdata_entry_add_category (entry, category);
 
 	/* Links */
-	link = gdata_link_new ("http://test.com/";, "self", "application/atom+xml", NULL, NULL, -1);
+	link = gdata_link_new ("http://test.com/";, "self");
+	gdata_link_set_content_type (link, "application/atom+xml");
 	gdata_entry_add_link (entry, link);
-	link = gdata_link_new ("http://example.com/";, NULL, NULL, NULL, NULL, -1);
+	link = gdata_link_new ("http://example.com/";, NULL);
 	gdata_entry_add_link (entry, link);
-	link = gdata_link_new ("http://test.mn/";, "related", "text/html", "mn", "A treatise on Mongolian test websites & other stuff.", 5010);
+	link = gdata_link_new ("http://test.mn/";, "related");
+	gdata_link_set_content_type (link, "text/html");
+	gdata_link_set_language (link, "mn");
+	gdata_link_set_title (link, "A treatise on Mongolian test websites & other stuff.");
+	gdata_link_set_length (link, 5010);
 	gdata_entry_add_link (entry, link);
 
 	/* Authors */



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