Skip to content

Commit

Permalink
Add IOF XML import, 2 CSV imports (prototype)
Browse files Browse the repository at this point in the history
- IOF XML import (now only single race support), from ORIS & IOF Eventor, imports: clubs, registration, classes, entries
- CSV imports (key is Cze registration or IOF ID), supported columns : SI, class, bib, start time
  • Loading branch information
arnost00 committed Jul 8, 2023
1 parent 21112eb commit 81aa8f1
Show file tree
Hide file tree
Showing 7 changed files with 887 additions and 2 deletions.
16 changes: 16 additions & 0 deletions quickevent/app/quickevent/plugins/Oris/src/orisplugin.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#include "orisplugin.h"
#include "orisimporter.h"
#include "txtimporter.h"
#include "xmlimporter.h"

#include <qf/qmlwidgets/framework/mainwindow.h>
#include <qf/qmlwidgets/menubar.h>
Expand All @@ -22,6 +23,7 @@ OrisPlugin::OrisPlugin(QObject *parent)
//setPersistentSettingsId("Oris");
m_orisImporter = new OrisImporter(this);
m_txtImporter = new TxtImporter(this);
m_xmlImporter = new XmlImporter(this);

connect(this, &OrisPlugin::installed, this, &OrisPlugin::onInstalled);
}
Expand Down Expand Up @@ -91,6 +93,20 @@ void OrisPlugin::onInstalled()
qfw::Action *a = act_import_txt->addActionInto("competitorsRanking", tr("&Ranking CSV (ORIS format)"));
connect(a, &qfw::Action::triggered, m_txtImporter, &TxtImporter::importRankingCsv);
}
act_import->addSeparatorInto();
{
qfw::Action *a = act_import->addActionInto("iofXmlEntry", tr("Import IOF XML 3.0"));
connect(a, &qfw::Action::triggered, m_xmlImporter, &XmlImporter::importXML30);
}
{
qfw::Action *a = act_import->addActionInto("runsCzeCSV", tr("Import CSV (key is CZE registration)"));
connect(a, &qfw::Action::triggered, m_txtImporter, &TxtImporter::importRunsCzeCSV);
}
{
qfw::Action *a = act_import->addActionInto("runsIofCSV", tr("Import CSV (key is Iof ID)"));
connect(a, &qfw::Action::triggered, m_txtImporter, &TxtImporter::importRunsIofCSV);
}

}

}
2 changes: 2 additions & 0 deletions quickevent/app/quickevent/plugins/Oris/src/orisplugin.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

class OrisImporter;
class TxtImporter;
class XmlImporter;

namespace Oris {

Expand All @@ -19,6 +20,7 @@ class OrisPlugin : public qf::qmlwidgets::framework::Plugin//, public qf::qmlwid
private:
OrisImporter *m_orisImporter = nullptr;
TxtImporter *m_txtImporter = nullptr;
XmlImporter *m_xmlImporter = nullptr;
};

}
6 changes: 4 additions & 2 deletions quickevent/app/quickevent/plugins/Oris/src/src.pri
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@ HEADERS += \
$$PWD/orisimporter.h \
$$PWD/chooseoriseventdialog.h \
$$PWD/orisplugin.h \
$$PWD/txtimporter.h
$$PWD/txtimporter.h \
$$PWD/xmlimporter.h

SOURCES += \
$$PWD/orisplugin.cpp \
$$PWD/orisimporter.cpp \
$$PWD/chooseoriseventdialog.cpp \
$$PWD/txtimporter.cpp
$$PWD/txtimporter.cpp \
$$PWD/xmlimporter.cpp

FORMS += \
$$PWD/chooseoriseventdialog.ui
229 changes: 229 additions & 0 deletions quickevent/app/quickevent/plugins/Oris/src/txtimporter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -238,3 +238,232 @@ void TxtImporter::importParsedCsv(const QList<QVariantList> &csv)
getPlugin<EventPlugin>()->emitReloadDataRequest();
getPlugin<EventPlugin>()->emitDbEvent(Event::EventPlugin::DBEVENT_COMPETITOR_COUNTS_CHANGED);
}

