1 /*
2 	Support for PathAway Palm Database,
3 	Copyright (C) 2005-2006 Olaf Klein, o.b.klein@gpsbabel.org
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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
18 
19 */
20 
21 /*
22 	remarks:
23 
24 	The german release 3.0 of PathAway violates the PathAway standards:
25 	* N.. .... O.. .... instead of N.. .... E.. ....
26 	* date is formatted in DDMMYYYY instead of YYYYMMDD
27 
28 	Release 4.x store only numeric coordinates and uses a six-number date.
29 
30 	Modified by by Andrei Boros <slackware@andrix.ro> 2008-11-07
31 	* added information about database vehicle icon
32 	* Pathaway 4.x can handle invalid date/time and date format apparently
33 	has changed slightly between revisions :
34 	131502.29 26102008 = HHMMSS.MS DDMMYYYY
35 	* work around errors reading date/time information
36 	(real life data collected by Pathaway sometimes has the date/time field
37 	contain some missing/invalid data. This information can be safely
38 	ignored most of the time. So far gpsbabel stopped processing files
39 	when encountering such invalid data)
40 	    - date/time field may contain one or more spaces between fields
41 	    - date/time field may start with one or more spaces
42 	    - date/time field may contain invalid characters -> ignore
43 	    - invalid or missing date/time -> ignore
44 	    - only time may be present (some older versions of Pathaway 4)
45 
46 	(this is still incomplete, but solved most of my problems when converting
47 	pathaway .pdb files)
48 
49 */
50 
51 
52 #include <ctype.h>
53 #include <math.h>
54 
55 #include "defs.h"
56 #if PDBFMTS_ENABLED
57 #include "csv_util.h"
58 #include "pdbfile.h"
59 #include "strptime.h"
60 
61 #define MYNAME "pathaway"
62 
63 #define PPDB_MAGIC_TRK	0x55735472		/* UsTr */
64 #define PPDB_MAGIC_WPT  0x506f4c69		/* PoLi */
65 #define PPDB_MAGIC	0x4b6e5772 		/* KwNr */
66 
67 #define VEHICLE_LEN	100
68 
69 static pdbfile *file_in, *file_out;
70 static char *fname_out;
71 static short_handle mkshort_handle;
72 static gpsdata_type ppdb_type;
73 static unsigned char german_release = 0;
74 static char *datefmt;
75 static int ct;
76 static int warn_ = 0;
77 
78 typedef struct ppdb_appdata {
79   unsigned char reservedA[274];		/* all 0 */
80   unsigned char dirtyFlag;
81   unsigned char dataBaseSubType; 		/* 0 = Track, 1 = Route */
82   short int dbAttributes;			/* 0 */
83   char vehicleStr[VEHICLE_LEN];
84   unsigned char reservedB[100];           /* all 0 */
85 } ppdb_appdata_t;
86 
87 #define PPDB_APPINFO_SIZE sizeof(struct ppdb_appdata)
88 static ppdb_appdata_t *appinfo;
89 
90 static char *opt_dbname = NULL;
91 static char *opt_dbicon = NULL;
92 static char *opt_deficon = NULL;
93 static char *opt_snlen = NULL;
94 static char *opt_date = NULL;
95 
96 static arglist_t ppdb_args[] = {
97   {"date",    &opt_date, "Read/Write date format (i.e. DDMMYYYY)", NULL, ARGTYPE_STRING, ARG_NOMINMAX},
98   {"dbname",  &opt_dbname, "Database name", NULL, ARGTYPE_STRING, ARG_NOMINMAX},
99   {"dbicon",  &opt_dbicon, "Database vehicle icon name", NULL, ARGTYPE_STRING, ARG_NOMINMAX},
100   {"deficon", &opt_deficon, "Default icon name", NULL, ARGTYPE_STRING, ARG_NOMINMAX},
101   {"snlen",   &opt_snlen, "Length of generated shortnames", "10", ARGTYPE_INT, "1", NULL },
102   ARG_TERMINATOR
103 };
104 
105 /*#undef PPDB_DEBUG*/
106 #define PPDB_DEBUG 1
107 
108 #if PPDB_DEBUG
109 static void
internal_debug1(const char * filename,int fileline)110 internal_debug1(const char *filename, int fileline)
111 {
112   static int ct=1;
113   printf("DBG(%d): file %s, line %d: ", ct++, filename, fileline);
114 }
115 static void
internal_debug2(const char * format,...)116 internal_debug2(const char *format, ...)
117 {
118   va_list args;
119 
120   va_start(args, format);
121   vprintf(format, args);
122   puts("");
123   va_end(args);
124 }
125 #define DBG(args)	internal_debug1(__FILE__, __LINE__);internal_debug2 args
126 #else
127 #define DBG(args)	;
128 #endif
129 
130 
131 #define CHECK_INP(i, j, k, l) is_fatal((i != j), "Error in data structure (in %s? Value is : %s).", (k), (l))
132 
133 /*
134  * utilities
135  */
136 
137 static
ppdb_strcat(char * dest,const char * src,const char * def,int * size)138 char *ppdb_strcat(char *dest, const char *src, const char *def, int *size)
139 {
140   int len;
141   char *res;
142   const char *tmp;
143 
144   tmp = src;
145   if (tmp == NULL) {
146     tmp = def;
147     if (tmp == NULL) {
148       return dest;
149     }
150   }
151   if (*tmp == '\0') {
152     return dest;
153   }
154 
155   len = strlen(dest) + strlen(tmp) + 1;
156   if (len > *size) {
157     *size = len;
158     res = (char*) xrealloc(dest, *size);
159   } else {
160     res = dest;
161   }
162   strcat(res, tmp);
163   return res;
164 }
165 
166 #define STR_POOL_SIZE 16	/* !!! any power of 2 !!! */
167 
168 static char *str_pool[STR_POOL_SIZE];
169 static size_t str_pool_s[STR_POOL_SIZE];
170 static int str_poolp = -1;
171 
172 static
str_pool_init(void)173 void str_pool_init(void)
174 {
175   int i;
176   for (i = 0; i < STR_POOL_SIZE; i++) {
177     str_pool[i] = NULL;
178     str_pool_s[i] = 0;
179   }
180 }
181 
182 static
str_pool_deinit(void)183 void str_pool_deinit(void)
184 {
185   int i;
186 
187   for (i = 0; i < STR_POOL_SIZE; i++)
188     if (str_pool_s[i] != 0) {
189       xfree(str_pool[i]);
190       str_pool[i] = NULL;
191       str_pool_s[i] = 0;
192     }
193 }
194 
195 static
str_pool_get(size_t size)196 char *str_pool_get(size_t size)
197 {
198   char *tmp;
199 
200   str_poolp = ((str_poolp + 1) & (STR_POOL_SIZE - 1));
201   tmp = str_pool[str_poolp];
202 
203   if (str_pool_s[str_poolp] == 0) {
204     tmp = (char*) xmalloc(size);
205   } else if (str_pool_s[str_poolp] < size) {
206     tmp = (char*) xrealloc(tmp, size);
207   } else {
208     return tmp;
209   }
210 
211   str_pool[str_poolp] = tmp;
212   str_pool_s[str_poolp] = size;
213 
214   return tmp;
215 }
216 
217 static
str_pool_getcpy(const char * src,const char * def)218 char *str_pool_getcpy(const char *src, const char *def)
219 {
220   char *res;
221 
222   if (src == NULL) {
223     src = def;
224     if (src == NULL) {
225       src = "";
226     }
227   }
228   res = str_pool_get(strlen(src) + 1);
229   strcpy(res, src);
230 
231   return res;
232 }
233 
234 /*
235  * decoding/formatting functions
236  */
237 
238 static
ppdb_fmt_float(const double val)239 char *ppdb_fmt_float(const double val)
240 {
241   char *str = str_pool_get(32);
242   char *c;
243   snprintf(str, 32, "%.8f", val);
244   c = str + strlen(str) - 1;
245   while ((c > str) && (*c == '0')) {
246     *c = '\0';
247     c--;
248     if (*c == '.') {
249       c++;
250       *c = '0';
251       break;
252     }
253   }
254   return str;
255 }
256 
257 static
ppdb_fmt_degrees(char dir,double val)258 char *ppdb_fmt_degrees(char dir, double val)
259 {
260   char *str = str_pool_get(32);
261   int deg = fabs(val);
262   double min = 60.0 * (fabs(val) - deg);
263   char *tmp;
264 
265   snprintf(str, 31, "%c%0*d %.8f", dir, (deg > 99) ? 3 : 2, deg, min);
266 
267   tmp = str + strlen(str) - 1;	/* trim trailing nulls */
268   while ((tmp > str) && (*tmp == '0')) {
269     *tmp = '\0';
270     tmp--;
271     if (*tmp == '.') {
272       tmp++;
273       *tmp = '0';
274       break;
275     }
276   }
277   return str;
278 }
279 
280 static
ppdb_decode_coord(const char * str)281 double ppdb_decode_coord(const char *str)
282 {
283   double val;
284   int deg;
285   char dir;
286 
287   if (*str < 'A') {	/* only numeric */
288     CHECK_INP(1, sscanf(str,"%lf", &val), "decode_coord(1) DD.dddd", str);
289     return val;
290   } else {
291     const char *tmp;
292 
293     if (*str == 'O') {
294       german_release = 1;
295     }
296 
297     tmp = strchr(str, ' ');
298     if ((tmp) && (tmp - str < 5)) {
299       CHECK_INP(3, sscanf(str,"%c%d %lf", &dir, &deg, &val), "decode_coord(2) DD MM.mmm", str);
300       val = deg + (val / 60.0);
301     } else {
302       CHECK_INP(2, sscanf(str,"%c%lf", &dir, &val), "decode_coord(3) DD.dddd", str);
303     }
304     if ((dir == 'S') || (dir == 'W')) {
305       val = -val;
306     }
307   }
308   return val;
309 }
310 
311 static
ppdb_decode_tm(char * str,struct tm * tm)312 int ppdb_decode_tm(char *str, struct tm *tm)
313 {
314   int msec, d1, d2, d3, d4;
315   int year;
316   int temp=0;
317   char *cx;
318 
319   str = lrtrim(str);              /* time field may start/end with spaces, drop them */
320 
321   if (*str == '\0') {
322     if (global_opts.debug_level > 0) {
323       warning(MYNAME ": Time value missing, reseting to 0\n");
324       warn_ = 1;
325     }
326     return 0;                       /* empty time field */
327   }
328 
329   if (strchr(str, '.')) {	/* time in hhmmss.ms */
330     CHECK_INP(4, sscanf(str, "%02d%02d%02d.%d",
331                         &tm->tm_hour, &tm->tm_min, &tm->tm_sec, &msec),
332               "decode_tm(1) hhmmss.ss", str);
333   } else if (sscanf(str,"%06d",&temp)==1)
334     /* WORKAROUND read time info only if a valid 6 digit string found */
335   {
336     CHECK_INP(3, sscanf(str, "%02d%02d%02d",
337                         &tm->tm_hour, &tm->tm_min, &tm->tm_sec),
338               "decode_tm(2) hhmmss", str);
339   } else {
340     if (global_opts.debug_level > 0) {
341       warning(MYNAME ": Invalid time value, reseting to 0\n");
342       warn_ = 1;
343     }
344     return 0;		/* WORKAROUND maybe invalid time, just ignore it and continue */
345   }
346   cx = strchr(str, ' ');
347 
348   if (cx == NULL) {
349     if (global_opts.debug_level > 0) {
350       warning(MYNAME ": Date value missing, reseting to 0\n");
351       warn_ = 1;
352     }
353     return 0;       /* empty date field */
354   }
355 
356   cx = lrtrim(cx);
357   if (*cx == '\0') {
358     if (global_opts.debug_level > 0) {
359       warning(MYNAME ": Date value missing, found only spaces, reseting to 0\n");
360       warn_ = 1;
361     }
362     return 0;       /* empty date field */
363   }
364 
365   if (datefmt) {
366     struct tm tm2;
367 
368     if (NULL == strptime(cx, datefmt, &tm2)) {
369       fatal(MYNAME ": Unable to convert date '%s' using format '%s' (%s)!\n", cx, datefmt, opt_date);
370     }
371 
372     tm->tm_year = tm2.tm_year + 1900;
373     tm->tm_mon = tm2.tm_mon + 1;
374     tm->tm_mday = tm2.tm_mday;
375   } else {
376     time_t tnow;
377     struct tm now;
378 
379 
380     tnow = current_time().toTime_t();
381     now = *localtime(&tnow);
382     now.tm_year += 1900;
383     now.tm_mon++;
384 
385     if (strlen(cx) == 8) {
386       CHECK_INP(4, sscanf(cx, "%02d%02d%02d%02d", &d1, &d2, &d3, &d4), "decode_tm(3) invalid date (YYYYMMDD)", cx);
387 
388       year = (d1 * 100) + d2;
389       /* the coordinates comes before date and time in
390          the dataset, so the flag "german_release" is set yet. */
391 
392       /* next code works for most, except for 19. and 20. of month */
393 
394       if ((german_release != 0) || (year < 1980) || (year > now.tm_year)) {	/* YYYYMMDD or DDMMYYYY ????? */
395         tm->tm_year = (d3 * 100) + d4;
396         tm->tm_mon = d2;
397         tm->tm_mday = d1;
398       } else {
399         tm->tm_year = (d1 * 100) + d2;
400         tm->tm_mon = d3;
401         tm->tm_mday = d4;
402       }
403     } else if (strlen(cx) == 6) {
404       CHECK_INP(3, sscanf(cx, "%02d%02d%02d", &d1, &d2, &d3), "decode_tm(3) invalid date (DDMMYY)", cx);
405       if (d3 < 1970) {		/* Usual Y2K interpretation */
406         year = d3 + 2000;
407       } else {
408         year = d3 + 1900;
409       }
410 
411       /* I don't know how a german release handles this
412        * so for now I will assume only DDMMYY if date has 6 digits
413        */
414       tm->tm_year = year;
415       tm->tm_mon = d2;
416       tm->tm_mday = d1;
417     } else {		/* date string is neither 8 nor 6 digits */
418       printf(MYNAME ": Date from first record is %s.\n", cx);
419       printf(MYNAME ": Please use option 'date' to specify how this is formatted.\n");
420       fatal(MYNAME  ": (... -i pathaway,date=DDMMYY ...)\n");
421     }
422   }
423   return 1;
424 }
425 
426 static
ppdb_read_wpt(route_head * head,int isRoute)427 int ppdb_read_wpt(route_head *head, int isRoute)
428 {
429   char *data, *str;
430   double altfeet;
431   struct tm tm;
432 
433   while (pdb_read_rec(file_in, NULL, NULL, NULL, (void **)&data) >= 0) {
434     waypoint *wpt_tmp = waypt_new();
435     int line = 0;
436     char *tmp = data;
437 
438     /* Print the whole input record. All input records are printed before processing. */
439     if (global_opts.debug_level >= 5) {
440       DBG(("\n\
441 --- BEGIN Input data record -----------------------------------------------\n\
442 %s\n\
443 --- END Input data record -------------------------------------------------\n",data));
444     }
445 
446     while ((str = csv_lineparse(tmp, ",", "\"", line++))) {
447       tmp = NULL;
448       switch (line) {
449       case 1:		/* latitude */
450         wpt_tmp->latitude = ppdb_decode_coord(str);
451         break;
452       case 2:		/* longitude */
453         wpt_tmp->longitude = ppdb_decode_coord(str);
454         break;
455       case 3:		/* altitude */
456         if (*str != '\0') {
457           CHECK_INP(1, sscanf(str, "%lf", &altfeet), "altitude", str);
458           if (altfeet != -9999) {
459             wpt_tmp->altitude = FEET_TO_METERS(altfeet);
460           }
461         }
462         break;
463       case 4:		/* time and date (optional) */
464         memset(&tm, 0, sizeof(tm));
465         if (ppdb_decode_tm(str, &tm)) {
466           tm.tm_year -= 1900;
467           tm.tm_mon--;
468           wpt_tmp->SetCreationTime(mkgmtime(&tm));
469         }
470         break;
471       case 5:		/* name */
472         if (*str != '\0') {
473           wpt_tmp->shortname = xstrdup(str);
474         }
475         break;
476       case 6:		/* icon */
477         if (*str != '\0') {
478           wpt_tmp->icon_descr = str;
479         }
480         break;
481       case 7:		/* notes */
482         if (*str != '\0') {
483           wpt_tmp->notes = xstrdup(str);
484         }
485         break;
486 
487       }
488     }
489 
490     /* Print the whole input record, should a warning be triggered.
491      * Use warning() here instead of DBG() to print the data record
492      * right after the warning is issued.
493      */
494     if (warn_ && (global_opts.debug_level > 1) && (global_opts.debug_level < 5)) {
495       warning("Faulty input data record : %s\n",data);
496       warn_ = 0;
497     }
498 
499     if (head && isRoute) {
500       route_add_wpt(head, wpt_tmp);
501     } else if (head) {
502       track_add_wpt(head, wpt_tmp);
503     } else {
504       waypt_add(wpt_tmp);
505     }
506 
507   }
508   return 0;
509 }
510 
511 /* ============================================================================================
512  * &&& gobal callbacks &&&
513  * ----------------------------------------------------------------------------------------- */
514 
ppdb_rd_init(const char * fname)515 static void ppdb_rd_init(const char *fname)
516 {
517   str_pool_init();
518   file_in = pdb_open(fname, MYNAME);
519   ct = 0;
520 
521   if (opt_date) {
522     datefmt = convert_human_date_format(opt_date);
523   } else {
524     datefmt = NULL;
525   }
526 }
527 
ppdb_rd_deinit(void)528 static void ppdb_rd_deinit(void)
529 {
530   pdb_close(file_in);
531   str_pool_deinit();
532   if (datefmt) {
533     xfree(datefmt);
534   }
535 }
536 
ppdb_read(void)537 static void ppdb_read(void)
538 {
539   ppdb_appdata_t *info = NULL;
540   route_head *track_head, *route_head;
541   const char *descr = NULL;
542 
543   if (file_in->creator != PPDB_MAGIC) {	/* identify the database */
544     fatal(MYNAME ": Not a PathAway pdb file.\n");
545   }
546 
547   if (file_in->version != 3) {	/* Currently we support only version 3 */
548     fatal(MYNAME ": This file is from an untested version (%d) of PathAway and is unsupported.\n", file_in->version);
549   }
550 
551   if ((file_in->appinfo_len > 0) && (file_in->appinfo != NULL)) {
552     info = (ppdb_appdata_t *) file_in->appinfo;
553     descr = info->vehicleStr;
554   }
555   switch (file_in->type) {
556   case PPDB_MAGIC_TRK:
557     ppdb_type = trkdata; /* as default */
558     if (info != NULL) {
559       switch (info->dataBaseSubType) {
560       case 0:
561         ppdb_type = trkdata;
562         break;
563       case 1:
564         ppdb_type = rtedata;
565         break;
566       default:
567         fatal(MYNAME": Invalid database subtype.\n");
568       }
569     }
570     break;
571 
572   case PPDB_MAGIC_WPT:
573     ppdb_type = wptdata;
574     break;
575 
576   default:
577     fatal(MYNAME ": It looks like a PathAway pdb, but has no gps magic.\n");
578   }
579 
580   switch (ppdb_type) {
581   case trkdata:
582     track_head = route_head_alloc();
583     track_add_head(track_head);
584     track_head->rte_name = xstrdup(file_in->name);
585     ppdb_read_wpt(track_head, 0);
586     break;
587   case rtedata:
588     route_head = route_head_alloc();
589     route_add_head(route_head);
590     route_head->rte_name = xstrdup(file_in->name);
591     ppdb_read_wpt(route_head, 1);
592     break;
593   case wptdata:
594   case unknown_gpsdata:
595     ppdb_read_wpt(NULL, 0);
596     break;
597   case posndata:
598     fatal(MYNAME ": Realtime positioning not supported.\n");
599     break;
600   }
601 }
602 
603 /* ============================================================================================
604  *   PPDB: Write support
605  * -------------------------------------------------------------------------------------------*/
606 
ppdb_wr_init(const char * fname)607 static void ppdb_wr_init(const char *fname)
608 {
609   int len;
610 
611   fname_out = xstrdup(fname);
612   str_pool_init();
613   file_out = pdb_create(fname, MYNAME);
614   mkshort_handle = mkshort_new_handle();
615   ct = 0;
616   appinfo = NULL;
617 
618   if (global_opts.synthesize_shortnames != 0) {
619     len = atoi(opt_snlen);
620     setshort_length(mkshort_handle, len);
621     setshort_mustupper(mkshort_handle, 1);
622     setshort_badchars(mkshort_handle, ",");
623     setshort_whitespace_ok(mkshort_handle, 0);
624   }
625   if (opt_date) {
626     char *c = convert_human_date_format(opt_date);
627     xasprintf(&datefmt, "%s %s", "%H%M%S", c);
628     xfree(c);
629   } else {
630     datefmt = xstrdup("%H%M%S %Y%m%d");
631   }
632 }
633 
ppdb_wr_deinit(void)634 static void ppdb_wr_deinit(void)
635 {
636   mkshort_del_handle(&mkshort_handle);
637   pdb_close(file_out);
638   str_pool_deinit();
639   xfree(fname_out);
640   if (datefmt) {
641     xfree(datefmt);
642   }
643   if (appinfo) {
644     xfree(appinfo);
645   }
646 }
647 
648 /*
649  * ppdb_write_wpt: callback for waypoint output
650  */
651 
652 #define REC_SIZE 128
653 
ppdb_write_wpt(const waypoint * wpt)654 static void ppdb_write_wpt(const waypoint *wpt)
655 {
656   char *buff, *tmp;
657   char latdir, longdir;
658   int len;
659   struct tm tm;
660 
661   buff = (char *) xcalloc(REC_SIZE, 1);
662 
663   if (wpt->latitude < 0) {
664     latdir = 'S';
665   } else {
666     latdir = 'N';
667   }
668   if (wpt->longitude < 0) {
669     longdir = 'W';
670   } else {
671     longdir = 'E';
672   }
673   /* 1 latitude,
674      2 longitude */
675 
676   snprintf(buff, REC_SIZE, "%s,%s,",
677            ppdb_fmt_degrees(latdir, wpt->latitude),
678            ppdb_fmt_degrees(longdir, wpt->longitude)
679           );
680 
681   len = REC_SIZE;		/* we have coordinates in buff, now optional stuff */
682   /* 3 altitude */
683 
684   if (fabs(wpt->altitude) < 9999.0) {
685     tmp = str_pool_get(32);
686     snprintf(tmp, 32, "%s", ppdb_fmt_float(METERS_TO_FEET(wpt->altitude)));
687     buff = ppdb_strcat(buff, tmp, NULL, &len);
688   }
689   buff = ppdb_strcat(buff, ",", NULL, &len);
690   /* 4 time, date */
691 
692   if (wpt->creation_time.isValid()) {
693     tmp = str_pool_get(20);
694     const time_t tt = wpt->GetCreationTime().toTime_t();
695     tm = *gmtime(&tt);
696     strftime(tmp, 20, datefmt, &tm);
697     buff = ppdb_strcat(buff, tmp, NULL, &len);
698   }
699   buff = ppdb_strcat(buff, ",", NULL, &len);
700   /* 5 name */
701 
702   if (global_opts.synthesize_shortnames != 0) {
703     tmp = mkshort_from_wpt(mkshort_handle, wpt);
704     DBG(("shortname %s from %s", tmp, wpt->shortname));
705   } else {
706     tmp = str_pool_getcpy(wpt->shortname, "");
707     while (strchr(tmp, ',') != NULL) {
708       *strchr(tmp, ',') = '.';
709     }
710   }
711   buff = ppdb_strcat(buff, tmp, "", &len);
712 
713   buff = ppdb_strcat(buff, ",", NULL, &len);
714   /* 6 icon */
715 
716   tmp = str_pool_getcpy(wpt->icon_descr.toUtf8().data(), opt_deficon);	/* point icon or deficon from options */
717   buff = ppdb_strcat(buff, tmp, NULL, &len);
718   buff = ppdb_strcat(buff, ",", NULL, &len);
719   /* 7 description */
720 
721   tmp = str_pool_getcpy(wpt->description, "");
722   if (strchr(tmp, ',') != NULL) {
723     buff = ppdb_strcat(buff, "\"", NULL, &len);
724     while (strchr(tmp, '"') != NULL) {
725       *strchr(tmp, '"') = '\'';
726     }
727     buff = ppdb_strcat(buff, tmp,  NULL, &len);
728     buff = ppdb_strcat(buff, "\"", NULL, &len);
729   } else {
730     buff = ppdb_strcat(buff, tmp, "", &len);
731   }
732 
733   len = strlen(buff) + 1;
734   pdb_write_rec(file_out, 0, 0, ct++, buff, len);
735 
736   xfree(buff);
737 }
738 
739 /*
740  * track and route write callbacks
741  */
742 
ppdb_write(void)743 static void ppdb_write(void)
744 {
745 
746   if (opt_dbname) {
747     strncpy(file_out->name, opt_dbname, PDB_DBNAMELEN);
748   }
749 
750   file_out->attr = PDB_FLAG_BACKUP;
751   file_out->ctime = file_out->mtime = current_time().toTime_t() + 2082844800U;
752   file_out->creator = PPDB_MAGIC;
753   file_out->version = 3;
754 
755   /*	Waypoint target does use vehicleStr from appinfo block
756    *	Actually, all 3 types have vehicle information.
757    *	if (global_opts.objective != wptdata)	/ * Waypoint target do not need appinfo block * /
758    *	{
759    */
760   appinfo = (ppdb_appdata_t *) xcalloc(1, sizeof(*appinfo));
761   file_out->appinfo = (void *)appinfo;
762   file_out->appinfo_len = PPDB_APPINFO_SIZE;
763   /*	}
764    */
765   if (opt_dbicon != NULL) {
766     strncpy(appinfo->vehicleStr, opt_dbicon, VEHICLE_LEN);
767   }
768 
769   switch (global_opts.objective) {	/* Only one target is possible */
770   case wptdata:
771   case unknown_gpsdata:
772     if (opt_dbname == NULL) {
773       strncpy(file_out->name, "PathAway Waypoints", PDB_DBNAMELEN);
774     }
775     file_out->type = PPDB_MAGIC_WPT;
776     waypt_disp_all(ppdb_write_wpt);
777     break;
778   case trkdata:
779     if (opt_dbname == NULL) {
780       strncpy(file_out->name, "PathAway Track", PDB_DBNAMELEN);
781     }
782     file_out->type = PPDB_MAGIC_TRK;
783     appinfo->dataBaseSubType = 0;
784     track_disp_all(NULL, NULL, ppdb_write_wpt);
785     break;
786   case rtedata:
787     if (opt_dbname == NULL) {
788       strncpy(file_out->name, "PathAway Route", PDB_DBNAMELEN);
789     }
790     file_out->type = PPDB_MAGIC_TRK;
791     appinfo->dataBaseSubType = 1;
792     route_disp_all(NULL, NULL, ppdb_write_wpt);
793     break;
794   case posndata:
795     fatal(MYNAME ": Realtime positioning not supported.\n");
796     break;
797   }
798 }
799 
800 
801 ff_vecs_t ppdb_vecs = {
802   ff_type_file,
803   FF_CAP_RW_ALL,
804   ppdb_rd_init,
805   ppdb_wr_init,
806   ppdb_rd_deinit,
807   ppdb_wr_deinit,
808   ppdb_read,
809   ppdb_write,
810   NULL,
811   ppdb_args,
812   CET_CHARSET_ASCII, 0	/* CET-REVIEW */
813 };
814 #endif
815