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#include "reservationconverter.h"
9
10#include <KItinerary/JsonLdDocument>
11
12#include <QDebug>
13#include <QJSEngine>
14#include <QJsonArray>
15#include <QJsonObject>
16#include <QLocale>
17#include <QMetaProperty>
18#include <QRegularExpression>
19#include <QUrl>
20
21using namespace KItinerary;
22
23JsApi::JsonLd::JsonLd(QJSEngine* engine)
24 : QObject(engine)
25 , m_engine(engine)
26{
27}
28
29JsApi::JsonLd::~JsonLd() = default;
30
32{
33 auto v = m_engine->newObject();
34 v.setProperty(QStringLiteral("@type"), typeName);
35 return v;
36}
37
38QJSValue JsApi::JsonLd::newPlace(const QString &type) const
39{
40 const auto addr = newObject(QStringLiteral("PostalAddress"));
41 const auto geo = newObject(QStringLiteral("GeoCoordinates"));
42
43 auto p = newObject(type);
44 p.setProperty(QStringLiteral("address"), addr);
45 p.setProperty(QStringLiteral("geo"), geo);
46
47 return p;
48}
49
51{
52 const auto dep = newPlace(QStringLiteral("Airport"));
53 const auto arr = newPlace(QStringLiteral("Airport"));
54 const auto airline = newObject(QStringLiteral("Airline"));
55 const auto person = newObject(QStringLiteral("Person"));
56
57 auto resFor = newObject(QStringLiteral("Flight"));
58 resFor.setProperty(QStringLiteral("departureAirport"), dep);
59 resFor.setProperty(QStringLiteral("arrivalAirport"), arr);
60 resFor.setProperty(QStringLiteral("airline"), airline);
61
62 const auto ticket = newObject(QStringLiteral("Ticket"));
63
64 auto res = newObject(QStringLiteral("FlightReservation"));
65 res.setProperty(QStringLiteral("reservationFor"), resFor);
66 res.setProperty(QStringLiteral("underName"), person);
67 res.setProperty(QStringLiteral("reservedTicket"), ticket);
68
69 return res;
70}
71
73{
74 const auto dep = newPlace(QStringLiteral("TrainStation"));
75 const auto arr = newPlace(QStringLiteral("TrainStation"));
76 const auto person = newObject(QStringLiteral("Person"));
77 const auto seat = newObject(QStringLiteral("Seat"));
78
79 auto ticket = newObject(QStringLiteral("Ticket"));
80 ticket.setProperty(QStringLiteral("ticketedSeat"), seat);
81
82 auto resFor = newObject(QStringLiteral("TrainTrip"));
83 resFor.setProperty(QStringLiteral("departureStation"), dep);
84 resFor.setProperty(QStringLiteral("arrivalStation"), arr);
85 resFor.setProperty(QStringLiteral("provider"), newObject(QStringLiteral("Organization")));
86
87 auto res = newObject(QStringLiteral("TrainReservation"));
88 res.setProperty(QStringLiteral("reservationFor"), resFor);
89 res.setProperty(QStringLiteral("underName"), person);
90 res.setProperty(QStringLiteral("reservedTicket"), ticket);
91 res.setProperty(QStringLiteral("programMembershipUsed"), newObject(QStringLiteral("ProgramMembership")));
92
93 return res;
94}
95
97{
98 const auto dep = newPlace(QStringLiteral("BusStation"));
99 const auto arr = newPlace(QStringLiteral("BusStation"));
100 const auto person = newObject(QStringLiteral("Person"));
101 const auto seat = newObject(QStringLiteral("Seat"));
102
103 auto ticket = newObject(QStringLiteral("Ticket"));
104 ticket.setProperty(QStringLiteral("ticketedSeat"), seat);
105
106 auto resFor = newObject(QStringLiteral("BusTrip"));
107 resFor.setProperty(QStringLiteral("departureBusStop"), dep);
108 resFor.setProperty(QStringLiteral("arrivalBusStop"), arr);
109 resFor.setProperty(QStringLiteral("provider"), newObject(QStringLiteral("Organization")));
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
200{
201 return m_engine->toScriptValue(ReservationConverter::trainToBus(QJsonValue::fromVariant(trainRes.toVariant()).toObject()));
202}
203
205{
206 return m_engine->toScriptValue(ReservationConverter::busToTrain(QJsonValue::fromVariant(busRes.toVariant()).toObject()));
207}
208
209QDateTime JsApi::JsonLd::toDateTime(const QString &dtStr, const QString &format, const QString &localeName) const
210{
211 QLocale locale(localeName);
212 auto dt = locale.toDateTime(dtStr, format);
213
214 // try harder for the "MMM" month format
215 // QLocale expects the exact string in QLocale::shortMonthName(), while we often encounter a three
216 // letter month identifier. For en_US that's the same, for Swedish it isn't though for example. So
217 // let's try to fix up the month identifiers to the full short name.
218 if (!dt.isValid() && format.contains(QLatin1StringView("MMM"))) {
219 auto dtStrFixed = dtStr;
220 for (int i = 1; i <= 12; ++i) {
221 const auto monthName = locale.monthName(i, QLocale::ShortFormat);
222 dtStrFixed.replace(monthName.left(3), monthName, Qt::CaseInsensitive);
223 }
224 dt = locale.toDateTime(dtStrFixed, format);
225 }
226
227 // try even harder for "MMM" month format
228 // in the de_DE locale we encounter sometimes almost arbitrary abbreviations for month
229 // names (eg. Mrz, Mär for März). So try to identify those and replace them with what QLocale
230 // expects
231 if (!dt.isValid() && format.contains(QLatin1StringView("MMM"))) {
232 auto dtStrFixed = dtStr;
233 for (int i = 1; i <= 12; ++i) {
234 const auto monthName = locale.monthName(i, QLocale::LongFormat);
235 const auto beginIdx = dtStr.indexOf(monthName.at(0));
236 if (beginIdx < 0) {
237 continue;
238 }
239 auto endIdx = beginIdx;
240 for (auto nameIdx = 0;
241 endIdx < dtStr.size() && nameIdx < monthName.size(); ++nameIdx) {
242 if (dtStr.at(endIdx).toCaseFolded() ==
243 monthName.at(nameIdx).toCaseFolded()) {
244 ++endIdx;
245 }
246 }
247 if (endIdx - beginIdx >= 3) {
248 dtStrFixed.replace(beginIdx, endIdx - beginIdx,
249 locale.monthName(i, QLocale::ShortFormat));
250 break;
251 }
252 }
253 dt = locale.toDateTime(dtStrFixed, format);
254 }
255
256 if (!dt.isValid()) {
257 return dt;
258 }
259
260 const bool hasFullYear = format.contains(QLatin1StringView("yyyy"));
261 const bool hasYear =
262 hasFullYear || format.contains(QLatin1StringView("yy"));
263 const bool hasMonth = format.contains(QLatin1Char('M'));
264 const bool hasDay = format.contains(QLatin1Char('d'));
265
266 // time only, set a default date
267 if (!hasDay && !hasMonth && !hasYear) {
268 dt.setDate({1970, 1, 1});
269 }
270
271 // if the date does not contain a year number, determine that based on the context date, if set
272 else if (!hasYear && m_contextDate.isValid()) {
273 dt.setDate({m_contextDate.date().year(), dt.date().month(), dt.date().day()});
274 // go one day back to leave a bit of room for documents produced very close to
275 // or even during the trip
276 if (dt < m_contextDate.addDays(-1)) {
277 dt = dt.addYears(1);
278 }
279 }
280
281 // fix two-digit years ending up in the wrong century
282 else if (!hasFullYear && dt.date().year() / 100 == 19) {
283 // be careful to only change the date, QDateTime::addYears can change
284 // the time as well if e.g. DST has been changed in the corresponding timezone
285 dt.setDate(dt.date().addYears(100));
286 }
287
288 return dt;
289}
290
291QDateTime JsApi::JsonLd::toDateTime(const QString &dtStr, const QJSValue &format, const QJSValue &localeName) const
292{
293 if (localeName.isString() && format.isString()) {
294 return toDateTime(dtStr, format.toString(), localeName.toString());
295 }
296 if (format.isArray()) {
297 const auto count = format.property(QLatin1StringView("length")).toInt();
298 for (auto i = 0; i < count; ++i) {
299 const auto dt = toDateTime(dtStr, format.property(i), localeName);
300 if (dt.isValid()) {
301 return dt;
302 }
303 }
304 }
305 if (format.isString() && localeName.isArray()) {
306 const auto count =
307 localeName.property(QLatin1StringView("length")).toInt();
308 for (auto i = 0; i < count; ++i) {
309 const auto dt = toDateTime(dtStr, format.toString(),
310 localeName.property(i).toString());
311 if (dt.isValid()) {
312 return dt;
313 }
314 }
315 }
316 return {};
317}
318
320{
321 if (v.canConvert<QList<QVariant>>()) {
322 return m_engine->toScriptValue(
324 }
325
326 const auto json = JsonLdDocument::toJsonValue(v);
327 return m_engine->toScriptValue(json);
328}
329
331{
332 return m_engine->toScriptValue(v.toVariant());
333}
334
336{
337 QUrl url(mapUrl);
338 if (url.host().contains(QLatin1StringView("google"))) {
339 QRegularExpression regExp(
340 QStringLiteral("[/=@](-?\\d+\\.\\d+),(-?\\d+\\.\\d+)"));
341 auto match = regExp.match(url.path());
342 if (!match.hasMatch()) {
343 match = regExp.match(url.query());
344 }
345
346 if (match.hasMatch()) {
347 auto geo = m_engine->newObject();
348 geo.setProperty(QStringLiteral("@type"),
349 QStringLiteral("GeoCoordinates"));
350 geo.setProperty(QStringLiteral("latitude"),
351 match.capturedView(1).toDouble());
352 geo.setProperty(QStringLiteral("longitude"),
353 match.capturedView(2).toDouble());
354 return geo;
355 }
356 }
357
358 return {};
359}
360
361QJSValue JsApi::JsonLd::readQDateTime(const QVariant &obj, const QString &propName) const
362{
363 const auto mo = QMetaType(obj.userType()).metaObject();
364 if (!mo) {
365 return {};
366 }
367 const auto propIdx = mo->indexOfProperty(propName.toUtf8().constData());
368 if (propIdx < 0) {
369 qWarning() << "Unknown property name:" << mo->className() << propName;
370 return {};
371 }
372 const auto prop = mo->property(propIdx);
373 const auto dt = prop.readOnGadget(obj.constData());
374 return toJson(dt);
375}
376
377QJSValue JsApi::JsonLd::apply(const QJSValue &lhs, const QJSValue &rhs) const
378{
381 const auto v = JsonLdDocument::apply(lhsVar, rhsVar);
382 return toJson(v);
383}
384
385void JsApi::JsonLd::setContextDate(const QDateTime& dt)
386{
387 m_contextDate = dt;
388}
389
390#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.
QJsonObject trainToBus(const QJsonObject &trainRes)
Convert a train reservation to a bus reservation.
QJsonObject busToTrain(const QJsonObject &busRes)
Convert a bus reservation to a train reservation.
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
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 Fri Oct 11 2024 12:14:41 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.