diff --git a/plugins/messageviewer/bodypartformatter/autotests/postprocessordata/train-unsorted.post.json b/plugins/messageviewer/bodypartformatter/autotests/postprocessordata/train-unsorted.post.json new file mode 100644 index 00000000..ebc64567 --- /dev/null +++ b/plugins/messageviewer/bodypartformatter/autotests/postprocessordata/train-unsorted.post.json @@ -0,0 +1,78 @@ +[ + { + "@context": "http://schema.org", + "@type": "TrainReservation", + "reservationFor": { + "@type": "TrainTrip", + "arrivalStation": { + "@type": "TrainStation", + "geo": { + "@type": "GeoCoordinates", + "latitude": 43.83229064941406, + "longitude": 4.365845203399658 + }, + "name": "Nîmes Gare" + }, + "arrivalTime": "2017-09-24T20:33:00+02:00", + "departureStation": { + "@type": "TrainStation", + "geo": { + "@type": "GeoCoordinates", + "latitude": 45.76055908203125, + "longitude": 4.8593549728393555 + }, + "name": "Lyon Part-Dieu" + }, + "departureTime": "2017-09-24T19:09:00+02:00", + "trainName": "TGV", + "trainNumber": "5119" + }, + "reservationNumber": "XXX007", + "reservedTicket": { + "@type": "Ticket", + "ticketedSeat": { + "@type": "Seat", + "seatNumber": "71", + "seatSection": "13" + } + } + }, + { + "@context": "http://schema.org", + "@type": "TrainReservation", + "reservationFor": { + "@type": "TrainTrip", + "arrivalStation": { + "@type": "TrainStation", + "geo": { + "@type": "GeoCoordinates", + "latitude": 45.76055908203125, + "longitude": 4.8593549728393555 + }, + "name": "Lyon Part-Dieu" + }, + "arrivalTime": "2017-09-29T19:52:00+02:00", + "departureStation": { + "@type": "TrainStation", + "geo": { + "@type": "GeoCoordinates", + "latitude": 43.83229064941406, + "longitude": 4.365845203399658 + }, + "name": "Nîmes Gare" + }, + "departureTime": "2017-09-29T18:26:00+02:00", + "trainName": "TGV", + "trainNumber": "5186" + }, + "reservationNumber": "XXX007", + "reservedTicket": { + "@type": "Ticket", + "ticketedSeat": { + "@type": "Seat", + "seatNumber": "62", + "seatSection": "17" + } + } + } +] diff --git a/plugins/messageviewer/bodypartformatter/autotests/postprocessordata/train-unsorted.pre.json b/plugins/messageviewer/bodypartformatter/autotests/postprocessordata/train-unsorted.pre.json new file mode 100644 index 00000000..73907d80 --- /dev/null +++ b/plugins/messageviewer/bodypartformatter/autotests/postprocessordata/train-unsorted.pre.json @@ -0,0 +1,128 @@ +[ + { + "@context": "http://schema.org", + "@type": "TrainReservation", + "bookingAgent": { + "@type": "Organization", + "email": "someone@trainline.fr", + "name": "Trainline" + }, + "bookingTime": "2017-09-19T13:42:00+02:00", + "cancelReservationUrl": "https://www.trainline.fr/tickets", + "modifiedTime": "2017-09-21T09:19:38+02:00", + "modifyReservationUrl": "https://www.trainline.fr/tickets", + "reservationFor": { + "@type": "TrainTrip", + "arrivalStation": { + "@type": "TrainStation", + "geo": { + "@type": "GeoCoordinates", + "latitude": 45.760559, + "longitude": 4.859355 + }, + "name": "Lyon Part-Dieu" + }, + "arrivalTime": "2017-09-29T19:52:00+02:00", + "departureStation": { + "@type": "TrainStation", + "geo": { + "@type": "GeoCoordinates", + "latitude": 43.832291, + "longitude": 4.365845 + }, + "name": "Nîmes Gare" + }, + "departureTime": "2017-09-29T18:26:00+02:00", + "trainCompany": { + "@type": "Organization", + "name": "SNCF" + }, + "trainName": "TGV", + "trainNumber": "5186" + }, + "reservationNumber": "XXX007", + "reservationStatus": "http://schema.org/ReservationConfirmed", + "reservedTicket": { + "@type": "Ticket", + "ticketToken": "aztecCode:somerandomdata DOE JOHN111110 00000", + "ticketedSeat": { + "@type": "Seat", + "seatNumber": "62", + "seatSection": "17", + "seatingType": "Economy" + }, + "underName": { + "@type": "Person", + "name": "John Doe" + } + }, + "underName": { + "@type": "Person", + "name": "John Doe" + }, + "url": "https://www.trainline.fr/tickets" + }, + { + "@context": "http://schema.org", + "@type": "TrainReservation", + "bookingAgent": { + "@type": "Organization", + "email": "somebody@trainline.fr", + "name": "Trainline" + }, + "bookingTime": "2017-09-19T13:42:00+02:00", + "cancelReservationUrl": "https://www.trainline.fr/tickets", + "modifiedTime": "2017-09-21T09:19:38+02:00", + "modifyReservationUrl": "https://www.trainline.fr/tickets", + "reservationFor": { + "@type": "TrainTrip", + "arrivalStation": { + "@type": "TrainStation", + "geo": { + "@type": "GeoCoordinates", + "latitude": 43.832291, + "longitude": 4.365845 + }, + "name": "Nîmes Gare" + }, + "arrivalTime": "2017-09-24T20:33:00+02:00", + "departureStation": { + "@type": "TrainStation", + "geo": { + "@type": "GeoCoordinates", + "latitude": 45.760559, + "longitude": 4.859355 + }, + "name": "Lyon Part-Dieu" + }, + "departureTime": "2017-09-24T19:09:00+02:00", + "trainCompany": { + "@type": "Organization", + "name": "SNCF" + }, + "trainName": "TGV", + "trainNumber": "5119" + }, + "reservationNumber": "XXX007", + "reservationStatus": "http://schema.org/ReservationConfirmed", + "reservedTicket": { + "@type": "Ticket", + "ticketToken": "aztecCode:somerandomdata DOE JOHN111110 00000", + "ticketedSeat": { + "@type": "Seat", + "seatNumber": "71", + "seatSection": "13", + "seatingType": "First" + }, + "underName": { + "@type": "Person", + "name": "John Doe" + } + }, + "underName": { + "@type": "Person", + "name": "John Doe" + }, + "url": "https://www.trainline.fr/tickets" + } +] diff --git a/plugins/messageviewer/bodypartformatter/semantic/extractorpostprocessor.cpp b/plugins/messageviewer/bodypartformatter/semantic/extractorpostprocessor.cpp index 88e87ffa..eabe7d03 100644 --- a/plugins/messageviewer/bodypartformatter/semantic/extractorpostprocessor.cpp +++ b/plugins/messageviewer/bodypartformatter/semantic/extractorpostprocessor.cpp @@ -1,173 +1,190 @@ /* Copyright (c) 2017 Volker Krause This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "extractorpostprocessor.h" #include "datatypes.h" #include "jsonlddocument.h" #include "airportdb/airportdb.h" #include #include +#include + void ExtractorPostprocessor::process(const QVector &data) { m_data.reserve(data.size()); for (auto d : data) { if (d.userType() == qMetaTypeId()) { d = processFlightReservation(d); } if (filterReservation(d)) { m_data.push_back(d); } } + + std::stable_sort(m_data.begin(), m_data.end(), [](const QVariant &lhs, const QVariant &rhs) { + return startDateTime(lhs) < startDateTime(rhs); + }); } QVector ExtractorPostprocessor::result() const { return m_data; } QVariant ExtractorPostprocessor::processProperty(QVariant obj, const char *name, QVariant (ExtractorPostprocessor::*processor)(QVariant) const) const { auto value = JsonLdDocument::readProperty(obj, name); value = (this->*processor)(value); JsonLdDocument::writeProperty(obj, name, value); return obj; } QVariant ExtractorPostprocessor::processFlightReservation(QVariant res) const { return processProperty(res, "reservationFor", &ExtractorPostprocessor::processFlight); } QVariant ExtractorPostprocessor::processFlight(QVariant flight) const { flight = processProperty(flight, "departureAirport", &ExtractorPostprocessor::processAirport); flight = processProperty(flight, "arrivalAirport", &ExtractorPostprocessor::processAirport); processFlightTime(flight, "boardingTime", "departureAirport"); processFlightTime(flight, "departureTime", "departureAirport"); processFlightTime(flight, "arrivalTime", "arrivalAirport"); return flight; } QVariant ExtractorPostprocessor::processAirport(QVariant airport) const { // complete missing IATA codes auto iataCode = JsonLdDocument::readProperty(airport, "iataCode").toString(); if (iataCode.isEmpty()) { iataCode = AirportDb::iataCodeFromName(JsonLdDocument::readProperty(airport, "name").toString()).toString(); if (!iataCode.isEmpty()) { JsonLdDocument::writeProperty(airport, "iataCode", iataCode); } } // complete missing geo coordinates auto geo = JsonLdDocument::readProperty(airport, "geo"); if (!geo.value().isValid()) { const auto coord = AirportDb::coordinateForAirport(AirportDb::IataCode{iataCode}); if (coord.isValid()) { geo = QVariant::fromValue(GeoCoordinates()); JsonLdDocument::writeProperty(geo, "latitude", coord.latitude); JsonLdDocument::writeProperty(geo, "longitude", coord.longitude); JsonLdDocument::writeProperty(airport, "geo", geo); } } return airport; } void ExtractorPostprocessor::processFlightTime(QVariant &flight, const char *timePropName, const char *airportPropName) const { const auto airport = JsonLdDocument::readProperty(flight, airportPropName); const auto iataCode = JsonLdDocument::readProperty(airport, "iataCode").toString(); if (iataCode.isEmpty()) { return; } auto dt = JsonLdDocument::readProperty(flight, timePropName).toDateTime(); if (!dt.isValid() || dt.timeSpec() == Qt::TimeZone) { return; } const auto tz = AirportDb::timezoneForAirport(AirportDb::IataCode{iataCode}); if (!tz.isValid()) { return; } // prefer our timezone over externally provided UTC offset, if they match if (dt.timeSpec() == Qt::OffsetFromUTC && tz.offsetFromUtc(dt) != dt.offsetFromUtc()) { return; } dt.setTimeSpec(Qt::TimeZone); dt.setTimeZone(tz); // if we updated from UTC offset to timezone spec here, QDateTime will compare equal // and the auto-generated property code will not actually update the property // so, clear the property first to force an update JsonLdDocument::writeProperty(flight, timePropName, QDateTime()); JsonLdDocument::writeProperty(flight, timePropName, dt); } bool ExtractorPostprocessor::filterReservation(const QVariant &res) const { const auto resFor = JsonLdDocument::readProperty(res, "reservationFor"); if (resFor.isNull()) { return false; } if (resFor.userType() == qMetaTypeId()) { return filterFlight(resFor); } else if (resFor.userType() == qMetaTypeId()) { return filterTrainTrip(resFor); } return true; } bool ExtractorPostprocessor::filterFlight(const QVariant &flight) const { const auto depDt = JsonLdDocument::readProperty(flight, "departureTime").toDateTime(); const auto arrDt = JsonLdDocument::readProperty(flight, "arrivalTime").toDateTime(); return filterAirport(JsonLdDocument::readProperty(flight, "departureAirport")) && filterAirport(JsonLdDocument::readProperty(flight, "arrivalAirport")) && depDt.isValid() && arrDt.isValid(); } bool ExtractorPostprocessor::filterAirport(const QVariant &airport) const { const auto iataCode = JsonLdDocument::readProperty(airport, "iataCode").toString(); const auto name = JsonLdDocument::readProperty(airport, "name").toString(); return !iataCode.isEmpty() || !name.isEmpty(); } bool ExtractorPostprocessor::filterTrainTrip(const QVariant &trip) const { const auto depDt = JsonLdDocument::readProperty(trip, "departureTime").toDateTime(); const auto arrDt = JsonLdDocument::readProperty(trip, "arrivalTime").toDateTime(); return filterAirport(JsonLdDocument::readProperty(trip, "departureStation")) && filterAirport(JsonLdDocument::readProperty(trip, "arrivalStation")) && depDt.isValid() && arrDt.isValid(); } bool ExtractorPostprocessor::filterTrainStation(const QVariant &station) const { return !JsonLdDocument::readProperty(station, "name").toString().isEmpty(); } + +QDateTime ExtractorPostprocessor::startDateTime(const QVariant& res) +{ + if (res.userType() == qMetaTypeId() || res.userType() == qMetaTypeId()) { + const auto trip = JsonLdDocument::readProperty(res, "reservationFor"); + return JsonLdDocument::readProperty(trip, "departureTime").toDateTime(); + } else if (res.userType() == qMetaTypeId()) { + return JsonLdDocument::readProperty(res, "checkinDate").toDateTime(); + } + return {}; +} diff --git a/plugins/messageviewer/bodypartformatter/semantic/extractorpostprocessor.h b/plugins/messageviewer/bodypartformatter/semantic/extractorpostprocessor.h index 005a00eb..1b7e5b35 100644 --- a/plugins/messageviewer/bodypartformatter/semantic/extractorpostprocessor.h +++ b/plugins/messageviewer/bodypartformatter/semantic/extractorpostprocessor.h @@ -1,50 +1,52 @@ /* Copyright (c) 2017 Volker Krause This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef EXTRACTORPOSTPROCESSOR_H #define EXTRACTORPOSTPROCESSOR_H #include #include /** Post-process extracted data to filter out garbage and augment data from other sources. */ class ExtractorPostprocessor { public: void process(const QVector &data); QVector result() const; private: QVariant processProperty(QVariant obj, const char *name, QVariant (ExtractorPostprocessor::*processor)(QVariant) const) const; QVariant processFlightReservation(QVariant res) const; QVariant processFlight(QVariant flight) const; QVariant processAirport(QVariant airport) const; void processFlightTime(QVariant &flight, const char *timePropName, const char *airportPropName) const; bool filterReservation(const QVariant &res) const; bool filterFlight(const QVariant &flight) const; bool filterAirport(const QVariant &airport) const; bool filterTrainTrip(const QVariant &trip) const; bool filterTrainStation(const QVariant &station) const; + static QDateTime startDateTime(const QVariant &res); + QVector m_data; }; #endif // EXTRACTORPOSTPROCESSOR_H