KItinerary

jsapi/jsonld.cpp
1/*
2 SPDX-FileCopyrightText: 2018 Volker Krause <vkrause@kde.org>
3
4 SPDX-License-Identifier: LGPL-2.0-or-later
5*/
6
7#include "jsonld.h"
8
9#include "json/jsonldfilterengine.h"
10
11#include <KItinerary/JsonLdDocument>
12
13#include <QDebug>
14#include <QJSEngine>
15#include <QJsonArray>
16#include <QJsonObject>
17#include <QLocale>
18#include <QMetaProperty>
19#include <QRegularExpression>
20#include <QUrl>
21
22using namespace KItinerary;
23
24JsApi::JsonLd::JsonLd(QJSEngine* engine)
25 : QObject(engine)
26 , m_engine(engine)
27{
28}
29
30JsApi::JsonLd::~JsonLd() = default;
31
33{
34 auto v = m_engine->newObject();
35 v.setProperty(QStringLiteral("@type"), typeName);
36 return v;
37}
38
39QJSValue JsApi::JsonLd::newPlace(const QString &type) const
40{
41 const auto addr = newObject(QStringLiteral("PostalAddress"));
42 const auto geo = newObject(QStringLiteral("GeoCoordinates"));
43
44 auto p = newObject(type);
45 p.setProperty(QStringLiteral("address"), addr);
46 p.setProperty(QStringLiteral("geo"), geo);
47
48 return p;
49}
50
52{
53 const auto dep = newObject(QStringLiteral("Airport"));
54 const auto arr = newObject(QStringLiteral("Airport"));
55 const auto airline = newObject(QStringLiteral("Airline"));
56 const auto person = newObject(QStringLiteral("Person"));
57
58 auto resFor = newObject(QStringLiteral("Flight"));
59 resFor.setProperty(QStringLiteral("departureAirport"), dep);
60 resFor.setProperty(QStringLiteral("arrivalAirport"), arr);
61 resFor.setProperty(QStringLiteral("airline"), airline);
62
63 const auto ticket = newObject(QStringLiteral("Ticket"));
64
65 auto res = newObject(QStringLiteral("FlightReservation"));
66 res.setProperty(QStringLiteral("reservationFor"), resFor);
67 res.setProperty(QStringLiteral("underName"), person);
68 res.setProperty(QStringLiteral("reservedTicket"), ticket);
69
70 return res;
71}
72
74{
75 const auto dep = newPlace(QStringLiteral("TrainStation"));
76 const auto arr = newPlace(QStringLiteral("TrainStation"));
77 const auto person = newObject(QStringLiteral("Person"));
78 const auto seat = newObject(QStringLiteral("Seat"));
79
80 auto ticket = newObject(QStringLiteral("Ticket"));
81 ticket.setProperty(QStringLiteral("ticketedSeat"), seat);
82
83 auto resFor = newObject(QStringLiteral("TrainTrip"));
84 resFor.setProperty(QStringLiteral("departureStation"), dep);
85 resFor.setProperty(QStringLiteral("arrivalStation"), arr);
86 resFor.setProperty(QStringLiteral("provider"), newObject(QStringLiteral("Organization")));
87
88 auto res = newObject(QStringLiteral("TrainReservation"));
89 res.setProperty(QStringLiteral("reservationFor"), resFor);
90 res.setProperty(QStringLiteral("underName"), person);
91 res.setProperty(QStringLiteral("reservedTicket"), ticket);
92 res.setProperty(QStringLiteral("programMembershipUsed"), newObject(QStringLiteral("ProgramMembership")));
93
94 return res;
95}
96
98{
99 const auto dep = newPlace(QStringLiteral("BusStation"));
100 const auto arr = newPlace(QStringLiteral("BusStation"));
101 const auto person = newObject(QStringLiteral("Person"));
102 const auto seat = newObject(QStringLiteral("Seat"));
103
104 auto ticket = newObject(QStringLiteral("Ticket"));
105 ticket.setProperty(QStringLiteral("ticketedSeat"), seat);
106
107 auto resFor = newObject(QStringLiteral("BusTrip"));
108 resFor.setProperty(QStringLiteral("departureBusStop"), dep);
109 resFor.setProperty(QStringLiteral("arrivalBusStop"), arr);
110
111 auto res = newObject(QStringLiteral("BusReservation"));
112 res.setProperty(QStringLiteral("reservationFor"), resFor);
113 res.setProperty(QStringLiteral("underName"), person);
114 res.setProperty(QStringLiteral("reservedTicket"), ticket);
115
116 return res;
117}
118
120{
121 const auto person = newObject(QStringLiteral("Person"));
122 const auto resFor = newPlace(QStringLiteral("LodgingBusiness"));
123
124 auto res = newObject(QStringLiteral("LodgingReservation"));
125 res.setProperty(QStringLiteral("reservationFor"), resFor);
126 res.setProperty(QStringLiteral("underName"), person);
127
128 return res;
129}
130
132{
133 auto resFor = newObject(QStringLiteral("Event"));
134 resFor.setProperty(QStringLiteral("location"), newPlace(QStringLiteral("Place")));
135
136 auto res = newObject(QStringLiteral("EventReservation"));
137 res.setProperty(QStringLiteral("reservationFor"), resFor);
138
139 auto ticket = newObject(QStringLiteral("Ticket"));
140 ticket.setProperty(QStringLiteral("ticketedSeat"), newObject(QStringLiteral("Seat")));
141 res.setProperty(QStringLiteral("reservedTicket"), ticket);
142
143 const auto person = newObject(QStringLiteral("Person"));
144 res.setProperty(QStringLiteral("underName"), person);
145
146 return res;
147}
148
150{
151 const auto person = newObject(QStringLiteral("Person"));
152 const auto resFor = newPlace(QStringLiteral("FoodEstablishment"));
153
154 auto res = newObject(QStringLiteral("FoodEstablishmentReservation"));
155 res.setProperty(QStringLiteral("reservationFor"), resFor);
156 res.setProperty(QStringLiteral("underName"), person);
157
158 return res;
159}
160
162{
163 const auto pickup = newPlace(QStringLiteral("Place"));
164 const auto dropoff = newPlace(QStringLiteral("Place"));
165 const auto person = newObject(QStringLiteral("Person"));
166 const auto org = newObject(QStringLiteral("Organization"));
167
168 auto resFor = newObject(QStringLiteral("RentalCar"));
169 resFor.setProperty(QStringLiteral("rentalCompany"), org);
170
171 auto res = newObject(QStringLiteral("RentalCarReservation"));
172 res.setProperty(QStringLiteral("reservationFor"), resFor);
173 res.setProperty(QStringLiteral("pickupLocation"), pickup);
174 res.setProperty(QStringLiteral("dropoffLocation"), dropoff);
175 res.setProperty(QStringLiteral("underName"), person);
176
177 return res;
178}
179
181{
182 const auto dep = newPlace(QStringLiteral("BoatTerminal"));
183 const auto arr = newPlace(QStringLiteral("BoatTerminal"));
184 const auto person = newObject(QStringLiteral("Person"));
185 const auto ticket = newObject(QStringLiteral("Ticket"));
186
187 auto resFor = newObject(QStringLiteral("BoatTrip"));
188 resFor.setProperty(QStringLiteral("departureBoatTerminal"), dep);
189 resFor.setProperty(QStringLiteral("arrivalBoatTerminal"), arr);
190
191 auto res = newObject(QStringLiteral("BoatReservation"));
192 res.setProperty(QStringLiteral("reservationFor"), resFor);
193 res.setProperty(QStringLiteral("underName"), person);
194 res.setProperty(QStringLiteral("reservedTicket"), ticket);
195
196 return res;
197}
198
199static constexpr const JsonLdFilterEngine::TypeMapping train_to_bus_type_map[] = {
200 { "TrainReservation", "BusReservation" },
201 { "TrainStation", "BusStation" },
202 { "TrainTrip", "BusTrip" },
203};
204
205static constexpr const JsonLdFilterEngine::PropertyMapping train_to_bus_property_map[] = {
206 { "BusTrip", "arrivalStation", "arrivalBusStop" },
207 { "BusTrip", "departureStation", "departureBusStop" },
208 { "BusTrip", "trainName", "busName" },
209 { "BusTrip", "trainNumber", "busNumber" },
210};
211
213{
215 JsonLdFilterEngine filterEngine;
216 filterEngine.setTypeMappings(train_to_bus_type_map);
217 filterEngine.setPropertyMappings(train_to_bus_property_map);
218 filterEngine.filterRecursive(obj);
219 return m_engine->toScriptValue(obj);
220}
221
222static constexpr const JsonLdFilterEngine::TypeMapping bus_to_train_type_map[] = {
223 { "BusReservation", "TrainReservation" },
224 { "BusStation", "TrainStation" },
225 { "BusStop", "TrainStation" },
226 { "BusTrip", "TrainTrip" },
227};
228
229static constexpr const JsonLdFilterEngine::PropertyMapping bus_to_train_property_map[] = {
230 { "TrainTrip", "arrivalBusStop", "arrivalStation" },
231 { "TrainTrip", "departureBusStop", "departureStation" },
232 { "TrainTrip", "busCompany", "provider" },
233 { "TrainTrip", "busName", "trainName" },
234 { "TrainTrip", "busNumber", "trainNumber" },
235};
236
238{
240 JsonLdFilterEngine filterEngine;
241 filterEngine.setTypeMappings(bus_to_train_type_map);
242 filterEngine.setPropertyMappings(bus_to_train_property_map);
243 filterEngine.filterRecursive(obj);
244 return m_engine->toScriptValue(obj);
245}
246
247QDateTime JsApi::JsonLd::toDateTime(const QString &dtStr, const QString &format, const QString &localeName) const
248{
249 QLocale locale(localeName);
250 auto dt = locale.toDateTime(dtStr, format);
251
252 // try harder for the "MMM" month format
253 // QLocale expects the exact string in QLocale::shortMonthName(), while we often encounter a three
254 // letter month identifier. For en_US that's the same, for Swedish it isn't though for example. So
255 // let's try to fix up the month identifiers to the full short name.
256 if (!dt.isValid() && format.contains(QLatin1StringView("MMM"))) {
257 auto dtStrFixed = dtStr;
258 for (int i = 1; i <= 12; ++i) {
259 const auto monthName = locale.monthName(i, QLocale::ShortFormat);
260 dtStrFixed.replace(monthName.left(3), monthName, Qt::CaseInsensitive);
261 }
262 dt = locale.toDateTime(dtStrFixed, format);
263 }
264
265 // try even harder for "MMM" month format
266 // in the de_DE locale we encounter sometimes almost arbitrary abbreviations for month
267 // names (eg. Mrz, Mär for März). So try to identify those and replace them with what QLocale
268 // expects
269 if (!dt.isValid() && format.contains(QLatin1StringView("MMM"))) {
270 auto dtStrFixed = dtStr;
271 for (int i = 1; i <= 12; ++i) {
272 const auto monthName = locale.monthName(i, QLocale::LongFormat);
273 const auto beginIdx = dtStr.indexOf(monthName.at(0));
274 if (beginIdx < 0) {
275 continue;
276 }
277 auto endIdx = beginIdx;
278 for (auto nameIdx = 0;
279 endIdx < dtStr.size() && nameIdx < monthName.size(); ++nameIdx) {
280 if (dtStr.at(endIdx).toCaseFolded() ==
281 monthName.at(nameIdx).toCaseFolded()) {
282 ++endIdx;
283 }
284 }
285 if (endIdx - beginIdx >= 3) {
286 dtStrFixed.replace(beginIdx, endIdx - beginIdx,
287 locale.monthName(i, QLocale::ShortFormat));
288 break;
289 }
290 }
291 dt = locale.toDateTime(dtStrFixed, format);
292 }
293
294 if (!dt.isValid()) {
295 return dt;
296 }
297
298 const bool hasFullYear = format.contains(QLatin1StringView("yyyy"));
299 const bool hasYear =
300 hasFullYear || format.contains(QLatin1StringView("yy"));
301 const bool hasMonth = format.contains(QLatin1Char('M'));
302 const bool hasDay = format.contains(QLatin1Char('d'));
303
304 // time only, set a default date
305 if (!hasDay && !hasMonth && !hasYear) {
306 dt.setDate({1970, 1, 1});
307 }
308
309 // if the date does not contain a year number, determine that based on the context date, if set
310 else if (!hasYear && m_contextDate.isValid()) {
311 dt.setDate({m_contextDate.date().year(), dt.date().month(), dt.date().day()});
312 // go one day back to leave a bit of room for documents produced very close to
313 // or even during the trip
314 if (dt < m_contextDate.addDays(-1)) {
315 dt = dt.addYears(1);
316 }
317 }
318
319 // fix two-digit years ending up in the wrong century
320 else if (!hasFullYear && dt.date().year() / 100 == 19) {
321 // be careful to only change the date, QDateTime::addYears can change
322 // the time as well if e.g. DST has been changed in the corresponding timezone
323 dt.setDate(dt.date().addYears(100));
324 }
325
326 return dt;
327}
328
329QDateTime JsApi::JsonLd::toDateTime(const QString &dtStr, const QJSValue &format, const QJSValue &localeName) const
330{
331 if (localeName.isString() && format.isString()) {
332 return toDateTime(dtStr, format.toString(), localeName.toString());
333 }
334 if (format.isArray()) {
335 const auto count = format.property(QLatin1StringView("length")).toInt();
336 for (auto i = 0; i < count; ++i) {
337 const auto dt = toDateTime(dtStr, format.property(i), localeName);
338 if (dt.isValid()) {
339 return dt;
340 }
341 }
342 }
343 if (format.isString() && localeName.isArray()) {
344 const auto count =
345 localeName.property(QLatin1StringView("length")).toInt();
346 for (auto i = 0; i < count; ++i) {
347 const auto dt = toDateTime(dtStr, format.toString(),
348 localeName.property(i).toString());
349 if (dt.isValid()) {
350 return dt;
351 }
352 }
353 }
354 return {};
355}
356
358{
359 if (v.canConvert<QList<QVariant>>()) {
360 return m_engine->toScriptValue(
362 }
363
364 const auto json = JsonLdDocument::toJsonValue(v);
365 return m_engine->toScriptValue(json);
366}
367
369{
370 return m_engine->toScriptValue(v.toVariant());
371}
372
374{
375 QUrl url(mapUrl);
376 if (url.host().contains(QLatin1StringView("google"))) {
377 QRegularExpression regExp(
378 QStringLiteral("[/=](-?\\d+\\.\\d+),(-?\\d+\\.\\d+)"));
379 auto match = regExp.match(url.path());
380 if (!match.hasMatch()) {
381 match = regExp.match(url.query());
382 }
383
384 if (match.hasMatch()) {
385 auto geo = m_engine->newObject();
386 geo.setProperty(QStringLiteral("@type"),
387 QStringLiteral("GeoCoordinates"));
388 geo.setProperty(QStringLiteral("latitude"),
389 match.capturedView(1).toDouble());
390 geo.setProperty(QStringLiteral("longitude"),
391 match.capturedView(2).toDouble());
392 return geo;
393 }
394 }
395
396 return {};
397}
398
399QJSValue JsApi::JsonLd::readQDateTime(const QVariant &obj, const QString &propName) const
400{
401 const auto mo = QMetaType(obj.userType()).metaObject();
402 if (!mo) {
403 return {};
404 }
405 const auto propIdx = mo->indexOfProperty(propName.toUtf8().constData());
406 if (propIdx < 0) {
407 qWarning() << "Unknown property name:" << mo->className() << propName;
408 return {};
409 }
410 const auto prop = mo->property(propIdx);
411 const auto dt = prop.readOnGadget(obj.constData());
412 return toJson(dt);
413}
414
415QJSValue JsApi::JsonLd::apply(const QJSValue &lhs, const QJSValue &rhs) const
416{
419 const auto v = JsonLdDocument::apply(lhsVar, rhsVar);
420 return toJson(v);
421}
422
423void JsApi::JsonLd::setContextDate(const QDateTime& dt)
424{
425 m_contextDate = dt;
426}
427
428#include "moc_jsonld.cpp"
Q_INVOKABLE QJSValue newEventReservation() const
Convenience method that generates a full EventReservation JS object.
Q_INVOKABLE QJSValue toGeoCoordinates(const QString &mapUrl)
Parses geo coordinates from a given mapping service URLs.
Q_INVOKABLE QJSValue newLodgingReservation() const
Convenience method that generates a full LodgingReservation JS object.
Q_INVOKABLE QJSValue newBusReservation() const
Convenience method that generates a full BusReservation JS object.
Q_INVOKABLE QJSValue busToTrainReservation(const QJSValue &busRes) const
Convert a bus reservation to a train reservation.
Q_INVOKABLE QJSValue newObject(const QString &typeName) const
Create a new JSON-LD object of type typeName.
Q_INVOKABLE QJSValue apply(const QJSValue &lhs, const QJSValue &rhs) const
Q_INVOKABLE QJSValue clone(const QJSValue &v) const
Clones the given JS object.
Q_INVOKABLE QJSValue toJson(const QVariant &v) const
Convert object v to a JSON-LD object.
Q_INVOKABLE QJSValue newRentalCarReservation() const
Convenience method that generates a full RentalCarReservation JS object.
Q_INVOKABLE QDateTime toDateTime(const QString &dtStr, const QJSValue &format, const QJSValue &localeName) const
Convert a date/time string to a date/time value.
Q_INVOKABLE QJSValue readQDateTime(const QVariant &obj, const QString &propName) const
Read a QDateTime property and return a JSON-LD serialization of it.
Q_INVOKABLE QJSValue newFoodEstablishmentReservation() const
Convenience method that generates a full FoodEstablishmentReservation JS object.
Q_INVOKABLE QJSValue newTrainReservation() const
Convenience method that generates a full TrainReservation JS object.
Q_INVOKABLE QJSValue newFlightReservation() const
Convenience method that generates a full FlightReservation JS object.
Q_INVOKABLE QJSValue newBoatReservation() const
Convenience method that generates a full BoatReservation JS object.
Q_INVOKABLE QJSValue trainToBusReservation(const QJSValue &trainRes) const
Convert a train reservation to a bus reservation.
static QJsonValue toJsonValue(const QVariant &data)
JSON-LD serrialization of an invidividual data value.
static QVariant apply(const QVariant &lhs, const QVariant &rhs)
Apply all properties of rhs on to lhs.
static QJsonArray toJson(const QList< QVariant > &data)
Serialize instantiated data types to JSON-LD.
static QVariant fromJsonSingular(const QJsonObject &obj)
Convert a single JSON-LD object into an instantiated data type.
JSON-LD filtering for input normalization or type transforms.
void filterRecursive(QJsonObject &obj)
Recursively apply filtering rules to obj.
Classes for reservation/travel data models, data extraction and data augmentation.
Definition berelement.h:17
const char * constData() const const
char32_t toCaseFolded(char32_t ucs4)
QJSValue newObject()
QJsonValue fromVariant(const QVariant &variant)
QJsonObject toObject() const const
bool isArray() const const
bool isString() const const
QJSValue property(const QString &name) const const
void setProperty(const QString &name, const QJSValue &value)
qint32 toInt() const const
QString toString() const const
QVariant toVariant() const const
int indexOfProperty(const char *name) const const
const QMetaObject * metaObject() const const
QRegularExpressionMatch match(QStringView subjectView, qsizetype offset, MatchType matchType, MatchOptions matchOptions) const const
const QChar at(qsizetype position) const const
bool contains(QChar ch, Qt::CaseSensitivity cs) const const
qsizetype indexOf(QChar ch, qsizetype from, Qt::CaseSensitivity cs) const const
QString & replace(QChar before, QChar after, Qt::CaseSensitivity cs)
qsizetype size() const const
QByteArray toUtf8() const const
CaseInsensitive
QString host(ComponentFormattingOptions options) const const
QString path(ComponentFormattingOptions options) const const
QString query(ComponentFormattingOptions options) const const
bool canConvert() const const
const void * constData() const const
int userType() const const
T value() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:14:49 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.