1 | /* gweather-xml.c - Locations.xml parsing code |
---|
2 | * |
---|
3 | * Copyright (C) 2004 Gareth Owen |
---|
4 | * |
---|
5 | * This program is free software; you can redistribute it and/or modify |
---|
6 | * it under the terms of the GNU General Public License as published by |
---|
7 | * the Free Software Foundation; either version 2 of the License, or |
---|
8 | * (at your option) any later version. |
---|
9 | * |
---|
10 | * This program is distributed in the hope that it will be useful, |
---|
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
---|
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
---|
13 | * GNU General Public License for more details. |
---|
14 | * |
---|
15 | * You should have received a copy of the GNU General Public License |
---|
16 | * along with this program; if not, write to the Free Software |
---|
17 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
---|
18 | */ |
---|
19 | |
---|
20 | |
---|
21 | /* There is very little error checking in the parsing code below, it relies |
---|
22 | * heavily on the locations file being in the correct format. The format is |
---|
23 | * as follows: |
---|
24 | * |
---|
25 | * <gweather format="1.0"> |
---|
26 | * <region> |
---|
27 | * <name>Name of the region</name> |
---|
28 | * <country> |
---|
29 | * <name>Name of the country</name> |
---|
30 | * <location> |
---|
31 | * <name>Name of the location</name> |
---|
32 | * <code>IWIN code</code> |
---|
33 | * <zone>Forecast code (North America, Australia, UK only)</zone> |
---|
34 | * <radar>Weather.com radar map code (North America only)</radar> |
---|
35 | * </location> |
---|
36 | * <state> |
---|
37 | * <location> |
---|
38 | * .... |
---|
39 | * </location> |
---|
40 | * </state> |
---|
41 | * </country> |
---|
42 | * </region> |
---|
43 | * <gweather> |
---|
44 | * |
---|
45 | * The thing to note is that each country can either contain different locations |
---|
46 | * or be split into "states" which in turn contain a list of locations. |
---|
47 | * |
---|
48 | */ |
---|
49 | |
---|
50 | #include <string.h> |
---|
51 | #include <locale.h> |
---|
52 | #include <gtk/gtk.h> |
---|
53 | #include <libxml/xmlreader.h> |
---|
54 | |
---|
55 | #include "weather.h" |
---|
56 | #include "gweather-pref.h" |
---|
57 | |
---|
58 | |
---|
59 | /* Location of the xml file */ |
---|
60 | #define GWEATHER_XML_LOCATION "gweather/Locations.xml" |
---|
61 | |
---|
62 | /* If you change the format of the Locations.xml file that would break the current |
---|
63 | * parsing then make sure you also add an extra define and maintain support for |
---|
64 | * old format. |
---|
65 | */ |
---|
66 | #define GWEATHER_XML_FORMAT_1_0 "1.0" |
---|
67 | |
---|
68 | |
---|
69 | /* XML Node names */ |
---|
70 | #define GWEATHER_XML_NODE_GWEATHER "gweather" |
---|
71 | #define GWEATHER_XML_NODE_REGION "region" |
---|
72 | #define GWEATHER_XML_NODE_COUNTRY "country" |
---|
73 | #define GWEATHER_XML_NODE_STATE "state" |
---|
74 | #define GWEATHER_XML_NODE_LOC "location" |
---|
75 | #define GWEATHER_XML_NODE_NAME "name" |
---|
76 | #define GWEATHER_XML_NODE_CODE "code" |
---|
77 | #define GWEATHER_XML_NODE_ZONE "zone" |
---|
78 | #define GWEATHER_XML_NODE_RADAR "radar" |
---|
79 | |
---|
80 | |
---|
81 | /* XML Attributes */ |
---|
82 | #define GWEATHER_XML_ATTR_FORMAT "format" |
---|
83 | |
---|
84 | /***************************************************************************** |
---|
85 | * Func: gweather_xml_location_sort_func() |
---|
86 | * Desc: compare two locales to see if they match |
---|
87 | * Parm: model: tree |
---|
88 | * a,b: iterators to sort |
---|
89 | * user_data: user data (unused) |
---|
90 | */ |
---|
91 | static gint gweather_xml_location_sort_func(GtkTreeModel *model, GtkTreeIter *a, GtkTreeIter *b, gpointer user_data) |
---|
92 | { |
---|
93 | gint res; |
---|
94 | gchar *name_a, *name_b; |
---|
95 | gchar *fold_a, *fold_b; |
---|
96 | |
---|
97 | gtk_tree_model_get (model, a, GWEATHER_PREF_COL_LOC, &name_a, -1); |
---|
98 | gtk_tree_model_get (model, b, GWEATHER_PREF_COL_LOC, &name_b, -1); |
---|
99 | |
---|
100 | fold_a = g_utf8_casefold(name_a, -1); |
---|
101 | fold_b = g_utf8_casefold(name_b, -1); |
---|
102 | |
---|
103 | res = g_utf8_collate(fold_a, fold_b); |
---|
104 | |
---|
105 | g_free(name_a); |
---|
106 | g_free(name_b); |
---|
107 | g_free(fold_a); |
---|
108 | g_free(fold_b); |
---|
109 | |
---|
110 | return res; |
---|
111 | } |
---|
112 | |
---|
113 | /***************************************************************************** |
---|
114 | * Func: gweather_xml_compare_locale() |
---|
115 | * Desc: compare two locales to see if they match |
---|
116 | * Parm: a, b: the two locales to compare |
---|
117 | */ |
---|
118 | static gint gweather_xml_compare_locale(gconstpointer a, gconstpointer b) |
---|
119 | { |
---|
120 | return (strcmp(a, b)); |
---|
121 | } |
---|
122 | |
---|
123 | /***************************************************************************** |
---|
124 | * Func: gweather_xml_get_value() |
---|
125 | * Desc: Extract the value from the xml |
---|
126 | * Parm: reader: xml text reader |
---|
127 | */ |
---|
128 | static xmlChar* gweather_xml_get_value (xmlTextReaderPtr reader) |
---|
129 | { |
---|
130 | int ret, type; |
---|
131 | xmlChar* value; |
---|
132 | |
---|
133 | /* check for null node */ |
---|
134 | if ( xmlTextReaderIsEmptyElement (reader) ) { |
---|
135 | return NULL; |
---|
136 | } |
---|
137 | |
---|
138 | value = NULL; |
---|
139 | /* the next "node" is the text node containing the value we want to get */ |
---|
140 | if (xmlTextReaderRead (reader) == 1) { |
---|
141 | value = xmlTextReaderValue (reader); |
---|
142 | } |
---|
143 | |
---|
144 | /* move on to the end of this node */ |
---|
145 | while ( (xmlTextReaderRead(reader) == 1) && |
---|
146 | (xmlTextReaderNodeType (reader) != XML_READER_TYPE_END_ELEMENT) ) { |
---|
147 | } |
---|
148 | |
---|
149 | return value; |
---|
150 | } |
---|
151 | |
---|
152 | /***************************************************************************** |
---|
153 | * Func: gweather_xml_parse_name() |
---|
154 | * Desc: Extract the name from the xml and add it to the tree |
---|
155 | * Parm: |
---|
156 | * *locale current locale |
---|
157 | * *tree: tree to view locations |
---|
158 | * *loc: currently selected location |
---|
159 | * reader: xml text reader |
---|
160 | * *iter: iterator to named node |
---|
161 | * **untrans_name: returns untranslated name |
---|
162 | * **trans_name: returns translated name |
---|
163 | * *pref: the preference of the current translation, the smaller the better |
---|
164 | */ |
---|
165 | static void gweather_xml_parse_name (const GList *locale, GtkTreeStore *tree, GtkTreeIter* iter, xmlTextReaderPtr reader, xmlChar **untrans_name, xmlChar **trans_name, gint *pref) |
---|
166 | { |
---|
167 | int ret, type; |
---|
168 | xmlChar *lang; |
---|
169 | GList *found_locale; |
---|
170 | gint position; |
---|
171 | |
---|
172 | /* First let's get the language */ |
---|
173 | lang = xmlTextReaderXmlLang (reader); |
---|
174 | |
---|
175 | /* the next "node" is text node containing the actual name */ |
---|
176 | ret = xmlTextReaderRead (reader); |
---|
177 | if (ret == 1) { |
---|
178 | |
---|
179 | /* Get the name, if this is the untranslated name, or the locale matches |
---|
180 | * the language |
---|
181 | */ |
---|
182 | if ( lang == NULL ) { |
---|
183 | *untrans_name = xmlTextReaderValue (reader); |
---|
184 | /* set the column entry, incase there is no translation */ |
---|
185 | gtk_tree_store_set (tree, iter, GWEATHER_PREF_COL_LOC, *untrans_name, -1); |
---|
186 | } else { |
---|
187 | found_locale = g_list_find_custom((GList*)locale, lang, gweather_xml_compare_locale); |
---|
188 | if (found_locale) { |
---|
189 | position = g_list_position((GList*)locale, found_locale); |
---|
190 | |
---|
191 | /* If this is higer up the preference list then use it */ |
---|
192 | if ( (*pref == -1) || (*pref > position) ) { |
---|
193 | *trans_name = xmlTextReaderValue (reader); |
---|
194 | gtk_tree_store_set (tree, iter, GWEATHER_PREF_COL_LOC, *trans_name, -1); |
---|
195 | *pref = position; |
---|
196 | } |
---|
197 | } |
---|
198 | } |
---|
199 | } |
---|
200 | |
---|
201 | xmlFree (lang); |
---|
202 | |
---|
203 | /* move on to the end of this node */ |
---|
204 | while ( (xmlTextReaderRead(reader) == 1) && |
---|
205 | (xmlTextReaderNodeType (reader) != XML_READER_TYPE_END_ELEMENT) ) { |
---|
206 | } |
---|
207 | } |
---|
208 | |
---|
209 | |
---|
210 | /***************************************************************************** |
---|
211 | * Func: gweather_xml_parse_location() |
---|
212 | * Desc: Parse a location node, creating a leaf for it in the tree |
---|
213 | * Parm: |
---|
214 | * *locale current locale |
---|
215 | * *tree: tree to view locations |
---|
216 | * *loc: currently selected location |
---|
217 | * reader: xml text reader |
---|
218 | * *iter: Changed to iterator for this node |
---|
219 | * *p_iter: parent node's iterator |
---|
220 | * *s_iter: sibling node's iterator (who this node appears after) |
---|
221 | */ |
---|
222 | static void gweather_xml_parse_location (const GList* locale, GtkTreeView *tree, WeatherLocation *loc, xmlTextReaderPtr reader, GtkTreeIter* iter, GtkTreeIter* r_iter, GtkTreeIter* c_iter) |
---|
223 | { |
---|
224 | int ret, type; |
---|
225 | xmlChar *name, *untrans_name, *trans_name, *code, *zone, *radar; |
---|
226 | GtkTreeStore *store; |
---|
227 | WeatherLocation* new_loc; |
---|
228 | gint pref; |
---|
229 | |
---|
230 | /* check for an empty element */ |
---|
231 | if ( xmlTextReaderIsEmptyElement (reader) ) { |
---|
232 | return; |
---|
233 | } |
---|
234 | |
---|
235 | /* initialise variables */ |
---|
236 | store = GTK_TREE_STORE (gtk_tree_view_get_model (tree)); |
---|
237 | untrans_name = NULL; |
---|
238 | trans_name = NULL; |
---|
239 | code = NULL; |
---|
240 | zone = NULL; |
---|
241 | radar = NULL; |
---|
242 | pref = -1; |
---|
243 | |
---|
244 | /* create a node in the tree for this region */ |
---|
245 | gtk_tree_store_insert_after(store, iter, r_iter, c_iter); |
---|
246 | |
---|
247 | /* Loop through any sub elements (ie until we get to a close for this element */ |
---|
248 | ret = xmlTextReaderRead(reader); |
---|
249 | type = XML_READER_TYPE_ELEMENT; |
---|
250 | while ( (ret == 1) && (type != XML_READER_TYPE_END_ELEMENT) ) { |
---|
251 | |
---|
252 | type = xmlTextReaderNodeType (reader); |
---|
253 | |
---|
254 | /* skip non-element types */ |
---|
255 | if ( type == XML_READER_TYPE_ELEMENT ) { |
---|
256 | name = xmlTextReaderName (reader); |
---|
257 | |
---|
258 | if ( strcmp (name, GWEATHER_XML_NODE_NAME) == 0 ) { |
---|
259 | gweather_xml_parse_name (locale, store, iter, reader, &untrans_name, &trans_name, &pref); |
---|
260 | } else if ( strcmp (name, GWEATHER_XML_NODE_CODE) == 0 ) { |
---|
261 | code = gweather_xml_get_value (reader); |
---|
262 | } else if ( strcmp (name, GWEATHER_XML_NODE_ZONE) == 0 ) { |
---|
263 | zone = gweather_xml_get_value (reader); |
---|
264 | } else if ( strcmp (name, GWEATHER_XML_NODE_RADAR) == 0 ) { |
---|
265 | radar = gweather_xml_get_value (reader); |
---|
266 | } |
---|
267 | |
---|
268 | xmlFree (name); |
---|
269 | } |
---|
270 | |
---|
271 | ret = xmlTextReaderRead(reader); |
---|
272 | |
---|
273 | } |
---|
274 | |
---|
275 | /* Add an entry in the tree for the location */ |
---|
276 | new_loc = weather_location_new(untrans_name, trans_name, code, zone, radar); |
---|
277 | gtk_tree_store_set (store, iter, GWEATHER_PREF_COL_POINTER, new_loc, -1); |
---|
278 | |
---|
279 | /* Free xml attributes */ |
---|
280 | xmlFree(untrans_name); |
---|
281 | xmlFree(trans_name); |
---|
282 | xmlFree(code); |
---|
283 | xmlFree(zone); |
---|
284 | xmlFree(radar); |
---|
285 | |
---|
286 | /* If this location is actually the currently selected one, select it */ |
---|
287 | if ( loc && weather_location_equal (new_loc, loc) ) { |
---|
288 | |
---|
289 | GtkTreePath *path; |
---|
290 | |
---|
291 | path = gtk_tree_model_get_path (GTK_TREE_MODEL (store), iter); |
---|
292 | gtk_tree_view_expand_to_path (tree, path); |
---|
293 | gtk_tree_view_set_cursor (tree, path, NULL, FALSE); |
---|
294 | gtk_tree_view_scroll_to_cell (tree, path, NULL, TRUE, 0.5, 0.5); |
---|
295 | gtk_tree_path_free (path); |
---|
296 | } |
---|
297 | } |
---|
298 | |
---|
299 | /***************************************************************************** |
---|
300 | * Func: gweather_xml_parse_node() |
---|
301 | * Desc: Parse a region/country/state node, creating a leaf for it in the tree |
---|
302 | * then parsing any sub-nodes. |
---|
303 | * This is called recursively as each node parses it's sub-nodes |
---|
304 | * Parm: |
---|
305 | * *locale current_locale |
---|
306 | * *tree: tree to view locations |
---|
307 | * *loc: currently selected location |
---|
308 | * reader: xml text reader |
---|
309 | * *iter: Changed to iterator for this node |
---|
310 | * *p_iter: iterator to parent node |
---|
311 | * *s_iter: iterator to last sibling node |
---|
312 | */ |
---|
313 | static void gweather_xml_parse_node (const GList *locale, GtkTreeView *tree, WeatherLocation *loc, xmlTextReaderPtr reader, GtkTreeIter* iter, GtkTreeIter* p_iter, GtkTreeIter* s_iter) |
---|
314 | { |
---|
315 | int ret, type; |
---|
316 | xmlChar *name, *untrans_name, *trans_name; |
---|
317 | GtkTreeStore *model; |
---|
318 | GtkTreeIter last_child; |
---|
319 | gboolean first; |
---|
320 | gint pref; |
---|
321 | |
---|
322 | /* check for an empty element */ |
---|
323 | if ( xmlTextReaderIsEmptyElement (reader) ) { |
---|
324 | return; |
---|
325 | } |
---|
326 | |
---|
327 | /* initialise variables */ |
---|
328 | model = GTK_TREE_STORE (gtk_tree_view_get_model (tree)); |
---|
329 | first = TRUE; |
---|
330 | pref = -1; |
---|
331 | |
---|
332 | /* create a node in the tree for this region */ |
---|
333 | gtk_tree_store_insert_after(model, iter, p_iter, s_iter); |
---|
334 | |
---|
335 | ret = xmlTextReaderRead(reader); |
---|
336 | type = XML_READER_TYPE_ELEMENT; |
---|
337 | while ( (ret == 1) && (type != XML_READER_TYPE_END_ELEMENT) ) { |
---|
338 | |
---|
339 | type = xmlTextReaderNodeType (reader); |
---|
340 | |
---|
341 | /* skip non-element types */ |
---|
342 | if ( type == XML_READER_TYPE_ELEMENT ) { |
---|
343 | name = xmlTextReaderName (reader); |
---|
344 | if ( strcmp (name, GWEATHER_XML_NODE_NAME) == 0 ) { |
---|
345 | gweather_xml_parse_name (locale, model, iter, reader, &untrans_name, &trans_name, &pref); |
---|
346 | } else if ( strcmp (name, GWEATHER_XML_NODE_COUNTRY) == 0 || |
---|
347 | strcmp (name, GWEATHER_XML_NODE_STATE) == 0 ) { |
---|
348 | if (first) { |
---|
349 | gweather_xml_parse_node (locale, tree, loc, reader, &last_child, iter , NULL); |
---|
350 | first = FALSE; |
---|
351 | } else { |
---|
352 | gweather_xml_parse_node (locale, tree, loc, reader, &last_child, iter, &last_child); |
---|
353 | } |
---|
354 | |
---|
355 | } else if ( strcmp (name, GWEATHER_XML_NODE_LOC) == 0 ) { |
---|
356 | if (first) { |
---|
357 | gweather_xml_parse_location (locale, tree, loc, reader, &last_child, iter, NULL); |
---|
358 | first = FALSE; |
---|
359 | } else { |
---|
360 | gweather_xml_parse_location (locale, tree, loc, reader, &last_child, iter, &last_child); |
---|
361 | } |
---|
362 | } |
---|
363 | |
---|
364 | xmlFree (name); |
---|
365 | } |
---|
366 | |
---|
367 | ret = xmlTextReaderRead (reader); |
---|
368 | } |
---|
369 | } |
---|
370 | |
---|
371 | /***************************************************************************** |
---|
372 | * Func: gweather_xml_parse() |
---|
373 | * Desc: Top of the xml parser |
---|
374 | * Parm: |
---|
375 | * *tree: tree to view locations |
---|
376 | * *loc: currently selected location |
---|
377 | * reader: xml text reader |
---|
378 | * locale: current locale |
---|
379 | */ |
---|
380 | static void gweather_xml_parse (GtkTreeView *tree, WeatherLocation *loc, xmlTextReaderPtr reader, const GList* locale) |
---|
381 | { |
---|
382 | int ret, type; |
---|
383 | xmlChar *name; |
---|
384 | GtkTreeIter iter; |
---|
385 | gboolean first; |
---|
386 | |
---|
387 | /* at the start there are no regions in the tree */ |
---|
388 | first = TRUE; |
---|
389 | |
---|
390 | /* We are expecting a region element anything else we simply skip */ |
---|
391 | for (ret = xmlTextReaderRead(reader); ret == 1; ret = xmlTextReaderRead(reader) ) { |
---|
392 | type = xmlTextReaderNodeType (reader); |
---|
393 | if (type == XML_READER_TYPE_ELEMENT) { |
---|
394 | name = xmlTextReaderName (reader); |
---|
395 | if ( strcmp(name, GWEATHER_XML_NODE_REGION) == 0 ) { |
---|
396 | if (first) { |
---|
397 | gweather_xml_parse_node (locale, tree, loc, reader, &iter, NULL, NULL); |
---|
398 | first = FALSE; |
---|
399 | } |
---|
400 | else { |
---|
401 | gweather_xml_parse_node (locale, tree, loc, reader, &iter, NULL, &iter); |
---|
402 | } |
---|
403 | } |
---|
404 | xmlFree (name); |
---|
405 | } |
---|
406 | } |
---|
407 | } |
---|
408 | |
---|
409 | /***************************************************************************** |
---|
410 | * Func: gweather_xml_load_locations() |
---|
411 | * Desc: Main entry point for loading the locations from the XML file |
---|
412 | * Parm: |
---|
413 | * *tree: tree to view locations |
---|
414 | * *loc: currently selected location |
---|
415 | */ |
---|
416 | void gweather_xml_load_locations (GtkTreeView *tree, WeatherLocation *loc) |
---|
417 | { |
---|
418 | xmlTextReaderPtr reader; |
---|
419 | gchar *file; |
---|
420 | int ret; |
---|
421 | xmlChar *name, *format; |
---|
422 | const GList *locale; |
---|
423 | GtkTreeSortable *sortable; |
---|
424 | |
---|
425 | /* Get the current locale */ |
---|
426 | locale = gnome_i18n_get_language_list("LC_MESSAGES"); |
---|
427 | |
---|
428 | /* Open the xml file containing the different locations */ |
---|
429 | file = gnome_datadir_file (GWEATHER_XML_LOCATION); |
---|
430 | g_return_if_fail (file); |
---|
431 | reader = xmlNewTextReaderFilename (file); |
---|
432 | g_return_if_fail (reader); |
---|
433 | |
---|
434 | /* The first node that is read is <gweather> */ |
---|
435 | ret = xmlTextReaderRead (reader); |
---|
436 | if ( ret == 1 ) { |
---|
437 | /* check the name and format */ |
---|
438 | name = xmlTextReaderName(reader); |
---|
439 | if ( strcmp(GWEATHER_XML_NODE_GWEATHER, name) == 0 ) { |
---|
440 | format = xmlTextReaderGetAttribute(reader, GWEATHER_XML_ATTR_FORMAT); |
---|
441 | if ( format != NULL ) { |
---|
442 | /* Parse depending on the format */ |
---|
443 | if ( strcmp (format, GWEATHER_XML_FORMAT_1_0) == 0 ) { |
---|
444 | gweather_xml_parse (tree, loc, reader, locale); |
---|
445 | } |
---|
446 | else { |
---|
447 | g_warning ("Unknown gweather xml format %s", format); |
---|
448 | } |
---|
449 | xmlFree (format); |
---|
450 | } |
---|
451 | } |
---|
452 | xmlFree (name); |
---|
453 | } |
---|
454 | xmlFreeTextReader (reader); |
---|
455 | |
---|
456 | /* Sort the tree */ |
---|
457 | sortable = GTK_TREE_SORTABLE (GTK_TREE_STORE(gtk_tree_view_get_model (tree))); |
---|
458 | gtk_tree_sortable_set_default_sort_func(sortable, &gweather_xml_location_sort_func, NULL, NULL); |
---|
459 | gtk_tree_sortable_set_sort_column_id(sortable,GTK_TREE_SORTABLE_DEFAULT_SORT_COLUMN_ID,GTK_SORT_ASCENDING); |
---|
460 | } |
---|