Index: libgweather/location-entry.h
===================================================================
--- libgweather/location-entry.h	(revision 0)
+++ libgweather/location-entry.h	(revision 0)
@@ -0,0 +1,36 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+
+#ifndef GWEATHER_LOCATION_ENTRY_H
+#define GWEATHER_LOCATION_ENTRY_H 1
+
+#include <gtk/gtk.h>
+#include <libgweather/weather.h>
+
+#define GWEATHER_TYPE_LOCATION_ENTRY            (gweather_location_entry_get_type ())
+#define GWEATHER_LOCATION_ENTRY(object)         (G_TYPE_CHECK_INSTANCE_CAST ((object), GWEATHER_TYPE_LOCATION_ENTRY, GWeatherLocationEntry))
+#define GWEATHER_LOCATION_ENTRY_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass), GWEATHER_TYPE_LOCATION_ENTRY, GWeatherLocationEntryClass))
+#define GWEATHER_IS_LOCATION_ENTRY(object)      (G_TYPE_CHECK_INSTANCE_TYPE ((object), GWEATHER_TYPE_LOCATION_ENTRY))
+#define GWEATHER_IS_LOCATION_ENTRY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GWEATHER_TYPE_LOCATION_ENTRY))
+#define GWEATHER_LOCATION_ENTRY_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj), GWEATHER_TYPE_LOCATION_ENTRY, GWeatherLocationEntryClass))
+
+typedef struct {
+    GtkEntry parent;
+
+    /*< private >*/
+    WeatherLocation *location;
+} GWeatherLocationEntry;
+
+typedef struct {
+    GtkEntryClass parent_class;
+
+} GWeatherLocationEntryClass;
+
+GType            gweather_location_entry_get_type     (void);
+
+GtkWidget       *gweather_location_entry_new          (void);
+
+void             gweather_location_entry_set_location (GWeatherLocationEntry *entry,
+						       WeatherLocation       *location);
+WeatherLocation *gweather_location_entry_get_location (GWeatherLocationEntry *entry);
+
+#endif
Index: libgweather/location-entry.c
===================================================================
--- libgweather/location-entry.c	(revision 0)
+++ libgweather/location-entry.c	(revision 0)
@@ -0,0 +1,584 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#define GWEATHER_I_KNOW_THIS_IS_UNSTABLE
+#include "location-entry.h"
+#include "xml-utils.h"
+
+#include <string.h>
+#include <libxml/xmlreader.h>
+
+G_DEFINE_TYPE (GWeatherLocationEntry, gweather_location_entry, GTK_TYPE_ENTRY)
+
+enum {
+    PROP_0,
+
+    PROP_LOCATION,
+
+    LAST_PROP
+};
+
+static void set_property (GObject *object, guint prop_id,
+			  const GValue *value, GParamSpec *pspec);
+static void get_property (GObject *object, guint prop_id,
+			  GValue *value, GParamSpec *pspec);
+
+enum
+{
+    GWEATHER_LOCATION_ENTRY_COL_DISPLAY_NAME = 0,
+    GWEATHER_LOCATION_ENTRY_COL_LOCATION,
+    GWEATHER_LOCATION_ENTRY_COL_COMPARE_NAME,
+    GWEATHER_LOCATION_ENTRY_NUM_COLUMNS
+}; 
+
+static GtkTreeModel *gweather_location_entry_load_locations (void);
+static gboolean matcher (GtkEntryCompletion *completion, const char *key,
+			 GtkTreeIter *iter, gpointer user_data);
+static gboolean match_selected (GtkEntryCompletion *completion,
+				GtkTreeModel       *model,
+				GtkTreeIter        *iter,
+				gpointer            entry);
+
+static void
+gweather_location_entry_init (GWeatherLocationEntry *entry)
+{
+    GtkEntryCompletion *completion;
+    GtkTreeModel *model;
+
+    completion = gtk_entry_completion_new ();
+
+    gtk_entry_completion_set_popup_set_width (completion, FALSE);
+    gtk_entry_completion_set_text_column (completion, GWEATHER_LOCATION_ENTRY_COL_DISPLAY_NAME);
+    gtk_entry_completion_set_match_func (completion, matcher, NULL, NULL);
+
+    model = gweather_location_entry_load_locations ();
+    gtk_entry_completion_set_model (completion, model);
+    g_object_unref (model);
+
+    g_signal_connect (completion, "match_selected",
+		      G_CALLBACK (match_selected), entry);
+
+    gtk_entry_set_completion (GTK_ENTRY (entry), completion);
+    g_object_unref (completion);
+}
+
+static void
+finalize (GObject *object)
+{
+    GWeatherLocationEntry *entry = GWEATHER_LOCATION_ENTRY (object);
+
+    if (entry->location)
+	weather_location_free (entry->location);
+
+    G_OBJECT_CLASS (gweather_location_entry_parent_class)->finalize (object);
+}
+
+static void
+gweather_location_entry_class_init (GWeatherLocationEntryClass *location_entry_class)
+{
+    GObjectClass *object_class = G_OBJECT_CLASS (location_entry_class);
+
+    object_class->finalize = finalize;
+    object_class->set_property = set_property;
+    object_class->get_property = get_property;
+
+    /* properties */
+    g_object_class_install_property (
+	object_class, PROP_LOCATION,
+	g_param_spec_pointer ("location",
+			      "Location",
+			      "The selected WeatherLocation",
+			      G_PARAM_READWRITE));
+}
+
+static void
+set_property (GObject *object, guint prop_id,
+	      const GValue *value, GParamSpec *pspec)
+{
+    switch (prop_id) {
+    case PROP_LOCATION:
+	gweather_location_entry_set_location (GWEATHER_LOCATION_ENTRY (object),
+					      g_value_get_pointer (value));
+	break;
+    default:
+	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+	break;
+    }
+}
+
+static void
+get_property (GObject *object, guint prop_id,
+	      GValue *value, GParamSpec *pspec)
+{
+    GWeatherLocationEntry *entry = GWEATHER_LOCATION_ENTRY (object);
+
+    switch (prop_id) {
+    case PROP_LOCATION:
+	g_value_set_pointer (value, entry->location);
+	break;
+    default:
+	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+	break;
+    }
+}
+
+void
+gweather_location_entry_set_location (GWeatherLocationEntry *entry,
+				      WeatherLocation       *location)
+{
+    g_return_if_fail (GWEATHER_IS_LOCATION_ENTRY (entry));
+
+    if (entry->location)
+	weather_location_free (entry->location);
+    if (location)
+	entry->location = weather_location_clone (location);
+    else
+	entry->location = NULL;
+    g_object_notify (G_OBJECT (entry), "location");
+}
+
+WeatherLocation *
+gweather_location_entry_get_location (GWeatherLocationEntry *entry)
+{
+    g_return_val_if_fail (GWEATHER_IS_LOCATION_ENTRY (entry), NULL);
+
+    if (entry->location)
+	return weather_location_clone (entry->location);
+    else
+	return NULL;
+}
+
+/* FIXME: this is wrong, we need to sort according to the XML hierarchy */
+static gint
+gweather_xml_location_sort_func (GtkTreeModel *model, GtkTreeIter *a,
+                                 GtkTreeIter *b, gpointer user_data)
+{
+    gint res;
+    gchar *name_a, *name_b;
+        
+    gtk_tree_model_get (model, a, GWEATHER_LOCATION_ENTRY_COL_COMPARE_NAME, &name_a, -1);
+    gtk_tree_model_get (model, b, GWEATHER_LOCATION_ENTRY_COL_COMPARE_NAME, &name_b, -1);
+        
+    res = g_utf8_collate (name_a, name_b);
+    
+    g_free (name_a);
+    g_free (name_b);
+    
+    return res;
+}
+ 
+static void
+set_node_name (GtkTreeStore *store, GtkTreeIter *self,
+	       const char *parent_name, const char *name, const char *cityname)
+{
+    char *display_name;
+    char *normal, *fold;
+
+    if (cityname) {
+	display_name = g_strdup_printf ("%s (%s)%s%s", cityname, name,
+					parent_name ? ", " : "",
+					parent_name ? parent_name : "");
+    } else {
+	display_name = g_strdup_printf ("%s%s%s", name,
+					parent_name ? ", " : "",
+					parent_name ? parent_name : "");
+    }
+    normal = g_utf8_normalize (display_name, -1, G_NORMALIZE_ALL);
+    fold = g_utf8_casefold (normal, -1);
+
+    gtk_tree_store_set (store, self,
+			GWEATHER_LOCATION_ENTRY_COL_DISPLAY_NAME, display_name,
+			GWEATHER_LOCATION_ENTRY_COL_COMPARE_NAME, fold,
+			-1);
+
+    g_free (fold);
+    g_free (normal);
+    g_free (display_name);
+}
+
+static gboolean
+gweather_xml_parse_node (xmlTextReaderPtr xml,
+			 GtkTreeStore *store, const char *parent_name,
+                         const char *dflt_radar, const char *dflt_zone,
+                         const char *dflt_country_code,
+			 const char *dflt_tz_hint, const char *cityname)
+{
+    char *name, *code, *zone, *radar, *coordinates;
+    char *country_code, *tz_hint;
+    char **city, *nocity = NULL;
+    GtkTreeIter iter, *self;
+    gboolean is_location;
+    char *tagname;
+    gboolean ret = FALSE;
+    int tagtype;
+
+    if ((tagname = (char *) xmlTextReaderName (xml)) == NULL)
+	return FALSE;
+
+    if (!strcmp (tagname, "city"))
+	city = &name;
+    else
+	city = &nocity;
+
+    is_location = !strcmp (tagname, "location");
+
+    /* if we're processing the top-level, then don't create a new iter */
+    if (!strcmp (tagname, "gweather"))
+	self = NULL;
+    else {
+	self = &iter;
+	/* insert this node into the tree */
+	gtk_tree_store_append (store, self, NULL);
+    }
+
+    xmlFree (tagname);
+
+    coordinates = NULL;
+    radar = NULL;
+    zone = NULL;
+    code = NULL;
+    name = NULL;
+
+    country_code = dflt_country_code ? (char *) xmlStrdup ((xmlChar *) dflt_country_code) : NULL;
+    tz_hint = dflt_tz_hint ? (char *) xmlStrdup ((xmlChar *) dflt_tz_hint) : NULL;
+
+    /* absorb the start tag */
+    if (xmlTextReaderRead (xml) != 1)
+	goto error_out;
+
+    /* start parsing the actual contents of the node */
+    while ((tagtype = xmlTextReaderNodeType (xml)) !=
+	   XML_READER_TYPE_END_ELEMENT) {
+
+	/* skip non-element types */
+	if (tagtype != XML_READER_TYPE_ELEMENT) {
+	    if (xmlTextReaderRead (xml) != 1)
+		goto error_out;
+
+	    continue;
+	}
+    
+	tagname = (char *) xmlTextReaderName (xml);
+
+	if (!strcmp (tagname, "region") || !strcmp (tagname, "country")) {
+	    /* Recursively handle sub-sections, but don't pass on parent
+	     * attributes. (This is when we're *starting* a region or
+	     * country, meaning that @self is either the <gweather> node
+	     * or a <region> node.)
+	     */
+	    if (!gweather_xml_parse_node (xml, store, NULL, NULL,
+					  NULL, NULL, NULL, NULL))
+		goto error_out;
+	} else if (!strcmp (tagname, "state") || !strcmp (tagname, "city") ||
+		   (!strcmp (tagname, "location") && !*city)) {
+	    char *display_name;
+
+	    gtk_tree_model_get (GTK_TREE_MODEL (store), self,
+				GWEATHER_LOCATION_ENTRY_COL_DISPLAY_NAME, &display_name,
+				-1);
+
+	    /* recursively handle sub-sections */
+	    if (!gweather_xml_parse_node (xml, store, display_name,
+					  radar, zone, country_code, tz_hint,
+					  NULL)) {
+		g_free (display_name);
+		goto error_out;
+	    }
+	    g_free (display_name);
+	} else if (!strcmp (tagname, "location") /* && *city */) {
+	    /* recursively handle sub-sections */
+	    if (!gweather_xml_parse_node (xml, store, parent_name,
+					  radar, zone, country_code, tz_hint,
+					  *city))
+		goto error_out;
+	} else if (!strcmp (tagname, "name")) {
+	    xmlFree (name);
+	    if ((name = gweather_xml_get_localized_value (xml)) == NULL)
+		goto error_out;
+	    if (self)
+		set_node_name (store, self, parent_name, name, cityname);
+	} else if (!strcmp (tagname, "iso-code")) {
+	    xmlFree (country_code);
+	    if ((country_code = gweather_xml_get_value (xml)) == NULL)
+		goto error_out;
+	} else if (!strcmp (tagname, "tz-hint")) {
+	    xmlFree (tz_hint);
+	    if ((tz_hint = gweather_xml_get_value (xml)) == NULL)
+		goto error_out;
+	} else if (!strcmp (tagname, "code")) {
+	    xmlFree (code);
+	    if ((code = gweather_xml_get_value (xml)) == NULL)
+		goto error_out;
+	} else if (!strcmp (tagname, "zone")) {
+	    xmlFree (zone);
+	    if ((zone = gweather_xml_get_value (xml)) == NULL)
+		goto error_out;
+	} else if (!strcmp (tagname, "radar")) {
+	    xmlFree (radar);
+	    if ((radar = gweather_xml_get_value (xml)) == NULL)
+		goto error_out;
+	} else if (!strcmp (tagname, "coordinates")) {
+	    xmlFree (coordinates);
+	    if ((coordinates = gweather_xml_get_value (xml)) == NULL)
+		goto error_out;
+	} else /* some strange tag */ {
+	    /* skip past it */
+	    char *junk;
+	    junk = gweather_xml_get_value (xml);
+	    if (junk)
+		xmlFree (junk);
+	}
+
+	xmlFree (tagname);
+    }
+
+    /* if this is an actual location, setup the WeatherLocation for it */
+    if (is_location) {
+	WeatherLocation *new_loc;
+
+	if (cityname == NULL)
+	    cityname = name;
+
+	if (radar != NULL)
+	    dflt_radar = radar;
+
+	if (zone != NULL)
+	    dflt_zone = zone;
+
+	new_loc =  weather_location_new (cityname, code, dflt_zone,
+					 dflt_radar, coordinates,
+					 country_code, tz_hint);
+
+	gtk_tree_store_set (store, &iter, GWEATHER_LOCATION_ENTRY_COL_LOCATION, new_loc, -1);
+    }
+
+    /* if this is a city with only one location, then we merge the location and
+     * the city */
+    if (*city) {
+	int n_children;
+
+	n_children = gtk_tree_model_iter_n_children (GTK_TREE_MODEL (store),
+						     &iter);
+	if (n_children == 1) {
+	    GtkTreeIter child;
+	    WeatherLocation *loc;
+
+	    gtk_tree_model_iter_children (GTK_TREE_MODEL (store), &child, &iter);
+	    gtk_tree_model_get (GTK_TREE_MODEL (store), &child,
+				GWEATHER_LOCATION_ENTRY_COL_LOCATION, &loc, -1);
+	    gtk_tree_store_remove (store, &child);
+	    gtk_tree_store_set (store, &iter, GWEATHER_LOCATION_ENTRY_COL_LOCATION, loc, -1);
+	}
+    }
+
+    /* absorb the end tag.  in the case of processing a <gweather> then 'self'
+       is NULL.  In this case, we let this fail since we might be at EOF */
+    if (xmlTextReaderRead (xml) == 1 || !self)
+	ret = TRUE;
+
+error_out:
+    xmlFree (name);
+    xmlFree (code);
+    xmlFree (zone);
+    xmlFree (radar);
+    xmlFree (coordinates);
+    xmlFree (country_code);
+    xmlFree (tz_hint);
+
+    return ret;
+}
+
+static GtkTreeModel *
+gweather_location_entry_load_locations (void)
+{
+    const char * const *languages;
+    int i;
+    char *filename;
+    char *tagname, *format;
+    GtkTreeSortable *sortable;
+    GtkTreeStore *store = NULL;
+    xmlTextReaderPtr xml;
+    int keep_going;
+
+    /* First try to load a locale-specific XML. It's much faster. */
+    languages = g_get_language_names ();
+    filename = NULL;
+
+    for (i = 0; languages[i] != NULL; i++) {
+	filename = g_strdup_printf ("%s/Locations.%s.xml",
+				    GWEATHER_XML_LOCATION_DIR, languages[i]);
+
+	if (g_file_test (filename, G_FILE_TEST_IS_REGULAR))
+	    break;
+
+	g_free (filename);
+	filename = NULL;
+    }
+
+    /* Fallback on the file containing either all translations, or only
+     * the english names (depending on the configure flags). Note that it's
+     * also the file that is advertised in our pkg-config file, so it's
+     * part of the API. */
+    if (!filename)
+	filename = g_strdup (GWEATHER_XML_LOCATION_DIR"/Locations.xml");
+
+    /* Open the xml file containing the different locations */
+    xml = xmlNewTextReaderFilename (filename);
+    g_free (filename);
+
+    if (xml == NULL)
+	return NULL;
+
+    /* fast forward to the first element */
+    do {
+	/* if we encounter a problem here, exit right away */
+	if (xmlTextReaderRead (xml) != 1)
+	    goto error_out;
+    } while (xmlTextReaderNodeType (xml) != XML_READER_TYPE_ELEMENT);
+
+    /* check the name and format */
+    tagname = (char *) xmlTextReaderName (xml);
+    keep_going = tagname && !strcmp (tagname, "gweather");
+    xmlFree (tagname);
+
+    if (!keep_going)
+	goto error_out;
+
+    format = (char *) xmlTextReaderGetAttribute (xml, (xmlChar *) "format");
+    keep_going = format && !strcmp (format, "1.0");
+    xmlFree (format);
+
+    if (!keep_going)
+	goto error_out;
+
+    store = gtk_tree_store_new (3, G_TYPE_STRING, G_TYPE_POINTER, G_TYPE_STRING);
+
+    if (!gweather_xml_parse_node (xml, store, NULL, NULL, NULL, NULL, NULL, NULL)) {
+	g_object_unref (store);
+	store = NULL;
+	goto error_out;
+    }
+
+    /* Sort the tree */
+    sortable = GTK_TREE_SORTABLE (store);
+    gtk_tree_sortable_set_default_sort_func (sortable,
+					     &gweather_xml_location_sort_func,
+					     NULL, NULL);
+    gtk_tree_sortable_set_sort_column_id (sortable, 
+					  GTK_TREE_SORTABLE_DEFAULT_SORT_COLUMN_ID,
+					  GTK_SORT_ASCENDING);
+error_out:
+    xmlFreeTextReader (xml);
+
+    return (GtkTreeModel *)store;
+}
+
+static char *
+find_word (const char *full_name, const char *word, int word_len,
+	   gboolean whole_word, gboolean is_first_word)
+{
+    char *p = (char *)full_name - 1;
+
+    while ((p = strchr (p + 1, *word))) {
+	if (strncmp (p, word, word_len) != 0)
+	    continue;
+
+	if (p > (char *)full_name) {
+	    char *prev = g_utf8_prev_char (p);
+
+	    /* Make sure p points to the start of a word */
+	    if (g_unichar_isalpha (g_utf8_get_char (prev)))
+		continue;
+
+	    /* If we're matching the first word of the key, it has to
+	     * match the first word of the location, city, state, or
+	     * country. Eg, it either matches the start of the string
+	     * (which we already know it doesn't at this point) or
+	     * it is preceded by the string ", " (which isn't actually
+	     * a perfect test. FIXME)
+	     */
+	    if (is_first_word) {
+		if (prev == (char *)full_name || strncmp (prev - 1, ", ", 2) != 0)
+		    continue;
+	    }
+	}
+
+	if (whole_word && g_unichar_isalpha (g_utf8_get_char (p + word_len)))
+	    continue;
+
+	return p;
+    }
+    return NULL;
+}
+
+static gboolean
+matcher (GtkEntryCompletion *completion, const char *key,
+	 GtkTreeIter *iter, gpointer user_data)
+{
+    char *name, *name_mem;
+    WeatherLocation *loc;
+    gboolean is_first_word = TRUE, match;
+    int len;
+
+    gtk_tree_model_get (gtk_entry_completion_get_model (completion), iter,
+			GWEATHER_LOCATION_ENTRY_COL_COMPARE_NAME, &name_mem,
+			GWEATHER_LOCATION_ENTRY_COL_LOCATION, &loc,
+			-1);
+    name = name_mem;
+
+    if (!loc) {
+	g_free (name_mem);
+	return FALSE;
+    }
+
+    /* All but the last word in KEY must match a full word from NAME,
+     * in order (but possibly skipping some words from NAME).
+     */
+    len = strcspn (key, " ");
+    while (key[len]) {
+	name = find_word (name, key, len, TRUE, is_first_word);
+	if (!name) {
+	    g_free (name_mem);
+	    return FALSE;
+	}
+
+	key += len;
+	while (*key && !g_unichar_isalpha (g_utf8_get_char (key)))
+	    key = g_utf8_next_char (key);
+	while (*name && !g_unichar_isalpha (g_utf8_get_char (name)))
+	    name = g_utf8_next_char (name);
+
+	len = strcspn (key, " ");
+	is_first_word = FALSE;
+    }
+
+    /* The last word in KEY must match a prefix of a following word in NAME */
+    match = find_word (name, key, strlen (key), FALSE, is_first_word) != NULL;
+    g_free (name_mem);
+    return match;
+}
+
+static gboolean
+match_selected (GtkEntryCompletion *completion,
+		GtkTreeModel       *model,
+		GtkTreeIter        *iter,
+		gpointer            entry)
+{
+    WeatherLocation *loc;
+
+    gtk_tree_model_get (model, iter,
+			GWEATHER_LOCATION_ENTRY_COL_LOCATION, &loc,
+			-1);
+    gweather_location_entry_set_location (entry, loc);
+
+    return FALSE;
+}
+
+GtkWidget *
+gweather_location_entry_new (void)
+{
+    return g_object_new (GWEATHER_TYPE_LOCATION_ENTRY, NULL);
+}
Index: libgweather/xml-utils.h
===================================================================
--- libgweather/xml-utils.h	(revision 0)
+++ libgweather/xml-utils.h	(revision 0)
@@ -0,0 +1,11 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+
+#ifndef GWEATHER_XML_UTILS_H
+#define GWEATHER_XML_UTILS_H 1
+
+#include <libxml/xmlreader.h>
+
+char *gweather_xml_get_value           (xmlTextReaderPtr xml);
+char *gweather_xml_get_localized_value (xmlTextReaderPtr xml);
+
+#endif
Index: libgweather/xml-utils.c
===================================================================
--- libgweather/xml-utils.c	(revision 0)
+++ libgweather/xml-utils.c	(revision 0)
@@ -0,0 +1,127 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "xml-utils.h"
+
+#include <string.h>
+#include <glib.h>
+#include <libxml/xmlreader.h>
+
+/**
+ * gweather_xml_get_value:
+ * @xml: an #xmlTextReaderPtr
+ *
+ * Gets the text of the element whose start tag @xml is pointing to.
+ * Leaves @xml pointing at the next node after the element's end tag.
+ *
+ * Return value: the text of the current node, as a libxml-allocated
+ * string, or %NULL if the node is empty.
+ **/
+char *
+gweather_xml_get_value (xmlTextReaderPtr xml)
+{
+    char *value;
+
+    /* check for null node */
+    if (xmlTextReaderIsEmptyElement (xml))
+	return NULL;
+
+    /* the next "node" is the text node containing the value we want to get */
+    if (xmlTextReaderRead (xml) != 1)
+	return NULL;
+
+    value = (char *) xmlTextReaderValue (xml);
+
+    /* move on to the end of this node */
+    while (xmlTextReaderNodeType (xml) != XML_READER_TYPE_END_ELEMENT) {
+	if (xmlTextReaderRead (xml) != 1) {
+	    xmlFree (value);
+	    return NULL;
+	}
+    }
+
+    /* consume the end element too */
+    if (xmlTextReaderRead (xml) != 1) {
+	xmlFree (value);
+	return NULL;
+    }
+
+    return value;
+}
+
+/**
+ * gweather_xml_get_localized_value:
+ * @xml: an #xmlTextReaderPtr
+ *
+ * Looks at the name of the element @xml is currently pointing to, and
+ * returns the content of either that node, or a following node with
+ * the same name but an "xml:lang" attribute naming one of the locale
+ * languages. Leaves @xml pointing to the next node after the last
+ * consecutive element with the same name as the original element.
+ *
+ * Return value: the localized (or unlocalized) text, as a
+ * libxml-allocated string, or %NULL if the node is empty.
+ **/
+char *
+gweather_xml_get_localized_value (xmlTextReaderPtr xml)
+{
+    const char * const *locales;
+    const char *this_language;
+    int best_match = INT_MAX;
+    const char *lang;
+    char *tagname, *next_tagname;
+    gboolean keep_going;
+    char *name = NULL;
+    int i;
+
+    locales = g_get_language_names ();
+    tagname = (char *) xmlTextReaderName (xml);
+
+    do {
+	/* First let's get the language */
+	lang = (const char *) xmlTextReaderConstXmlLang (xml);
+
+	if (lang == NULL)
+	    this_language = "C";
+	else
+	    this_language = lang;
+
+	/* the next "node" is text node containing the actual name */
+	if (xmlTextReaderRead (xml) != 1) {
+	    if (name)
+		xmlFree (name);
+	    return NULL;
+	}
+
+	for (i = 0; locales[i] && i < best_match; i++) {
+	    if (!strcmp (locales[i], this_language)) {
+		/* if we've already encounted a less accurate
+		   translation, then free it */
+		g_free (name);
+
+		name = (char *) xmlTextReaderValue (xml);
+		best_match = i;
+
+		break;
+	    }
+	}
+
+	while (xmlTextReaderNodeType (xml) != XML_READER_TYPE_ELEMENT) {
+	    if (xmlTextReaderRead (xml) != 1) {
+		xmlFree (name);
+		return NULL;
+	    }
+	}
+
+	/* if the next tag has the same name then keep going */
+	next_tagname = (char *) xmlTextReaderName (xml);
+	keep_going = !strcmp (next_tagname, tagname);
+	xmlFree (next_tagname);
+
+    } while (keep_going);
+
+    return name;
+}
Index: libgweather/gweather-xml.c
===================================================================
--- libgweather/gweather-xml.c	(revision 249)
+++ libgweather/gweather-xml.c	(working copy)
@@ -72,6 +72,7 @@
 
 #define GWEATHER_I_KNOW_THIS_IS_UNSTABLE
 #include "gweather-xml.h"
