diff --git a/autotests/jsonlddocumenttest.cpp b/autotests/jsonlddocumenttest.cpp index 07c77f9..0067eea 100644 --- a/autotests/jsonlddocumenttest.cpp +++ b/autotests/jsonlddocumenttest.cpp @@ -1,367 +1,367 @@ /* Copyright (c) 2018 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 #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace KItinerary; class JsonLdDocumentTest : public QObject { Q_OBJECT private Q_SLOTS: void initTestCase() { qputenv("TZ", "GMT"); } void testSerialization() { Flight f; f.setFlightNumber(QLatin1String("1234")); f.setDepartureTime(QDateTime(QDate(2018, 3, 18), QTime(18, 44, 0), QTimeZone("Europe/Berlin"))); f.setDepartureDay(QDate(2018, 3, 18)); f.setArrivalTime(QDateTime(QDate(2018, 3, 18), QTime(19, 44, 0), Qt::UTC)); Airport ap; ap.setName(QLatin1String("Berlin Tegel")); ap.setIataCode(QLatin1String("TXL")); f.setDepartureAirport(ap); f.setDepartureGate(QLatin1String("")); Airline airline; airline.setIataCode(QLatin1String("LH")); f.setAirline(airline); auto array = JsonLdDocument::toJson({QVariant::fromValue(f)}); QCOMPARE(array.size(), 1); auto obj = array.at(0).toObject(); QCOMPARE(obj.value(QLatin1String("@context")).toString(), QLatin1String("http://schema.org")); QCOMPARE(obj.value(QLatin1String("@type")).toString(), QLatin1String("Flight")); QCOMPARE(obj.value(QLatin1String("flightNumber")).toString(), QLatin1String("1234")); QCOMPARE(obj.value(QLatin1String("arrivalTime")).toString(), QLatin1String("2018-03-18T19:44:00Z")); auto dtObj = obj.value(QLatin1String("departureTime")).toObject(); QCOMPARE(dtObj.value(QLatin1String("@value")).toString(), QLatin1String("2018-03-18T18:44:00+01:00")); QCOMPARE(dtObj.value(QLatin1String("@type")).toString(), QLatin1String("QDateTime")); QCOMPARE(dtObj.value(QLatin1String("timezone")).toString(), QLatin1String("Europe/Berlin")); QCOMPARE(obj.value(QLatin1String("departureDay")).toString(), QLatin1String("2018-03-18")); auto obj2 = obj.value(QLatin1String("departureAirport")).toObject(); QCOMPARE(obj2.value(QLatin1String("@type")).toString(), QLatin1String("Airport")); QVERIFY(obj.contains(QLatin1String("departureGate"))); QCOMPARE(obj.value(QLatin1String("departureGate")).toString(), QLatin1String("")); QVERIFY(obj.contains(QLatin1String("airline"))); qDebug().noquote() << QJsonDocument(obj).toJson(); // integer values FoodEstablishmentReservation res; res.setPartySize(2); const QString reservationNumber{QStringLiteral("OT123456")}; res.setReservationNumber(reservationNumber); res.setStartTime(QDateTime(QDate(2018, 3, 18), QTime(18, 44, 0), QTimeZone("Europe/Berlin"))); Person person; const QString fullName{QStringLiteral("John")}; person.setName(fullName); res.setUnderName(person); array = JsonLdDocument::toJson({res}); QCOMPARE(array.size(), 1); obj = array.at(0).toObject(); QCOMPARE(obj.value(QLatin1String("partySize")).toInt(), 2); QCOMPARE(obj.value(QLatin1String("reservationNumber")).toString(), reservationNumber); auto resDtObj = obj.value(QLatin1String("startTime")).toObject(); QCOMPARE(resDtObj.value(QLatin1String("@value")).toString(), QLatin1String("2018-03-18T18:44:00+01:00")); QCOMPARE(resDtObj.value(QLatin1String("@type")).toString(), QLatin1String("QDateTime")); QCOMPARE(resDtObj.value(QLatin1String("timezone")).toString(), QLatin1String("Europe/Berlin")); qDebug().noquote() << QJsonDocument(obj).toJson(); auto undernameObj = obj.value(QLatin1String("underName")).toObject(); QCOMPARE(undernameObj.value(QLatin1String("name")).toString(), QLatin1String("John")); //Rental Car RentalCarReservation rentalRes; const QString reservationRentalNumber{QStringLiteral("OT1234567")}; rentalRes.setReservationNumber(reservationRentalNumber); Person personRentalCal; const QString fullNameRentalCar{QStringLiteral("John2")}; personRentalCal.setName(fullNameRentalCar); rentalRes.setUnderName(personRentalCal); rentalRes.setPickupTime(QDateTime(QDate(2018, 3, 18), QTime(18, 44, 0), QTimeZone("Europe/Berlin"))); rentalRes.setDropoffTime(QDateTime(QDate(2018, 3, 21), QTime(18, 44, 0), QTimeZone("Europe/Berlin"))); Place placeDropLocation; placeDropLocation.setName(QStringLiteral("droplocation")); KItinerary::PostalAddress placeDropPostalAddress; placeDropPostalAddress.setStreeAddress(QStringLiteral("8 foo bla bla")); placeDropPostalAddress.setAddressLocality(QStringLiteral("bli")); placeDropLocation.setAddress(placeDropPostalAddress); - rentalRes.setDropOffLocation(placeDropLocation); + rentalRes.setDropoffLocation(placeDropLocation); Place placePickupLocation; placePickupLocation.setName(QStringLiteral("pickuplocation")); KItinerary::PostalAddress placePickupPostalAddress; placePickupPostalAddress.setStreeAddress(QStringLiteral("5 kde foo bla bla")); placePickupPostalAddress.setAddressLocality(QStringLiteral("bli2")); placePickupLocation.setAddress(placePickupPostalAddress); - rentalRes.setPickUpLocation(placePickupLocation); + rentalRes.setPickupLocation(placePickupLocation); array = JsonLdDocument::toJson({rentalRes}); QCOMPARE(array.size(), 1); obj = array.at(0).toObject(); QCOMPARE(obj.value(QLatin1String("reservationNumber")).toString(), reservationRentalNumber); qDebug().noquote() << QJsonDocument(obj).toJson(); undernameObj = obj.value(QLatin1String("underName")).toObject(); QCOMPARE(undernameObj.value(QLatin1String("name")).toString(), fullNameRentalCar); auto pickupTimeObj = obj.value(QLatin1String("dropoffTime")).toObject(); QCOMPARE(pickupTimeObj.value(QLatin1String("@value")).toString(), QLatin1String("2018-03-21T18:44:00+01:00")); QCOMPARE(pickupTimeObj.value(QLatin1String("@type")).toString(), QLatin1String("QDateTime")); QCOMPARE(pickupTimeObj.value(QLatin1String("timezone")).toString(), QLatin1String("Europe/Berlin")); auto droptimeObj = obj.value(QLatin1String("pickupTime")).toObject(); QCOMPARE(droptimeObj.value(QLatin1String("@value")).toString(), QLatin1String("2018-03-18T18:44:00+01:00")); QCOMPARE(droptimeObj.value(QLatin1String("@type")).toString(), QLatin1String("QDateTime")); QCOMPARE(droptimeObj.value(QLatin1String("timezone")).toString(), QLatin1String("Europe/Berlin")); } void testDeserialization() { QByteArray b("[{" "\"@context\": \"http://schema.org\"," "\"@type\": \"Flight\"," "\"departureAirport\": {" "\"@type\": \"Airport\"," "\"iataCode\": \"TXL\"," "\"name\": \"Berlin Tegel\"" "}," "\"departureTime\": \"2018-03-18T18:44:00+01:00\"," "\"departureDay\": \"2018-03-17\"," "\"arrivalTime\": { \"@type\": \"QDateTime\", \"@value\": \"2018-03-18T19:44:00+01:00\", \"timezone\": \"Europe/Berlin\" }," "\"departureGate\": \"\"," "\"flightNumber\": \"1234\"" "}]"); auto array = QJsonDocument::fromJson(b).array(); auto datas = JsonLdDocument::fromJson(array); QCOMPARE(datas.size(), 1); auto data = datas.at(0); QVERIFY(data.canConvert()); Flight flight = data.value(); QCOMPARE(flight.flightNumber(), QLatin1String("1234")); QCOMPARE(flight.departureAirport().iataCode(), QLatin1String("TXL")); QCOMPARE(flight.departureAirport().name(), QLatin1String("Berlin Tegel")); QCOMPARE(flight.departureTime(), QDateTime(QDate(2018, 3, 18), QTime(18, 44, 0), QTimeZone("Europe/Berlin"))); QCOMPARE(flight.departureDay(), QDate(2018, 3, 17)); QCOMPARE(flight.arrivalTime(), QDateTime(QDate(2018, 3, 18), QTime(19, 44, 0), QTimeZone("Europe/Berlin"))); QCOMPARE(flight.arrivalTime().timeSpec(), Qt::TimeZone); QCOMPARE(flight.arrivalTime().timeZone(), QTimeZone("Europe/Berlin")); QVERIFY(flight.departureGate().isEmpty()); QVERIFY(!flight.departureGate().isNull()); // integer values b = QByteArray("[{" "\"@context\": \"http://schema.org\"," "\"@type\": \"FoodEstablishmentReservation\"," "\"partySize\": 42," "\"reservationNumber\": \"0T44542\"," "\"underName\": {" " \"@type\": \"Person\"," " \"name\": \"John Smith\"," " \"email\": \"foo@kde.org\"" "}," "\"reservationFor\": {" " \"@type\": \"FoodEstablishment\"," " \"name\": \"Wagamama\"," " \"address\": {" " \"@type\": \"PostalAddress\"," " \"streetAddress\": \"1 Tavistock Street\"," " \"addressLocality\": \"London\"," " \"addressRegion\": \"Greater London\"," " \"postalCode\": \"WC2E 7PG\"," " \"addressCountry\": \"United Kingdom\"" " }" "}" "}]"); array = QJsonDocument::fromJson(b).array(); datas = JsonLdDocument::fromJson(array); QCOMPARE(datas.size(), 1); data = datas.at(0); QVERIFY(data.canConvert()); auto res = data.value(); QCOMPARE(res.partySize(), 42); QCOMPARE(res.reservationNumber(), QStringLiteral("0T44542")); QCOMPARE(res.underName().value().name(), QStringLiteral("John Smith")); QCOMPARE(res.underName().value().email(), QStringLiteral("foo@kde.org")); const auto foodEstablishment = res.reservationFor().value(); const auto address = foodEstablishment.address(); QCOMPARE(address.addressCountry(), QStringLiteral("United Kingdom")); QCOMPARE(address.addressLocality(), QStringLiteral("London")); QCOMPARE(address.postalCode(), QStringLiteral("WC2E 7PG")); QCOMPARE(address.streetAddress(), QStringLiteral("1 Tavistock Street")); QCOMPARE(address.addressRegion(), QStringLiteral("Greater London")); //RentalCar b = QByteArray("[{" "\"@context\": \"http://schema.org\"," "\"@type\": \"RentalCarReservation\"," "\"reservationNumber\": \"0T445424\"," "\"underName\": {" " \"@type\": \"Person\"," " \"name\": \"John Smith\"," " \"email\": \"foo@kde.org\"" "}," - "\"pickUpLocation\": {" + "\"pickupLocation\": {" " \"@type\": \"Place\"," " \"address\": {" " \"@type\": \"PostalAddress\"," " \"addressLocality\": \"bli2\"," " \"streetAddress\": \"5 kde foo bla bla\"" "}," "\"name\": \"pickuplocation\"" "}," - "\"dropOffLocation\": {" + "\"dropoffLocation\": {" " \"@type\": \"Place\"," " \"address\": {" " \"@type\": \"PostalAddress\"," " \"addressLocality\": \"bli3\"," " \"streetAddress\": \"7 kde foo bla bla\"" "}," "\"name\": \"droplocation\"" "}," "\"pickupTime\": \"2018-03-18T18:44:00+01:00\"," "\"dropoffTime\": \"2018-03-21T18:44:00+01:00\"" "}]"); array = QJsonDocument::fromJson(b).array(); datas = JsonLdDocument::fromJson(array); QCOMPARE(datas.size(), 1); data = datas.at(0); QVERIFY(data.canConvert()); auto resRentCar = data.value(); QCOMPARE(resRentCar.reservationNumber(), QStringLiteral("0T445424")); QCOMPARE(resRentCar.underName().value().name(), QStringLiteral("John Smith")); QCOMPARE(resRentCar.underName().value().email(), QStringLiteral("foo@kde.org")); QCOMPARE(resRentCar.pickupTime(), QDateTime(QDate(2018, 3, 18), QTime(18, 44, 0), QTimeZone("Europe/Berlin"))); QCOMPARE(resRentCar.dropoffTime(), QDateTime(QDate(2018, 3, 21), QTime(18, 44, 0), QTimeZone("Europe/Berlin"))); - const auto dropOffLocation = resRentCar.dropOffLocation(); - QCOMPARE(dropOffLocation.name(), QStringLiteral("droplocation")); - const auto dropOffLocationAddress = dropOffLocation.address(); + const auto dropoffLocation = resRentCar.dropoffLocation(); + QCOMPARE(dropoffLocation.name(), QStringLiteral("droplocation")); + const auto dropOffLocationAddress = dropoffLocation.address(); QCOMPARE(dropOffLocationAddress.streetAddress(), QStringLiteral("7 kde foo bla bla")); QCOMPARE(dropOffLocationAddress.addressLocality(), QStringLiteral("bli3")); - const auto pickUpLocation = resRentCar.pickUpLocation(); - QCOMPARE(pickUpLocation.name(), QStringLiteral("pickuplocation")); - const auto pickupLocationAddress = pickUpLocation.address(); + const auto pickupLocation = resRentCar.pickupLocation(); + QCOMPARE(pickupLocation.name(), QStringLiteral("pickuplocation")); + const auto pickupLocationAddress = pickupLocation.address(); QCOMPARE(pickupLocationAddress.streetAddress(), QStringLiteral("5 kde foo bla bla")); QCOMPARE(pickupLocationAddress.addressLocality(), QStringLiteral("bli2")); //Taxi Reservation b = QByteArray("[{" "\"@context\": \"http://schema.org\"," "\"@type\": \"TaxiReservation\"," "\"reservationId\": \"0T445424\"," "\"underName\": {" " \"@type\": \"Person\"," " \"name\": \"John Smith2\"," " \"email\": \"foo@kde.org\"" "}," - "\"pickUpLocation\": {" + "\"pickupLocation\": {" " \"@type\": \"Place\"," " \"address\": {" " \"@type\": \"PostalAddress\"," " \"addressLocality\": \"bli2\"," " \"streetAddress\": \"5 kde foo bla bla\"" " }" "}," "\"reservationFor\": {" " \"@type\": \"Taxi\"," " \"provider\": {" " \"@type\": \"Organization\"," " \"name\": \"Checker Cab\"" " }" "}," "\"pickupTime\": \"2018-03-18T18:44:00+01:00\"" "}]"); array = QJsonDocument::fromJson(b).array(); datas = JsonLdDocument::fromJson(array); QCOMPARE(datas.size(), 1); data = datas.at(0); QVERIFY(data.canConvert()); auto resTaxi = data.value(); QCOMPARE(resTaxi.reservationNumber(), QStringLiteral("0T445424")); QCOMPARE(resTaxi.underName().value().name(), QStringLiteral("John Smith2")); QCOMPARE(resTaxi.underName().value().email(), QStringLiteral("foo@kde.org")); - const auto pickUpLocationTaxi = resTaxi.pickUpLocation(); + const auto pickUpLocationTaxi = resTaxi.pickupLocation(); const auto pickupLocationAddressTaxi = pickUpLocationTaxi.address(); QCOMPARE(pickupLocationAddressTaxi.streetAddress(), QStringLiteral("5 kde foo bla bla")); QCOMPARE(pickupLocationAddressTaxi.addressLocality(), QStringLiteral("bli2")); QCOMPARE(resTaxi.pickupTime(), QDateTime(QDate(2018, 3, 18), QTime(18, 44, 0), QTimeZone("Europe/Berlin"))); QVERIFY(resTaxi.reservationFor().canConvert()); } void testApply() { Flight f1; f1.setDepartureGate(QLatin1String("38")); Airline a1; a1.setIataCode(QLatin1String("AB")); f1.setAirline(a1); Flight f2; f2.setDepartureTerminal(QLatin1String("A")); Airline a2; a2.setName(QLatin1String("Air Berlin")); f2.setAirline(a2); f1 = JsonLdDocument::apply(f1, f2).value(); QCOMPARE(f1.departureGate(), QLatin1String("38")); QCOMPARE(f1.departureTerminal(), QLatin1String("A")); QCOMPARE(f1.airline().iataCode(), QLatin1String("AB")); QCOMPARE(f1.airline().name(), QLatin1String("Air Berlin")); } }; QTEST_APPLESS_MAIN(JsonLdDocumentTest) #include "jsonlddocumenttest.moc" diff --git a/src/calendarhandler.cpp b/src/calendarhandler.cpp index 9445edb..4dc203b 100644 --- a/src/calendarhandler.cpp +++ b/src/calendarhandler.cpp @@ -1,457 +1,457 @@ /* 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 #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 QVector &reservations, const KCalCore::Event::Ptr &event); template static void fillTripReservation(const Res &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 fillEventReservation(const QVector &reservations, const KCalCore::Event::Ptr &event); static void fillGeoPosition(const QVariant &place, const KCalCore::Event::Ptr &event); static void fillFoodReservation(const FoodEstablishmentReservation &reservation, const KCalCore::Event::Ptr &event); static void fillRentalCarReservation(const RentalCarReservation &reservation, const KCalCore::Event::Ptr &event); static void fillTaxiReservation(const TaxiReservation &reservation, const KCalCore::Event::Ptr &event); #endif QSharedPointer CalendarHandler::findEvent(const QSharedPointer &calendar, const QVariant &reservation) { #ifdef HAVE_KCAL if (!JsonLd::canConvert(reservation)) { return {}; } const auto dt = SortUtil::startDateTime(reservation).date(); const auto events = calendar->events(dt); for (const auto &event : events) { if (!event->uid().startsWith(QLatin1String("KIT-"))) { continue; } const auto otherRes = CalendarHandler::reservationsForEvent(event); for (const auto &other : otherRes) { if (MergeUtil::isSame(other, reservation)) { return event; } } } #else Q_UNUSED(calendar); Q_UNUSED(reservation); #endif return {}; } QVector CalendarHandler::reservationsForEvent(const QSharedPointer &event) { #ifdef HAVE_KCAL const auto payload = event->customProperty("KITINERARY", "RESERVATION").toUtf8(); const auto json = QJsonDocument::fromJson(payload).array(); return JsonLdDocument::fromJson(json); #else Q_UNUSED(event); return {}; #endif } void CalendarHandler::fillEvent(const QVector &reservations, const QSharedPointer &event) { if (reservations.isEmpty()) { return; } #ifdef HAVE_KCAL // TODO pass reservationS into all functions below for multi-traveler support const auto reservation = reservations.at(0); const int typeId = reservation.userType(); if (typeId == qMetaTypeId()) { fillFlightReservation(reservations, 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 if (JsonLd::isA(reservation)) { fillEventReservation(reservations, event); } else if (JsonLd::isA(reservation)) { fillFoodReservation(reservation.value(), event); } else if (JsonLd::isA(reservation)) { fillRentalCarReservation(reservation.value(), event); } else if (JsonLd::isA(reservation)) { fillTaxiReservation(reservation.value(), event); } else { return; } if (!event->uid().startsWith(QLatin1String("KIT-"))) { event->setUid(QLatin1String("KIT-") + event->uid()); } const auto payload = QJsonDocument(JsonLdDocument::toJson(reservations)).toJson(QJsonDocument::Compact); event->setCustomProperty("KITINERARY", "RESERVATION", QString::fromUtf8(payload)); #else Q_UNUSED(event); #endif } #ifdef HAVE_KCAL static void fillFlightReservation(const QVector &reservations, const KCalCore::Event::Ptr &event) { const auto flight = reservations.at(0).value().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)); } for (const auto &r : reservations) { const auto reservation = r.value(); const auto person = reservation.underName().value(); if (!person.name().isEmpty()) { desc.push_back(person.name()); } 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'))); } template static void fillTripReservation(const Res &reservation, const KCalCore::Event::Ptr &event) { const auto trip = reservation.reservationFor().template value(); const auto depStation = trip.departureStation(); event->setLocation(depStation.name()); fillGeoPosition(depStation, event); event->setDtStart(trip.departureTime()); event->setDtEnd(trip.arrivalTime()); event->setAllDay(false); QStringList desc; if (!trip.departurePlatform().isEmpty()) { desc.push_back(i18n("Departure platform: %1", trip.departurePlatform())); } const auto ticket = reservation.reservedTicket().template value(); const auto seat = ticket.ticketedSeat(); if (!seat.seatSection().isEmpty()) { desc.push_back(i18n("Coach: %1", seat.seatSection())); } if (!seat.seatNumber().isEmpty()) { desc.push_back(i18n("Seat: %1", seat.seatNumber())); } if (!trip.arrivalPlatform().isEmpty()) { desc.push_back(i18n("Arrival platform: %1", trip.arrivalPlatform())); } if (!reservation.reservationNumber().isEmpty()) { desc.push_back(i18n("Booking reference: %1", reservation.reservationNumber())); } 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(KCalCore::Event::Transparent); } static void fillEventReservation(const QVector &reservations, const KCalCore::Event::Ptr &event) { const auto ev = reservations.at(0).value().reservationFor().value(); Place location; if (JsonLd::canConvert(ev.location())) { location = JsonLd::convert(ev.location()); } event->setSummary(ev.name()); event->setLocation(location.name()); fillGeoPosition(location, event); event->setDtStart(ev.startDate()); event->setDtEnd(ev.endDate()); event->setAllDay(false); if (ev.doorTime().isValid()) { const auto startOffset = Duration(event->dtStart(), ev.doorTime()); 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(), ev.doorTime())); alarm->setDisplayAlarm(i18n("Entrance for %1", ev.name())); alarm->setEnabled(true); event->addAlarm(alarm); } } QStringList desc; for (const auto &r : reservations) { const auto reservation = r.value(); const auto person = reservation.underName().value(); if (!person.name().isEmpty()) { desc.push_back(person.name()); } // TODO: add seat information if present if (!reservation.reservationNumber().isEmpty()) { desc.push_back(i18n("Booking reference: %1", reservation.reservationNumber())); } } event->setDescription(desc.join(QLatin1Char('\n'))); } 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()); } static void fillFoodReservation(const FoodEstablishmentReservation &reservation, const KCalCore::Event::Ptr &event) { const auto foodEstablishment = reservation.reservationFor().value(); const auto address = foodEstablishment.address(); event->setSummary(i18n("Restaurant reservation: %1", foodEstablishment.name())); #ifdef HAVE_KCONTACTS event->setLocation(i18nc(", , ", "%1, %2 %3, %4", address.streetAddress(), address.postalCode(), address.addressLocality(), KContacts::Address::ISOtoCountry(address.addressCountry()))); #endif fillGeoPosition(foodEstablishment, event); event->setDtStart(reservation.startTime()); auto endTime = reservation.endTime(); if (!endTime.isValid()) { endTime = QDateTime(reservation.startTime().date(), QTime(23, 59, 59)); } event->setDtEnd(endTime); event->setAllDay(false); event->setTransparency(KCalCore::Event::Transparent); event->setSummary(i18n("Restaurant reservation: %1", foodEstablishment.name())); event->setDescription(i18n("Number Of People: %1\nReservation reference: %2\nUnder name: %3", reservation.partySize(), reservation.reservationNumber(), reservation.underName().value().name())); } static void fillRentalCarReservation(const RentalCarReservation &reservation, const KCalCore::Event::Ptr &event) { - const auto rentalCalPickup = reservation.pickUpLocation(); + const auto rentalCalPickup = reservation.pickupLocation(); const auto addressPickUp = rentalCalPickup.address(); const auto rentalCar = reservation.reservationFor().value(); event->setSummary(i18n("Rental Car reservation: %1", rentalCar.name())); #ifdef HAVE_KCONTACTS event->setLocation(i18nc(", , ", "%1, %2 %3, %4", addressPickUp.streetAddress(), addressPickUp.postalCode(), addressPickUp.addressLocality(), KContacts::Address::ISOtoCountry(addressPickUp.addressCountry()))); #endif fillGeoPosition(rentalCalPickup, event); event->setDtStart(reservation.pickupTime()); event->setDtEnd(reservation.dropoffTime()); event->setAllDay(false); event->setTransparency(KCalCore::Event::Transparent); event->setSummary(i18n("Rent car reservation: %1", rentalCar.name())); #ifdef HAVE_KCONTACTS const QString pickUpAddress = i18nc(", , ", "%1, %2 %3, %4", addressPickUp.streetAddress(), addressPickUp.postalCode(), addressPickUp.addressLocality(), KContacts::Address::ISOtoCountry(addressPickUp.addressCountry())); - const auto rentalCalDropOff = reservation.dropOffLocation(); + const auto rentalCalDropOff = reservation.dropoffLocation(); const auto addressDropOff = rentalCalDropOff.address(); const QString dropAddress = i18nc(", , ", "%1, %2 %3, %4", addressDropOff.streetAddress(), addressDropOff.postalCode(), addressDropOff.addressLocality(), KContacts::Address::ISOtoCountry(addressDropOff.addressCountry())); const QString description = i18n("Reservation reference: %1\nUnder name: %2\n\nPickUp location: %3\n\nDropoff Location: %4", reservation.reservationNumber(), reservation.underName().value().name(), pickUpAddress, dropAddress); event->setDescription(description); #endif } static void fillTaxiReservation(const TaxiReservation &reservation, const KCalCore::Event::Ptr &event) { - const auto taxiPickup = reservation.pickUpLocation(); + const auto taxiPickup = reservation.pickupLocation(); const auto addressPickUp = taxiPickup.address(); //TODO const auto rentalCar = reservation.reservationFor().value(); //TODO event->setSummary(i18n("Rental Car reservation: %1", rentalCar.name())); #ifdef HAVE_KCONTACTS event->setLocation(i18nc(", , ", "%1, %2 %3, %4", addressPickUp.streetAddress(), addressPickUp.postalCode(), addressPickUp.addressLocality(), KContacts::Address::ISOtoCountry(addressPickUp.addressCountry()))); #endif fillGeoPosition(taxiPickup, event); event->setDtStart(reservation.pickupTime()); //TODO event->setDtEnd(reservation.dropoffTime()); event->setAllDay(false); event->setTransparency(KCalCore::Event::Transparent); //TODO event->setSummary(i18n("Rent car reservation: %1", rentalCar.name())); #ifdef HAVE_KCONTACTS const QString pickUpAddress = i18nc(", , ", "%1, %2 %3, %4", addressPickUp.streetAddress(), addressPickUp.postalCode(), addressPickUp.addressLocality(), KContacts::Address::ISOtoCountry(addressPickUp.addressCountry())); const QString description = i18n("Reservation reference: %1\nUnder name: %2\nPickUp location: %3", reservation.reservationNumber(), reservation.underName().value().name(), pickUpAddress); event->setDescription(description); #endif } #endif diff --git a/src/datatypes/reservation.cpp b/src/datatypes/reservation.cpp index bf23afa..920f9da 100644 --- a/src/datatypes/reservation.cpp +++ b/src/datatypes/reservation.cpp @@ -1,152 +1,152 @@ /* 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 "reservation.h" #include "organization.h" #include "datatypes_p.h" #include #include #include #include using namespace KItinerary; namespace KItinerary { class ReservationPrivate : public QSharedData { KITINERARY_PRIVATE_BASE_GADGET(Reservation) public: QString reservationNumber; QVariant reservationFor; QVariant reservedTicket; QVariant underName; QUrl url; QString pkpassPassTypeIdentifier; QString pkpassSerialNumber; Organization provider; QVariantList potentialAction; }; KITINERARY_MAKE_BASE_CLASS(Reservation) KITINERARY_MAKE_PROPERTY(Reservation, QString, reservationNumber, setReservationNumber) KITINERARY_MAKE_PROPERTY(Reservation, QVariant, reservationFor, setReservationFor) KITINERARY_MAKE_PROPERTY(Reservation, QVariant, reservedTicket, setReservedTicket) KITINERARY_MAKE_PROPERTY(Reservation, QVariant, underName, setUnderName) KITINERARY_MAKE_PROPERTY(Reservation, QUrl, url, setUrl) KITINERARY_MAKE_PROPERTY(Reservation, QString, pkpassPassTypeIdentifier, setPkpassPassTypeIdentifier) KITINERARY_MAKE_PROPERTY(Reservation, QString, pkpassSerialNumber, setPkpassSerialNumber) KITINERARY_MAKE_PROPERTY(Reservation, Organization, provider, setProvider) KITINERARY_MAKE_PROPERTY(Reservation, QVariantList, potentialAction, setPotentialAction) class LodgingReservationPrivate : public ReservationPrivate { KITINERARY_PRIVATE_GADGET(LodgingReservation) public: QDateTime checkinTime; QDateTime checkoutTime; }; KITINERARY_MAKE_SUB_CLASS(LodgingReservation, Reservation) KITINERARY_MAKE_PROPERTY(LodgingReservation, QDateTime, checkinTime, setCheckinTime) KITINERARY_MAKE_PROPERTY(LodgingReservation, QDateTime, checkoutTime, setCheckoutTime) class FlightReservationPrivate : public ReservationPrivate { KITINERARY_PRIVATE_GADGET(FlightReservation) public: QString passengerSequenceNumber; QString airplaneSeat; QString boardingGroup; }; KITINERARY_MAKE_SUB_CLASS(FlightReservation, Reservation) KITINERARY_MAKE_PROPERTY(FlightReservation, QString, passengerSequenceNumber, setPassengerSequenceNumber) KITINERARY_MAKE_PROPERTY(FlightReservation, QString, airplaneSeat, setAirplaneSeat) KITINERARY_MAKE_PROPERTY(FlightReservation, QString, boardingGroup, setBoardingGroup) class TrainReservationPrivate : public ReservationPrivate { KITINERARY_PRIVATE_GADGET(TrainReservation) }; KITINERARY_MAKE_SUB_CLASS(TrainReservation, Reservation) class BusReservationPrivate : public ReservationPrivate { KITINERARY_PRIVATE_GADGET(BusReservation) }; KITINERARY_MAKE_SUB_CLASS(BusReservation, Reservation) class FoodEstablishmentReservationPrivate : public ReservationPrivate { KITINERARY_PRIVATE_GADGET(FoodEstablishmentReservation) public: QDateTime endTime; QDateTime modifiedTime; QDateTime startTime; int partySize = 0; }; KITINERARY_MAKE_SUB_CLASS(FoodEstablishmentReservation, Reservation) KITINERARY_MAKE_PROPERTY(FoodEstablishmentReservation, QDateTime, modifiedTime, setModifiedTime) KITINERARY_MAKE_PROPERTY(FoodEstablishmentReservation, QDateTime, endTime, setEndTime) KITINERARY_MAKE_PROPERTY(FoodEstablishmentReservation, int, partySize, setPartySize) KITINERARY_MAKE_PROPERTY(FoodEstablishmentReservation, QDateTime, startTime, setStartTime) class EventReservationPrivate : public ReservationPrivate { KITINERARY_PRIVATE_GADGET(EventReservation) }; KITINERARY_MAKE_SUB_CLASS(EventReservation, Reservation) class RentalCarReservationPrivate : public ReservationPrivate { KITINERARY_PRIVATE_GADGET(RentalCarReservation) public: QDateTime dropoffTime; QDateTime pickupTime; - Place pickUpLocation; - Place dropOffLocation; + Place pickupLocation; + Place dropoffLocation; }; KITINERARY_MAKE_SUB_CLASS(RentalCarReservation, Reservation) KITINERARY_MAKE_PROPERTY(RentalCarReservation, QDateTime, dropoffTime, setDropoffTime) KITINERARY_MAKE_PROPERTY(RentalCarReservation, QDateTime, pickupTime, setPickupTime) -KITINERARY_MAKE_PROPERTY(RentalCarReservation, Place, pickUpLocation, setPickUpLocation) -KITINERARY_MAKE_PROPERTY(RentalCarReservation, Place, dropOffLocation, setDropOffLocation) +KITINERARY_MAKE_PROPERTY(RentalCarReservation, Place, pickupLocation, setPickupLocation) +KITINERARY_MAKE_PROPERTY(RentalCarReservation, Place, dropoffLocation, setDropoffLocation) class TaxiReservationPrivate : public ReservationPrivate { KITINERARY_PRIVATE_GADGET(TaxiReservation) public: QDateTime pickupTime; - Place pickUpLocation; + Place pickupLocation; }; KITINERARY_MAKE_SUB_CLASS(TaxiReservation, Reservation) KITINERARY_MAKE_PROPERTY(TaxiReservation, QDateTime, pickupTime, setPickupTime) -KITINERARY_MAKE_PROPERTY(TaxiReservation, Place, pickUpLocation, setPickUpLocation) +KITINERARY_MAKE_PROPERTY(TaxiReservation, Place, pickupLocation, setPickupLocation) } template <> KItinerary::ReservationPrivate *QExplicitlySharedDataPointer::clone() { return d->clone(); } #include "moc_reservation.cpp" diff --git a/src/datatypes/reservation.h b/src/datatypes/reservation.h index 28f4b30..1d8dd91 100644 --- a/src/datatypes/reservation.h +++ b/src/datatypes/reservation.h @@ -1,164 +1,164 @@ /* 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_RESERVATION_H #define KITINERARY_RESERVATION_H #include "kitinerary_export.h" #include "datatypes.h" #include "place.h" class QUrl; namespace KItinerary { class ReservationPrivate; class Organization; /** Abstract base class for reservations. * @see https://schema.org/Reservation */ class KITINERARY_EXPORT Reservation { KITINERARY_BASE_GADGET(Reservation) KITINERARY_PROPERTY(QString, reservationNumber, setReservationNumber) KITINERARY_PROPERTY(QVariant, reservationFor, setReservationFor) KITINERARY_PROPERTY(QVariant, reservedTicket, setReservedTicket) KITINERARY_PROPERTY(QVariant, underName, setUnderName) KITINERARY_PROPERTY(KItinerary::Organization, provider, setProvider) KITINERARY_PROPERTY(QUrl, url, setUrl) KITINERARY_PROPERTY(QVariantList, potentialAction, setPotentialAction) // KDE extensions /** Pass type identifier of an associated Apple Wallet boarding pass. * @see KPkPass::Pass::passTypeIdentifier */ KITINERARY_PROPERTY(QString, pkpassPassTypeIdentifier, setPkpassPassTypeIdentifier) /** Serial number of an associated Apple Wallet boarding pass. * @see KPkPass::Pass::serialNumber */ KITINERARY_PROPERTY(QString, pkpassSerialNumber, setPkpassSerialNumber) protected: ///@cond internal QExplicitlySharedDataPointer d; ///@endcond }; /** A hotel reservation. * @see https://schema.org/LodgingReservation * @see https://developers.google.com/gmail/markup/reference/hotel-reservation */ class KITINERARY_EXPORT LodgingReservation : public Reservation { KITINERARY_GADGET(LodgingReservation) KITINERARY_PROPERTY(QDateTime, checkinTime, setCheckinTime) KITINERARY_PROPERTY(QDateTime, checkoutTime, setCheckoutTime) }; /** A flight reservation. * @see https://schema.org/FlightReservation * @see https://developers.google.com/gmail/markup/reference/flight-reservation */ class KITINERARY_EXPORT FlightReservation : public Reservation { KITINERARY_GADGET(FlightReservation) /** Passenger sequnce number * Despite the name, do not expect this to be a number, infants without * their own seat get vendor-defined codes here for example. * @see https://schema.org/passengerSequenceNumber */ KITINERARY_PROPERTY(QString, passengerSequenceNumber, setPassengerSequenceNumber) // Google extensions KITINERARY_PROPERTY(QString, airplaneSeat, setAirplaneSeat) KITINERARY_PROPERTY(QString, boardingGroup, setBoardingGroup) }; /** A train reservation. * @see https://schema.org/TrainReservation */ class KITINERARY_EXPORT TrainReservation : public Reservation { KITINERARY_GADGET(TrainReservation) }; /** A bus reservation. * @see https://schema.org/BusReservation */ class KITINERARY_EXPORT BusReservation : public Reservation { KITINERARY_GADGET(BusReservation) }; /** A restaurant reservation. * @see https://schema.org/FoodEstablishmentReservation * @see https://developers.google.com/gmail/markup/reference/restaurant-reservation */ class KITINERARY_EXPORT FoodEstablishmentReservation : public Reservation { KITINERARY_GADGET(FoodEstablishmentReservation) KITINERARY_PROPERTY(QDateTime, endTime, setEndTime) KITINERARY_PROPERTY(QDateTime, modifiedTime, setModifiedTime) KITINERARY_PROPERTY(int, partySize, setPartySize) KITINERARY_PROPERTY(QDateTime, startTime, setStartTime) }; /** An event reservation. * @see https://schema.org/EventReservation * @see https://developers.google.com/gmail/markup/reference/event-reservation */ class KITINERARY_EXPORT EventReservation : public Reservation { KITINERARY_GADGET(EventReservation) }; /** A Rental Car reservation. * @see https://developers.google.com/gmail/markup/reference/rental-car */ class KITINERARY_EXPORT RentalCarReservation : public Reservation { KITINERARY_GADGET(RentalCarReservation) KITINERARY_PROPERTY(QDateTime, dropoffTime, setDropoffTime) KITINERARY_PROPERTY(QDateTime, pickupTime, setPickupTime) - KITINERARY_PROPERTY(KItinerary::Place, pickUpLocation, setPickUpLocation) - KITINERARY_PROPERTY(KItinerary::Place, dropOffLocation, setDropOffLocation) + KITINERARY_PROPERTY(KItinerary::Place, pickupLocation, setPickupLocation) + KITINERARY_PROPERTY(KItinerary::Place, dropoffLocation, setDropoffLocation) }; /** A Taxi reservation. * @see https://schema.org/TaxiReservation */ class KITINERARY_EXPORT TaxiReservation : public Reservation { KITINERARY_GADGET(TaxiReservation) KITINERARY_PROPERTY(QDateTime, pickupTime, setPickupTime) - KITINERARY_PROPERTY(KItinerary::Place, pickUpLocation, setPickUpLocation) + KITINERARY_PROPERTY(KItinerary::Place, pickupLocation, setPickupLocation) }; } Q_DECLARE_METATYPE(KItinerary::FlightReservation) Q_DECLARE_METATYPE(KItinerary::LodgingReservation) Q_DECLARE_METATYPE(KItinerary::TrainReservation) Q_DECLARE_METATYPE(KItinerary::BusReservation) Q_DECLARE_METATYPE(KItinerary::FoodEstablishmentReservation) Q_DECLARE_METATYPE(KItinerary::EventReservation) Q_DECLARE_METATYPE(KItinerary::RentalCarReservation) Q_DECLARE_METATYPE(KItinerary::Place) #endif // KITINERARY_RESERVATION_H diff --git a/src/extractorpostprocessor.cpp b/src/extractorpostprocessor.cpp index fc35db2..c9ff9f2 100644 --- a/src/extractorpostprocessor.cpp +++ b/src/extractorpostprocessor.cpp @@ -1,611 +1,618 @@ /* 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 "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 #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; QDateTime processFlightTime(QDateTime dt, const Flight &flight, 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; BusReservation processBusReservation(BusReservation res) const; BusTrip processBusTrip(BusTrip trip) const; LodgingReservation processLodgingReservation(LodgingReservation res) const; FoodEstablishmentReservation processFoodEstablishmentReservation(FoodEstablishmentReservation res) const; TouristAttractionVisit processTouristAttractionVisit(TouristAttractionVisit visit) const; EventReservation processEventReservation(EventReservation res) const; RentalCarReservation processRentalCarReservation(RentalCarReservation res) const; TaxiReservation processTaxiReservation(TaxiReservation res) const; Event processEvent(Event event) const; template T processReservation(T res) const; Person processPerson(Person person) const; template T processPlace(T place) const; QVariantList processActions(QVariantList actions) 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; template bool filterTrainOrBusTrip(const T &trip) const; template bool filterTrainOrBusStation(const T &station) const; bool filterEventReservation(const EventReservation &res) const; bool filterFoodReservation(const FoodEstablishmentReservation &res) const; QVector m_data; QDateTime m_contextDate; bool m_resultFinalized = false; }; } ExtractorPostprocessor::ExtractorPostprocessor() : d(new ExtractorPostprocessorPrivate) { } ExtractorPostprocessor::ExtractorPostprocessor(ExtractorPostprocessor &&) noexcept = default; ExtractorPostprocessor::~ExtractorPostprocessor() = default; void ExtractorPostprocessor::process(const QVector &data) { + qDebug() << "======"; + qDebug() << JsonLdDocument::toJson(data); + qDebug() << "-----"; + qDebug() << data; + qDebug() << "~~~~"; + 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()); } else if (JsonLd::isA(elem)) { elem = d->processBusReservation(elem.value()); } else if (JsonLd::isA(elem)) { elem = d->processEventReservation(elem.value()); } else if (JsonLd::isA(elem)) { elem = d->processRentalCarReservation(elem.value()); } else if (JsonLd::isA(elem)) { elem = d->processTaxiReservation(elem.value()); } 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(), 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(); } else { for (const auto &data : bcbpData) { if (MergeUtil::isSame(res, data)) { res = JsonLdDocument::apply(data, res).value(); break; } } } } res.setReservationFor(processFlight(res.reservationFor().value())); return processReservation(res); } Flight ExtractorPostprocessorPrivate::processFlight(Flight flight) const { flight.setDepartureAirport(processAirport(flight.departureAirport())); flight.setArrivalAirport(processAirport(flight.arrivalAirport())); flight.setAirline(processAirline(flight.airline())); flight.setBoardingTime(processFlightTime(flight.boardingTime(), flight, flight.departureAirport())); flight.setDepartureTime(processFlightTime(flight.departureTime(), flight, flight.departureAirport())); flight.setArrivalTime(processFlightTime(flight.arrivalTime(), flight, 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 = KnowledgeDb::iataCodeFromName(airport.name()).toString(); if (!iataCode.isEmpty()) { airport.setIataCode(iataCode); } } // complete missing geo coordinates auto geo = airport.geo(); if (!geo.isValid()) { const auto coord = KnowledgeDb::coordinateForAirport(KnowledgeDb::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 = KnowledgeDb::countryForAirport(KnowledgeDb::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; } QDateTime ExtractorPostprocessorPrivate::processFlightTime(QDateTime dt, const Flight &flight, const Airport &airport) const { if (!dt.isValid()) { return dt; } if (dt.date().year() <= 1970 && flight.departureDay().isValid()) { // we just have the time, but not the day dt.setDate(flight.departureDay()); } if (dt.timeSpec() == Qt::TimeZone || airport.iataCode().isEmpty()) { return dt; } const auto tz = KnowledgeDb::timezoneForAirport(KnowledgeDb::IataCode{airport.iataCode()}); 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; } TrainReservation ExtractorPostprocessorPrivate::processTrainReservation(TrainReservation res) const { res.setReservationFor(processTrainTrip(res.reservationFor().value())); return processReservation(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 = KnowledgeDb::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 = KnowledgeDb::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 = KnowledgeDb::stationForGaresConnexionsId(KnowledgeDb::GaresConnexionsId{station.identifier().mid(5)}); tz = record.timezone.toQTimeZone(); } else if (station.identifier().startsWith(QLatin1String("ibnr:"))) { const auto record = KnowledgeDb::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; } BusReservation ExtractorPostprocessorPrivate::processBusReservation(BusReservation res) const { res.setReservationFor(processBusTrip(res.reservationFor().value())); return processReservation(res); } BusTrip ExtractorPostprocessorPrivate::processBusTrip(BusTrip trip) const { trip.setDepartureStation(processPlace(trip.departureStation())); trip.setArrivalStation(processPlace(trip.arrivalStation())); return trip; } LodgingReservation ExtractorPostprocessorPrivate::processLodgingReservation(LodgingReservation res) const { res.setReservationFor(processPlace(res.reservationFor().value())); return processReservation(res); } TaxiReservation ExtractorPostprocessorPrivate::processTaxiReservation(TaxiReservation res) const { - res.setPickUpLocation(processPlace(res.pickUpLocation())); + res.setPickupLocation(processPlace(res.pickupLocation())); return processReservation(res); } RentalCarReservation ExtractorPostprocessorPrivate::processRentalCarReservation(RentalCarReservation res) const { - res.setPickUpLocation(processPlace(res.pickUpLocation())); - res.setDropOffLocation(processPlace(res.dropOffLocation())); + qDebug() << JsonLdDocument::toJson({res}); + res.setPickupLocation(processPlace(res.pickupLocation())); + res.setDropoffLocation(processPlace(res.dropoffLocation())); return processReservation(res); } FoodEstablishmentReservation ExtractorPostprocessorPrivate::processFoodEstablishmentReservation(FoodEstablishmentReservation res) const { res.setReservationFor(processPlace(res.reservationFor().value())); return processReservation(res); } TouristAttractionVisit ExtractorPostprocessorPrivate::processTouristAttractionVisit(TouristAttractionVisit visit) const { visit.setTouristAttraction(processPlace(visit.touristAttraction())); return visit; } EventReservation ExtractorPostprocessorPrivate::processEventReservation(EventReservation res) const { res.setReservationFor(processEvent(res.reservationFor().value())); return processReservation(res); } Event ExtractorPostprocessorPrivate::processEvent(Event event) const { // normalize location to be a Place if (JsonLd::isA(event.location())) { Place place; place.setAddress(event.location().value()); event.setLocation(place); } if (JsonLd::isA(event.location())) { event.setLocation(processPlace(event.location().value())); } return event; } template T ExtractorPostprocessorPrivate::processReservation(T res) const { res.setUnderName(processPerson(res.underName().template value())); res.setPotentialAction(processActions(res.potentialAction())); return res; } Person ExtractorPostprocessorPrivate::processPerson(Person person) const { person.setName(person.name().simplified()); if (person.name().isEmpty() && !person.familyName().isEmpty() && !person.givenName().isEmpty()) { person.setName(person.givenName() + QLatin1Char(' ') + person.familyName()); } // strip prefixes, they break comparisons static const char* honorificPrefixes[] = { "MR ", "MS ", "MRS " }; for (auto prefix : honorificPrefixes) { if (person.name().startsWith(QLatin1String(prefix), Qt::CaseInsensitive)) { person.setName(person.name().mid(strlen(prefix))); break; } } 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; } QVariantList ExtractorPostprocessorPrivate::processActions(QVariantList actions) const { // remove non-actions and actions with invalid URLs QUrl viewUrl; for (auto it = actions.begin(); it != actions.end();) { if (!JsonLd::canConvert(*it)) { it = actions.erase(it); continue; } const auto action = JsonLd::convert(*it); if (!action.target().isValid()) { it = actions.erase(it); continue; } if (JsonLd::isA(*it)) { viewUrl = action.target(); } ++it; } // normalize the order, so JSON comparisson still yields correct results std::sort(actions.begin(), actions.end(), [](const QVariant &lhs, const QVariant &rhs) { return strcmp(lhs.typeName(), rhs.typeName()) < 0; }); // remove actions that don't actually have their own target, or duplicates QUrl prevUrl; const char* prevType = nullptr; for (auto it = actions.begin(); it != actions.end();) { const auto action = JsonLd::convert(*it); const auto isDuplicate = action.target() == prevUrl && (prevType ? strcmp(prevType, (*it).typeName()) == 0 : false); if ((JsonLd::isA(*it) || action.target() != viewUrl) && !isDuplicate) { prevUrl = action.target(); prevType = (*it).typeName(); ++it; } else { it = actions.erase(it); } } return actions; } 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().value()); } if (JsonLd::isA(res)) { return filterTrainOrBusTrip(res.value().reservationFor().value()); } if (JsonLd::isA(res)) { return filterLodgingReservation(res.value()); } if (JsonLd::isA(res)) { return filterEventReservation(res.value()); } if (JsonLd::isA(res)) { return filterFoodReservation(res.value()); } // types without specific filters yet if (JsonLd::isA(res) || 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(); } template bool ExtractorPostprocessorPrivate::filterTrainOrBusTrip(const T &trip) const { return filterTrainOrBusStation(trip.departureStation()) && filterTrainOrBusStation(trip.arrivalStation()) && trip.departureTime().isValid() && trip.arrivalTime().isValid(); } template bool ExtractorPostprocessorPrivate::filterTrainOrBusStation(const T &station) const { return !station.name().isEmpty(); } bool ExtractorPostprocessorPrivate::filterEventReservation(const EventReservation &res) const { const auto event = res.reservationFor().value(); return !event.name().isEmpty() && event.startDate().isValid(); } bool ExtractorPostprocessorPrivate::filterFoodReservation(const FoodEstablishmentReservation &res) const { return res.startTime().isValid(); } diff --git a/src/extractors/hertz.js b/src/extractors/hertz.js index 3d1eb30..38d86ed 100644 --- a/src/extractors/hertz.js +++ b/src/extractors/hertz.js @@ -1,99 +1,99 @@ /* Copyright (c) 2018 Laurent Montel 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. */ function main(text) { var res = JsonLd.newObject("RentalCarReservation"); var bookingRef = text.match(/Your reservation number is\s+([A-Z0-9-]+)\s+/); if (!bookingRef) return null; res.reservationNumber = bookingRef[1]; var idx = bookingRef.index + bookingRef[0].length; res.underName = JsonLd.newObject("Person"); var name = text.substr(idx).match(/Customer Name:\s+(.*)/); if (!name) return null; res.underName.name = name[1]; idx += name.index + name[0].length; var renting = text.substr(idx).match(/Renting\s+/); if (!renting) return null; idx += renting.index + renting[0].length; var cityPickup = text.substr(idx).match(/City:\s+(.*)/); - res.pickUpLocation = JsonLd.newObject("Place"); - res.pickUpLocation.address = JsonLd.newObject("PostalAddress"); - res.pickUpLocation.address.addressLocality = cityPickup[1]; + res.pickupLocation = JsonLd.newObject("Place"); + res.pickupLocation.address = JsonLd.newObject("PostalAddress"); + res.pickupLocation.address.addressLocality = cityPickup[1]; idx += cityPickup.index + cityPickup[0].length; var locationPickup = text.substr(idx).match(/Location:\s+(.*)/); - res.pickUpLocation.name = locationPickup[1]; + res.pickupLocation.name = locationPickup[1]; idx += locationPickup.index + locationPickup[0].length; var addressPickup = text.substr(idx).match(/Address:\s+(.*)/); - res.pickUpLocation.address.streetAddress = addressPickup[1]; + res.pickupLocation.address.streetAddress = addressPickup[1]; idx += addressPickup.index + addressPickup[0].length; //Problem for parsing date/time ! var pickUpDate = text.substr(idx).match(/Date\/Time:\s+(.*)/) if (!pickUpDate) return null; res.pickupTime = JsonLd.toDateTime(pickUpDate[1], "ddd dd MMM yyyy hh:mm A", "en"); idx += pickUpDate.index + pickUpDate[0].length; //Add tel/email etc. var returnCar = text.substr(idx).match(/Return\s+/); if (!returnCar) return null; idx += returnCar.index + returnCar[0].length; var cityDropoff = text.substr(idx).match(/City:\s+(.*)/); - res.dropOffLocation = JsonLd.newObject("Place"); - res.dropOffLocation.address = JsonLd.newObject("PostalAddress"); - res.dropOffLocation.address.addressLocality = cityDropoff[1]; + res.dropoffLocation = JsonLd.newObject("Place"); + res.dropoffLocation.address = JsonLd.newObject("PostalAddress"); + res.dropoffLocation.address.addressLocality = cityDropoff[1]; idx += cityDropoff.index + cityDropoff[0].length; var locationDropOff = text.substr(idx).match(/Location:\s+(.*)/); - res.dropOffLocation.name = locationDropOff[1]; + res.dropoffLocation.name = locationDropOff[1]; idx += locationDropOff.index + locationDropOff[0].length; var addressDropOff = text.substr(idx).match(/Address:\s+(.*)/); - res.dropOffLocation.address.streetAddress = addressDropOff[1]; + res.dropoffLocation.address.streetAddress = addressDropOff[1]; idx += addressDropOff.index + addressDropOff[0].length; var dropOffDate = text.substr(idx).match(/Date\/Time:\s*(.*)/) if (!dropOffDate) return null; //Need to convert datetime as "Date/Time: MON 22 MAY 2017 09:30 AM" res.dropoffTime = JsonLd.toDateTime(dropOffDate[1], "ddd dd MMM yyyy hh:mm A", "en"); idx += dropOffDate.index + dropOffDate[0].length; res.reservationFor = JsonLd.newObject("RentalCar"); //Fix me it seems to use 2 lines ! var vehiculeType = text.substr(idx).match(/Vehicle:\s+(.*)\s+/) if (!vehiculeType) return null; res.reservationFor.model = vehiculeType[1]; idx += vehiculeType.index + vehiculeType[0].length; return res; }