1 /*
2     XCSV - X Character Separated Values (.???)
3 
4     A hopefully not too feeble attempt at parsing whatever separated values
5     files into the waypoint structure and back out again.  This is a config-
6     file wrapper around csv_util.c.
7 
8     Copyright (C) 2002 Alex Mottram (geo_alexm at cox-internet.com)
9 
10     This program 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 2 of the License, or
13     (at your option) any later version.
14 
15     This program 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 this program; if not, write to the Free Software
22     Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
23 
24  */
25 
26 #include <cassert>                 // for assert
27 #include <cctype>                  // for isdigit, tolower
28 #include <cmath>                   // for fabs, pow
29 #include <cstdio>                  // for snprintf, sscanf
30 #include <cstdlib>                 // for atof, atoi, strtod, atol
31 #include <cstring>                 // for strlen, strncmp, strcmp, strncpy, memset
32 #include <ctime>                   // for gmtime, localtime, mktime, strftime
33 
34 #include <QtCore/QByteArray>       // for QByteArray
35 #include <QtCore/QChar>            // for QChar
36 #include <QtCore/QCharRef>         // for QCharRef
37 #include <QtCore/QDate>            // for QDate
38 #include <QtCore/QDateTime>        // for QDateTime
39 #include <QtCore/QHash>            // for QHash
40 #include <QtCore/QIODevice>        // for operator|, QIODevice::ReadOnly, QIODevice::Text, QIODevice::WriteOnly
41 #include <QtCore/QList>            // for QList
42 #include <QtCore/QRegExp>          // for QRegExp
43 #include <QtCore/QString>          // for QString, operator+, operator==, QByteArray::append
44 #include <QtCore/QStringList>      // for QStringList
45 #include <QtCore/QTextStream>      // for QTextStream
46 #include <QtCore/QTime>            // for QTime
47 #include <QtCore/QtGlobal>         // for qAsConst, QAddConst<>::Type, qPrintable
48 
49 #include "defs.h"
50 #include "csv_util.h"              // for csv_stringtrim, dec_to_human, csv_stringclean, human_to_dec, ddmmdir_to_degrees, dec_to_intdeg, decdir_to_dec, intdeg_to_dec, csv_linesplit
51 #include "garmin_fs.h"             // for garmin_fs_t, garmin_fs_flags_t, GMSD_FIND, GMSD_GET, GMSD_SET, garmin_fs_alloc
52 #include "gbfile.h"                // for gbfgetstr, gbfclose, gbfopen, gbfile
53 #include "grtcirc.h"               // for RAD, gcdist, radtomiles
54 #include "jeeps/gpsmath.h"         // for GPS_Math_WGS84_To_UTM_EN, GPS_Lookup_Datum_Index, GPS_Math_Known_Datum_To_WGS84_M, GPS_Math_UTM_EN_To_Known_Datum, GPS_Math_WGS84_To_Known_Datum_M, GPS_Math_WGS84_To_UKOSMap_M
55 #include "jeeps/gpsport.h"         // for int32
56 #include "session.h"               // for session_t
57 #include "src/core/datetime.h"     // for DateTime
58 #include "src/core/logging.h"      // for Warning, Fatal
59 #include "src/core/optional.h"     // for optional
60 #include "src/core/textstream.h"
61 #include "strptime.h"              // for strptime
62 #include "xcsv.h"
63 
64 
65 #if CSVFMTS_ENABLED
66 
67 #define MYNAME	"XCSV"
68 
69 /*
70  * Internal numeric value to associate with each keyword in a style file.
71  * To add new keywords, just add an entry here, handle it in the switch
72  * statements below, add it to xcsv_tokens.in, and rebuild on a system
73  * that has GNU gperf on it.
74  */
75 enum xcsv_token {
76   XT_unused = 0,
77   XT_ALT_FEET,
78   XT_ALT_METERS,
79   XT_ANYNAME,
80   XT_CADENCE,
81   XT_CITY,
82   XT_CONSTANT,
83   XT_COUNTRY,
84   XT_DESCRIPTION,
85   XT_EMAIL,
86   XT_EXCEL_TIME,
87   XT_FACILITY,
88   XT_FILENAME,
89   XT_FORMAT,
90   XT_GEOCACHE_CONTAINER,
91   XT_GEOCACHE_DIFF,
92   XT_GEOCACHE_HINT,
93   XT_GEOCACHE_LAST_FOUND,
94   XT_GEOCACHE_PLACER,
95   XT_GEOCACHE_TERR,
96   XT_GEOCACHE_TYPE,
97   XT_GEOCACHE_ISAVAILABLE,
98   XT_GEOCACHE_ISARCHIVED,
99   XT_GMT_TIME,
100   XT_GPS_FIX,
101   XT_GPS_HDOP,
102   XT_GPS_PDOP,
103   XT_GPS_SAT,
104   XT_GPS_VDOP,
105   XT_HEART_RATE,
106   XT_HMSG_TIME,
107   XT_HMSL_TIME,
108   XT_ICON_DESCR,
109   XT_IGNORE,
110   XT_INDEX,
111   XT_ISO_TIME,
112   XT_ISO_TIME_MS,
113   XT_LATLON_HUMAN_READABLE,
114   XT_LAT_DECIMAL,
115   XT_LAT_DECIMALDIR,
116   XT_LAT_DIR,
117   XT_LAT_DIRDECIMAL,
118   XT_LAT_HUMAN_READABLE,
119   XT_LAT_INT32DEG,
120   XT_LAT_DDMMDIR,
121   XT_LAT_NMEA,
122   XT_LOCAL_TIME,
123   XT_LON_DECIMAL,
124   XT_LON_DECIMALDIR,
125   XT_LON_DIR,
126   XT_LON_DIRDECIMAL,
127   XT_LON_HUMAN_READABLE,
128   XT_LON_INT32DEG,
129   XT_LON_DDMMDIR,
130   XT_LON_NMEA,
131   XT_MAP_EN_BNG,
132   XT_NOTES,
133   XT_NET_TIME,
134   XT_PATH_COURSE,
135   XT_PATH_DISTANCE_KM,
136   XT_PATH_DISTANCE_METERS,
137   XT_PATH_DISTANCE_MILES,
138   XT_PATH_SPEED,
139   XT_PATH_SPEED_KNOTS,
140   XT_PATH_SPEED_KPH,
141   XT_PATH_SPEED_MPH,
142   XT_PHONE_NR,
143   XT_POSTAL_CODE,
144   XT_POWER,
145   XT_ROUTE_NAME,
146   XT_SHORTNAME,
147   XT_STATE,
148   XT_STREET_ADDR,
149   XT_TEMPERATURE,
150   XT_TEMPERATURE_F,
151   XT_TIMET_TIME,
152   XT_TIMET_TIME_MS,
153   XT_TRACK_NAME,
154   XT_TRACK_NEW,
155   XT_URL,
156   XT_UTM,
157   XT_UTM_ZONE,
158   XT_UTM_ZONEC,
159   XT_UTM_ZONEF,
160   XT_UTM_EASTING,
161   XT_UTM_NORTHING,
162   XT_URL_LINK_TEXT,
163   XT_YYYYMMDD_TIME
164 };
165 
166 #include "xcsv_tokens.gperf"       // for Perfect_Hash, xt_mapping
167 
168 /* a table of config file constants mapped to chars */
169 const XcsvStyle::char_map_t XcsvStyle::xcsv_char_table[] = {
170   { "COMMA",		"," 	},
171   { "COMMASPACE",		", " 	},
172   { "SINGLEQUOTE",	"'"	},
173   { "DOUBLEQUOTE",	"\""	},
174   { "COLON",		":"	},
175   { "SEMICOLON",		";"	},
176   { "NEWLINE",		"\n"	},
177   { "CR",			"\n"	},
178   { "CRNEWLINE",  	"\r\n"	},
179   { "TAB",  		"\t"	},
180   { "SPACE",  		" "	},
181   { "HASH",  		"#"	},
182   { "WHITESPACE",		"\\w"	},
183   { "PIPE",		"|"	},
184   { nullptr, 		nullptr	}
185 };
186 
187 // Given a keyword of "COMMASPACE", return ", ".
188 QString
xcsv_get_char_from_constant_table(const QString & key)189 XcsvStyle::xcsv_get_char_from_constant_table(const QString& key)
190 {
191   static QHash<QString, QString> substitutions;
192   if (substitutions.empty()) {
193     for (const char_map_t* cm = xcsv_char_table; !cm->key.isNull(); cm++) {
194       substitutions.insert(cm->key, cm->chars);
195     }
196   }
197   if (substitutions.contains(key)) {
198     return substitutions[key];
199   }
200   // No substitution found? Just return original.
201   return key;
202 }
203 
204 // Remove outer quotes.
205 // Should probably be in csv_util.
dequote(const QString & in)206 QString XcsvStyle::dequote(const QString& in) {
207   QString r = in.simplified();
208   if (r.startsWith("\"")) r = r.mid(1);
209   if (r.endsWith("\"")) r.chop(1);
210   return r;
211 }
212 
validate_fieldmap(const field_map & fmp,bool is_output)213 void XcsvStyle::validate_fieldmap(const field_map& fmp, bool is_output) {
214   if (fmp.key.isEmpty()) {
215     fatal(FatalMsg() << MYNAME << ": xcsv style is missing" <<
216             (is_output ? "output" : "input") << "field type.");
217   }
218   if (fmp.val.isNull()) {
219     fatal(FatalMsg() << MYNAME << ": xcsv style" << fmp.key.constData() << "is missing default.");
220   }
221   if (is_output && fmp.printfc.isNull()) {
222     fatal(FatalMsg() << MYNAME << ": xcsv style" << fmp.key.constData() << "output is missing format specifier.");
223   }
224 }
225 
226 /*****************************************************************************/
227 /* xcsv_ifield_add() - add input field to ifield queue.                      */
228 /* usage: xcsv_ifield_add("DESCRIPTION", "", "%s")                           */
229 /*****************************************************************************/
230 void
xcsv_ifield_add(XcsvStyle * style,const QString & qkey,const QString & qval,const QString & qpfc)231 XcsvStyle::xcsv_ifield_add(XcsvStyle* style, const QString& qkey, const QString& qval, const QString& qpfc)
232 {
233   QByteArray key = qkey.toUtf8();
234   QByteArray val = qval.toUtf8();
235   QByteArray pfc = qpfc.toUtf8();
236 
237   struct xt_mapping* xm = Perfect_Hash::in_word_set(key.constData(), strlen(key.constData()));
238 
239   field_map fmp(key, val, pfc, xm ? xm->xt_token : -1);
240   validate_fieldmap(fmp, false);
241 
242   style->ifields.append(fmp);
243 }
244 
245 /*****************************************************************************/
246 /* xcsv_ofield_add() - add output field to ofield queue.                     */
247 /* usage: xcsv_ofield_add("LAT_DECIMAL", "", "%08.5lf")                      */
248 /*****************************************************************************/
249 void
xcsv_ofield_add(XcsvStyle * style,const QString & qkey,const QString & qval,const QString & qpfc,unsigned options)250 XcsvStyle::xcsv_ofield_add(XcsvStyle* style, const QString& qkey, const QString& qval, const QString& qpfc, unsigned options)
251 {
252   QByteArray key = qkey.toUtf8();
253   QByteArray val = qval.toUtf8();
254   QByteArray pfc = qpfc.toUtf8();
255 
256   struct xt_mapping* xm = Perfect_Hash::in_word_set(key.constData(), strlen(key.constData()));
257 
258   field_map fmp(key, val, pfc, xm ? xm->xt_token : -1, options);
259   validate_fieldmap(fmp, true);
260 
261   style->ofields.append(fmp);
262 }
263 
264 QDateTime
yyyymmdd_to_time(const char * s)265 XcsvFormat::yyyymmdd_to_time(const char* s)
266 {
267   QDate d = QDate::fromString(s, "yyyyMMdd");
268   return QDateTime(d);
269 }
270 
271 
272 /*
273  * sscanftime - Parse a date buffer using strftime format
274  */
275 time_t
sscanftime(const char * s,const char * format,const int gmt)276 XcsvFormat::sscanftime(const char* s, const char* format, const int gmt)
277 {
278   struct tm stm;
279   memset(&stm, 0, sizeof(stm));
280 
281   if (strptime(s, format, &stm)) {
282     if ((stm.tm_mday == 0) && (stm.tm_mon == 0) && (stm.tm_year == 0)) {
283       stm.tm_mday = 1;
284       stm.tm_mon = 0;
285       stm.tm_year = 70;
286     }
287     stm.tm_isdst = -1;
288     if (gmt) {
289       return mkgmtime(&stm);
290     } else {
291       return mktime(&stm);
292     }
293   }
294   // Don't fuss for empty strings.
295   if (*s) {
296     warning("date parse of string '%s' with format '%s' failed.\n",
297             s, format);
298   }
299   return 0;
300 }
301 
302 time_t
addhms(const char * s,const char * format)303 XcsvFormat::addhms(const char* s, const char* format)
304 {
305   time_t tt = 0;
306   int hour = 0;
307   int min = 0;
308   int sec = 0;
309 
310   char* ampm = (char*) xmalloc(strlen(s) + 1);
311   int ac = sscanf(s, format, &hour, &min, &sec, ampm);
312   /* If no time format in arg string, assume AM */
313   if (ac < 4) {
314     ampm[0] = 0;
315   }
316   if (ac) {
317     tt = ((tolower(ampm[0])=='p') ? 43200 : 0) + 3600 * hour + 60 * min + sec;
318   }
319   xfree(ampm);
320 
321   return tt;
322 }
323 
324 QString
writetime(const char * format,time_t t,bool gmt)325 XcsvFormat::writetime(const char* format, time_t t, bool gmt)
326 {
327   static struct tm* stmp;
328 
329   if (gmt) {
330     stmp = gmtime(&t);
331   } else {
332     stmp = localtime(&t);
333   }
334 
335   // It's unfortunate that we publish the definition of "strftime specifiers"
336   // in the style definitions.  For this reason, we have to bust everything
337   // down to a time_t and then let strftime handle them.
338 
339   char tbuff[1024];
340   strftime(tbuff, sizeof tbuff, format, stmp);
341   return QString(tbuff);
342 }
343 
344 QString
writetime(const char * format,const gpsbabel::DateTime & t,bool gmt)345 XcsvFormat::writetime(const char* format, const gpsbabel::DateTime& t, bool gmt)
346 {
347   return writetime(format, t.toTime_t(), gmt);
348 }
349 
350 QString
writehms(const char * format,time_t t,int gmt)351 XcsvFormat::writehms(const char* format, time_t t, int gmt)
352 {
353   static struct tm no_time = tm();
354   static struct tm* stmp = &no_time;
355 
356   if (gmt) {
357     stmp = gmtime(&t);
358   } else {
359     stmp = localtime(&t);
360   }
361 
362   if (stmp == nullptr) {
363     stmp = &no_time;
364   }
365 
366   return QString::asprintf(format,
367                             stmp->tm_hour, stmp->tm_min, stmp->tm_sec,
368                             (stmp->tm_hour >= 12 ? "PM" : "AM"));
369 }
370 
371 QString
writehms(const char * format,const gpsbabel::DateTime & t,int gmt)372 XcsvFormat::writehms(const char* format, const gpsbabel::DateTime& t, int gmt)
373 {
374   return writehms(format, t.toTime_t(), gmt);
375 }
376 
377 long
time_to_yyyymmdd(const QDateTime & t)378 XcsvFormat::time_to_yyyymmdd(const QDateTime& t)
379 {
380   QDate d = t.date();
381   return d.year() * 10000 + d.month() * 100 + d.day();
382 }
383 
384 garmin_fs_t*
gmsd_init(Waypoint * wpt)385 XcsvFormat::gmsd_init(Waypoint* wpt)
386 {
387   garmin_fs_t* gmsd = garmin_fs_t::find(wpt);
388   if (gmsd == nullptr) {
389     gmsd = garmin_fs_alloc(-1);
390     wpt->fs.FsChainAdd(gmsd);
391   }
392   return gmsd;
393 }
394 
395 /*****************************************************************************/
396 /* xcsv_parse_val() - parse incoming data into the waypt structure.          */
397 /* usage: xcsv_parse_val("-123.34", *waypt, *field_map)                      */
398 /*****************************************************************************/
399 void
xcsv_parse_val(const QString & value,Waypoint * wpt,const XcsvStyle::field_map & fmp,xcsv_parse_data * parse_data,const int line_no)400 XcsvFormat::xcsv_parse_val(const QString& value, Waypoint* wpt, const XcsvStyle::field_map& fmp,
401                xcsv_parse_data* parse_data, const int line_no)
402 {
403   const char* enclosure = "";
404   geocache_data* gc_data = nullptr;
405 
406   if (fmp.printfc.isNull()) {
407     fatal(MYNAME ": xcsv style '%s' is missing format specifier", fmp.key.constData());
408   }
409 
410   if (0 == strcmp(fmp.printfc.constData(), "\"%s\"")) {
411     enclosure = "\"";
412   }
413 
414   // TODO: eliminate this char string usage.
415   QByteArray value_utf8 = value.toUtf8();
416   const char* s = value_utf8.constData();
417 
418   switch (fmp.hashed_key) {
419   case XT_IGNORE:
420     /* IGNORE -- Categorically ignore this... */
421     break;
422   case XT_CONSTANT:
423     /* CONSTANT -- Ignore on Input... */
424     break;
425   case XT_ANYNAME:
426     /* ANYNAME -- Ignore -- this is output magic. */
427     break;
428   case XT_INDEX:
429     /* IGNORE -- Calculated Sequence # For Output*/
430     break;
431   case XT_SHORTNAME:
432     wpt->shortname = csv_stringtrim(s, enclosure);
433     break;
434   case XT_DESCRIPTION:
435     wpt->description = csv_stringtrim(s, enclosure);
436     break;
437   case XT_NOTES:
438     wpt->notes = csv_stringtrim(s, "");
439     break;
440   case XT_URL:
441     if (!parse_data->link_) {
442       parse_data->link_ = new UrlLink;
443     }
444     parse_data->link_->url_ = value.trimmed();
445     break;
446   case XT_URL_LINK_TEXT:
447     if (!parse_data->link_) {
448       parse_data->link_ = new UrlLink;
449     }
450     parse_data->link_->url_link_text_ = value.trimmed();
451     break;
452   case XT_ICON_DESCR:
453     wpt->icon_descr = value.trimmed();
454     break;
455 
456     /* LATITUDE CONVERSIONS**************************************************/
457   case XT_LAT_DECIMAL:
458     /* latitude as a pure decimal value */
459     wpt->latitude = atof(s);
460     break;
461   case XT_LAT_DECIMALDIR:
462   case XT_LAT_DIRDECIMAL:
463     /* latitude as a decimal with N/S in it. */
464     wpt->latitude = decdir_to_dec(s);
465     break;
466   case XT_LAT_INT32DEG:
467     /* latitude as a 32 bit integer offset */
468     wpt->latitude = intdeg_to_dec((int) atof(s));
469     break;
470   case XT_LAT_HUMAN_READABLE:
471     human_to_dec(s, &wpt->latitude, &wpt->longitude, 1);
472     break;
473   case XT_LAT_DDMMDIR:
474     wpt->latitude = ddmmdir_to_degrees(s);
475     break;
476   case XT_LAT_NMEA:
477     wpt->latitude = ddmm2degrees(atof(s));
478     break;
479     // XT_LAT_10E is handled outside the switch.
480     /* LONGITUDE CONVERSIONS ***********************************************/
481   case XT_LON_DECIMAL:
482     /* longitude as a pure decimal value */
483     wpt->longitude = atof(s);
484     break;
485   case XT_LON_DECIMALDIR:
486   case XT_LON_DIRDECIMAL:
487     /* longitude as a decimal with N/S in it. */
488     wpt->longitude = decdir_to_dec(s);
489     break;
490   case XT_LON_INT32DEG:
491     /* longitude as a 32 bit integer offset  */
492     wpt->longitude = intdeg_to_dec((int) atof(s));
493     break;
494   case XT_LON_HUMAN_READABLE:
495     human_to_dec(s, &wpt->latitude, &wpt->longitude, 2);
496     break;
497   case XT_LON_DDMMDIR:
498     wpt->longitude = ddmmdir_to_degrees(s);
499     break;
500   case XT_LON_NMEA:
501     wpt->longitude = ddmm2degrees(atof(s));
502     break;
503     // case XT_LON_10E is handled outside the switch.
504     /* LAT AND LON CONVERSIONS ********************************************/
505   case XT_LATLON_HUMAN_READABLE:
506     human_to_dec(s, &wpt->latitude, &wpt->longitude, 0);
507     break;
508     /* DIRECTIONS **********************************************************/
509   case XT_LAT_DIR:
510     /* latitude N/S. */
511     if (*s == 'n' || *s == 'N') {
512       parse_data->lat_dir_positive = true;
513     } else if (*s == 's' || *s == 'S') {
514       parse_data->lat_dir_positive = false;
515     } else {
516       warning("parse of string '%s' on line number %d as LAT_DIR failed.  Expected 'n', 'N', 's' or 'S'.\n", s, line_no);
517     }
518     break;
519   case XT_LON_DIR:
520     /* longitude E/W. */
521     if (*s == 'e' || *s == 'E') {
522       parse_data->lon_dir_positive = true;
523     } else if (*s == 'w' || *s == 'W') {
524       parse_data->lon_dir_positive = false;
525     } else {
526       warning("parse of string '%s' on line number %d as LON_DIR failed.  Expected 'e', 'E', 'w' or 'W'.\n", s, line_no);
527     }
528     break;
529     /* SPECIAL COORDINATES/GRID */
530   case XT_MAP_EN_BNG:
531     parse_coordinates(s, DATUM_OSGB36, grid_bng,
532                       &wpt->latitude, &wpt->longitude, MYNAME);
533     break;
534   case XT_UTM_ZONE:
535     parse_data->utm_zone = atoi(s);
536     break;
537   case XT_UTM_ZONEC:
538     parse_data->utm_zonec = s[0];
539     break;
540   case XT_UTM_ZONEF:
541     parse_data->utm_zone = atoi(s);
542     parse_data->utm_zonec = s[strlen(s) - 1];
543     break;
544   case XT_UTM_EASTING:
545     parse_data->utm_easting = atof(s);
546     break;
547   case XT_UTM_NORTHING:
548     parse_data->utm_northing = atof(s);
549     break;
550   case XT_UTM: {
551     char* ss;
552     int i = 0;
553 
554     parse_data->utm_zone = strtod(s, &ss);
555     parse_data->utm_zonec = ss[i];
556     ss++;
557     parse_data->utm_easting = strtod(ss, &ss);
558     while (*ss && !isdigit(*ss)) {
559       ss++;
560     }
561     parse_data->utm_northing = strtod(ss, nullptr);
562   }
563   break;
564   /* ALTITUDE CONVERSIONS ************************************************/
565   case XT_ALT_FEET: {
566     char *endptr;
567     double val = strtod(s, &endptr);
568     if ((val == 0 && s==endptr)) {
569       wpt->altitude = unknown_alt;
570     } else {
571       wpt->altitude = FEET_TO_METERS(val);
572       if (wpt->altitude < unknown_alt + 1) {
573         wpt->altitude = unknown_alt;
574       }
575     }
576   }
577   break;
578   case XT_ALT_METERS: {
579     char *endptr;
580     double val = strtod(s, &endptr);
581     if ((val == 0 && s==endptr)) {
582       wpt->altitude = unknown_alt;
583     } else {
584       wpt->altitude = val;
585       if (wpt->altitude < unknown_alt + 1) {
586         wpt->altitude = unknown_alt;
587       }
588     }
589   }
590   break;
591 
592     /* PATH CONVERSIONS ************************************************/
593   case XT_PATH_SPEED:
594     WAYPT_SET(wpt, speed, atof(s));
595     break;
596   case XT_PATH_SPEED_KPH:
597     WAYPT_SET(wpt, speed, KPH_TO_MPS(atof(s)));
598     break;
599   case XT_PATH_SPEED_MPH:
600     WAYPT_SET(wpt, speed, MPH_TO_MPS(atof(s)));
601     break;
602   case XT_PATH_SPEED_KNOTS:
603     WAYPT_SET(wpt, speed, KNOTS_TO_MPS(atof(s)));
604     break;
605   case XT_PATH_COURSE:
606     WAYPT_SET(wpt, course, atof(s));
607     break;
608 
609     /* TIME CONVERSIONS ***************************************************/
610   case XT_EXCEL_TIME:
611     /* Time as Excel Time  */
612     wpt->SetCreationTime(excel_to_timet(atof(s)));
613     break;
614   case XT_TIMET_TIME: {
615     /* Time as time_t */
616     bool ok;
617     wpt->SetCreationTime(value.toLongLong(&ok));
618     if (!ok) {
619       warning("parse of string '%s' on line number %d as TIMET_TIME failed.\n", s, line_no);
620     }
621   }
622   break;
623   case XT_TIMET_TIME_MS: {
624     /* Time as time_t in milliseconds */
625     bool ok;
626     wpt->SetCreationTime(0, value.toLongLong(&ok));
627     if (!ok) {
628       warning("parse of string '%s' on line number %d as TIMET_TIME_MS failed.\n", s, line_no);
629     }
630   }
631   break;
632   case XT_YYYYMMDD_TIME:
633     wpt->SetCreationTime(yyyymmdd_to_time(s));
634     break;
635   case XT_GMT_TIME:
636     wpt->SetCreationTime(sscanftime(s, fmp.printfc.constData(), 1));
637     break;
638   case XT_LOCAL_TIME:
639     if (!gpsbabel_testmode()) {
640       wpt->creation_time = wpt->creation_time.addSecs(sscanftime(s, fmp.printfc.constData(), 0));
641     } else {
642       /* Force constant time zone for test */
643       wpt->creation_time = wpt->creation_time.addSecs(sscanftime(s, fmp.printfc.constData(), 1));
644     }
645     break;
646     /* Useful when time and date are in separate fields
647     	GMT / Local offset is handled by the two cases above */
648   case XT_HMSG_TIME:
649   case XT_HMSL_TIME:
650     wpt->creation_time = wpt->creation_time.addSecs(addhms(s, fmp.printfc.constData()));
651     break;
652   case XT_ISO_TIME:
653   case XT_ISO_TIME_MS:
654     wpt->SetCreationTime(xml_parse_time(s));
655     break;
656   case XT_NET_TIME: {
657     fatal("XT_NET_TIME can't have possibly ever worked.");
658 //    time_t tt = wpt->GetCreationTime();
659 //    dotnet_time_to_time_t(atof(s), &tt, &wpt->microseconds);
660   }
661   break;
662   case XT_GEOCACHE_LAST_FOUND:
663     wpt->AllocGCData()->last_found = yyyymmdd_to_time(s);
664     break;
665 
666     /* GEOCACHING STUFF ***************************************************/
667   case XT_GEOCACHE_DIFF:
668     /* Geocache Difficulty as an int */
669     wpt->AllocGCData()->diff = atof(s) * 10;
670     break;
671   case XT_GEOCACHE_TERR:
672     /* Geocache Terrain as an int */
673     wpt->AllocGCData()->terr = atof(s) * 10;
674     break;
675   case XT_GEOCACHE_TYPE:
676     /* Geocache Type */
677     wpt->AllocGCData()->type = gs_mktype(s);
678     break;
679   case XT_GEOCACHE_CONTAINER:
680     wpt->AllocGCData()->container = gs_mkcont(s);
681     break;
682   case XT_GEOCACHE_HINT:
683     wpt->AllocGCData()->hint = value.trimmed();
684     break;
685   case XT_GEOCACHE_PLACER:
686     wpt->AllocGCData()->placer = value.trimmed();
687     break;
688   case XT_GEOCACHE_ISAVAILABLE:
689     gc_data = wpt->AllocGCData();
690     if (case_ignore_strcmp(csv_stringtrim(s, ""), "False") == 0) {
691       gc_data->is_available = status_false;
692     } else if (case_ignore_strcmp(csv_stringtrim(s, ""), "True") == 0) {
693       gc_data->is_available = status_true;
694     } else {
695       gc_data->is_available = status_unknown;
696     }
697     break;
698   case XT_GEOCACHE_ISARCHIVED:
699     gc_data = wpt->AllocGCData();
700     if (case_ignore_strcmp(csv_stringtrim(s, ""), "False") == 0) {
701       gc_data->is_archived = status_false;
702     } else if (case_ignore_strcmp(csv_stringtrim(s, ""), "True") == 0) {
703       gc_data->is_archived = status_true;
704     } else {
705       gc_data->is_archived = status_unknown;
706     }
707     break;
708 
709     /* GPS STUFF *******************************************************/
710   case XT_GPS_HDOP:
711     wpt->hdop = atof(s);
712     break;
713   case XT_GPS_VDOP:
714     wpt->vdop = atof(s);
715     break;
716   case XT_GPS_PDOP:
717     wpt->pdop = atof(s);
718     break;
719   case XT_GPS_SAT:
720     wpt->sat = atoi(s);
721     break;
722   case XT_GPS_FIX:
723     wpt->fix = (fix_type)(atoi(s)-(fix_type)1);
724     if (wpt->fix < fix_2d) {
725       if (!case_ignore_strcmp(s, "none")) {
726         wpt->fix = fix_none;
727       } else if (!case_ignore_strcmp(s, "dgps")) {
728         wpt->fix = fix_dgps;
729       } else if (!case_ignore_strcmp(s, "pps")) {
730         wpt->fix = fix_pps;
731       } else {
732         wpt->fix = fix_unknown;
733       }
734     }
735     break;
736     /* Tracks and routes *********************************************/
737   case XT_ROUTE_NAME:
738     parse_data->rte_name = csv_stringtrim(s, enclosure);
739     break;
740   case XT_TRACK_NEW:
741     parse_data->new_track = atoi(s);
742     break;
743   case XT_TRACK_NAME:
744     parse_data->trk_name = csv_stringtrim(s, enclosure);
745     break;
746 
747     /* OTHER STUFF ***************************************************/
748   case XT_PATH_DISTANCE_METERS:
749     wpt->odometer_distance = atof(s);
750     break;
751   case XT_PATH_DISTANCE_KM:
752     wpt->odometer_distance = atof(s) * 1000.0;
753     break;
754   case XT_PATH_DISTANCE_MILES:
755     wpt->odometer_distance = MILES_TO_METERS(atof(s));
756     break;
757   case XT_HEART_RATE:
758     wpt->heartrate = atoi(s);
759     break;
760   case XT_CADENCE:
761     wpt->cadence = atoi(s);
762     break;
763   case XT_POWER:
764     wpt->power = atof(s);
765     break;
766   case XT_TEMPERATURE:
767     wpt->temperature = atof(s);
768     break;
769   case XT_TEMPERATURE_F:
770     wpt->temperature = (FAHRENHEIT_TO_CELSIUS(atof(s)));
771     break;
772     /* GMSD ****************************************************************/
773   case XT_COUNTRY: {
774     garmin_fs_t* gmsd = gmsd_init(wpt);
775     garmin_fs_t::set_country(gmsd, csv_stringtrim(value, enclosure, 0));
776   }
777   break;
778   case XT_STATE: {
779     garmin_fs_t* gmsd = gmsd_init(wpt);
780     garmin_fs_t::set_state(gmsd, csv_stringtrim(value, enclosure, 0));
781   }
782   break;
783   case XT_CITY: {
784     garmin_fs_t* gmsd = gmsd_init(wpt);
785     garmin_fs_t::set_city(gmsd, csv_stringtrim(value, enclosure, 0));
786   }
787   break;
788   case XT_STREET_ADDR: {
789     garmin_fs_t* gmsd = gmsd_init(wpt);
790     garmin_fs_t::set_addr(gmsd, csv_stringtrim(value, enclosure, 0));
791   }
792   break;
793   case XT_POSTAL_CODE: {
794     garmin_fs_t* gmsd = gmsd_init(wpt);
795     garmin_fs_t::set_postal_code(gmsd, csv_stringtrim(value, enclosure, 0));
796   }
797   break;
798   case XT_PHONE_NR: {
799     garmin_fs_t* gmsd = gmsd_init(wpt);
800     garmin_fs_t::set_phone_nr(gmsd, csv_stringtrim(value, enclosure, 0));
801   }
802   break;
803   case XT_FACILITY: {
804     garmin_fs_t* gmsd = gmsd_init(wpt);
805     garmin_fs_t::set_facility(gmsd, csv_stringtrim(value, enclosure, 0));
806   }
807   break;
808   case XT_EMAIL: {
809     garmin_fs_t* gmsd = gmsd_init(wpt);
810     garmin_fs_t::set_email(gmsd, csv_stringtrim(value, enclosure, 0));
811   }
812   break;
813   case -1:
814     if (strncmp(fmp.key.constData(), "LON_10E", 7) == 0) {
815       wpt->longitude = atof(s) / pow(10.0, atof(fmp.key.constData()+7));
816     } else if (strncmp(fmp.key.constData(), "LAT_10E", 7) == 0) {
817       wpt->latitude = atof(s) / pow(10.0, atof(fmp.key.constData()+7));
818     } else {
819       warning(MYNAME ": Unknown style directive: %s\n", fmp.key.constData());
820     }
821     break;
822 
823   default:
824     fatal("This can't happen\n");
825     break;
826   }
827 }
828 
829 /*****************************************************************************/
830 /* read() - read input file, parsing lines, fields and handling              */
831 /*                   any data conversion (the input meat)                    */
832 /*****************************************************************************/
833 void
read()834 XcsvFormat::read()
835 {
836   int linecount = 0;
837   route_head* rte = nullptr;
838   route_head* trk = nullptr;
839 
840   while (true) {
841     QString buff = xcsv_file->stream.readLine();
842     if (buff.isNull()) {
843       break;
844     }
845     linecount++;
846     /* Whack trailing space; leading space may matter if our field sep
847      * is whitespace and we have leading whitespace.
848      */
849     // This could be hoisted out as a generic rtrim() if we need such a thing.
850     while(buff.size() > 0 && buff.at(buff.size() - 1).isSpace()) {
851       buff.chop(1);
852     }
853 
854     /* skip over x many lines on the top for the prologue... */
855     if ((linecount - 1) < xcsv_style->prologue.count()) {
856       continue;
857     }
858 
859     /* We should skip over epilogue lines also.  Since we don't want to
860      * pre-read the file to know how many data lines we should be seeing,
861      * we take this cheap shot at the data and cross our fingers.
862      */
863     for(const auto& ogp : qAsConst(xcsv_style->epilogue)) {
864        if (ogp.startsWith(buff)) {
865          buff.clear();
866          break;
867        }
868     }
869     if (!buff.isEmpty()) {
870       auto* wpt_tmp = new Waypoint;
871       // initialize parse data for accumulation of line results from all fields in this line.
872       xcsv_parse_data parse_data;
873       const QStringList values = csv_linesplit(buff, xcsv_style->field_delimiter,
874                         xcsv_style->field_encloser, linecount);
875 
876       if (xcsv_style->ifields.isEmpty()) {
877         fatal(MYNAME ": attempt to read, but style '%s' has no IFIELDs in it.\n", qPrintable(xcsv_style->description)? qPrintable(xcsv_style->description) : "unknown");
878       }
879 
880       int ifield_idx = 0;
881 
882       /* now rip the line apart */
883       for (const auto& value : values) {
884         const XcsvStyle::field_map& fmp = xcsv_style->ifields.at(ifield_idx++);
885         xcsv_parse_val(value, wpt_tmp, fmp, &parse_data, linecount);
886 
887         if (ifield_idx >= xcsv_style->ifields.size()) {
888           /* no more fields, stop parsing! */
889           break;
890         }
891       }
892 
893       // If XT_LAT_DIR(XT_LON_DIR) was an input field, and the latitude(longitude) is positive,
894       // assume the latitude(longitude) was the absolute value and take the sign from XT_LAT_DIR(XT_LON_DIR).
895       if (parse_data.lat_dir_positive.has_value() && !parse_data.lat_dir_positive.value() && (wpt_tmp->latitude > 0.0)) {
896         wpt_tmp->latitude = -wpt_tmp->latitude;
897       }
898       if (parse_data.lon_dir_positive.has_value() && !parse_data.lon_dir_positive.value() && (wpt_tmp->longitude > 0.0)) {
899         wpt_tmp->longitude = -wpt_tmp->longitude;
900       }
901 
902       if ((xcsv_file->gps_datum_idx > -1) && (xcsv_file->gps_datum_idx != gps_datum_wgs84)) {
903         double alt;
904         GPS_Math_Known_Datum_To_WGS84_M(wpt_tmp->latitude, wpt_tmp->longitude, 0.0,
905                                         &wpt_tmp->latitude, &wpt_tmp->longitude, &alt, xcsv_file->gps_datum_idx);
906       }
907 
908       if (parse_data.utm_easting || parse_data.utm_northing) {
909         GPS_Math_UTM_EN_To_Known_Datum(&wpt_tmp->latitude,
910                                        &wpt_tmp->longitude,
911                                        parse_data.utm_easting, parse_data.utm_northing,
912                                        parse_data.utm_zone, parse_data.utm_zonec,
913                                        DATUM_WGS84);
914       }
915 
916       if (parse_data.link_) {
917         wpt_tmp->AddUrlLink(*parse_data.link_);
918         delete parse_data.link_;
919         parse_data.link_ = nullptr;
920       }
921 
922       switch (xcsv_style->datatype) {
923       case unknown_gpsdata:
924       case wptdata:
925         waypt_add(wpt_tmp);
926         break;
927       case trkdata:
928         if ((trk == nullptr) || parse_data.new_track) {
929           trk = new route_head;
930           track_add_head(trk);
931         }
932         if (!parse_data.trk_name.isEmpty()) {
933           trk->rte_name = parse_data.trk_name;
934         }
935         track_add_wpt(trk, wpt_tmp);
936         break;
937       case rtedata:
938         if (rte == nullptr) {
939           rte = new route_head;
940           route_add_head(rte);
941         }
942         if (!parse_data.rte_name.isEmpty()) {
943           rte->rte_name = parse_data.rte_name;
944         }
945         route_add_wpt(rte, wpt_tmp);
946         break;
947       default:
948         ;
949       }
950     }
951   }
952 }
953 
954 void
xcsv_resetpathlen(const route_head * head)955 XcsvFormat::xcsv_resetpathlen(const route_head* head)
956 {
957   pathdist = 0;
958   oldlat = 999;
959   oldlon = 999;
960   csv_route = csv_track = nullptr;
961   switch (xcsv_style->datatype) {
962   case trkdata:
963     csv_track = head;
964     break;
965   case rtedata:
966     csv_route = head;
967     break;
968   default:
969     break;
970   }
971 }
972 
973 /*****************************************************************************/
974 /* xcsv_waypt_pr() - write output file, handling output conversions          */
975 /*                  (the output meat)                                        */
976 /*****************************************************************************/
977 void
xcsv_waypt_pr(const Waypoint * wpt)978 XcsvFormat::xcsv_waypt_pr(const Waypoint* wpt)
979 {
980   QString buff;
981   double latitude, longitude;
982   int32 utmz;
983   double utme, utmn;
984   char utmzc;
985 
986   if (oldlon < 900) {
987     pathdist += radtomiles(gcdist(RAD(oldlat),RAD(oldlon),
988                                   RAD(wpt->latitude),RAD(wpt->longitude)));
989   }
990   longitude = oldlon = wpt->longitude;
991   latitude = oldlat = wpt->latitude;
992 
993   QString write_delimiter;
994   if (xcsv_style->field_delimiter == "\\w") {
995     write_delimiter = " ";
996   } else {
997     write_delimiter = xcsv_style->field_delimiter;
998   }
999 
1000   QString description;
1001   QString shortname;
1002   if (wpt->shortname.isEmpty() || global_opts.synthesize_shortnames) {
1003     if (!wpt->description.isEmpty()) {
1004       if (global_opts.synthesize_shortnames) {
1005         shortname = mkshort_from_wpt(xcsv_file->mkshort_handle, wpt);
1006       } else {
1007         shortname = csv_stringclean(wpt->description, xcsv_style->badchars);
1008       }
1009     } else {
1010       /* no shortname available -- let shortname default on output */
1011     }
1012   } else {
1013     shortname = csv_stringclean(wpt->shortname, xcsv_style->badchars);
1014   }
1015   if (wpt->description.isEmpty()) {
1016     if (!shortname.isEmpty()) {
1017       description = csv_stringclean(shortname, xcsv_style->badchars);
1018     } else {
1019       /* no description -- let description default on output */
1020     }
1021   } else {
1022     description = csv_stringclean(wpt->description, xcsv_style->badchars);
1023   }
1024 
1025   if (prefer_shortnames) {
1026     description = shortname;
1027   }
1028 
1029   if ((xcsv_file->gps_datum_idx > -1) && (xcsv_file->gps_datum_idx != gps_datum_wgs84)) {
1030     double alt;
1031     GPS_Math_WGS84_To_Known_Datum_M(latitude, longitude, 0.0,
1032                                     &latitude, &longitude, &alt, xcsv_file->gps_datum_idx);
1033   }
1034 
1035   int i = 0;
1036   for (const auto& fmp : qAsConst(xcsv_style->ofields)) {
1037     double lat = latitude;
1038     double lon = longitude;
1039     /*
1040      * A klunky concept.   This should evaluate to true for any
1041      * field if we think we don't have realistic value for it.
1042      * This is used by the 'optional' attribute for suppressing
1043      * fields on output.
1044      */
1045     int field_is_unknown = 0;
1046 
1047     if ((i != 0) && !(fmp.options & XcsvStyle::options_nodelim)) {
1048       xcsv_file->stream << write_delimiter;
1049     }
1050 
1051     if (fmp.options & XcsvStyle::options_absolute) {
1052       lat = fabs(lat);
1053       lon = fabs(lon);
1054     }
1055 
1056     i++;
1057 
1058     switch (fmp.hashed_key) {
1059     case XT_IGNORE:
1060       /* IGNORE -- Write the char printf conversion */
1061       buff = QString::asprintf(fmp.printfc.constData(), "");
1062       break;
1063     case XT_INDEX:
1064       buff = QString::asprintf(fmp.printfc.constData(), waypt_out_count + atoi(fmp.val.constData()));
1065       break;
1066     case XT_CONSTANT: {
1067       auto cp = XcsvStyle::xcsv_get_char_from_constant_table(fmp.val.constData());
1068       if (!cp.isEmpty()) {
1069         buff = QString::asprintf(fmp.printfc.constData(), CSTR(cp));
1070       } else {
1071         buff = QString::asprintf(fmp.printfc.constData(), fmp.val.constData());
1072       }
1073     }
1074     break;
1075     case XT_SHORTNAME:
1076 		buff = QString::asprintf(fmp.printfc.constData(),
1077                 shortname.isEmpty() ? fmp.val.constData() : CSTR(shortname));
1078 
1079       break;
1080     case XT_ANYNAME:
1081       {
1082       QString anyname = wpt->shortname;
1083       if (anyname.isEmpty()) {
1084         anyname = mkshort(xcsv_file->mkshort_handle, wpt->description);
1085       }
1086       if (anyname.isEmpty()) {
1087         anyname = mkshort(xcsv_file->mkshort_handle, wpt->description);
1088       }
1089       if (anyname.isEmpty()) {
1090         anyname = wpt->notes;
1091       }
1092       if (anyname.isEmpty()) {
1093         anyname = fmp.val.constData();
1094       }
1095       buff = QString::asprintf(fmp.printfc.constData(), CSTR(anyname));
1096       }
1097 
1098       break;
1099     case XT_DESCRIPTION:
1100       buff = QString::asprintf(fmp.printfc.constData(),
1101                 description.isEmpty() ? fmp.val.constData() : CSTR(description));
1102       break;
1103     case XT_NOTES:
1104       buff = QString::asprintf(fmp.printfc.constData(),
1105                 wpt->notes.isEmpty() ? fmp.val.constData() : CSTR(wpt->notes));
1106       break;
1107     case XT_URL: {
1108       if (xcsv_urlbase) {
1109         buff = xcsv_urlbase;
1110       }
1111       if (wpt->HasUrlLink()) {
1112         UrlLink l = wpt->GetUrlLink();
1113         buff += QString::asprintf(fmp.printfc.constData(), CSTR(l.url_));
1114       } else {
1115         buff += QString::asprintf(fmp.printfc.constData(), fmp.val.constData() && *fmp.val.constData() ? fmp.val.constData() : "\"\"");
1116       }
1117     }
1118     break;
1119     case XT_URL_LINK_TEXT:
1120       if (wpt->HasUrlLink()) {
1121         UrlLink l = wpt->GetUrlLink();
1122         buff = QString::asprintf(fmp.printfc.constData(),
1123                  !l.url_link_text_.isEmpty() ? CSTR(l.url_link_text_) : fmp.val.constData());
1124       }
1125       break;
1126     case XT_ICON_DESCR:
1127       buff = QString::asprintf(fmp.printfc.constData(),
1128                 (!wpt->icon_descr.isNull()) ?
1129                 CSTR(wpt->icon_descr) : fmp.val.constData());
1130       break;
1131 
1132       /* LATITUDE CONVERSION***********************************************/
1133     case XT_LAT_DECIMAL:
1134       /* latitude as a pure decimal value */
1135       buff = QString::asprintf(fmp.printfc.constData(), lat);
1136       break;
1137     case XT_LAT_DECIMALDIR:
1138       /* latitude as a decimal value with N/S after it */
1139       buff = QString::asprintf(fmp.printfc.constData(), fabs(lat),
1140                lat_dir(lat));
1141       break;
1142     case XT_LAT_DIRDECIMAL:
1143       /* latitude as a decimal value with N/S before it */
1144       buff = QString::asprintf(fmp.printfc.constData(),
1145                lat_dir(lat),
1146                fabs(lat));
1147       break;
1148     case XT_LAT_INT32DEG:
1149       /* latitude as an integer offset from 0 degrees */
1150       buff = QString::asprintf(fmp.printfc.constData(),
1151                 dec_to_intdeg(lat));
1152       break;
1153     case XT_LAT_DDMMDIR:
1154       /*latitude as (degrees * 100) + decimal minutes, with N/S after it */
1155       buff = dec_to_human(fmp.printfc.constData(), "SN", degrees2ddmm(lat));
1156       break;
1157     case XT_LAT_HUMAN_READABLE:
1158       buff = dec_to_human(fmp.printfc.constData(), "SN", lat);
1159       break;
1160     case XT_LAT_NMEA:
1161       buff = QString::asprintf(fmp.printfc.constData(), degrees2ddmm(lat));
1162       break;
1163       // case XT_LAT_10E is handled outside the switch.
1164       /* LONGITUDE CONVERSIONS*********************************************/
1165     case XT_LON_DECIMAL:
1166       /* longitude as a pure decimal value */
1167       buff = QString::asprintf(fmp.printfc.constData(), lon);
1168       break;
1169     case XT_LON_DECIMALDIR:
1170       /* latitude as a decimal value with N/S after it */
1171       buff = QString::asprintf(fmp.printfc.constData(),
1172                fabs(lon),
1173                lon_dir(lon));
1174       break;
1175     case XT_LON_DIRDECIMAL:
1176       /* latitude as a decimal value with N/S before it */
1177       buff = QString::asprintf(fmp.printfc.constData(),
1178                lon_dir(lon),
1179                fabs(lon));
1180       break;
1181     case XT_LON_INT32DEG:
1182       /* longitude as an integer offset from 0 degrees */
1183       buff = QString::asprintf(fmp.printfc.constData(),
1184                 dec_to_intdeg(lon));
1185       break;
1186     case XT_LON_DDMMDIR:
1187       /* longitude as (degrees * 100) + decimal minutes, with W/E after it*/
1188       buff = dec_to_human(fmp.printfc.constData(), "WE", degrees2ddmm(lon));
1189       break;
1190     case XT_LON_HUMAN_READABLE:
1191       buff = dec_to_human(fmp.printfc.constData(), "WE", lon);
1192       break;
1193     case XT_LATLON_HUMAN_READABLE:
1194       buff = dec_to_human(fmp.printfc.constData(), "SN", lat);
1195         buff += " ";
1196       buff += dec_to_human(fmp.printfc.constData(), "WE", lon);
1197       // Tidy up leading, trailing, middle whitespace.
1198       buff = buff.simplified();
1199       break;
1200     case XT_LON_NMEA:
1201       buff = QString::asprintf(fmp.printfc.constData(), degrees2ddmm(lon));
1202       break;
1203       // case XT_LON_10E is handled outside the switch.
1204       /* DIRECTIONS *******************************************************/
1205     case XT_LAT_DIR:
1206       /* latitude N/S as a char */
1207       buff = QString::asprintf(fmp.printfc.constData(),
1208                 lat_dir(lat));
1209       break;
1210     case XT_LON_DIR:
1211       /* longitude E/W as a char */
1212       buff = QString::asprintf(fmp.printfc.constData(),
1213                 lon_dir(lon));
1214       break;
1215 
1216       /* SPECIAL COORDINATES */
1217     case XT_MAP_EN_BNG: {
1218       char map[3];
1219       double north, east;
1220       if (! GPS_Math_WGS84_To_UKOSMap_M(wpt->latitude, wpt->longitude, &east, &north, map))
1221         fatal(MYNAME ": Position (%.5f/%.5f) outside of BNG.\n",
1222               wpt->latitude, wpt->longitude);
1223       buff = QString::asprintf(fmp.printfc.constData(), map, (int)(east + 0.5), (int)(north + 0.5));
1224     }
1225     break;
1226     case XT_UTM: {
1227       char tbuf[100];
1228       GPS_Math_WGS84_To_UTM_EN(wpt->latitude, wpt->longitude,
1229                                &utme, &utmn, &utmz, &utmzc);
1230       snprintf(tbuf, sizeof(tbuf), "%d%c %6.0f %7.0f",
1231                utmz, utmzc, utme, utmn);
1232       buff = QString::asprintf(fmp.printfc.constData(), tbuf);
1233     }
1234     break;
1235     case XT_UTM_ZONE:
1236       GPS_Math_WGS84_To_UTM_EN(wpt->latitude, wpt->longitude,
1237                                &utme, &utmn, &utmz, &utmzc);
1238       buff = QString::asprintf(fmp.printfc.constData(), utmz);
1239       break;
1240     case XT_UTM_ZONEC:
1241       GPS_Math_WGS84_To_UTM_EN(wpt->latitude, wpt->longitude,
1242                                &utme, &utmn, &utmz, &utmzc);
1243       buff = QString::asprintf(fmp.printfc.constData(), utmzc);
1244       break;
1245     case XT_UTM_ZONEF: {
1246       char tbuf[10];
1247       GPS_Math_WGS84_To_UTM_EN(wpt->latitude, wpt->longitude,
1248                                &utme, &utmn, &utmz, &utmzc);
1249       tbuf[0] = 0;
1250       snprintf(tbuf, sizeof(tbuf), "%d%c", utmz, utmzc);
1251       buff = QString::asprintf(fmp.printfc.constData(), tbuf);
1252     }
1253     break;
1254     case XT_UTM_NORTHING:
1255       GPS_Math_WGS84_To_UTM_EN(wpt->latitude, wpt->longitude,
1256                                &utme, &utmn, &utmz, &utmzc);
1257       buff = QString::asprintf(fmp.printfc.constData(), utmn);
1258       break;
1259     case XT_UTM_EASTING:
1260       GPS_Math_WGS84_To_UTM_EN(wpt->latitude, wpt->longitude,
1261                                &utme, &utmn, &utmz, &utmzc);
1262       buff = QString::asprintf(fmp.printfc.constData(), utme);
1263       break;
1264 
1265       /* ALTITUDE CONVERSIONS**********************************************/
1266     case XT_ALT_FEET:
1267       /* altitude in feet as a decimal value */
1268       if (wpt->altitude != unknown_alt) {
1269         buff = QString::asprintf(fmp.printfc.constData(),
1270                   METERS_TO_FEET(wpt->altitude));
1271       }
1272       break;
1273     case XT_ALT_METERS:
1274       /* altitude in meters as a decimal value */
1275       if (wpt->altitude != unknown_alt) {
1276         buff = QString::asprintf(fmp.printfc.constData(),
1277                   wpt->altitude);
1278       }
1279       break;
1280 
1281       /* DISTANCE CONVERSIONS**********************************************/
1282       /* prefer odometer distance. */
1283       /* if not available, use calculated distance from positions */
1284     case XT_PATH_DISTANCE_MILES:
1285       /* path (route/track) distance in miles */
1286       if (wpt->odometer_distance) {
1287         buff = QString::asprintf(fmp.printfc.constData(), METERS_TO_MILES(wpt->odometer_distance));
1288       } else {
1289         buff = QString::asprintf(fmp.printfc.constData(), pathdist);
1290       }
1291       break;
1292     case XT_PATH_DISTANCE_METERS:
1293       /* path (route/track) distance in meters */
1294       if (wpt->odometer_distance) {
1295         buff = QString::asprintf(fmp.printfc.constData(), wpt->odometer_distance);
1296       } else {
1297         buff = QString::asprintf(fmp.printfc.constData(), MILES_TO_METERS(pathdist));
1298       }
1299       break;
1300     case XT_PATH_DISTANCE_KM:
1301       /* path (route/track) distance in kilometers */
1302       if (wpt->odometer_distance) {
1303         buff = QString::asprintf(fmp.printfc.constData(), wpt->odometer_distance / 1000.0);
1304       } else {
1305         buff = QString::asprintf(fmp.printfc.constData(), MILES_TO_METERS(pathdist) / 1000.0);
1306       }
1307       break;
1308     case XT_PATH_SPEED:
1309       buff = QString::asprintf(fmp.printfc.constData(), wpt->speed);
1310       break;
1311     case XT_PATH_SPEED_KPH:
1312       buff = QString::asprintf(fmp.printfc.constData(), MPS_TO_KPH(wpt->speed));
1313       break;
1314     case XT_PATH_SPEED_MPH:
1315       buff = QString::asprintf(fmp.printfc.constData(), MPS_TO_MPH(wpt->speed));
1316       break;
1317     case XT_PATH_SPEED_KNOTS:
1318       buff = QString::asprintf(fmp.printfc.constData(), MPS_TO_KNOTS(wpt->speed));
1319       break;
1320     case XT_PATH_COURSE:
1321       buff = QString::asprintf(fmp.printfc.constData(), wpt->course);
1322       break;
1323 
1324       /* HEART RATE CONVERSION***********************************************/
1325     case XT_HEART_RATE:
1326       buff = QString::asprintf(fmp.printfc.constData(), wpt->heartrate);
1327       break;
1328       /* CADENCE CONVERSION***********************************************/
1329     case XT_CADENCE:
1330       buff = QString::asprintf(fmp.printfc.constData(), wpt->cadence);
1331       break;
1332       /* POWER CONVERSION***********************************************/
1333     case XT_POWER:
1334       buff = QString::asprintf(fmp.printfc.constData(), wpt->power);
1335       break;
1336     case XT_TEMPERATURE:
1337       buff = QString::asprintf(fmp.printfc.constData(), wpt->temperature);
1338       break;
1339     case XT_TEMPERATURE_F:
1340       buff = QString::asprintf(fmp.printfc.constData(), CELSIUS_TO_FAHRENHEIT(wpt->temperature));
1341       break;
1342       /* TIME CONVERSIONS**************************************************/
1343     case XT_EXCEL_TIME:
1344       /* creation time as an excel (double) time */
1345       buff = QString::asprintf(fmp.printfc.constData(), timet_to_excel(wpt->GetCreationTime().toTime_t()));
1346       break;
1347     case XT_TIMET_TIME:
1348       /* time as a time_t variable in seconds */
1349       buff = QString::asprintf(fmp.printfc.constData(), wpt->GetCreationTime().toSecsSinceEpoch());
1350       break;
1351     case XT_TIMET_TIME_MS:
1352       /* time as a time_t variable in milliseconds */
1353       buff = QString::asprintf(fmp.printfc.constData(), wpt->GetCreationTime().toMSecsSinceEpoch());
1354       break;
1355     case XT_YYYYMMDD_TIME:
1356       buff = QString::asprintf(fmp.printfc.constData(), time_to_yyyymmdd(wpt->GetCreationTime()));
1357       break;
1358     case XT_GMT_TIME:
1359       buff = writetime(fmp.printfc.constData(), wpt->GetCreationTime(), true);
1360       break;
1361     case XT_LOCAL_TIME:
1362       buff = writetime(fmp.printfc.constData(), wpt->GetCreationTime(), false);
1363       break;
1364     case XT_HMSG_TIME:
1365       buff = writehms(fmp.printfc.constData(), wpt->GetCreationTime(), 1);
1366       break;
1367     case XT_HMSL_TIME:
1368       buff = writehms(fmp.printfc.constData(), wpt->GetCreationTime(), 0);
1369       break;
1370     case XT_ISO_TIME:
1371       buff = writetime("%Y-%m-%dT%H:%M:%SZ", wpt->GetCreationTime(), true);
1372       break;
1373     case XT_ISO_TIME_MS:
1374       buff = wpt->GetCreationTime().toPrettyString();
1375       break;
1376     case XT_GEOCACHE_LAST_FOUND:
1377       buff = QString::asprintf(fmp.printfc.constData(), time_to_yyyymmdd(wpt->gc_data->last_found));
1378       break;
1379       /* GEOCACHE STUFF **************************************************/
1380     case XT_GEOCACHE_DIFF:
1381       /* Geocache Difficulty as a double */
1382       buff = QString::asprintf(fmp.printfc.constData(), wpt->gc_data->diff / 10.0);
1383       field_is_unknown = !wpt->gc_data->diff;
1384       break;
1385     case XT_GEOCACHE_TERR:
1386       /* Geocache Terrain as a double */
1387       buff = QString::asprintf(fmp.printfc.constData(), wpt->gc_data->terr / 10.0);
1388       field_is_unknown = !wpt->gc_data->terr;
1389       break;
1390     case XT_GEOCACHE_CONTAINER:
1391       /* Geocache Container */
1392       buff = QString::asprintf(fmp.printfc.constData(), gs_get_container(wpt->gc_data->container));
1393       field_is_unknown = wpt->gc_data->container == gc_unknown;
1394       break;
1395     case XT_GEOCACHE_TYPE:
1396       /* Geocache Type */
1397       buff = QString::asprintf(fmp.printfc.constData(), gs_get_cachetype(wpt->gc_data->type));
1398       field_is_unknown = wpt->gc_data->type == gt_unknown;
1399       break;
1400     case XT_GEOCACHE_HINT:
1401       buff = QString::asprintf(fmp.printfc.constData(), CSTR(wpt->gc_data->hint));
1402       field_is_unknown = !wpt->gc_data->hint.isEmpty();
1403       break;
1404     case XT_GEOCACHE_PLACER:
1405       buff = QString::asprintf(fmp.printfc.constData(), CSTR(wpt->gc_data->placer));
1406       field_is_unknown = !wpt->gc_data->placer.isEmpty();
1407       break;
1408     case XT_GEOCACHE_ISAVAILABLE:
1409       if (wpt->gc_data->is_available == status_false) {
1410         buff = QString::asprintf(fmp.printfc.constData(), "False");
1411       } else if (wpt->gc_data->is_available == status_true) {
1412         buff = QString::asprintf(fmp.printfc.constData(), "True");
1413       } else {
1414         buff = QString::asprintf(fmp.printfc.constData(), "Unknown");
1415       }
1416       break;
1417     case XT_GEOCACHE_ISARCHIVED:
1418       if (wpt->gc_data->is_archived == status_false) {
1419         buff = QString::asprintf(fmp.printfc.constData(), "False");
1420       } else if (wpt->gc_data->is_archived == status_true) {
1421         buff = QString::asprintf(fmp.printfc.constData(), "True");
1422       } else {
1423         buff = QString::asprintf(fmp.printfc.constData(), "Unknown");
1424       }
1425       break;
1426       /* Tracks and Routes ***********************************************/
1427     case XT_TRACK_NEW:
1428       if (csv_track) {
1429         if (WAYPT_HAS(wpt,new_trkseg)) {
1430           buff = QString::asprintf(fmp.printfc.constData(), 1);
1431         } else {
1432           buff = QString::asprintf(fmp.printfc.constData(), 0);
1433         }
1434       }
1435       break;
1436     case XT_TRACK_NAME:
1437       if (csv_track) {
1438         buff = QString::asprintf(fmp.printfc.constData(), CSTR(csv_track->rte_name));
1439       }
1440       break;
1441     case XT_ROUTE_NAME:
1442       if (csv_route) {
1443         buff = QString::asprintf(fmp.printfc.constData(), CSTR(csv_route->rte_name));
1444       }
1445       break;
1446 
1447       /* GPS STUFF *******************************************************/
1448     case XT_GPS_HDOP:
1449       buff = QString::asprintf(fmp.printfc.constData(), wpt->hdop);
1450       field_is_unknown = !wpt->hdop;
1451       break;
1452     case XT_GPS_VDOP:
1453       buff = QString::asprintf(fmp.printfc.constData(), wpt->vdop);
1454       field_is_unknown = !wpt->vdop;
1455       break;
1456     case XT_GPS_PDOP:
1457       buff = QString::asprintf(fmp.printfc.constData(), wpt->pdop);
1458       field_is_unknown = !wpt->pdop;
1459       break;
1460     case XT_GPS_SAT:
1461       buff = QString::asprintf(fmp.printfc.constData(), wpt->sat);
1462       field_is_unknown = !wpt->sat;
1463       break;
1464     case XT_GPS_FIX: {
1465       const char* fix = nullptr;
1466       switch (wpt->fix) {
1467       case fix_unknown:
1468         field_is_unknown = 1;
1469         fix = "Unknown";
1470         break;
1471       case fix_none:
1472         fix = "None";
1473         break;
1474       case fix_2d:
1475         fix = "2d";
1476         break;
1477       case fix_3d:
1478         fix = "3d";
1479         break;
1480       case fix_dgps:
1481         fix = "dgps";
1482         break;
1483       case fix_pps:
1484         fix = "pps";
1485         break;
1486       }
1487       buff = QString::asprintf(fmp.printfc.constData(), fix);
1488     }
1489     break;
1490     /* GMSD ************************************************************/
1491     case XT_COUNTRY: {
1492       garmin_fs_t* gmsd = garmin_fs_t::find(wpt);
1493       buff = QString::asprintf(fmp.printfc.constData(), CSTR(garmin_fs_t::get_country(gmsd, "")));
1494     }
1495     break;
1496     case XT_STATE: {
1497       garmin_fs_t* gmsd = garmin_fs_t::find(wpt);
1498       buff = QString::asprintf(fmp.printfc.constData(), CSTR(garmin_fs_t::get_state(gmsd, "")));
1499     }
1500     break;
1501     case XT_CITY: {
1502       garmin_fs_t* gmsd = garmin_fs_t::find(wpt);
1503       buff = QString::asprintf(fmp.printfc.constData(), CSTR(garmin_fs_t::get_city(gmsd, "")));
1504     }
1505     break;
1506     case XT_POSTAL_CODE: {
1507       garmin_fs_t* gmsd = garmin_fs_t::find(wpt);
1508       buff = QString::asprintf(fmp.printfc.constData(), CSTR(garmin_fs_t::get_postal_code(gmsd, "")));
1509     }
1510     break;
1511     case XT_STREET_ADDR: {
1512       garmin_fs_t* gmsd = garmin_fs_t::find(wpt);
1513       buff = QString::asprintf(fmp.printfc.constData(), CSTR(garmin_fs_t::get_addr(gmsd, "")));
1514     }
1515     break;
1516     case XT_PHONE_NR: {
1517       garmin_fs_t* gmsd = garmin_fs_t::find(wpt);
1518       buff = QString::asprintf(fmp.printfc.constData(), CSTR(garmin_fs_t::get_phone_nr(gmsd, "")));
1519     }
1520     break;
1521     case XT_FACILITY: {
1522       garmin_fs_t* gmsd = garmin_fs_t::find(wpt);
1523       buff = QString::asprintf(fmp.printfc.constData(), CSTR(garmin_fs_t::get_facility(gmsd, "")));
1524     }
1525     break;
1526     case XT_EMAIL: {
1527       garmin_fs_t* gmsd = garmin_fs_t::find(wpt);
1528       buff = QString::asprintf(fmp.printfc.constData(), CSTR(garmin_fs_t::get_email(gmsd, "")));
1529     }
1530     break;
1531     /* specials */
1532     case XT_FILENAME:
1533       buff = QString::asprintf(fmp.printfc.constData(), CSTR(wpt->session->filename));
1534       break;
1535     case XT_FORMAT:
1536       buff = QString::asprintf(fmp.printfc.constData(), CSTR(wpt->session->name));
1537       break;
1538     case -1:
1539       if (strncmp(fmp.key.constData(), "LON_10E", 7) == 0) {
1540         buff = QString::asprintf(fmp.printfc.constData(), lon * pow(10.0, atof(fmp.key.constData()+7)));
1541       } else if (strncmp(fmp.key.constData(), "LAT_10E", 7) == 0) {
1542         buff = QString::asprintf(fmp.printfc.constData(), lat * pow(10.0, atof(fmp.key.constData()+7)));
1543       }
1544       break;
1545     default:
1546       warning(MYNAME ": Unknown style directive: %s\n", fmp.key.constData());
1547       break;
1548     }
1549     QString obuff = csv_stringclean(buff, xcsv_style->badchars);
1550 
1551     if (field_is_unknown && fmp.options & XcsvStyle::options_optional) {
1552       continue;
1553     }
1554 
1555     if (!xcsv_style->field_encloser.isEmpty()) {
1556       /* print the enclosing character(s) */
1557       xcsv_file->stream << xcsv_style->field_encloser;
1558     }
1559 
1560     /* As a special case (pronounced "horrible hack") we allow
1561      * ""%s"" to smuggle bad characters through.
1562      */
1563     if (0 == strcmp(fmp.printfc.constData(), "\"%s\"")) {
1564       obuff = '"' + obuff + '"';
1565     }
1566     xcsv_file->stream << obuff;
1567 
1568     if (!xcsv_style->field_encloser.isEmpty()) {
1569       /* print the enclosing character(s) */
1570       xcsv_file->stream << xcsv_style->field_encloser;
1571     }
1572     buff.clear();
1573   }
1574 
1575   xcsv_file->stream << xcsv_style->record_delimiter;
1576 
1577   /* increment the index counter */
1578   waypt_out_count++;
1579 }
1580 
1581 // return |original| after performing token replacement.
1582 QString
xcsv_replace_tokens(const QString & original) const1583 XcsvFormat::xcsv_replace_tokens(const QString& original) const {
1584   QString replacement = original;
1585     // Don't do potentially expensive replacements if token prefix
1586     // isn't present;
1587     if (original.contains("__")) {
1588       replacement.replace("__FILE__", xcsv_file->fname);
1589       replacement.replace("__VERSION__", gpsbabel_testmode()? "" : gpsbabel_version);
1590 
1591       QDateTime dt = current_time().toUTC();
1592 
1593       QString dts = dt.toString("ddd MMM dd hh:mm:ss yyyy");
1594       replacement.replace("__DATE_AND_TIME__", dts);
1595 
1596       QString d = dt.toString("MM/dd/yyyy");
1597       replacement.replace("__DATE__", d);
1598 
1599       QString t = dt.toString("hh:mm:ss");
1600       replacement.replace("__TIME__", t);
1601     }
1602   return replacement;
1603 }
1604 
1605 /*****************************************************************************/
1606 /* write(void) - write prologues, spawn the output loop, and write           */
1607 /*                         epilogues.                                        */
1608 /*****************************************************************************/
1609 void
write()1610 XcsvFormat::write()
1611 {
1612   /* reset the index counter */
1613   waypt_out_count = 0;
1614 
1615   /* output prologue lines, if any. */
1616   for (const auto& line : qAsConst(xcsv_style->prologue)) {
1617    QString line_to_write = xcsv_replace_tokens(line);
1618     xcsv_file->stream << line_to_write <<  xcsv_style->record_delimiter;
1619   }
1620 
1621   auto xcsv_waypt_pr_lambda = [this](const Waypoint* wpt)->void {
1622     xcsv_waypt_pr(wpt);
1623   };
1624   auto xcsv_resetpathlen_lambda = [this](const route_head* rte)->void {
1625     xcsv_resetpathlen(rte);
1626   };
1627 
1628   if ((xcsv_style->datatype == 0) || (xcsv_style->datatype == wptdata)) {
1629     waypt_disp_all(xcsv_waypt_pr_lambda);
1630   }
1631   if ((xcsv_style->datatype == 0) || (xcsv_style->datatype == rtedata)) {
1632     route_disp_all(xcsv_resetpathlen_lambda, nullptr, xcsv_waypt_pr_lambda);
1633   }
1634   if ((xcsv_style->datatype == 0) || (xcsv_style->datatype == trkdata)) {
1635     track_disp_all(xcsv_resetpathlen_lambda, nullptr, xcsv_waypt_pr_lambda);
1636   }
1637 
1638   /* output epilogue lines, if any. */
1639   for (const auto& line : qAsConst(xcsv_style->epilogue)) {
1640     QString line_to_write = xcsv_replace_tokens(line);
1641     xcsv_file->stream << line_to_write << xcsv_style->record_delimiter;
1642   }
1643 }
1644 
1645 void
xcsv_parse_style_line(XcsvStyle * style,QString line)1646 XcsvStyle::xcsv_parse_style_line(XcsvStyle* style, QString line)
1647 {
1648   // The lines to be parsed have a leading operation |op| that is
1649   // separated by whitespace from the rest. Each op may have zero or
1650   // more comma separated tokens  |token[]|.
1651 
1652   // Handle comments, with an escape. Probably not optimal.
1653   int escape_idx = line.indexOf('\\');
1654   int comment_idx = line.indexOf('#');
1655   if (comment_idx > 0 && escape_idx +1 != comment_idx) {
1656     line = line.mid(0, line.indexOf("#")).trimmed();
1657   } else {
1658     line = line.replace("\\#", "#");
1659   }
1660 
1661   // Separate op and tokens.
1662   int sep = line.indexOf(QRegExp("\\s+"));
1663 
1664   // the first token is the operation, e.g. "IFIELD"
1665   QString op = line.mid(0, sep).trimmed().toUpper();
1666   QString tokenstr = line.mid(sep).trimmed();
1667   QStringList tokens = tokenstr.split(",");
1668 
1669   if (op == "FIELD_DELIMITER") {
1670     auto cp = xcsv_get_char_from_constant_table(tokens[0]);
1671     style->field_delimiter = cp;
1672 
1673     char* p = csv_stringtrim(CSTR(style->field_delimiter), " ", 0);
1674       /* field delimiters are always bad characters */
1675     if (0 == strcmp(p, "\\w")) {
1676       style->badchars = " \n\r";
1677     } else {
1678       style->badchars += p;
1679     }
1680     xfree(p);
1681 
1682   } else
1683 
1684   if (op == "FIELD_ENCLOSER") {
1685     auto cp = xcsv_get_char_from_constant_table(tokens[0]);
1686     style->field_encloser = cp;
1687 
1688     char* p = csv_stringtrim(CSTR(style->field_encloser), " ", 0);
1689     style->badchars += p;
1690     xfree(p);
1691   } else
1692 
1693   if (op == "RECORD_DELIMITER") {
1694     auto cp = xcsv_get_char_from_constant_table(tokens[0]);
1695     style->record_delimiter = cp;
1696 
1697       // Record delimiters are always bad characters.
1698     auto p = csv_stringtrim(CSTR(style->record_delimiter), " ", 0);
1699     style->badchars += p;
1700     xfree(p);
1701 
1702   } else
1703 
1704   if (op == "FORMAT_TYPE") {
1705     if (tokens[0] == "INTERNAL") {
1706       style->type = ff_type_internal;
1707     }
1708       // this is almost inconceivable...
1709     if (tokens[0] == "SERIAL") {
1710       style->type = ff_type_serial;
1711     }
1712   } else
1713 
1714   if (op == "DESCRIPTION") {
1715     style->description = tokens[0];
1716   } else
1717 
1718   if (op == "EXTENSION") {
1719     style->extension = tokens[0];
1720   } else
1721 
1722   if (op == "SHORTLEN") {
1723     style->shortlen = tokens[0].toInt();
1724   } else
1725 
1726   if (op == "SHORTWHITE") {
1727     style->whitespace_ok = tokens[0].toInt();
1728   } else
1729 
1730   if (op == "BADCHARS") {
1731     char* sp = csv_stringtrim(CSTR(tokenstr), "\"", 1);
1732     QString cp = xcsv_get_char_from_constant_table(sp);
1733     style->badchars += cp;
1734     xfree(sp);
1735   } else
1736 
1737   if (op =="PROLOGUE") {
1738     style->prologue.append(tokenstr);
1739   } else
1740 
1741   if (op == "EPILOGUE") {
1742     style->epilogue.append(tokenstr);
1743   } else
1744 
1745   if (op == "ENCODING") {
1746     style->codecname = tokens[0];
1747   } else
1748 
1749   if (op == "DATUM") {
1750     style->gps_datum_name = tokens[0];
1751   } else
1752 
1753   if (op == "DATATYPE") {
1754     QString p = tokens[0].toUpper();
1755     if (p == "TRACK") {
1756       style->datatype = trkdata;
1757     } else if (p == "ROUTE") {
1758       style->datatype = rtedata;
1759     } else if (p == "WAYPOINT") {
1760       style->datatype = wptdata;
1761     } else {
1762       fatal(FatalMsg() << MYNAME << ": Unknown data type" << p);
1763     }
1764   } else
1765 
1766   if (op == "IFIELD") {
1767     if (tokens.size() < 3) {
1768       fatal(FatalMsg() << "Invalid IFIELD line: " << tokenstr);
1769     }
1770 
1771     // The key ("LAT_DIR") should never contain quotes.
1772 
1773     const QString key = tokens[0].simplified();
1774     const QString val = dequote(tokens[1]);
1775     const QString pfc = dequote(tokens[2]);
1776     xcsv_ifield_add(style, key, val, pfc);
1777   } else
1778 
1779       //
1780       //  as OFIELDs are implemented as an after-thought, I'll
1781       //  leave this as it's own parsing for now.  We could
1782       //  change the world on ifield vs ofield format later..
1783       //
1784   if (op == "OFIELD") {
1785     unsigned options = 0;
1786       // Note: simplified() has to run after split().
1787     if (tokens.size() < 3) {
1788       fatal(FatalMsg() << "Invalid OFIELD line: " << tokenstr);
1789     }
1790 
1791     // The key ("LAT_DIR") should never contain quotes.
1792     const QString key = tokens[0].simplified();
1793     const QString val = dequote(tokens[1]);
1794     const QString pfc = dequote(tokens[2]);
1795 
1796     // This is pretty lazy way to parse write options.
1797     // They've very rarely used, so we'll go for simple.
1798     // We may have split the optional fourth and final field which can contain
1799     // option[s], so look at all the remaining tokens.
1800     for (int token_idx = 3; token_idx < tokens.size(); ++token_idx) {
1801       QString options_string = tokens[token_idx].simplified();
1802       if (options_string.contains("no_delim_before")) {
1803         options |= options_nodelim;
1804       }
1805       if (options_string.contains("absolute")) {
1806         options |= options_absolute;
1807       }
1808       if (options_string.contains("optional")) {
1809         options |= options_optional;
1810       }
1811     }
1812     xcsv_ofield_add(style, key, val, pfc, options);
1813   }
1814 }
1815 
1816 
1817 /*
1818  * A wrapper for xcsv_parse_style_line that reads until it hits
1819  * a terminating null.   Makes multiple calls to that function so
1820  * that "ignore to end of line" comments work right.
1821  */
1822 XcsvStyle
xcsv_parse_style_buff(const char * sbuff)1823 XcsvStyle::xcsv_parse_style_buff(const char* sbuff)
1824 {
1825   XcsvStyle style;
1826   const QStringList lines = QString(sbuff).split('\n');
1827   for (const auto& line : lines) {
1828     xcsv_parse_style_line(&style, line);
1829   }
1830   return style;
1831 }
1832 
1833 XcsvStyle
xcsv_read_style(const char * fname)1834 XcsvStyle::xcsv_read_style(const char* fname)
1835 {
1836   gbfile* fp = gbfopen(fname, "rb", MYNAME);
1837   XcsvStyle style;
1838   for  (QString sbuff = gbfgetstr(fp); !sbuff.isNull(); sbuff = gbfgetstr(fp)) {
1839     sbuff = sbuff.trimmed();
1840     xcsv_parse_style_line(&style, sbuff);
1841   }
1842 
1843   /* if we have no output fields, use input fields as output fields */
1844   if (style.ofields.isEmpty()) {
1845     style.ofields = style.ifields;
1846   }
1847   gbfclose(fp);
1848 
1849   return style;
1850 }
1851 
1852 /*
1853  * Passed a pointer to an internal buffer that would be identical
1854  * to the series of bytes that would be in a style file, we set up
1855  * the xcsv parser and make it ready for general use.
1856  */
1857 XcsvStyle
xcsv_read_internal_style(const char * style_buf)1858 XcsvStyle::xcsv_read_internal_style(const char* style_buf)
1859 {
1860   XcsvStyle style = xcsv_parse_style_buff(style_buf);
1861 
1862   /* if we have no output fields, use input fields as output fields */
1863   if (style.ofields.isEmpty()) {
1864     style.ofields = style.ifields;
1865   }
1866 
1867   return style;
1868 }
1869 
1870 void
xcsv_setup_internal_style(const char * style_buf)1871 XcsvFormat::xcsv_setup_internal_style(const char* style_buf)
1872 {
1873   intstylebuf = style_buf;
1874 }
1875 
1876 void
rd_init(const QString & fname)1877 XcsvFormat::rd_init(const QString& fname)
1878 {
1879   /*
1880    * if we don't have an internal style defined, we need to
1881    * read it from a user-supplied style file, or die trying.
1882    */
1883   if (intstylebuf != nullptr) {
1884     xcsv_style = new XcsvStyle(XcsvStyle::xcsv_read_internal_style(intstylebuf));
1885   } else {
1886     if (!styleopt) {
1887       fatal(MYNAME ": XCSV input style not declared.  Use ... -i xcsv,style=path/to/file.style\n");
1888     }
1889 
1890     xcsv_style = new XcsvStyle(XcsvStyle::xcsv_read_style(styleopt));
1891   }
1892 
1893   if ((xcsv_style->datatype == 0) || (xcsv_style->datatype == wptdata)) {
1894     if (global_opts.masked_objective & (TRKDATAMASK|RTEDATAMASK)) {
1895       warning(MYNAME " attempt to read %s as a track or route, but this format only supports waypoints on read.  Reading as waypoints instead.\n", qPrintable(fname));
1896     }
1897   }
1898 
1899   xcsv_file = new XcsvFile;
1900   if (xcsv_style->codecname.isEmpty()) {
1901     xcsv_file->stream.open(fname, QIODevice::ReadOnly, MYNAME);
1902   } else {
1903     xcsv_file->stream.open(fname, QIODevice::ReadOnly, MYNAME, CSTR(xcsv_style->codecname));
1904   }
1905   xcsv_file->fname = fname;
1906 
1907   QString datum_name;
1908   if (opt_datum != nullptr) {
1909     datum_name = opt_datum;
1910   } else if (!xcsv_style->gps_datum_name.isEmpty()) {
1911     datum_name = xcsv_style->gps_datum_name;
1912   } else {
1913     datum_name = "WGS 84";
1914   }
1915   xcsv_file->gps_datum_idx = GPS_Lookup_Datum_Index(datum_name);
1916   is_fatal(xcsv_file->gps_datum_idx < 0, MYNAME ": datum \"%s\" is not supported.", qPrintable(datum_name));
1917   assert(gps_datum_wgs84 == GPS_Lookup_Datum_Index("WGS 84"));
1918 }
1919 
1920 void
rd_deinit()1921 XcsvFormat::rd_deinit()
1922 {
1923   xcsv_file->stream.close();
1924   delete xcsv_file;
1925   xcsv_file = nullptr;
1926 
1927   delete xcsv_style;
1928   xcsv_style = nullptr;
1929 }
1930 
1931 void
wr_init(const QString & fname)1932 XcsvFormat::wr_init(const QString& fname)
1933 {
1934   /*
1935    * if we don't have an internal style defined, we need to
1936    * read it from a user-supplied style file, or die trying.
1937    */
1938   if (intstylebuf != nullptr) {
1939     xcsv_style = new XcsvStyle(XcsvStyle::xcsv_read_internal_style(intstylebuf));
1940   } else {
1941     if (!styleopt) {
1942       fatal(MYNAME ": XCSV output style not declared.  Use ... -o xcsv,style=path/to/file.style\n");
1943     }
1944 
1945     xcsv_style = new XcsvStyle(XcsvStyle::xcsv_read_style(styleopt));
1946   }
1947 
1948   xcsv_file = new XcsvFile;
1949   if (xcsv_style->codecname.isEmpty()) {
1950     xcsv_file->stream.open(fname, QIODevice::WriteOnly | QIODevice::Text, MYNAME);
1951   } else {
1952     xcsv_file->stream.open(fname, QIODevice::WriteOnly | QIODevice::Text, MYNAME, CSTR(xcsv_style->codecname));
1953   }
1954   xcsv_file->fname = fname;
1955 
1956   if (xcsv_style->shortlen) {
1957     setshort_length(xcsv_file->mkshort_handle, *xcsv_style->shortlen);
1958   }
1959   if (xcsv_style->whitespace_ok) {
1960     setshort_whitespace_ok(xcsv_file->mkshort_handle, *xcsv_style->whitespace_ok);
1961   }
1962 
1963   /* set mkshort options from the command line */
1964   if (global_opts.synthesize_shortnames) {
1965 
1966     if (snlenopt) {
1967       setshort_length(xcsv_file->mkshort_handle, atoi(snlenopt));
1968     }
1969 
1970     if (snwhiteopt) {
1971       setshort_whitespace_ok(xcsv_file->mkshort_handle, atoi(snwhiteopt));
1972     }
1973 
1974     if (snupperopt) {
1975       setshort_mustupper(xcsv_file->mkshort_handle, atoi(snupperopt));
1976     }
1977 
1978     if (snuniqueopt) {
1979       setshort_mustuniq(xcsv_file->mkshort_handle, atoi(snuniqueopt));
1980     }
1981 
1982     setshort_badchars(xcsv_file->mkshort_handle, CSTR(xcsv_style->badchars));
1983 
1984   }
1985 
1986   QString datum_name;
1987   if (opt_datum != nullptr) {
1988     datum_name = opt_datum;
1989   } else if (!xcsv_style->gps_datum_name.isEmpty()) {
1990     datum_name = xcsv_style->gps_datum_name;
1991   } else {
1992     datum_name = "WGS 84";
1993   }
1994   xcsv_file->gps_datum_idx = GPS_Lookup_Datum_Index(datum_name);
1995   is_fatal(xcsv_file->gps_datum_idx < 0, MYNAME ": datum \"%s\" is not supported.", qPrintable(datum_name));
1996   assert(gps_datum_wgs84 == GPS_Lookup_Datum_Index("WGS 84"));
1997 }
1998 
1999 void
wr_position_init(const QString & fname)2000 XcsvFormat::wr_position_init(const QString& fname)
2001 {
2002   wr_init(fname);
2003 }
2004 
2005 void
wr_deinit()2006 XcsvFormat::wr_deinit()
2007 {
2008   xcsv_file->stream.close();
2009   delete xcsv_file;
2010   xcsv_file = nullptr;
2011 
2012   delete xcsv_style;
2013   xcsv_style = nullptr;
2014 }
2015 
2016 void
wr_position_deinit()2017 XcsvFormat::wr_position_deinit()
2018 {
2019   wr_deinit();
2020 }
2021 
2022 void
wr_position(Waypoint * wpt)2023 XcsvFormat::wr_position(Waypoint* wpt)
2024 {
2025   /* Tweak incoming name if we don't have a fix */
2026   switch (wpt->fix) {
2027   case fix_none:
2028     wpt->shortname = "ESTIMATED Position";
2029     break;
2030   default:
2031     break;
2032   }
2033 
2034   waypt_add(wpt);
2035   write();
2036   waypt_del(wpt);
2037 
2038   xcsv_file->stream.flush();
2039 }
2040 #endif //CSVFMTS_ENABLED
2041