+#include "xml-utils.h"
 
 static gint
 gweather_xml_location_sort_func( GtkTreeModel *model, GtkTreeIter *a,
@@ -97,101 +98,6 @@
     return res;
 }
  
-static char*
-gweather_xml_get_value( xmlTextReaderPtr xml )
-{
-  char* value;
-
-  /* check for null node */
-  if ( xmlTextReaderIsEmptyElement( xml ) )
-    return NULL;
-    
-  /* the next "node" is the text node containing the value we want to get */
-  if( xmlTextReaderRead( xml ) != 1 )
-    return NULL;
-
-  value = (char *) xmlTextReaderValue( xml );
-
-  /* move on to the end of this node */
-  while( xmlTextReaderNodeType( xml ) != XML_READER_TYPE_END_ELEMENT )
-    if( xmlTextReaderRead( xml ) != 1 )
-    {
-      xmlFree( value );
-      return NULL;
-    }
-
-  /* consume the end element too */
-  if( xmlTextReaderRead( xml ) != 1 )
-  {
-    xmlFree( value );
-    return NULL;
-  }
-    
-  return value;
-}
-
-static char *
-gweather_xml_parse_name( xmlTextReaderPtr xml )
-{
-  const char * const *locales;
-  const char *this_language;
-  int best_match = INT_MAX;
-  char *lang, *tagname;
-  gboolean keep_going;
-  char *name = NULL;
-  int i;
-
-  locales = g_get_language_names();
-
-  do
-  {
-    /* First let's get the language */
-    lang = (char *) xmlTextReaderXmlLang( xml );
-
-    if( lang == NULL )
-      this_language = "C";
-    else
-      this_language = lang;
-
-    /* the next "node" is text node containing the actual name */
-    if( xmlTextReaderRead( xml ) != 1 )
-    {
-      xmlFree( lang );
-      return NULL;
-    }
-
-    for( i = 0; locales[i] && i < best_match; i++ )
-      if( !strcmp( locales[i], this_language ) )
-      {
-        /* if we've already encounted a less accurate
-           translation, then free it */
-        xmlFree( name );
-
-        name = (char *) xmlTextReaderValue( xml );
-        best_match = i;
-
-        break;
-      }
-
-    xmlFree( lang );
-
-    while( xmlTextReaderNodeType( xml ) != XML_READER_TYPE_ELEMENT )
-      if( xmlTextReaderRead( xml ) != 1 )
-      {
-        xmlFree( name );
-        return NULL;
-      }
-
-    /* if the next tag is another <name> then keep going */
-    tagname = (char *) xmlTextReaderName( xml );
-    keep_going = !strcmp( tagname, "name" );
-    xmlFree( tagname );
-
-  } while( keep_going );
-
-  return name;
-}
-
 static gboolean
 gweather_xml_parse_node (xmlTextReaderPtr xml,
 			 GtkTreeStore *store, GtkTreeIter *parent,
@@ -272,7 +178,7 @@
     else if ( !strcmp( tagname, "name" ) )
     {
       xmlFree( name );
-      if( (name = gweather_xml_parse_name( xml )) == NULL )
+      if( (name = gweather_xml_get_localized_value( xml )) == NULL )
         goto error_out;
     }
     else if ( !strcmp( tagname, "iso-code" ) )
@@ -347,7 +253,7 @@
     gtk_tree_store_set( store, &iter, GWEATHER_XML_COL_POINTER, new_loc, -1 );
   }
   /* if this is not a location and there's no child, then it's useless */
