diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 50f9d04..3648329 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,157 +1,159 @@ set(KDE_INSTALL_INCLUDEDIR_PIM ${KDE_INSTALL_INCLUDEDIR}/KPim) add_subdirectory(knowledgedb-generator) if(TARGET Poppler::Core) set(HAVE_POPPLER ON) if (${Poppler_VERSION} VERSION_GREATER 0.57) set(HAVE_POPPLER_0_58 ON) endif() endif() if (TARGET zxing::libzxing) set(HAVE_ZXING ON) endif() if (ZLIB_FOUND) set(HAVE_ZLIB ON) endif() if (TARGET KF5::CalendarCore) set(HAVE_KCAL ON) endif() if (TARGET KF5::Contacts) set(HAVE_KCONTACTS ON) endif() configure_file(config-kitinerary.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-kitinerary.h) set(kitinerary_lib_srcs datatypes/bustrip.cpp datatypes/datatypes.cpp datatypes/flight.cpp datatypes/organization.cpp datatypes/person.cpp datatypes/place.cpp datatypes/reservation.cpp datatypes/ticket.cpp datatypes/traintrip.cpp datatypes/visit.cpp knowledgedb/airportdb.cpp knowledgedb/countrydb.cpp knowledgedb/knowledgedb.cpp knowledgedb/timezonedb.cpp knowledgedb/trainstationdb.cpp barcodedecoder.cpp calendarhandler.cpp extractor.cpp extractorengine.cpp extractorfilter.cpp extractorpreprocessor.cpp extractorpostprocessor.cpp extractorrepository.cpp iatabcbpparser.cpp jsonlddocument.cpp jsonldimportfilter.cpp mergeutil.cpp pdfdocument.cpp + sortutil.cpp structureddataextractor.cpp uic9183parser.cpp ) qt5_add_resources(kitinerary_lib_srcs extractors/extractors.qrc) ecm_qt_declare_logging_category(kitinerary_lib_srcs HEADER logging.h IDENTIFIER KItinerary::Log CATEGORY_NAME org.kde.kitinerary) kde_source_files_enable_exceptions(barcodedecoder.cpp) add_library(KPimItinerary SHARED ${kitinerary_lib_srcs}) add_library(KPim::Itinerary ALIAS KPimItinerary) generate_export_header(KPimItinerary BASE_NAME KItinerary) set_target_properties(KPimItinerary PROPERTIES VERSION ${KITINERARY_VERSION_STRING} SOVERSION ${KITINERARY_SOVERSION} EXPORT_NAME Itinerary ) target_include_directories(KPimItinerary INTERFACE "$") target_include_directories(KPimItinerary PUBLIC "$") target_link_libraries(KPimItinerary PUBLIC Qt5::Core KF5::Mime PRIVATE Qt5::Qml KF5::I18n KPim::PkPass ) if (HAVE_POPPLER) target_link_libraries(KPimItinerary PRIVATE Poppler::Core) endif() if (HAVE_ZXING) target_link_libraries(KPimItinerary PRIVATE zxing::libzxing) endif() if (HAVE_ZLIB) target_link_libraries(KPimItinerary PRIVATE ${ZLIB_LIBRARIES}) endif() if (HAVE_KCAL) target_link_libraries(KPimItinerary PUBLIC KF5::CalendarCore) endif() if (HAVE_KCONTACTS) target_link_libraries(KPimItinerary PUBLIC KF5::Contacts) endif() ecm_generate_headers(KItinerary_FORWARDING_HEADERS HEADER_NAMES BarcodeDecoder CalendarHandler Extractor ExtractorEngine ExtractorPreprocessor ExtractorPostprocessor ExtractorRepository IataBcbpParser JsonLdDocument MergeUtil PdfDocument + SortUtil StructuredDataExtractor Uic9183Parser PREFIX KItinerary REQUIRED_HEADERS KItinerary_HEADERS ) ecm_generate_headers(KItinerary_KnowledgeDb_FORWARDING_HEADERS HEADER_NAMES AirportDb CountryDb KnowledgeDb TrainStationDb PREFIX KItinerary REQUIRED_HEADERS KItinerary_KnowledgeDb_HEADERS RELATIVE knowledgedb ) ecm_generate_headers(KItinerary_Datatypes_FORWARDING_HEADERS HEADER_NAMES BusTrip Datatypes Flight Organization Reservation Person Place Ticket TrainTrip Visit PREFIX KItinerary REQUIRED_HEADERS KItinerary_Datatypes_HEADERS RELATIVE datatypes ) install(TARGETS KPimItinerary EXPORT KPimItineraryTargets ${INSTALL_TARGETS_DEFAULT_ARGS}) install(FILES ${KItinerary_FORWARDING_HEADERS} ${KItinerary_KnowledgeDb_FORWARDING_HEADERS} ${KItinerary_Datatypes_FORWARDING_HEADERS} DESTINATION ${KDE_INSTALL_INCLUDEDIR_PIM}/KItinerary ) install(FILES ${KItinerary_HEADERS} ${KItinerary_AirportDb_HEADERS} ${KItinerary_Datatypes_HEADERS} ${KItinerary_KnowledgeDb_HEADERS} ${CMAKE_CURRENT_BINARY_DIR}/kitinerary_export.h DESTINATION ${KDE_INSTALL_INCLUDEDIR_PIM}/kitinerary ) diff --git a/src/calendarhandler.cpp b/src/calendarhandler.cpp index 61730e3..66d2da7 100644 --- a/src/calendarhandler.cpp +++ b/src/calendarhandler.cpp @@ -1,317 +1,296 @@ /* 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 "config-kitinerary.h" #include "calendarhandler.h" #include "jsonlddocument.h" #include "logging.h" #include "mergeutil.h" +#include "sortutil.h" #include #include #include #include #include #include #include #ifdef HAVE_KCAL #include #include #include #endif #ifdef HAVE_KCONTACTS #include #endif #include #include #include #include using namespace KItinerary; #ifdef HAVE_KCAL using namespace KCalCore; static void fillFlightReservation(const FlightReservation &reservation, const KCalCore::Event::Ptr &event); static void fillTripReservation(const QVariant &reservation, const KCalCore::Event::Ptr &event); static void fillTrainReservation(const TrainReservation &reservation, const KCalCore::Event::Ptr &event); static void fillBusReservation(const BusReservation &reservation, const KCalCore::Event::Ptr &event); static void fillLodgingReservation(const LodgingReservation &reservation, const KCalCore::Event::Ptr &event); static void fillGeoPosition(const QVariant &place, const KCalCore::Event::Ptr &event); #endif QDateTime CalendarHandler::startDateTime(const QVariant &reservation) { - if (JsonLd::isA(reservation)) { - const auto flight = reservation.value().reservationFor().value(); - if (flight.departureTime().isValid()) { - return flight.departureTime(); - } - return QDateTime(flight.departureDay(), QTime()); - } - if (JsonLd::isA(reservation)) { - return reservation.value().reservationFor().value().departureTime(); - } - if (JsonLd::isA(reservation)) { - return reservation.value().reservationFor().value().departureTime(); - } - if (JsonLd::isA(reservation)) { - return reservation.value().checkinTime(); - } - if (JsonLd::isA(reservation)) { - return reservation.value().startTime(); - } - if (JsonLd::isA(reservation)) { - return reservation.value().arrivalTime(); - } - return {}; + return SortUtil::startDateTime(reservation); } QSharedPointer CalendarHandler::findEvent(const QSharedPointer &calendar, const QVariant &reservation) { #ifdef HAVE_KCAL if (!JsonLd::canConvert(reservation)) { return {}; } auto bookingRef = JsonLd::convert(reservation).reservationNumber(); if (bookingRef.isEmpty()) { return {}; } bookingRef.prepend(QLatin1String("KIT-")); - const auto dt = startDateTime(reservation).date(); + const auto dt = SortUtil::startDateTime(reservation).date(); const auto events = calendar->events(dt); for (const auto &event : events) { if (!event->uid().startsWith(bookingRef)) { continue; } const auto otherRes = CalendarHandler::reservationForEvent(event); if (MergeUtil::isSame(otherRes, reservation)) { return event; } } #else Q_UNUSED(calendar); Q_UNUSED(reservation); #endif return {}; } QVariant CalendarHandler::reservationForEvent(const QSharedPointer &event) { #ifdef HAVE_KCAL const auto payload = event->customProperty("KITINERARY", "RESERVATION").toUtf8(); const auto json = QJsonDocument::fromJson(payload).array(); const auto data = JsonLdDocument::fromJson(json); if (data.size() != 1) { return {}; } return data.at(0); #else Q_UNUSED(event); return {}; #endif } void CalendarHandler::fillEvent(const QVariant &reservation, const QSharedPointer &event) { #ifdef HAVE_KCAL const int typeId = reservation.userType(); if (typeId == qMetaTypeId()) { fillFlightReservation(reservation.value(), event); } else if (typeId == qMetaTypeId()) { fillLodgingReservation(reservation.value(), event); } else if (typeId == qMetaTypeId()) { fillTrainReservation(reservation.value(), event); } else if (typeId == qMetaTypeId()) { fillBusReservation(reservation.value(), event); } else { return; } const auto bookingRef = JsonLd::convert(reservation).reservationNumber(); if (!event->uid().startsWith(QLatin1String("KIT-") + bookingRef)) { event->setUid(QLatin1String("KIT-") + bookingRef + QLatin1Char('-') + event->uid()); } const auto payload = QJsonDocument(JsonLdDocument::toJson({reservation})).toJson(QJsonDocument::Compact); event->setCustomProperty("KITINERARY", "RESERVATION", QString::fromUtf8(payload)); #else Q_UNUSED(reservation); Q_UNUSED(event); #endif } #ifdef HAVE_KCAL static void fillFlightReservation(const FlightReservation &reservation, const KCalCore::Event::Ptr &event) { const auto flight = reservation.reservationFor().value(); const auto airline = flight.airline(); const auto depPort = flight.departureAirport(); const auto arrPort = flight.arrivalAirport(); const QString flightNumber = airline.iataCode() + QLatin1Char(' ') + flight.flightNumber(); event->setSummary(i18n("Flight %1 from %2 to %3", flightNumber, depPort.iataCode(), arrPort.iataCode())); event->setLocation(depPort.name()); fillGeoPosition(depPort, event); event->setDtStart(flight.departureTime()); event->setDtEnd(flight.arrivalTime()); event->setAllDay(false); const auto boardingTime = flight.boardingTime(); const auto departureGate = flight.departureGate(); if (boardingTime.isValid()) { const auto startOffset = Duration(event->dtStart(), boardingTime); const auto existinAlarms = event->alarms(); const auto it = std::find_if(existinAlarms.begin(), existinAlarms.end(), [startOffset](const Alarm::Ptr &other) { return other->startOffset() == startOffset; }); if (it == existinAlarms.end()) { 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)); } if (!reservation.boardingGroup().isEmpty()) { desc.push_back(i18n("Boarding group: %1", reservation.boardingGroup())); } if (!reservation.airplaneSeat().isEmpty()) { desc.push_back(i18n("Seat: %1", reservation.airplaneSeat())); } if (!reservation.reservationNumber().isEmpty()) { desc.push_back(i18n("Booking reference: %1", reservation.reservationNumber())); } event->setDescription(desc.join(QLatin1Char('\n'))); } static void 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"); 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'))); } static void fillTrainReservation(const TrainReservation &reservation, const KCalCore::Event::Ptr &event) { const auto trip = reservation.reservationFor().value(); const auto depStation = trip.departureStation(); const auto arrStation = trip.arrivalStation(); event->setSummary(i18n("Train %1 from %2 to %3", trip.trainNumber(), depStation.name(), arrStation.name())); fillTripReservation(reservation, event); } static void fillBusReservation(const BusReservation &reservation, const KCalCore::Event::Ptr &event) { const auto trip = reservation.reservationFor().value(); const auto depStation = trip.departureStation(); const auto arrStation = trip.arrivalStation(); event->setSummary(i18n("Bus %1 from %2 to %3", trip.busNumber(), depStation.name(), arrStation.name())); fillTripReservation(reservation, event); } static void fillLodgingReservation(const LodgingReservation &reservation, const KCalCore::Event::Ptr &event) { const auto lodgingBusiness = reservation.reservationFor().value(); const auto address = lodgingBusiness.address(); event->setSummary(i18n("Hotel reservation: %1", lodgingBusiness.name())); #ifdef HAVE_KCONTACTS event->setLocation(i18nc(", , ", "%1, %2 %3, %4", address.streetAddress(), address.postalCode(), address.addressLocality(), KContacts::Address::ISOtoCountry(address.addressCountry()))); #endif fillGeoPosition(lodgingBusiness, event); event->setDtStart(QDateTime(reservation.checkinTime().date(), QTime())); event->setDtEnd(QDateTime(reservation.checkoutTime().date(), QTime())); event->setAllDay(true); event->setDescription(i18n("Check-in: %1\nCheck-out: %2\nBooking reference: %3", QLocale().toString(reservation.checkinTime().time(), QLocale::ShortFormat), QLocale().toString(reservation.checkoutTime().time(), QLocale::ShortFormat), reservation.reservationNumber())); event->setTransparency(Event::Transparent); } static void fillGeoPosition(const QVariant &place, const KCalCore::Event::Ptr &event) { if (!JsonLd::canConvert(place)) { return; } const auto geo = JsonLd::convert(place).geo(); if (!geo.isValid()) { return; } event->setHasGeo(true); event->setGeoLatitude(geo.latitude()); event->setGeoLongitude(geo.longitude()); } #endif diff --git a/src/calendarhandler.h b/src/calendarhandler.h index cf0963f..2db3064 100644 --- a/src/calendarhandler.h +++ b/src/calendarhandler.h @@ -1,57 +1,57 @@ /* 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 "kitinerary_export.h" #include class QDateTime; class QVariant; namespace KCalCore { class Calendar; class Event; } namespace KItinerary { /** Methods for converting between ical events and JSON-LD booking data. */ namespace CalendarHandler { /** Returns the start time associated with the given reservation. */ - KITINERARY_EXPORT QDateTime startDateTime(const QVariant &reservation); + KITINERARY_DEPRECATED_EXPORT QDateTime startDateTime(const QVariant &reservation); /** Attempts to find an event in @p calendar for @p reservation. */ KITINERARY_EXPORT QSharedPointer findEvent(const QSharedPointer &calendar, const QVariant &reservation); /** Returns the reservation object for this event. */ KITINERARY_EXPORT QVariant reservationForEvent(const QSharedPointer &event); /** Fills @p event with details of @p reservation. * Can be used on new events or to update existing ones. */ KITINERARY_EXPORT void fillEvent(const QVariant &reservation, const QSharedPointer &event); } } #endif // CALENDARHANDLER_H diff --git a/src/extractorpostprocessor.cpp b/src/extractorpostprocessor.cpp index bd4e92e..c696090 100644 --- a/src/extractorpostprocessor.cpp +++ b/src/extractorpostprocessor.cpp @@ -1,485 +1,482 @@ /* 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 "config-kitinerary.h" #include "extractorpostprocessor.h" -#include "calendarhandler.h" #include "iatabcbpparser.h" #include "jsonlddocument.h" #include "logging.h" #include "mergeutil.h" +#include "sortutil.h" #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_KCONTACTS #include #endif #include #include #include #include #include #include using namespace KItinerary; namespace KItinerary { class ExtractorPostprocessorPrivate { public: void mergeOrAppend(const QVariant &elem); QVariant processFlightReservation(FlightReservation res) const; Flight processFlight(Flight flight) const; Airport processAirport(Airport airport) const; Airline processAirline(Airline airline) const; void processFlightTime(Flight &flight, QDateTime(Flight::* getter)() const, void(Flight::* setter)(const QDateTime&), const Airport &airport) const; TrainReservation processTrainReservation(TrainReservation res) const; TrainTrip processTrainTrip(TrainTrip trip) const; TrainStation processTrainStation(TrainStation station) const; QDateTime processTrainTripTime(QDateTime dt, const TrainStation &station) const; LodgingReservation processLodgingReservation(LodgingReservation res) const; FoodEstablishmentReservation processFoodEstablishmentReservation(FoodEstablishmentReservation res) const; TouristAttractionVisit processTouristAttractionVisit(TouristAttractionVisit visit) const; QVariant processReservation(QVariant res) const; Person processPerson(Person person) const; template T processPlace(T place) const; bool filterReservation(const QVariant &res) const; bool filterLodgingReservation(const LodgingReservation &res) const; bool filterFlight(const Flight &flight) const; bool filterAirport(const Airport &airport) const; bool filterTrainOrBusTrip(const QVariant &trip) const; bool filterTrainOrBusStation(const QVariant &station) const; QVector m_data; QDateTime m_contextDate; bool m_resultFinalized = false; }; } ExtractorPostprocessor::ExtractorPostprocessor() : d(new ExtractorPostprocessorPrivate) { } ExtractorPostprocessor::ExtractorPostprocessor(ExtractorPostprocessor &&) = default; ExtractorPostprocessor::~ExtractorPostprocessor() = default; void ExtractorPostprocessor::process(const QVector &data) { d->m_resultFinalized = false; d->m_data.reserve(d->m_data.size() + data.size()); for (auto elem : data) { if (JsonLd::isA(elem)) { elem = d->processFlightReservation(elem.value()); } else if (JsonLd::isA(elem)) { elem = d->processTrainReservation(elem.value()); } else if (JsonLd::isA(elem)) { elem = d->processLodgingReservation(elem.value()); } else if (JsonLd::isA(elem)) { elem = d->processFoodEstablishmentReservation(elem.value()); } else if (JsonLd::isA(elem)) { elem = d->processTouristAttractionVisit(elem.value()); } if (JsonLd::canConvert(elem)) { auto res = JsonLd::convert(elem); if (!res.underName().isNull()) { const auto person = d->processPerson(res.underName().value()); res.setUnderName(person); JsonLdDocument::writeProperty(elem, "underName", person); } elem = d->processReservation(elem); } d->mergeOrAppend(elem); } } QVector ExtractorPostprocessor::result() const { if (!d->m_resultFinalized) { for (auto it = d->m_data.begin(); it != d->m_data.end();) { if (d->filterReservation(*it)) { ++it; } else { //qCDebug(Log).noquote() << "Discarding element:" << QJsonDocument(JsonLdDocument::toJson({*it})).toJson(); it = d->m_data.erase(it); } } d->m_resultFinalized = true; } - std::stable_sort(d->m_data.begin(), d->m_data.end(), [](const QVariant &lhs, const QVariant &rhs) { - return CalendarHandler::startDateTime(lhs) < CalendarHandler::startDateTime(rhs); - }); - + std::stable_sort(d->m_data.begin(), d->m_data.end(), SortUtil::isBefore); return d->m_data; } void ExtractorPostprocessor::setContextDate(const QDateTime& dt) { d->m_contextDate = dt; } void ExtractorPostprocessorPrivate::mergeOrAppend(const QVariant &elem) { const auto it = std::find_if(m_data.begin(), m_data.end(), [elem](const QVariant &other) { return MergeUtil::isSame(elem, other); }); if (it == m_data.end()) { m_data.push_back(elem); } else { *it = JsonLdDocument::apply(*it, elem); } } QVariant ExtractorPostprocessorPrivate::processFlightReservation(FlightReservation res) const { // expand ticketToken for IATA BCBP data const auto bcbp = res.reservedTicket().value().ticketTokenData(); if (!bcbp.isEmpty()) { const auto bcbpData = IataBcbpParser::parse(bcbp, m_contextDate.date()); if (bcbpData.size() == 1) { res = JsonLdDocument::apply(bcbpData.at(0), res).value(); } } res.setReservationFor(processFlight(res.reservationFor().value())); return res; } Flight ExtractorPostprocessorPrivate::processFlight(Flight flight) const { flight.setDepartureAirport(processAirport(flight.departureAirport())); flight.setArrivalAirport(processAirport(flight.arrivalAirport())); flight.setAirline(processAirline(flight.airline())); processFlightTime(flight, &Flight::boardingTime, &Flight::setBoardingTime, flight.departureAirport()); processFlightTime(flight, &Flight::departureTime, &Flight::setDepartureTime, flight.departureAirport()); processFlightTime(flight, &Flight::arrivalTime, &Flight::setArrivalTime, flight.arrivalAirport()); return flight; } Airport ExtractorPostprocessorPrivate::processAirport(Airport airport) const { // clean up name airport.setName(airport.name().trimmed()); // complete missing IATA codes auto iataCode = airport.iataCode(); if (iataCode.isEmpty()) { iataCode = AirportDb::iataCodeFromName(airport.name()).toString(); if (!iataCode.isEmpty()) { airport.setIataCode(iataCode); } } // complete missing geo coordinates auto geo = airport.geo(); if (!geo.isValid()) { const auto coord = AirportDb::coordinateForAirport(AirportDb::IataCode{iataCode}); if (coord.isValid()) { geo.setLatitude(coord.latitude); geo.setLongitude(coord.longitude); airport.setGeo(geo); } } // add country auto addr = airport.address(); if (addr.addressCountry().isEmpty()) { const auto isoCode = AirportDb::countryForAirport(AirportDb::IataCode{iataCode}); if (isoCode.isValid()) { addr.setAddressCountry(isoCode.toString()); airport.setAddress(addr); } } return processPlace(airport); } Airline ExtractorPostprocessorPrivate::processAirline(Airline airline) const { airline.setName(airline.name().trimmed()); return airline; } void ExtractorPostprocessorPrivate::processFlightTime(Flight &flight, QDateTime(Flight::* getter)() const, void(Flight::* setter)(const QDateTime&), const Airport &airport) const { auto dt = (flight.*getter)(); if (!dt.isValid()) { return; } if (dt.date().year() <= 1970 && flight.departureDay().isValid()) { // we just have the time, but not the day dt.setDate(flight.departureDay()); (flight.*setter)(dt); } if (dt.timeSpec() == Qt::TimeZone || airport.iataCode().isEmpty()) { return; } const auto tz = AirportDb::timezoneForAirport(AirportDb::IataCode{airport.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; } if (dt.timeSpec() == Qt::OffsetFromUTC || dt.timeSpec() == Qt::LocalTime) { dt.setTimeSpec(Qt::TimeZone); dt.setTimeZone(tz); } else if (dt.timeSpec() == Qt::UTC) { dt = dt.toTimeZone(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 (flight.*setter)(QDateTime()); (flight.*setter)(dt); } TrainReservation ExtractorPostprocessorPrivate::processTrainReservation(TrainReservation res) const { res.setReservationFor(processTrainTrip(res.reservationFor().value())); return res; } TrainTrip ExtractorPostprocessorPrivate::processTrainTrip(TrainTrip trip) const { trip.setArrivalPlatform(trip.arrivalPlatform().trimmed()); trip.setDeparturePlatform(trip.departurePlatform().trimmed()); trip.setDeparatureStation(processTrainStation(trip.departureStation())); trip.setArrivalStation(processTrainStation(trip.arrivalStation())); trip.setDepartureTime(processTrainTripTime(trip.departureTime(), trip.departureStation())); trip.setArrivalTime(processTrainTripTime(trip.arrivalTime(), trip.arrivalStation())); return trip; } TrainStation ExtractorPostprocessorPrivate::processTrainStation(TrainStation station) const { const auto id = station.identifier(); if (id.isEmpty()) { // empty -> null cleanup, to have more compact json-ld output station.setIdentifier(QString()); } else if (id.startsWith(QLatin1String("sncf:")) && id.size() == 10) { // Gare & Connexion ids start with a country code, propagate that to the station address field auto addr = station.address(); if (addr.addressCountry().isEmpty()) { addr.setAddressCountry(id.mid(5, 2).toUpper()); station.setAddress(addr); } const auto record = TrainStationDb::stationForGaresConnexionsId(KnowledgeDb::GaresConnexionsId{id.mid(5)}); if (!station.geo().isValid() && record.coordinate.isValid()) { GeoCoordinates geo; geo.setLatitude(record.coordinate.latitude); geo.setLongitude(record.coordinate.longitude); station.setGeo(geo); } if (addr.addressCountry().isEmpty() && record.country.isValid()) { addr.setAddressCountry(record.country.toString()); station.setAddress(addr); } } else if (id.startsWith(QLatin1String("ibnr:")) && id.size() == 12) { const auto record = TrainStationDb::stationForIbnr(KnowledgeDb::IBNR{id.mid(5).toUInt()}); if (!station.geo().isValid() && record.coordinate.isValid()) { GeoCoordinates geo; geo.setLatitude(record.coordinate.latitude); geo.setLongitude(record.coordinate.longitude); station.setGeo(geo); } auto addr = station.address(); if (addr.addressCountry().isEmpty() && record.country.isValid()) { addr.setAddressCountry(record.country.toString()); station.setAddress(addr); } } return processPlace(station); } QDateTime ExtractorPostprocessorPrivate::processTrainTripTime(QDateTime dt, const TrainStation& station) const { if (!dt.isValid()) { return dt; } if (dt.timeSpec() == Qt::TimeZone || station.identifier().isEmpty()) { return dt; } QTimeZone tz; if (station.identifier().startsWith(QLatin1String("sncf:"))) { const auto record = TrainStationDb::stationForGaresConnexionsId(KnowledgeDb::GaresConnexionsId{station.identifier().mid(5)}); tz = record.timezone.toQTimeZone(); } else if (station.identifier().startsWith(QLatin1String("ibnr:"))) { const auto record = TrainStationDb::stationForIbnr(KnowledgeDb::IBNR{station.identifier().mid(5).toUInt()}); tz = record.timezone.toQTimeZone(); } if (!tz.isValid()) { return dt; } // prefer our timezone over externally provided UTC offset, if they match if (dt.timeSpec() == Qt::OffsetFromUTC && tz.offsetFromUtc(dt) != dt.offsetFromUtc()) { return dt; } if (dt.timeSpec() == Qt::OffsetFromUTC || dt.timeSpec() == Qt::LocalTime) { dt.setTimeSpec(Qt::TimeZone); dt.setTimeZone(tz); } else if (dt.timeSpec() == Qt::UTC) { dt = dt.toTimeZone(tz); } return dt; } LodgingReservation ExtractorPostprocessorPrivate::processLodgingReservation(LodgingReservation res) const { res.setReservationFor(processPlace(res.reservationFor().value())); return res; } FoodEstablishmentReservation ExtractorPostprocessorPrivate::processFoodEstablishmentReservation(FoodEstablishmentReservation res) const { res.setReservationFor(processPlace(res.reservationFor().value())); return res; } TouristAttractionVisit ExtractorPostprocessorPrivate::processTouristAttractionVisit(TouristAttractionVisit visit) const { visit.setTouristAttraction(processPlace(visit.touristAttraction())); return visit; } QVariant ExtractorPostprocessorPrivate::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"); } return res; } Person ExtractorPostprocessorPrivate::processPerson(Person person) const { if (person.name().isEmpty() && !person.familyName().isEmpty() && !person.givenName().isEmpty()) { person.setName(person.givenName() + QLatin1Char(' ') + person.familyName()); } return person; } template T ExtractorPostprocessorPrivate::processPlace(T place) const { #ifdef HAVE_KCONTACTS auto addr = place.address(); if (!addr.addressCountry().isEmpty() && addr.addressCountry().size() != 2) { const auto isoCode = KContacts::Address::countryToISO(addr.addressCountry()).toUpper(); if (!isoCode.isEmpty()) { addr.setAddressCountry(isoCode); place.setAddress(addr); } } #endif return place; } bool ExtractorPostprocessorPrivate::filterReservation(const QVariant &res) const { if (JsonLd::isA(res)) { return filterFlight(res.value().reservationFor().value()); } if (JsonLd::isA(res)) { return filterTrainOrBusTrip(res.value().reservationFor()); } if (JsonLd::isA(res)) { return filterTrainOrBusTrip(res.value().reservationFor()); } if (JsonLd::isA(res)) { return filterLodgingReservation(res.value()); } // types without specific filters yet if (JsonLd::isA(res) || JsonLd::isA(res)) { return true; } // unknown top-level type return false; } bool ExtractorPostprocessorPrivate::filterLodgingReservation(const LodgingReservation &res) const { return res.checkinTime().isValid() && res.checkoutTime().isValid(); } bool ExtractorPostprocessorPrivate::filterFlight(const Flight &flight) const { // this will be valid if either boarding time, departure time or departure day is set const auto validDate = flight.departureDay().isValid(); return filterAirport(flight.departureAirport()) && filterAirport(flight.arrivalAirport()) && validDate; } bool ExtractorPostprocessorPrivate::filterAirport(const Airport &airport) const { return !airport.iataCode().isEmpty() || !airport.name().isEmpty(); } bool ExtractorPostprocessorPrivate::filterTrainOrBusTrip(const QVariant &trip) const { const auto depDt = JsonLdDocument::readProperty(trip, "departureTime").toDateTime(); const auto arrDt = JsonLdDocument::readProperty(trip, "arrivalTime").toDateTime(); return filterTrainOrBusStation(JsonLdDocument::readProperty(trip, "departureStation")) && filterTrainOrBusStation(JsonLdDocument::readProperty(trip, "arrivalStation")) && depDt.isValid() && arrDt.isValid(); } bool ExtractorPostprocessorPrivate::filterTrainOrBusStation(const QVariant &station) const { return !JsonLdDocument::readProperty(station, "name").toString().isEmpty(); } diff --git a/src/sortutil.cpp b/src/sortutil.cpp new file mode 100644 index 0000000..a38c2ac --- /dev/null +++ b/src/sortutil.cpp @@ -0,0 +1,97 @@ +/* + Copyright (C) 2018 Volker Krause + + This program 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 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 Library 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 "sortutil.h" + +#include +#include +#include +#include +#include +#include + +#include + +using namespace KItinerary; + +QDateTime SortUtil::startDateTime(const QVariant &res) +{ + if (JsonLd::isA(res)) { + const auto flight = res.value().reservationFor().value(); + if (flight.departureTime().isValid()) { + return flight.departureTime(); + } + if (flight.boardingTime().isValid()) { + return flight.boardingTime(); + } + return QDateTime(flight.departureDay(), QTime(23, 59, 59)); + } + if (JsonLd::isA(res)) { + return res.value().reservationFor().value().departureTime(); + } + if (JsonLd::isA(res)) { + return res.value().reservationFor().value().departureTime(); + } + if (JsonLd::isA(res)) { + return res.value().startTime(); + } + if (JsonLd::isA(res)) { + const auto hotel = res.value(); + // hotel checkin/checkout is always considered the first/last thing of the day + return QDateTime(hotel.checkinTime().date(), QTime(23, 59, 59)); + } + if (JsonLd::isA(res)) { + return res.value().arrivalTime(); + } + + return {}; +} + +QDateTime SortUtil::endtDateTime(const QVariant &res) +{ + if (JsonLd::isA(res)) { + const auto flight = res.value().reservationFor().value(); + if (flight.arrivalTime().isValid()) { + return flight.arrivalTime(); + } + return QDateTime(flight.departureDay(), QTime(23, 59, 59)); + } + if (JsonLd::isA(res)) { + return res.value().reservationFor().value().arrivalTime(); + } + if (JsonLd::isA(res)) { + return res.value().reservationFor().value().arrivalTime(); + } + if (JsonLd::isA(res)) { + return res.value().endTime(); + } + if (JsonLd::isA(res)) { + const auto hotel = res.value(); + // hotel checkin/checkout is always considered the first/last thing of the day + return QDateTime(hotel.checkoutTime().date(), QTime(0, 0, 0)); + } + if (JsonLd::isA(res)) { + return res.value().departureTime(); + } + + return {}; +} + +bool SortUtil::isBefore(const QVariant &lhs, const QVariant &rhs) +{ + return startDateTime(lhs) < startDateTime(rhs); +} diff --git a/src/sortutil.h b/src/sortutil.h new file mode 100644 index 0000000..ebf9c50 --- /dev/null +++ b/src/sortutil.h @@ -0,0 +1,43 @@ +/* + Copyright (C) 2018 Volker Krause + + This program 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 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 Library 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 . +*/ + +#ifndef KITINERARY_SORTUTIL_H +#define KITINERARY_SORTUTIL_H + +#include "kitinerary_export.h" + +class QDateTime; +class QVariant; + +namespace KItinerary { + +/** Utility function for sorting reservations/visits/events. */ +namespace SortUtil +{ + /** Returns the (start) time associated with the given reservation. */ + KITINERARY_EXPORT QDateTime startDateTime(const QVariant &res); + + /** Returns the (end) time associated with the given reservation. */ + KITINERARY_EXPORT QDateTime endtDateTime(const QVariant &res); + + /** Sorting function for top-level reservation/visit/event elements. */ + KITINERARY_EXPORT bool isBefore(const QVariant &lhs, const QVariant &rhs); +} + +} + +#endif // KITINERARY_SORTUTIL_H