diff --git a/semantic/calendarhandler.cpp b/semantic/calendarhandler.cpp index 6f6ddd2..a15ced6 100644 --- a/semantic/calendarhandler.cpp +++ b/semantic/calendarhandler.cpp @@ -1,229 +1,259 @@ /* 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 "calendarhandler.h" #include "datatypes.h" #include "jsonlddocument.h" #include "semantic_debug.h" #include #include using namespace KCalCore; QDateTime CalendarHandler::startDateTime(const QVariant &reservation) { - if (reservation.userType() == qMetaTypeId() || reservation.userType() == qMetaTypeId()) { + if (reservation.userType() == qMetaTypeId() + || reservation.userType() == qMetaTypeId() + || reservation.userType() == qMetaTypeId()) { const auto trip = JsonLdDocument::readProperty(reservation, "reservationFor"); return JsonLdDocument::readProperty(trip, "departureTime").toDateTime(); } else if (reservation.userType() == qMetaTypeId()) { return JsonLdDocument::readProperty(reservation, "checkinDate").toDateTime(); } return {}; } Event::Ptr CalendarHandler::findEvent(const Calendar::Ptr &calendar, const QVariant &reservation) { const auto bookingRef = JsonLdDocument::readProperty(reservation, "reservationNumber").toString(); if (bookingRef.isEmpty()) { return {}; } auto dt = startDateTime(reservation); if (reservation.userType() == qMetaTypeId()) { dt = QDateTime(dt.date(), QTime()); } const auto events = calendar->events(dt); for (const auto &event : events) { if (event->dtStart() == dt && event->uid().startsWith(bookingRef)) { return event; } } return {}; } void CalendarHandler::fillEvent(const QVariant &reservation, const KCalCore::Event::Ptr &event) { const int typeId = reservation.userType(); if (typeId == qMetaTypeId()) { fillFlightReservation(reservation, event); } else if (typeId == qMetaTypeId()) { fillLodgingReservation(reservation, event); } else if (typeId == qMetaTypeId()) { fillTrainReservation(reservation, event); + } else if (typeId == qMetaTypeId()) { + fillBusReservation(reservation, event); } const auto bookingRef = JsonLdDocument::readProperty(reservation, "reservationNumber").toString(); if (!event->uid().startsWith(bookingRef)) { event->setUid(bookingRef + QLatin1Char('-') + event->uid()); } } void CalendarHandler::fillFlightReservation(const QVariant &reservation, const KCalCore::Event::Ptr &event) { const auto flight = JsonLdDocument::readProperty(reservation, "reservationFor"); const auto airline = JsonLdDocument::readProperty(flight, "airline"); const auto depPort = JsonLdDocument::readProperty(flight, "departureAirport"); const auto arrPort = JsonLdDocument::readProperty(flight, "arrivalAirport"); if (flight.isNull() || airline.isNull() || depPort.isNull() || arrPort.isNull()) { qCDebug(SEMANTIC_LOG) << "got invalid flight reservation"; return; } const auto flightNumber = JsonLdDocument::readProperty(airline, "iataCode").toString() + QLatin1Char(' ') + JsonLdDocument::readProperty(flight, "flightNumber").toString(); event->setSummary(i18n("Flight %1 from %2 to %3", flightNumber, JsonLdDocument::readProperty(depPort, "iataCode").toString(), JsonLdDocument::readProperty(arrPort, "iataCode").toString() )); event->setLocation(JsonLdDocument::readProperty(depPort, "name").toString()); fillGeoPosition(depPort, event); event->setDtStart(JsonLdDocument::readProperty(flight, "departureTime").toDateTime()); event->setDtEnd(JsonLdDocument::readProperty(flight, "arrivalTime").toDateTime()); event->setAllDay(false); const auto boardingTime = JsonLdDocument::readProperty(flight, "boardingTime").toDateTime(); const auto departureGate = JsonLdDocument::readProperty(flight, "departureGate").toString(); if (boardingTime.isValid()) { Alarm::Ptr alarm(new Alarm(event.data())); alarm->setStartOffset(Duration(event->dtStart(), boardingTime)); if (departureGate.isEmpty()) { alarm->setDisplayAlarm(i18n("Boarding for flight %1", flightNumber)); } else { alarm->setDisplayAlarm(i18n("Boarding for flight %1 at gate %2", flightNumber, departureGate)); } alarm->setEnabled(true); event->addAlarm(alarm); } QStringList desc; if (boardingTime.isValid()) { desc.push_back(i18n("Boarding time: %1", QLocale().toString(boardingTime.time(), QLocale::ShortFormat))); } if (!departureGate.isEmpty()) { desc.push_back(i18n("Departure gate: %1", departureGate)); } auto s = JsonLdDocument::readProperty(reservation, "boardingGroup").toString(); if (!s.isEmpty()) { desc.push_back(i18n("Boarding group: %1", s)); } s = JsonLdDocument::readProperty(reservation, "airplaneSeat").toString(); if (!s.isEmpty()) { desc.push_back(i18n("Seat: %1", s)); } s = JsonLdDocument::readProperty(reservation, "reservationNumber").toString(); if (!s.isEmpty()) { desc.push_back(i18n("Booking reference: %1", s)); } event->setDescription(desc.join(QLatin1Char('\n'))); } -void CalendarHandler::fillTrainReservation(const QVariant &reservation, const KCalCore::Event::Ptr &event) +void CalendarHandler::fillTripReservation(const QVariant &reservation, const KCalCore::Event::Ptr &event) { const auto trip = JsonLdDocument::readProperty(reservation, "reservationFor"); const auto depStation = JsonLdDocument::readProperty(trip, "departureStation"); const auto arrStation = JsonLdDocument::readProperty(trip, "arrivalStation"); - if (trip.isNull() || depStation.isNull() || arrStation.isNull()) { - return; - } - event->setSummary(i18n("Train %1 from %2 to %3", - JsonLdDocument::readProperty(trip, "trainNumber").toString(), - JsonLdDocument::readProperty(depStation, "name").toString(), - JsonLdDocument::readProperty(arrStation, "name").toString() - )); event->setLocation(JsonLdDocument::readProperty(depStation, "name").toString()); fillGeoPosition(depStation, event); event->setDtStart(JsonLdDocument::readProperty(trip, "departureTime").toDateTime()); event->setDtEnd(JsonLdDocument::readProperty(trip, "arrivalTime").toDateTime()); event->setAllDay(false); QStringList desc; auto s = JsonLdDocument::readProperty(trip, "departurePlatform").toString(); if (!s.isEmpty()) { desc.push_back(i18n("Departure platform: %1", s)); } const auto ticket = JsonLdDocument::readProperty(reservation, "reservedTicket"); const auto seat = JsonLdDocument::readProperty(ticket, "ticketedSeat"); s = JsonLdDocument::readProperty(seat, "seatSection").toString(); if (!s.isEmpty()) { desc.push_back(i18n("Coach: %1", s)); } s = JsonLdDocument::readProperty(seat, "seatNumber").toString(); if (!s.isEmpty()) { desc.push_back(i18n("Seat: %1", s)); } s = JsonLdDocument::readProperty(trip, "arrivalPlatform").toString(); if (!s.isEmpty()) { desc.push_back(i18n("Arrival platform: %1", s)); } s = JsonLdDocument::readProperty(reservation, "reservationNumber").toString(); if (!s.isEmpty()) { desc.push_back(i18n("Booking reference: %1", s)); } event->setDescription(desc.join(QLatin1Char('\n'))); } +void CalendarHandler::fillTrainReservation(const QVariant &reservation, const KCalCore::Event::Ptr &event) +{ + const auto trip = JsonLdDocument::readProperty(reservation, "reservationFor"); + const auto depStation = JsonLdDocument::readProperty(trip, "departureStation"); + const auto arrStation = JsonLdDocument::readProperty(trip, "arrivalStation"); + if (trip.isNull() || depStation.isNull() || arrStation.isNull()) { + return; + } + + event->setSummary(i18n("Train %1 from %2 to %3", + JsonLdDocument::readProperty(trip, "trainNumber").toString(), + JsonLdDocument::readProperty(depStation, "name").toString(), + JsonLdDocument::readProperty(arrStation, "name").toString() + )); + fillTripReservation(reservation, event); +} + +void CalendarHandler::fillBusReservation(const QVariant &reservation, const KCalCore::Event::Ptr &event) +{ + const auto trip = JsonLdDocument::readProperty(reservation, "reservationFor"); + const auto depStation = JsonLdDocument::readProperty(trip, "departureStation"); + const auto arrStation = JsonLdDocument::readProperty(trip, "arrivalStation"); + if (trip.isNull() || depStation.isNull() || arrStation.isNull()) { + return; + } + + event->setSummary(i18n("Bus %1 from %2 to %3", + JsonLdDocument::readProperty(trip, "busNumber").toString(), + JsonLdDocument::readProperty(depStation, "name").toString(), + JsonLdDocument::readProperty(arrStation, "name").toString() + )); + fillTripReservation(reservation, event); +} + void CalendarHandler::fillLodgingReservation(const QVariant &reservation, const KCalCore::Event::Ptr &event) { const auto lodgingBusiness = JsonLdDocument::readProperty(reservation, "reservationFor"); const auto address = JsonLdDocument::readProperty(lodgingBusiness, "address"); if (lodgingBusiness.isNull() || address.isNull()) { return; } event->setSummary(i18n("Hotel reservation: %1", JsonLdDocument::readProperty(lodgingBusiness, "name").toString() )); event->setLocation(i18n("%1, %2 %3, %4", JsonLdDocument::readProperty(address, "streetAddress").toString(), JsonLdDocument::readProperty(address, "postalCode").toString(), JsonLdDocument::readProperty(address, "addressLocality").toString(), JsonLdDocument::readProperty(address, "addressCountry").toString() )); fillGeoPosition(lodgingBusiness, event); const auto checkinDt = JsonLdDocument::readProperty(reservation, "checkinDate").toDateTime(); const auto checkoutDt = JsonLdDocument::readProperty(reservation, "checkoutDate").toDateTime(); event->setDtStart(QDateTime(checkinDt.date(), QTime())); event->setDtEnd(QDateTime(checkoutDt.date(), QTime())); event->setAllDay(true); event->setDescription(i18n("Check-in: %1\nCheck-out: %2\nBooking reference: %3", QLocale().toString(checkinDt.time(), QLocale::ShortFormat), QLocale().toString(checkoutDt.time(), QLocale::ShortFormat), JsonLdDocument::readProperty(reservation, "reservationNumber").toString() )); event->setTransparency(Event::Transparent); } void CalendarHandler::fillGeoPosition(const QVariant &place, const KCalCore::Event::Ptr &event) { const auto geo = JsonLdDocument::readProperty(place, "geo"); if (geo.isNull()) { return; } event->setHasGeo(true); event->setGeoLatitude(JsonLdDocument::readProperty(geo, "latitude").toFloat()); event->setGeoLongitude(JsonLdDocument::readProperty(geo, "longitude").toFloat()); } diff --git a/semantic/calendarhandler.h b/semantic/calendarhandler.h index 24bbc2d..94f8fbc 100644 --- a/semantic/calendarhandler.h +++ b/semantic/calendarhandler.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 CALENDARHANDLER_H #define CALENDARHANDLER_H #include #include class QVariant; /** Methods for converting between ical events and JSON-LD booking data. */ class CalendarHandler { public: /** Returns the start time associated with the given reservation. */ static QDateTime startDateTime(const QVariant &reservation); /** Attempts to find an event in @p calendar for @p reservation. */ static KCalCore::Event::Ptr findEvent(const KCalCore::Calendar::Ptr &calendar, const QVariant &reservation); /** Fills @p event with details of @p reservation. * Can be used on new events or to update existing ones. */ static void fillEvent(const QVariant &reservation, const KCalCore::Event::Ptr &event); private: static void fillFlightReservation(const QVariant &reservation, const KCalCore::Event::Ptr &event); + static void fillTripReservation(const QVariant &reservation, const KCalCore::Event::Ptr &event); static void fillTrainReservation(const QVariant &reservation, const KCalCore::Event::Ptr &event); + static void fillBusReservation(const QVariant &reservation, const KCalCore::Event::Ptr &event); static void fillLodgingReservation(const QVariant &reservation, const KCalCore::Event::Ptr &event); static void fillGeoPosition(const QVariant &place, const KCalCore::Event::Ptr &event); }; #endif // CALENDARHANDLER_H diff --git a/semantic/datatypes.cpp b/semantic/datatypes.cpp index 2e35e83..63b04d0 100644 --- a/semantic/datatypes.cpp +++ b/semantic/datatypes.cpp @@ -1,104 +1,119 @@ /* 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; } +bool BusStation::operator!=(const BusStation &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); } + +QString BusTrip::departureTimeLocalized() const +{ + return QLocale().toString(m_departureTime, QLocale::ShortFormat); +} + +QString BusTrip::arrivalTimeLocalized() const +{ + return QLocale().toString(m_arrivalTime, QLocale::ShortFormat); +} diff --git a/semantic/datatypes.h b/semantic/datatypes.h index f016883..87b638d 100644 --- a/semantic/datatypes.h +++ b/semantic/datatypes.h @@ -1,223 +1,260 @@ /* 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 #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; }; +class BusStation : protected Place +{ + SEMANTIC_GADGET + SEMANTIC_PROPERTY(QString, name) +public: + bool operator!=(const BusStation &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 BusTrip +{ + SEMANTIC_GADGET + SEMANTIC_PROPERTY(QString, arrivalPlatform) + SEMANTIC_PROPERTY(BusStation, arrivalStation) + SEMANTIC_PROPERTY(QDateTime, arrivalTime) + SEMANTIC_PROPERTY(QString, departurePlatform) + SEMANTIC_PROPERTY(BusStation, departureStation) + SEMANTIC_PROPERTY(QDateTime, departureTime) + SEMANTIC_PROPERTY(QString, busName) + SEMANTIC_PROPERTY(QString, busNumber) + + 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) SEMANTIC_PROPERTY(QString, ticketToken) }; class Reservation { Q_GADGET SEMANTIC_PROPERTY(QString, reservationNumber) SEMANTIC_PROPERTY(QVariant, reservationFor) SEMANTIC_PROPERTY(QVariant, reservedTicket) // Google extension SEMANTIC_PROPERTY(QUrl, cancelReservationUrl) SEMANTIC_PROPERTY(QUrl, modifyReservationUrl) SEMANTIC_PROPERTY(QString, ticketToken) SEMANTIC_PROPERTY(QUrl, url) }; 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) SEMANTIC_PROPERTY(QUrl, ticketDownloadUrl) }; class TrainReservation : protected Reservation { SEMANTIC_GADGET }; +class BusReservation : 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) +Q_DECLARE_METATYPE(TrainReservation) +Q_DECLARE_METATYPE(BusStation) +Q_DECLARE_METATYPE(BusTrip) +Q_DECLARE_METATYPE(BusReservation) #undef SEMANTIC_GADGET #endif // DATATYPES_H diff --git a/semantic/extractorpostprocessor.cpp b/semantic/extractorpostprocessor.cpp index 3edbae3..3bed110 100644 --- a/semantic/extractorpostprocessor.cpp +++ b/semantic/extractorpostprocessor.cpp @@ -1,238 +1,242 @@ /* 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 "calendarhandler.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); } else if (d.userType() == qMetaTypeId()) { d = processReservation(d); } else if (d.userType() == qMetaTypeId()) { d = processReservation(d); + } else if (d.userType() == qMetaTypeId()) { + d = processReservation(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 CalendarHandler::startDateTime(lhs) < CalendarHandler::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 { res = processReservation(res); res = processProperty(res, "reservationFor", &ExtractorPostprocessor::processFlight); return res; } QVariant ExtractorPostprocessor::processFlight(QVariant flight) const { flight = processProperty(flight, "departureAirport", &ExtractorPostprocessor::processAirport); flight = processProperty(flight, "arrivalAirport", &ExtractorPostprocessor::processAirport); flight = processProperty(flight, "airline", &ExtractorPostprocessor::processAirline); processFlightTime(flight, "boardingTime", "departureAirport"); processFlightTime(flight, "departureTime", "departureAirport"); processFlightTime(flight, "arrivalTime", "arrivalAirport"); return flight; } QVariant ExtractorPostprocessor::processAirport(QVariant airport) const { // clean up name const auto name = JsonLdDocument::readProperty(airport, "name").toString(); JsonLdDocument::writeProperty(airport, "name", name.trimmed()); // complete missing IATA codes auto iataCode = JsonLdDocument::readProperty(airport, "iataCode").toString(); if (iataCode.isEmpty()) { iataCode = AirportDb::iataCodeFromName(name).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; } QVariant ExtractorPostprocessor::processAirline(QVariant airline) const { const auto name = JsonLdDocument::readProperty(airline, "name").toString(); JsonLdDocument::writeProperty(airline, "name", name.trimmed()); return airline; } 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); } QVariant ExtractorPostprocessor::processReservation(QVariant res) const { const auto viewUrl = JsonLdDocument::readProperty(res, "url").toUrl(); const auto modUrl = JsonLdDocument::readProperty(res, "modifyReservationUrl").toUrl(); const auto cancelUrl = JsonLdDocument::readProperty(res, "cancelReservationUrl").toUrl(); // remove duplicated urls if (modUrl.isValid() && viewUrl == modUrl) { JsonLdDocument::removeProperty(res, "modifyReservationUrl"); } if (cancelUrl.isValid() && viewUrl == cancelUrl) { JsonLdDocument::removeProperty(res, "cancelReservationUrl"); } // move ticketToken to Ticket (Google vs. schema.org difference) const auto token = JsonLdDocument::readProperty(res, "ticketToken").toString(); if (!token.isEmpty()) { auto ticket = JsonLdDocument::readProperty(res, "reservedTicket"); if (ticket.isNull()) { ticket = QVariant::fromValue(Ticket{}); } if (JsonLdDocument::readProperty(ticket, "ticketToken").toString().isEmpty()) { JsonLdDocument::writeProperty(ticket, "ticketToken", token); JsonLdDocument::writeProperty(res, "reservedTicket", ticket); } } return res; } 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 filterTrainOrBusTrip(resFor); + } else if (resFor.userType() == qMetaTypeId()) { + return filterTrainOrBusTrip(resFor); } if (res.userType() == qMetaTypeId()) { return filterLodgingReservation(res); } return true; } bool ExtractorPostprocessor::filterLodgingReservation(const QVariant &res) const { const auto checkinDate = JsonLdDocument::readProperty(res, "checkinDate").toDateTime(); const auto checkoutDate = JsonLdDocument::readProperty(res, "checkoutDate").toDateTime(); return checkinDate.isValid() && checkoutDate.isValid(); } 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 +bool ExtractorPostprocessor::filterTrainOrBusTrip(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")) + return filterTrainOrBusStation(JsonLdDocument::readProperty(trip, "departureStation")) + && filterTrainOrBusStation(JsonLdDocument::readProperty(trip, "arrivalStation")) && depDt.isValid() && arrDt.isValid(); } -bool ExtractorPostprocessor::filterTrainStation(const QVariant &station) const +bool ExtractorPostprocessor::filterTrainOrBusStation(const QVariant &station) const { return !JsonLdDocument::readProperty(station, "name").toString().isEmpty(); } diff --git a/semantic/extractorpostprocessor.h b/semantic/extractorpostprocessor.h index b37028c..61b00e9 100644 --- a/semantic/extractorpostprocessor.h +++ b/semantic/extractorpostprocessor.h @@ -1,53 +1,53 @@ /* 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; QVariant processAirline(QVariant airline) const; void processFlightTime(QVariant &flight, const char *timePropName, const char *airportPropName) const; QVariant processReservation(QVariant res) const; bool filterReservation(const QVariant &res) const; bool filterLodgingReservation(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; + bool filterTrainOrBusTrip(const QVariant &trip) const; + bool filterTrainOrBusStation(const QVariant &station) const; QVector m_data; }; #endif // EXTRACTORPOSTPROCESSOR_H diff --git a/semantic/jsonlddocument.cpp b/semantic/jsonlddocument.cpp index 63b0c6a..ac76430 100644 --- a/semantic/jsonlddocument.cpp +++ b/semantic/jsonlddocument.cpp @@ -1,226 +1,229 @@ /* 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 "jsonlddocument.h" #include "datatypes.h" #include "semantic_debug.h" #include #include #include static QVariant createInstance(const QJsonObject &obj); // Eurowings workarounds... static const char *fallbackDateTimePattern[] = { "yyyy-MM-dd HH:mm:ss", "yyyy-MM-dd HH:mm", "MM-dd-yyyy HH:mm" // yes, seriously ;( }; static const auto fallbackDateTimePatternCount = sizeof(fallbackDateTimePattern) / sizeof(const char *); static QVariant propertyValue(const QMetaProperty &prop, const QJsonValue &v) { switch (prop.type()) { case QVariant::String: return v.toString(); case QVariant::DateTime: { auto str = v.toString(); auto dt = QDateTime::fromString(str, Qt::ISODate); for (unsigned int i = 0; i < fallbackDateTimePatternCount && dt.isNull(); ++i) { dt = QDateTime::fromString(str, QString::fromLatin1(fallbackDateTimePattern[i])); } if (dt.isNull()) { qCDebug(SEMANTIC_LOG) << "Datetime parsing failed for" << str; } return dt; } case QVariant::Double: return v.toDouble(); case QVariant::Url: return QUrl(v.toString()); default: break; } if (prop.type() == qMetaTypeId()) { return v.toDouble(); } return createInstance(v.toObject()); } template static QVariant createInstance(const QJsonObject &obj) { T t; for (auto it = obj.begin(); it != obj.end(); ++it) { if (it.key().startsWith(QLatin1Char('@'))) { continue; } const auto idx = T::staticMetaObject.indexOfProperty(it.key().toLatin1()); if (idx < 0) { qCDebug(SEMANTIC_LOG) << "property" << it.key() << "could not be set on object of type" << T::staticMetaObject.className(); continue; } const auto prop = T::staticMetaObject.property(idx); const auto value = propertyValue(prop, it.value()); prop.writeOnGadget(&t, value); } return QVariant::fromValue(t); } #define MAKE_FACTORY(Class) \ if (type == QLatin1String(#Class)) \ return createInstance(obj) static QVariant createInstance(const QJsonObject &obj) { const auto type = obj.value(QLatin1String("@type")).toString(); MAKE_FACTORY(GeoCoordinates); MAKE_FACTORY(Airline); MAKE_FACTORY(Airport); MAKE_FACTORY(FlightReservation); MAKE_FACTORY(Flight); MAKE_FACTORY(LodgingBusiness); MAKE_FACTORY(LodgingReservation); MAKE_FACTORY(PostalAddress); MAKE_FACTORY(Seat); MAKE_FACTORY(Ticket); MAKE_FACTORY(TrainStation); MAKE_FACTORY(TrainTrip); MAKE_FACTORY(TrainReservation); + MAKE_FACTORY(BusStation); + MAKE_FACTORY(BusTrip); + MAKE_FACTORY(BusReservation); return {}; } #undef MAKE_FACTORY QVector JsonLdDocument::fromJson(const QJsonArray &array) { QVector l; l.reserve(array.size()); for (const auto &obj : array) { const auto v = createInstance(obj.toObject()); if (!v.isNull()) { l.push_back(v); } } return l; } static bool valueIsNull(const QVariant &v) { if (v.type() == QVariant::Url) { return !v.toUrl().isValid(); } return v.isNull(); } static QJsonValue toJson(const QVariant &v) { const auto mo = QMetaType(v.userType()).metaObject(); if (!mo) { // basic types switch (v.type()) { case QVariant::String: return v.toString(); case QVariant::Double: return v.toDouble(); case QVariant::Int: return v.toInt(); case QVariant::DateTime: return v.toDateTime().toString(Qt::ISODate); case QVariant::Url: return v.toUrl().toString(); default: break; } if (v.userType() == qMetaTypeId()) { return v.toFloat(); } qCDebug(SEMANTIC_LOG) << "unhandled value:" << v; return {}; } // composite types QJsonObject obj; obj.insert(QStringLiteral("@type"), QString::fromUtf8(mo->className())); for (int i = 0; i < mo->propertyCount(); ++i) { const auto prop = mo->property(i); if (!prop.isStored()) { continue; } const auto value = prop.readOnGadget(v.constData()); if (!valueIsNull(value)) { obj.insert(QString::fromUtf8(prop.name()), toJson(value)); } } return obj; } QJsonArray JsonLdDocument::toJson(const QVector &data) { QJsonArray a; for (const auto &d : data) { const auto value = toJson(d); if (!value.isObject()) { continue; } auto obj = value.toObject(); obj.insert(QStringLiteral("@context"), QStringLiteral("http://schema.org")); a.push_back(obj); } return a; } QVariant JsonLdDocument::readProperty(const QVariant &obj, const char *name) { const auto mo = QMetaType(obj.userType()).metaObject(); if (!mo) { return {}; } const auto idx = mo->indexOfProperty(name); if (idx < 0) { return {}; } const auto prop = mo->property(idx); return prop.readOnGadget(obj.constData()); } void JsonLdDocument::writeProperty(QVariant &obj, const char *name, const QVariant &value) { const auto mo = QMetaType(obj.userType()).metaObject(); if (!mo) { return; } const auto idx = mo->indexOfProperty(name); if (idx < 0) { return; } const auto prop = mo->property(idx); prop.writeOnGadget(obj.data(), value); } void JsonLdDocument::removeProperty(QVariant &obj, const char *name) { writeProperty(obj, name, QVariant()); }