Rev 313 | Blame | Compare with Previous | Last modification | View Log | RSS feed
/***************************************************************************
* Copyright (C) 2007 - 2013 by Andreas Theofilu *
* andreas@theosys.at *
* *
* 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 version 3 of the License. *
* *
* 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., *
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
***************************************************************************/
#include <time.h>
#include <string.h>
#include <math.h>
#include <iostream>
#include <qlistview.h>
#include <qdatetime.h>
#include <QXmlReader>
#include <klocale.h>
#include "import.h"
#define FLD_NAME 1
#define FLD_TOTALTIMESECONDS 2
#define FLD_DISTANCEMETERS 3
#define FLD_LATITUDEDEGREES 4
#define FLD_LONGITUDEDEGREES 5
#define FLD_VALUE 6
#define FLD_INTENSITY 7
#define FLD_TIME 8
#define FLD_ALTITUDEMETERS 9
#define FLD_SENSORSTATE 10
#define FLD_STARTTIME 11
#define FLD_AVERAGECADENCE 12
#define FLD_SPORTTYPE 13
#define FLD_VERSIONMAJOR 14
#define FLD_VERSIONMINOR 15
#define FLD_BUILDMAJOR 16
#define FLD_BUILDMINOR 17
#define FLD_TYPE 18
#define FLD_BUILDER 19
#define FLD_LANGID 20
#define FLD_PARTNUMBER 21
#define FLD_CADENCE 22
#define FLD_MAXSPEED 23
#define FLD_CALORIES 24
#define FLD_ID 25
#define FLD_TRIGGERMETHOD 26
#define FLD_TPX 27
#define FLD_UNITID 28
#define FLD_PRODUCTID 29
#define FLD_NOTES 30
#define CON_COURSES 100
#define CON_COURSE 101
#define CON_LAP 102
#define CON_BEGINPOSITION 103
#define CON_ENDPOSITION 104
#define CON_AVERAGEHEARTRATEBPM 105
#define CON_MAXIMUMHEARTRATEBPM 106
#define CON_TRACK 107
#define CON_TRACKPOINT 108
#define CON_POSITION 109
#define CON_HEARTRATEBPM 110
#define CON_AUTHOR 111
#define CON_VERSION 112
#define CON_BUILD 113
#define CON_ACTIVITY 114
#define CON_EXTENSIONS 115
#define CON_CREATOR 116
#define CON_MULTISPORTSESSION 117
#define CON_FIRSTSPORT 118
#define CON_NEXTSPORT 119
#define CON_TRAINING 120
#define CON_QUICKWORKOUTRESULTS 121
#define ATT_CADENCESENSOR 200
#define STOPSTOP 0
KEYS keys[] =
{
/* Fields */
{ FLD_NAME, QString("name") },
{ FLD_TOTALTIMESECONDS, QString("TotalTimeSeconds") },
{ FLD_DISTANCEMETERS, QString("DistanceMeters") },
{ FLD_LATITUDEDEGREES, QString("LatitudeDegrees") },
{ FLD_LONGITUDEDEGREES, QString("LongitudeDegrees") },
{ FLD_VALUE, QString("Value") },
{ FLD_INTENSITY, QString("Intensity") },
{ FLD_TIME, QString("Time") },
{ FLD_ALTITUDEMETERS, QString("AltitudeMeters") },
{ FLD_SENSORSTATE, QString("SensorState") },
{ FLD_STARTTIME, QString("StartTime") },
{ FLD_AVERAGECADENCE, QString("AverageCadence") },
{ FLD_SPORTTYPE, QString("SportType") },
{ FLD_VERSIONMAJOR, QString("VersionMajor") },
{ FLD_VERSIONMINOR, QString("VersionMinor") },
{ FLD_BUILDMAJOR, QString("BuildMajor") },
{ FLD_BUILDMINOR, QString("BuildMinor") },
{ FLD_TYPE, QString("Type") },
{ FLD_BUILDER, QString("Builder") },
{ FLD_LANGID, QString("LangID") },
{ FLD_PARTNUMBER, QString("PartNumber") },
{ FLD_CADENCE, QString("Cadence") },
{ FLD_CALORIES, QString("Calories") },
{ FLD_MAXSPEED, QString("MaximumSpeed") },
{ FLD_ID, QString("Id") },
{ FLD_TRIGGERMETHOD, QString("TriggerMethod") },
{ FLD_TPX, QString("TPX") },
{ FLD_UNITID, QString("UnitId") },
{ FLD_PRODUCTID, QString("ProductID") },
{ FLD_NOTES, QString("Notes") },
/* Attributes */
{ ATT_CADENCESENSOR, QString("CadenceSensor") },
/* Container */
{ CON_COURSES, QString("Courses") },
{ CON_COURSE, QString("Course") },
{ CON_LAP, QString("Lap") },
{ CON_BEGINPOSITION, QString("BeginPosition") },
{ CON_ENDPOSITION, QString("EndPosition") },
{ CON_AVERAGEHEARTRATEBPM, QString("AverageHeartRateBpm") },
{ CON_MAXIMUMHEARTRATEBPM, QString("MaximumHeartRateBpm") },
{ CON_TRACK, QString("Track") },
{ CON_TRACKPOINT, QString("Trackpoint") },
{ CON_POSITION, QString("Position") },
{ CON_HEARTRATEBPM, QString("HeartRateBpm") },
{ CON_AUTHOR, QString("Author") },
{ CON_VERSION, QString("Version") },
{ CON_BUILD, QString("Build") },
{ CON_ACTIVITY, QString("Activity") },
{ CON_EXTENSIONS, QString("Extensions") },
{ CON_CREATOR, QString("Creator") },
{ CON_MULTISPORTSESSION, QString("MultiSportSession") },
{ CON_FIRSTSPORT, QString("FirstSport") },
{ CON_NEXTSPORT, QString("NextSport") },
{ CON_TRAINING, QString("Training") },
{ CON_QUICKWORKOUTRESULTS, QString("QuickWorkoutResults") },
/* Errors */
{ ERR_OK, QString("OK") }, // this is no error
{ ERR_NOFILE, i18n("IMPORT: No file name to parse XML!") }, // but this
{ ERR_TAGS, i18n("IMPORT: More end tags than open tags! Invalid XML file.") },
{ ERR_ALLOCGMN, i18n("IMPORT: Error allocating memory for base Garmin structure.") },
{ ERR_ALLOCLAP, i18n("IMPORT: Error allocating memory for a lap.") },
{ ERR_ALLOCPOINT, i18n("IMPORT: Error allocating memory for a track point.") },
{ ERR_ALLOCRUN, i18n("IMPORT: Error allocating memory for running information.") },
{ ERR_ALLOCLIST, i18n("IMPORT: Error allocating memory for a list node.") },
{ STOPSTOP, QString::null }
};
void gmn_import::Initialize()
{
qfstat = false;
qfopen = false;
__error = 0;
gmn = 0;
list_lap = list_track = 0;
ds = 0;
history = false;
inst = 0;
m_cb = 0;
doCallback = FALSE;
}
gmn_import::gmn_import(const QFile &qfile)
{
Initialize();
file.setFileName(qfile.fileName());
if(file.exists())
qfstat = true;
}
void gmn_import::setFile(const QFile &qfile)
{
if(qfopen)
{
file.close();
qfopen = false;
}
qfstat = false;
file.setFileName(qfile.fileName());
if(file.exists())
qfstat = true;
}
void gmn_import::setFile(const QString &sfile)
{
if(qfopen)
{
file.close();
qfopen = false;
__error = 1;
}
qfstat = false;
file.setFileName(sfile);
if(file.exists())
qfstat = true;
}
/*
* Convert an ISO8601 formated date into garmin epoch.
*/
unsigned int gmn_import::garmin_time(const QString& tstamp)
{
QDateTime dt;
time_t tval;
/*
* tstamp should contain a valid ISO8601 formated date and time stamp.
* We will convert it to a kind of epoch, but with a garmin specific
* offset.
*/
dt = dt.fromString(tstamp, Qt::ISODate);
if(!dt.isValid())
return 0;
tval = dt.toTime_t() - TIME_OFFSET;
return (unsigned int)tval;
}
/*
* This function initializes the XML parser.
*/
bool gmn_import::startDocument()
{
indent = 0;
con.clear();
subCon.clear();
lpos = tpos = oldLPos = 0;
history = fakeLap = false;
first_tpos = 0;
prun = 0;
plap = 0;
return TRUE;
}
/*
* This is called every time a new start element was parsed.
*/
bool gmn_import::startElement(const QString&, const QString&,
const QString& qName,
const QXmlAttributes& att)
{
int i = CON_FIRST;
int index;
while(i <= CON_LAST)
{
if(qName.toLower() == getKey(i).toLower())
{
if(i == CON_MULTISPORTSESSION)
doCallback = TRUE;
if(qName.toLower() == QString("course") || qName.toLower() == QString("activity"))
{
memset(&run, 0, sizeof(D1009));
if(i == CON_ACTIVITY)
{
con = "activity";
history = true;
}
else
con = "course";
run.track_index = 0;
run.first_lap_index = 0;
// Find sport type, if there is one
if((index = att.index(QString("Sport"))) != -1)
{
if(att.value(index).toAscii().toLower() == QString("Running").toLower())
run.sport_type = D1000_running;
else if(att.value(index).toAscii().toLower() == QString("Biking").toLower())
run.sport_type = D1000_biking;
else if(att.value(index).toAscii().toLower() == QString("Other").toLower())
run.sport_type = D1000_other;
else
run.sport_type = D1000_other;
}
else
run.sport_type = D1000_other; // Other
tk = i;
}
if(qName.toLower() == QString("lap"))
{
memset(&lap, 0, sizeof(D1015));
lap.index = lpos;
lap.begin.lat = 0x7fffffff;
lap.begin.lon = 0x7fffffff;
lap.end.lat = 0x7fffffff;
lap.end.lon = 0x7fffffff;
con = "lap";
lpos++;
tk = i;
}
if(con == QString("lap") && qName.toLower() == QString("BeginPosition").toLower())
subCon = qName.toLower();
else if(con == QString("lap") && qName.toLower() == QString("EndPosition").toLower())
subCon = qName.toLower();
else if(con == QString("lap") && qName.toLower() == QString("AverageHeartRateBpm").toLower())
subCon = qName.toLower();
else if(con == QString("lap") && qName.toLower() == QString("MaximumHeartRateBpm").toLower())
subCon = qName.toLower();
if(history && qName.toLower() == QString("track") && con.toLower() == QString("lap"))
{
first_tpos = tpos + 1;
endElement(QString::null, QString::null, QString("Lap"));
indent++;
con = "lap";
fakeLap = true;
}
if(qName.toLower() == QString("trackpoint"))
{
memset(&point, 0, sizeof(D304));
point.alt = 1.0e24;
point.posn.lat = 0x7fffffff;
point.posn.lon = 0x7fffffff;
con = "trackpoint";
tpos++;
tk = i;
}
if(con == QString("trackpoint") && qName.toLower() == QString("position"))
subCon = qName.toLower();
else if(con == QString("trackpoint") && qName.toLower() == QString("heartratebpm"))
subCon = qName.toLower();
}
i++;
}
i = FLD_FIRST;
while(i <= FLD_LAST)
{
if(qName.toLower() == getKey(i).toLower())
tk = i;
i++;
}
indent++;
return TRUE;
}
/*
* This is called every time an element is closed.
*/
bool gmn_import::endElement(const QString&, const QString&, const QString& qName)
{
if(!fakeLap)
indent--;
if(qName.toLower() == QString("lap"))
{
garmin_data *gdt;
garmin_list *l;
if(!fakeLap)
con.clear();
if(history && fakeLap)
{
fakeLap = false;
return TRUE;
}
if(!gmn) /* allocating space for first structure */
{
if((gmn = garmin_alloc_data(data_Dlist)) == NULL)
{
__error = 3;
return FALSE;
}
list = (garmin_list *)gmn->data;
/*
* This is the first data structure. It contains the total
* number of laps and the name of the course, if it was
* named.
*/
if((gdt = garmin_alloc_data(data_D1009)) == NULL)
{
__error = 6;
return FALSE;
}
memmove(gdt->data, &run, sizeof(D1009));
prun = (D1009 *)gdt->data;
if(ds)
ds->garmin_print_data(gdt);
if((l = garmin_list_append(list, gdt)) == NULL)
{
__error = 7;
return FALSE;
}
list = l;
}
if(!list_lap)
{
if((gmn_lap = garmin_alloc_data(data_Dlist)) == NULL)
{
__error = 3;
return FALSE;
}
list_lap = (garmin_list *)gmn_lap->data;
if(garmin_list_append(list, gmn_lap) == NULL)
{
__error = 7;
return FALSE;
}
list = list_lap;
}
else
list = list_lap;
if((gdt = garmin_alloc_data(data_D1015)) == NULL)
{
__error = 4;
return FALSE;
}
memmove(gdt->data, &lap, sizeof(D1015));
plap = (D1015 *)gdt->data;
if(ds)
ds->garmin_print_data(gdt);
if((l = garmin_list_append(list, gdt)) == NULL)
{
__error = 7;
return FALSE;
}
list = l;
}
if(history && qName.toLower() == QString("track"))
first_tpos = 0;
if(qName.toLower() == QString("trackpoint"))
{
garmin_data *gdt;
garmin_list *l;
con.clear();
if(!gmn) /* allocating space for first structure */
{
if((gmn = garmin_alloc_data(data_Dlist)) == NULL)
{
__error = 3;
return FALSE;
}
list = (garmin_list *)gmn->data;
/*
* This is the first data structure. It contains the total
* number of laps and the name of the course, if it was
* named.
*/
if((gdt = garmin_alloc_data(data_D1009)) == NULL)
{
__error = 6;
return FALSE;
}
memmove(gdt->data, &run, sizeof(D1009));
prun = (D1009 *)gdt->data;
if(ds)
ds->garmin_print_data(gdt);
if((l = garmin_list_append(list, gdt)) == NULL)
{
__error = 7;
return FALSE;
}
list = l;
}
if(!list_track)
{
if((gmn_track = garmin_alloc_data(data_Dlist)) == NULL)
{
__error = 3;
return FALSE;
}
list_track = (garmin_list *)gmn_track->data;
if(garmin_list_append(list, gmn_track) == NULL)
{
__error = 7;
return FALSE;
}
list = list_track;
}
else
list = list_track;
if((gdt = garmin_alloc_data(data_D304)) == NULL)
{
__error = 5;
return FALSE;
}
memmove(gdt->data, &point, sizeof(D304));
if(ds)
ds->garmin_print_data(gdt);
if((l = garmin_list_append(list, gdt)) == NULL)
{
__error = 7;
return FALSE;
}
list = l;
}
if(qName.toLower() == QString("course") || qName.toLower() == QString("activity"))
{
con.clear();
run.track_index = tpos - 1;
run.last_lap_index = lpos - 1;
memmove(prun, &run, sizeof(D1009));
if (doCallback && m_cb)
{
m_cb->cbiCallbackFunction((void *)gmn);
subCon.clear();
lpos = tpos = oldLPos = 0;
fakeLap = false;
first_tpos = 0;
garmin_free_data(gmn);
gmn = 0;
}
else if (doCallback)
std::cerr << "No callback function was defined! Can't save single track!" << std::endl;
}
if (qName.toLower() == QString("activities"))
{
con.clear();
subCon.clear();
history = false;
}
if(qName.toLower() == QString("beginposition") || qName.toLower() == QString("endposition") ||
qName.toLower() == QString("averageheartratebpm") || qName.toLower() == QString("maximumheartratebpm"))
subCon.clear();
if(qName.toLower() == QString("heartratebpm") || qName.toLower() == QString("position"))
subCon.clear();
if(indent < 0)
{
__error = 2;
return FALSE;
}
return TRUE;
}
/*
* The reader calls this function when it has parsed a chunk of character data
* - either normal character data or character data inside a CDATA section.
*/
bool gmn_import::characters(const QString& ch)
{
if(con == QString("course"))
{
if(tk == FLD_NAME)
{
strncpy(run.workout.name, ch.toAscii(), 15);
run.workout.name[15] = 0;
tk = 0;
}
}
if(history && con == QString("activity"))
{
if(tk == FLD_ID)
{
strncpy(run.workout.name, ch.toAscii(), 15);
run.workout.name[15] = 0;
tk = 0;
}
}
if(con == QString("lap"))
{
if(tk == FLD_DISTANCEMETERS)
{
lap.total_dist = (float32)ch.toFloat();
tk = 0;
}
if(tk == FLD_INTENSITY)
{
lap.intensity = (ch.toLower() == QString("activ")) ? 0 : 1;
tk = 0;
}
if(tk == FLD_STARTTIME)
{
lap.start_time = garmin_time(ch);
tk = 0;
}
if(tk == FLD_TOTALTIMESECONDS)
{
lap.total_time = (uint32)(ch.toDouble() * 100.0);
tk = 0;
}
if(tk == FLD_CADENCE)
{
lap.avg_cadence = (uint8)ch.toUInt();
tk = 0;
}
if(tk == FLD_CALORIES)
{
lap.calories = (uint16)ch.toUInt();
tk = 0;
}
if(tk == FLD_MAXSPEED)
{
lap.max_speed = (float32)ch.toFloat();
tk = 0;
}
if(tk == FLD_TRIGGERMETHOD)
{
if(ch.toLower() == QString("manual"))
lap.trigger_method = D1011_manual;
else if(ch.toLower() == QString("distance"))
lap.trigger_method = D1011_distance;
else if(ch.toLower() == QString("location"))
lap.trigger_method = D1011_location;
else if(ch.toLower() == QString("time"))
lap.trigger_method = D1011_time;
else if(ch.toLower() == QString("HeartRate"))
lap.trigger_method = D1011_heart_rate;
}
if(subCon.toLower() == QString("BeginPosition").toLower())
{
if(tk == FLD_LATITUDEDEGREES)
{
lap.begin.lat = (ch.toDouble() == 180.0) ? 0x7fffffff : (sint32)DEG2SEMI(ch.toDouble());
tk = 0;
}
if(tk == FLD_LONGITUDEDEGREES)
{
lap.begin.lon = (ch.toDouble() == 180.0) ? 0x7fffffff : (sint32)DEG2SEMI(ch.toDouble());
tk = 0;
}
}
else if(subCon.toLower() == QString("EndPosition").toLower())
{
if(tk == FLD_LATITUDEDEGREES)
{
lap.end.lat = (ch.toDouble() == 180.0) ? 0x7fffffff : (sint32)DEG2SEMI(ch.toDouble());
tk = 0;
}
if(tk == FLD_LONGITUDEDEGREES)
{
lap.end.lon = (ch.toDouble() == 180.0) ? 0x7fffffff : (sint32)DEG2SEMI(ch.toDouble());
tk = 0;
}
}
else if(subCon.toLower() == QString("AverageHeartRateBpm").toLower())
{
if(tk == FLD_VALUE)
{
lap.avg_heart_rate = (uint8)ch.toInt();
tk = 0;
}
}
else if(subCon.toLower() == QString("MaximumHeartRateBpm").toLower())
{
if(tk == FLD_VALUE)
{
lap.max_heart_rate = (uint8)ch.toInt();
tk = 0;
}
}
}
if(con == QString("trackpoint"))
{
if(tk == FLD_TIME)
{
point.time = garmin_time(ch);
if(history && first_tpos == tpos && plap)
plap->start_time = point.time;
tk = 0;
}
if(tk == FLD_ALTITUDEMETERS)
{
point.alt = (ch.toFloat() >= 1.0e24) ? 1.0e24 : (float32)ch.toFloat();
tk = 0;
}
if(tk == FLD_DISTANCEMETERS)
{
point.distance = (ch.toFloat() >= 1.0e24) ? 1.0e24 : (float32)ch.toFloat();
tk = 0;
}
if(tk == FLD_SENSORSTATE)
{
point.sensor = (ch.toLower() == QString("absent")) ? false : true;
tk = 0;
}
if(subCon.toLower() == QString("position"))
{
if(tk == FLD_LATITUDEDEGREES)
{
point.posn.lat = (ch.toDouble() == 180.0) ? 0x7fffffff : (sint32)DEG2SEMI(ch.toDouble());
if(history && tpos == first_tpos && plap) // Add mising information to first lap
plap->begin.lat = point.posn.lat;
else if(history && point.posn.lat != 0x7fffffff && plap)
plap->end.lat = point.posn.lat;
tk = 0;
}
if(tk == FLD_LONGITUDEDEGREES)
{
point.posn.lon = (ch.toDouble() == 180.0) ? 0x7fffffff : (sint32)DEG2SEMI(ch.toDouble());
if(history && tpos == first_tpos && plap) // Add mising information to first lap
plap->begin.lon = point.posn.lon;
else if(history && point.posn.lon != 0x7fffffff && plap)
plap->end.lat = point.posn.lon;
tk = 0;
}
}
else if(subCon.toLower() == QString("heartratebpm"))
{
if(tk == FLD_VALUE)
{
point.heart_rate = (unsigned char)ch.toInt();
tk = 0;
}
}
}
return TRUE;
}
/*
* The following function reads a Garmin HST file, parses the XML
* content and creates the gmn files. If there already exists a file,
* it's not overwritten.
*/
int gmn_import::import()
{
QXmlSimpleReader reader;
if(!qfstat)
return 1;
QXmlInputSource source(&file);
reader.setContentHandler(this);
reader.parse(source);
return 0;
}
QString gmn_import::getKey(int pos)
{
int i = 0;
while(keys[i].id > 0)
{
if(keys[i].id == pos)
return keys[i].name;
i++;
}
return QString::null;
}
QString gmn_import::getError(int err)
{
if(err > eMax || err < 1)
return 0;
return getKey(ERR_FIRST + err);
}
QString gmn_import::getError()
{
return getKey(ERR_FIRST + __error);
}