1 /*
2 
3     Support for MapSource Text Export (Tab delimited) files.
4 
5     Copyright (C) 2006 Olaf Klein, o.b.klein@gpsbabel.org
6 
7     This program is free software; you can redistribute it and/or modify
8     it under the terms of the GNU General Public License as published by
9     the Free Software Foundation; either version 2 of the License, or
10     (at your option) any later version.
11 
12     This program is distributed in the hope that it will be useful,
13     but WITHOUT ANY WARRANTY; without even the implied warranty of
14     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15     GNU General Public License for more details.
16 
17     You should have received a copy of the GNU General Public License
18     along with this program; if not, write to the Free Software
19     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
20 
21  */
22 
23 #include "defs.h"
24 
25 #if CSVFMTS_ENABLED
26 #include <cctype>                  // for toupper
27 #include <cmath>                   // for fabs, floor
28 #include <cstdio>                  // for NULL, snprintf, sscanf
29 #include <cstdint>
30 #include <cstdlib>                 // for atoi, abs, qsort
31 #include <cstring>                 // for memset, strstr, strcat, strchr, strlen, strcmp, strcpy, strncpy
32 #include <ctime>                   // for gmtime, localtime, strftime
33 
34 #include <QtCore/QByteArray>       // for QByteArray
35 #include <QtCore/QChar>            // for QChar, QChar::Other_Control
36 #include <QtCore/QIODevice>        // for QIODevice, QIODevice::ReadOnly, QIODevice::WriteOnly
37 #include <QtCore/QString>          // for QString, operator!=
38 #include <QtCore/QTextStream>      // for QTextStream
39 #include <QtCore/QVector>          // for QVector
40 #include <QtCore/Qt>               // for CaseInsensitive
41 #include <QtCore/QtGlobal>         // for qPrintable
42 
43 #include "csv_util.h"              // for csv_lineparse
44 #include "garmin_fs.h"             // for garmin_fs_t, garmin_fs_alloc, garmin_fs_convert_category, GMSD_SECTION_CATEGORIES
45 #include "garmin_tables.h"         // for gt_display_modes_e, gt_find_desc_from_icon_number, gt_find_icon_number_from_desc, gt_get_mps_grid_longname, gt_lookup_datum_index, gt_lookup_grid_type, GDB, gt_get_icao_cc, gt_get_icao_country, gt_get_mps_datum_name, gt_waypt_class_names, GT_DISPLAY_MODE...
46 #include "inifile.h"               // for inifile_readstr
47 #include "jeeps/gpsmath.h"         // for GPS_Math_Known_Datum_To_UTM_EN, GPS_Math_WGS84_To_Known_Datum_M, GPS_Math_WGS84_To_Swiss_EN, GPS_Math_WGS84_To_UKOSMap_M
48 #include "src/core/datetime.h"     // for DateTime
49 #include "src/core/textstream.h"   // for TextStream
50 #include "strptime.h"              // for strptime
51 
52 
53 #define MYNAME "garmin_txt"
54 
55 struct gtxt_flags_t {
56   unsigned int metric:1;
57   unsigned int celsius:1;
58   unsigned int utc:1;
59   unsigned int enum_waypoints:1;
60   unsigned int route_header_written:1;
61   unsigned int track_header_written:1;
62 };
63 
64 static gpsbabel::TextStream* fin = nullptr;
65 static gpsbabel::TextStream* fout = nullptr;
66 static route_head* current_trk, *current_rte;
67 static int waypoints;
68 static int routepoints;
69 static const Waypoint** wpt_a;
70 static int wpt_a_ct;
71 static grid_type grid_index;
72 static int datum_index;
73 static const char* datum_str;
74 static int current_line;
75 static char* date_time_format = nullptr;
76 static int precision = 3;
77 static time_t utc_offs = 0;
78 
79 static gtxt_flags_t gtxt_flags;
80 
81 enum header_type {
82   waypt_header = 0,
83   rtept_header,
84   trkpt_header,
85   route_header,
86   track_header,
87   unknown_header
88 };
89 
operator ++(header_type & s)90 inline header_type& operator++(header_type& s) // prefix
91 {
92   return s = static_cast<header_type>(s + 1);
93 }
operator ++(header_type & s,int)94 inline header_type operator++(header_type& s, int) // postfix
95 {
96   header_type ret(s);
97   s = ++s;
98   return ret;
99 }
100 
operator ++(gt_display_modes_e & s)101 inline gt_display_modes_e& operator++(gt_display_modes_e& s) // prefix
102 {
103   return s = static_cast<gt_display_modes_e>(s + 1);
104 }
operator ++(gt_display_modes_e & s,int)105 inline gt_display_modes_e operator++(gt_display_modes_e& s, int) // postfix
106 {
107   gt_display_modes_e ret(s);
108   s = ++s;
109   return ret;
110 }
111 
112 #define MAX_HEADER_FIELDS 36
113 
114 static char* header_lines[unknown_header + 1][MAX_HEADER_FIELDS];
115 static int header_fields[unknown_header + 1][MAX_HEADER_FIELDS];
116 static int header_ct[unknown_header + 1];
117 
118 #define GARMIN_UNKNOWN_ALT 1.0e25f
119 #define DEFAULT_DISPLAY garmin_display_symbol_and_name
120 #define DEFAULT_DATE_FORMAT "dd/mm/yyyy"
121 #define DEFAULT_TIME_FORMAT "HH:mm:ss"
122 
123 /* macros */
124 
125 #define IS_VALID_ALT(a) (((a) != unknown_alt) && ((a) < GARMIN_UNKNOWN_ALT))
126 #define DUPSTR(a) (((a) != NULL) && ((a)[0] != 0)) ? ((a)) : NULL
127 
128 static char* opt_datum = nullptr;
129 static char* opt_dist = nullptr;
130 static char* opt_temp = nullptr;
131 static char* opt_date_format = nullptr;
132 static char* opt_time_format = nullptr;
133 static char* opt_precision = nullptr;
134 static char* opt_utc = nullptr;
135 static char* opt_grid = nullptr;
136 
137 static
138 QVector<arglist_t> garmin_txt_args = {
139   {"date",  &opt_date_format, "Read/Write date format (i.e. yyyy/mm/dd)", nullptr, ARGTYPE_STRING, ARG_NOMINMAX, nullptr},
140   {"datum", &opt_datum, 	    "GPS datum (def. WGS 84)", "WGS 84", ARGTYPE_STRING, ARG_NOMINMAX, nullptr},
141   {"dist",  &opt_dist,        "Distance unit [m=metric, s=statute]", "m", ARGTYPE_STRING, ARG_NOMINMAX, nullptr},
142   {"grid",  &opt_grid,        "Write position using this grid.", nullptr, ARGTYPE_STRING, ARG_NOMINMAX, nullptr},
143   {"prec",  &opt_precision,   "Precision of coordinates", "3", ARGTYPE_INT, ARG_NOMINMAX, nullptr},
144   {"temp",  &opt_temp,        "Temperature unit [c=Celsius, f=Fahrenheit]", "c", ARGTYPE_STRING, ARG_NOMINMAX, nullptr},
145   {"time",  &opt_time_format, "Read/Write time format (i.e. HH:mm:ss xx)", nullptr, ARGTYPE_STRING, ARG_NOMINMAX, nullptr},
146   {"utc",   &opt_utc,         "Write timestamps with offset x to UTC time", nullptr, ARGTYPE_INT, "-23", "+23", nullptr},
147 };
148 
149 struct info_t {
150   double length;
151   time_t start;
152   time_t time;
153   double speed;
154   double total;
155   int count;
156   const Waypoint* prev_wpt;
157   const Waypoint* first_wpt;
158   const Waypoint* last_wpt;
159 };
160 
161 static info_t* route_info;
162 static int route_idx;
163 static info_t* cur_info;
164 
165 static const char* headers[] = {
166   "Name\tDescription\tType\tPosition\tAltitude\tDepth\tProximity\tTemperature\t"
167   "Display Mode\tColor\tSymbol\tFacility\tCity\tState\tCountry\t"
168   "Date Modified\tLink\tCategories",
169   "Waypoint Name\tDistance\tLeg Length\tCourse",
170   "Position\tTime\tAltitude\tDepth\tTemperature\tLeg Length\tLeg Time\tLeg Speed\tLeg Course",
171   "Name\tLength\tCourse\tWaypoints\tLink",
172   "Name\tStart Time\tElapsed Time\tLength\tAverage Speed\tLink",
173   nullptr
174 };
175 
176 /* helpers */
177 
178 static const char*
get_option_val(const char * option,const char * def)179 get_option_val(const char* option, const char* def)
180 {
181   const char* c = (option != nullptr) ? option : def;
182   return c;
183 }
184 
185 static void
init_date_and_time_format()186 init_date_and_time_format()
187 {
188   const char* f = get_option_val(opt_date_format, DEFAULT_DATE_FORMAT);
189   date_time_format = convert_human_date_format(f);
190 
191   date_time_format = xstrappend(date_time_format, " ");
192 
193   f = get_option_val(opt_time_format, DEFAULT_TIME_FORMAT);
194   const char* c = convert_human_time_format(f);
195   date_time_format = xstrappend(date_time_format, c);
196   xfree((void*) c);
197 }
198 
199 static void
convert_datum(const Waypoint * wpt,double * dest_lat,double * dest_lon)200 convert_datum(const Waypoint* wpt, double* dest_lat, double* dest_lon)
201 {
202   double alt;
203 
204   if (datum_index == DATUM_WGS84) {
205     *dest_lat = wpt->latitude;
206     *dest_lon = wpt->longitude;
207   } else GPS_Math_WGS84_To_Known_Datum_M(wpt->latitude, wpt->longitude, 0.0,
208                                            dest_lat, dest_lon, &alt, datum_index);
209 }
210 
211 /* WRITER *****************************************************************/
212 
213 /* Waypoint preparation */
214 
215 static void
enum_waypt_cb(const Waypoint * wpt)216 enum_waypt_cb(const Waypoint* wpt)
217 {
218   garmin_fs_t* gmsd = garmin_fs_t::find(wpt);
219   int wpt_class = garmin_fs_t::get_wpt_class(gmsd, 0);
220   if (wpt_class < 0x80) {
221     if (gtxt_flags.enum_waypoints) {		/* enumerate only */
222       waypoints++;
223       return;
224     }
225     for (int i = 0; i < wpt_a_ct; i++) {		/* check for duplicates */
226       const Waypoint* tmp = wpt_a[i];
227       if (case_ignore_strcmp(tmp->shortname, wpt->shortname) == 0) {
228         wpt_a[i] = wpt;
229         waypoints--;
230         return;
231 
232       }
233     }
234     wpt_a[wpt_a_ct++] = wpt;
235   }
236 
237 }
238 
239 static int
sort_waypt_cb(const void * a,const void * b)240 sort_waypt_cb(const void* a, const void* b)
241 {
242   const Waypoint* wa = *(Waypoint**)a;
243   const Waypoint* wb = *(Waypoint**)b;
244   return wa->shortname.compare(wb->shortname, Qt::CaseInsensitive);
245 }
246 
247 
248 /* common route and track pre-work */
249 
250 static void
prework_hdr_cb(const route_head *)251 prework_hdr_cb(const route_head*)
252 {
253   cur_info = &route_info[route_idx];
254   cur_info->prev_wpt = nullptr;
255   cur_info->length = 0;
256   cur_info->time = 0;
257 }
258 
259 static void
prework_tlr_cb(const route_head *)260 prework_tlr_cb(const route_head*)
261 {
262   cur_info->last_wpt = cur_info->prev_wpt;
263   route_idx++;
264 }
265 
266 static void
prework_wpt_cb(const Waypoint * wpt)267 prework_wpt_cb(const Waypoint* wpt)
268 {
269   const Waypoint* prev = cur_info->prev_wpt;
270 
271   if (prev != nullptr) {
272     cur_info->time += (wpt->GetCreationTime().toTime_t() - prev->GetCreationTime().toTime_t());
273     cur_info->length += waypt_distance_ex(prev, wpt);
274   } else {
275     cur_info->first_wpt = wpt;
276     cur_info->start = wpt->GetCreationTime().toTime_t();
277   }
278   cur_info->prev_wpt = wpt;
279   cur_info->count++;
280   routepoints++;
281 }
282 
283 
284 /* output helpers */
285 
286 static void
print_position(const Waypoint * wpt)287 print_position(const Waypoint* wpt)
288 {
289   int valid = 1;
290   double lat, lon, north, east;
291   int zone;
292   char map[3], zonec;
293 
294   convert_datum(wpt, &lat, &lon);
295 
296   /* ----------------------------------------------------------------------------*/
297   /*            the following code is from pretty_deg_format (util.c)            */
298   /* ----------------------------------------------------------------------------*/
299   /* !ToDo! generate common code for calculating of degrees, minutes and seconds */
300   /* ----------------------------------------------------------------------------*/
301 
302   char latsig = lat < 0 ? 'S':'N';
303   char lonsig = lon < 0 ? 'W':'E';
304   int latint = abs((int) lat);
305   int lonint = abs((int) lon);
306   double latmin = 60.0 * (fabs(lat) - latint);
307   double lonmin = 60.0 * (fabs(lon) - lonint);
308   double latsec = 60.0 * (latmin - floor(latmin));
309   double lonsec = 60.0 * (lonmin - floor(lonmin));
310 
311   switch (grid_index) {
312 
313   case grid_lat_lon_ddd:
314 
315     *fout << QString::asprintf("%c%0.*f %c%0.*f\t",
316                                latsig, precision, fabs(lat),
317                                lonsig, precision, fabs(lon));
318     break;
319 
320   case grid_lat_lon_dmm:
321 
322     *fout << QString::asprintf("%c%d %0*.*f %c%d %0*.*f\t",
323                                latsig, latint, precision + 3, precision, latmin,
324                                lonsig, lonint, precision + 3, precision, lonmin);
325     break;
326 
327   case grid_lat_lon_dms:
328 
329     *fout << QString::asprintf("%c%d %d %.*f %c%d %d %.*f\t",
330                                latsig, latint, (int)latmin, precision, latsec,
331                                lonsig, lonint, (int)lonmin, precision, lonsec);
332     break;
333 
334   case grid_bng:
335 
336     valid = GPS_Math_WGS84_To_UKOSMap_M(wpt->latitude, wpt->longitude, &east, &north, map);
337     if (valid) {
338       *fout << QString::asprintf("%s %5.0f %5.0f\t", map, east, north);
339     }
340     break;
341 
342   case grid_utm:
343 
344     valid = GPS_Math_Known_Datum_To_UTM_EN(lat, lon,
345                                            &east, &north, &zone, &zonec, datum_index);
346     if (valid) {
347       *fout << QString::asprintf("%02d %c %.0f %.0f\t", zone, zonec, east, north);
348     }
349     break;
350 
351   case grid_swiss:
352 
353     valid = GPS_Math_WGS84_To_Swiss_EN(wpt->latitude, wpt->longitude, &east, &north);
354     if (valid) {
355       *fout << QString::asprintf("%.f %.f\t", east, north);
356     }
357     break;
358 
359   default:
360     fatal("ToDo\n");
361   }
362 
363   if (! valid) {
364     *fout << "#####\n";
365     fatal(MYNAME ": %s (%s) is outside of convertible area \"%s\"!\n",
366           wpt->shortname.isEmpty() ? "Waypoint" : qPrintable(wpt->shortname),
367           pretty_deg_format(wpt->latitude, wpt->longitude, 'd', nullptr, 0),
368           gt_get_mps_grid_longname(grid_index, MYNAME));
369   }
370 }
371 
372 static void
print_date_and_time(const time_t time,const int time_only)373 print_date_and_time(const time_t time, const int time_only)
374 {
375   struct tm tm;
376   char tbuf[32];
377 
378   if (time < 0) {
379     *fout << "\t";
380     return;
381   }
382   if (time_only) {
383     tm = *gmtime(&time);
384     snprintf(tbuf, sizeof(tbuf), "%d:%02d:%02d", tm.tm_hour, tm.tm_min, tm.tm_sec);
385     *fout << QString::asprintf("%s", tbuf);
386   } else if (time != 0) {
387     if (gtxt_flags.utc) {
388       time_t t = time + utc_offs;
389       tm = *gmtime(&t);
390     } else {
391       tm = *localtime(&time);
392     }
393     strftime(tbuf, sizeof(tbuf), date_time_format, &tm);
394     *fout << QString::asprintf("%s ", tbuf);
395   }
396   *fout << "\t";
397 }
398 
399 static void
print_categories(uint16_t categories)400 print_categories(uint16_t categories)
401 {
402   if (categories == 0) {
403     return;
404   }
405 
406   int count = 0;
407   for (int i = 0; i < 16; i++) {
408     if ((categories & 1) != 0) {
409       QString c;
410       if (global_opts.inifile != nullptr) {
411         QString key = QString::number(i + 1);
412         c = inifile_readstr(global_opts.inifile, GMSD_SECTION_CATEGORIES, key);
413       }
414 
415       *fout << QString::asprintf("%s", (count++ > 0) ? "," : "");
416       if (c.isNull()) {
417         *fout << QString::asprintf("Category %d", i+1);
418       }
419 //				*fout << QString::asprintf("%s", gps_categories[i]);
420       else {
421         *fout << c;
422       }
423 
424     }
425     categories = categories >> 1;
426   }
427 }
428 
429 static void
print_course(const Waypoint * A,const Waypoint * B)430 print_course(const Waypoint* A, const Waypoint* B)		/* seems to be okay */
431 {
432   if ((A != nullptr) && (B != nullptr) && (A != B)) {
433     int course = si_round(waypt_course(A, B));
434     *fout << QString::asprintf("%d° true", course);
435   }
436 }
437 
438 static void
print_distance(const double distance,const int no_scale,const int with_tab,const int decis)439 print_distance(const double distance, const int no_scale, const int with_tab, const int decis)
440 {
441   double dist = distance;
442 
443   if (gtxt_flags.metric == 0) {
444     dist = METERS_TO_FEET(dist);
445 
446     if ((dist < 5280) || no_scale) {
447       *fout << QString::asprintf("%.*f ft", decis, dist);
448     } else {
449       dist = METERS_TO_MILES(distance);
450       if (dist < 100.0) {
451         *fout << QString::asprintf("%.1f mi", dist);
452       } else {
453         *fout << QString::asprintf("%d mi", si_round(dist));
454       }
455     }
456   } else {
457     if ((dist < 1000) || no_scale) {
458       *fout << QString::asprintf("%.*f m", decis, dist);
459     } else {
460       dist = dist / 1000.0;
461       if (dist < 100.0) {
462         *fout << QString::asprintf("%.1f km", dist);
463       } else {
464         *fout << QString::asprintf("%d km", si_round(dist));
465       }
466     }
467   }
468   if (with_tab) {
469     *fout << "\t";
470   }
471 }
472 
473 static void
print_speed(const double * distance,const time_t * time)474 print_speed(const double* distance, const time_t* time)
475 {
476   double dist = *distance;
477   const char* unit;
478 
479   if (!gtxt_flags.metric) {
480     dist = METERS_TO_MILES(dist) * 1000.0;
481     unit = "mph";
482   } else {
483     unit = "kph";
484   }
485   int idist = si_round(dist);
486 
487   if ((*time != 0) && (idist > 0)) {
488     double speed = MPS_TO_KPH(dist / (double)*time);
489     int ispeed = si_round(speed);
490 
491     if (speed < 0.01) {
492       *fout << QString::asprintf("0 %s", unit);
493     } else if (ispeed < 2) {
494       *fout << QString::asprintf("%.1f %s", speed, unit);
495     } else {
496       *fout << QString::asprintf("%d %s", ispeed, unit);
497     }
498   } else {
499     *fout << QString::asprintf("0 %s", unit);
500   }
501   *fout << "\t";
502 }
503 
504 static void
print_temperature(const float temperature)505 print_temperature(const float temperature)
506 {
507   if (gtxt_flags.celsius) {
508     *fout << QString::asprintf("%.f C", temperature);
509   } else {
510     *fout << QString::asprintf("%.f F", (temperature * 1.8) + 32);
511   }
512 }
513 
514 static void
print_string(const char * fmt,const QString & string)515 print_string(const char* fmt, const QString& string)
516 {
517   /* remove unwanted characters from source string */
518   QString cleanstring;
519   for (const auto chr : string) {
520     if (chr.category() != QChar::Other_Control) {
521       cleanstring.append(chr);
522     } else {
523       cleanstring.append(' ');
524     }
525   }
526   *fout << QString::asprintf(fmt, CSTR(cleanstring));
527 }
528 
529 
530 /* main cb's */
531 
532 static void
write_waypt(const Waypoint * wpt)533 write_waypt(const Waypoint* wpt)
534 {
535   const char* wpt_type;
536 
537   garmin_fs_t* gmsd = garmin_fs_t::find(wpt);
538 
539   int i = garmin_fs_t::get_display(gmsd, 0);
540   if (i > GT_DISPLAY_MODE_MAX) {
541     i = 0;
542   }
543   const char* dspl_mode = gt_display_mode_names[i];
544 
545   unsigned char wpt_class = garmin_fs_t::get_wpt_class(gmsd, 0);
546   if (wpt_class <= gt_waypt_class_map_line) {
547     wpt_type = gt_waypt_class_names[wpt_class];
548   } else {
549     wpt_type = gt_waypt_class_names[0];
550   }
551 
552   *fout << "Waypoint\t" << wpt->shortname << "\t";
553   if (wpt_class <= gt_waypt_class_airport_ndb) {
554     QString temp = wpt->notes;
555     if (temp.isEmpty()) {
556       if (wpt->description != wpt->shortname) {
557         temp = wpt->description;
558       } else {
559         temp = "";
560       }
561     }
562     print_string("%s\t", temp);
563   } else {
564     *fout << "\t";
565   }
566   *fout << QString::asprintf("%s\t", wpt_type);
567 
568   print_position(wpt);
569 
570   if IS_VALID_ALT(wpt->altitude) {
571     print_distance(wpt->altitude, 1, 0, 0);
572   }
573   *fout << "\t";
574 
575   double x = WAYPT_GET(wpt, depth, unknown_alt);
576   if (x != unknown_alt) {
577     print_distance(x, 1, 0, 1);
578   }
579   *fout << "\t";
580 
581   x = WAYPT_GET(wpt, proximity, unknown_alt);
582   if (x != unknown_alt) {
583     print_distance(x, 0, 0, 0);
584   }
585   *fout << "\t";
586 
587   x = WAYPT_GET(wpt, temperature, -999);
588   if (x != -999) {
589     print_temperature(x);
590   }
591   *fout << QString::asprintf("\t%s\t", dspl_mode);
592 
593   *fout << "Unknown\t"; 				/* Color is fixed: Unknown */
594 
595   int icon = garmin_fs_t::get_icon(gmsd, -1);
596   if (icon == -1) {
597     icon = gt_find_icon_number_from_desc(wpt->icon_descr, GDB);
598   }
599   print_string("%s\t", gt_find_desc_from_icon_number(icon, GDB));
600 
601   print_string("%s\t", garmin_fs_t::get_facility(gmsd, ""));
602   print_string("%s\t", garmin_fs_t::get_city(gmsd, ""));
603   print_string("%s\t", garmin_fs_t::get_state(gmsd, ""));
604   const char* country = gt_get_icao_country(garmin_fs_t::get_cc(gmsd, ""));
605   print_string("%s\t", (country != nullptr) ? country : "");
606   print_date_and_time(wpt->GetCreationTime().toTime_t(), 0);
607   if (wpt->HasUrlLink()) {
608     UrlLink l = wpt->GetUrlLink();
609     print_string("%s\t", l.url_);
610   } else {
611     print_string("%s\t", "");
612   }
613   print_categories(garmin_fs_t::get_category(gmsd, 0));
614 
615   *fout << "\r\n";
616 }
617 
618 static void
route_disp_hdr_cb(const route_head * rte)619 route_disp_hdr_cb(const route_head* rte)
620 {
621   cur_info = &route_info[route_idx];
622   cur_info->prev_wpt = nullptr;
623   cur_info->total = 0;
624   if (rte->rte_waypt_ct <= 0) {
625     return;
626   }
627 
628   if (!gtxt_flags.route_header_written) {
629     gtxt_flags.route_header_written = 1;
630     *fout << QString::asprintf("\r\n\r\nHeader\t%s\r\n", headers[route_header]);
631   }
632   print_string("\r\nRoute\t%s\t", rte->rte_name);
633   print_distance(cur_info->length, 0, 1, 0);
634   print_course(cur_info->first_wpt, cur_info->last_wpt);
635   *fout << QString::asprintf("\t%d waypoints\t", cur_info->count);
636   if (rte->rte_urls.HasUrlLink()) {
637     print_string("%s\r\n", rte->rte_urls.GetUrlLink().url_);
638   } else {
639     print_string("%s\r\n", "");
640   }
641   *fout << QString::asprintf("\r\nHeader\t%s\r\n\r\n", headers[rtept_header]);
642 }
643 
644 static void
route_disp_tlr_cb(const route_head *)645 route_disp_tlr_cb(const route_head*)
646 {
647   route_idx++;
648 }
649 
650 static void
route_disp_wpt_cb(const Waypoint * wpt)651 route_disp_wpt_cb(const Waypoint* wpt)
652 {
653   const Waypoint* prev = cur_info->prev_wpt;
654 
655   *fout << "Route Waypoint\t" << wpt->shortname << "\t";
656 
657   if (prev != nullptr) {
658     double dist = waypt_distance_ex(prev, wpt);
659     cur_info->total += dist;
660     print_distance(cur_info->total, 0, 1, 0);
661     print_distance(dist, 0, 1, 0);
662     print_course(prev, wpt);
663   } else {
664     print_distance(0, 1, 0, 0);
665   }
666 
667   *fout << "\r\n";
668 
669   cur_info->prev_wpt = wpt;
670 }
671 
672 static void
track_disp_hdr_cb(const route_head * track)673 track_disp_hdr_cb(const route_head* track)
674 {
675   cur_info = &route_info[route_idx];
676   cur_info->prev_wpt = nullptr;
677   cur_info->total = 0;
678   if (track->rte_waypt_ct <= 0) {
679     return;
680   }
681 
682   if (!gtxt_flags.track_header_written) {
683     gtxt_flags.track_header_written = 1;
684     *fout << QString::asprintf("\r\n\r\nHeader\t%s\r\n", headers[track_header]);
685   }
686   print_string("\r\nTrack\t%s\t", track->rte_name);
687   print_date_and_time(cur_info->start, 0);
688   print_date_and_time(cur_info->time, 1);
689   print_distance(cur_info->length, 0, 1, 0);
690   print_speed(&cur_info->length, &cur_info->time);
691   if (track->rte_urls.HasUrlLink()) {
692     print_string("%s", track->rte_urls.GetUrlLink().url_);
693   } else {
694     print_string("%s", "");
695   }
696   *fout << QString::asprintf("\r\n\r\nHeader\t%s\r\n\r\n", headers[trkpt_header]);
697 }
698 
699 static void
track_disp_tlr_cb(const route_head *)700 track_disp_tlr_cb(const route_head*)
701 {
702   route_idx++;
703 }
704 
705 static void
track_disp_wpt_cb(const Waypoint * wpt)706 track_disp_wpt_cb(const Waypoint* wpt)
707 {
708   const Waypoint* prev = cur_info->prev_wpt;
709   time_t delta;
710   double dist;
711 
712   *fout << "Trackpoint\t";
713 
714   print_position(wpt);
715   print_date_and_time(wpt->GetCreationTime().toTime_t(), 0);
716   if IS_VALID_ALT(wpt->altitude) {
717     print_distance(wpt->altitude, 1, 0, 0);
718   }
719 
720   *fout << "\t";
721   double depth = WAYPT_GET(wpt, depth, unknown_alt);
722   if (depth != unknown_alt) {
723     print_distance(depth, 1, 0, 1);
724   }
725 
726   if (prev != nullptr) {
727     *fout << "\t";
728     delta = wpt->GetCreationTime().toTime_t() - prev->GetCreationTime().toTime_t();
729     float temp = WAYPT_GET(wpt, temperature, -999);
730     if (temp != -999) {
731       print_temperature(temp);
732     }
733     *fout << "\t";
734     dist = waypt_distance_ex(prev, wpt);
735     print_distance(dist, 0, 1, 0);
736     print_date_and_time(delta, 1);
737     print_speed(&dist, &delta);
738     print_course(prev, wpt);
739   }
740   *fout << "\r\n";
741 
742   cur_info->prev_wpt = wpt;
743 }
744 
745 /*******************************************************************************
746 * %%%        global callbacks called by gpsbabel main process              %%% *
747 *******************************************************************************/
748 
749 static void
garmin_txt_wr_init(const QString & fname)750 garmin_txt_wr_init(const QString& fname)
751 {
752   memset(&gtxt_flags, 0, sizeof(gtxt_flags));
753 
754   fout = new gpsbabel::TextStream;
755   fout->open(fname, QIODevice::WriteOnly, MYNAME, "Windows-1252");
756 
757   gtxt_flags.metric = (toupper(*get_option_val(opt_dist, "m")) == 'M');
758   gtxt_flags.celsius = (toupper(*get_option_val(opt_temp, "c")) == 'C');
759   init_date_and_time_format();
760   if (opt_precision) {
761     precision = atoi(opt_precision);
762     is_fatal(precision < 0, MYNAME ": Invalid precision (%s)!", opt_precision);
763   }
764 
765   datum_str = get_option_val(opt_datum, nullptr);
766   const char* grid_str = get_option_val(opt_grid, nullptr);
767 
768   grid_index = grid_lat_lon_dmm;
769   if (grid_str != nullptr) {
770     int i;
771 
772     if (sscanf(grid_str, "%d", &i)) {
773       grid_index = (grid_type) i;
774       if ((grid_index < GRID_INDEX_MIN) || (grid_index > GRID_INDEX_MAX))
775         fatal(MYNAME ": Grid index out of range (%d..%d)!",
776               (int)GRID_INDEX_MIN, (int)GRID_INDEX_MAX);
777     } else {
778       grid_index = gt_lookup_grid_type(grid_str, MYNAME);
779     }
780   }
781 
782   switch (grid_index) {
783   case grid_bng: /* force datum to "Ord Srvy Grt Britn" */
784     datum_index = DATUM_OSGB36;
785     break;
786   case grid_swiss: /* force datum to WGS84 */
787     datum_index = DATUM_WGS84;
788     break;
789   default:
790     datum_index = gt_lookup_datum_index(datum_str, MYNAME);
791   }
792 
793   if (opt_utc != nullptr) {
794     if (case_ignore_strcmp(opt_utc, "utc") == 0) {
795       utc_offs = 0;
796     } else {
797       utc_offs = atoi(opt_utc);
798     }
799     utc_offs *= (60 * 60);
800     gtxt_flags.utc = 1;
801   }
802 }
803 
804 static void
garmin_txt_wr_deinit()805 garmin_txt_wr_deinit()
806 {
807   fout->close();
808   delete fout;
809   fout = nullptr;
810   xfree(date_time_format);
811 }
812 
813 static void
garmin_txt_write()814 garmin_txt_write()
815 {
816   QString grid_str = gt_get_mps_grid_longname(grid_index, MYNAME);
817   grid_str = grid_str.replace('*', "°");
818   *fout << "Grid\t" << grid_str << "\r\n";
819 
820   datum_str = gt_get_mps_datum_name(datum_index);
821   *fout << QString::asprintf("Datum\t%s\r\n\r\n", datum_str);
822 
823   waypoints = 0;
824   gtxt_flags.enum_waypoints = 1;			/* enum all waypoints */
825   waypt_disp_all(enum_waypt_cb);
826   route_disp_all(nullptr, nullptr, enum_waypt_cb);
827   gtxt_flags.enum_waypoints = 0;
828 
829   if (waypoints > 0) {
830     wpt_a_ct = 0;
831     wpt_a = (const Waypoint**)xcalloc(waypoints, sizeof(*wpt_a));
832     waypt_disp_all(enum_waypt_cb);
833     route_disp_all(nullptr, nullptr, enum_waypt_cb);
834     qsort(wpt_a, waypoints, sizeof(*wpt_a), sort_waypt_cb);
835 
836     *fout << QString::asprintf("Header\t%s\r\n\r\n", headers[waypt_header]);
837     for (int i = 0; i < waypoints; i++) {
838       const Waypoint* wpt = wpt_a[i];
839       write_waypt(wpt);
840     }
841     xfree(wpt_a);
842 
843     route_idx = 0;
844     route_info = (info_t*) xcalloc(route_count(), sizeof(info_t));
845     routepoints = 0;
846     route_disp_all(prework_hdr_cb, prework_tlr_cb, prework_wpt_cb);
847     if (routepoints > 0) {
848       route_idx = 0;
849       route_disp_all(route_disp_hdr_cb, route_disp_tlr_cb, route_disp_wpt_cb);
850     }
851     xfree(route_info);
852   }
853 
854   route_idx = 0;
855   route_info = (info_t*) xcalloc(track_count(), sizeof(info_t));
856   routepoints = 0;
857   track_disp_all(prework_hdr_cb, prework_tlr_cb, prework_wpt_cb);
858 
859   if (routepoints > 0) {
860     route_idx = 0;
861     track_disp_all(track_disp_hdr_cb, track_disp_tlr_cb, track_disp_wpt_cb);
862   }
863   xfree(route_info);
864 }
865 
866 /* READER *****************************************************************/
867 
868 /* helpers */
869 
870 static void
free_header(const header_type ht)871 free_header(const header_type ht)
872 {
873   for (int i = 0; i < MAX_HEADER_FIELDS; i++) {
874     char* c = header_lines[ht][i];
875     if (c != nullptr) {
876       xfree(c);
877       header_lines[ht][i] = nullptr;
878     }
879   }
880   header_ct[ht] = 0;
881   memset(header_fields[ht], 0, sizeof(header_fields[ht]));
882 }
883 
884 /* data parsers */
885 
886 static int
parse_date_and_time(char * str,time_t * value)887 parse_date_and_time(char* str, time_t* value)
888 {
889   struct tm tm;
890 
891   memset(&tm, 0, sizeof(tm));
892   char* cin = lrtrim(str);
893   if (*cin == '\0') {
894     return 0;
895   }
896 
897   char* cerr = strptime(cin, date_time_format, &tm);
898   if (cerr == nullptr) {
899     cerr = strptime(cin, "%m/%d/%Y %I:%M:%S %p", &tm);
900     is_fatal(cerr == nullptr, MYNAME ": Invalid date or/and time \"%s\" at line %d!", cin, current_line);
901   }
902 
903 //	printf(MYNAME "_parse_date_and_time: %02d.%02d.%04d, %02d:%02d:%02d\n",
904 //		tm.tm_mday, tm.tm_mon+1, tm.tm_year+1900, tm.tm_hour, tm.tm_min, tm.tm_sec);
905 
906   *value = mklocaltime(&tm);
907   return 1;
908 }
909 
910 static uint16_t
parse_categories(const char * str)911 parse_categories(const char* str)
912 {
913   char buff[256];
914   uint16_t val;
915   uint16_t res = 0;
916   char* cx;
917 
918   if (*str == '\0') {
919     return 0;
920   }
921 
922   strncpy(buff, str, sizeof(buff));
923   char* cin = lrtrim(buff);
924   if (*cin == '\0') {
925     return 0;
926   }
927 
928   strcat(cin, ",");
929 
930   while ((cx = strchr(cin, ','))) {
931     *cx++ = '\0';
932     cin = lrtrim(cin);
933     if (*cin != '\0') {
934       if (!garmin_fs_convert_category(cin, &val)) {
935         warning(MYNAME ": Unable to convert category \"%s\" at line %d!\n", cin, current_line);
936       } else {
937         res = res | val;
938       }
939     }
940     cin = cx;
941   }
942   return res;
943 }
944 
945 static int
parse_temperature(const char * str,double * temperature)946 parse_temperature(const char* str, double* temperature)
947 {
948   double value;
949   unsigned char unit;
950 
951   if ((str == nullptr) || (*str == '\0')) {
952     return 0;
953   }
954 
955   if (sscanf(str, "%lf %c", &value, &unit) == 2) {
956     unit = toupper(unit);
957     switch (unit) {
958     case 'C':
959       *temperature = value;
960       break;
961     case 'F':
962       *temperature = FAHRENHEIT_TO_CELSIUS(value);
963       break;
964     default:
965       fatal(MYNAME ": Unknown temperature unit \"%c\" at line %d!\n", unit, current_line);
966     }
967     return 1;
968   } else {
969     fatal(MYNAME ": Invalid temperature \"%s\" at line %d!\n", str, current_line);
970   }
971   return 0;
972 }
973 
974 static void
parse_header()975 parse_header()
976 {
977   char* str;
978   int column = -1;
979 
980   free_header(unknown_header);
981 
982   while ((str = csv_lineparse(nullptr, "\t", "", column++))) {
983     header_lines[unknown_header][column] = strupper(xstrdup(str));
984     header_ct[unknown_header]++;
985     if (header_ct[unknown_header] >= MAX_HEADER_FIELDS) {
986       break;
987     }
988   }
989 }
990 
991 static int
parse_display(const char * str,int * val)992 parse_display(const char* str, int* val)
993 {
994   if ((str == nullptr) || (*str == '\0')) {
995     return 0;
996   }
997 
998   for (gt_display_modes_e i = GT_DISPLAY_MODE_MIN; i <= GT_DISPLAY_MODE_MAX; ++i) {
999     if (case_ignore_strcmp(str, gt_display_mode_names[i]) == 0) {
1000       *val = i;
1001       return 1;
1002     }
1003   }
1004   warning(MYNAME ": Unknown display mode \"%s\" at line %d.\n", str, current_line);
1005   return 0;
1006 }
1007 
1008 static void
bind_fields(const header_type ht)1009 bind_fields(const header_type ht)
1010 {
1011   is_fatal((grid_index < 0) || (datum_index < 0), MYNAME ": Incomplete or invalid file header!");
1012 
1013   if (header_ct[unknown_header] <= 0) {
1014     return;
1015   }
1016   free_header(ht);
1017 
1018   /* make a copy of headers[ht], uppercase, replace "\t" with "\0" */
1019 
1020   int i = strlen(headers[ht]);
1021   char* fields = (char*) xmalloc(i + 2);
1022   strcpy(fields, headers[ht]);
1023   strcat(fields, "\t");
1024   char* c = strupper(fields);
1025   while ((c = strchr(c, '\t'))) {
1026     *c++ = '\0';
1027   }
1028 
1029   for (i = 0; i < header_ct[unknown_header]; i++) {
1030     char* name = header_lines[ht][i] = header_lines[unknown_header][i];
1031     header_lines[unknown_header][i] = nullptr;
1032 
1033     c = fields;
1034     int field_no = 1;
1035     while (*c) {
1036       if (strcmp(c, name) == 0) {
1037         header_fields[ht][i] = field_no;
1038 #if 0
1039         printf("Binding field \"%s\" to internal number %d (%d,%d)\n", name, field_no, ht, i);
1040 #endif
1041         break;
1042       }
1043       field_no++;
1044       c = c + strlen(c) + 1;
1045     }
1046   }
1047   header_ct[unknown_header] = 0;
1048   xfree(fields);
1049 }
1050 
1051 static void
parse_grid()1052 parse_grid()
1053 {
1054   char* str = csv_lineparse(nullptr, "\t", "", 1);
1055 
1056   if (str != nullptr) {
1057     if (strstr(str, "dd.ddddd") != nullptr) {
1058       grid_index = grid_lat_lon_ddd;
1059     } else if (strstr(str, "mm.mmm") != nullptr) {
1060       grid_index = grid_lat_lon_dmm;
1061     } else if (strstr(str, "mm'ss.s") != nullptr) {
1062       grid_index = grid_lat_lon_dms;
1063     } else {
1064       grid_index = gt_lookup_grid_type(str, MYNAME);
1065     }
1066   } else {
1067     fatal(MYNAME ": Missing grid headline!\n");
1068   }
1069 }
1070 
1071 static void
parse_datum()1072 parse_datum()
1073 {
1074   char* str = csv_lineparse(nullptr, "\t", "", 1);
1075 
1076   if (str != nullptr) {
1077     datum_index = gt_lookup_datum_index(str, MYNAME);
1078   } else {
1079     fatal(MYNAME ": Missing GPS datum headline!\n");
1080   }
1081 }
1082 
1083 static void
parse_waypoint()1084 parse_waypoint()
1085 {
1086   char* str;
1087   int column = -1;
1088 
1089   bind_fields(waypt_header);
1090 
1091   auto* wpt = new Waypoint;
1092   garmin_fs_t* gmsd = garmin_fs_alloc(-1);
1093   wpt->fs.FsChainAdd(gmsd);
1094 
1095   while ((str = csv_lineparse(nullptr, "\t", "", column++))) {
1096     int i;
1097     double d;
1098     int field_no = header_fields[waypt_header][column];
1099 
1100     switch (field_no) {
1101     case  1:
1102       wpt->shortname = DUPSTR(str);
1103       break;
1104     case  2:
1105       wpt->notes = DUPSTR(str);
1106       break;
1107     case  3:
1108       for (i = 0; i <= gt_waypt_class_map_line; i++) {
1109         if (case_ignore_strcmp(str, gt_waypt_class_names[i]) == 0) {
1110           garmin_fs_t::set_wpt_class(gmsd, i);
1111           break;
1112         }
1113       }
1114       break;
1115     case  4:
1116       parse_coordinates(str, datum_index, grid_index,
1117                         &wpt->latitude, &wpt->longitude, MYNAME);
1118       break;
1119     case  5:
1120       if (parse_distance(str, &d, 1, MYNAME)) {
1121         wpt->altitude = d;
1122       }
1123       break;
1124     case  6:
1125       if (parse_distance(str, &d, 1, MYNAME)) {
1126         WAYPT_SET(wpt, depth, d);
1127       }
1128       break;
1129     case  7:
1130       if (parse_distance(str, &d, 1, MYNAME)) {
1131         WAYPT_SET(wpt, proximity, d);
1132       }
1133       break;
1134     case  8:
1135       if (parse_temperature(str, &d)) {
1136         WAYPT_SET(wpt, temperature, d);
1137       }
1138       break;
1139     case  9:
1140       if (parse_display(str, &i)) {
1141         garmin_fs_t::set_display(gmsd, i);
1142       }
1143       break;
1144     case 10:
1145       break;	/* skip color */
1146     case 11:
1147       i = gt_find_icon_number_from_desc(str, GDB);
1148       garmin_fs_t::set_icon(gmsd, i);
1149       wpt->icon_descr = gt_find_desc_from_icon_number(i, GDB);
1150       break;
1151     case 12:
1152       garmin_fs_t::set_facility(gmsd, str);
1153       break;
1154     case 13:
1155       garmin_fs_t::set_city(gmsd, str);
1156       break;
1157     case 14:
1158       garmin_fs_t::set_state(gmsd, str);
1159       break;
1160     case 15:
1161       garmin_fs_t::set_country(gmsd, str);
1162       garmin_fs_t::set_cc(gmsd, gt_get_icao_cc(str, wpt->shortname));
1163       break;
1164     case 16: {
1165       time_t ct;
1166       if (parse_date_and_time(str, &ct)) {
1167         wpt->SetCreationTime(ct);
1168       }
1169     }
1170     break;
1171     case 17: {
1172       wpt->AddUrlLink(str);
1173     }
1174     break;
1175     case 18:
1176       garmin_fs_t::set_category(gmsd, parse_categories(str));
1177       break;
1178     default:
1179       break;
1180     }
1181   }
1182   waypt_add(wpt);
1183 }
1184 
1185 static void
parse_route_header()1186 parse_route_header()
1187 {
1188   char* str;
1189   int column = -1;
1190 
1191   auto* rte = new route_head;
1192 
1193   bind_fields(route_header);
1194   while ((str = csv_lineparse(nullptr, "\t", "", column++))) {
1195     int field_no = header_fields[route_header][column];
1196     switch (field_no) {
1197     case 1:
1198       rte->rte_name = DUPSTR(str);
1199       break;
1200     case 5:
1201       rte->rte_urls.AddUrlLink(UrlLink(str));
1202       break;
1203     }
1204   }
1205   route_add_head(rte);
1206   current_rte = rte;
1207 }
1208 
1209 static void
parse_track_header()1210 parse_track_header()
1211 {
1212   char* str;
1213   int column = -1;
1214 
1215   bind_fields(track_header);
1216   auto* trk = new route_head;
1217   while ((str = csv_lineparse(nullptr, "\t", "", column++))) {
1218     int field_no = header_fields[track_header][column];
1219     switch (field_no) {
1220     case 1:
1221       trk->rte_name = DUPSTR(str);
1222       break;
1223     case 6:
1224       trk->rte_urls.AddUrlLink(UrlLink(str));
1225       break;
1226     }
1227   }
1228   track_add_head(trk);
1229   current_trk = trk;
1230 }
1231 
1232 static void
parse_route_waypoint()1233 parse_route_waypoint()
1234 {
1235   char* str;
1236   int column = -1;
1237   Waypoint* wpt = nullptr;
1238 
1239   bind_fields(rtept_header);
1240 
1241   while ((str = csv_lineparse(nullptr, "\t", "", column++))) {
1242     int field_no = header_fields[rtept_header][column];
1243     switch (field_no) {
1244     case 1:
1245       is_fatal((*str == '\0'), MYNAME ": Route waypoint without name at line %d!\n", current_line);
1246       wpt = find_waypt_by_name(str);
1247       is_fatal((wpt == nullptr), MYNAME ": Route waypoint \"%s\" not in waypoint list (line %d)!\n", str, current_line);
1248       wpt = new Waypoint(*wpt);
1249       break;
1250     }
1251   }
1252   if (wpt != nullptr) {
1253     route_add_wpt(current_rte, wpt);
1254   }
1255 }
1256 
1257 static void
parse_track_waypoint()1258 parse_track_waypoint()
1259 {
1260   char* str;
1261   int column = -1;
1262 
1263   bind_fields(trkpt_header);
1264   auto* wpt = new Waypoint;
1265 
1266   while ((str = csv_lineparse(nullptr, "\t", "", column++))) {
1267     double x;
1268 
1269     if (! *str) {
1270       continue;
1271     }
1272 
1273     int field_no = header_fields[trkpt_header][column];
1274     switch (field_no) {
1275     case 1:
1276       parse_coordinates(str, datum_index, grid_index,
1277                         &wpt->latitude, &wpt->longitude, MYNAME);
1278       break;
1279     case 2: {
1280       time_t ct;
1281       if (parse_date_and_time(str, &ct)) {
1282         wpt->SetCreationTime(ct);
1283       }
1284     }
1285     break;
1286     case 3:
1287       if (parse_distance(str, &x, 1, MYNAME)) {
1288         wpt->altitude = x;
1289       }
1290       break;
1291     case 4:
1292       if (parse_distance(str, &x, 1, MYNAME)) {
1293         WAYPT_SET(wpt, depth, x);
1294       }
1295       break;
1296     case 5:
1297       if (parse_temperature(str, &x)) {
1298         WAYPT_SET(wpt, temperature, x);
1299       }
1300       break;
1301     case 8:
1302       if (parse_speed(str, &x, 1, MYNAME)) {
1303         WAYPT_SET(wpt, speed, x);
1304       }
1305       break;
1306     case 9:
1307       WAYPT_SET(wpt, course, atoi(str));
1308       break;
1309     }
1310   }
1311   track_add_wpt(current_trk, wpt);
1312 }
1313 
1314 /***************************************************************/
1315 
1316 static void
garmin_txt_rd_init(const QString & fname)1317 garmin_txt_rd_init(const QString& fname)
1318 {
1319   memset(&gtxt_flags, 0, sizeof(gtxt_flags));
1320 
1321   fin = new gpsbabel::TextStream;
1322   fin->open(fname, QIODevice::ReadOnly, MYNAME, "Windows-1252");
1323   memset(&header_ct, 0, sizeof(header_ct));
1324 
1325   datum_index = -1;
1326   grid_index = (grid_type) -1;
1327 
1328   init_date_and_time_format();
1329 }
1330 
1331 static void
garmin_txt_rd_deinit()1332 garmin_txt_rd_deinit()
1333 {
1334   for (header_type h = waypt_header; h <= unknown_header; ++h) {
1335     free_header(h);
1336   }
1337   fin->close();
1338   delete fin;
1339   fin = nullptr;
1340   xfree(date_time_format);
1341 }
1342 
1343 static void
garmin_txt_read()1344 garmin_txt_read()
1345 {
1346   QString buff;
1347 
1348   current_line = 0;
1349   while ((buff = fin->readLine(), !buff.isNull())) {
1350     ++current_line;
1351     buff = buff.trimmed();
1352 
1353     if (buff.isEmpty()) {
1354       continue;
1355     }
1356 
1357     /* bail back to a (utf-8) char string, this format isn't worth the conversion work */
1358     QByteArray utf8str = buff.toUtf8();
1359     char* cin = csv_lineparse(utf8str.constData(), "\t", "", 0);
1360 
1361     if (cin == nullptr) {
1362       continue;
1363     }
1364 
1365     if (case_ignore_strcmp(cin, "Header") == 0) {
1366       parse_header();
1367     } else if (case_ignore_strcmp(cin, "Grid") == 0) {
1368       parse_grid();
1369     } else if (case_ignore_strcmp(cin, "Datum") == 0) {
1370       parse_datum();
1371     } else if (case_ignore_strcmp(cin, "Waypoint") == 0) {
1372       parse_waypoint();
1373     } else if (case_ignore_strcmp(cin, "Route Waypoint") == 0) {
1374       parse_route_waypoint();
1375     } else if (case_ignore_strcmp(cin, "Trackpoint") == 0) {
1376       parse_track_waypoint();
1377     } else if (case_ignore_strcmp(cin, "Route") == 0) {
1378       parse_route_header();
1379     } else if (case_ignore_strcmp(cin, "Track") == 0) {
1380       parse_track_header();
1381     } else if (case_ignore_strcmp(cin, "Map") == 0) /* do nothing */ ;
1382     else {
1383       fatal(MYNAME ": Unknown identifier (%s) at line %d!\n", cin, current_line);
1384     }
1385 
1386     /* flush pending data */
1387     while (csv_lineparse(nullptr, "\t", "", 0));
1388   }
1389 }
1390 
1391 ff_vecs_t garmin_txt_vecs = {
1392   ff_type_file,
1393   FF_CAP_RW_ALL,
1394   garmin_txt_rd_init,
1395   garmin_txt_wr_init,
1396   garmin_txt_rd_deinit,
1397   garmin_txt_wr_deinit,
1398   garmin_txt_read,
1399   garmin_txt_write,
1400   nullptr,
1401   &garmin_txt_args,
1402   /*
1403    * The file encoding is Windows-1252, a.k.a CET_CHARSET_MS_ANSI.
1404    * Conversion between Windows-1252 and utf-16 is handled by the stream.
1405    * Conversion between utf-16 and utf-8 is handled by this format.
1406    * Let main know char strings have already been converted to utf-8
1407    * so it doesn't attempt to re-convert any char strings including gmsd data.
1408    */
1409   CET_CHARSET_UTF8, 0
1410   , NULL_POS_OPS,
1411   nullptr
1412 };
1413 
1414 #endif // CSVFMTS_ENABLED
1415