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