From e01ae8b18d9a8996329fe121d0873e3dc4b4ba72 Mon Sep 17 00:00:00 2001 From: David van Rijn Date: Thu, 17 Feb 2022 11:43:08 +0100 Subject: [PATCH 1/3] gitiginore --- .gitignore | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.gitignore b/.gitignore index faca3dbb..cbde1218 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,12 @@ + +# kdevelop + +*.kdev4 +*.kate-swp +kdev4/ + + +build/ .directory /.clickable/ /.make.cache From e2ba4294f66ba8b0be33786fbaf6c6555f9ff2d0 Mon Sep 17 00:00:00 2001 From: David van Rijn Date: Thu, 17 Feb 2022 11:43:18 +0100 Subject: [PATCH 2/3] 9292 fixed search Although we are using `/suggest` which is for the autocomplete, and really a proper search. At least Zurich (nl also has one) doesn't show up. --- src/parser/parser_ninetwo.cpp | 53 +++++++++++++---------------------- src/parser/parser_ninetwo.h | 8 +++--- 2 files changed, 24 insertions(+), 37 deletions(-) diff --git a/src/parser/parser_ninetwo.cpp b/src/parser/parser_ninetwo.cpp index 1dab8be3..ddfe66f4 100644 --- a/src/parser/parser_ninetwo.cpp +++ b/src/parser/parser_ninetwo.cpp @@ -28,7 +28,7 @@ #endif #include -#define BASE_URL "https://api.9292.nl/0.1/" +#define BASE_URL "https://9292.nl/en" inline qreal deg2rad(qreal deg) { @@ -78,19 +78,18 @@ void ParserNinetwo::getTimeTableForStation(const Station ¤tStation, void ParserNinetwo::findStationsByName(const QString &stationName) { - qDebug() << "FINDBYNAME"; - QUrl uri(BASE_URL "locations"); + QUrl url(BASE_URL "/suggest"); + qDebug() << "FINDBYNAME" << stationName << url; #if defined(BUILD_FOR_QT5) QUrlQuery query; #else QUrl query; #endif - query.addQueryItem("lang", "en-GB"); - query.addQueryItem("q", stationName); - uri.setQuery(query); + query.addQueryItem("userInput", stationName); + url.setQuery(query); - sendHttpRequest(uri); + sendHttpRequest(url); currentRequestState=FahrplanNS::stationsByNameRequest; } @@ -105,7 +104,6 @@ void ParserNinetwo::findStationsByCoordinates(qreal longitude, qreal latitude) QUrl query; #endif query.addQueryItem("lang", "en-GB"); - query.addQueryItem("type", "station,stop"); query.addQueryItem("latlong", QString("%1,%2").arg(latitude).arg(longitude)); query.addQueryItem("includestation", "true"); uri.setQuery(query); @@ -129,13 +127,15 @@ void ParserNinetwo::searchJourney(const Station &departureStation, lastsearch.to=arrivalStation; lastsearch.restrictions=trainrestrictions; lastsearch.via=viaStation; - QUrl uri(BASE_URL "journeys"); + QUrl uri(BASE_URL "journeyadvice"); #if defined(BUILD_FOR_QT5) QUrlQuery query; #else QUrl query; #endif + + query.addQueryItem("lang", "en-GB"); query.addQueryItem("before", "1"); query.addQueryItem("from", departureStation.id.toString()); @@ -207,7 +207,7 @@ void ParserNinetwo::parseTimeTable(QNetworkReply *networkReply) { TimetableEntriesList result; QByteArray allData = networkReply->readAll(); -// qDebug() << "REPLY:>>>>>>>>>>>>\n" << allData; + qDebug() << "REPLY:>>>>>>>>>>>>\n" << allData; QVariantMap doc = parseJson(allData); if (doc.isEmpty()) { @@ -283,12 +283,13 @@ void ParserNinetwo::parseTimeTable(QNetworkReply *networkReply) void ParserNinetwo::parseStationsByName(QNetworkReply *networkReply) { - qDebug() << "PARSING STATIONS"; + qDebug() << __PRETTY_FUNCTION__ << "PARSING STATIONS"; QByteArray allData = networkReply->readAll(); -// qDebug() << "REPLY:>>>>>>>>>>>>\n" << allData; + qDebug() << "REPLY:>>>>>>>>>>>>\n" << allData; QVariantMap doc = parseJson(allData); if (doc.isEmpty()) { + qWarning() << __PRETTY_FUNCTION__ << "empty response"; emit errorOccured(tr("Cannot parse reply from the server")); return; } @@ -301,32 +302,18 @@ void ParserNinetwo::parseStationsByName(QNetworkReply *networkReply) for (i = stations.constBegin(); i != stations.constEnd(); ++i) { QVariantMap station = i->toMap(); Station s; - bool okLat, okLon; - double lat = station.value("latLong").toMap().value("lat").toDouble(&okLat); - double lon = station.value("latLong").toMap().value("long").toDouble(&okLon); //s.name=QString("[%1]%2").arg(station.value("type").toString(), station.value("name").toString()); - const QString name = station.value("name").toString(); - const QString place = station.value("place").toMap().value("name").toString(); + const QString name = station.value("Displayname").toString(); + const QString place = station.value("Region").toString(); if (place.isEmpty()) s.name = name; else s.name = QString("%1 (%2)").arg(name, place); - s.miscInfo = station.value("type").toString(); - if (okLat && okLon) { - s.latitude = lat; - s.longitude = lon; - if (lastCoordinates.isValid) { - if (!s.miscInfo.isEmpty()) - s.miscInfo.prepend(", "); - //: Distance in meters - s.miscInfo.prepend(tr("%n m", "", - distance(lastCoordinates.latitude, lastCoordinates.longitude, - s.latitude, s.longitude))); - } - } - if (station.value("type").toString() == "address") - s.name = s.name + " " + station.value("houseNr").toString(); - s.id = station.value("id").toString(); + // type is "stop" or "address" + // subType is busstop + // so we only care about subtype + s.miscInfo = station.value("EnglishSubType",station.value("SubType")).toString(); + s.id = station.value("Url").toString(); result.append(s); } diff --git a/src/parser/parser_ninetwo.h b/src/parser/parser_ninetwo.h index ed379065..3682a3c6 100644 --- a/src/parser/parser_ninetwo.h +++ b/src/parser/parser_ninetwo.h @@ -75,10 +75,10 @@ public slots: void searchJourneyLater(); void searchJourneyEarlier(); void getJourneyDetails(const QString &id); - bool supportsGps() { return true; } - bool supportsVia() { return true; } - bool supportsTimeTable() { return true; } - bool supportsTimeTableDirection() { return false; } + bool supportsGps() final { return false; } + bool supportsVia() final { return false; } + bool supportsTimeTable() final { return false; } + bool supportsTimeTableDirection() final { return false; } QStringList getTrainRestrictions(); protected: From 357983a5ead97da869ecdc5b9b6049d17d75f70e Mon Sep 17 00:00:00 2001 From: David van Rijn Date: Fri, 18 Feb 2022 00:41:49 +0100 Subject: [PATCH 3/3] 9292: Added journey parser/scraper Now we use the website, which has a hidden input containing json data. --- src/parser/parser_ninetwo.cpp | 527 +++++++++++++++++++++------------- src/parser/parser_ninetwo.h | 4 +- 2 files changed, 332 insertions(+), 199 deletions(-) diff --git a/src/parser/parser_ninetwo.cpp b/src/parser/parser_ninetwo.cpp index ddfe66f4..8d418c96 100644 --- a/src/parser/parser_ninetwo.cpp +++ b/src/parser/parser_ninetwo.cpp @@ -1,26 +1,28 @@ /**************************************************************************** -** -** This file is a part of Fahrplan. -** -** 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; either version 2 of the License, or -** (at your option) any later version. -** -** 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, see . -** -****************************************************************************/ + * * + ** This file is a part of Fahrplan. + ** + ** 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; either version 2 of the License, or + ** (at your option) any later version. + ** + ** 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, see . + ** + ****************************************************************************/ #include "parser_ninetwo.h" #include #include +#include +#include #ifdef BUILD_FOR_QT5 # include #else @@ -62,11 +64,11 @@ void ParserNinetwo::getTimeTableForStation(const Station ¤tStation, { QUrl uri(QString(BASE_URL "/locations/%1/departure-times").arg(currentStation.id.toString())); -#if defined(BUILD_FOR_QT5) + #if defined(BUILD_FOR_QT5) QUrlQuery query; -#else + #else QUrl query; -#endif + #endif query.addQueryItem("lang", "en-GB"); uri.setQuery(query); @@ -78,14 +80,16 @@ void ParserNinetwo::getTimeTableForStation(const Station ¤tStation, void ParserNinetwo::findStationsByName(const QString &stationName) { + // TODO: do a bogus search, and use the "did you perhapss mean ..." response + // bacause it seems that some places do not get suggested QUrl url(BASE_URL "/suggest"); qDebug() << "FINDBYNAME" << stationName << url; -#if defined(BUILD_FOR_QT5) + #if defined(BUILD_FOR_QT5) QUrlQuery query; -#else + #else QUrl query; -#endif + #endif query.addQueryItem("userInput", stationName); url.setQuery(query); @@ -95,14 +99,18 @@ void ParserNinetwo::findStationsByName(const QString &stationName) void ParserNinetwo::findStationsByCoordinates(qreal longitude, qreal latitude) { + // No longer supported. + // TODO: use a free geocoding api, and then search on + // postal code + QUrl uri(BASE_URL "locations"); -#if defined(BUILD_FOR_QT5) + #if defined(BUILD_FOR_QT5) QUrlQuery query; -#else + #else QUrl query; -#endif + #endif query.addQueryItem("lang", "en-GB"); query.addQueryItem("latlong", QString("%1,%2").arg(latitude).arg(longitude)); query.addQueryItem("includestation", "true"); @@ -127,45 +135,66 @@ void ParserNinetwo::searchJourney(const Station &departureStation, lastsearch.to=arrivalStation; lastsearch.restrictions=trainrestrictions; lastsearch.via=viaStation; - QUrl uri(BASE_URL "journeyadvice"); - -#if defined(BUILD_FOR_QT5) - QUrlQuery query; -#else - QUrl query; -#endif + QUrl url(BASE_URL "/journeyadvice"); + #if defined(BUILD_FOR_QT5) + QUrlQuery postData; + #else + QUrl postData; + #endif - query.addQueryItem("lang", "en-GB"); - query.addQueryItem("before", "1"); + /* + // this is for /journeyadvice-page query.addQueryItem("from", departureStation.id.toString()); - if(viaStation.valid) - query.addQueryItem("via", viaStation.id.toString()); query.addQueryItem("to", arrivalStation.id.toString()); - query.addQueryItem("sequence", "1"); - query.addQueryItem("dateTime", dateTime.toString("yyyy-MM-ddTHHmm")); - - switch(trainrestrictions){ - default: - case all: - query.addQueryItem("byFerry", "true"); - case noFerry: - query.addQueryItem("byBus", "true"); - query.addQueryItem("byTram", "true"); - query.addQueryItem("bySubway", "true"); - case trainsOnly: - query.addQueryItem("byTrain", "true"); - } - query.addQueryItem("searchtype", mode==Departure?"departure":"arrival"); - - //TODO: make transport types work - query.addQueryItem("after", "5"); - - uri.setQuery(query); - sendHttpRequest(uri); - + query.addQueryItem("searchType", mode == Mode::Arrival ? "arrival" : "departure"); + query.addQueryItem("dateTime", dateTime.toString("yyyy-MM-ddThhmm")); + query.addQueryItem("extraInterchangeTime", 0); + query.addQueryItem("flexStop", "False"); + query.addQueryItem("flexAddress", "False"); + query.addQueryItem("before", QString::number(-1)); + query.addQueryItem("bfter", QString::number(5)); + */ + + // This post request is inconsistent AF. here and there it uses "true"/"false" + // and other places it uses "on"/"off" + + static const char* const o[] = {"off", "on"}; + postData.addQueryItem("fromText",""); + postData.addQueryItem("fromLock","false"); + postData.addQueryItem("fromOriginal",""); + postData.addQueryItem("fromRef",departureStation.id.toString()); + postData.addQueryItem("fromRefOriginal",""); + postData.addQueryItem("viaText", viaStation.valid ? viaStation.name : ""); + postData.addQueryItem("viaOriginal",""); + postData.addQueryItem("viaRef", viaStation.valid ? viaStation.id.toString() : ""); + postData.addQueryItem("viaRefOriginal",""); + postData.addQueryItem("toText",""); + postData.addQueryItem("toLock","false"); + postData.addQueryItem("toOriginal",""); + postData.addQueryItem("toRef",arrivalStation.id.toString()); + postData.addQueryItem("toRefOriginal",""); + postData.addQueryItem("date",dateTime.toString("dd-MM-yyyy")); + postData.addQueryItem("time",dateTime.toString("hh:mm")); + postData.addQueryItem("dateTime",dateTime.toString("yyyy-MM-ddhh:mm")); + postData.addQueryItem("searchType", (mode == Mode::Arrival) ? "aankomst" : "vertrek"); + postData.addQueryItem("extraInterchangeTime","0"); + postData.addQueryItem("travelByBus",o[trainrestrictions != restrictions::trainsOnly ]); + postData.addQueryItem("travelByTrain","on"); + postData.addQueryItem("travelByTram",o[trainrestrictions != restrictions::trainsOnly ]); + postData.addQueryItem("travelBySubway",o[trainrestrictions != restrictions::trainsOnly ]); + postData.addQueryItem("travelByFerry",o[trainrestrictions == restrictions::all]); + postData.addQueryItem("planWithAccessibility","false"); + + #if defined(BUILD_FOR_QT5) || defined(BUILD_FOR_QT6) + QByteArray data = postData.query().toUtf8().replace(' ', '+') ; + #else + QByteArray data = postData.encodedQuery().toUtf8().replace(' ', '+') ; + #endif + + qDebug() << "SEARCH JOURNEYS" << url << postData.query(); + sendHttpRequest(url, data); currentRequestState=FahrplanNS::searchJourneyRequest; - } void ParserNinetwo::searchJourneyLater() @@ -188,11 +217,11 @@ void ParserNinetwo::getJourneyDetails(const QString &id) QStringList ParserNinetwo::getTrainRestrictions() { - QStringList restrictions; - restrictions << tr("All"); - restrictions << tr("Only trains"); - restrictions << tr("All, except ferry"); - return restrictions; + QStringList restrictions; + restrictions << tr("All"); + restrictions << tr("Only trains"); + restrictions << tr("All, except ferry"); + return restrictions; } bool sortByTimeLessThan(const TimetableEntry &first, const TimetableEntry &second) @@ -226,14 +255,14 @@ void ParserNinetwo::parseTimeTable(QNetworkReply *networkReply) switch(timetableRestrictions){ case all: default: + break; + case trainsOnly: + if(type!="train") + continue; break; - case trainsOnly: - if(type!="train") - continue; - break; - case noFerry: - if(type=="ferry")//not sure if this ever happens - continue; + case noFerry: + if(type=="ferry")//not sure if this ever happens + continue; break; } @@ -275,7 +304,7 @@ void ParserNinetwo::parseTimeTable(QNetworkReply *networkReply) // Departures / arrivals are grouped by transportation type, // while we want them sorted by departure / arrival time. - qSort(result.begin(), result.end(), sortByTimeLessThan); + std::sort(result.begin(), result.end(), sortByTimeLessThan); emit timetableResult(result); @@ -305,14 +334,10 @@ void ParserNinetwo::parseStationsByName(QNetworkReply *networkReply) //s.name=QString("[%1]%2").arg(station.value("type").toString(), station.value("name").toString()); const QString name = station.value("Displayname").toString(); const QString place = station.value("Region").toString(); - if (place.isEmpty()) - s.name = name; - else - s.name = QString("%1 (%2)").arg(name, place); - // type is "stop" or "address" - // subType is busstop - // so we only care about subtype s.miscInfo = station.value("EnglishSubType",station.value("SubType")).toString(); + + s.name = QString("%3 %1 (%2)").arg(name, place, s.miscInfo); + s.id = station.value("Url").toString(); result.append(s); } @@ -329,69 +354,59 @@ void ParserNinetwo::parseStationsByCoordinates(QNetworkReply *networkReply) void ParserNinetwo::parseSearchJourney(QNetworkReply *networkReply) { - qDebug() << "PARSING JOURNEYS"; - QByteArray allData = networkReply->readAll(); - qDebug() << "REPLY:>>>>>>>>>>>>\n" << allData; - - QVariantMap doc = parseJson(allData); - if (doc.isEmpty()) { - emit errorOccured(tr("Cannot parse reply from the server")); - return; - } - QVariantList journeys = doc.value("journeys").toList(); + std::unique_ptr resultList{ new JourneyResultList}; - JourneyResultList* result=new JourneyResultList; + qDebug() << __LINE__ << "PARSING JOURNEYS" ; + //qDebug() << "REPLY:>>>>>>>>>>>>\n" << allData; +// QVariantMap data = parseJson(allData); - QDateTime arrival; - QDateTime departure; + //QDomDocument slider { data.value("JourneySliderBlock").toString() }; + //qDebug() << "DETAIL" << data.value("JourneyDetailBlock"); + //QDomDocument detail { data.value("JourneyDetailBlock").toString().replace("\\r\\n", "\n").replace("\\\"", "\"" ).replace("&", "&") }; - QVariantList::const_iterator i; - for (i = journeys.constBegin(); i != journeys.constEnd(); ++i) { - QVariantMap journey = i->toMap(); - parseJourneyOption(journey); - JourneyResultItem* item = new JourneyResultItem; - arrival = QDateTime::fromString(journey.value("arrival").toString(), "yyyy-MM-ddTHH:mm"); - departure = QDateTime::fromString(journey.value("departure").toString(), - "yyyy-MM-ddTHH:mm"); - if (i == journeys.constBegin()) - lastsearch.firstOption=departure; - - item->setArrivalTime(arrival.toString("HH:mm")); - item->setDepartureTime(departure.toString("HH:mm")); - - QVariantList legs = journey.value("legs").toList(); - QStringList trains; + QTextStream stream{networkReply}; - QVariantList::const_iterator j; - for (j = legs.constBegin(); j != legs.constEnd(); ++j) + QString line; + while(!stream.atEnd()) + { + stream.readLineInto(&line); + if(line.contains("Journey.Legs")) { - const QVariantMap mode = j->toMap().value("mode").toMap(); - if (mode.value("type").toString() != "walk") { - const QString typeName = mode.value("name").toString(); - if (!typeName.isEmpty()) - trains.append(typeName); + // use qDom struff only on this one element + // to extract the "value" and to do urlDecode + QDomDocument detail; + QString error; + detail.setContent(line, &error); + if(!error.isEmpty()) + { + qWarning() << "error parsing html" << "\n" << error; } + auto input = detail.documentElement(); + JourneyResultItem* option = parseJourneyJson(input.attribute("value")); + + if(option) + resultList->appendItem(option); + else + qWarning() << "parsing json faied"; } + } - trains.removeDuplicates(); - item->setTrainType(trains.join(", ").trimmed()); - item->setTransfers(QString::number((int) journey.value("numberOfChanges").toDouble())); - int minutes = departure.secsTo(arrival)/60; - item->setDuration(QString("%1:%2").arg(minutes/60).arg(minutes%60,2,10,QChar('0'))); - item->setId(journey.value("id").toString()); - result->appendItem(item); + //for (int i = 0; i < divs.count(); i++) + //{ + // auto div = divs.item(i); + // if(!div.isElement()) continue; + // if(!div.toElement().hasAttribute("data-icscontent")) continue; + // qDebug() << "found ics data"; + // auto* res = parseJourneyICS(div.toElement().attribute("data-icscontent")); + // if(res) + // resultList->appendItem(res); + //} + //lastsearch.lastOption=departure; - //Set result metadata based on first result - if (result->itemcount() == 1) { - result->setTimeInfo(arrival.date().toString()); - result->setDepartureStation(cachedResults[item->id()]->departureStation()); - result->setArrivalStation(cachedResults[item->id()]->arrivalStation()); - } - } - lastsearch.lastOption=departure; - emit journeyResult(result); + + emit journeyResult(resultList.release()); } void ParserNinetwo::parseSearchLaterJourney(QNetworkReply *) @@ -409,90 +424,206 @@ void ParserNinetwo::parseJourneyDetails(QNetworkReply *) //should never happen } -void ParserNinetwo::parseJourneyOption(const QVariantMap &object) -{ - JourneyDetailResultList* result = new JourneyDetailResultList; - QString id = object.value("id").toString(); - QVariantList legs = object.value("legs").toList(); - - QDateTime arrival = QDateTime::fromString(object.value("arrival").toString(), - "yyyy-MM-ddTHH:mm"); - QDateTime departure = QDateTime::fromString(object.value("departure").toString(), - "yyyy-MM-ddTHH:mm"); - result->setArrivalDateTime(arrival); - result->setDepartureDateTime(departure); - int minutes=departure.secsTo(arrival)/60; - int hours=minutes/60; - minutes=minutes%60; - result->setDuration(QString("%1:%2").arg(hours).arg(minutes, 2, 10, QChar('0'))); - result->setId(id); - for(int i = 0; i < legs.count(); i++) - { - QVariantMap leg = legs.at(i).toMap(); - JourneyDetailResultItem* resultItem = new JourneyDetailResultItem; +// Not finished and untested +// but might be a start, if the json way breaks:w + +JourneyResultItem* ParserNinetwo::parseJourneyICS(QString const & ics) +{ + std::unique_ptr item {new JourneyResultItem}; + qDebug() << "parsing ICS" << ics; + QStringList lines = ics.split("\r\n"); - QVariantList stops = leg.value("stops").toList(); - QVariantMap firstStop = stops[0].toMap(); - QVariantMap lastStop = stops[stops.size()-1].toMap(); - QVariantMap firstLocation = firstStop.value("location").toMap(); - QVariantMap lastLocation = lastStop.value("location").toMap(); + QDateTime startTime{}; + QString description{}; + for(QString line: lines) + { + auto sp = line.split(':'); + if(sp.length() < 2) continue; + QString key = sp[0]; + QString value = sp[1]; + if(key == "DTSTART" ) startTime = QDateTime::fromString(value, Qt::DateFormat::ISODate); + if(key == "DESCRIPTION") description = value; + } - resultItem->setArrivalStation(lastLocation.value("name").toString()); - resultItem->setDepartureStation(firstLocation.value("name").toString()); + if(description.isEmpty()) return nullptr; - QDateTime stopDeparture = QDateTime::fromString(firstStop.value("departure").toString(), - "yyyy-MM-ddTHH:mm"); - QDateTime stopArrival = QDateTime::fromString(lastStop.value("arrival").toString(), - "yyyy-MM-ddTHH:mm"); + QStringList descLines {description.split("\\n") }; - resultItem->setDepartureDateTime(stopDeparture); - resultItem->setArrivalDateTime(stopArrival); + int walkCount = 0; - QString type = leg.value("mode").toMap().value("type").toString(); - QString typeName; - if (type != "walk") - typeName = leg.value("mode").toMap().value("name").toString(); + auto iter = descLines.begin(); + std::unique_ptr detail{ new JourneyDetailResultList }; - //Fallback if typeName is empty - if (typeName.isEmpty() && !type.isEmpty()) { - typeName = type; - typeName[0] = type.at(0).toUpper(); + while(iter != descLines.end()) + { + // legs are separated by a blank line + QStringList legLines; + do + { + legLines.append(iter->toLower()); + iter++; } + while(iter != descLines.end() && *iter != ""); - const QString service = leg.value("service").toString(); - if (!service.isEmpty()) - typeName.append(" ").append(service); + JourneyDetailResultItem* leg = new JourneyDetailResultItem; - if (type == "walk") { - const QString duration = leg.value("duration").toString(); - resultItem->setTrain(tr("%1 for %2 min").arg(typeName, duration)); - } else { - resultItem->setTrain(typeName); + QString verb; // walk, train, metro etc + QString direction; + QString train;// metro B + { + QStringList words{ legLines[0].split(' ', Qt::SkipEmptyParts) }; + verb = words[0]; + if(verb == "walk") + walkCount++; + int directionIndex = words.indexOf("direction"); + if(directionIndex >= 0 && directionIndex+1 < words.count()) + { + direction = words.takeAt(directionIndex + 1); + words.takeAt(directionIndex); // "direction" + } + train = words.join(' '); } - if (!firstStop.value("platform").toString().isEmpty()) { - resultItem->setDepartureInfo(tr("Pl. %1") - .arg(firstStop.value("platform").toString())); - } - if (!lastStop.value("platform").toString().isEmpty()) { - resultItem->setArrivalInfo(tr("Pl. %1").arg(lastStop.value("platform").toString())); + for(QString line : legLines) + { + if(line.startsWith("departure") || line.startsWith("arrival")) + { + QStringList words = line.split(' ', Qt::SkipEmptyParts); + if(words.count() < 3) return nullptr; + QString depArr = words.takeFirst(); + QTime time = QTime::fromString(words.takeFirst(), "hh:mm"); + QDateTime dateTime {startTime.date(), time}; + QString platform; + int platformIndex = words.indexOf("platform"); + if(platformIndex >= 0 && platformIndex+1 < words.count()) + { + platform = words.takeAt(platformIndex + 1); + words.takeAt(platformIndex); // "platform" + } + QString station = words.join(' '); + leg->setProperty((depArr + "DateTime").toStdString().c_str(), dateTime); + leg->setProperty((depArr + "Station").toStdString().c_str(), station); + leg->setProperty((depArr + "Station").toStdString().c_str(), station); + } } - resultItem->setDirection(leg.value("destination").toString()); + detail->appendItem(leg); + } + if(detail->itemcount() == 0) + return nullptr; + static int count = 0; + QString id = QString::number(count ++); + cachedResults.insert(id, detail.release()); + + item->setDepartureTime(detail->getItem(0)->departureDateTime().toString("hh:mm")); + item->setArrivalTime (detail->getItem(detail->itemcount()-1)->arrivalDateTime().toString("hh:mm")); + item->setTransfers(QString::number(detail->itemcount() -walkCount-1) + " +" + QString::number(walkCount) ); + item->setId(id); + + return item.release(); +} + +JourneyResultItem * ParserNinetwo::parseJourneyJson(const QString& jsonData) +{ + // this really helps :) + //std::ofstream("/tmp/journey.json") << jsonData.toStdString(); + std::unique_ptr response {new JourneyResultItem}; + std::unique_ptr detail {new JourneyDetailResultList}; + + + // wrap it in an object, bcause it is a naked list + auto json = parseJson("{\"legs\":" + jsonData.toUtf8() + "}"); + if(json["legs"].type() != QVariant::List) + return nullptr; + QVariantList legs = json["legs"].toList(); - QStringList attributes; - const QVariantList attrs = leg.value("attributes").toList(); - foreach (const QVariant &attr, attrs) { - attributes << attr.toMap().value("title").toString(); + QSet transportTypes; + int walkCount = 0; + for(auto const & legv: legs) + { + if(legv.type() != QVariant::Map) + { + qWarning() << "non-map leg detected"; + continue; } - resultItem->setInfo(attributes.join(tr(", "))); + QVariantMap leg = legv.toMap(); + QString mode = leg["Mode"].toMap()["ModeType"].toString(); + transportTypes.insert(mode); + QString modeName = leg["Mode"].toMap()["Name"].toString(); + QString direction = leg["Destination"].toString(); + + // eg. B for metro line b + QString service = leg["Service"].toString(); + QString company = leg["Operator"].toMap()["Name"].toString(); + + if(mode == "walk") + walkCount++; + QVariantList calls = leg["Calls"].toList(); + + QString departureStation = calls[0].toMap()["Location"].toMap()["Cluster-Type"].toString() + " " + + calls[0].toMap()["Location"].toMap()["Name"].toString(); + QString departurePlatform = calls[0].toMap()["Platform"].toString(); + QDateTime departureTime = QDateTime::fromString(calls[0].toMap()["Departure"].toString(), Qt::ISODate); + int const lastCall = calls.count()-1; + QString arrivalStation = calls[lastCall].toMap()["Location"].toMap()["Cluster-Type"].toString() + " " + + calls[lastCall].toMap()["Location"].toMap()["Name"].toString(); + QDateTime arrivalTime = QDateTime::fromString(calls[lastCall].toMap()["Arrival"].toString(), Qt::ISODate); + QString arrivalPlatform = calls[lastCall].toMap()["Platform"].toString(); + + JourneyDetailResultItem* item = new JourneyDetailResultItem; + + item->setDirection(direction); + + if(mode == "walk") + item->setTrain(tr("Walk")); + else + item->setTrain(modeName + ": " + service); + + item->setArrivalDateTime(arrivalTime); + item->setArrivalStation(arrivalStation); + if(!arrivalPlatform.isEmpty()) + item->setArrivalInfo(QString(tr("Pl. %1")).arg(arrivalPlatform)); - result->appendItem(resultItem); + item->setDepartureDateTime(departureTime); + item->setDepartureStation(departureStation); + if(!departurePlatform.isEmpty()) + item->setDepartureInfo(QString(tr("Pl. %1")).arg(departurePlatform)); + + detail->appendItem(item); + } + + if(detail->itemcount() == 0) + return nullptr; + + static int count = 0; + count++; + QString id = QString::number(count); + + detail->setId(id); + + response->setId(id); + response->setDate(detail->getItem(0)->departureDateTime().date()); + response->setDepartureTime(detail->getItem(0)->departureDateTime().toString("hh:mm")); + response->setArrivalTime (detail->getItem(detail->itemcount()-1)->arrivalDateTime().toString("hh:mm")); + response->setTransfers(QString::number(detail->itemcount()-1 -walkCount) + " +" + QString::number(walkCount) ); + + { + QString tt; + for(QString const & t: transportTypes) + tt += t + ','; + tt.resize(tt.size()-1); + response->setTrainType(tt); } + int durationMins = ( + detail->getItem(detail->itemcount()-1)->arrivalDateTime().toMSecsSinceEpoch() - + detail->getItem(0)->departureDateTime().toMSecsSinceEpoch() + )/1000 / 60; + response->setDuration( + QString::number(durationMins / 60) + ":" + QString::number(durationMins % 60) + ); - result->setDepartureStation(result->getItem(0)->departureStation()); - result->setArrivalStation(result->getItem(result->itemcount() - 1)->arrivalStation()); - cachedResults.insert(id, result); + cachedResults.insert(id, detail.release()); + return response.release(); } diff --git a/src/parser/parser_ninetwo.h b/src/parser/parser_ninetwo.h index 3682a3c6..da31fbeb 100644 --- a/src/parser/parser_ninetwo.h +++ b/src/parser/parser_ninetwo.h @@ -92,7 +92,9 @@ public slots: QMap cachedResults; private: - void parseJourneyOption(const QVariantMap &object); + JourneyResultItem* parseJourneyICS(QString const & ics); + JourneyResultItem* parseJourneyJson(QString const & json); }; + #endif // PARSER_NINETWO_H