-  else if ( !gtk_tree_model_iter_has_child( GTK_TREE_MODEL (store), &iter ) )
+  else if ( self && !gtk_tree_model_iter_has_child( GTK_TREE_MODEL (store), &iter ) )
   {
     gtk_tree_store_remove( store, &iter );
   }
Index: libgweather/Makefile.am
===================================================================
--- libgweather/Makefile.am	(revision 249)
+++ libgweather/Makefile.am	(working copy)
@@ -13,7 +13,9 @@
 	weather-sun.c \
 	gweather-prefs.c gweather-prefs.h \
 	gweather-gconf.c gweather-gconf.h \
-	gweather-xml.c gweather-xml.h
+	gweather-xml.c gweather-xml.h \
+	location-entry.c location-entry.h \
+	xml-utils.c xml-utils.h
 
 libgweather_la_CFLAGS = \
 	-I$(top_srcdir)			\
@@ -48,8 +50,20 @@
 	$(GNOME_VFS_APPLETS_LIB) \
 	libgweather.la
 
+test_locations_SOURCES = test_locations.c
+
+test_locations_CFLAGS = \
+	-I$(top_srcdir)			\
+	-I$(srcdir)			\
+	$(WARN_CFLAGS)			\
+	$(GTK_CFLAGS)			\
+	$(GNOME_VFS_APPLETS_CFLAGS)	\
+	-DG_LOG_DOMAIN=\"GWeather\"
+
+test_locations_LDADD = libgweather.la
+
 noinst_HEADERS = weather-priv.h
