/*
XCSV - X Character Separated Values (.???)
A hopefully not too feeble attempt at parsing whatever separated values
files into the waypoint structure and back out again. This is a config-
file wrapper around csv_util.c.
Copyright (C) 2002 Alex Mottram (geo_alexm at cox-internet.com)
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include <cassert> // for assert
#include <cctype> // for isdigit, tolower
#include <cmath> // for fabs, pow
#include <cstdio> // for snprintf, sscanf
#include <cstdlib> // for atof, atoi, strtod, atol
#include <cstring> // for strlen, strncmp, strcmp, strncpy, memset
#include <ctime> // for gmtime, localtime, mktime, strftime
#include <QtCore/QByteArray> // for QByteArray
#include <QtCore/QChar> // for QChar
#include <QtCore/QCharRef> // for QCharRef
#include <QtCore/QDate> // for QDate
#include <QtCore/QDateTime> // for QDateTime
#include <QtCore/QHash> // for QHash
#include <QtCore/QIODevice> // for operator|, QIODevice::ReadOnly, QIODevice::Text, QIODevice::WriteOnly
#include <QtCore/QList> // for QList
#include <QtCore/QRegExp> // for QRegExp
#include <QtCore/QString> // for QString, operator+, operator==, QByteArray::append
#include <QtCore/QStringList> // for QStringList
#include <QtCore/QTextStream> // for QTextStream
#include <QtCore/QTime> // for QTime
#include <QtCore/QtGlobal> // for qAsConst, QAddConst<>::Type, qPrintable
#include "defs.h"
#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
#include "garmin_fs.h" // for garmin_fs_t, garmin_fs_flags_t, GMSD_FIND, GMSD_GET, GMSD_SET, garmin_fs_alloc
#include "gbfile.h" // for gbfgetstr, gbfclose, gbfopen, gbfile
#include "grtcirc.h" // for RAD, gcdist, radtomiles
#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
#include "jeeps/gpsport.h" // for int32
#include "session.h" // for session_t
#include "src/core/datetime.h" // for DateTime
#include "src/core/logging.h" // for Warning, Fatal
#include "src/core/optional.h" // for optional
#include "src/core/textstream.h"
#include "strptime.h" // for strptime
#include "xcsv.h"
#if CSVFMTS_ENABLED
#define MYNAME "XCSV"
/*
* Internal numeric value to associate with each keyword in a style file.
* To add new keywords, just add an entry here, handle it in the switch
* statements below, add it to xcsv_tokens.in, and rebuild on a system
* that has GNU gperf on it.
*/
enum xcsv_token {
XT_unused = 0,
XT_ALT_FEET,
XT_ALT_METERS,
XT_ANYNAME,
XT_CADENCE,
XT_CITY,
XT_CONSTANT,
XT_COUNTRY,
XT_DESCRIPTION,
XT_EMAIL,
XT_EXCEL_TIME,
XT_FACILITY,
XT_FILENAME,
XT_FORMAT,
XT_GEOCACHE_CONTAINER,
XT_GEOCACHE_DIFF,
XT_GEOCACHE_HINT,
XT_GEOCACHE_LAST_FOUND,
XT_GEOCACHE_PLACER,
XT_GEOCACHE_TERR,
XT_GEOCACHE_TYPE,
XT_GEOCACHE_ISAVAILABLE,
XT_GEOCACHE_ISARCHIVED,
XT_GMT_TIME,
XT_GPS_FIX,
XT_GPS_HDOP,
XT_GPS_PDOP,
XT_GPS_SAT,
XT_GPS_VDOP,
XT_HEART_RATE,
XT_HMSG_TIME,
XT_HMSL_TIME,
XT_ICON_DESCR,
XT_IGNORE,
XT_INDEX,
XT_ISO_TIME,
XT_ISO_TIME_MS,
XT_LATLON_HUMAN_READABLE,
XT_LAT_DECIMAL,
XT_LAT_DECIMALDIR,
XT_LAT_DIR,
XT_LAT_DIRDECIMAL,
XT_LAT_HUMAN_READABLE,
XT_LAT_INT32DEG,
XT_LAT_DDMMDIR,
XT_LAT_NMEA,
XT_LOCAL_TIME,
XT_LON_DECIMAL,
XT_LON_DECIMALDIR,
XT_LON_DIR,
XT_LON_DIRDECIMAL,
XT_LON_HUMAN_READABLE,
XT_LON_INT32DEG,
XT_LON_DDMMDIR,
XT_LON_NMEA,
XT_MAP_EN_BNG,
XT_NOTES,
XT_NET_TIME,
XT_PATH_COURSE,
XT_PATH_DISTANCE_KM,
XT_PATH_DISTANCE_METERS,
XT_PATH_DISTANCE_MILES,
XT_PATH_SPEED,
XT_PATH_SPEED_KNOTS,
XT_PATH_SPEED_KPH,
XT_PATH_SPEED_MPH,
XT_PHONE_NR,
XT_POSTAL_CODE,
XT_POWER,
XT_ROUTE_NAME,
XT_SHORTNAME,
XT_STATE,
XT_STREET_ADDR,
XT_TEMPERATURE,
XT_TEMPERATURE_F,
XT_TIMET_TIME,
XT_TIMET_TIME_MS,
XT_TRACK_NAME,
XT_TRACK_NEW,
XT_URL,
XT_UTM,
XT_UTM_ZONE,
XT_UTM_ZONEC,
XT_UTM_ZONEF,
XT_UTM_EASTING,
XT_UTM_NORTHING,
XT_URL_LINK_TEXT,
XT_YYYYMMDD_TIME
};
#include "xcsv_tokens.gperf" // for Perfect_Hash, xt_mapping
/* a table of config file constants mapped to chars */
const XcsvStyle::char_map_t XcsvStyle::xcsv_char_table[] = {
{ "COMMA", "," },
{ "COMMASPACE", ", " },
{ "SINGLEQUOTE", "'" },
{ "DOUBLEQUOTE", "\"" },
{ "COLON", ":" },
{ "SEMICOLON", ";" },
{ "NEWLINE", "\n" },
{ "CR", "\n" },
{ "CRNEWLINE", "\r\n" },
{ "TAB", "\t" },
{ "SPACE", " " },
{ "HASH", "#" },
{ "WHITESPACE", "\\w" },
{ "PIPE", "|" },
{ nullptr, nullptr }
};
// Given a keyword of "COMMASPACE", return ", ".
QString
XcsvStyle::xcsv_get_char_from_constant_table(const QString& key)
{
static QHash<QString, QString> substitutions;
if (substitutions.empty()) {
for (const char_map_t* cm = xcsv_char_table; !cm->key.isNull(); cm++) {
substitutions.insert(cm->key, cm->chars);
}
}
if (substitutions.contains(key)) {
return substitutions[key];
}
// No substitution found? Just return original.
return key;
}
// Remove outer quotes.
// Should probably be in csv_util.
QString XcsvStyle::dequote(const QString& in) {
QString r = in.simplified();
if (r.startsWith("\"")) r = r.mid(1);
if (r.endsWith("\"")) r.chop(1);
return r;
}
void XcsvStyle::validate_fieldmap(const field_map& fmp, bool is_output) {
if (fmp.key.isEmpty()) {
fatal(FatalMsg() << MYNAME << ": xcsv style is missing" <<
(is_output ? "output" : "input") << "field type.");
}
if (fmp.val.isNull()) {
fatal(FatalMsg() << MYNAME << ": xcsv style" << fmp.key.constData() << "is missing default.");
}
if (is_output && fmp.printfc.isNull()) {
fatal(FatalMsg() << MYNAME << ": xcsv style" << fmp.key.constData() << "output is missing format specifier.");
}
}
/*****************************************************************************/
/* xcsv_ifield_add() - add input field to ifield queue. */
/* usage: xcsv_ifield_add("DESCRIPTION", "", "%s") */
/*****************************************************************************/
void
XcsvStyle::xcsv_ifield_add(XcsvStyle* style, const QString& qkey, const QString& qval, const QString& qpfc)
{
QByteArray key = qkey.toUtf8();
QByteArray val = qval.toUtf8();
QByteArray pfc = qpfc.toUtf8();
struct xt_mapping* xm = Perfect_Hash::in_word_set(key.constData(), strlen(key.constData()));
field_map fmp(key, val, pfc, xm ? xm->xt_token : -1);
validate_fieldmap(fmp, false);
style->ifields.append(fmp);
}
/*****************************************************************************/
/* xcsv_ofield_add() - add output field to ofield queue. */
/* usage: xcsv_ofield_add("LAT_DECIMAL", "", "%08.5lf") */
/*****************************************************************************/
void
XcsvStyle::xcsv_ofield_add(XcsvStyle* style, const QString& qkey, const QString& qval, const QString& qpfc, unsigned options)
{
QByteArray key = qkey.toUtf8();
QByteArray val = qval.toUtf8();
QByteArray pfc = qpfc.toUtf8();
struct xt_mapping* xm = Perfect_Hash::in_word_set(key.constData(), strlen(key.constData()));
field_map fmp(key, val, pfc, xm ? xm->xt_token : -1, options);
validate_fieldmap(fmp, true);
style->ofields.append(fmp);
}
QDateTime
XcsvFormat::yyyymmdd_to_time(const char* s)
{
QDate d = QDate::fromString(s, "yyyyMMdd");
return QDateTime(d);
}
/*
* sscanftime - Parse a date buffer using strftime format
*/
time_t
XcsvFormat::sscanftime(const char* s, const char* format, const int gmt)
{
struct tm stm;
memset(&stm, 0, sizeof(stm));
if (strptime(s, format, &stm)) {
if ((stm.tm_mday == 0) && (stm.tm_mon == 0) && (stm.tm_year == 0)) {
stm.tm_mday = 1;
stm.tm_mon = 0;
stm.tm_year = 70;
}
stm.tm_isdst = -1;
if (gmt) {
return mkgmtime(&stm);
} else {
return mktime(&stm);
}
}
// Don't fuss for empty strings.
if (*s) {
warning("date parse of string '%s' with format '%s' failed.\n",
s, format);
}
return 0;
}
time_t
XcsvFormat::addhms(const char* s, const char* format)
{
time_t tt = 0;
int hour = 0;
int min = 0;
int sec = 0;
char* ampm = (char*) xmalloc(strlen(s) + 1);
int ac = sscanf(s, format, &hour, &min, &sec, ampm);
/* If no time format in arg string, assume AM */
if (ac < 4) {
ampm[0] = 0;
}
if (ac) {
tt = ((tolower(ampm[0])=='p') ? 43200 : 0) + 3600 * hour + 60 * min + sec;
}
xfree(ampm);
return tt;
}
QString
XcsvFormat::writetime(const char* format, time_t t, bool gmt)
{
static struct tm* stmp;
if (gmt) {
stmp = gmtime(&t);
} else {
stmp = localtime(&t);
}
// It's unfortunate that we publish the definition of "strftime specifiers"
// in the style definitions. For this reason, we have to bust everything
// down to a time_t and then let strftime handle them.
char tbuff[1024];
strftime(tbuff, sizeof tbuff, format, stmp);
return QString(tbuff);
}
QString
XcsvFormat::writetime(const char* format, const gpsbabel::DateTime& t, bool gmt)
{
return writetime(format, t.toTime_t(), gmt);
}
QString
XcsvFormat::writehms(const char* format, time_t t, int gmt)
{
static struct tm no_time = tm();
static struct tm* stmp = &no_time;
if (gmt) {
stmp = gmtime(&t);
} else {
stmp = localtime(&t);
}
if (stmp == nullptr) {
stmp = &no_time;
}
return QString::asprintf(format,
stmp->tm_hour, stmp->tm_min, stmp->tm_sec,
(stmp->tm_hour >= 12 ? "PM" : "AM"));
}
QString
XcsvFormat::writehms(const char* format, const gpsbabel::DateTime& t, int gmt)
{
return writehms(format, t.toTime_t(), gmt);
}
long
XcsvFormat::time_to_yyyymmdd(const QDateTime& t)
{
QDate d = t.date();
return d.year() * 10000 + d.month() * 100 + d.day();
}
garmin_fs_t*
XcsvFormat::gmsd_init(Waypoint* wpt)
{
garmin_fs_t* gmsd = garmin_fs_t::find(wpt);
if (gmsd == nullptr) {
gmsd = garmin_fs_alloc(-1);
wpt->fs.FsChainAdd(gmsd);
}
return gmsd;
}
/*****************************************************************************/
/* xcsv_parse_val() - parse incoming data into the waypt structure. */
/* usage: xcsv_parse_val("-123.34", *waypt, *field_map) */
/*****************************************************************************/
void
XcsvFormat::xcsv_parse_val(const QString& value, Waypoint* wpt, const XcsvStyle::field_map& fmp,
xcsv_parse_data* parse_data, const int line_no)
{
const char* enclosure = "";
geocache_data* gc_data = nullptr;
if (fmp.printfc.isNull()) {
fatal(MYNAME ": xcsv style '%s' is missing format specifier", fmp.key.constData());
}
if (0 == strcmp(fmp.printfc.constData(), "\"%s\"")) {
enclosure = "\"";
}
// TODO: eliminate this char string usage.
QByteArray value_utf8 = value.toUtf8();
const char* s = value_utf8.constData();
switch (fmp.hashed_key) {
case XT_IGNORE:
/* IGNORE -- Categorically ignore this... */
break;
case XT_CONSTANT:
/* CONSTANT -- Ignore on Input... */
break;
case XT_ANYNAME:
/* ANYNAME -- Ignore -- this is output magic. */
break;
case XT_INDEX:
/* IGNORE -- Calculated Sequence # For Output*/
break;
case XT_SHORTNAME:
wpt->shortname = csv_stringtrim(s, enclosure);
break;
case XT_DESCRIPTION:
wpt->description = csv_stringtrim(s, enclosure);
break;
case XT_NOTES:
wpt->notes = csv_stringtrim(s, "");
break;
case XT_URL:
if (!parse_data->link_) {
parse_data->link_ = new UrlLink;
}
parse_data->link_->url_ = value.trimmed();
break;
case XT_URL_LINK_TEXT:
if (!parse_data->link_) {
parse_data->link_ = new UrlLink;
}
parse_data->link_->url_link_text_ = value.trimmed();
break;
case XT_ICON_DESCR:
wpt->icon_descr = value.trimmed();
break;
/* LATITUDE CONVERSIONS**************************************************/
case XT_LAT_DECIMAL:
/* latitude as a pure decimal value */
wpt->latitude = atof(s);
break;
case XT_LAT_DECIMALDIR:
case XT_LAT_DIRDECIMAL:
/* latitude as a decimal with N/S in it. */
wpt->latitude = decdir_to_dec(s);
break;
case XT_LAT_INT32DEG:
/* latitude as a 32 bit integer offset */
wpt->latitude = intdeg_to_dec((int) atof(s));
break;
case XT_LAT_HUMAN_READABLE:
human_to_dec(s, &wpt->latitude, &wpt->longitude, 1);
break;
case XT_LAT_DDMMDIR:
wpt->latitude = ddmmdir_to_degrees(s);
break;
case XT_LAT_NMEA:
wpt->latitude = ddmm2degrees(atof(s));
break;
// XT_LAT_10E is handled outside the switch.
/* LONGITUDE CONVERSIONS ***********************************************/
case XT_LON_DECIMAL:
/* longitude as a pure decimal value */
wpt->longitude = atof(s);
break;
case XT_LON_DECIMALDIR:
case XT_LON_DIRDECIMAL:
/* longitude as a decimal with N/S in it. */
wpt->longitude = decdir_to_dec(s);
break;
case XT_LON_INT32DEG:
/* longitude as a 32 bit integer offset */
wpt->longitude = intdeg_to_dec((int) atof(s));
break;
case XT_LON_HUMAN_READABLE:
human_to_dec(s, &wpt->latitude, &wpt->longitude, 2);
break;
case XT_LON_DDMMDIR:
wpt->longitude = ddmmdir_to_degrees(s);
break;
case XT_LON_NMEA:
wpt->longitude = ddmm2degrees(atof(s));
break;
// case XT_LON_10E is handled outside the switch.
/* LAT AND LON CONVERSIONS ********************************************/
case XT_LATLON_HUMAN_READABLE:
human_to_dec(s, &wpt->latitude, &wpt->longitude, 0);
break;
/* DIRECTIONS **********************************************************/
case XT_LAT_DIR:
/* latitude N/S. */
if (*s == 'n' || *s == 'N') {
parse_data->lat_dir_positive = true;
} else if (*s == 's' || *s == 'S') {
parse_data->lat_dir_positive = false;
} else {
warning("parse of string '%s' on line number %d as LAT_DIR failed. Expected 'n', 'N', 's' or 'S'.\n", s, line_no);
}
break;
case XT_LON_DIR:
/* longitude E/W. */
if (*s == 'e' || *s == 'E') {
parse_data->lon_dir_positive = true;
} else if (*s == 'w' || *s == 'W') {
parse_data->lon_dir_positive = false;
} else {
warning("parse of string '%s' on line number %d as LON_DIR failed. Expected 'e', 'E', 'w' or 'W'.\n", s, line_no);
}
break;
/* SPECIAL COORDINATES/GRID */
case XT_MAP_EN_BNG:
parse_coordinates(s, DATUM_OSGB36, grid_bng,
&wpt->latitude, &wpt->longitude, MYNAME);
break;
case XT_UTM_ZONE:
parse_data->utm_zone = atoi(s);
break;
case XT_UTM_ZONEC:
parse_data->utm_zonec = s[0];
break;
case XT_UTM_ZONEF:
parse_data->utm_zone = atoi(s);
parse_data->utm_zonec = s[strlen(s) - 1];
break;
case XT_UTM_EASTING:
parse_data->utm_easting = atof(s);
break;
case XT_UTM_NORTHING:
parse_data->utm_northing = atof(s);
break;
case XT_UTM: {
char* ss;
int i = 0;
parse_data->utm_zone = strtod(s, &ss);
parse_data->utm_zonec = ss[i];
ss++;
parse_data->utm_easting = strtod(ss, &ss);
while (*ss && !isdigit(*ss)) {
ss++;
}
parse_data->utm_northing = strtod(ss, nullptr);
}
break;
/* ALTITUDE CONVERSIONS ************************************************/
case XT_ALT_FEET: {
char *endptr;
double val = strtod(s, &endptr);
if ((val == 0 && s==endptr)) {
wpt->altitude = unknown_alt;
} else {
wpt->altitude = FEET_TO_METERS(val);
if (wpt->altitude < unknown_alt + 1) {
wpt->altitude = unknown_alt;
}
}
}
break;
case XT_ALT_METERS: {
char *endptr;
double val = strtod(s, &endptr);
if ((val == 0 && s==endptr)) {
wpt->altitude = unknown_alt;
} else {
wpt->altitude = val;
if (wpt->altitude < unknown_alt + 1) {
wpt->altitude = unknown_alt;
}
}
}
break;
/* PATH CONVERSIONS ************************************************/
case XT_PATH_SPEED:
WAYPT_SET(wpt, speed, atof(s));
break;
case XT_PATH_SPEED_KPH:
WAYPT_SET(wpt, speed, KPH_TO_MPS(atof(s)));
break;
case XT_PATH_SPEED_MPH:
WAYPT_SET(wpt, speed, MPH_TO_MPS(atof(s)));
break;
case XT_PATH_SPEED_KNOTS:
WAYPT_SET(wpt, speed, KNOTS_TO_MPS(atof(s)));
break;
case XT_PATH_COURSE:
WAYPT_SET(wpt, course, atof(s));
break;
/* TIME CONVERSIONS ***************************************************/
case XT_EXCEL_TIME:
/* Time as Excel Time */
wpt->SetCreationTime(excel_to_timet(atof(s)));
break;
case XT_TIMET_TIME: {
/* Time as time_t */
bool ok;
wpt->SetCreationTime(value.toLongLong(&ok));
if (!ok) {
warning("parse of string '%s' on line number %d as TIMET_TIME failed.\n", s, line_no);
}
}
break;
case XT_TIMET_TIME_MS: {
/* Time as time_t in milliseconds */
bool ok;
wpt->SetCreationTime(0, value.toLongLong(&ok));
if (!ok) {
warning("parse of string '%s' on line number %d as TIMET_TIME_MS failed.\n", s, line_no);
}
}
break;
case XT_YYYYMMDD_TIME:
wpt->SetCreationTime(yyyymmdd_to_time(s));
break;
case XT_GMT_TIME:
wpt->SetCreationTime(sscanftime(s, fmp.printfc.constData(), 1));
break;
case XT_LOCAL_TIME:
if (!gpsbabel_testmode()) {
wpt->creation_time = wpt->creation_time.addSecs(sscanftime(s, fmp.printfc.constData(), 0));
} else {
/* Force constant time zone for test */
wpt->creation_time = wpt->creation_time.addSecs(sscanftime(s, fmp.printfc.constData(), 1));
}
break;
/* Useful when time and date are in separate fields
GMT / Local offset is handled by the two cases above */
case XT_HMSG_TIME:
case XT_HMSL_TIME:
wpt->creation_time = wpt->creation_time.addSecs(addhms(s, fmp.printfc.constData()));
break;
case XT_ISO_TIME:
case XT_ISO_TIME_MS:
wpt->SetCreationTime(xml_parse_time(s));
break;
case XT_NET_TIME: {
fatal("XT_NET_TIME can't have possibly ever worked.");
// time_t tt = wpt->GetCreationTime();
// dotnet_time_to_time_t(atof(s), &tt, &wpt->microseconds);
}
break;
case XT_GEOCACHE_LAST_FOUND:
wpt->AllocGCData()->last_found = yyyymmdd_to_time(s);
break;
/* GEOCACHING STUFF ***************************************************/
case XT_GEOCACHE_DIFF:
/* Geocache Difficulty as an int */
wpt->AllocGCData()->diff = atof(s) * 10;
break;
case XT_GEOCACHE_TERR:
/* Geocache Terrain as an int */
wpt->AllocGCData()->terr = atof(s) * 10;
break;
case XT_GEOCACHE_TYPE:
/* Geocache Type */
wpt->AllocGCData()->type = gs_mktype(s);
break;
case XT_GEOCACHE_CONTAINER:
wpt->AllocGCData()->container = gs_mkcont(s);
break;
case XT_GEOCACHE_HINT:
wpt->AllocGCData()->hint = value.trimmed();
break;
case XT_GEOCACHE_PLACER:
wpt->AllocGCData()->placer = value.trimmed();
break;
case XT_GEOCACHE_ISAVAILABLE:
gc_data = wpt->AllocGCData();
if (case_ignore_strcmp(csv_stringtrim(s, ""), "False") == 0) {
gc_data->is_available = status_false;
} else if (case_ignore_strcmp(csv_stringtrim(s, ""), "True") == 0) {
gc_data->is_available = status_true;
} else {
gc_data->is_available = status_unknown;
}
break;
case XT_GEOCACHE_ISARCHIVED:
gc_data = wpt->AllocGCData();
if (case_ignore_strcmp(csv_stringtrim(s, ""), "False") == 0) {
gc_data->is_archived = status_false;
} else if (case_ignore_strcmp(csv_stringtrim(s, ""), "True") == 0) {
gc_data->is_archived = status_true;
} else {
gc_data->is_archived = status_unknown;
}
break;
/* GPS STUFF *******************************************************/
case XT_GPS_HDOP:
wpt->hdop = atof(s);
break;
case XT_GPS_VDOP:
wpt->vdop = atof(s);
break;
case XT_GPS_PDOP:
wpt->pdop = atof(s);
break;
case XT_GPS_SAT:
wpt->sat = atoi(s);
break;
case XT_GPS_FIX:
wpt->fix = (fix_type)(atoi(s)-(fix_type)1);
if (wpt->fix < fix_2d) {
if (!case_ignore_strcmp(s, "none")) {
wpt->fix = fix_none;
} else if (!case_ignore_strcmp(s, "dgps")) {
wpt->fix = fix_dgps;
} else if (!case_ignore_strcmp(s, "pps")) {
wpt->fix = fix_pps;
} else {
wpt->fix = fix_unknown;
}
}
break;
/* Tracks and routes *********************************************/
case XT_ROUTE_NAME:
parse_data->rte_name = csv_stringtrim(s, enclosure);
break;
case XT_TRACK_NEW:
parse_data->new_track = atoi(s);
break;
case XT_TRACK_NAME:
parse_data->trk_name = csv_stringtrim(s, enclosure);
break;
/* OTHER STUFF ***************************************************/
case XT_PATH_DISTANCE_METERS:
wpt->odometer_distance = atof(s);
break;
case XT_PATH_DISTANCE_KM:
wpt->odometer_distance = atof(s) * 1000.0;
break;
case XT_PATH_DISTANCE_MILES:
wpt->odometer_distance = MILES_TO_METERS(atof(s));
break;
case XT_HEART_RATE:
wpt->heartrate = atoi(s);
break;
case XT_CADENCE:
wpt->cadence = atoi(s);
break;
case XT_POWER:
wpt->power = atof(s);
break;
case XT_TEMPERATURE:
wpt->temperature = atof(s);
break;
case XT_TEMPERATURE_F:
wpt->temperature = (FAHRENHEIT_TO_CELSIUS(atof(s)));
break;
/* GMSD ****************************************************************/
case XT_COUNTRY: {
garmin_fs_t* gmsd = gmsd_init(wpt);
garmin_fs_t::set_country(gmsd, csv_stringtrim(value, enclosure, 0));
}
break;
case XT_STATE: {
garmin_fs_t* gmsd = gmsd_init(wpt);
garmin_fs_t::set_state(gmsd, csv_stringtrim(value, enclosure, 0));
}
break;
case XT_CITY: {
garmin_fs_t* gmsd = gmsd_init(wpt);
garmin_fs_t::set_city(gmsd, csv_stringtrim(value, enclosure, 0));
}
break;
case XT_STREET_ADDR: {
garmin_fs_t* gmsd = gmsd_init(wpt);
garmin_fs_t::set_addr(gmsd, csv_stringtrim(value, enclosure, 0));
}
break;
case XT_POSTAL_CODE: {
garmin_fs_t* gmsd = gmsd_init(wpt);
garmin_fs_t::set_postal_code(gmsd, csv_stringtrim(value, enclosure, 0));
}
break;
case XT_PHONE_NR: {
garmin_fs_t* gmsd = gmsd_init(wpt);
garmin_fs_t::set_phone_nr(gmsd, csv_stringtrim(value, enclosure, 0));
}
break;
case XT_FACILITY: {
garmin_fs_t* gmsd = gmsd_init(wpt);
garmin_fs_t::set_facility(gmsd, csv_stringtrim(value, enclosure, 0));
}
break;
case XT_EMAIL: {
garmin_fs_t* gmsd = gmsd_init(wpt);
garmin_fs_t::set_email(gmsd, csv_stringtrim(value, enclosure, 0));
}
break;
case -1:
if (strncmp(fmp.key.constData(), "LON_10E", 7) == 0) {
wpt->longitude = atof(s) / pow(10.0, atof(fmp.key.constData()+7));
} else if (strncmp(fmp.key.constData(), "LAT_10E", 7) == 0) {
wpt->latitude = atof(s) / pow(10.0, atof(fmp.key.constData()+7));
} else {
warning(MYNAME ": Unknown style directive: %s\n", fmp.key.constData());
}
break;
default:
fatal("This can't happen\n");
break;
}
}
/*****************************************************************************/
/* read() - read input file, parsing lines, fields and handling */
/* any data conversion (the input meat) */
/*****************************************************************************/
void
XcsvFormat::read()
{
int linecount = 0;
route_head* rte = nullptr;
route_head* trk = nullptr;
while (true) {
QString buff = xcsv_file->stream.readLine();
if (buff.isNull()) {
break;
}
linecount++;
/* Whack trailing space; leading space may matter if our field sep
* is whitespace and we have leading whitespace.
*/
// This could be hoisted out as a generic rtrim() if we need such a thing.
while(buff.size() > 0 && buff.at(buff.size() - 1).isSpace()) {
buff.chop(1);
}
/* skip over x many lines on the top for the prologue... */
if ((linecount - 1) < xcsv_style->prologue.count()) {
continue;
}
/* We should skip over epilogue lines also. Since we don't want to
* pre-read the file to know how many data lines we should be seeing,
* we take this cheap shot at the data and cross our fingers.
*/
for(const auto& ogp : qAsConst(xcsv_style->epilogue)) {
if (ogp.startsWith(buff)) {
buff.clear();
break;
}
}
if (!buff.isEmpty()) {
auto* wpt_tmp = new Waypoint;
// initialize parse data for accumulation of line results from all fields in this line.
xcsv_parse_data parse_data;
const QStringList values = csv_linesplit(buff, xcsv_style->field_delimiter,
xcsv_style->field_encloser, linecount);
if (xcsv_style->ifields.isEmpty()) {
fatal(MYNAME ": attempt to read, but style '%s' has no IFIELDs in it.\n", qPrintable(xcsv_style->description)? qPrintable(xcsv_style->description) : "unknown");
}
int ifield_idx = 0;
/* now rip the line apart */
for (const auto& value : values) {
const XcsvStyle::field_map& fmp = xcsv_style->ifields.at(ifield_idx++);
xcsv_parse_val(value, wpt_tmp, fmp, &parse_data, linecount);
if (ifield_idx >= xcsv_style->ifields.size()) {
/* no more fields, stop parsing! */
break;
}
}
// If XT_LAT_DIR(XT_LON_DIR) was an input field, and the latitude(longitude) is positive,
// assume the latitude(longitude) was the absolute value and take the sign from XT_LAT_DIR(XT_LON_DIR).
if (parse_data.lat_dir_positive.has_value() && !parse_data.lat_dir_positive.value() && (wpt_tmp->latitude > 0.0)) {
wpt_tmp->latitude = -wpt_tmp->latitude;
}
if (parse_data.lon_dir_positive.has_value() && !parse_data.lon_dir_positive.value() && (wpt_tmp->longitude > 0.0)) {
wpt_tmp->longitude = -wpt_tmp->longitude;
}
if ((xcsv_file->gps_datum_idx > -1) && (xcsv_file->gps_datum_idx != gps_datum_wgs84)) {
double alt;
GPS_Math_Known_Datum_To_WGS84_M(wpt_tmp->latitude, wpt_tmp->longitude, 0.0,
&wpt_tmp->latitude, &wpt_tmp->longitude, &alt, xcsv_file->gps_datum_idx);
}
if (parse_data.utm_easting || parse_data.utm_northing) {
GPS_Math_UTM_EN_To_Known_Datum(&wpt_tmp->latitude,
&wpt_tmp->longitude,
parse_data.utm_easting, parse_data.utm_northing,
parse_data.utm_zone, parse_data.utm_zonec,
DATUM_WGS84);
}
if (parse_data.link_) {
wpt_tmp->AddUrlLink(*parse_data.link_);
delete parse_data.link_;
parse_data.link_ = nullptr;
}
switch (xcsv_style->datatype) {
case unknown_gpsdata:
case wptdata:
waypt_add(wpt_tmp);
break;
case trkdata:
if ((trk == nullptr) || parse_data.new_track) {
trk = new route_head;
track_add_head(trk);
}
if (!parse_data.trk_name.isEmpty()) {
trk->rte_name = parse_data.trk_name;
}
track_add_wpt(trk, wpt_tmp);
break;
case rtedata:
if (rte == nullptr) {
rte = new route_head;
route_add_head(rte);
}
if (!parse_data.rte_name.isEmpty()) {
rte->rte_name = parse_data.rte_name;
}
route_add_wpt(rte, wpt_tmp);
break;
default:
;
}
}
}
}
void
XcsvFormat::xcsv_resetpathlen(const route_head* head)
{
pathdist = 0;
oldlat = 999;
oldlon = 999;
csv_route = csv_track = nullptr;
switch (xcsv_style->datatype) {
case trkdata:
csv_track = head;
break;
case rtedata:
csv_route = head;
break;
default:
break;
}
}
/*****************************************************************************/
/* xcsv_waypt_pr() - write output file, handling output conversions */
/* (the output meat) */
/*****************************************************************************/
void
XcsvFormat::xcsv_waypt_pr(const Waypoint* wpt)
{
QString buff;
double latitude, longitude;
int32 utmz;
double utme, utmn;
char utmzc;
if (oldlon < 900) {
pathdist += radtomiles(gcdist(RAD(oldlat),RAD(oldlon),
RAD(wpt->latitude),RAD(wpt->longitude)));
}
longitude = oldlon = wpt->longitude;
latitude = oldlat = wpt->latitude;
QString write_delimiter;
if (xcsv_style->field_delimiter == "\\w") {
write_delimiter = " ";
} else {
write_delimiter = xcsv_style->field_delimiter;
}
QString description;
QString shortname;
if (wpt->shortname.isEmpty() || global_opts.synthesize_shortnames) {
if (!wpt->description.isEmpty()) {
if (global_opts.synthesize_shortnames) {
shortname = mkshort_from_wpt(xcsv_file->mkshort_handle, wpt);
} else {
shortname = csv_stringclean(wpt->description, xcsv_style->badchars);
}
} else {
/* no shortname available -- let shortname default on output */
}
} else {
shortname = csv_stringclean(wpt->shortname, xcsv_style->badchars);
}
if (wpt->description.isEmpty()) {
if (!shortname.isEmpty()) {
description = csv_stringclean(shortname, xcsv_style->badchars);
} else {
/* no description -- let description default on output */
}
} else {
description = csv_stringclean(wpt->description, xcsv_style->badchars);
}
if (prefer_shortnames) {
description = shortname;
}
if ((xcsv_file->gps_datum_idx > -1) && (xcsv_file->gps_datum_idx != gps_datum_wgs84)) {
double alt;
GPS_Math_WGS84_To_Known_Datum_M(latitude, longitude, 0.0,
&latitude, &longitude, &alt, xcsv_file->gps_datum_idx);
}
int i = 0;
for (const auto& fmp : qAsConst(xcsv_style->ofields)) {
double lat = latitude;
double lon = longitude;
/*
* A klunky concept. This should evaluate to true for any
* field if we think we don't have realistic value for it.
* This is used by the 'optional' attribute for suppressing
* fields on output.
*/
int field_is_unknown = 0;
if ((i != 0) && !(fmp.options & XcsvStyle::options_nodelim)) {
xcsv_file->stream << write_delimiter;
}
if (fmp.options & XcsvStyle::options_absolute) {
lat = fabs(lat);
lon = fabs(lon);
}
i++;
switch (fmp.hashed_key) {
case XT_IGNORE:
/* IGNORE -- Write the char printf conversion */
buff = QString::asprintf(fmp.printfc.constData(), "");
break;
case XT_INDEX:
buff = QString::asprintf(fmp.printfc.constData(), waypt_out_count + atoi(fmp.val.constData()));
break;
case XT_CONSTANT: {
auto cp = XcsvStyle::xcsv_get_char_from_constant_table(fmp.val.constData());
if (!cp.isEmpty()) {
buff = QString::asprintf(fmp.printfc.constData(), CSTR(cp));
} else {
buff = QString::asprintf(fmp.printfc.constData(), fmp.val.constData());
}
}
break;
case XT_SHORTNAME:
buff = QString::asprintf(fmp.printfc.constData(),
shortname.isEmpty() ? fmp.val.constData() : CSTR(shortname));
break;
case XT_ANYNAME:
{
QString anyname = wpt->shortname;
if (anyname.isEmpty()) {
anyname = mkshort(xcsv_file->mkshort_handle, wpt->description);
}
if (anyname.isEmpty()) {
anyname = mkshort(xcsv_file->mkshort_handle, wpt->description);
}
if (anyname.isEmpty()) {
anyname = wpt->notes;
}
if (anyname.isEmpty()) {
anyname = fmp.val.constData();
}
buff = QString::asprintf(fmp.printfc.constData(), CSTR(anyname));
}
break;
case XT_DESCRIPTION:
buff = QString::asprintf(fmp.printfc.constData(),
description.isEmpty() ? fmp.val.constData() : CSTR(description));
break;
case XT_NOTES:
buff = QString::asprintf(fmp.printfc.constData(),
wpt->notes.isEmpty() ? fmp.val.constData() : CSTR(wpt->notes));
break;
case XT_URL: {
if (xcsv_urlbase) {
buff = xcsv_urlbase;
}
if (wpt->HasUrlLink()) {
UrlLink l = wpt->GetUrlLink();
buff += QString::asprintf(fmp.printfc.constData(), CSTR(l.url_));
} else {
buff += QString::asprintf(fmp.printfc.constData(), fmp.val.constData() && *fmp.val.constData() ? fmp.val.constData() : "\"\"");
}
}
break;
case XT_URL_LINK_TEXT:
if (wpt->HasUrlLink()) {
UrlLink l = wpt->GetUrlLink();
buff = QString::asprintf(fmp.printfc.constData(),
!l.url_link_text_.isEmpty() ? CSTR(l.url_link_text_) : fmp.val.constData());
}
break;
case XT_ICON_DESCR:
buff = QString::asprintf(fmp.printfc.constData(),
(!wpt->icon_descr.isNull()) ?
CSTR(wpt->icon_descr) : fmp.val.constData());
break;
/* LATITUDE CONVERSION***********************************************/
case XT_LAT_DECIMAL:
/* latitude as a pure decimal value */
buff = QString::asprintf(fmp.printfc.constData(), lat);
break;
case XT_LAT_DECIMALDIR:
/* latitude as a decimal value with N/S after it */
buff = QString::asprintf(fmp.printfc.constData(), fabs(lat),
lat_dir(lat));
break;
case XT_LAT_DIRDECIMAL:
/* latitude as a decimal value with N/S before it */
buff = QString::asprintf(fmp.printfc.constData(),
lat_dir(lat),
fabs(lat));
break;
case XT_LAT_INT32DEG:
/* latitude as an integer offset from 0 degrees */
buff = QString::asprintf(fmp.printfc.constData(),
dec_to_intdeg(lat));
break;
case XT_LAT_DDMMDIR:
/*latitude as (degrees * 100) + decimal minutes, with N/S after it */
buff = dec_to_human(fmp.printfc.constData(), "SN", degrees2ddmm(lat));
break;
case XT_LAT_HUMAN_READABLE:
buff = dec_to_human(fmp.printfc.constData(), "SN", lat);
break;
case XT_LAT_NMEA:
buff = QString::asprintf(fmp.printfc.constData(), degrees2ddmm(lat));
break;
// case XT_LAT_10E is handled outside the switch.
/* LONGITUDE CONVERSIONS*********************************************/
case XT_LON_DECIMAL:
/* longitude as a pure decimal value */
buff = QString::asprintf(fmp.printfc.constData(), lon);
break;
case XT_LON_DECIMALDIR:
/* latitude as a decimal value with N/S after it */
buff = QString::asprintf(fmp.printfc.constData(),
fabs(lon),
lon_dir(lon));
break;
case XT_LON_DIRDECIMAL:
/* latitude as a decimal value with N/S before it */
buff = QString::asprintf(fmp.printfc.constData(),
lon_dir(lon),
fabs(lon));
break;
case XT_LON_INT32DEG:
/* longitude as an integer offset from 0 degrees */
buff = QString::asprintf(fmp.printfc.constData(),
dec_to_intdeg(lon));
break;
case XT_LON_DDMMDIR:
/* longitude as (degrees * 100) + decimal minutes, with W/E after it*/
buff = dec_to_human(fmp.printfc.constData(), "WE", degrees2ddmm(lon));
break;
case XT_LON_HUMAN_READABLE:
buff = dec_to_human(fmp.printfc.constData(), "WE", lon);
break;
case XT_LATLON_HUMAN_READABLE:
buff = dec_to_human(fmp.printfc.constData(), "SN", lat);
buff += " ";
buff += dec_to_human(fmp.printfc.constData(), "WE", lon);
// Tidy up leading, trailing, middle whitespace.
buff = buff.simplified();
break;
case XT_LON_NMEA:
buff = QString::asprintf(fmp.printfc.constData(), degrees2ddmm(lon));
break;
// case XT_LON_10E is handled outside the switch.
/* DIRECTIONS *******************************************************/
case XT_LAT_DIR:
/* latitude N/S as a char */
buff = QString::asprintf(fmp.printfc.constData(),
lat_dir(lat));
break;
case XT_LON_DIR:
/* longitude E/W as a char */
buff = QString::asprintf(fmp.printfc.constData(),
lon_dir(lon));
break;
/* SPECIAL COORDINATES */
case XT_MAP_EN_BNG: {
char map[3];
double north, east;
if (! GPS_Math_WGS84_To_UKOSMap_M(wpt->latitude, wpt->longitude, &east, &north, map))
fatal(MYNAME ": Position (%.5f/%.5f) outside of BNG.\n",
wpt->latitude, wpt->longitude);
buff = QString::asprintf(fmp.printfc.constData(), map, (int)(east + 0.5), (int)(north + 0.5));
}
break;
case XT_UTM: {
char tbuf[100];
GPS_Math_WGS84_To_UTM_EN(wpt->latitude, wpt->longitude,
&utme, &utmn, &utmz, &utmzc);
snprintf(tbuf, sizeof(tbuf), "%d%c %6.0f %7.0f",
utmz, utmzc, utme, utmn);
buff = QString::asprintf(fmp.printfc.constData(), tbuf);
}
break;
case XT_UTM_ZONE:
GPS_Math_WGS84_To_UTM_EN(wpt->latitude, wpt->longitude,
&utme, &utmn, &utmz, &utmzc);
buff = QString::asprintf(fmp.printfc.constData(), utmz);
break;
case XT_UTM_ZONEC:
GPS_Math_WGS84_To_UTM_EN(wpt->latitude, wpt->longitude,
&utme, &utmn, &utmz, &utmzc);
buff = QString::asprintf(fmp.printfc.constData(), utmzc);
break;
case XT_UTM_ZONEF: {
char tbuf[10];
GPS_Math_WGS84_To_UTM_EN(wpt->latitude, wpt->longitude,
&utme, &utmn, &utmz, &utmzc);
tbuf[0] = 0;
snprintf(tbuf, sizeof(tbuf), "%d%c", utmz, utmzc);
buff = QString::asprintf(fmp.printfc.constData(), tbuf);
}
break;
case XT_UTM_NORTHING:
GPS_Math_WGS84_To_UTM_EN(wpt->latitude, wpt->longitude,
&utme, &utmn, &utmz, &utmzc);
buff = QString::asprintf(fmp.printfc.constData(), utmn);
break;
case XT_UTM_EASTING:
GPS_Math_WGS84_To_UTM_EN(wpt->latitude, wpt->longitude,
&utme, &utmn, &utmz, &utmzc);
buff = QString::asprintf(fmp.printfc.constData(), utme);
break;
/* ALTITUDE CONVERSIONS**********************************************/
case XT_ALT_FEET:
/* altitude in feet as a decimal value */
if (wpt->altitude != unknown_alt) {
buff = QString::asprintf(fmp.printfc.constData(),
METERS_TO_FEET(wpt->altitude));
}
break;
case XT_ALT_METERS:
/* altitude in meters as a decimal value */
if (wpt->altitude != unknown_alt) {
buff = QString::asprintf(fmp.printfc.constData(),
wpt->altitude);
}
break;
/* DISTANCE CONVERSIONS**********************************************/
/* prefer odometer distance. */
/* if not available, use calculated distance from positions */
case XT_PATH_DISTANCE_MILES:
/* path (route/track) distance in miles */
if (wpt->odometer_distance) {
buff = QString::asprintf(fmp.printfc.constData(), METERS_TO_MILES(wpt->odometer_distance));
} else {
buff = QString::asprintf(fmp.printfc.constData(), pathdist);
}
break;
case XT_PATH_DISTANCE_METERS:
/* path (route/track) distance in meters */
if (wpt->odometer_distance) {
buff = QString::asprintf(fmp.printfc.constData(), wpt->odometer_distance);
} else {
buff = QString::asprintf(fmp.printfc.constData(), MILES_TO_METERS(pathdist));
}
break;
case XT_PATH_DISTANCE_KM:
/* path (route/track) distance in kilometers */
if (wpt->odometer_distance) {
buff = QString::asprintf(fmp.printfc.constData(), wpt->odometer_distance / 1000.0);
} else {
buff = QString::asprintf(fmp.printfc.constData(), MILES_TO_METERS(pathdist) / 1000.0);
}
break;
case XT_PATH_SPEED:
buff = QString::asprintf(fmp.printfc.constData(), wpt->speed);
break;
case XT_PATH_SPEED_KPH:
buff = QString::asprintf(fmp.printfc.constData(), MPS_TO_KPH(wpt->speed));
break;
case XT_PATH_SPEED_MPH:
buff = QString::asprintf(fmp.printfc.constData(), MPS_TO_MPH(wpt->speed));
break;
case XT_PATH_SPEED_KNOTS:
buff = QString::asprintf(fmp.printfc.constData(), MPS_TO_KNOTS(wpt->speed));
break;
case XT_PATH_COURSE:
buff = QString::asprintf(fmp.printfc.constData(), wpt->course);
break;
/* HEART RATE CONVERSION***********************************************/
case XT_HEART_RATE:
buff = QString::asprintf(fmp.printfc.constData(), wpt->heartrate);
break;
/* CADENCE CONVERSION***********************************************/
case XT_CADENCE:
buff = QString::asprintf(fmp.printfc.constData(), wpt->cadence);
break;
/* POWER CONVERSION***********************************************/
case XT_POWER:
buff = QString::asprintf(fmp.printfc.constData(), wpt->power);
break;
case XT_TEMPERATURE:
buff = QString::asprintf(fmp.printfc.constData(), wpt->temperature);
break;
case XT_TEMPERATURE_F:
buff = QString::asprintf(fmp.printfc.constData(), CELSIUS_TO_FAHRENHEIT(wpt->temperature));
break;
/* TIME CONVERSIONS**************************************************/
case XT_EXCEL_TIME:
/* creation time as an excel (double) time */
buff = QString::asprintf(fmp.printfc.constData(), timet_to_excel(wpt->GetCreationTime().toTime_t()));
break;
case XT_TIMET_TIME:
/* time as a time_t variable in seconds */
buff = QString::asprintf(fmp.printfc.constData(), wpt->GetCreationTime().toSecsSinceEpoch());
break;
case XT_TIMET_TIME_MS:
/* time as a time_t variable in milliseconds */
buff = QString::asprintf(fmp.printfc.constData(), wpt->GetCreationTime().toMSecsSinceEpoch());
break;
case XT_YYYYMMDD_TIME:
buff = QString::asprintf(fmp.printfc.constData(), time_to_yyyymmdd(wpt->GetCreationTime()));
break;
case XT_GMT_TIME:
buff = writetime(fmp.printfc.constData(), wpt->GetCreationTime(), true);
break;
case XT_LOCAL_TIME:
buff = writetime(fmp.printfc.constData(), wpt->GetCreationTime(), false);
break;
case XT_HMSG_TIME:
buff = writehms(fmp.printfc.constData(), wpt->GetCreationTime(), 1);
break;
case XT_HMSL_TIME:
buff = writehms(fmp.printfc.constData(), wpt->GetCreationTime(), 0);
break;
case XT_ISO_TIME:
buff = writetime("%Y-%m-%dT%H:%M:%SZ", wpt->GetCreationTime(), true);
break;
case XT_ISO_TIME_MS:
buff = wpt->GetCreationTime().toPrettyString();
break;
case XT_GEOCACHE_LAST_FOUND:
buff = QString::asprintf(fmp.printfc.constData(), time_to_yyyymmdd(wpt->gc_data->last_found));
break;
/* GEOCACHE STUFF **************************************************/
case XT_GEOCACHE_DIFF:
/* Geocache Difficulty as a double */
buff = QString::asprintf(fmp.printfc.constData(), wpt->gc_data->diff / 10.0);
field_is_unknown = !wpt->gc_data->diff;
break;
case XT_GEOCACHE_TERR:
/* Geocache Terrain as a double */
buff = QString::asprintf(fmp.printfc.constData(), wpt->gc_data->terr / 10.0);
field_is_unknown = !wpt->gc_data->terr;
break;
case XT_GEOCACHE_CONTAINER:
/* Geocache Container */
buff = QString::asprintf(fmp.printfc.constData(), gs_get_container(wpt->gc_data->container));
field_is_unknown = wpt->gc_data->container == gc_unknown;
break;
case XT_GEOCACHE_TYPE:
/* Geocache Type */
buff = QString::asprintf(fmp.printfc.constData(), gs_get_cachetype(wpt->gc_data->type));
field_is_unknown = wpt->gc_data->type == gt_unknown;
break;
case XT_GEOCACHE_HINT:
buff = QString::asprintf(fmp.printfc.constData(), CSTR(wpt->gc_data->hint));
field_is_unknown = !wpt->gc_data->hint.isEmpty();
break;
case XT_GEOCACHE_PLACER:
buff = QString::asprintf(fmp.printfc.constData(), CSTR(wpt->gc_data->placer));
field_is_unknown = !wpt->gc_data->placer.isEmpty();
break;
case XT_GEOCACHE_ISAVAILABLE:
if (wpt->gc_data->is_available == status_false) {
buff = QString::asprintf(fmp.printfc.constData(), "False");
} else if (wpt->gc_data->is_available == status_true) {
buff = QString::asprintf(fmp.printfc.constData(), "True");
} else {
buff = QString::asprintf(fmp.printfc.constData(), "Unknown");
}
break;
case XT_GEOCACHE_ISARCHIVED:
if (wpt->gc_data->is_archived == status_false) {
buff = QString::asprintf(fmp.printfc.constData(), "False");
} else if (wpt->gc_data->is_archived == status_true) {
buff = QString::asprintf(fmp.printfc.constData(), "True");
} else {
buff = QString::asprintf(fmp.printfc.constData(), "Unknown");
}
break;
/* Tracks and Routes ***********************************************/
case XT_TRACK_NEW:
if (csv_track) {
if (WAYPT_HAS(wpt,new_trkseg)) {
buff = QString::asprintf(fmp.printfc.constData(), 1);
} else {
buff = QString::asprintf(fmp.printfc.constData(), 0);
}
}
break;
case XT_TRACK_NAME:
if (csv_track) {
buff = QString::asprintf(fmp.printfc.constData(), CSTR(csv_track->rte_name));
}
break;
case XT_ROUTE_NAME:
if (csv_route) {
buff = QString::asprintf(fmp.printfc.constData(), CSTR(csv_route->rte_name));
}
break;
/* GPS STUFF *******************************************************/
case XT_GPS_HDOP:
buff = QString::asprintf(fmp.printfc.constData(), wpt->hdop);
field_is_unknown = !wpt->hdop;
break;
case XT_GPS_VDOP:
buff = QString::asprintf(fmp.printfc.constData(), wpt->vdop);
field_is_unknown = !wpt->vdop;
break;
case XT_GPS_PDOP:
buff = QString::asprintf(fmp.printfc.constData(), wpt->pdop);
field_is_unknown = !wpt->pdop;
break;
case XT_GPS_SAT:
buff = QString::asprintf(fmp.printfc.constData(), wpt->sat);
field_is_unknown = !wpt->sat;
break;
case XT_GPS_FIX: {
const char* fix = nullptr;
switch (wpt->fix) {
case fix_unknown:
field_is_unknown = 1;
fix = "Unknown";
break;
case fix_none:
fix = "None";
break;
case fix_2d:
fix = "2d";
break;
case fix_3d:
fix = "3d";
break;
case fix_dgps:
fix = "dgps";
break;
case fix_pps:
fix = "pps";
break;
}
buff = QString::asprintf(fmp.printfc.constData(), fix);
}
break;
/* GMSD ************************************************************/
case XT_COUNTRY: {
garmin_fs_t* gmsd = garmin_fs_t::find(wpt);
buff = QString::asprintf(fmp.printfc.constData(), CSTR(garmin_fs_t::get_country(gmsd, "")));
}
break;
case XT_STATE: {
garmin_fs_t* gmsd = garmin_fs_t::find(wpt);
buff = QString::asprintf(fmp.printfc.constData(), CSTR(garmin_fs_t::get_state(gmsd, "")));
}
break;
case XT_CITY: {
garmin_fs_t* gmsd = garmin_fs_t::find(wpt);
buff = QString::asprintf(fmp.printfc.constData(), CSTR(garmin_fs_t::get_city(gmsd, "")));
}
break;
case XT_POSTAL_CODE: {
garmin_fs_t* gmsd = garmin_fs_t::find(wpt);
buff = QString::asprintf(fmp.printfc.constData(), CSTR(garmin_fs_t::get_postal_code(gmsd, "")));
}
break;
case XT_STREET_ADDR: {
garmin_fs_t* gmsd = garmin_fs_t::find(wpt);
buff = QString::asprintf(fmp.printfc.constData(), CSTR(garmin_fs_t::get_addr(gmsd, "")));
}
break;
case XT_PHONE_NR: {
garmin_fs_t* gmsd = garmin_fs_t::find(wpt);
buff = QString::asprintf(fmp.printfc.constData(), CSTR(garmin_fs_t::get_phone_nr(gmsd, "")));
}
break;
case XT_FACILITY: {
garmin_fs_t* gmsd = garmin_fs_t::find(wpt);
buff = QString::asprintf(fmp.printfc.constData(), CSTR(garmin_fs_t::get_facility(gmsd, "")));
}
break;
case XT_EMAIL: {
garmin_fs_t* gmsd = garmin_fs_t::find(wpt);
buff = QString::asprintf(fmp.printfc.constData(), CSTR(garmin_fs_t::get_email(gmsd, "")));
}
break;
/* specials */
case XT_FILENAME:
buff = QString::asprintf(fmp.printfc.constData(), CSTR(wpt->session->filename));
break;
case XT_FORMAT:
buff = QString::asprintf(fmp.printfc.constData(), CSTR(wpt->session->name));
break;
case -1:
if (strncmp(fmp.key.constData(), "LON_10E", 7) == 0) {
buff = QString::asprintf(fmp.printfc.constData(), lon * pow(10.0, atof(fmp.key.constData()+7)));
} else if (strncmp(fmp.key.constData(), "LAT_10E", 7) == 0) {
buff = QString::asprintf(fmp.printfc.constData(), lat * pow(10.0, atof(fmp.key.constData()+7)));
}
break;
default:
warning(MYNAME ": Unknown style directive: %s\n", fmp.key.constData());
break;
}
QString obuff = csv_stringclean(buff, xcsv_style->badchars);
if (field_is_unknown && fmp.options & XcsvStyle::options_optional) {
continue;
}
if (!xcsv_style->field_encloser.isEmpty()) {
/* print the enclosing character(s) */
xcsv_file->stream << xcsv_style->field_encloser;
}
/* As a special case (pronounced "horrible hack") we allow
* ""%s"" to smuggle bad characters through.
*/
if (0 == strcmp(fmp.printfc.constData(), "\"%s\"")) {
obuff = '"' + obuff + '"';
}
xcsv_file->stream << obuff;
if (!xcsv_style->field_encloser.isEmpty()) {
/* print the enclosing character(s) */
xcsv_file->stream << xcsv_style->field_encloser;
}
buff.clear();
}
xcsv_file->stream << xcsv_style->record_delimiter;
/* increment the index counter */
waypt_out_count++;
}
// return |original| after performing token replacement.
QString
XcsvFormat::xcsv_replace_tokens(const QString& original) const {
QString replacement = original;
// Don't do potentially expensive replacements if token prefix
// isn't present;
if (original.contains("__")) {
replacement.replace("__FILE__", xcsv_file->fname);
replacement.replace("__VERSION__", gpsbabel_testmode()? "" : gpsbabel_version);
QDateTime dt = current_time().toUTC();
QString dts = dt.toString("ddd MMM dd hh:mm:ss yyyy");
replacement.replace("__DATE_AND_TIME__", dts);
QString d = dt.toString("MM/dd/yyyy");
replacement.replace("__DATE__", d);
QString t = dt.toString("hh:mm:ss");
replacement.replace("__TIME__", t);
}
return replacement;
}
/*****************************************************************************/
/* write(void) - write prologues, spawn the output loop, and write */
/* epilogues. */
/*****************************************************************************/
void
XcsvFormat::write()
{
/* reset the index counter */
waypt_out_count = 0;
/* output prologue lines, if any. */
for (const auto& line : qAsConst(xcsv_style->prologue)) {
QString line_to_write = xcsv_replace_tokens(line);
xcsv_file->stream << line_to_write << xcsv_style->record_delimiter;
}
auto xcsv_waypt_pr_lambda = [this](const Waypoint* wpt)->void {
xcsv_waypt_pr(wpt);
};
auto xcsv_resetpathlen_lambda = [this](const route_head* rte)->void {
xcsv_resetpathlen(rte);
};
if ((xcsv_style->datatype == 0) || (xcsv_style->datatype == wptdata)) {
waypt_disp_all(xcsv_waypt_pr_lambda);
}
if ((xcsv_style->datatype == 0) || (xcsv_style->datatype == rtedata)) {
route_disp_all(xcsv_resetpathlen_lambda, nullptr, xcsv_waypt_pr_lambda);
}
if ((xcsv_style->datatype == 0) || (xcsv_style->datatype == trkdata)) {
track_disp_all(xcsv_resetpathlen_lambda, nullptr, xcsv_waypt_pr_lambda);
}
/* output epilogue lines, if any. */
for (const auto& line : qAsConst(xcsv_style->epilogue)) {
QString line_to_write = xcsv_replace_tokens(line);
xcsv_file->stream << line_to_write << xcsv_style->record_delimiter;
}
}
void
XcsvStyle::xcsv_parse_style_line(XcsvStyle* style, QString line)
{
// The lines to be parsed have a leading operation |op| that is
// separated by whitespace from the rest. Each op may have zero or
// more comma separated tokens |token[]|.
// Handle comments, with an escape. Probably not optimal.
int escape_idx = line.indexOf('\\');
int comment_idx = line.indexOf('#');
if (comment_idx > 0 && escape_idx +1 != comment_idx) {
line = line.mid(0, line.indexOf("#")).trimmed();
} else {
line = line.replace("\\#", "#");
}
// Separate op and tokens.
int sep = line.indexOf(QRegExp("\\s+"));
// the first token is the operation, e.g. "IFIELD"
QString op = line.mid(0, sep).trimmed().toUpper();
QString tokenstr = line.mid(sep).trimmed();
QStringList tokens = tokenstr.split(",");
if (op == "FIELD_DELIMITER") {
auto cp = xcsv_get_char_from_constant_table(tokens[0]);
style->field_delimiter = cp;
char* p = csv_stringtrim(CSTR(style->field_delimiter), " ", 0);
/* field delimiters are always bad characters */
if (0 == strcmp(p, "\\w")) {
style->badchars = " \n\r";
} else {
style->badchars += p;
}
xfree(p);
} else
if (op == "FIELD_ENCLOSER") {
auto cp = xcsv_get_char_from_constant_table(tokens[0]);
style->field_encloser = cp;
char* p = csv_stringtrim(CSTR(style->field_encloser), " ", 0);
style->badchars += p;
xfree(p);
} else
if (op == "RECORD_DELIMITER") {
auto cp = xcsv_get_char_from_constant_table(tokens[0]);
style->record_delimiter = cp;
// Record delimiters are always bad characters.
auto p = csv_stringtrim(CSTR(style->record_delimiter), " ", 0);
style->badchars += p;
xfree(p);
} else
if (op == "FORMAT_TYPE") {
if (tokens[0] == "INTERNAL") {
style->type = ff_type_internal;
}
// this is almost inconceivable...
if (tokens[0] == "SERIAL") {
style->type = ff_type_serial;
}
} else
if (op == "DESCRIPTION") {
style->description = tokens[0];
} else
if (op == "EXTENSION") {
style->extension = tokens[0];
} else
if (op == "SHORTLEN") {
style->shortlen = tokens[0].toInt();
} else
if (op == "SHORTWHITE") {
style->whitespace_ok = tokens[0].toInt();
} else
if (op == "BADCHARS") {
char* sp = csv_stringtrim(CSTR(tokenstr), "\"", 1);
QString cp = xcsv_get_char_from_constant_table(sp);
style->badchars += cp;
xfree(sp);
} else
if (op =="PROLOGUE") {
style->prologue.append(tokenstr);
} else
if (op == "EPILOGUE") {
style->epilogue.append(tokenstr);
} else
if (op == "ENCODING") {
style->codecname = tokens[0];
} else
if (op == "DATUM") {
style->gps_datum_name = tokens[0];
} else
if (op == "DATATYPE") {
QString p = tokens[0].toUpper();
if (p == "TRACK") {
style->datatype = trkdata;
} else if (p == "ROUTE") {
style->datatype = rtedata;
} else if (p == "WAYPOINT") {
style->datatype = wptdata;
} else {
fatal(FatalMsg() << MYNAME << ": Unknown data type" << p);
}
} else
if (op == "IFIELD") {
if (tokens.size() < 3) {
fatal(FatalMsg() << "Invalid IFIELD line: " << tokenstr);
}
// The key ("LAT_DIR") should never contain quotes.
const QString key = tokens[0].simplified();
const QString val = dequote(tokens[1]);
const QString pfc = dequote(tokens[2]);
xcsv_ifield_add(style, key, val, pfc);
} else
//
// as OFIELDs are implemented as an after-thought, I'll
// leave this as it's own parsing for now. We could
// change the world on ifield vs ofield format later..
//
if (op == "OFIELD") {
unsigned options = 0;
// Note: simplified() has to run after split().
if (tokens.size() < 3) {
fatal(FatalMsg() << "Invalid OFIELD line: " << tokenstr);
}
// The key ("LAT_DIR") should never contain quotes.
const QString key = tokens[0].simplified();
const QString val = dequote(tokens[1]);
const QString pfc = dequote(tokens[2]);
// This is pretty lazy way to parse write options.
// They've very rarely used, so we'll go for simple.
// We may have split the optional fourth and final field which can contain
// option[s], so look at all the remaining tokens.
for (int token_idx = 3; token_idx < tokens.size(); ++token_idx) {
QString options_string = tokens[token_idx].simplified();
if (options_string.contains("no_delim_before")) {
options |= options_nodelim;
}
if (options_string.contains("absolute")) {
options |= options_absolute;
}
if (options_string.contains("optional")) {
options |= options_optional;
}
}
xcsv_ofield_add(style, key, val, pfc, options);
}
}
/*
* A wrapper for xcsv_parse_style_line that reads until it hits
* a terminating null. Makes multiple calls to that function so
* that "ignore to end of line" comments work right.
*/
XcsvStyle
XcsvStyle::xcsv_parse_style_buff(const char* sbuff)
{
XcsvStyle style;
const QStringList lines = QString(sbuff).split('\n');
for (const auto& line : lines) {
xcsv_parse_style_line(&style, line);
}
return style;
}
XcsvStyle
XcsvStyle::xcsv_read_style(const char* fname)
{
gbfile* fp = gbfopen(fname, "rb", MYNAME);
XcsvStyle style;
for (QString sbuff = gbfgetstr(fp); !sbuff.isNull(); sbuff = gbfgetstr(fp)) {
sbuff = sbuff.trimmed();
xcsv_parse_style_line(&style, sbuff);
}
/* if we have no output fields, use input fields as output fields */
if (style.ofields.isEmpty()) {
style.ofields = style.ifields;
}
gbfclose(fp);
return style;
}
/*
* Passed a pointer to an internal buffer that would be identical
* to the series of bytes that would be in a style file, we set up
* the xcsv parser and make it ready for general use.
*/
XcsvStyle
XcsvStyle::xcsv_read_internal_style(const char* style_buf)
{
XcsvStyle style = xcsv_parse_style_buff(style_buf);
/* if we have no output fields, use input fields as output fields */
if (style.ofields.isEmpty()) {
style.ofields = style.ifields;
}
return style;
}
void
XcsvFormat::xcsv_setup_internal_style(const char* style_buf)
{
intstylebuf = style_buf;
}
void
XcsvFormat::rd_init(const QString& fname)
{
/*
* if we don't have an internal style defined, we need to
* read it from a user-supplied style file, or die trying.
*/
if (intstylebuf != nullptr) {
xcsv_style = new XcsvStyle(XcsvStyle::xcsv_read_internal_style(intstylebuf));
} else {
if (!styleopt) {
fatal(MYNAME ": XCSV input style not declared. Use ... -i xcsv,style=path/to/file.style\n");
}
xcsv_style = new XcsvStyle(XcsvStyle::xcsv_read_style(styleopt));
}
if ((xcsv_style->datatype == 0) || (xcsv_style->datatype == wptdata)) {
if (global_opts.masked_objective & (TRKDATAMASK|RTEDATAMASK)) {
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));
}
}
xcsv_file = new XcsvFile;
if (xcsv_style->codecname.isEmpty()) {
xcsv_file->stream.open(fname, QIODevice::ReadOnly, MYNAME);
} else {
xcsv_file->stream.open(fname, QIODevice::ReadOnly, MYNAME, CSTR(xcsv_style->codecname));
}
xcsv_file->fname = fname;
QString datum_name;
if (opt_datum != nullptr) {
datum_name = opt_datum;
} else if (!xcsv_style->gps_datum_name.isEmpty()) {
datum_name = xcsv_style->gps_datum_name;
} else {
datum_name = "WGS 84";
}
xcsv_file->gps_datum_idx = GPS_Lookup_Datum_Index(datum_name);
is_fatal(xcsv_file->gps_datum_idx < 0, MYNAME ": datum \"%s\" is not supported.", qPrintable(datum_name));
assert(gps_datum_wgs84 == GPS_Lookup_Datum_Index("WGS 84"));
}
void
XcsvFormat::rd_deinit()
{
xcsv_file->stream.close();
delete xcsv_file;
xcsv_file = nullptr;
delete xcsv_style;
xcsv_style = nullptr;
}
void
XcsvFormat::wr_init(const QString& fname)
{
/*
* if we don't have an internal style defined, we need to
* read it from a user-supplied style file, or die trying.
*/
if (intstylebuf != nullptr) {
xcsv_style = new XcsvStyle(XcsvStyle::xcsv_read_internal_style(intstylebuf));
} else {
if (!styleopt) {
fatal(MYNAME ": XCSV output style not declared. Use ... -o xcsv,style=path/to/file.style\n");
}
xcsv_style = new XcsvStyle(XcsvStyle::xcsv_read_style(styleopt));
}
xcsv_file = new XcsvFile;
if (xcsv_style->codecname.isEmpty()) {
xcsv_file->stream.open(fname, QIODevice::WriteOnly | QIODevice::Text, MYNAME);
} else {
xcsv_file->stream.open(fname, QIODevice::WriteOnly | QIODevice::Text, MYNAME, CSTR(xcsv_style->codecname));
}
xcsv_file->fname = fname;
if (xcsv_style->shortlen) {
setshort_length(xcsv_file->mkshort_handle, *xcsv_style->shortlen);
}
if (xcsv_style->whitespace_ok) {
setshort_whitespace_ok(xcsv_file->mkshort_handle, *xcsv_style->whitespace_ok);
}
/* set mkshort options from the command line */
if (global_opts.synthesize_shortnames) {
if (snlenopt) {
setshort_length(xcsv_file->mkshort_handle, atoi(snlenopt));
}
if (snwhiteopt) {
setshort_whitespace_ok(xcsv_file->mkshort_handle, atoi(snwhiteopt));
}
if (snupperopt) {
setshort_mustupper(xcsv_file->mkshort_handle, atoi(snupperopt));
}
if (snuniqueopt) {
setshort_mustuniq(xcsv_file->mkshort_handle, atoi(snuniqueopt));
}
setshort_badchars(xcsv_file->mkshort_handle, CSTR(xcsv_style->badchars));
}
QString datum_name;
if (opt_datum != nullptr) {
datum_name = opt_datum;
} else if (!xcsv_style->gps_datum_name.isEmpty()) {
datum_name = xcsv_style->gps_datum_name;
} else {
datum_name = "WGS 84";
}
xcsv_file->gps_datum_idx = GPS_Lookup_Datum_Index(datum_name);
is_fatal(xcsv_file->gps_datum_idx < 0, MYNAME ": datum \"%s\" is not supported.", qPrintable(datum_name));
assert(gps_datum_wgs84 == GPS_Lookup_Datum_Index("WGS 84"));
}
void
XcsvFormat::wr_position_init(const QString& fname)
{
wr_init(fname);
}
void
XcsvFormat::wr_deinit()
{
xcsv_file->stream.close();
delete xcsv_file;
xcsv_file = nullptr;
delete xcsv_style;
xcsv_style = nullptr;
}
void
XcsvFormat::wr_position_deinit()
{
wr_deinit();
}
void
XcsvFormat::wr_position(Waypoint* wpt)
{
/* Tweak incoming name if we don't have a fix */
switch (wpt->fix) {
case fix_none:
wpt->shortname = "ESTIMATED Position";
break;
default:
break;
}
waypt_add(wpt);
write();
waypt_del(wpt);
xcsv_file->stream.flush();
}
#endif //CSVFMTS_ENABLED