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