diff --git a/autotests/bcbpdata/iata-resolution792-example1.json b/autotests/bcbpdata/iata-resolution792-example1.json index e808abc..895f63d 100644 --- a/autotests/bcbpdata/iata-resolution792-example1.json +++ b/autotests/bcbpdata/iata-resolution792-example1.json @@ -1,29 +1,30 @@ [ { "@context": "http://schema.org", "@type": "FlightReservation", "airplaneSeat": "1A", "reservationFor": { "@type": "Flight", "airline": { "@type": "Airline", "iataCode": "AC" }, "arrivalAirport": { "@type": "Airport", "iataCode": "FRA" }, "departureAirport": { "@type": "Airport", "iataCode": "YUL" }, "departureDay": "2018-11-22", "flightNumber": "834" }, "reservationNumber": "ABC123", "underName": { "@type": "Person", - "name": "DESMARAIS/LUC" + "familyName": "DESMARAIS", + "givenName": "LUC" } } ] diff --git a/autotests/bcbpdata/iata-resolution792-example2.json b/autotests/bcbpdata/iata-resolution792-example2.json index c6f6d99..ecc65f4 100644 --- a/autotests/bcbpdata/iata-resolution792-example2.json +++ b/autotests/bcbpdata/iata-resolution792-example2.json @@ -1,29 +1,30 @@ [ { "@context": "http://schema.org", "@type": "FlightReservation", "airplaneSeat": "3A", "reservationFor": { "@type": "Flight", "airline": { "@type": "Airline", "iataCode": "AC" }, "arrivalAirport": { "@type": "Airport", "iataCode": "FRA" }, "departureAirport": { "@type": "Airport", "iataCode": "YUL" }, "departureDay": "2011-11-22", "flightNumber": "834" }, "reservationNumber": "AB12C3", "underName": { "@type": "Person", - "name": "DESMARAIS/LUC" + "familyName": "DESMARAIS", + "givenName": "LUC" } } ] diff --git a/autotests/bcbpdata/iata-resolution792-example3.json b/autotests/bcbpdata/iata-resolution792-example3.json index f54170b..027f3ef 100644 --- a/autotests/bcbpdata/iata-resolution792-example3.json +++ b/autotests/bcbpdata/iata-resolution792-example3.json @@ -1,29 +1,30 @@ [ { "@context": "http://schema.org", "@type": "FlightReservation", "airplaneSeat": "2F", "reservationFor": { "@type": "Flight", "airline": { "@type": "Airline", "iataCode": "AF" }, "arrivalAirport": { "@type": "Airport", "iataCode": "CDG" }, "departureAirport": { "@type": "Airport", "iataCode": "GVA" }, "departureDay": "2018-12-05", "flightNumber": "123" }, "reservationNumber": "ABC123", "underName": { "@type": "Person", - "name": "GRANDMAIRE/MELANIE" + "familyName": "GRANDMAIRE", + "givenName": "MELANIE" } } ] diff --git a/autotests/bcbpdata/iata-resolution792-example4.json b/autotests/bcbpdata/iata-resolution792-example4.json index cc5c318..4f99c53 100644 --- a/autotests/bcbpdata/iata-resolution792-example4.json +++ b/autotests/bcbpdata/iata-resolution792-example4.json @@ -1,56 +1,58 @@ [ { "@context": "http://schema.org", "@type": "FlightReservation", "airplaneSeat": "3A", "reservationFor": { "@type": "Flight", "airline": { "@type": "Airline", "iataCode": "AC" }, "arrivalAirport": { "@type": "Airport", "iataCode": "FRA" }, "departureAirport": { "@type": "Airport", "iataCode": "YUL" }, "departureDay": "2011-11-22", "flightNumber": "834" }, "reservationNumber": "AB12C3", "underName": { "@type": "Person", - "name": "DESMARAIS/LUC" + "familyName": "DESMARAIS", + "givenName": "LUC" } }, { "@context": "http://schema.org", "@type": "FlightReservation", "airplaneSeat": "12C", "reservationFor": { "@type": "Flight", "airline": { "@type": "Airline", "iataCode": "LH" }, "arrivalAirport": { "@type": "Airport", "iataCode": "GVA" }, "departureAirport": { "@type": "Airport", "iataCode": "FRA" }, "departureDay": "2011-11-23", "flightNumber": "3664" }, "reservationNumber": "DEF456", "underName": { "@type": "Person", - "name": "DESMARAIS/LUC" + "familyName": "DESMARAIS", + "givenName": "LUC" } } ] diff --git a/autotests/bcbpdata/iata-resolution792-example5.json b/autotests/bcbpdata/iata-resolution792-example5.json index 0e65d25..4fe3370 100644 --- a/autotests/bcbpdata/iata-resolution792-example5.json +++ b/autotests/bcbpdata/iata-resolution792-example5.json @@ -1,56 +1,58 @@ [ { "@context": "http://schema.org", "@type": "FlightReservation", "airplaneSeat": "2F", "reservationFor": { "@type": "Flight", "airline": { "@type": "Airline", "iataCode": "AF" }, "arrivalAirport": { "@type": "Airport", "iataCode": "CDG" }, "departureAirport": { "@type": "Airport", "iataCode": "GVA" }, "departureDay": "2018-12-05", "flightNumber": "123" }, "reservationNumber": "ABC123", "underName": { "@type": "Person", - "name": "GRANDMAIRE/MELANIE" + "familyName": "GRANDMAIRE", + "givenName": "MELANIE" } }, { "@context": "http://schema.org", "@type": "FlightReservation", "airplaneSeat": "1A", "reservationFor": { "@type": "Flight", "airline": { "@type": "Airline", "iataCode": "NW" }, "arrivalAirport": { "@type": "Airport", "iataCode": "DTW" }, "departureAirport": { "@type": "Airport", "iataCode": "CDG" }, "departureDay": "2018-12-05", "flightNumber": "49" }, "reservationNumber": "DEF456", "underName": { "@type": "Person", - "name": "GRANDMAIRE/MELANIE" + "familyName": "GRANDMAIRE", + "givenName": "MELANIE" } } ] diff --git a/autotests/bcbpdata/issue-date.json b/autotests/bcbpdata/issue-date.json index a4b726a..c5d346d 100644 --- a/autotests/bcbpdata/issue-date.json +++ b/autotests/bcbpdata/issue-date.json @@ -1,29 +1,30 @@ [ { "@context": "http://schema.org", "@type": "FlightReservation", "airplaneSeat": "23D", "reservationFor": { "@type": "Flight", "airline": { "@type": "Airline", "iataCode": "SN" }, "arrivalAirport": { "@type": "Airport", "iataCode": "BRU" }, "departureAirport": { "@type": "Airport", "iataCode": "TXL" }, "departureDay": "2017-02-03", "flightNumber": "2588" }, "reservationNumber": "XXX007", "underName": { "@type": "Person", - "name": "DOE/JOHN" + "familyName": "DOE", + "givenName": "JOHN" } } ] diff --git a/autotests/bcbpdata/missing-eticket-indicator.json b/autotests/bcbpdata/missing-eticket-indicator.json index b6f6648..0865499 100644 --- a/autotests/bcbpdata/missing-eticket-indicator.json +++ b/autotests/bcbpdata/missing-eticket-indicator.json @@ -1,29 +1,30 @@ [ { "@context": "http://schema.org", "@type": "FlightReservation", "airplaneSeat": "12C", "reservationFor": { "@type": "Flight", "airline": { "@type": "Airline", "iataCode": "EW" }, "arrivalAirport": { "@type": "Airport", "iataCode": "TXL" }, "departureAirport": { "@type": "Airport", "iataCode": "BRU" }, "departureDay": "2018-02-04", "flightNumber": "8103" }, "reservationNumber": "XXX007", "underName": { "@type": "Person", - "name": "DOE/JOHN" + "familyName": "DOE", + "givenName": "JOHN" } } ] diff --git a/autotests/mergeutiltest.cpp b/autotests/mergeutiltest.cpp index 1e70023..53aacf1 100644 --- a/autotests/mergeutiltest.cpp +++ b/autotests/mergeutiltest.cpp @@ -1,156 +1,165 @@ /* 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 #include #include #include #include #include #include #include using namespace KItinerary; class MergeUtilTest : public QObject { Q_OBJECT private: bool isSameFlight(const Flight &lhs, const Flight &rhs) { FlightReservation r1; r1.setReservationFor(lhs); r1.setReservationNumber(QLatin1String("XXX007")); FlightReservation r2; r2.setReservationFor(rhs); r2.setReservationNumber(QLatin1String("XXX007")); return MergeUtil::isSameReservation(r1, r2); } private Q_SLOTS: void testIsSameReservation() { QVERIFY(!MergeUtil::isSameReservation({}, {})); FlightReservation res1; QVERIFY(!MergeUtil::isSameReservation(res1, {})); QVERIFY(!MergeUtil::isSameReservation({}, res1)); res1.setReservationNumber(QLatin1String("XXX007")); Flight flight1; flight1.setFlightNumber(QLatin1String("1234")); flight1.setDepartureDay(QDate(2018, 4, 21)); res1.setReservationFor(flight1); FlightReservation res2; res2.setReservationNumber(QLatin1String("YYY008")); Flight flight2; flight2.setFlightNumber(QLatin1String("1234")); res2.setReservationFor(flight2); QVERIFY(!MergeUtil::isSameReservation(res1, res2)); flight2.setDepartureDay(QDate(2018, 4, 21)); res2.setReservationFor(flight2); QVERIFY(!MergeUtil::isSameReservation(res1, res2)); res2.setReservationNumber(QLatin1String("XXX007")); QVERIFY(MergeUtil::isSameReservation(res1, res2)); } void testIsSameFlight() { Airline airline1; airline1.setIataCode(QLatin1String("KL")); Flight f1; f1.setAirline(airline1); f1.setFlightNumber(QLatin1String("8457")); f1.setDepartureTime(QDateTime(QDate(2018, 4, 2), QTime(17, 51, 0))); Flight f2; QVERIFY(!isSameFlight(f1, f2)); f2.setFlightNumber(QLatin1String("8457")); QVERIFY(!isSameFlight(f1, f2)); Airline airline2; airline2.setIataCode(QLatin1String("AF")); f2.setAirline(airline2); QVERIFY(!isSameFlight(f1, f2)); airline2.setIataCode(QLatin1String("KL")); f2.setAirline(airline2); QVERIFY(!isSameFlight(f1, f2)); f2.setDepartureDay(QDate(2018, 4, 2)); QVERIFY(isSameFlight(f1, f2)); } void testCodeShareFlight() { Airline a1; a1.setIataCode(QLatin1String("4U")); Flight f1; f1.setAirline(a1); f1.setFlightNumber(QLatin1String("42")); f1.setDepartureDay(QDate(2018, 04, 21)); Airline a2; a2.setIataCode(QLatin1String("EW")); Flight f2(f1); f2.setAirline(a2); QVERIFY(isSameFlight(f1, f2)); } void testIsSamePerson() { Person p1; p1.setName(QLatin1String("VOLKER KRAUSE")); QVERIFY(!MergeUtil::isSamePerson(p1, {})); QVERIFY(!MergeUtil::isSamePerson({}, p1)); QVERIFY(MergeUtil::isSamePerson(p1, p1)); Person p2; p2.setName(QLatin1String("Volker Krause")); QVERIFY(MergeUtil::isSamePerson(p1, p2)); + + Person p3; + p3.setName(QLatin1String("KRAUSE Volker")); + p3.setFamilyName(QLatin1String("KRAUSE")); + p3.setGivenName(QLatin1String("Volker")); + + p1.setFamilyName(QLatin1String("Krause")); + p1.setGivenName(QLatin1String("VOLKER")); + QVERIFY(MergeUtil::isSamePerson(p1, p3)); } void testIsSameLodingReservation() { LodgingReservation res1; LodgingBusiness hotel1; hotel1.setName(QLatin1String("Haus Randa")); res1.setReservationFor(hotel1); res1.setCheckinTime(QDateTime(QDate(2018, 4, 9), QTime(10, 0))); res1.setReservationNumber(QLatin1String("1234")); LodgingReservation res2; QVERIFY(!MergeUtil::isSameReservation(res1, res2)); res2.setReservationNumber(QLatin1String("1234")); QVERIFY(!MergeUtil::isSameReservation(res1, res2)); res2.setCheckinTime(QDateTime(QDate(2018, 4, 9), QTime(15, 0))); QVERIFY(!MergeUtil::isSameReservation(res1, res2)); LodgingBusiness hotel2; hotel2.setName(QLatin1String("Haus Randa")); res2.setReservationFor(hotel2); QVERIFY(MergeUtil::isSameReservation(res1, res2)); } }; QTEST_APPLESS_MAIN(MergeUtilTest) #include "mergeutiltest.moc" diff --git a/autotests/pkpassdata/airbaltic.json b/autotests/pkpassdata/airbaltic.json index 349efa0..d6926fd 100644 --- a/autotests/pkpassdata/airbaltic.json +++ b/autotests/pkpassdata/airbaltic.json @@ -1,48 +1,50 @@ [ { "@context": "http://schema.org", "@type": "FlightReservation", "airplaneSeat": "14E", "reservationFor": { "@type": "Flight", "airline": { "@type": "Airline", "iataCode": "BT" }, "arrivalAirport": { "@type": "Airport", "geo": { "@type": "GeoCoordinates", "latitude": 56.92359924316406, "longitude": 23.971099853515625 }, "iataCode": "RIX" }, "boardingTime": { "@type": "QDateTime", "@value": "2017-11-05T08:25:00+01:00", "timezone": "Europe/Berlin" }, "departureAirport": { "@type": "Airport", "geo": { "@type": "GeoCoordinates", "latitude": 52.55970001220703, "longitude": 13.287799835205078 }, "iataCode": "TXL" }, "departureDay": "2017-11-05", "flightNumber": "212" }, "reservationNumber": "XXX007", "reservedTicket": { "@type": "Ticket", "ticketToken": "aztecCode:M1KRAUSE/VOLKER EXXX007 TXLRIXBT 0212 309Y014E0063 100" }, "underName": { "@type": "Person", - "name": "KRAUSE/VOLKER" + "familyName": "KRAUSE", + "givenName": "VOLKER", + "name": "VOLKER KRAUSE" } } ] diff --git a/autotests/pkpassdata/airberlin.json b/autotests/pkpassdata/airberlin.json index 0ea70b3..6ee5fb2 100644 --- a/autotests/pkpassdata/airberlin.json +++ b/autotests/pkpassdata/airberlin.json @@ -1,48 +1,50 @@ [ { "@context": "http://schema.org", "@type": "FlightReservation", "airplaneSeat": "19F", "reservationFor": { "@type": "Flight", "airline": { "@type": "Airline", "iataCode": "AB" }, "arrivalAirport": { "@type": "Airport", "geo": { "@type": "GeoCoordinates", "latitude": 48.35390090942383, "longitude": 11.786100387573242 }, "iataCode": "MUC" }, "boardingTime": { "@type": "QDateTime", "@value": "2017-10-24T15:55:00+02:00", "timezone": "Europe/Berlin" }, "departureAirport": { "@type": "Airport", "geo": { "@type": "GeoCoordinates", "latitude": 52.559688568115234, "longitude": 13.287711143493652 }, "iataCode": "TXL" }, "departureDay": "2017-10-24", "flightNumber": "6203" }, "reservationNumber": "XXX007", "reservedTicket": { "@type": "Ticket", "ticketToken": "aztecCode:M1KRAUSE/VOLKER EXXX007 TXLMUCAB 6203 297Y019F0060 33C>5080 B2A N00 " }, "underName": { "@type": "Person", - "name": "KRAUSE/VOLKER" + "familyName": "KRAUSE", + "givenName": "VOLKER", + "name": "VOLKER KRAUSE" } } ] diff --git a/autotests/pkpassdata/eurowings.json b/autotests/pkpassdata/eurowings.json index 4d2a8a5..166a5c8 100644 --- a/autotests/pkpassdata/eurowings.json +++ b/autotests/pkpassdata/eurowings.json @@ -1,52 +1,54 @@ [ { "@context": "http://schema.org", "@type": "FlightReservation", "airplaneSeat": "17C", "reservationFor": { "@type": "Flight", "airline": { "@type": "Airline", "iataCode": "4U", "name": "Germanwings" }, "arrivalAirport": { "@type": "Airport", "geo": { "@type": "GeoCoordinates", "latitude": 52.55970001220703, "longitude": 13.287799835205078 }, "iataCode": "TXL", "name": "Berlin-Tegel" }, "boardingTime": { "@type": "QDateTime", "@value": "2017-06-18T18:40:00+01:00", "timezone": "Europe/London" }, "departureAirport": { "@type": "Airport", "geo": { "@type": "GeoCoordinates", "latitude": 51.477500915527344, "longitude": -0.4613890051841736 }, "iataCode": "LHR", "name": "London Heathrow" }, "departureDay": "2017-06-18", "departureGate": " - ", "flightNumber": "8465" }, "reservationNumber": "XXX007", "reservedTicket": { "@type": "Ticket", "ticketToken": "aztecCode:M1KRAUSE/VOLKER EXXX007 LHRTXL4U 8465 169Y017C0040 147>1181 7168B4U 0000000000000291040PASSPORTID2 LH 123412341234012 " }, "underName": { "@type": "Person", - "name": "KRAUSE/VOLKER" + "familyName": "KRAUSE", + "givenName": "VOLKER", + "name": "VOLKER KRAUSE" } } ] diff --git a/autotests/pkpassdata/lufthansa.json b/autotests/pkpassdata/lufthansa.json index 6670dcf..c8c9382 100644 --- a/autotests/pkpassdata/lufthansa.json +++ b/autotests/pkpassdata/lufthansa.json @@ -1,51 +1,53 @@ [ { "@context": "http://schema.org", "@type": "FlightReservation", "airplaneSeat": "5A", "reservationFor": { "@type": "Flight", "airline": { "@type": "Airline", "iataCode": "LH" }, "arrivalAirport": { "@type": "Airport", "geo": { "@type": "GeoCoordinates", "latitude": 52.55970001220703, "longitude": 13.287799835205078 }, "iataCode": "TXL", "name": "BERLIN" }, "boardingTime": { "@type": "QDateTime", "@value": "2018-03-06T17:05:00+01:00", "timezone": "Europe/Berlin" }, "departureAirport": { "@type": "Airport", "geo": { "@type": "GeoCoordinates", "latitude": 48.35390090942383, "longitude": 11.786100387573242 }, "iataCode": "MUC", "name": "MUNICH" }, "departureDay": "2018-03-06", "departureGate": "G20", "flightNumber": "2724" }, "reservationNumber": "XXX007", "reservedTicket": { "@type": "Ticket", "ticketToken": "aztecCode:M1DOE/JOHN EXXX007 MUCTXLLH 2724 065M005A0017 35D>6180WW8064BLH 2A22012345678900 LH N*30600000K09 " }, "underName": { "@type": "Person", - "name": "DOE/JOHN" + "familyName": "DOE", + "givenName": "JOHN", + "name": "JOHN DOE" } } ] diff --git a/autotests/pkpassdata/swiss.json b/autotests/pkpassdata/swiss.json index e099d06..8546f57 100644 --- a/autotests/pkpassdata/swiss.json +++ b/autotests/pkpassdata/swiss.json @@ -1,51 +1,53 @@ [ { "@context": "http://schema.org", "@type": "FlightReservation", "airplaneSeat": "10E", "pkpassPassTypeIdentifier": "pass.booking.swiss.com", "pkpassSerialNumber": "123456789", "reservationFor": { "@type": "Flight", "airline": { "@type": "Airline", "iataCode": "LX" }, "arrivalAirport": { "@type": "Airport", "geo": { "@type": "GeoCoordinates", "latitude": 52.55970001220703, "longitude": 13.287799835205078 }, "iataCode": "TXL" }, "boardingTime": { "@type": "QDateTime", "@value": "2017-09-15T20:25:00+02:00", "timezone": "Europe/Zurich" }, "departureAirport": { "@type": "Airport", "geo": { "@type": "GeoCoordinates", "latitude": 47.452301025390625, "longitude": 8.560830116271973 }, "iataCode": "ZRH" }, "departureDay": "2017-09-15", "departureGate": "AB", "flightNumber": "962" }, "reservationNumber": "XXX007", "reservedTicket": { "@type": "Ticket", "ticketToken": "aztecCode:M1KRAUSE/VOLKER EXXX007 ZRHTXLLX 0962 258Y010E0143 15D>5180 M7258BLX 2A724xxxxxxxxxx0 LX LH 123456789012345 N*30600000K09 " }, "underName": { "@type": "Person", - "name": "KRAUSE/VOLKER" + "familyName": "KRAUSE", + "givenName": "VOLKER", + "name": "VOLKER KRAUSE" } } ] diff --git a/autotests/postprocessordata/bcbp-expansion.post.json b/autotests/postprocessordata/bcbp-expansion.post.json index e6d7d5b..3d18feb 100644 --- a/autotests/postprocessordata/bcbp-expansion.post.json +++ b/autotests/postprocessordata/bcbp-expansion.post.json @@ -1,48 +1,50 @@ [ { "@context": "http://schema.org", "@type": "FlightReservation", "airplaneSeat": "1A", "reservationFor": { "@type": "Flight", "airline": { "@type": "Airline", "iataCode": "AC" }, "arrivalAirport": { "@type": "Airport", "geo": { "@type": "GeoCoordinates", "latitude": 50.03329849243164, "longitude": 8.570560455322266 }, "iataCode": "FRA" }, "boardingTime": { "@type": "QDateTime", "@value": "2027-03-05T06:30:00-05:00", "timezone": "America/Toronto" }, "departureAirport": { "@type": "Airport", "geo": { "@type": "GeoCoordinates", "latitude": 45.47060012817383, "longitude": -73.74079895019531 }, "iataCode": "YUL" }, "departureDay": "2027-03-05", "flightNumber": "834" }, "reservationNumber": "ABC123", "reservedTicket": { "@type": "Ticket", "ticketToken": "aztecCode:M1DESMARAIS/LUC EABC123 YULFRAAC 0834 326J001A0025 100" }, "underName": { "@type": "Person", - "name": "DESMARAIS/LUC" + "name": "LUC DESMARAIS", + "familyName": "DESMARAIS", + "givenName": "LUC" } } ] diff --git a/autotests/postprocessordata/flight-merge.post.json b/autotests/postprocessordata/flight-merge.post.json index 5664cc7..c01626e 100644 --- a/autotests/postprocessordata/flight-merge.post.json +++ b/autotests/postprocessordata/flight-merge.post.json @@ -1,63 +1,65 @@ [ { "@context": "http://schema.org", "@type": "FlightReservation", "airplaneSeat": "14D", "pkpassPassTypeIdentifier": "pass.ncr.brussels.boarding-pass", "pkpassSerialNumber": "123456789", "reservationFor": { "@type": "Flight", "airline": { "@type": "Airline", "iataCode": "SN", "name": "Brussels Airlines" }, "arrivalAirport": { "@type": "Airport", "geo": { "@type": "GeoCoordinates", "latitude": 43.6349983215332, "longitude": 1.3677799701690674 }, "iataCode": "TLS", "name": "Toulouse, Blagnac Airport, FR" }, "arrivalTime": { "@type": "QDateTime", "@value": "2018-04-20T11:20:00+02:00", "timezone": "Europe/Paris" }, "boardingTime": { "@type": "QDateTime", "@value": "2018-04-20T09:10:00+02:00", "timezone": "Europe/Brussels" }, "departureAirport": { "@type": "Airport", "geo": { "@type": "GeoCoordinates", "latitude": 50.88970184326172, "longitude": 4.478000164031982 }, "iataCode": "BRU", "name": "Brussels Airport, BE" }, "departureDay": "2018-04-20", "departureTime": { "@type": "QDateTime", "@value": "2018-04-20T09:40:00+02:00", "timezone": "Europe/Brussels" }, "flightNumber": "3667" }, "reservationNumber": "XXX007", "reservedTicket": { "@type": "Ticket", "ticketToken": "aztecCode:M1DOE/JOHNMR EXXX007 BRUTLSSN 3667 110Y014D0014 35D>5181WM8109BSN 2A08221234567890 SN LH 123456789012345 *30600000K0902 " }, "underName": { "@type": "Person", - "name": "DOE/JOHNMR" + "name": "JOHNMR DOE", + "familyName": "DOE", + "givenName": "JOHNMR" } } ] diff --git a/src/datatypes/person.cpp b/src/datatypes/person.cpp index c1b0cff..a4cf97a 100644 --- a/src/datatypes/person.cpp +++ b/src/datatypes/person.cpp @@ -1,38 +1,42 @@ /* 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 "person.h" #include "datatypes_p.h" using namespace KItinerary; namespace KItinerary { class PersonPrivate : public QSharedData { public: QString name; QString email; + QString familyName; + QString givenName; }; KITINERARY_MAKE_SIMPLE_CLASS(Person) KITINERARY_MAKE_PROPERTY(Person, QString, name, setName) KITINERARY_MAKE_PROPERTY(Person, QString, email, setEmail) +KITINERARY_MAKE_PROPERTY(Person, QString, familyName, setFamilyName) +KITINERARY_MAKE_PROPERTY(Person, QString, givenName, setGivenName) } #include "moc_person.cpp" diff --git a/src/datatypes/person.h b/src/datatypes/person.h index d98301a..8d96fe0 100644 --- a/src/datatypes/person.h +++ b/src/datatypes/person.h @@ -1,44 +1,46 @@ /* 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_PERSON_H #define KITINERARY_PERSON_H #include "kitinerary_export.h" #include "datatypes.h" namespace KItinerary { class PersonPrivate; /** A person * @see https://schema.org/Person */ class KITINERARY_EXPORT Person { KITINERARY_GADGET(Person) KITINERARY_PROPERTY(QString, name, setName) KITINERARY_PROPERTY(QString, email, setEmail) + KITINERARY_PROPERTY(QString, familyName, setFamilyName) + KITINERARY_PROPERTY(QString, givenName, setGivenName) private: QExplicitlySharedDataPointer d; }; } Q_DECLARE_METATYPE(KItinerary::Person) #endif // KITINERARY_PERSON_H diff --git a/src/extractorpostprocessor.cpp b/src/extractorpostprocessor.cpp index 2fa2a51..4428d84 100644 --- a/src/extractorpostprocessor.cpp +++ b/src/extractorpostprocessor.cpp @@ -1,323 +1,342 @@ /* Copyright (c) 2017 Volker Krause This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "extractorpostprocessor.h" #include "airportdb/airportdb.h" #include "calendarhandler.h" #include "iatabcbpparser.h" #include "jsonlddocument.h" #include "logging.h" #include "mergeutil.h" #include #include #include +#include #include #include #include #include #include #include #include #include #include using namespace KItinerary; namespace KItinerary { class ExtractorPostprocessorPrivate { public: void mergeOrAppend(const QVariant &elem); template ObjT processProperty(ObjT obj, const char *name, PropT (ExtractorPostprocessorPrivate::*processor)(PropT) const) const; QVariant processFlightReservation(FlightReservation res) const; Flight processFlight(Flight flight) const; Airport processAirport(Airport airport) const; Airline processAirline(Airline airline) const; void processFlightTime(Flight &flight, const char *timePropName, const Airport &airport) const; QVariant processReservation(QVariant res) const; + Person processPerson(Person person) 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 (elem.userType() == qMetaTypeId()) { elem = d->processFlightReservation(elem.value()); } else if (elem.userType() == qMetaTypeId()) { elem = d->processReservation(elem); } else if (elem.userType() == qMetaTypeId()) { elem = d->processReservation(elem); } else if (elem.userType() == qMetaTypeId()) { elem = d->processReservation(elem); } + + if (JsonLd::canConvert(elem)) { + const auto res = JsonLd::convert(elem); + if (!res.underName().isNull()) { + const auto person = d->processPerson(res.underName().value()); + JsonLdDocument::writeProperty(elem, "underName", person); + } + } + 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); }); 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::isSameReservation(elem, other); }); if (it == m_data.end()) { m_data.push_back(elem); } else { *it = JsonLdDocument::apply(*it, elem); } } template ObjT ExtractorPostprocessorPrivate::processProperty(ObjT obj, const char *name, PropT (ExtractorPostprocessorPrivate::*processor)(PropT) const) const { auto value = JsonLdDocument::readProperty(obj, name).template value(); value = (this->*processor)(value); JsonLdDocument::writeProperty(obj, name, value); return obj; } QVariant ExtractorPostprocessorPrivate::processFlightReservation(FlightReservation res) const { // expand ticketToken for IATA BCBP data auto bcbp = res.reservedTicket().value().ticketToken(); if (!bcbp.isEmpty()) { if (bcbp.startsWith(QLatin1String("aztecCode:"))) { bcbp = bcbp.mid(10); } else if (bcbp.startsWith(QLatin1String("qrCode:"))) { bcbp = bcbp.mid(7); } const auto bcbpData = IataBcbpParser::parse(bcbp, m_contextDate.date()); if (bcbpData.size() == 1) { res = JsonLdDocument::apply(bcbpData.at(0), res).value(); } } res = processReservation(res).value(); res = processProperty(res, "reservationFor", &ExtractorPostprocessorPrivate::processFlight); return res; } Flight ExtractorPostprocessorPrivate::processFlight(Flight flight) const { flight = processProperty(flight, "departureAirport", &ExtractorPostprocessorPrivate::processAirport); flight = processProperty(flight, "arrivalAirport", &ExtractorPostprocessorPrivate::processAirport); flight = processProperty(flight, "airline", &ExtractorPostprocessorPrivate::processAirline); processFlightTime(flight, "boardingTime", flight.departureAirport()); processFlightTime(flight, "departureTime", flight.departureAirport()); processFlightTime(flight, "arrivalTime", 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); } } return airport; } Airline ExtractorPostprocessorPrivate::processAirline(Airline airline) const { airline.setName(airline.name().trimmed()); return airline; } void ExtractorPostprocessorPrivate::processFlightTime(Flight &flight, const char *timePropName, const Airport &airport) const { auto dt = JsonLdDocument::readProperty(flight, timePropName).toDateTime(); 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()); JsonLdDocument::writeProperty(flight, timePropName, 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 JsonLdDocument::writeProperty(flight, timePropName, QDateTime()); JsonLdDocument::writeProperty(flight, timePropName, dt); } 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; +} + bool ExtractorPostprocessorPrivate::filterReservation(const QVariant &res) const { const auto resFor = JsonLdDocument::readProperty(res, "reservationFor"); if (resFor.isNull()) { return false; } if (resFor.userType() == qMetaTypeId()) { return filterFlight(resFor.value()); } else if (resFor.userType() == qMetaTypeId()) { return filterTrainOrBusTrip(resFor); } else if (resFor.userType() == qMetaTypeId()) { return filterTrainOrBusTrip(resFor); } if (res.userType() == qMetaTypeId()) { return filterLodgingReservation(res.value()); } return true; } 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/iatabcbpparser.cpp b/src/iatabcbpparser.cpp index 3c846c4..831ef24 100644 --- a/src/iatabcbpparser.cpp +++ b/src/iatabcbpparser.cpp @@ -1,211 +1,219 @@ /* 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 "iatabcbpparser.h" #include "logging.h" #include #include #include #include #include #include #include using namespace KItinerary; namespace KItinerary { enum Constants { UniqueMandatorySize = 23, RepeastedMandatorySize = 37, FormatCode = 'M', BeginOfVersionNumber = '>' }; static QStringRef stripLeadingZeros(const QStringRef &s) { const auto it = std::find_if(s.begin(), s.end(), [](const QChar &c) { return c != QLatin1Char('0'); }); const auto d = std::distance(s.begin(), it); return s.mid(d); } static int readHexValue(const QStringRef &s, int width) { return s.mid(0, width).toInt(nullptr, 16); } static int parseRepeatedMandatorySection(const QStringRef& msg, FlightReservation& res) { res.setReservationNumber(msg.mid(0, 7).trimmed().toString()); Flight flight; Airport airport; airport.setIataCode(msg.mid(7, 3).toString()); flight.setDepartureAirport(airport); airport.setIataCode(msg.mid(10, 3).toString()); flight.setArrivalAirport(airport); Airline airline; airline.setIataCode(msg.mid(13, 3).trimmed().toString()); flight.setAirline(airline); flight.setFlightNumber(stripLeadingZeros(msg.mid(16, 5).trimmed()).toString()); // 3x Date of flight, as days since Jan 1st // we don't know the year here, so use 1970, will be filled up or discarded by the caller const auto days = msg.mid(21, 3).toInt() - 1; flight.setDepartureDay(QDate(1970, 1, 1).addDays(days)); res.setReservationFor(flight); // 1x Compartment code res.setAirplaneSeat(stripLeadingZeros(msg.mid(25, 4)).toString()); // 5x Checkin sequence number // 1x Passenger status // field size of conditional section + airline use section return readHexValue(msg.mid(35), 2); } } QVector IataBcbpParser::parse(const QString& message, const QDate &externalIssueDate) { if (message.size() < (UniqueMandatorySize + RepeastedMandatorySize)) { qCWarning(Log) << "IATA BCBP code too short"; return {}; } if (message.at(0) != QLatin1Char(FormatCode) || !message.at(1).isDigit()) { qCWarning(Log) << "IATA BCBP code invalid unique mandatory section format"; return {}; } // parse unique mandatory section const auto legCount = message.at(1).toLatin1() - '0'; QVector result; result.reserve(legCount); FlightReservation res1; - Person person; - const auto fullName = message.midRef(2, 20).trimmed(); - // TODO split in family and given name - person.setName(fullName.toString()); - res1.setUnderName(person); + { + Person person; + const auto fullName = message.midRef(2, 20).trimmed(); + + const auto idx = fullName.indexOf(QLatin1Char('/')); + if (idx > 0 && idx < fullName.size() - 1) { + person.setFamilyName(fullName.left(idx).toString()); + person.setGivenName(fullName.mid(idx + 1).toString()); + } else { + person.setName(fullName.toString()); + } + res1.setUnderName(person); + } const auto varSize = parseRepeatedMandatorySection(message.midRef(UniqueMandatorySize), res1); int index = UniqueMandatorySize + RepeastedMandatorySize; if (message.size() < (index + varSize)) { qCWarning(Log) << "IATA BCBP code too short for conditional section in first leg" << varSize << message.size(); return {}; } auto issueDate = externalIssueDate; if (varSize > 0) { // parse unique conditional section if (message.at(index) != QLatin1Char(BeginOfVersionNumber)) { qCWarning(Log) << "IATA BCBP unique conditional section has invalid format"; return {}; } // 1x version number // 2x field size of unique conditional section const auto uniqCondSize = readHexValue(message.midRef(index + 2), 2); if (uniqCondSize + 4 > varSize) { qCWarning(Log) << "IATA BCBP unique conditional section has invalid size" << varSize << uniqCondSize; return {}; } // 1x passenger description // 1x source of checking // 1x source of boarding pass issuance // 4x date of issue of boarding pass // this only contains the last digit of the year (sic), but we assume it to be in the past // so this still gives us a 10 year range of correctly determined times if (uniqCondSize >= 11 && externalIssueDate.isValid()) { const auto year = message.at(index + 7).toLatin1() - '0'; const auto days = message.midRef(index + 8, 3).toInt() - 1; if (year < 0 || year > 9 || days < 0 || days > 365) { qCWarning(Log) << "IATA BCBP invalid boarding pass issue date format" << message.midRef(index + 7, 8); return {}; } auto currentYear = externalIssueDate.year() - externalIssueDate.year() % 10 + year; if (currentYear > externalIssueDate.year()) { currentYear -= 10; } issueDate = QDate(currentYear, 1, 1).addDays(days); } // 1x document type // 3x airline code of boarding pass issuer // 3x 13x baggage tag numbers // skip repeated conditional section, containing mainly bonus program data // skip for airline use section index += varSize; } result.push_back(res1); // all following legs only contain repeated sections, copy content from the unique ones from the first leg for (int i = 1; i < legCount; ++i) { if (message.size() < (index + RepeastedMandatorySize)) { qCWarning(Log) << "IATA BCBP repeated mandatory section too short" << i; return {}; } FlightReservation res = res1; const auto varSize = parseRepeatedMandatorySection(message.midRef(index), res); index += RepeastedMandatorySize; if (message.size() < (index + varSize)) { qCWarning(Log) << "IATA BCBP repeated conditional section too short" << i; return {}; } // skip repeated conditional section // skip for airline use section index += varSize; result.push_back(res); } // optional security section at the end, not interesting for us // complete departure dates with the now (hopefully known issue date) for (auto it = result.begin(); it != result.end(); ++it) { auto res = (*it).value(); auto flight = res.reservationFor().value(); if (issueDate.isValid()) { const auto days = flight.departureDay().dayOfYear() - 1; QDate date(issueDate.year(), 1, 1); date = date.addDays(days); if (date >= issueDate) { flight.setDepartureDay(date); } else { flight.setDepartureDay(QDate(issueDate.year() + 1, 1, 1).addDays(days)); } } else { flight.setDepartureDay(QDate()); } res.setReservationFor(flight); *it = res; } return result; } diff --git a/src/mergeutil.cpp b/src/mergeutil.cpp index 01afa17..0ea46b2 100644 --- a/src/mergeutil.cpp +++ b/src/mergeutil.cpp @@ -1,211 +1,217 @@ /* 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 "mergeutil.h" #include #include #include #include #include #include #include #include #include #include #include using namespace KItinerary; /* Checks that @p lhs and @p rhs are non-empty and equal. */ -static bool equalAndPresent(const QString &lhs, const QString &rhs) +static bool equalAndPresent(const QString &lhs, const QString &rhs, Qt::CaseSensitivity caseSensitive = Qt::CaseSensitive) { - return !lhs.isEmpty() && lhs == rhs; + return !lhs.isEmpty() && (lhs.compare(rhs, caseSensitive) == 0); } static bool equalAndPresent(const QDate &lhs, const QDate &rhs) { return lhs.isValid() && lhs == rhs; } /* Checks that @p lhs and @p rhs are not non-equal if both values are set. */ static bool conflictIfPresent(const QString &lhs, const QString &rhs) { return !lhs.isEmpty() && !rhs.isEmpty() && lhs != rhs; } static bool conflictIfPresent(const QDateTime &lhs, const QDateTime &rhs) { return lhs.isValid() && rhs.isValid() && lhs != rhs; } static bool isSameFlight(const Flight &lhs, const Flight &rhs); static bool isSameTrainTrip(const TrainTrip &lhs, const TrainTrip &rhs); static bool isSameBusTrip(const BusTrip &lhs, const BusTrip &rhs); static bool isSameLodingBusiness(const LodgingBusiness &lhs, const LodgingBusiness &rhs); static bool isSameFoodEstablishment(const FoodEstablishment &lhs, const FoodEstablishment &rhs); bool MergeUtil::isSameReservation(const QVariant& lhs, const QVariant& rhs) { if (lhs.isNull() || rhs.isNull() || !JsonLd::canConvert(lhs) || !JsonLd::canConvert(rhs)) { return false; } if (lhs.userType() != rhs.userType()) { return false; } // flight: booking ref, flight number and departure day match if (lhs.userType() == qMetaTypeId()) { const auto lhsRes = lhs.value(); const auto rhsRes = rhs.value(); if (lhsRes.reservationNumber() != rhsRes.reservationNumber() || lhsRes.reservationNumber().isEmpty()) { return false; } const auto lhsFlight = lhsRes.reservationFor().value(); const auto rhsFlight = rhsRes.reservationFor().value(); if (!isSameFlight(lhsFlight, rhsFlight)) { return false; } } // train: booking ref, train number and depature day match if (lhs.userType() == qMetaTypeId()) { const auto lhsRes = lhs.value(); const auto rhsRes = rhs.value(); if (lhsRes.reservationNumber() != rhsRes.reservationNumber()) { return false; } const auto lhsTrip = lhsRes.reservationFor().value(); const auto rhsTrip = rhsRes.reservationFor().value(); if (!isSameTrainTrip(lhsTrip, rhsTrip)) { return false; } } // bus: booking ref, number and depature time match if (lhs.userType() == qMetaTypeId()) { const auto lhsRes = lhs.value(); const auto rhsRes = rhs.value(); if (lhsRes.reservationNumber() != rhsRes.reservationNumber()) { return false; } const auto lhsTrip = lhsRes.reservationFor().value(); const auto rhsTrip = rhsRes.reservationFor().value(); if (!isSameBusTrip(lhsTrip, rhsTrip)) { return false; } } // hotel: booking ref, checkin day, name match if (lhs.userType() == qMetaTypeId()) { const auto lhsRes = lhs.value(); const auto rhsRes = rhs.value(); if (lhsRes.reservationNumber() != rhsRes.reservationNumber()) { return false; } const auto lhsHotel = lhsRes.reservationFor().value(); const auto rhsHotel = rhsRes.reservationFor().value(); if (!isSameLodingBusiness(lhsHotel, rhsHotel) || lhsRes.checkinTime().date() != rhsRes.checkinTime().date()) { return false; } } // restaurant reservation: sane restaurant, same booking ref, same day if (lhs.userType() == qMetaTypeId()) { const auto lhsRes = lhs.value(); const auto rhsRes = rhs.value(); if (lhsRes.reservationNumber() != rhsRes.reservationNumber()) { return false; } const auto lhsRestaurant = lhsRes.reservationFor().value(); const auto rhsRestaurant = rhsRes.reservationFor().value(); if (!isSameFoodEstablishment(lhsRestaurant, rhsRestaurant) || lhsRes.startTime().date() != rhsRes.endTime().date()) { return false; } } // for all: underName either matches or is not set const auto lhsUN = JsonLd::convert(lhs).underName(); const auto rhsUN = JsonLd::convert(rhs).underName(); return lhsUN.isNull() || rhsUN.isNull() || isSamePerson(lhsUN.value(), rhsUN.value()); } static bool isSameFlight(const Flight& lhs, const Flight& rhs) { // if there is a conflict on where this is going, or when, this is obviously not the same flight if (conflictIfPresent(lhs.departureAirport().iataCode(), rhs.departureAirport().iataCode()) || conflictIfPresent(lhs.arrivalAirport().iataCode(), rhs.arrivalAirport().iataCode()) || !equalAndPresent(lhs.departureDay(), rhs.departureDay())) { return false; } // same flight number and airline (on the same day) -> we assume same flight if (equalAndPresent(lhs.flightNumber(), rhs.flightNumber()) && equalAndPresent(lhs.airline().iataCode(), rhs.airline().iataCode())) { return true; } // we get here if we have matching origin/destination on the same day, but mismatching flight numbers // so this might be a codeshare flight // our caller checks for matching booking ref, so just look for a few counter-indicators here // (that is, if this is ever made available as standalone API, the last return should not be true) if (conflictIfPresent(lhs.departureTime(), rhs.departureTime())) { return false; } return true; } static bool isSameTrainTrip(const TrainTrip &lhs, const TrainTrip &rhs) { if (lhs.trainNumber().isEmpty() || rhs.trainNumber().isEmpty()) { return false; } return lhs.trainName() == rhs.trainName() && lhs.trainNumber() == rhs.trainNumber() && lhs.departureTime().date() == rhs.departureTime().date(); } static bool isSameBusTrip(const BusTrip &lhs, const BusTrip &rhs) { if (lhs.busNumber().isEmpty() || rhs.busNumber().isEmpty()) { return false; } return lhs.busName() == rhs.busName() && lhs.busNumber() == rhs.busNumber() && lhs.departureTime() == rhs.departureTime(); } static bool isSameLodingBusiness(const LodgingBusiness &lhs, const LodgingBusiness &rhs) { if (lhs.name().isEmpty() || rhs.name().isEmpty()) { return false; } return lhs.name() == rhs.name(); } static bool isSameFoodEstablishment(const FoodEstablishment &lhs, const FoodEstablishment &rhs) { if (lhs.name().isEmpty() || rhs.name().isEmpty()) { return false; } return lhs.name() == rhs.name(); } bool MergeUtil::isSamePerson(const Person& lhs, const Person& rhs) { - // TODO: extend this once Person has familiy and given name fields - return lhs.name().compare(rhs.name(), Qt::CaseInsensitive) == 0; + if (lhs.name().compare(rhs.name(), Qt::CaseInsensitive) == 0) { + return true; + } + + return equalAndPresent(lhs.familyName(), rhs.familyName(), Qt::CaseInsensitive) + && equalAndPresent(lhs.givenName(), rhs.givenName(), Qt::CaseInsensitive); + + // TODO deal with cases where on side has the name split, and the other side only has the full name }