diff --git a/quickevent/app/quickevent/plugins/Oris/src/orisplugin.cpp b/quickevent/app/quickevent/plugins/Oris/src/orisplugin.cpp index 113475539..520b8eeb9 100644 --- a/quickevent/app/quickevent/plugins/Oris/src/orisplugin.cpp +++ b/quickevent/app/quickevent/plugins/Oris/src/orisplugin.cpp @@ -1,6 +1,7 @@ #include "orisplugin.h" #include "orisimporter.h" #include "txtimporter.h" +#include "xmlimporter.h" #include #include @@ -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); } @@ -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); + } + } } diff --git a/quickevent/app/quickevent/plugins/Oris/src/orisplugin.h b/quickevent/app/quickevent/plugins/Oris/src/orisplugin.h index 40682b920..abf984881 100644 --- a/quickevent/app/quickevent/plugins/Oris/src/orisplugin.h +++ b/quickevent/app/quickevent/plugins/Oris/src/orisplugin.h @@ -5,6 +5,7 @@ class OrisImporter; class TxtImporter; +class XmlImporter; namespace Oris { @@ -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; }; } diff --git a/quickevent/app/quickevent/plugins/Oris/src/src.pri b/quickevent/app/quickevent/plugins/Oris/src/src.pri index 3f99577b1..ab48bf505 100644 --- a/quickevent/app/quickevent/plugins/Oris/src/src.pri +++ b/quickevent/app/quickevent/plugins/Oris/src/src.pri @@ -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 diff --git a/quickevent/app/quickevent/plugins/Oris/src/txtimporter.cpp b/quickevent/app/quickevent/plugins/Oris/src/txtimporter.cpp index 6be30d4a9..1ef1b1dd0 100644 --- a/quickevent/app/quickevent/plugins/Oris/src/txtimporter.cpp +++ b/quickevent/app/quickevent/plugins/Oris/src/txtimporter.cpp @@ -238,3 +238,232 @@ void TxtImporter::importParsedCsv(const QList &csv) getPlugin()->emitReloadDataRequest(); getPlugin()->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(;)
Uupdates only existing runners (key is Czech registration).")); + mbx.setInformativeText(tr("Each row should have following columns: " + "
    " + "
  1. Registration - key
  2. " + "
  3. SI
  4. " + "
  5. Class
  6. " + "
  7. Bib
  8. " + "
  9. Start time (in format mmm.ss from zero time)
  10. " + "
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 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(;)
Updates only existing runners (key is IOF ID).")); + mbx.setInformativeText(tr("Each row should have following columns: " + "
    " + "
  1. IOF ID - key
  2. " + "
  3. SI
  4. " + "
  5. Class
  6. " + "
  7. Bib
  8. " + "
  9. Start time (in format mmm.ss from zero time)
  10. " + "
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 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); + } +} diff --git a/quickevent/app/quickevent/plugins/Oris/src/txtimporter.h b/quickevent/app/quickevent/plugins/Oris/src/txtimporter.h index c62c5d625..eb10736a2 100644 --- a/quickevent/app/quickevent/plugins/Oris/src/txtimporter.h +++ b/quickevent/app/quickevent/plugins/Oris/src/txtimporter.h @@ -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 &csv); }; diff --git a/quickevent/app/quickevent/plugins/Oris/src/xmlimporter.cpp b/quickevent/app/quickevent/plugins/Oris/src/xmlimporter.cpp new file mode 100644 index 000000000..ba98192f1 --- /dev/null +++ b/quickevent/app/quickevent/plugins/Oris/src/xmlimporter.cpp @@ -0,0 +1,570 @@ +#include "xmlimporter.h" + +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace qfw = qf::qmlwidgets; +namespace qfd = qf::qmlwidgets::dialogs; +using qf::qmlwidgets::framework::getPlugin; +using Event::EventPlugin; + +XmlImporter::XmlImporter(QObject *parent) + : QObject(parent) +{ +} + +bool XmlImporter::readPersonNode (SPerson &s, QXmlStreamReader &reader, const XmlCreators creator) +{ + bool result = false; + while(reader.readNextStartElement()) { + if (reader.name() == "Person") { + while(reader.readNextStartElement()) { + if (reader.name() == "Id") { + if (reader.attributes().hasAttribute("type") && reader.attributes().value("type").toString() == "IOF") + s.iofId = reader.readElementText().toInt(); + else if (reader.attributes().hasAttribute("type") && reader.attributes().value("type").toString() == "CZE") + s.regCz = reader.readElementText(); + else if (reader.attributes().hasAttribute("type") && reader.attributes().value("type").toString() == "ORIS") + s.orisId = reader.readElementText().toInt(); + else + reader.skipCurrentElement(); + result = (s.iofId != -1 || !s.regCz.isEmpty()); + } + else if (reader.name() == "Nationality") { + if (reader.attributes().hasAttribute("code")) + s.nationalityCode = reader.attributes().value("code").toString(); + s.nationalityName = reader.readElementText(); + } + else if (reader.name() == "Name") { + while(reader.readNextStartElement()) { + if (reader.name() == "Family") + s.nameFamily = reader.readElementText(); + else if (reader.name() == "Given") + s.nameGiven = reader.readElementText(); + else + reader.skipCurrentElement(); + } + } + else + reader.skipCurrentElement(); + } + } + else if (reader.name() == "Organisation" && reader.attributes().hasAttribute("type")) { + if (reader.attributes().value("type").toString() == "NationalFederation") { + while(reader.readNextStartElement()) { + if (reader.name() == "Country") { + if (reader.attributes().hasAttribute("code")) + s.country_abbr = reader.attributes().value("code").toString(); + reader.readElementText(); // need to be here, for next element + } + else + reader.skipCurrentElement(); + } + } + else if (reader.attributes().value("type").toString() == "Club") { + while(reader.readNextStartElement()) { + if (reader.name() == "Name") { + if (reader.attributes().hasAttribute("code")) + s.clubCode = reader.attributes().value("code").toString(); + s.clubName = reader.readElementText(); + } + else if (reader.name() == "Id" && reader.attributes().hasAttribute("type") && reader.attributes().value("type").toString() == "IOF") + s.clubIdIof = reader.readElementText().toInt(); + else + reader.skipCurrentElement(); + } + } + else + reader.skipCurrentElement(); + } + else if (reader.name() == "ControlCard" && reader.attributes().hasAttribute("punchingSystem") && reader.attributes().value("punchingSystem").toString() == "SI") { + s.siNumber = reader.readElementText().toInt(); + } + else if (reader.name() == "Class") { + while(reader.readNextStartElement()) { + if (reader.name() == "Name") + s.className = reader.readElementText(); + else if (reader.name() == "ShortName") + s.classNameShort = reader.readElementText(); + else + reader.skipCurrentElement(); + } + } + else if (reader.name() == "RaceNumber") + s.enterRaces.insert(reader.readElementText().toInt()); + else if (reader.name() == "Leg") + s.leg = reader.readElementText().toInt(); + else if (reader.name() == "Extensions") { + while(reader.readNextStartElement()) { + if (reader.name() == "Note") + s.noteOris = reader.readElementText(); + else + reader.skipCurrentElement(); + } + } + else + reader.skipCurrentElement(); + } + +// s.country_abbr = (!country_abbr.isEmpty()) ? country_abbr : nationality_abbr; + return result; +} + + +bool XmlImporter::importEntries(QXmlStreamReader &reader, const XmlCreators creator) +{ + QMap 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(); + } + if (classes_map.size() == 0){ + qfError() << "Undefined classes for entries"; + return false; + } + + QMap clubs_map; + if (creator == XmlCreators::Eventor) { + q.exec("SELECT abbr, importId FROM clubs", qf::core::Exception::Throw); + while(q.next()) { + clubs_map[q.value(1).toInt()] = q.value(0).toString(); + } + if (clubs_map.size() == 0){ + qfError() << "Undefined clubs for entries"; + return false; + } + } + + int selected_race = 0; // if entries has more races in (defined in Event), selected race + // load from XML & insert to db + int items_processed = 0; + qf::core::sql::Transaction transaction; + while(reader.readNextStartElement()) { + if(reader.name() == "PersonEntry") { + SPerson person; + if (readPersonNode(person,reader, creator)) { + + if (selected_race != 0 && person.enterRaces.size() > 0 && !person.enterRaces.contains(selected_race)) { + qfInfo() << "Skip entry" << person.nameGiven << person.nameFamily << "- not participate in this race"; + } + else { + Competitors::CompetitorDocument doc; + doc.setEmitDbEventsOnSave(false); + doc.loadForInsert(); + int class_id = classes_map.value(person.className); + if(class_id == 0) { + class_id = classes_map.value(person.classNameShort); + if(class_id == 0) { + qfError() << "Undefined class name:" << person.className << "for runner:" << person.nameGiven << " " << person.nameFamily; + transaction.rollback(); + return false; + } + } + doc.setValue("classId", class_id); + if(person.siNumber > 0) { + doc.setSiid(person.siNumber); + } + doc.setValue("firstName", person.nameGiven); + doc.setValue("lastName", person.nameFamily); + if (creator == XmlCreators::Oris) { + doc.setValue("registration", person.regCz); + doc.setValue("note", person.noteOris); + doc.setValue("importId",person.orisId); + } else if (creator == XmlCreators::Eventor) { + QString club_abbr = clubs_map[person.clubIdIof]; + if (!club_abbr.isEmpty()) + doc.setValue("registration",club_abbr); + doc.setValue("country",person.nationalityName); + doc.setValue("club",person.clubName); + doc.setValue("importId",person.iofId); + } + doc.setValue("iofId", person.iofId); + doc.save(); + items_processed++; + } + } + else + qfWarning() << "Failed to read runner entry on pos" << items_processed; + } + else if (reader.name() == "Event") { + QMap races; + while(reader.readNextStartElement()) { + if (reader.name() == "Race") { + QString name, date; + int number; + while(reader.readNextStartElement()) { + if (reader.name() == "Name") + name = reader.readElementText(); + else if (reader.name() == "RaceNumber") + number = reader.readElementText().toInt(); + else if (reader.name() == "StartTime") { + while(reader.readNextStartElement()) { + if (reader.name() == "Date") + date = reader.readElementText(); + else + reader.skipCurrentElement(); + } + } + else + reader.skipCurrentElement(); + } + QString race = QString("[%1] %2 %3").arg(number).arg(name).arg(date); + races[race] = number; + } + else + reader.skipCurrentElement(); + } + if (races.size() > 0) { + // when defined some races ... + if (races.size() == 1) + selected_race = races.first(); + else { + QStringList items; + auto it = races.begin(); + while (it != races.end()) { + items << it.key(); + it++; + } + bool ok; + QString item = QInputDialog::getItem(qf::qmlwidgets::framework::MainWindow::frameWork(), tr("Select which race import)"), + tr("Races:"), items, 0, false, &ok); + if (ok && !item.isEmpty()) + selected_race = races[item]; + else + return false; + } + } + } + else + reader.skipCurrentElement(); + } + + if (items_processed > 0) { + transaction.commit(); + qfInfo() << "Imported entry for" << items_processed << "runners."; + getPlugin()->emitReloadDataRequest(); + getPlugin()->emitDbEvent(Event::EventPlugin::DBEVENT_COMPETITOR_COUNTS_CHANGED); + } + + return items_processed > 0; +} + +bool XmlImporter::importStartlist(QXmlStreamReader &reader, const XmlCreators creator) +{ + // load from XML & insert to db + int items_processed = 0; + qf::core::sql::Transaction transaction; + while(reader.readNextStartElement()) { + if(reader.name() == "PersonEntry") { + SPerson person; + if (readPersonNode(person,reader, creator)) { + //insert to db + items_processed++; + } + } + else + reader.skipCurrentElement(); + } + + if (items_processed > 0) { + transaction.commit(); + qfInfo() << "Imported entry for" << items_processed << "runners."; + getPlugin()->emitReloadDataRequest(); + getPlugin()->emitDbEvent(Event::EventPlugin::DBEVENT_COMPETITOR_COUNTS_CHANGED); + } + + return items_processed > 0; +} + +bool XmlImporter::importClasses(QXmlStreamReader &reader, const XmlCreators creator) +{ + // load data from XML + qf::core::sql::Transaction transaction; + int items_processed = 0; + while(reader.readNextStartElement()) { + if(reader.name() == "Class") { + QString class_name; + int class_id = 0; + while(reader.readNextStartElement()) { + if (reader.name() == "Name") + class_name = reader.readElementText(); + else if (reader.name() == "Id" && reader.attributes().hasAttribute("type") && reader.attributes().value("type").toString() == "ORIS" && creator == XmlCreators::Oris) + class_id = reader.readElementText().toInt(); + else if (reader.name() == "Id" && reader.attributes().hasAttribute("type") && reader.attributes().value("type").toString() == "IOF" && creator == XmlCreators::Eventor) + class_id = reader.readElementText().toInt(); + else + reader.skipCurrentElement(); + } + + // insert to db + if (class_id != 0 && !class_name.isEmpty()) { + Classes::ClassDocument doc; + qfInfo() << "adding class id:" << class_id << "name:" << class_name; + doc.loadForInsert(); + doc.setValue("id", class_id); + doc.setValue("name", class_name); + doc.save(); + items_processed++; + } + } + else + reader.skipCurrentElement(); + } + + if (items_processed > 0) { + transaction.commit(); + qfInfo() << "Imported" << items_processed << "classes."; +// getPlugin()->emitReloadDataRequest(); -- not exists + } + + return (items_processed > 0); +} + +QString XmlImporter::GenFakeCzClubAbbr(QString country) +{ + if (country.isEmpty()) + return ""; + QString c = QString(country[0]); + int pos = fakeCzClubMap[c]; + fakeCzClubMap[c]++; + QString result = QString("%1%2").arg(c).arg(pos,2,36,QLatin1Char('0')).toUpper(); + if (result == country) + return GenFakeCzClubAbbr(country); + else + return result; +} + + +bool XmlImporter::importClubs(QXmlStreamReader &reader, const XmlCreators creator) +{ + // load data from XML + qf::core::sql::Transaction transaction; + qf::core::sql::Query q; + q.exec("DELETE FROM clubs", qf::core::Exception::Throw); + q.prepare("INSERT INTO clubs (name, abbr, importId) VALUES (:name, :abbr, :importId)", qf::core::Exception::Throw); + int items_processed = 0; + if (creator == XmlCreators::Eventor) + fakeCzClubMap.clear(); + while(reader.readNextStartElement()) { + if(reader.name() == "Organisation" && reader.attributes().hasAttribute("type")) { + QString name; + + // ORIS + QString abbr_cz; + QString code_cz; + int id_cz = -1; + // Eventor + int id_iof = -1; + QString country; + QString country_code; + bool federation_iof = false; + + if (reader.attributes().value("type").toString() == "NationalFederation") { + federation_iof = true; + while(reader.readNextStartElement()) { + if (reader.name() == "Id" && reader.attributes().hasAttribute("type")&& reader.attributes().value("type").toString() == "IOF") + id_iof = reader.readElementText().toInt(); + else if (reader.name() == "Name") + name = reader.readElementText(); + else if (reader.name() == "Country") { + if (reader.attributes().hasAttribute("code")) + country_code = reader.attributes().value("code").toString(); + country = reader.readElementText(); + } + else + reader.skipCurrentElement(); + } + } + else if (reader.attributes().value("type").toString() == "Club" || reader.attributes().value("type").toString() == "Other") { + while(reader.readNextStartElement()) { + if (reader.name() == "Id" && reader.attributes().hasAttribute("type")&& reader.attributes().value("type").toString() == "ORIS") + id_cz = reader.readElementText().toInt(); + else if (reader.name() == "Id" && reader.attributes().hasAttribute("type")&& reader.attributes().value("type").toString() == "IOF") + id_iof = reader.readElementText().toInt(); + else if (reader.name() == "Name") { + if (reader.attributes().hasAttribute("code")) + code_cz = reader.attributes().value("code").toString(); + name = reader.readElementText(); + } + else if (reader.name() == "Extensions") + { + while(reader.readNextStartElement()) { + if (reader.name() == "Abbreviation") + abbr_cz = reader.readElementText(); + else + reader.skipCurrentElement(); + } + } + else if (reader.name() == "Country") { + if (reader.attributes().hasAttribute("code")) + country_code = reader.attributes().value("code").toString(); + reader.readElementText(); // need to be here, for next element + } + else + reader.skipCurrentElement(); + } + } + else + reader.skipCurrentElement(); + + // insert to db + if (!name.isEmpty() || (federation_iof && !country.isEmpty())) { + if (creator == XmlCreators::Oris) { + q.bindValue(":name", name); + q.bindValue(":abbr", abbr_cz); + q.bindValue(":importId", id_cz); + } + else if (creator == XmlCreators::Eventor) { + if (federation_iof) { + q.bindValue(":abbr", country_code); + q.bindValue(":name", country); + } + else { + q.bindValue(":name", name); + q.bindValue(":abbr", GenFakeCzClubAbbr(country_code)); + } + q.bindValue(":importId", id_iof); + } + q.exec(qf::core::Exception::Throw); + items_processed++; + } + } + else + reader.skipCurrentElement(); + } + if (items_processed > 0) { + transaction.commit(); + qfInfo() << "Imported" << items_processed << "clubs."; + } + + if (creator == XmlCreators::Eventor){ + int max_item = -1; + QString key; + auto it = fakeCzClubMap.begin(); + while (it != fakeCzClubMap.end()) { + if (it.value() > max_item) { + max_item = it.value(); + key = it.key(); + } + it++; + } + qfInfo() << "Maximum clubs from one country was" << max_item << "in" << key; + fakeCzClubMap.clear(); + } + return items_processed > 0; +} + +bool XmlImporter::importRegistration(QXmlStreamReader &reader, const XmlCreators creator) +{ + QMap clubs_map; + qf::core::sql::Query q; + if (creator == XmlCreators::Eventor) { + q.exec("SELECT abbr, importId FROM clubs", qf::core::Exception::Throw); + while(q.next()) { + clubs_map[q.value(1).toInt()] = q.value(0).toString(); + } + if (clubs_map.size() == 0){ + qfError() << "Undefined clubs for registration"; + return false; + } + } + qf::core::sql::Transaction transaction; + q.exec("DELETE FROM registrations", qf::core::Exception::Throw); + q.prepare("INSERT INTO registrations (firstName, lastName, registration, licence, clubAbbr, country, siId, importId) VALUES (:firstName, :lastName, :registration, :licence, :clubAbbr, :country, :siId, :importId)", qf::core::Exception::Throw); + // load data from XML + int items_processed = 0; + while(reader.readNextStartElement()) { + if(reader.name() == "Competitor") { + SPerson person; + if (readPersonNode(person,reader, creator)) { + q.bindValue(":firstName", person.nameGiven); + q.bindValue(":lastName", person.nameFamily); + if (creator == XmlCreators::Oris) { + q.bindValue(":licence", ""); + q.bindValue(":importId", person.orisId); + q.bindValue(":siId", person.siNumber); + if(!person.regCz.isEmpty()) { + q.bindValue(":registration", person.regCz); + q.bindValue(":clubAbbr", person.regCz.mid(0, 3)); + } + } + else if (creator == XmlCreators::Eventor) { + QString club_abbr = clubs_map.value(person.clubIdIof); + if(!club_abbr.isEmpty()) { + q.bindValue(":clubAbbr", club_abbr); + q.bindValue(":registration", person.regCz); + } + q.bindValue(":importId", person.iofId); + } + q.bindValue(":country",person.nationalityCode); + q.exec(qf::core::Exception::Throw); + items_processed++; + } + else + qfWarning() << "Failed to read runner registration on pos" << items_processed; + } + else + reader.skipCurrentElement(); + } + + if (items_processed > 0) { + transaction.commit(); + qfInfo() << "Imported" << items_processed << "registered runners."; + getPlugin()->emitDbEvent(EventPlugin::DBEVENT_REGISTRATIONS_IMPORTED, QVariant(), true); + } + + return items_processed > 0; +} + + +bool XmlImporter::importXML30() +{ + qf::qmlwidgets::framework::MainWindow *fwk = qf::qmlwidgets::framework::MainWindow::frameWork(); + QString fn = qfd::FileDialog::getOpenFileName(fwk, tr("Open XML Entry file"), QString(), tr("IOF XML v3 files (*.xml)")); + if(fn.isEmpty()) + return false; + QFile file(fn); + if(!file.open(QFile::ReadOnly | QFile::Text)) + return false; + + QXmlStreamReader reader(&file); + if (reader.readNextStartElement()) { + XmlCreators creator = XmlCreators::QuickEvent; + if (reader.attributes().hasAttribute("creator")) { + QString name = reader.attributes().value("creator").toString(); + if (name == "ORIS") + creator = XmlCreators::Oris; + else if (name == "Eventor") + creator = XmlCreators::Eventor; + } + + if (reader.name() == "EntryList") + return importEntries(reader, creator); + else if (reader.name() == "StartList") + return importStartlist(reader, creator); + else if (reader.name() == "ClassList") + return importClasses(reader, creator); + else if (reader.name() == "OrganisationList") // clubs + return importClubs(reader, creator); + else if (reader.name() == "CompetitorList") // registration + return importRegistration(reader, creator); + else if (reader.name() == "EventList") + return false; // not used yet - maybe for race info, maybe for racelist (WMOC) + else + qfd::MessageBox::showWarning(fwk, QString(tr("Unsuported IOF XML 3.0 type (%1)")).arg(reader.name())); + } + return false; +} + diff --git a/quickevent/app/quickevent/plugins/Oris/src/xmlimporter.h b/quickevent/app/quickevent/plugins/Oris/src/xmlimporter.h new file mode 100644 index 000000000..be8ab6f0d --- /dev/null +++ b/quickevent/app/quickevent/plugins/Oris/src/xmlimporter.h @@ -0,0 +1,64 @@ +#ifndef XMLIMPORTER_H +#define XMLIMPORTER_H + +#include +#include +#include +#include + +class XmlImporter : public QObject +{ + Q_OBJECT +public: + enum class XmlCreators : int + { + QuickEvent = 0, + Oris, + Eventor, + }; + struct SPerson + { + QString nameGiven; + QString nameFamily; + + QString className; + QString classNameShort; // backup, if not defined className + + int iofId = -1; + QString regCz; // ORIS + QString noteOris; // ORIS + int orisId = -1; // ORIS + + int siNumber = 0; + + QString nationalityCode; + QString nationalityName; + QString clubCode; + QString clubName; + int clubIdIof = -1; // Eventor + QSet enterRaces; + // ... + QString country_abbr; + int bib = 0; + int rank = 0; + int start_order_pref = 0; // 0-none / 1- early / 2- middle / 3- late + int leg = 0; + }; + + explicit XmlImporter(QObject *parent = nullptr); + + Q_INVOKABLE bool importXML30(); +protected: + bool readPersonNode(SPerson &s, QXmlStreamReader &reader, const XmlCreators creator); + + bool importEntries(QXmlStreamReader &reader, const XmlCreators creator); + bool importStartlist(QXmlStreamReader &reader, const XmlCreators creator); + bool importClasses(QXmlStreamReader &reader, const XmlCreators creator); + bool importClubs(QXmlStreamReader &reader, const XmlCreators creator); + bool importRegistration(QXmlStreamReader &reader, const XmlCreators creator); + + QString GenFakeCzClubAbbr(QString country); + QMap fakeCzClubMap; +}; + +#endif // XMLIMPORTER_H