]> git.itanic.dy.fi Git - maemo-mapper/blob - src/gpx.c
Fixed GPX writing to support output of XML-escaped characters
[maemo-mapper] / src / gpx.c
1 /*
2  * Copyright (C) 2006, 2007 John Costigan.
3  *
4  * POI and GPS-Info code originally written by Cezary Jackiewicz.
5  *
6  * Default map data provided by http://www.openstreetmap.org/
7  *
8  * This file is part of Maemo Mapper.
9  *
10  * Maemo Mapper is free software: you can redistribute it and/or modify
11  * it under the terms of the GNU General Public License as published by
12  * the Free Software Foundation, either version 3 of the License, or
13  * (at your option) any later version.
14  *
15  * Maemo Mapper is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU General Public License for more details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with Maemo Mapper.  If not, see <http://www.gnu.org/licenses/>.
22  */
23
24 #define _GNU_SOURCE
25
26 #include <string.h>
27 #include <math.h>
28 #include <libxml/parser.h>
29
30 #include "types.h"
31 #include "data.h"
32 #include "defines.h"
33
34 #include "gpx.h"
35 #include "path.h"
36 #include "util.h"
37
38 /**
39  * Handle a start tag in the parsing of a GPX file.
40  */
41 #define MACRO_SET_UNKNOWN() { \
42     data->sax_data.prev_state = data->sax_data.state; \
43     data->sax_data.state = UNKNOWN; \
44     data->sax_data.unknown_depth = 1; \
45 }
46
47 static gchar XML_TZONE[7];
48
49 /**
50  * Handle char data in the parsing of a GPX file.
51  */
52 static void
53 gpx_chars(SaxData *data, const xmlChar *ch, int len)
54 {
55     gint i;
56     vprintf("%s()\n", __PRETTY_FUNCTION__);
57
58     switch(data->state)
59     {
60         case ERROR:
61         case UNKNOWN:
62             break;
63         case INSIDE_WPT_NAME:
64         case INSIDE_WPT_DESC:
65         case INSIDE_PATH_POINT_ELE:
66         case INSIDE_PATH_POINT_TIME:
67         case INSIDE_PATH_POINT_DESC:
68             for(i = 0; i < len; i++)
69                 data->chars = g_string_append_c(data->chars, ch[i]);
70             vprintf("%s\n", data->chars->str);
71             break;
72         default:
73             break;
74     }
75
76     vprintf("%s(): return\n", __PRETTY_FUNCTION__);
77 }
78
79 /**
80  * Handle an entity in the parsing of a GPX file.  We don't do anything
81  * special here.
82  */
83 static xmlEntityPtr
84 gpx_get_entity(SaxData *data, const xmlChar *name)
85 {
86     vprintf("%s()\n", __PRETTY_FUNCTION__);
87     vprintf("%s(): return\n", __PRETTY_FUNCTION__);
88     return xmlGetPredefinedEntity(name);
89 }
90
91 /**
92  * Handle an error in the parsing of a GPX file.
93  */
94 static void
95 gpx_error(SaxData *data, const gchar *msg, ...)
96 {
97     vprintf("%s()\n", __PRETTY_FUNCTION__);
98     vprintf("%s(): return\n", __PRETTY_FUNCTION__);
99     data->state = ERROR;
100 }
101
102 static gboolean
103 gpx_write_string(GnomeVFSHandle *handle, const gchar *str)
104 {
105     GnomeVFSResult vfs_result;
106     GnomeVFSFileSize size;
107     if(GNOME_VFS_OK != (vfs_result = gnome_vfs_write(
108                     handle, str, strlen(str), &size)))
109     {
110         gchar buffer[BUFFER_SIZE];
111         snprintf(buffer, sizeof(buffer),
112                 "%s:\n%s\n%s", _("Error while writing to file"),
113                 _("File is incomplete."),
114                 gnome_vfs_result_to_string(vfs_result));
115         popup_error(_window, buffer);
116         return FALSE;
117     }
118     return TRUE;
119 }
120
121 static gboolean
122 gpx_write_escaped(GnomeVFSHandle *handle, const gchar *str)
123 {
124     const gchar *ptr = str;
125     const gchar *nullchr = ptr + strlen(ptr);
126     while(ptr < nullchr)
127     {
128         gchar *newptr = strpbrk(ptr, "&<>");
129         if(newptr != NULL)
130         {
131             /* First, write out what we have so far. */
132             const gchar *to_write;
133             GnomeVFSResult vfs_result;
134             GnomeVFSFileSize size;
135             if(GNOME_VFS_OK != (vfs_result = gnome_vfs_write(
136                             handle, ptr, newptr - ptr, &size)))
137             {
138                 gchar buffer[BUFFER_SIZE];
139                 snprintf(buffer, sizeof(buffer),
140                         "%s:\n%s\n%s", _("Error while writing to file"),
141                         _("File is incomplete."),
142                         gnome_vfs_result_to_string(vfs_result));
143                 popup_error(_window, buffer);
144                 return FALSE;
145             }
146
147             /* Now, write the XML entity. */
148             switch(*newptr)
149             {
150                 case '&':
151                     to_write = "&amp;";
152                     break;
153                 case '<':
154                     to_write = "&lt;";
155                     break;
156                 case '>':
157                     to_write = "&gt;";
158                     break;
159             }
160             gpx_write_string(handle, to_write);
161
162             /* Advance the pointer to continue searching for entities. */
163             ptr = newptr + 1;
164         }
165         else
166         {
167             /* No characters need escaping - write the whole thing. */
168             gpx_write_string(handle, ptr);
169             ptr = nullchr;
170         }
171     }
172     return TRUE;
173 }
174
175 /****************************************************************************
176  * BELOW: OPEN PATH *********************************************************
177  ****************************************************************************/
178
179 static void
180 gpx_path_start_element(PathSaxData *data,
181         const xmlChar *name, const xmlChar **attrs)
182 {
183     vprintf("%s(%s)\n", __PRETTY_FUNCTION__, name);
184
185     switch(data->sax_data.state)
186     {
187         case ERROR:
188             printf("ERROR!\n");
189             break;
190         case START:
191             if(!strcmp((gchar*)name, "gpx"))
192                 data->sax_data.state = INSIDE_GPX;
193             else
194                 MACRO_SET_UNKNOWN();
195             break;
196         case INSIDE_GPX:
197             if(!strcmp((gchar*)name, "trk"))
198                 data->sax_data.state = INSIDE_PATH;
199             else
200                 MACRO_SET_UNKNOWN();
201             break;
202         case INSIDE_PATH:
203             if(!strcmp((gchar*)name, "trkseg"))
204             {
205                 data->sax_data.state = INSIDE_PATH_SEGMENT;
206                 data->sax_data.at_least_one_trkpt = FALSE;
207             }
208             else
209                 MACRO_SET_UNKNOWN();
210             break;
211         case INSIDE_PATH_SEGMENT:
212             if(!strcmp((gchar*)name, "trkpt"))
213             {
214                 const xmlChar **curr_attr;
215                 gchar *error_check;
216                 gdouble lat = 0.0, lon = 0.0;
217                 gboolean has_lat, has_lon;
218                 has_lat = FALSE;
219                 has_lon = FALSE;
220                 for(curr_attr = attrs; *curr_attr != NULL; )
221                 {
222                     const gchar *attr_name = *curr_attr++;
223                     const gchar *attr_val = *curr_attr++;
224                     if(!strcmp(attr_name, "lat"))
225                     {
226                         lat = g_ascii_strtod(attr_val, &error_check);
227                         if(error_check != attr_val)
228                             has_lat = TRUE;
229                     }
230                     else if(!strcmp(attr_name, "lon"))
231                     {
232                         lon = g_ascii_strtod(attr_val, &error_check);
233                         if(error_check != attr_val)
234                             has_lon = TRUE;
235                     }
236                 }
237                 if(has_lat && has_lon)
238                 {
239                     MACRO_PATH_INCREMENT_TAIL(data->path);
240                     latlon2unit(lat, lon,
241                             data->path.tail->unitx,
242                             data->path.tail->unity);
243                     data->path.tail->time = 0;
244                     data->path.tail->altitude = 0;
245                     data->sax_data.state = INSIDE_PATH_POINT;
246                 }
247                 else
248                     data->sax_data.state = ERROR;
249             }
250             else
251                 MACRO_SET_UNKNOWN();
252             break;
253         case INSIDE_PATH_POINT:
254             if(!strcmp((gchar*)name, "time"))
255                 data->sax_data.state = INSIDE_PATH_POINT_TIME;
256             else if(!strcmp((gchar*)name, "ele"))
257                 data->sax_data.state = INSIDE_PATH_POINT_ELE;
258             else if(!strcmp((gchar*)name, "desc"))
259                 data->sax_data.state = INSIDE_PATH_POINT_DESC;
260
261             else
262                 MACRO_SET_UNKNOWN();
263             break;
264         case UNKNOWN:
265             data->sax_data.unknown_depth++;
266             break;
267         default:
268             ;
269     }
270
271     vprintf("%s(): return\n", __PRETTY_FUNCTION__);
272 }
273
274 /**
275  * Handle an end tag in the parsing of a GPX file.
276  */
277 static void
278 gpx_path_end_element(PathSaxData *data, const xmlChar *name)
279 {
280     vprintf("%s(%s)\n", __PRETTY_FUNCTION__, name);
281
282     switch(data->sax_data.state)
283     {
284         case ERROR:
285             printf("ERROR!\n");
286             break;
287         case START:
288             data->sax_data.state = ERROR;
289             break;
290         case INSIDE_GPX:
291             if(!strcmp((gchar*)name, "gpx"))
292                 data->sax_data.state = FINISH;
293             else
294                 data->sax_data.state = ERROR;
295             break;
296         case INSIDE_PATH:
297             if(!strcmp((gchar*)name, "trk"))
298                 data->sax_data.state = INSIDE_GPX;
299             else
300                 data->sax_data.state = ERROR;
301             break;
302         case INSIDE_PATH_SEGMENT:
303             if(!strcmp((gchar*)name, "trkseg"))
304             {
305                 if(data->sax_data.at_least_one_trkpt)
306                 {
307                     MACRO_PATH_INCREMENT_TAIL(data->path);
308                     *data->path.tail = _point_null;
309                 }
310                 data->sax_data.state = INSIDE_PATH;
311             }
312             else
313                 data->sax_data.state = ERROR;
314             break;
315         case INSIDE_PATH_POINT:
316             if(!strcmp((gchar*)name, "trkpt"))
317             {
318                 data->sax_data.state = INSIDE_PATH_SEGMENT;
319                 data->sax_data.at_least_one_trkpt = TRUE;
320             }
321             else
322                 data->sax_data.state = ERROR;
323             break;
324         case INSIDE_PATH_POINT_ELE:
325             if(!strcmp((gchar*)name, "ele"))
326             {
327                 gchar *error_check;
328                 data->path.tail->altitude
329                     = g_ascii_strtod(data->sax_data.chars->str, &error_check);
330                 if(error_check == data->sax_data.chars->str)
331                     data->path.tail->altitude = 0;
332                 data->sax_data.state = INSIDE_PATH_POINT;
333                 g_string_free(data->sax_data.chars, TRUE);
334                 data->sax_data.chars = g_string_new("");
335             }
336             else
337                 data->sax_data.state = ERROR;
338             break;
339         case INSIDE_PATH_POINT_TIME:
340             if(!strcmp((gchar*)name, "time"))
341             {
342                 struct tm time;
343                 gchar *ptr;
344
345                 if(NULL == (ptr = strptime(data->sax_data.chars->str,
346                             XML_DATE_FORMAT, &time)))
347                     /* Failed to parse dateTime format. */
348                     data->sax_data.state = ERROR;
349                 else
350                 {
351                     /* Parse was successful. Now we have to parse timezone.
352                      * From here on, if there is an error, I just assume local
353                      * timezone.  Yes, this is not proper XML, but I don't
354                      * care. */
355                     gchar *error_check;
356
357                     /* First, set time in "local" time zone. */
358                     data->path.tail->time = (mktime(&time));
359
360                     /* Now, skip inconsequential characters */
361                     while(*ptr && *ptr != 'Z' && *ptr != '-' && *ptr != '+')
362                         ptr++;
363
364                     /* Check if we ran to the end of the string. */
365                     if(*ptr)
366                     {
367                         /* Next character is either 'Z', '-', or '+' */
368                         if(*ptr == 'Z')
369                             /* Zulu (UTC) time. Undo the local time zone's
370                              * offset. */
371                             data->path.tail->time += time.tm_gmtoff;
372                         else
373                         {
374                             /* Not Zulu (UTC). Must parse hours and minutes. */
375                             gint offhours = strtol(ptr, &error_check, 10);
376                             if(error_check != ptr
377                                     && *(ptr = error_check) == ':')
378                             {
379                                 /* Parse of hours worked. Check minutes. */
380                                 gint offmins = strtol(ptr + 1,
381                                         &error_check, 10);
382                                 if(error_check != (ptr + 1))
383                                 {
384                                     /* Parse of minutes worked. Calculate. */
385                                     data->path.tail->time
386                                         += (time.tm_gmtoff
387                                                 - (offhours * 60 * 60
388                                                     + offmins * 60));
389                                 }
390                             }
391                         }
392                     }
393                     /* Successfully parsed dateTime. */
394                     data->sax_data.state = INSIDE_PATH_POINT;
395                 }
396
397                 g_string_free(data->sax_data.chars, TRUE);
398                 data->sax_data.chars = g_string_new("");
399             }
400             else
401                 data->sax_data.state = ERROR;
402             break;
403         case INSIDE_PATH_POINT_DESC:
404             /* only parse description for routes */
405             if(!strcmp((gchar*)name, "desc"))
406             {
407                 MACRO_PATH_INCREMENT_WTAIL(data->path);
408                 data->path.wtail->point = data->path.tail;
409                 data->path.wtail->desc
410                     = g_string_free(data->sax_data.chars, FALSE);
411                 data->sax_data.chars = g_string_new("");
412                 data->sax_data.state = INSIDE_PATH_POINT;
413             }
414             else
415                 data->sax_data.state = ERROR;
416             break;
417         case UNKNOWN:
418             if(!--data->sax_data.unknown_depth)
419                 data->sax_data.state = data->sax_data.prev_state;
420             else
421                 data->sax_data.state = ERROR;
422             break;
423         default:
424             ;
425     }
426
427     vprintf("%s(): return\n", __PRETTY_FUNCTION__);
428 }
429
430 gboolean
431 gpx_path_parse(Path *to_replace, gchar *buffer, gint size, gint policy_old)
432 {
433     PathSaxData data;
434     xmlSAXHandler sax_handler;
435     printf("%s()\n", __PRETTY_FUNCTION__);
436
437     MACRO_PATH_INIT(data.path);
438     data.sax_data.state = START;
439     data.sax_data.chars = g_string_new("");
440
441     memset(&sax_handler, 0, sizeof(sax_handler));
442     sax_handler.characters = (charactersSAXFunc)gpx_chars;
443     sax_handler.startElement = (startElementSAXFunc)gpx_path_start_element;
444     sax_handler.endElement = (endElementSAXFunc)gpx_path_end_element;
445     sax_handler.entityDecl = (entityDeclSAXFunc)gpx_get_entity;
446     sax_handler.warning = (warningSAXFunc)gpx_error;
447     sax_handler.error = (errorSAXFunc)gpx_error;
448     sax_handler.fatalError = (fatalErrorSAXFunc)gpx_error;
449
450     xmlSAXUserParseMemory(&sax_handler, &data, buffer, size);
451     g_string_free(data.sax_data.chars, TRUE);
452
453     if(data.sax_data.state != FINISH)
454     {
455         vprintf("%s(): return FALSE\n", __PRETTY_FUNCTION__);
456         return FALSE;
457     }
458
459     if(policy_old && to_replace->head != to_replace->tail)
460     {
461         Point *src_first;
462         Path *src, *dest;
463
464         if(policy_old > 0)
465         {
466             /* Append to current path. Make sure last path point is zero. */
467             if(to_replace->tail->unity != 0)
468             {
469                 MACRO_PATH_INCREMENT_TAIL((*to_replace));
470                 *to_replace->tail = _point_null;
471             }
472             src = &data.path;
473             dest = to_replace;
474         }
475         else
476         {
477             /* Prepend to current route. */
478             src = to_replace;
479             dest = &data.path;
480         }
481
482         /* Find src_first non-zero point. */
483         for(src_first = src->head - 1; src_first++ != src->tail; )
484             if(src_first->unity)
485                 break;
486
487         /* Append route points from src to dest. */
488         if(src->tail >= src_first)
489         {
490             WayPoint *curr;
491             gint num_dest_points = dest->tail - dest->head + 1;
492             gint num_src_points = src->tail - src_first + 1;
493
494             /* Adjust dest->tail to be able to fit src route data
495              * plus room for more route data. */
496             path_resize(dest,
497                     num_dest_points + num_src_points + ARRAY_CHUNK_SIZE);
498
499             memcpy(dest->tail + 1, src_first,
500                     num_src_points * sizeof(Point));
501
502             dest->tail += num_src_points;
503
504             /* Append waypoints from src to dest->. */
505             path_wresize(dest, (dest->wtail - dest->whead)
506                     + (src->wtail - src->whead) + 2 + ARRAY_CHUNK_SIZE);
507             for(curr = src->whead - 1; curr++ != src->wtail; )
508             {
509                 (++(dest->wtail))->point = dest->head + num_dest_points
510                     + (curr->point - src_first);
511                 dest->wtail->desc = curr->desc;
512             }
513
514         }
515
516         /* Kill old route - don't use MACRO_PATH_FREE(), because that
517          * would free the string desc's that we just moved to data.route. */
518         g_free(src->head);
519         g_free(src->whead);
520         if(policy_old < 0)
521             (*to_replace) = *dest;
522     }
523     else
524     {
525         MACRO_PATH_FREE((*to_replace));
526         /* Overwrite with data.route. */
527         (*to_replace) = data.path;
528         path_resize(to_replace,
529                 to_replace->tail - to_replace->head + 1 + ARRAY_CHUNK_SIZE);
530         path_wresize(to_replace,
531                 to_replace->wtail - to_replace->whead + 1 + ARRAY_CHUNK_SIZE);
532     }
533
534     vprintf("%s(): return TRUE\n", __PRETTY_FUNCTION__);
535     return TRUE;
536 }
537
538 /****************************************************************************
539  * ABOVE: OPEN PATH *********************************************************
540  ****************************************************************************/
541
542 /****************************************************************************
543  * BELOW: SAVE PATH *********************************************************
544  ****************************************************************************/
545
546 gboolean
547 gpx_path_write(Path *path, GnomeVFSHandle *handle)
548 {
549     Point *curr = NULL;
550     WayPoint *wcurr = NULL;
551     gboolean trkseg_break = FALSE;
552     printf("%s()\n", __PRETTY_FUNCTION__);
553
554     /* Find first non-zero point. */
555     for(curr = path->head - 1, wcurr = path->whead; curr++ != path->tail; )
556     {
557         if(curr->unity)
558             break;
559         else if(wcurr <= path->wtail && curr == wcurr->point)
560             wcurr++;
561     }
562
563     /* Write the header. */
564     gpx_write_string(handle,
565             "<?xml version=\"1.0\"?>\n"
566             "<gpx version=\"1.0\" creator=\"maemo-mapper\" "
567             "xmlns=\"http://www.topografix.com/GPX/1/0\">\n"
568             "  <trk>\n"
569             "    <trkseg>\n");
570
571     /* Curr points to first non-zero point. */
572     for(curr--; curr++ != path->tail; )
573     {
574         gdouble lat, lon;
575         if(curr->unity)
576         {
577             gchar buffer[80];
578             gboolean first_sub = TRUE;
579             if(trkseg_break)
580             {
581                 /* First trkpt of the segment - write trkseg header. */
582                 gpx_write_string(handle, "    </trkseg>\n"
583                              "    <trkseg>\n");
584                 trkseg_break = FALSE;
585             }
586             unit2latlon(curr->unitx, curr->unity, lat, lon);
587             gpx_write_string(handle, "      <trkpt lat=\"");
588             g_ascii_formatd(buffer, sizeof(buffer), "%.06f", lat);
589             gpx_write_string(handle, buffer);
590             gpx_write_string(handle, "\" lon=\"");
591             g_ascii_formatd(buffer, sizeof(buffer), "%.06f", lon);
592             gpx_write_string(handle, buffer);
593             gpx_write_string(handle, "\"");
594
595             /* write the elevation */
596             if(curr->altitude != 0)
597             {
598                 if(first_sub)
599                 {
600                     gpx_write_string(handle, ">\n");
601                     first_sub = FALSE;
602                 }
603                 gpx_write_string(handle, "        <ele>");
604                 {
605                     g_ascii_formatd(buffer, 80, "%.2f", curr->altitude);
606                     gpx_write_string(handle, buffer);
607                 }
608                 gpx_write_string(handle, "</ele>\n");
609             }
610
611             /* write the time */
612             if(curr->time)
613             {
614                 if(first_sub)
615                 {
616                     gpx_write_string(handle, ">\n");
617                     first_sub = FALSE;
618                 }
619                 gpx_write_string(handle, "        <time>");
620                 strftime(buffer, 80, XML_DATE_FORMAT, localtime(&curr->time));
621                 gpx_write_string(handle, buffer);
622                 gpx_write_string(handle, XML_TZONE);
623                 gpx_write_string(handle, "</time>\n");
624             }
625
626             if(wcurr && curr == wcurr->point)
627             {
628                 if(first_sub)
629                 {
630                     gpx_write_string(handle, ">\n");
631                     first_sub = FALSE;
632                 }
633                 gpx_write_string(handle, "        <desc>");
634                 gpx_write_escaped(handle, wcurr->desc);
635                 gpx_write_string(handle, "</desc>\n");
636                 wcurr++;
637             }
638             if(first_sub)
639             {
640                 gpx_write_string(handle, "/>\n");
641             }
642             else
643             {
644                 gpx_write_string(handle, "      </trkpt>\n");
645             }
646         }
647         else
648             trkseg_break = TRUE;
649     }
650
651     /* Write the footer. */
652     gpx_write_string(handle,
653             "    </trkseg>\n"
654             "  </trk>\n"
655             "</gpx>\n");
656
657
658     vprintf("%s(): return TRUE\n", __PRETTY_FUNCTION__);
659     return TRUE;
660 }
661
662 /****************************************************************************
663  * ABOVE: SAVE PATH *********************************************************
664  ****************************************************************************/
665
666 /****************************************************************************
667  * BELOW: OPEN POI **********************************************************
668  ****************************************************************************/
669
670 static void
671 gpx_poi_start_element(PoiSaxData *data,
672         const xmlChar *name, const xmlChar **attrs)
673 {
674     vprintf("%s(%s)\n", __PRETTY_FUNCTION__, name);
675
676     switch(data->sax_data.state)
677     {
678         case ERROR:
679             printf("ERROR!\n");
680             break;
681         case START:
682             if(!strcmp((gchar*)name, "gpx"))
683                 data->sax_data.state = INSIDE_GPX;
684             else
685                 MACRO_SET_UNKNOWN();
686             break;
687         case INSIDE_GPX:
688             if(!strcmp((gchar*)name, "wpt"))
689             {
690                 const xmlChar **curr_attr;
691                 gchar *error_check;
692                 gdouble lat = 0.0, lon = 0.0;
693                 gboolean has_lat, has_lon;
694                 has_lat = FALSE;
695                 has_lon = FALSE;
696
697                 /* Parse the attributes - there should be lat and lon. */
698                 for(curr_attr = attrs; *curr_attr != NULL; )
699                 {
700                     const gchar *attr_name = *curr_attr++;
701                     const gchar *attr_val = *curr_attr++;
702                     if(!strcmp(attr_name, "lat"))
703                     {
704                         lat = g_ascii_strtod(attr_val, &error_check);
705                         if(error_check != attr_val)
706                             has_lat = TRUE;
707                     }
708                     else if(!strcmp(attr_name, "lon"))
709                     {
710                         lon = g_ascii_strtod(attr_val, &error_check);
711                         if(error_check != attr_val)
712                             has_lon = TRUE;
713                     }
714                 }
715                 if(has_lat && has_lon)
716                 {
717                     data->sax_data.state = INSIDE_WPT;
718                     data->curr_poi = g_slice_new0(PoiInfo);
719                     data->curr_poi->lat = lat;
720                     data->curr_poi->lon = lon;
721                     data->poi_list = g_list_append(
722                             data->poi_list, data->curr_poi);
723                 }
724                 else
725                     data->sax_data.state = ERROR;
726             }
727             else
728                 MACRO_SET_UNKNOWN();
729             break;
730         case INSIDE_WPT:
731             if(!strcmp((gchar*)name, "name"))
732                 data->sax_data.state = INSIDE_WPT_NAME;
733             else if(!strcmp((gchar*)name, "desc"))
734                 data->sax_data.state = INSIDE_WPT_DESC;
735             else
736                 MACRO_SET_UNKNOWN();
737             break;
738         case UNKNOWN:
739             printf("UNKNOWN!\n");
740             data->sax_data.unknown_depth++;
741             break;
742         default:
743             ;
744     }
745
746     vprintf("%s(): return\n", __PRETTY_FUNCTION__);
747 }
748
749 /**
750  * Handle an end tag in the parsing of a GPX file.
751  */
752 static void
753 gpx_poi_end_element(PoiSaxData *data, const xmlChar *name)
754 {
755     vprintf("%s(%s)\n", __PRETTY_FUNCTION__, name);
756
757     switch(data->sax_data.state)
758     {
759         case ERROR:
760             printf("ERROR!\n");
761             break;
762         case START:
763             data->sax_data.state = ERROR;
764             break;
765         case INSIDE_GPX:
766             if(!strcmp((gchar*)name, "gpx"))
767             {
768                 data->sax_data.state = FINISH;
769             }
770             else
771                 data->sax_data.state = ERROR;
772             break;
773         case INSIDE_WPT:
774             if(!strcmp((gchar*)name, "wpt"))
775                 data->sax_data.state = INSIDE_GPX;
776             else
777                 data->sax_data.state = ERROR;
778             break;
779         case INSIDE_WPT_NAME:
780             if(!strcmp((gchar*)name, "name"))
781             {
782                 data->curr_poi->label
783                     = g_string_free(data->sax_data.chars, FALSE);
784                 data->sax_data.chars = g_string_new("");
785                 data->sax_data.state = INSIDE_WPT;
786             }
787             else
788                 data->sax_data.state = ERROR;
789             break;
790         case INSIDE_WPT_DESC:
791             if(!strcmp((gchar*)name, "desc"))
792             {
793                 data->curr_poi->desc
794                     = g_string_free(data->sax_data.chars, FALSE);
795                 data->sax_data.chars = g_string_new("");
796                 data->sax_data.state = INSIDE_WPT;
797             }
798             else
799                 data->sax_data.state = ERROR;
800             break;
801         case UNKNOWN:
802             printf("UNKNOWN!\n");
803             if(!--data->sax_data.unknown_depth)
804                 data->sax_data.state = data->sax_data.prev_state;
805             else
806                 data->sax_data.state = ERROR;
807             break;
808         default:
809             ;
810     }
811
812     vprintf("%s(): return\n", __PRETTY_FUNCTION__);
813 }
814
815 gboolean
816 gpx_poi_parse(gchar *buffer, gint size, GList **poi_list)
817 {
818     PoiSaxData data;
819     xmlSAXHandler sax_handler;
820     printf("%s()\n", __PRETTY_FUNCTION__);
821
822     data.poi_list = *poi_list;
823     data.sax_data.state = START;
824     data.sax_data.chars = g_string_new("");
825
826     memset(&sax_handler, 0, sizeof(sax_handler));
827     sax_handler.characters = (charactersSAXFunc)gpx_chars;
828     sax_handler.startElement = (startElementSAXFunc)gpx_poi_start_element;
829     sax_handler.endElement = (endElementSAXFunc)gpx_poi_end_element;
830     sax_handler.entityDecl = (entityDeclSAXFunc)gpx_get_entity;
831     sax_handler.warning = (warningSAXFunc)gpx_error;
832     sax_handler.error = (errorSAXFunc)gpx_error;
833     sax_handler.fatalError = (fatalErrorSAXFunc)gpx_error;
834
835     xmlSAXUserParseMemory(&sax_handler, &data, buffer, size);
836     g_string_free(data.sax_data.chars, TRUE);
837     *poi_list = data.poi_list;
838
839     if(data.sax_data.state != FINISH)
840     {
841         vprintf("%s(): return FALSE\n", __PRETTY_FUNCTION__);
842         return FALSE;
843     }
844
845     vprintf("%s(): return TRUE\n", __PRETTY_FUNCTION__);
846     return TRUE;
847 }
848
849 /****************************************************************************
850  * ABOVE: OPEN POI **********************************************************
851  ****************************************************************************/
852
853 /****************************************************************************
854  * BELOW: SAVE POI **********************************************************
855  ****************************************************************************/
856
857 gint
858 gpx_poi_write(GtkTreeModel *model, GnomeVFSHandle *handle)
859 {
860     gint num_written = 0;
861     GtkTreeIter iter;
862     printf("%s()\n", __PRETTY_FUNCTION__);
863
864     /* Write the header. */
865     gpx_write_string(handle,
866             "<?xml version=\"1.0\"?>\n"
867             "<gpx version=\"1.0\" creator=\"maemo-mapper\" "
868             "xmlns=\"http://www.topografix.com/GPX/1/0\">\n");
869
870     /* Iterate through the data model and import as desired. */
871     if(gtk_tree_model_get_iter_first(model, &iter)) do
872     {   
873         PoiInfo poi;
874         gboolean selected;
875         memset(&poi, 0, sizeof(poi));
876
877         gtk_tree_model_get(model, &iter,
878                 POI_SELECTED, &selected,
879                 POI_POIID, &(poi.poi_id),
880                 POI_CATID, &(poi.cat_id),
881                 POI_LAT, &(poi.lat),
882                 POI_LON, &(poi.lon),
883                 POI_LABEL, &(poi.label),
884                 POI_DESC, &(poi.desc),
885                 POI_CLABEL, &(poi.clabel),
886                 -1);
887
888         if(selected)
889         {
890             gchar buffer[80];
891
892             gpx_write_string(handle, "  <wpt lat=\"");
893             g_ascii_formatd(buffer, sizeof(buffer), "%.06f", poi.lat);
894             gpx_write_string(handle, buffer);
895             gpx_write_string(handle, "\" lon=\"");
896             g_ascii_formatd(buffer, sizeof(buffer), "%.06f", poi.lon);
897             gpx_write_string(handle, buffer);
898             gpx_write_string(handle, "\">\n");
899
900             if(poi.label && *poi.label)
901             {
902                 gpx_write_string(handle, "    <name>");
903                 gpx_write_escaped(handle, poi.label);
904                 gpx_write_string(handle, "</name>\n");
905             }
906
907             if(poi.desc && *poi.desc)
908             {
909                 gpx_write_string(handle, "    <desc>");
910                 gpx_write_escaped(handle, poi.desc);
911                 gpx_write_string(handle, "</desc>\n");
912             }
913             gpx_write_string(handle, "  </wpt>\n");
914             ++ num_written;
915         }
916     } while(gtk_tree_model_iter_next(model, &iter));
917
918     /* Write the footer. */
919     gpx_write_string(handle, "</gpx>\n");
920
921     vprintf("%s(): return %d\n", __PRETTY_FUNCTION__, num_written);
922     return num_written;
923 }
924
925 /****************************************************************************
926  * ABOVE: SAVE POI **********************************************************
927  ****************************************************************************/
928
929 void
930 gpx_init()
931 {
932     printf("%s()\n", __PRETTY_FUNCTION__);
933
934     /* set XML_TZONE */
935     {   
936         time_t time1;
937         struct tm time2;
938         time1 = time(NULL);
939         localtime_r(&time1, &time2);
940         snprintf(XML_TZONE, sizeof(XML_TZONE), "%+03ld:%02ld",
941                 (time2.tm_gmtoff / 60 / 60), (time2.tm_gmtoff / 60) % 60);
942     }
943
944     vprintf("%s(): return\n", __PRETTY_FUNCTION__);
945 }