7#include "datatypes/place.h"
8#include "jsonldimportfilter.h"
9#include "locationutil.h"
10#include "json/jsonld.h"
11#include "json/jsonldfilterengine.h"
17#include <QJsonDocument>
30 {
"AutoDealer",
"LocalBusiness" },
31 {
"AutoRepair",
"LocalBusiness" },
32 {
"AutomotiveBusiness",
"LocalBusiness" },
33 {
"Bakery",
"FoodEstablishment" },
34 {
"BarOrPub",
"FoodEstablishment" },
35 {
"BedAndBreakfast",
"LodgingBusiness" },
36 {
"Brewery",
"FoodEstablishment" },
37 {
"BusStop",
"BusStation" },
38 {
"BusinessEvent",
"Event" },
39 {
"CafeOrCoffeeShop",
"FoodEstablishment" },
40 {
"Campground",
"LodgingBusiness" },
41 {
"ChildrensEvent",
"Event" },
42 {
"ComedyEvent",
"Event" },
43 {
"ComputerStore",
"LocalBusiness" },
44 {
"DanceEvent",
"Event" },
45 {
"Distillery",
"FoodEstablishment" },
46 {
"EditAction",
"UpdateAction" },
47 {
"EducationEvent",
"Event" },
48 {
"ElectronicsStore",
"LocalBusiness" },
49 {
"EntertainmentBusiness",
"LocalBusiness" },
50 {
"ExhibitionEvent",
"Event" },
51 {
"FastFoodRestaurant",
"FoodEstablishment" },
52 {
"Festival",
"Event" },
53 {
"HobbyShop",
"LocalBusiness" },
54 {
"HomeAndConstructionBusiness",
"LocalBusiness" },
55 {
"Hostel",
"LodgingBusiness" },
56 {
"Hotel",
"LodgingBusiness" },
57 {
"IceCreamShop",
"FoodEstablishment" },
58 {
"LiteraryEvent",
"Event" },
59 {
"Motel",
"LodgingBusiness" },
60 {
"MovieTheater",
"LocalBusiness" },
61 {
"MusicEvent",
"Event" },
62 {
"Resort",
"LodgingBusiness" },
63 {
"Restaurant",
"FoodEstablishment" },
64 {
"SaleEvent",
"Event" },
65 {
"ScreeningEvent",
"Event" },
66 {
"SocialEvent",
"Event" },
67 {
"SportsEvent",
"Event" },
68 {
"Store",
"LocalBusiness" },
69 {
"TheaterEvent",
"Event" },
70 {
"VisualArtsEvent",
"Event" },
71 {
"Winery",
"FoodEstablishment" },
75 const auto val = obj.
value(key);
83 obj.
insert(key, arr.at(0));
86static void migrateToAction(
QJsonObject &obj,
const char *propName,
const char *typeName,
bool remove)
89 if (value.isNull() || value.isUndefined()) {
93 const auto actionsVal = obj.
value(
"potentialAction"_L1);
95 if (actionsVal.isArray()) {
96 actions = actionsVal.toArray();
97 }
else if (actionsVal.isObject()) {
98 actions = { actionsVal };
101 for (
const auto &act : actions) {
109 action.
insert(QStringLiteral(
"target"), value);
110 actions.push_back(action);
111 obj.
insert(QStringLiteral(
"potentialAction"), actions);
121 if (
const auto addr = obj.
value(
"address"_L1); addr.
isString()) {
123 {
"@type"_L1,
"PostalAddress"_L1},
124 {
"streetAddress"_L1, addr.toString()},
128 std::optional<double> lat;
129 std::optional<double> lon;
131 const auto latValue = obj.
value(
"latitude"_L1);
132 const auto lonValue = obj.
value(
"longitude"_L1);
133 if (latValue.isDouble() && lonValue.isDouble()) {
134 lat = latValue.toDouble();
135 lon = lonValue.toDouble();
138 for (
const auto &key : {
"hasMap"_L1,
"maps"_L1,
"map"_L1}) {
145 if (!
geo.isValid()) {
149 lat =
geo.latitude();
150 lon =
geo.longitude();
155 if (lat.has_value() && lon.has_value()) {
157 if (!
geo.contains(
"@type"_L1)) {
158 geo.insert(
"@type"_L1,
"GeoCoordinates"_L1);
160 if (!
geo.contains(
"latitude"_L1)) {
161 geo.insert(
"latitude"_L1, *lat);
163 if (!
geo.contains(
"longitude"_L1)) {
164 geo.insert(
"longitude"_L1, *lon);
166 obj.
insert(
"geo"_L1, geo);
181 for (
const auto key : {
"ticketToken"_L1,
"ticketNumber"_L1}) {
185 if (ticket.isEmpty()) {
186 ticket.insert(
"@type"_L1, u
"Ticket"_s);
188 if (!ticket.contains(key)) {
189 ticket.insert(key, v);
190 res.
insert(
"reservedTicket"_L1, ticket);
198 if (!resStat.isEmpty()) {
199 if (resStat.startsWith(
"https:"_L1)) {
200 resStat.remove(4, 1);
202 if (!resStat.contains(
"/Reservation"_L1)) {
203 res.
insert(
"reservationStatus"_L1, resStat.replace(
"http://schema.org/"_L1,
"http://schema.org/Reservation"_L1));
215 migrateToAction(res,
"cancelReservationUrl",
"CancelAction",
true);
216 migrateToAction(res,
"checkinUrl",
"CheckInAction",
true);
217 migrateToAction(res,
"modifyReservationUrl",
"UpdateAction",
true);
218 migrateToAction(res,
"ticketDownloadUrl",
"DownloadAction",
true);
219 migrateToAction(res,
"url",
"ViewAction",
false);
228static void filterFoodEstablishment(
QJsonObject &restaurant)
231 auto reservationsValue =
233 if (reservationsValue.isString()) {
234 const QString reservations = reservationsValue.toString();
240 migrateToAction(restaurant,
"acceptsReservations",
"ReserveAction",
245 filterPlace(restaurant);
251 QString filteredTargetUrlString;
260 for (
auto it = targets.
begin(); it != targets.
end(); ++it) {
261 auto target = (*it).toObject();
267 if (actionPlatform.
isArray()) {
268 platforms = actionPlatform.
toArray();
274 if (!filteredTargetUrlString.
isEmpty()) {
275 const bool hasPreferredPlatform = std::any_of(platforms.
begin(), platforms.
end(), [](
const QJsonValue &platformValue) {
276 const QString platform = platformValue.toString();
278 return platform == QLatin1StringView(
279 "http://schema.org/DesktopWebPlatform");
282 if (!hasPreferredPlatform) {
294 filteredTargetUrlString = url.
toString();
297 if (filteredTargetUrlString.
isEmpty()) {
300 action.
insert(QStringLiteral(
"target"), filteredTargetUrlString);
313 for (
auto it = actions.
begin(); it != actions.
end(); ++it) {
314 auto action = (*it).toObject();
315 filterActionTarget(action);
324 unpackArray(obj,
"location"_L1);
327 if (
const auto endDate = obj.
value(
"endDate"_L1).
toString(); endDate.
size() == 10) {
329 if (date.isValid()) {
348 {
"Event", filterEvent },
349 {
"Flight", filterFlight },
350 {
"FoodEstablishment", filterFoodEstablishment },
351 {
"LocalBusiness", filterPlace },
352 {
"LodgingBusiness", filterPlace },
353 {
"Organization", filterPlace },
354 {
"Place", filterPlace },
355 {
"PostalAddress", filterPostalAddress },
361 {
"BusTrip",
"arrivalStation",
"arrivalBusStop" },
362 {
"BusTrip",
"busCompany",
"provider" },
363 {
"BusTrip",
"departureStation",
"departureBusStop" },
366 {
"Flight",
"provider",
"airline" },
369 {
"LodgingReservation",
"checkinDate",
"checkinTime" },
370 {
"LodgingReservation",
"checkoutDate",
"checkoutTime" },
372 {
"ProgramMembership",
"program",
"programName" },
373 {
"ProgramMembership",
"memberNumber",
"membershipNumber" },
375 {
"Reservation",
"price",
"totalPrice" },
376 {
"Ticket",
"price",
"totalPrice" },
379 {
"TrainTrip",
"trainCompany",
"provider" },
387 for (
const auto &o : graph) {
389 std::copy(a.begin(), a.end(), std::back_inserter(result));
399 if (typeVal.isString()) {
401 }
else if (typeVal.isArray()) {
402 const auto typeNames = typeVal.toArray();
403 for (
const auto &t : typeNames) {
412 return graphExpand(obj);
418 filterEngine.setTypeMappings(type_mapping);
419 filterEngine.setTypeFilters(type_filters);
420 filterEngine.setPropertyMappings(property_mappings);
421 for (
const auto &type : types) {
423 res.
insert(QStringLiteral(
"@type"), type);
427 if (
const auto mainEntityOfPage =
429 !mainEntityOfPage.isEmpty()) {
431 for (
auto it = mainEntityOfPage.begin(); it != mainEntityOfPage.end();
437 res.
insert(it.key(), it.value());
442 filterReservation(res);
446 if (!actions.isUndefined()) {
447 res.
insert(QStringLiteral(
"potentialAction"), filterActions(actions));
452 if (image.isObject()) {
453 const auto imageObject = image.toObject();
456 res.
insert(QStringLiteral(
"image"),
463 if (
const auto a = resFor.toArray(); !a.isEmpty()) {
464 for (
const auto &entry : a) {
JSON-LD filtering for input normalization or type transforms.
void filterRecursive(QJsonObject &obj)
Recursively apply filtering rules to obj.
char * toString(const EngineQuery &query)
QJsonArray filterObject(const QJsonObject &obj)
Filter the top-level object obj for loading with JsonLdDocument.
QString typeName(const QJsonObject &obj)
Normalized type name from object.
void renameProperty(QJsonObject &obj, const char *oldName, const char *newName)
Rename a property, if present and the new name isn't in use already.
GeoCoordinates geo(const QVariant &location)
Returns the geo coordinates of a given location.
GeoCoordinates geoFromUrl(const QUrl &url)
Parses geo coordinates from a given mapping service URLs, such as Google Maps links.
Classes for reservation/travel data models, data extraction and data augmentation.
KI18NLOCALEDATA_EXPORT KCountry country(const char *ianaId)
QDate fromString(QStringView string, QStringView format, QCalendar cal)
void push_back(const QJsonValue &value)
bool contains(QLatin1StringView key) const const
iterator insert(QLatin1StringView key, const QJsonValue &value)
void remove(QLatin1StringView key)
QJsonValue value(QLatin1StringView key) const const
bool isArray() const const
bool isObject() const const
bool isString() const const
QJsonArray toArray() const const
QJsonObject toObject() const const
QString toString() const const
bool isEmpty() const const
void push_back(parameter_type value)
bool isEmpty() const const
qsizetype size() const const
bool isValid() const const
QString toString(FormattingOptions options) const const