KItinerary

extractorpostprocessor.cpp
1 /*
2  SPDX-FileCopyrightText: 2017 Volker Krause <[email protected]>
3 
4  SPDX-License-Identifier: LGPL-2.0-or-later
5 */
6 
7 #include "config-kitinerary.h"
8 #include "extractorpostprocessor.h"
9 #include "extractorpostprocessor_p.h"
10 #include "extractorvalidator.h"
11 #include "flightpostprocessor_p.h"
12 #include "stringutil.h"
13 
14 #include "iata/iatabcbpparser.h"
15 #include "jsonlddocument.h"
16 #include "logging.h"
17 #include "mergeutil.h"
18 #include "sortutil.h"
19 #include "text/addressparser_p.h"
20 
21 #include "knowledgedb/airportdb.h"
22 #include "knowledgedb/timezonedb_p.h"
23 #include "knowledgedb/trainstationdb.h"
24 
25 #include <KItinerary/Action>
26 #include <KItinerary/BusTrip>
27 #include <KItinerary/Event>
28 #include <KItinerary/Flight>
29 #include <KItinerary/Organization>
30 #include <KItinerary/Person>
31 #include <KItinerary/Place>
32 #include <KItinerary/ProgramMembership>
33 #include <KItinerary/RentalCar>
34 #include <KItinerary/Reservation>
35 #include <KItinerary/Taxi>
36 #include <KItinerary/Ticket>
37 #include <KItinerary/TrainTrip>
38 #include <KItinerary/Visit>
39 
40 #include <KCountry>
41 
42 #include <QDebug>
43 #include <QJsonArray>
44 #include <QJsonDocument>
45 #include <QTimeZone>
46 #include <QUrl>
47 
48 #if HAVE_PHONENUMBER
49 #include <phonenumbers/phonenumberutil.h>
50 #endif
51 
52 #include <algorithm>
53 
54 using namespace KItinerary;
55 
56 ExtractorPostprocessor::ExtractorPostprocessor()
57  : d(new ExtractorPostprocessorPrivate)
58 {
59  // configure the default set of accepted types, for backward compatibility
60  d->m_validator.setAcceptedTypes<
69  // reservationFor types
70  Flight,
71  TrainTrip,
72  BusTrip,
73  RentalCar,
74  Taxi,
75  Event,
76  TouristAttractionVisit,
78  // PBI types
80  >();
81 }
82 
83 ExtractorPostprocessor::ExtractorPostprocessor(ExtractorPostprocessor &&) noexcept = default;
85 
86 void ExtractorPostprocessor::process(const QVector<QVariant> &data)
87 {
88  d->m_resultFinalized = false;
89  d->m_data.reserve(d->m_data.size() + data.size());
90  for (auto elem : data) {
91  // reservation types
92  if (JsonLd::isA<FlightReservation>(elem)) {
93  elem = d->processFlightReservation(elem.value<FlightReservation>());
94  } else if (JsonLd::isA<TrainReservation>(elem)) {
95  elem = d->processTrainReservation(elem.value<TrainReservation>());
96  } else if (JsonLd::isA<LodgingReservation>(elem)) {
97  elem = d->processLodgingReservation(elem.value<LodgingReservation>());
98  } else if (JsonLd::isA<FoodEstablishmentReservation>(elem)) {
99  elem = d->processFoodEstablishmentReservation(elem.value<FoodEstablishmentReservation>());
100  } else if (JsonLd::isA<TouristAttractionVisit>(elem)) {
101  elem = d->processTouristAttractionVisit(elem.value<TouristAttractionVisit>());
102  } else if (JsonLd::isA<BusReservation>(elem)) {
103  elem = d->processBusReservation(elem.value<BusReservation>());
104  } else if (JsonLd::isA<EventReservation>(elem)) {
105  elem = d->processEventReservation(elem.value<EventReservation>());
106  } else if (JsonLd::isA<RentalCarReservation>(elem)) {
107  elem = d->processRentalCarReservation(elem.value<RentalCarReservation>());
108  } else if (JsonLd::isA<TaxiReservation>(elem)) {
109  elem = d->processTaxiReservation(elem.value<TaxiReservation>());
110  }
111 
112  // "reservationFor" types
113  else if (JsonLd::isA<LodgingBusiness>(elem)) {
114  elem = d->processPlace(elem.value<LodgingBusiness>());
115  } else if (JsonLd::isA<FoodEstablishment>(elem)) {
116  elem = d->processPlace(elem.value<FoodEstablishment>());
117  } else if (JsonLd::isA<Event>(elem)) {
118  elem = d->processEvent(elem.value<Event>());
119  }
120 
121  // non-reservation types
122  else if (JsonLd::isA<ProgramMembership>(elem)) {
123  elem = d->processProgramMembership(elem.value<ProgramMembership>());
124  } else if (JsonLd::isA<Ticket>(elem)) {
125  elem = d->processTicket(elem.value<Ticket>());
126  }
127 
128  d->mergeOrAppend(elem);
129  }
130 }
131 
133 {
134  if (!d->m_resultFinalized) {
135  if (d->m_validationEnabled) {
136  d->m_data.erase(std::remove_if(d->m_data.begin(), d->m_data.end(), [this](const auto &elem) {
137  return !d->m_validator.isValidElement(elem);
138  }), d->m_data.end());
139  }
140 
141  // search for "triangular" patterns, ie. a location change element that has a matching departure
142  // and matching arrival to two different other location change elements (A->C vs A->B + B->C).
143  // we remove those, as the fine-granular results are better
144  if (d->m_data.size() >= 3) {
145  for (auto it = d->m_data.begin(); it != d->m_data.end();) {
146  auto depIt = it;
147  auto arrIt = it;
148  for (auto it2 = d->m_data.begin(); it2 != d->m_data.end(); ++it2) {
149  if (it == it2) {
150  continue;
151  }
152  if (MergeUtil::hasSameDeparture(*it, *it2)) {
153  depIt = it2;
154  }
155  if (MergeUtil::hasSameArrival(*it, *it2)) {
156  arrIt = it2;
157  }
158  }
159 
160  if (depIt != it && arrIt != it && depIt != arrIt) {
161  it = d->m_data.erase(it);
162  } else {
163  ++it;
164  }
165  }
166  }
167 
168  d->m_resultFinalized = true;
169  }
170 
171  std::stable_sort(d->m_data.begin(), d->m_data.end(), SortUtil::isBefore);
172  return d->m_data;
173 }
174 
176 {
177  d->m_contextDate = dt;
178 }
179 
181 {
182  d->m_validationEnabled = validate;
183 }
184 
185 void ExtractorPostprocessorPrivate::mergeOrAppend(const QVariant &elem)
186 {
187  const auto it = std::find_if(m_data.begin(), m_data.end(), [elem](const QVariant &other) {
188  return MergeUtil::isSame(elem, other);
189  });
190 
191  if (it == m_data.end()) {
192  m_data.push_back(elem);
193  } else {
194  *it = MergeUtil::merge(*it, elem);
195  }
196 }
197 
198 QVariant ExtractorPostprocessorPrivate::processFlightReservation(FlightReservation res) const
199 {
200  // expand ticketToken for IATA BCBP data
201  const auto bcbp = res.reservedTicket().value<Ticket>().ticketTokenData().toString();
202  if (!bcbp.isEmpty()) {
203  const auto bcbpData = IataBcbpParser::parse(bcbp, m_contextDate);
204  if (bcbpData.size() == 1) {
205  res = JsonLdDocument::apply(bcbpData.at(0), res).value<FlightReservation>();
206  // standardize on the BCBP booking reference, not some secondary one we might have in structured data for example
207  res.setReservationNumber(bcbpData.at(0).value<FlightReservation>().reservationNumber());
208  } else {
209  for (const auto &data : bcbpData) {
210  if (MergeUtil::isSame(res, data)) {
211  res = JsonLdDocument::apply(data, res).value<FlightReservation>();
212  break;
213  }
214  }
215  }
216  }
217 
218  if (res.reservationFor().isValid()) {
219  FlightPostProcessor p;
220  res.setReservationFor(p.processFlight(res.reservationFor().value<Flight>()));
221  }
222  return processReservation(res);
223 }
224 
225 TrainReservation ExtractorPostprocessorPrivate::processTrainReservation(TrainReservation res) const
226 {
227  if (res.reservationFor().isValid()) {
228  res.setReservationFor(processTrainTrip(res.reservationFor().value<TrainTrip>()));
229  }
230  return processReservation(res);
231 }
232 
233 TrainTrip ExtractorPostprocessorPrivate::processTrainTrip(TrainTrip trip) const
234 {
235  trip.setArrivalPlatform(trip.arrivalPlatform().trimmed());
236  trip.setDeparturePlatform(trip.departurePlatform().trimmed());
237  trip.setDepartureStation(processTrainStation(trip.departureStation()));
238  trip.setArrivalStation(processTrainStation(trip.arrivalStation()));
239  trip.setDepartureTime(processTrainTripTime(trip.departureTime(), trip.departureDay(), trip.departureStation()));
240  trip.setArrivalTime(processTrainTripTime(trip.arrivalTime(), trip.departureDay(), trip.arrivalStation()));
241  trip.setTrainNumber(trip.trainNumber().simplified());
242  trip.setTrainName(trip.trainName().simplified());
243  return trip;
244 }
245 
246 static void applyStationData(const KnowledgeDb::TrainStation &record, TrainStation &station)
247 {
248  if (!station.geo().isValid() && record.coordinate.isValid()) {
250  geo.setLatitude(record.coordinate.latitude);
251  geo.setLongitude(record.coordinate.longitude);
252  station.setGeo(geo);
253  }
254  auto addr = station.address();
255  if (addr.addressCountry().isEmpty() && record.country.isValid()) {
256  addr.setAddressCountry(record.country.toString());
257  station.setAddress(addr);
258  }
259 }
260 
261 static void applyStationCountry(const QString &isoCode, TrainStation &station)
262 {
263  auto addr = station.address();
264  if (addr.addressCountry().isEmpty()) {
265  addr.setAddressCountry(isoCode.toUpper());
266  station.setAddress(addr);
267  }
268 }
269 
270 TrainStation ExtractorPostprocessorPrivate::processTrainStation(TrainStation station) const
271 {
272  const auto id = station.identifier();
273  if (id.isEmpty()) { // empty -> null cleanup, to have more compact json-ld output
274  station.setIdentifier(QString());
275  } else if (id.startsWith(QLatin1String("sncf:")) && id.size() == 10) {
277  applyStationData(record, station);
278  applyStationCountry(id.mid(5, 2).toUpper(), station);
279  } else if (id.startsWith(QLatin1String("ibnr:")) && id.size() == 12) {
280  const auto record = KnowledgeDb::stationForIbnr(KnowledgeDb::IBNR{id.mid(5).toUInt()});
281  applyStationData(record, station);
282  const auto country = KnowledgeDb::countryIdForUicCode(QStringView(id).mid(5, 2).toUShort()).toString();
283  applyStationCountry(country, station);
284  } else if (id.startsWith(QLatin1String("uic:")) && id.size() == 11) {
285  const auto record = KnowledgeDb::stationForUic(KnowledgeDb::UICStation{id.mid(4).toUInt()});
286  applyStationData(record, station);
287  const auto country = KnowledgeDb::countryIdForUicCode(QStringView(id).mid(4, 2).toUShort()).toString();
288  applyStationCountry(country, station);
289  } else if (id.startsWith(QLatin1String("ir:")) && id.size() > 4) {
290  const auto record = KnowledgeDb::stationForIndianRailwaysStationCode(id.mid(3));
291  applyStationData(record, station);
292  } else if (id.startsWith(QLatin1String("benerail:")) && id.size() == 14) {
294  applyStationData(record, station);
295  applyStationCountry(id.mid(9, 2).toUpper(), station);
296  } else if (id.startsWith(QLatin1String("vrfi:")) && id.size() >= 7 && id.size() <= 9) {
298  applyStationData(record, station);
299  } else if (id.startsWith(QLatin1String("iata:")) && id.size() == 8) {
300  const auto iataCode = KnowledgeDb::IataCode(QStringView(id).mid(5));
301  const auto record = KnowledgeDb::stationForIataCode(iataCode);
302  applyStationData(record, station);
303  // fall back to the airport with the matching IATA code for the country information
304  // we cannot use the coordinate though, as that points to the actual airport, not the station
305  applyStationCountry(KnowledgeDb::countryForAirport(iataCode).toString(), station);
306  } else if (id.startsWith(QLatin1String("amtrak:")) && id.size() == 10) {
308  applyStationData(record, station);
309  } else if (id.startsWith(QLatin1String("via:")) && id.size() == 8) {
311  applyStationData(record, station);
312  } else if (id.startsWith(QLatin1String("uk:")) && id.size() == 6) {
314  applyStationData(record, station);
315  }
316 
317  return processPlace(station);
318 }
319 
320 QDateTime ExtractorPostprocessorPrivate::processTrainTripTime(QDateTime dt, QDate departureDay, const TrainStation& station) const
321 {
322  if (!dt.isValid()) {
323  return dt;
324  }
325 
326  if (dt.date().year() <= 1970 && departureDay.isValid()) { // we just have the time, but not the day
327  dt.setDate(departureDay);
328  }
329  return processTimeForLocation(dt, station);
330 }
331 
332 BusReservation ExtractorPostprocessorPrivate::processBusReservation(BusReservation res) const
333 {
334  if (res.reservationFor().isValid()) {
335  res.setReservationFor(processBusTrip(res.reservationFor().value<BusTrip>()));
336  }
337  return processReservation(res);
338 }
339 
340 BusTrip ExtractorPostprocessorPrivate::processBusTrip(BusTrip trip) const
341 {
342  trip.setDepartureBusStop(processPlace(trip.departureBusStop()));
343  trip.setArrivalBusStop(processPlace(trip.arrivalBusStop()));
344  trip.setDepartureTime(processTimeForLocation(trip.departureTime(), trip.departureBusStop()));
345  trip.setArrivalTime(processTimeForLocation(trip.arrivalTime(), trip.arrivalBusStop()));
346  trip.setBusNumber(trip.busNumber().simplified());
347  trip.setBusName(trip.busName().simplified());
348  return trip;
349 }
350 
351 LodgingReservation ExtractorPostprocessorPrivate::processLodgingReservation(LodgingReservation res) const
352 {
353  if (res.reservationFor().isValid()) {
354  res.setReservationFor(processPlace(res.reservationFor().value<LodgingBusiness>()));
355  res.setCheckinTime(processTimeForLocation(res.checkinTime(), res.reservationFor().value<LodgingBusiness>()));
356  res.setCheckoutTime(processTimeForLocation(res.checkoutTime(), res.reservationFor().value<LodgingBusiness>()));
357  }
358  return processReservation(res);
359 }
360 
361 TaxiReservation ExtractorPostprocessorPrivate::processTaxiReservation(TaxiReservation res) const
362 {
363  res.setPickupLocation(processPlace(res.pickupLocation()));
364  res.setPickupTime(processTimeForLocation(res.pickupTime(), res.pickupLocation()));
365  return processReservation(res);
366 }
367 
368 RentalCarReservation ExtractorPostprocessorPrivate::processRentalCarReservation(RentalCarReservation res) const
369 {
370  if (res.reservationFor().isValid()) {
371  res.setReservationFor(processRentalCar(res.reservationFor().value<RentalCar>()));
372  }
373  res.setPickupLocation(processPlace(res.pickupLocation()));
374  res.setDropoffLocation(processPlace(res.dropoffLocation()));
375  res.setPickupTime(processTimeForLocation(res.pickupTime(), res.pickupLocation()));
376  res.setDropoffTime(processTimeForLocation(res.dropoffTime(), res.dropoffLocation()));
377  return processReservation(res);
378 }
379 
380 RentalCar ExtractorPostprocessorPrivate::processRentalCar(RentalCar car) const
381 {
382  car.setName(car.name().trimmed());
383  return car;
384 }
385 
386 FoodEstablishmentReservation ExtractorPostprocessorPrivate::processFoodEstablishmentReservation(FoodEstablishmentReservation res) const
387 {
388  if (res.reservationFor().isValid()) {
389  res.setReservationFor(processPlace(res.reservationFor().value<FoodEstablishment>()));
390  res.setStartTime(processTimeForLocation(res.startTime(), res.reservationFor().value<FoodEstablishment>()));
391  res.setEndTime(processTimeForLocation(res.endTime(), res.reservationFor().value<FoodEstablishment>()));
392  }
393  return processReservation(res);
394 }
395 
396 TouristAttractionVisit ExtractorPostprocessorPrivate::processTouristAttractionVisit(TouristAttractionVisit visit) const
397 {
398  visit.setTouristAttraction(processPlace(visit.touristAttraction()));
399  visit.setArrivalTime(processTimeForLocation(visit.arrivalTime(), visit.touristAttraction()));
400  visit.setDepartureTime(processTimeForLocation(visit.departureTime(), visit.touristAttraction()));
401  return visit;
402 }
403 
404 EventReservation ExtractorPostprocessorPrivate::processEventReservation(EventReservation res) const
405 {
406  if (res.reservationFor().isValid()) {
407  res.setReservationFor(processEvent(res.reservationFor().value<Event>()));
408  }
409  return processReservation(res);
410 }
411 
412 KItinerary::Event ExtractorPostprocessorPrivate::processEvent(KItinerary::Event event) const
413 {
414  event.setName(StringUtil::clean(event.name()));
415 
416  // normalize location to be a Place
417  if (JsonLd::isA<PostalAddress>(event.location())) {
418  Place place;
419  place.setAddress(event.location().value<PostalAddress>());
420  event.setLocation(place);
421  }
422 
423  if (JsonLd::isA<Place>(event.location())) {
424  event.setLocation(processPlace(event.location().value<Place>()));
425 
426  // try to obtain timezones if we have a location
427  event.setStartDate(processTimeForLocation(event.startDate(), event.location().value<Place>()));
428  event.setEndDate(processTimeForLocation(event.endDate(), event.location().value<Place>()));
429  event.setDoorTime(processTimeForLocation(event.doorTime(), event.location().value<Place>()));
430  }
431 
432  return event;
433 }
434 
435 Ticket ExtractorPostprocessorPrivate::processTicket(Ticket ticket) const
436 {
437  ticket.setName(StringUtil::clean(ticket.name()));
438  ticket.setTicketNumber(ticket.ticketNumber().simplified());
439  ticket.setUnderName(processPerson(ticket.underName()));
440  return ticket;
441 }
442 
443 ProgramMembership ExtractorPostprocessorPrivate::processProgramMembership(ProgramMembership program) const
444 {
445  program.setProgramName(program.programName().simplified());
446  // avoid emitting spurious empty ProgramMembership objects caused by empty elements in JSON-LD/Microdata input
447  if (program.programName().isEmpty() && !program.programName().isNull()) {
448  program.setProgramName(QString());
449  }
450  program.setMember(processPerson(program.member()));
451  return program;
452 }
453 
454 template <typename T>
455 T ExtractorPostprocessorPrivate::processReservation(T res) const
456 {
457  res.setUnderName(processPerson(res.underName().template value<Person>()));
458  res.setPotentialAction(processActions(res.potentialAction()));
459  res.setReservationNumber(res.reservationNumber().trimmed());
460  res.setProgramMembershipUsed(processProgramMembership(res.programMembershipUsed()));
461 
462  if (JsonLd::isA<Ticket>(res.reservedTicket())) {
463  res.setReservedTicket(processTicket(res.reservedTicket().template value<Ticket>()));
464  }
465  return res;
466 }
467 
468 
469 KItinerary::Person ExtractorPostprocessorPrivate::processPerson(KItinerary::Person person) const
470 {
471  person.setName(person.name().simplified());
472  person.setFamilyName(person.familyName().simplified());
473  person.setGivenName(person.givenName().simplified());
474 
475  // fill name with name parts, if it's empty
476  if ((person.name().isEmpty() || person.name() == person.familyName() || person.name() == person.givenName())
477  && !person.familyName().isEmpty() && !person.givenName().isEmpty())
478  {
479  person.setName(person.givenName() + QLatin1Char(' ') + person.familyName());
480  }
481 
482  // strip prefixes, they break comparisons
483  static const char* const honorificPrefixes[] = { "MR ", "MS ", "MRS " };
484  for (auto prefix : honorificPrefixes) {
485  if (person.name().startsWith(QLatin1String(prefix), Qt::CaseInsensitive)) {
486  person.setName(person.name().mid(strlen(prefix)));
487  break;
488  }
489  }
490 
491  return person;
492 }
493 
494 PostalAddress ExtractorPostprocessorPrivate::processAddress(PostalAddress addr, const QString &phoneNumber, const GeoCoordinates &geo)
495 {
496  // convert to ISO 3166-1 alpha-2 country codes
497  if (addr.addressCountry().size() > 2) {
498  QString alpha2Code;
499 
500  // try ISO 3166-1 alpha-3, we get that e.g. from Flixbus
501  if (addr.addressCountry().size() == 3) {
502  alpha2Code = KCountry::fromAlpha3(addr.addressCountry()).alpha2();
503  }
504  if (alpha2Code.isEmpty()) {
505  alpha2Code = KCountry::fromName(addr.addressCountry()).alpha2();
506  }
507  if (!alpha2Code.isEmpty()) {
508  addr.setAddressCountry(alpha2Code);
509  }
510  }
511 
512  // upper case country codes
513  if (addr.addressCountry().size() == 2) {
514  addr.setAddressCountry(addr.addressCountry().toUpper());
515  }
516 
517  // normalize strings
518  addr.setStreetAddress(addr.streetAddress().simplified());
519  addr.setAddressLocality(addr.addressLocality().simplified());
520  addr.setAddressRegion(addr.addressRegion().simplified());
521 
522 #if HAVE_PHONENUMBER
523  // recover country from phone number, if we have that
524  if (!phoneNumber.isEmpty() && addr.addressCountry().size() != 2) {
525  const auto phoneStr = phoneNumber.toStdString();
526  const auto util = i18n::phonenumbers::PhoneNumberUtil::GetInstance();
527  i18n::phonenumbers::PhoneNumber number;
528  if (util->ParseAndKeepRawInput(phoneStr, "ZZ", &number) == i18n::phonenumbers::PhoneNumberUtil::NO_PARSING_ERROR) {
529  std::string isoCode;
530  util->GetRegionCodeForNumber(number, &isoCode);
531  if (!isoCode.empty()) {
532  addr.setAddressCountry(QString::fromStdString(isoCode));
533  }
534  }
535  }
536 #endif
537 
538  if (geo.isValid() && addr.addressCountry().isEmpty()) {
539  addr.setAddressCountry(KCountry::fromLocation(geo.latitude(), geo.longitude()).alpha2());
540  }
541 
542  AddressParser addrParser;
543  addrParser.setFallbackCountry(KCountry::fromQLocale(QLocale().country()).alpha2());
544  addrParser.parse(addr);
545  addr = addrParser.result();
546  return addr;
547 }
548 
549 QString ExtractorPostprocessorPrivate::processPhoneNumber(const QString &phoneNumber, const PostalAddress &addr)
550 {
551 #if HAVE_PHONENUMBER
552  // or complete the phone number if we know the country
553  if (!phoneNumber.isEmpty() && addr.addressCountry().size() == 2) {
554  auto phoneStr = phoneNumber.toStdString();
555  const auto isoCode = addr.addressCountry().toStdString();
556  const auto util = i18n::phonenumbers::PhoneNumberUtil::GetInstance();
557  i18n::phonenumbers::PhoneNumber number;
558  if (util->ParseAndKeepRawInput(phoneStr, isoCode, &number) == i18n::phonenumbers::PhoneNumberUtil::NO_PARSING_ERROR) {
559  if (number.country_code_source() == i18n::phonenumbers::PhoneNumber_CountryCodeSource_FROM_DEFAULT_COUNTRY) {
560  util->Format(number, i18n::phonenumbers::PhoneNumberUtil::INTERNATIONAL, &phoneStr);
561  return QString::fromStdString(phoneStr);
562  }
563  }
564  }
565 #else
566  Q_UNUSED(addr)
567 #endif
568  return phoneNumber;
569 }
570 
571 QVariantList ExtractorPostprocessorPrivate::processActions(QVariantList actions) const
572 {
573  // remove non-actions and actions with invalid URLs
574  QUrl viewUrl;
575  for (auto it = actions.begin(); it != actions.end();) {
576  if (!JsonLd::canConvert<Action>(*it)) {
577  it = actions.erase(it);
578  continue;
579  }
580 
581  const auto action = JsonLd::convert<Action>(*it);
582  if (!action.target().isValid()) {
583  it = actions.erase(it);
584  continue;
585  }
586 
587  if (JsonLd::isA<ViewAction>(*it)) {
588  viewUrl = action.target();
589  }
590  ++it;
591  }
592 
593  // normalize the order, so JSON comparison still yields correct results
594  std::sort(actions.begin(), actions.end(), [](const QVariant &lhs, const QVariant &rhs) {
595  return strcmp(lhs.typeName(), rhs.typeName()) < 0;
596  });
597 
598  // remove actions that don't actually have their own target, or duplicates
599  QUrl prevUrl;
600  const char* prevType = nullptr;
601  for (auto it = actions.begin(); it != actions.end();) {
602  const auto action = JsonLd::convert<Action>(*it);
603  const auto isDuplicate = action.target() == prevUrl && (prevType ? strcmp(prevType, (*it).typeName()) == 0 : false);
604  if ((JsonLd::isA<ViewAction>(*it) || action.target() != viewUrl) && !isDuplicate) {
605  prevUrl = action.target();
606  prevType = (*it).typeName();
607  ++it;
608  } else {
609  it = actions.erase(it);
610  }
611  }
612 
613  return actions;
614 }
615 
616 template <typename T>
617 QDateTime ExtractorPostprocessorPrivate::processTimeForLocation(QDateTime dt, const T &place) const
618 {
619  if (!dt.isValid() || (dt.timeSpec() == Qt::TimeZone && dt.timeZone() != QTimeZone::utc())) {
620  return dt;
621  }
622 
623  const auto tz = KnowledgeDb::timezoneForLocation(place.geo().latitude(), place.geo().longitude(), place.address().addressCountry(), place.address().addressRegion());
624  if (!tz.isValid()) {
625  return dt;
626  }
627 
628  // prefer our timezone over externally provided UTC offset, if they match
629  if (dt.timeSpec() == Qt::OffsetFromUTC && tz.offsetFromUtc(dt) != dt.offsetFromUtc()) {
630  qCDebug(Log) << "UTC offset clashes with expected timezone!" << dt << dt.offsetFromUtc() << tz.id() << tz.offsetFromUtc(dt);
631  return dt;
632  }
633 
634  if (dt.timeSpec() == Qt::OffsetFromUTC || dt.timeSpec() == Qt::LocalTime) {
636  dt.setTimeZone(tz);
637  } else if (dt.timeSpec() == Qt::UTC || (dt.timeSpec() == Qt::TimeZone && dt.timeZone() == QTimeZone::utc())) {
638  dt = dt.toTimeZone(tz);
639  }
640  return dt;
641 }
bool hasSameDeparture(const QVariant &lhs, const QVariant &rhs)
Checks whether two transport reservation elements refer to the same departure.
Definition: mergeutil.cpp:633
TrainStation stationForVRStationCode(VRStationCode vrStation)
Lookup train station data by VR (Finland) station code.
QTimeZone utc()
TrainStation stationForUic(UICStation uic)
Lookup train station data by UIC station id.
AlphaId< uint16_t, 3 > IataCode
IATA airport code.
Definition: iatacode.h:17
A train reservation.
Definition: reservation.h:99
TrainStation stationForAmtrakStationCode(AmtrakStationCode code)
Lookup train station data by Amtrak station code.
QString toUpper() const const
QTimeZone timeZone() const const
A Taxi reservation.
Definition: reservation.h:148
A booked ticket.
Definition: ticket.h:38
CaseInsensitive
TimeZone
Food-related business (such as a restaurant, or a bakery).
Definition: organization.h:89
bool isSame(const QVariant &lhs, const QVariant &rhs)
Checks if two Reservation or Trip values refer to the same booking element.
Definition: mergeutil.cpp:100
T value() const const
KI18NLOCALEDATA_EXPORT KCountry country(const char *ianaId)
TrainStation stationForBenerailId(BenerailStationId id)
Lookup train station data by Benerail station identifier.
int year() const const
Post-process extracted data to filter out garbage and augment data from other sources.
An event reservation.
Definition: reservation.h:128
QVector< QVariant > parse(const QString &message, const QDateTime &externalIssueDateTime=QDateTime())
Parses the bar coded boarding pass message message into a list of FlightReservation instances.
constexpr bool isValid() const
Returns true if this is a valid identifier.
Definition: alphaid.h:56
static KCountry fromQLocale(QLocale::Country country)
A person.
Definition: person.h:19
static KCountry fromName(QStringView name)
QString alpha2() const
static KCountry fromLocation(float latitude, float longitude)
TrainStation stationForIndianRailwaysStationCode(const QString &code)
Lookup train station data by Indian Railways station code.
TrainStation stationForIbnr(IBNR ibnr)
Lookup train station data by IBNR.
bool isBefore(const QVariant &lhs, const QVariant &rhs)
Sorting function for top-level reservation/visit/event elements.
Definition: sortutil.cpp:144
void setDate(const QDate &date)
QDateTime toTimeZone(const QTimeZone &timeZone) const const
void setValidationEnabled(bool validate)
Enable or disable validation.
QVector< QVariant > result() const
This returns the final result of all previously executed processing steps followed by sorting and fil...
A bus trip.
Definition: bustrip.h:21
A hotel reservation.
Definition: reservation.h:70
Base class for places.
Definition: place.h:68
Train station entry in the station table.
TrainStation stationForIataCode(IataCode iataCode)
Lookup train station data by IATA location code.
QString fromStdString(const std::string &str)
A bus reservation.
Definition: reservation.h:107
char * toString(const T &value)
QString toString() const
Returns a string representation of this identifier.
Definition: alphaid.h:75
bool isEmpty() const const
std::string toStdString() const const
KnowledgeDb::CountryId countryForAirport(IataCode iataCode)
Returns the country the airport with IATA code iataCode is in.
Definition: airportdb.cpp:50
void setTimeSpec(Qt::TimeSpec spec)
TrainStation stationForViaRailStationCode(ViaRailStationCode code)
Lookup train station data by Via Rail station code.
bool isValid() const const
An event.
Definition: event.h:20
static KCountry fromAlpha3(const char *alpha3Code)
AKONADI_CALENDAR_EXPORT KCalendarCore::Event::Ptr event(const Akonadi::Item &item)
GeoCoordinates geo(const QVariant &location)
Returns the geo coordinates of a given location.
static QVariant apply(const QVariant &lhs, const QVariant &rhs)
Apply all properties of rhs on to lhs.
Postal address.
Definition: place.h:45
QVariant merge(const QVariant &lhs, const QVariant &rhs)
Merge the two given objects.
Definition: mergeutil.cpp:536
Geographic coordinates.
Definition: place.h:22
Train station.
Definition: place.h:119
bool hasSameArrival(const QVariant &lhs, const QVariant &rhs)
Checks whether two transport reservation elements refer to the same arrival.
Definition: mergeutil.cpp:647
A frequent traveler, bonus points or discount scheme program membership.
A car rental.
Definition: rentalcar.h:21
CountryId countryIdForUicCode(uint16_t uicCountryCode)
Look up country ISO code from a UIC country code.
Definition: countrydb.cpp:82
A flight.
Definition: flight.h:24
int offsetFromUtc() const const
Qt::TimeSpec timeSpec() const const
QDate date() const const
bool isValid() const const
A Rental Car reservation.
Definition: reservation.h:136
A train trip.
Definition: traintrip.h:23
KIOCORE_EXPORT QString number(KIO::filesize_t size)
bool isEmpty
The country this address is in, as ISO 3166-1 alpha 2 code.
Definition: place.h:55
void setContextDate(const QDateTime &dt)
The date the reservation(s) processed here have been made, if known.
TrainStation stationForSncfStationId(SncfStationId sncfId)
Lookup train station data by SNCF station id.
void setTimeZone(const QTimeZone &toZone)
A flight reservation.
Definition: reservation.h:81
QString clean(const QString &s)
Cleans up extra white spaces and XML entities from s.
Definition: stringutil.cpp:117
TrainStation stationForUkRailwayStationCode(UKRailwayStationCode code)
Lookup train station data by UK railway station code.
This file is part of the KDE documentation.
Documentation copyright © 1996-2022 The KDE developers.
Generated on Mon Jun 27 2022 04:16:14 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.