#include "SportModel.h" #include "Competitor.h" // categories #include #include #include #include // sorting and filtering //#include #include // float to string formatting #include #include #include #include #include #include namespace { const QString &k_requestUrl = "https://sph-s-api.olympics.com/summer/schedules/api/ENG/schedule/discipline/"; } SportModel::SportModel(QObject *parent) : QAbstractListModel(parent) { } int SportModel::rowCount(const QModelIndex &parent) const { Q_UNUSED(parent); return m_sportList.size(); } QVariant SportModel::data(const QModelIndex &index, int role) const { if (index.isValid() && index.row() >= 0 && index.row() < m_sportList.size()) { EventInfo *event = m_sportList[index.row()]; switch ((Role) role) { case EventName: return event->eventName(); case Competitors: return QVariant::fromValue(event->competitors()); } } return {}; } QHash SportModel::roleNames() const { QHash names; names[EventName] = "eventName"; names[Competitors] = "competitors"; return names; } QString SportModel::discipline() const { return m_discipline; } void SportModel::setDiscipline(const QString &discipline) { m_discipline = discipline; disciplineChanged(); } void SportModel::request(QString discipline) { setDiscipline(discipline); m_reply = m_networkManager.get(QNetworkRequest( k_requestUrl + m_discipline)); qDebug() << m_reply; connect(m_reply, &QNetworkReply::finished, this, &SportModel::parseData); } void SportModel::parseData() { if (m_reply->error() == QNetworkReply::NoError) { beginResetModel(); qDeleteAll(m_sportList); m_sportList.clear(); QByteArray strReply = m_reply->readAll(); //parse json // qDebug() << "Response:" << strReply; QJsonDocument jsonDocument = QJsonDocument::fromJson(strReply); map> medals = getMedalsOfCompetitors(); QJsonArray sports = jsonDocument["units"].toArray(); for (const auto &sport : sports) { QJsonObject entry = sport.toObject(); EventInfo *event = new EventInfo(this); event->setEventName(entry["eventUnitName"].toString()); QList competitors; for (const auto &competitor : entry["competitors"].toArray()) { Competitor *comp = new Competitor(this); comp->setCompetitor(competitor.toObject()); if (medals.find(comp->name()) != medals.end()) comp->setMedals(medals.find(comp->name())->second); if (!competitors.empty()) comp->setStatistic(competitors.first()->mark()); competitors << comp; } event->setCompetitors(competitors); qDebug() << entry["eventUnitName"].toString(); m_sportList << event; } endResetModel(); } } // QJsonArray filter function, provide with input array and evaluation function QJsonArray filter(QJsonArray input, function eval) { QJsonArray output; for (const QJsonValueRef &elemRef :input) { QJsonObject elem = elemRef.toObject(); if(eval(elem)) { output.append(elem); } } return output; } /** * @brief Sport::lastName Reduce the full name to the part that is marked in capital letters (probably last name). * @param competitors The competitors of one category. */ void SportModel::lastName(QList &competitors) { // validate competitors if (competitors.isEmpty()) return; for (int i = 0; i < competitors.size(); i++) { Competitor* comp = competitors.value(i); string fullName = comp->name().toUtf8().constData(); // regex to identify names, written in CAPS regex r("[A-Z']{2,}"); smatch m; regex_search(fullName, m, r); // combine found names again string lastName = ""; for (string s : m) lastName = lastName + s + " "; // remove last space QString name = QString(lastName.substr(0, lastName.size() - 1).c_str()); // replace competitor name in list comp->setName(name); } } /** * @brief Sport::validateDiscipline Validates the discipline object. Checks for the units attribute. * @return True, if discipline contains units. */ bool SportModel::validateDiscipline() { return this->o_discipline.contains("units"); } /** * @brief Sport::getCategories Reads all possible categories (also called units). * @return A set of all category names. */ set SportModel::getCategories() { set categoryNames; if (!validateDiscipline()) return categoryNames; // search in each unit for the category (named "eventUnitName") for (const QJsonValueRef &unitRef : this->o_discipline["units"].toArray()) { QJsonObject unit = unitRef.toObject(); // validate unit if (!unit.contains("eventUnitName")) continue; categoryNames.insert(unit["eventUnitName"].toString()); } return categoryNames; } /** * @brief Sport::getMedalsOfCompetitor Filters all competitors, who have at least one medal. These objects are different from getCompetitorsByCategory !!! * @return All competitors, who won at least one medal. Structure of one competitor: {code, name, m_noc, medals{ME_GOLD, ME_SILVER, ME_BRONZE}} */ map> SportModel::getMedalsOfCompetitors() { map> competitors; if (!validateDiscipline()) return competitors; // filter all units, which have medal events QJsonArray units = filter(this->o_discipline["units"].toArray(), [](QJsonObject unit){ // search all units with Final, Gold or Bronze in their name, because these are the categories with the medal winners QString unitName = unit["eventUnitName"].toString(); return unitName.contains("Bronze", Qt::CaseSensitive) || unitName.contains("Gold", Qt::CaseSensitive) || unitName.contains("Final", Qt::CaseSensitive); }); for (const QJsonValueRef &unitRef : units) { QJsonObject unit = unitRef.toObject(); // validate unit if (!unit.contains("competitors")) continue; // filter all competitors, who won medals QJsonArray medalComps = filter(unit["competitors"].toArray(), [](QJsonObject comp) { if (!comp.contains("results")) return false; QString medalType = comp["results"].toObject()["medalType"].toString(); return !medalType.isEmpty(); }); for (const QJsonValueRef &medalCompRef : medalComps) { QJsonObject medalComp = medalCompRef.toObject(); // validate competitor (with medal) if (!medalComp.contains("name") || !medalComp.contains("results") || !medalComp["results"].toObject().contains("medalType")) continue; QString name = medalComp["name"].toString(); QString medalType = medalComp["results"].toObject()["medalType"].toString(); // check if competitor has other medal(s) if (competitors.find(name) == competitors.end()) { map emptyMedalObject = { {"ME_GOLD", 0}, {"ME_SILVER", 0}, {"ME_BRONZE", 0} }; competitors.insert({name, emptyMedalObject}); } // update the medal count competitors.find(name)->second.find(medalType)->second++; } } return competitors; } /** * @brief Sport::filterByName Filter the competitors by name (case insensitive). * @param competitors The competitors of one category. * @param name The (part of the) name to search for. */ void SportModel::filterByName(QList &competitors, QString name) { filterCompetitors(competitors, name); } /** * @brief Sport::filterByCountry Filter the competitors by their national olympics comittee (case insensitive, short form). * @param competitors The competitors of one category. * @param nocShort The (part of the) national olympics comittee short name to search for. */ void SportModel::filterByCountry(QList &competitors, QString nocShort) { filterCompetitors(competitors, nocShort); } /** * @brief Sport::filterCompetitors Filters the given QJsonArray by comparing the value of a certain attribute with the given filter string. * @param competitors The competitors of one category. * @param attribute The attribute to filter by. * @param filter The string, which should be contained. */ void SportModel::filterCompetitors(QList &competitors, QString filter) { for (int i = 0; i < competitors.size(); i++) { if (!competitors.value(i)->noc().contains(filter)) { competitors.remove(i); i--; } } } /** * @brief Sport::sortByName Sort the competitors by their name (alphabetical, ascending). * @param competitors The competitors of one category. */ //void SportModel::sortByName(QList &competitors) { // if (competitors.isEmpty()) return; // std::sort(competitors.begin(), competitors.end(), Competitor::compareName); //} /** * @brief Sport::sortByCountry Sort the competitors by their national olympic comittee short name (alphabetical, ascending). * @param competitors The competitors of one category. */ //void SportModel::sortByCountry(QList &competitors) { // if (competitors.isEmpty()) return; // std::sort(competitors.begin(), competitors.end(), Competitor::compareNOC); //} /** * @brief Sport::sortByResult Sort the competitors by their results in one specific category (numerical, ascending). * @param competitors The competitors of one category. */ //void SportModel::sortByResult(QList &competitors) { // if (competitors.isEmpty()) return; // std::sort(competitors.begin(), competitors.end(), Competitor::compareMark); //} /** * @brief Sport::sortByMedals Sort the competitors by their medal amounts in one specific category (numerical, ascending). * @param competitors The competitors of one category. */ //void SportModel::sortByMedals(QList &competitors) { // if (competitors.isEmpty()) return; // std::sort(competitors.begin(), competitors.end(), Competitor::compareMedals); //} /** * @brief Sport::reverseOrder Reverses the order of the competitors. * @param competitors The competitors of one category. */ void SportModel::reverseOrder(QList &competitors) { int iterations = competitors.size() / 2; // automatically rounds down for (int i = 0; i < iterations; i++) { Competitor *left = competitors.value(i); Competitor *right = competitors.value(competitors.size() - 1 - i); competitors.replace(i, right); competitors.replace(competitors.size() - 1 - i, left); } } /** * @brief Sport::addRelativeToFirst Adds a relative value to the result of all competitors. Relative to the first competitor in the QJsonArray. * Stores the m_statistic in obj->results->stat for each competitor. * @param competitors The competitors of one category. */ void SportModel::addRelativeToFirst(QList &competitors) { if (competitors.isEmpty()) return; QString reference = competitors.value(0)->mark(); for (int i = 0; i < competitors.size(); i++) { Competitor *comp = competitors.value(i); QString result = comp->mark(); // format relative float value to string with 2 digits after decimal point and sign stringstream sstream; sstream << fixed << setprecision(2) << calcRelativeStat(reference, result); QString stat(sstream.str().c_str()); stat.append("%"); if (stat.at(0).isNumber()) stat = QString("+").append(stat); comp->setStatistic(stat); } } /** * @brief Sport::calcRelativeStat Calculates the relative deviation of val from ref in percent. * @param ref The reference value. * @param val The value to calculate the deviation from. * @return The deviation from ref to val in percent. */ float SportModel::calcRelativeStat(QString ref, QString val) { // check if the value is not a time if (!ref.contains(":") && !val.contains(":")) { float fRef = ref.toFloat(); if (fRef == 0) return 0.0; return ((val.toFloat() * 100)/ fRef) - 100; } regex r("\\d+"); smatch mref, mval; string sref = ref.toUtf8().constData(); string sval = val.toUtf8().constData(); regex_search(sref, mref, r); regex_search(sval, mval, r); float timeRef = stof(mref.str(1)) * 60 * 100 + stof(mref.str(2)) * 100 + stof(mref.str(3)); float timeVal = stof(mval.str(1)) * 60 * 100 + stof(mval.str(2)) * 100 + stof(mval.str(3)); return ((timeVal * 100) / timeRef) - 100; }