void TxtImporter::importRunsCzeCSV()
{
qf::qmlwidgets::framework::MainWindow *fwk = qf::qmlwidgets::framework::MainWindow::frameWork();
qf::qmlwidgets::dialogs::MessageBox mbx(fwk);
mbx.setIcon(QMessageBox::Information);
mbx.setText(tr("Import comma separated values UTF8 text files with header, Separator is semicolon(;)<br/>Uupdates only existing runners (key is Czech registration)."));
mbx.setInformativeText(tr("Each row should have following columns: "
"<ol>"
"<li>Registration <i>- key</i></li>"
"<li>SI</li>"
"<li>Class</li>"
"<li>Bib</li>"
"<li>Start time <i>(in format mmm.ss from zero time)</i></li>"
"</ol> Only first column is mandatory, others can be empty."));
mbx.setDoNotShowAgainPersistentKey("importRunsCzeCSV");
int res = mbx.exec();
if(res != QMessageBox::Ok)
return;
QString fn = qfd::FileDialog::getOpenFileName(fwk, tr("Open file"), QString(), tr("CSV files (*.csv *.txt)"));
if(fn.isEmpty())
return;

QMap<QString, int> classes_map; // classes.name->classes.id
qf::core::sql::Query q;
q.exec("SELECT id, name FROM classes", qf::core::Exception::Throw);
while(q.next()) {
classes_map[q.value(1).toString()] = q.value(0).toInt();
}

try {
QFile f(fn);
if(!f.open(QFile::ReadOnly))
QF_EXCEPTION(tr("Cannot open file '%1' for reading.").arg(fn));
QTextStream ts(&f);
qf::core::utils::CSVReader reader(&ts);
reader.setSeparator(';');
enum {ColRegistration = 0, ColSI, ColClass, ColBib, ColStarttime};

qfLogScope("importRunsCzeCSV");
qf::core::sql::Transaction transaction;
qf::core::sql::Query q1,q2,q3,q4;
q.prepare("SELECT id FROM competitors WHERE registration=:registration", qf::core::Exception::Throw);
q1.prepare("UPDATE competitors SET siId=:si WHERE registration=:registration", qf::core::Exception::Throw);
q2.prepare("UPDATE competitors SET classId=:class WHERE registration=:registration", qf::core::Exception::Throw);
q3.prepare("UPDATE competitors SET startNumber=:bib WHERE registration=:registration", qf::core::Exception::Throw);
q4.prepare("UPDATE runs SET startTimeMs=:starttime WHERE competitorId=:id", qf::core::Exception::Throw);

int n = 0;
while (!ts.atEnd()) {
QStringList line = reader.readCSVLineSplitted();
if(line.count() <= 1)
QF_EXCEPTION(tr("Fields separation error, invalid CSV format, Error reading CSV line: [%1]").arg(line.join(';').mid(0, 100)));
if(n++ == 0) // skip column names
continue;
QString registration = line.value(ColRegistration).trimmed();
if(registration.isEmpty()) {
QF_EXCEPTION(tr("Error reading CSV line: [%1]").arg(line.join(';')));
}
q.bindValue(":registration", registration);
q.exec(qf::core::Exception::Throw);
if(q.next()) {
// if registration found in db - start update data
int comp_id = q.value(0).toInt();

int si = line.value(ColSI).toInt();
QString class_name = line.value(ColClass).trimmed();
int bib = line.value(ColBib).toInt();
QString starttime = line.value(ColStarttime).trimmed();

qfDebug() << registration << "-> (" << si << "," << class_name << "," << bib << "," << starttime << ")";
if (si != 0) {
q1.bindValue(":si", si);
q1.bindValue(":registration", registration);
q1.exec(qf::core::Exception::Throw);
}
if (!class_name.isEmpty()) {
int class_id = classes_map.value(class_name);
if(class_id == 0)
QF_EXCEPTION(tr("Undefined class name: '%1'").arg(class_name));

q2.bindValue(":class", class_id);
q2.bindValue(":registration", registration);
q2.exec(qf::core::Exception::Throw);
}
if (bib != 0) {
q3.bindValue(":bib", bib);
q3.bindValue(":registration", registration);
q3.exec(qf::core::Exception::Throw);
}
if (!starttime.isEmpty()) {
bool ok;
double dbl_time = starttime.toDouble(&ok);
if(!ok) {
qfWarning() << "Cannot convert" << starttime << "to double.";
continue;
}
int st_time = ((int)dbl_time) * 60 + (((int)(dbl_time * 100)) % 100);
st_time *= 1000;

q4.bindValue(":starttime", st_time);
q4.bindValue(":id", comp_id);
q4.exec(qf::core::Exception::Throw);
}
}
else
qfWarning() << registration << "not found in database.";
}
transaction.commit();
qfInfo() << fn << n << "lines imported";
}
catch (const qf::core::Exception &e) {
qf::qmlwidgets::dialogs::MessageBox::showException(fwk, e);
}
}
void TxtImporter::importRunsIofCSV()
{
qf::qmlwidgets::framework::MainWindow *fwk = qf::qmlwidgets::framework::MainWindow::frameWork();
qf::qmlwidgets::dialogs::MessageBox mbx(fwk);
mbx.setIcon(QMessageBox::Information);
mbx.setText(tr("Import comma separated values UTF8 text files with header, Separator is semicolon(;)<br/>Updates only existing runners (key is IOF ID)."));
mbx.setInformativeText(tr("Each row should have following columns: "
"<ol>"
"<li>IOF ID <i>- key</i></li>"
"<li>SI</li>"
"<li>Class</li>"
"<li>Bib</li>"
"<li>Start time <i>(in format mmm.ss from zero time)</i></li>"
"</ol> Only first column is mandatory, others can be empty."));
mbx.setDoNotShowAgainPersistentKey("importRunsIofCSV");
int res = mbx.exec();
if(res != QMessageBox::Ok)
return;
QString fn = qfd::FileDialog::getOpenFileName(fwk, tr("Open file"), QString(), tr("CSV files (*.csv *.txt)"));
if(fn.isEmpty())
return;

QMap<QString, int> classes_map; // classes.name->classes.id
qf::core::sql::Query q;
q.exec("SELECT id, name FROM classes", qf::core::Exception::Throw);
while(q.next()) {
classes_map[q.value(1).toString()] = q.value(0).toInt();
}

try {
QFile f(fn);
if(!f.open(QFile::ReadOnly))
QF_EXCEPTION(tr("Cannot open file '%1' for reading.").arg(fn));
QTextStream ts(&f);
qf::core::utils::CSVReader reader(&ts);
reader.setSeparator(';');
enum {ColIofId = 0, ColSI, ColClass, ColBib, ColStarttime};

qfLogScope("importRunsCzeCSV");
qf::core::sql::Transaction transaction;
qf::core::sql::Query q1,q2,q3,q4;
q.prepare("SELECT id FROM competitors WHERE iofId=:iofId", qf::core::Exception::Throw);
q1.prepare("UPDATE competitors SET siId=:si WHERE iofId=:iofId", qf::core::Exception::Throw);
q2.prepare("UPDATE competitors SET classId=:class WHERE iofId=:iofId", qf::core::Exception::Throw);
q3.prepare("UPDATE competitors SET startNumber=:bib WHERE iofId=:iofId", qf::core::Exception::Throw);
q4.prepare("UPDATE runs SET startTimeMs=:starttime WHERE competitorId=:id", qf::core::Exception::Throw);

int n = 0;
while (!ts.atEnd()) {
QStringList line = reader.readCSVLineSplitted();
if(line.count() <= 1)
QF_EXCEPTION(tr("Fields separation error, invalid CSV format, Error reading CSV line: [%1]").arg(line.join(';').mid(0, 100)));
if(n++ == 0) // skip column names
continue;
int iof_id = line.value(ColIofId).toInt();
if(iof_id == 0) {
QF_EXCEPTION(tr("Error reading CSV line: [%1]").arg(line.join(';')));
}
q.bindValue(":iofId", iof_id);
q.exec(qf::core::Exception::Throw);
if(q.next()) {
// if registration found in db - start update data
int comp_id = q.value(0).toInt();

int si = line.value(ColSI).toInt();
QString class_name = line.value(ColClass).trimmed();
int bib = line.value(ColBib).toInt();
QString starttime = line.value(ColStarttime).trimmed();

qfDebug() << iof_id << "-> (" << si << "," << class_name << "," << bib << "," << starttime << ")";
if (si != 0) {
q1.bindValue(":si", si);
q1.bindValue(":iofId", iof_id);
q1.exec(qf::core::Exception::Throw);
}
if (!class_name.isEmpty()) {
int class_id = classes_map.value(class_name);
if(class_id == 0)
QF_EXCEPTION(tr("Undefined class name: '%1'").arg(class_name));

q2.bindValue(":class", class_id);
q2.bindValue(":iofId", iof_id);
q2.exec(qf::core::Exception::Throw);
}
if (bib != 0) {
q3.bindValue(":bib", bib);
q3.bindValue(":iofId", iof_id);
q3.exec(qf::core::Exception::Throw);
}
if (!starttime.isEmpty()) {
bool ok;
double dbl_time = starttime.toDouble(&ok);
if(!ok) {
qfWarning() << "Cannot convert" << starttime << "to double.";
continue;
}
int st_time = ((int)dbl_time) * 60 + (((int)(dbl_time * 100)) % 100);
st_time *= 1000;

q4.bindValue(":starttime", st_time);
q4.bindValue(":id", comp_id);
q4.exec(qf::core::Exception::Throw);
}
}
else
qfWarning() << iof_id << "not found in database.";
}
transaction.commit();
qfInfo() << fn << n << "lines imported";
}
catch (const qf::core::Exception &e) {
qf::qmlwidgets::dialogs::MessageBox::showException(fwk, e);
}
}
2 changes: 2 additions & 0 deletions quickevent/app/quickevent/plugins/Oris/src/txtimporter.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ class TxtImporter : public QObject
Q_INVOKABLE void importCompetitorsCSOS();
Q_INVOKABLE void importCompetitorsCSV();
Q_INVOKABLE void importRankingCsv();
Q_INVOKABLE void importRunsCzeCSV();
Q_INVOKABLE void importRunsIofCSV();
protected:
void importParsedCsv(const QList<QVariantList> &csv);
};
Expand Down
Loading

0 comments on commit 81aa8f1

Please sign in to comment.