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