-noinst_PROGRAMS = test_metar
+noinst_PROGRAMS = test_metar test_locations
 
 schemadir   = @GCONF_SCHEMA_FILE_DIR@
 schema_in_files = gweather.schemas.in
Index: libgweather/test_locations.c
===================================================================
--- libgweather/test_locations.c	(revision 0)
+++ libgweather/test_locations.c	(revision 0)
@@ -0,0 +1,64 @@
+
+#define GWEATHER_I_KNOW_THIS_IS_UNSTABLE
+#include "location-entry.h"
+
+#ifdef HAVE_TIMEZONE_MENU
+#include "timezone-menu.h"
+#endif
+
+static void
+deleted (GtkWidget *widget, GdkEvent *event, gpointer data)
+{
+    gtk_main_quit ();
+}
+
+#ifdef HAVE_TIMEZONE_MENU
+static void
+location_changed (GObject *object, GParamSpec *param, gpointer tzmenu)
+{
+    GWeatherLocationEntry *entry = GWEATHER_LOCATION_ENTRY (object);
+    WeatherLocation *location;
+
+    location = gweather_location_entry_get_location (entry);
+    g_return_if_fail (location != NULL);
+    gweather_timezone_menu_set_timezone (tzmenu, location->tz_hint);
+    weather_location_free (location);
+}
+#endif
+
+int
+main (int argc, char **argv)
+{
+    GtkWidget *window, *vbox, *entry;
+#ifdef HAVE_TIMEZONE_MENU
+    GtkWidget *combo;
+#endif
+    gtk_init (&argc, &argv);
+
+    window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
+    gtk_window_set_title (GTK_WINDOW (window), "location");
+    gtk_container_set_border_width (GTK_CONTAINER (window), 8);
+    g_signal_connect (window, "delete-event",
+		      G_CALLBACK (deleted), NULL);
+
+    vbox = gtk_vbox_new (FALSE, 8);
+    gtk_container_add (GTK_CONTAINER (window), vbox);
+
+    entry = gweather_location_entry_new ();
+    gtk_widget_set_size_request (entry, 400, -1);
+    gtk_box_pack_start (GTK_BOX (vbox), entry, FALSE, TRUE, 0);
+
+#ifdef HAVE_TIMEZONE_MENU
+    combo = gweather_timezone_menu_new ();
+    gtk_box_pack_start (GTK_BOX (vbox), combo, FALSE, TRUE, 0);
+
+    g_signal_connect (entry, "notify::location",
+		      G_CALLBACK (location_changed), combo);
+#endif
+
+    gtk_widget_show_all (window);
+
+    gtk_main ();
+
+    return 0;
+}
