diff --git a/plugins/messageviewer/bodypartformatter/semantic/datatypes.cpp b/plugins/messageviewer/bodypartformatter/semantic/datatypes.cpp index b5a90223..2e35e832 100644 --- a/plugins/messageviewer/bodypartformatter/semantic/datatypes.cpp +++ b/plugins/messageviewer/bodypartformatter/semantic/datatypes.cpp @@ -1,95 +1,104 @@ /* 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 "datatypes.h" #include #include #include GeoCoordinates::GeoCoordinates() : m_latitude(NAN) , m_longitude(NAN) { } bool GeoCoordinates::isValid() const { return !std::isnan(m_latitude) && !std::isnan(m_longitude); } bool Place::operator!=(const Place &other) const { return m_geo != other.m_geo || m_address != other.m_address; } bool Airport::operator!=(const Airport &other) const { return m_iataCode != other.m_iataCode || m_name != other.m_name || Place::operator!=(other); } bool Airline::operator!=(const Airline &other) const { return m_iataCode != other.m_iataCode || m_name != other.m_name; } bool TrainStation::operator!=(const TrainStation &other) const { return m_name != other.m_name; } static QString localizedDateTime(const QDateTime &dt) { auto s = QLocale().toString(dt, QLocale::ShortFormat); if (dt.timeSpec() == Qt::TimeZone || dt.timeSpec() == Qt::OffsetFromUTC) { s += QLatin1Char(' ') + dt.timeZone().abbreviation(dt); } return s; } QString Flight::departureTimeLocalized() const { return localizedDateTime(m_departureTime); } QString Flight::arrivalTimeLocalized() const { return localizedDateTime(m_arrivalTime); } +QString Flight::boardingTimeLocalized() const +{ + auto s = QLocale().toString(m_boardingTime.time(), QLocale::ShortFormat); + if (m_boardingTime.timeSpec() == Qt::TimeZone || m_boardingTime.timeSpec() == Qt::OffsetFromUTC) { + s += QLatin1Char(' ') + m_boardingTime.timeZone().abbreviation(m_boardingTime); + } + return s; +} + QString LodgingReservation::checkinDateLocalized() const { return QLocale().toString(m_checkinDate.date(), QLocale::ShortFormat); } QString LodgingReservation::checkoutDateLocalized() const { return QLocale().toString(m_checkoutDate.date(), QLocale::ShortFormat); } QString TrainTrip::departureTimeLocalized() const { return QLocale().toString(m_departureTime, QLocale::ShortFormat); } QString TrainTrip::arrivalTimeLocalized() const { return QLocale().toString(m_arrivalTime, QLocale::ShortFormat); } diff --git a/plugins/messageviewer/bodypartformatter/semantic/datatypes.h b/plugins/messageviewer/bodypartformatter/semantic/datatypes.h index 6aff23f9..7925054a 100644 --- a/plugins/messageviewer/bodypartformatter/semantic/datatypes.h +++ b/plugins/messageviewer/bodypartformatter/semantic/datatypes.h @@ -1,196 +1,214 @@ /* 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 DATATYPES_H #define DATATYPES_H #include #include #include #define SEMANTIC_GADGET \ Q_GADGET \ Q_PROPERTY(QString className READ className STORED false CONSTANT) \ inline QString className() const { return QString::fromUtf8(staticMetaObject.className()); } #define SEMANTIC_PROPERTY(Type, Name) \ Q_PROPERTY(Type Name MEMBER m_##Name) \ Type m_##Name; /** @file * The classes in here could possibly be auto-generated from the ontology defined by http://schema.org... */ class GeoCoordinates { SEMANTIC_GADGET SEMANTIC_PROPERTY(float, latitude) SEMANTIC_PROPERTY(float, longitude) public: GeoCoordinates(); bool isValid() const; }; class PostalAddress { SEMANTIC_GADGET SEMANTIC_PROPERTY(QString, streetAddress) SEMANTIC_PROPERTY(QString, addressLocality) SEMANTIC_PROPERTY(QString, postalCode) SEMANTIC_PROPERTY(QString, addressCountry) }; class Place { Q_GADGET SEMANTIC_PROPERTY(QVariant, address) SEMANTIC_PROPERTY(QVariant, geo) public: bool operator!=(const Place &other) const; }; class Airport : protected Place { SEMANTIC_GADGET SEMANTIC_PROPERTY(QString, name) SEMANTIC_PROPERTY(QString, iataCode) public: bool operator!=(const Airport &other) const; }; class Airline { SEMANTIC_GADGET SEMANTIC_PROPERTY(QString, name) SEMANTIC_PROPERTY(QString, iataCode) public: bool operator!=(const Airline &other) const; }; class TrainStation : protected Place { SEMANTIC_GADGET SEMANTIC_PROPERTY(QString, name) public: bool operator!=(const TrainStation &other) const; }; +/** + * @see https://schema.org/Flight + * @see https://developers.google.com/gmail/markup/reference/flight-reservation + */ class Flight { SEMANTIC_GADGET SEMANTIC_PROPERTY(QString, flightNumber) SEMANTIC_PROPERTY(Airline, airline) SEMANTIC_PROPERTY(Airport, departureAirport) SEMANTIC_PROPERTY(QDateTime, departureTime) SEMANTIC_PROPERTY(Airport, arrivalAirport) SEMANTIC_PROPERTY(QDateTime, arrivalTime) + // Google extension for boarding pass data + SEMANTIC_PROPERTY(QDateTime, boardingTime) + SEMANTIC_PROPERTY(QString, departureGate) + Q_PROPERTY(QString departureTimeLocalized READ departureTimeLocalized STORED false CONSTANT) Q_PROPERTY(QString arrivalTimeLocalized READ arrivalTimeLocalized STORED false CONSTANT) + Q_PROPERTY(QString boardingTimeLocalized READ boardingTimeLocalized STORED false CONSTANT) private: QString departureTimeLocalized() const; QString arrivalTimeLocalized() const; + QString boardingTimeLocalized() const; }; class LodgingBusiness: protected Place { SEMANTIC_GADGET SEMANTIC_PROPERTY(QString, name) }; class TrainTrip { SEMANTIC_GADGET SEMANTIC_PROPERTY(QString, arrivalPlatform) SEMANTIC_PROPERTY(TrainStation, arrivalStation) SEMANTIC_PROPERTY(QDateTime, arrivalTime) SEMANTIC_PROPERTY(QString, departurePlatform) SEMANTIC_PROPERTY(TrainStation, departureStation) SEMANTIC_PROPERTY(QDateTime, departureTime) SEMANTIC_PROPERTY(QString, trainName) SEMANTIC_PROPERTY(QString, trainNumber) Q_PROPERTY(QString departureTimeLocalized READ departureTimeLocalized STORED false CONSTANT) Q_PROPERTY(QString arrivalTimeLocalized READ arrivalTimeLocalized STORED false CONSTANT) private: QString departureTimeLocalized() const; QString arrivalTimeLocalized() const; }; class Seat { SEMANTIC_GADGET SEMANTIC_PROPERTY(QString, seatNumber) SEMANTIC_PROPERTY(QString, seatRow) SEMANTIC_PROPERTY(QString, seatSection) }; class Ticket { SEMANTIC_GADGET SEMANTIC_PROPERTY(QVariant, ticketedSeat) }; class Reservation { Q_GADGET SEMANTIC_PROPERTY(QString, reservationNumber) SEMANTIC_PROPERTY(QVariant, reservationFor) SEMANTIC_PROPERTY(QVariant, reservedTicket) }; class LodgingReservation : protected Reservation { SEMANTIC_GADGET SEMANTIC_PROPERTY(QDateTime, checkinDate) SEMANTIC_PROPERTY(QDateTime, checkoutDate) Q_PROPERTY(QString checkinDateLocalized READ checkinDateLocalized STORED false CONSTANT) Q_PROPERTY(QString checkoutDateLocalized READ checkoutDateLocalized STORED false CONSTANT) private: QString checkinDateLocalized() const; QString checkoutDateLocalized() const; }; +/** + * @see https://schema.org/FlightReservation + * @see https://developers.google.com/gmail/markup/reference/flight-reservation + */ class FlightReservation : protected Reservation { SEMANTIC_GADGET + + // Google extensions + SEMANTIC_PROPERTY(QString, airplaneSeat) + SEMANTIC_PROPERTY(QString, boardingGroup) }; class TrainReservation : protected Reservation { SEMANTIC_GADGET }; Q_DECLARE_METATYPE(GeoCoordinates) Q_DECLARE_METATYPE(Airport) Q_DECLARE_METATYPE(Airline) Q_DECLARE_METATYPE(Flight) Q_DECLARE_METATYPE(FlightReservation) Q_DECLARE_METATYPE(LodgingBusiness) Q_DECLARE_METATYPE(LodgingReservation) Q_DECLARE_METATYPE(PostalAddress) Q_DECLARE_METATYPE(Seat) Q_DECLARE_METATYPE(Ticket) Q_DECLARE_METATYPE(TrainStation) Q_DECLARE_METATYPE(TrainTrip) #undef SEMANTIC_GADGET #endif // DATATYPES_H diff --git a/plugins/messageviewer/bodypartformatter/semantic/extractorpostprocessor.cpp b/plugins/messageviewer/bodypartformatter/semantic/extractorpostprocessor.cpp index 753b7c1a..88e87ffa 100644 --- a/plugins/messageviewer/bodypartformatter/semantic/extractorpostprocessor.cpp +++ b/plugins/messageviewer/bodypartformatter/semantic/extractorpostprocessor.cpp @@ -1,172 +1,173 @@ /* 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 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); } } } 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(); } diff --git a/plugins/messageviewer/bodypartformatter/semantic/templates/flightreservation.html b/plugins/messageviewer/bodypartformatter/semantic/templates/flightreservation.html index 9366754e..65a6fc2e 100644 --- a/plugins/messageviewer/bodypartformatter/semantic/templates/flightreservation.html +++ b/plugins/messageviewer/bodypartformatter/semantic/templates/flightreservation.html @@ -1,24 +1,41 @@ {% if item.reservationFor.departureAirport.iataCode %} {% else %} {% endif %} {% if item.reservationFor.arrivalAirport.iataCode %} {% else %} {% endif %} +
{{ item.reservationFor.departureAirport.iataCode }} {{ item.reservationFor.departureAirport.name }} {{ item.reservationFor.arrivalAirport.iataCode }} {{ item.reservationFor.arrivalAirport.name }}
{{ item.reservationFor.departureTimeLocalized }} {{ item.reservationFor.airline.iataCode }} {{ item.reservationFor.flightNumber }} {{ item.reservationFor.arrivalTimeLocalized }}
+ {% if item.reservationFor.departureGate %} + {% i18n "Gate: %1" item.reservationFor.departureGate %} + {% endif %} + + {% if item.reservationFor.boardingTimeLocalized %} + {% i18n "Boarding: %1" item.reservationFor.boardingTimeLocalized %} + {% endif %} + + {% if item.boardingGroup %} + {% i18n "Group: %1" item.boardingGroup %} + {% endif %} + + {% if item.airplaneSeat %} + {% i18n "Seat: %1" item.airplaneSeat %} + {% endif %} +