Subversion Repositories public

Rev

Rev 232 | Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | RSS feed

/***************************************************************************
 *   Copyright (C) 2007 - 2009 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 <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;
}

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 (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));
           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);
}