KItinerary

jsonlddocument.cpp
1/*
2 SPDX-FileCopyrightText: 2017 Volker Krause <vkrause@kde.org>
3
4 SPDX-License-Identifier: LGPL-2.0-or-later
5*/
6
7#include "jsonlddocument.h"
8#include "json/jsonld.h"
9#include "json/jsonldimportfilter.h"
10#include "logging.h"
11
12#include <KItinerary/Action>
13#include <KItinerary/BoatTrip>
14#include <KItinerary/Brand>
15#include <KItinerary/BusTrip>
16#include <KItinerary/CreativeWork>
17#include <KItinerary/Event>
18#include <KItinerary/Flight>
19#include <KItinerary/Organization>
20#include <KItinerary/Person>
21#include <KItinerary/Place>
22#include <KItinerary/ProgramMembership>
23#include <KItinerary/Reservation>
24#include <KItinerary/RentalCar>
25#include <KItinerary/Taxi>
26#include <KItinerary/Ticket>
27#include <KItinerary/TrainTrip>
28#include <KItinerary/Visit>
29
30#include <QDateTime>
31#include <QJsonArray>
32#include <QJsonObject>
33#include <QMetaProperty>
34#include <QUrl>
35#include <QTimeZone>
36#include <QSequentialIterable>
37
38#include <cassert>
39#include <cmath>
40#include <cstring>
41
42using namespace KItinerary;
43
44struct TypeInfo {
45 const char *name;
46 const QMetaObject *mo;
47 int metaTypeId;
48};
49
50static void registerBuiltInTypes(std::vector<TypeInfo> &r);
51static std::vector<TypeInfo>& typeResgistry()
52{
53 static std::vector<TypeInfo> s_typeResgistry;
54 if (s_typeResgistry.empty()) {
55 registerBuiltInTypes(s_typeResgistry);
56 }
57 return s_typeResgistry;
58}
59
60template <typename T>
61static void add(std::vector<TypeInfo> &registry)
62{
63 registry.push_back({ T::typeName(), &T::staticMetaObject, qMetaTypeId<T>() });
64}
65
66static void registerBuiltInTypes(std::vector<TypeInfo> &r)
67{
68 add<Action>(r);
69 add<Airline>(r);
70 add<Airport>(r);
71 add<BoatReservation>(r);
72 add<BoatTerminal>(r);
73 add<BoatTrip>(r);
74 add<Brand>(r);
75 add<BusReservation>(r);
76 add<BusStation>(r);
77 add<BusTrip>(r);
78 add<CancelAction>(r);
79 add<CheckInAction>(r);
80 add<CreativeWork>(r);
81 add<DigitalDocument>(r);
82 add<DownloadAction>(r);
83 add<EmailMessage>(r);
84 add<Event>(r);
85 add<EventReservation>(r);
86 add<Flight>(r);
87 add<FlightReservation>(r);
88 add<FoodEstablishment>(r);
89 add<FoodEstablishmentReservation>(r);
90 add<GeoCoordinates>(r);
91 add<JoinAction>(r);
92 add<LocalBusiness>(r);
93 add<LodgingBusiness>(r);
94 add<LodgingReservation>(r);
95 add<Organization>(r);
96 add<Person>(r);
97 add<Place>(r);
98 add<PostalAddress>(r);
99 add<ProgramMembership>(r);
100 add<RentalCar>(r);
101 add<RentalCarReservation>(r);
102 add<ReserveAction>(r);
103 add<Seat>(r);
104 add<Taxi>(r);
105 add<TaxiReservation>(r);
106 add<Ticket>(r);
107 add<TouristAttraction>(r);
108 add<TouristAttractionVisit>(r);
109 add<TrainReservation>(r);
110 add<TrainStation>(r);
111 add<TrainTrip>(r);
112 add<UpdateAction>(r);
113 add<ViewAction>(r);
114
115 assert(std::is_sorted(r.begin(), r.end(), [](const auto &lhs, const auto &rhs) {
116 return std::strcmp(lhs.name, rhs.name) < 0;
117 }));
118}
119
120static QVariant createInstance(const QJsonObject &obj);
121static QVariant createInstance(const QJsonObject &obj, const QMetaProperty &prop);
122
123// Eurowings workarounds...
124static const char* const fallbackDateTimePattern[] = {
125 "yyyy-MM-dd HH:mm:ss",
126 "yyyy-MM-dd HH:mm",
127 "MM-dd-yyyy HH:mm", // yes, seriously ;(
128 "yyyyMMddTHHmmsst"
129};
130static const auto fallbackDateTimePatternCount = sizeof(fallbackDateTimePattern) / sizeof(const char *);
131
132static bool isEmptyJsonLdObject(const QJsonObject &obj)
133{
134 for (auto it = obj.begin(); it != obj.end(); ++it) {
135 if (it.key() == QLatin1StringView("@type")) {
136 continue;
137 }
138 if (it.value().type() != QJsonValue::Object) {
139 return false;
140 }
141 if (!isEmptyJsonLdObject(it.value().toObject())) {
142 return false;
143 }
144 }
145 return true;
146}
147
148// cache timezones by IANA id, with Qt6 QTimeZone(QByteArray) is unreasonably slow
149// on Android, so that loading Itinerary's cached public transport data takes up to 20secs...
150// can and should be removed once this has been fixed in Qt
151static QTimeZone timeZone(const QByteArray &tzId)
152{
153 static QHash<QByteArray, QTimeZone> s_tzCache;
154 const auto it = s_tzCache.constFind(tzId);
155 if (it != s_tzCache.constEnd()) {
156 return it.value();
157 }
158 auto tz = QTimeZone(tzId);
159 s_tzCache.insert(tzId, tz);
160 return tz;
161}
162
163static QVariant propertyValue(const QMetaProperty &prop, const QJsonValue &v)
164{
165 // enum handling must be done first, as prop.type() == Int
166 if (prop.isEnumType() && v.isString()) {
167 const auto value = JsonLd::normalizeTypeName(v.toString());
168 bool success = false;
169 const auto key = prop.enumerator().keyToValue(value.toUtf8().constData(), &success);
170 if (success) {
171 return key;
172 }
173 qCWarning(Log) << "Unknown enum value" << value << "for" << prop.typeName();
174 return {};
175 }
176
177 switch (prop.userType()) {
179 if (v.isDouble()) {
180 double i = 0.0;
181 const auto frac = std::modf(v.toDouble(), &i);
182 if (frac == 0.0) {
183 return QString::number(static_cast<uint64_t>(i));
184 }
185 return QString::number(v.toDouble());
186 }
187 return v.toString();
188 case QMetaType::QDate:
191 {
192 QDateTime dt;
193 if (v.isObject()) {
194 const auto dtObj = v.toObject();
195 if (dtObj.value(QLatin1StringView("@type")).toString() ==
196 QLatin1StringView("QDateTime")) {
198 dtObj.value(QLatin1StringView("@value")).toString(),
200 dt.setTimeZone(timeZone(dtObj.value(QLatin1StringView("timezone"))
201 .toString()
202 .toUtf8()));
203 }
204 } else {
205 auto str = v.toString();
207 if (dt.isNull()) {
208 dt = QDateTime::fromString(QString(str).simplified().remove(QLatin1Char(' ')), Qt::ISODate);
209 }
210 for (unsigned int i = 0; i < fallbackDateTimePatternCount && dt.isNull(); ++i) {
211 dt = QDateTime::fromString(str, QString::fromLatin1(fallbackDateTimePattern[i]));
212 }
213 // HACK QDateTimeParser handles 't' in the format but then forces it back to LocalTime in the end...
214 if (dt.isValid() && dt.timeSpec() == Qt::LocalTime && str.endsWith(QLatin1Char('Z'))) {
215 dt = dt.toTimeZone(QTimeZone::utc());
216 }
217 if (dt.isNull()) {
218 qCDebug(Log) << "Datetime parsing failed for" << str;
219 }
220 }
221
222 return dt;
223 }
225 case QMetaType::Float:
226 {
227 if (v.isDouble()) {
228 return v.toDouble();
229 }
230 bool ok = false;
231 const auto res = v.toString().toDouble(&ok);
232 return ok ? res : QVariant();
233 }
234 case QMetaType::Int:
235 if (v.isDouble()) {
236 return v.toDouble();
237 }
238 return v.toString().toInt();
239 case QMetaType::QTime:
241 case QMetaType::QUrl:
242 return QUrl(v.toString());
244 {
245 QVariantList l;
246 if (v.isArray()) {
247 const auto array = v.toArray();
248 l.reserve(array.size());
249 for (const auto &elem : array) {
250 if (elem.isObject()) {
251 const auto var = createInstance(elem.toObject());
252 if (!var.isNull()) {
253 l.push_back(var);
254 }
255 } else if (elem.isString()) {
256 l.push_back(elem.toString());
257 }
258 }
259 }
260 return QVariant::fromValue(l);
261 }
262 default:
263 break;
264 }
265
266 const auto obj = v.toObject();
267 if (prop.userType() == qMetaTypeId<QVariant>() && isEmptyJsonLdObject(obj)) {
268 return {};
269 }
270 return createInstance(obj, prop);
271}
272
273static void createInstance(const QMetaObject *mo, void *v, const QJsonObject &obj)
274{
275 for (auto it = obj.begin(); it != obj.end(); ++it) {
276 if (it.key().startsWith(QLatin1Char('@'))) {
277 continue;
278 }
279 const auto idx = mo->indexOfProperty(it.key().toLatin1().constData());
280 if (idx < 0) {
281 qCDebug(Log) << "property" << it.key() << "could not be set on object of type" << mo->className();
282 continue;
283 }
284 const auto prop = mo->property(idx);
285 const auto value = propertyValue(prop, it.value());
286 if (!value.isNull()) {
287 prop.writeOnGadget(v, value);
288 }
289 }
290}
291
292static QVariant createInstance(const QJsonObject& obj, const QString &type)
293{
294 const auto& registry = typeResgistry();
295 const auto it = std::lower_bound(registry.begin(), registry.end(), type,
296 [](const auto &lhs, const auto &rhs) {
297 return QLatin1StringView(lhs.name) < rhs;
298 });
299 if (it != registry.end() && QLatin1StringView((*it).name) == type) {
300 QVariant value(QMetaType((*it).metaTypeId), nullptr);
301 createInstance((*it).mo, value.data(), obj);
302 return value;
303 }
304
305 if (type == QLatin1StringView("QDateTime")) {
306 auto dt = QDateTime::fromString(
307 obj.value(QLatin1StringView("@value")).toString(), Qt::ISODate);
308 dt.setTimeZone(timeZone(
309 obj.value(QLatin1StringView("timezone")).toString().toUtf8()));
310 return dt;
311 }
312
313 return {};
314}
315
316static QVariant createInstance(const QJsonObject &obj)
317{
318 return createInstance(obj, JsonLd::typeName(obj));
319}
320
321static QVariant createInstance(const QJsonObject &obj, const QMetaProperty &prop)
322{
323 const auto type = JsonLd::typeName(obj);
324 if (!type.isEmpty()) {
325 return createInstance(obj, type);
326 }
327
328 // if @type is (wrongly) not specified, try to recover from our own knowledge of a property type
329 const auto mo = QMetaType(prop.userType()).metaObject();
330 if (mo) {
331 QVariant value(prop.metaType(), nullptr);
332 createInstance(mo, value.data(), obj);
333 return value;
334 }
335
336 return {};
337}
338
341 l.reserve(array.size());
342 for (const auto &obj : array) {
343 l.append(JsonLdDocument::fromJson(obj.toObject()));
344 }
345 return l;
346}
347
349 const auto normalized = JsonLdImportFilter::filterObject(obj);
350 QList<QVariant> result;
351 result.reserve(normalized.size());
352 for (const auto &n : normalized) {
353 const auto v = createInstance(n.toObject());
354 if (!v.isNull()) {
355 result.push_back(v);
356 }
357 }
358 return result;
359}
360
362{
363 const auto normalized = JsonLdImportFilter::filterObject(obj);
364 if (normalized.isEmpty()) {
365 return {};
366 }
367 return createInstance(normalized.at(0).toObject());
368}
369
371{
372 switch (v.userType()) {
373 case QMetaType::QUrl:
374 return !v.toUrl().isValid();
375 case QMetaType::Float:
376 return std::isnan(v.toFloat());
378 return std::isnan(v.toDouble());
380 return v.toList().isEmpty();
381 // starting with Qt6, QVariant::isNull is "shallow" and would miss the following as well
383 return v.toString().isNull();
385 return v.toDateTime().isNull();
386 case QMetaType::QDate:
387 return v.toDate().isNull();
388 }
389 return v.isNull();
390}
391
392static QString typeName(const QMetaObject *mo, const QVariant &v)
393{
394 const auto n = JsonLdDocument::readProperty(v, "className").toString();
395 if (!n.isEmpty()) {
396 return n;
397 }
398
399 if (auto c = strstr(mo->className(), "::")) {
400 return QString::fromUtf8(c + 2);
401 }
402 return QString::fromUtf8(mo->className());
403}
404
406{
407 const auto mo = QMetaType(v.userType()).metaObject();
408 if (!mo) {
409 // basic types
410 switch (v.userType()) {
412 return v.toString();
414 return v.toDouble();
415 case QMetaType::Int:
416 return v.toInt();
417 case QMetaType::QDate:
418 return v.toDate().toString(Qt::ISODate);
420 {
421 const auto dt = v.toDateTime();
422 if (dt.timeSpec() == Qt::TimeZone && dt.timeZone() != QTimeZone::utc()) {
423 QJsonObject dtObj;
424 dtObj.insert(QStringLiteral("@type"), QStringLiteral("QDateTime"));
425 dtObj.insert(QStringLiteral("@value"), dt.toString(Qt::ISODate));
426 dtObj.insert(QStringLiteral("timezone"), QString::fromUtf8(dt.timeZone().id()));
427 return dtObj;
428 }
429 return v.toDateTime().toString(Qt::ISODate);
430 }
431 case QMetaType::QTime:
432 return v.toTime().toString(Qt::ISODate);
433 case QMetaType::QUrl:
434 return v.toUrl().toString();
435 case QMetaType::Bool:
436 return v.toBool();
437 case QMetaType::Float:
438 return v.toFloat();
439 default:
440 break;
441 }
442
443 if (v.canConvert<QVariantList>()) {
444 auto iterable = v.value<QSequentialIterable>();
445 if (iterable.size() == 0) {
446 return {};
447 }
448 QJsonArray array;
449 for (const auto &var : iterable) {
450 array.push_back(toJsonValue(var));
451 }
452 return array;
453 }
454
455 qCDebug(Log) << "unhandled value:" << v;
456 return {};
457 }
458
459 // composite types
460 QJsonObject obj;
461 obj.insert(QStringLiteral("@type"), typeName(mo, v));
462 for (int i = 0; i < mo->propertyCount(); ++i) {
463 const auto prop = mo->property(i);
464 if (!prop.isStored()) {
465 continue;
466 }
467
468 if (prop.isEnumType()) { // enums defined in this QMO
469 const auto key = prop.readOnGadget(v.constData()).toInt();
470 auto value = QString::fromUtf8(prop.enumerator().valueToKey(key));
471 // this is (ab)used elsewhere, so let's not interfere with enum serialization there for now
472 if (strncmp(mo->className(), "KItinerary::", 12) == 0) {
473 value = QLatin1StringView("http://schema.org/") + value;
474 }
475 obj.insert(QString::fromUtf8(prop.name()), value);
476 continue;
477 } else if (QMetaType(prop.userType()).flags() & QMetaType::IsEnumeration) { // external enums
479 continue;
480 }
481
482 const auto value = prop.readOnGadget(v.constData());
483 if (!JsonLd::valueIsNull(value)) {
484 const auto jsVal = toJsonValue(value);
485 if (jsVal.type() != QJsonValue::Null) {
486 obj.insert(QString::fromUtf8(prop.name()), jsVal);
487 }
488 }
489 }
490 if (obj.size() > 1) {
491 return obj;
492 }
493
494 return {};
495}
496
498 QJsonArray a;
499 for (const auto &d : data) {
500 const auto value = toJsonValue(d);
501 if (!value.isObject()) {
502 continue;
503 }
504 auto obj = value.toObject();
505 obj.insert(QStringLiteral("@context"), QStringLiteral("http://schema.org"));
506 a.push_back(obj);
507 }
508 return a;
509}
510
512{
513 const auto value = toJsonValue(data);
514 if (!value.isObject()) {
515 return {};
516 }
517 auto obj = value.toObject();
518 obj.insert(QStringLiteral("@context"), QStringLiteral("http://schema.org"));
519 return obj;
520}
521
522QVariant JsonLdDocument::readProperty(const QVariant &obj, const char *name)
523{
524 const auto mo = QMetaType(obj.userType()).metaObject();
525 if (!mo) {
526 return {};
527 }
528
529 const auto idx = mo->indexOfProperty(name);
530 if (idx < 0) {
531 return {};
532 }
533
534 const auto prop = mo->property(idx);
535 return prop.readOnGadget(obj.constData());
536}
537
538void JsonLdDocument::writeProperty(QVariant &obj, const char *name, const QVariant &value)
539{
540 const auto mo = QMetaType(obj.userType()).metaObject();
541 if (!mo) {
542 return;
543 }
544
545 writePropertyImpl(mo, obj.data(), name, value);
546}
547
548void JsonLdDocument::writePropertyImpl(const QMetaObject* mo, void* obj, const char* name, const QVariant& value)
549{
550 const auto idx = mo->indexOfProperty(name);
551 if (idx < 0) {
552 return;
553 }
554
555 const auto prop = mo->property(idx);
556 prop.writeOnGadget(obj, value);
557}
558
559void JsonLdDocument::removeProperty(QVariant &obj, const char *name)
560{
561 writeProperty(obj, name, QVariant());
562}
563
565{
566 if (rhs.isNull()) {
567 return lhs;
568 }
569 if (lhs.isNull()) {
570 return rhs;
571 }
572 if (lhs.userType() != rhs.userType()) {
573 qCWarning(Log) << "type mismatch during merging:" << lhs << rhs;
574 return {};
575 }
576
577 auto res = lhs;
578 const auto mo = QMetaType(res.userType()).metaObject();
579 for (int i = 0; i < mo->propertyCount(); ++i) {
580 const auto prop = mo->property(i);
581 if (!prop.isStored()) {
582 continue;
583 }
584
585 if (prop.isEnumType() && rhs.userType() == QMetaType::QString) { // internal enums in this QMO
586 bool success = false;
587 const auto key = prop.enumerator().keyToValue(rhs.toString().toUtf8().constData(), &success);
588 if (success) {
589 prop.writeOnGadget(res.data(), key);
590 } else {
591 qCWarning(Log) << "Got unknown enum value" << rhs.toString() << "for" << prop.typeName();
592 }
593 continue;
594 }
595 if ((QMetaType(prop.userType()).flags() & QMetaType::IsEnumeration) && rhs.userType() == QMetaType::QString) { // external enums
596 const QMetaType mt(prop.userType());
597 const auto mo = mt.metaObject();
598 if (!mo) {
599 qCWarning(Log) << "No meta object found for enum type:" << prop.typeName();
600 continue;
601 }
602 const auto enumIdx = mo->indexOfEnumerator(prop.typeName() + strlen(mo->className()) + 2);
603 if (enumIdx < 0) {
604 qCWarning(Log) << "Could not find QMetaEnum for" << prop.typeName();
605 continue;
606 }
607 const auto me = mo->enumerator(enumIdx);
608 bool success = false;
609 const auto numValue = me.keyToValue(rhs.toString().toUtf8().constData(), &success);
610 if (!success) {
611 qCWarning(Log) << "Unknown enum value" << rhs.toString() << "for" << prop.typeName();
612 continue;
613 }
614 auto valueData = mt.create();
615 *reinterpret_cast<int*>(valueData) = numValue;
616 QVariant value(prop.metaType(), valueData);
617 prop.writeOnGadget(res.data(), value);
618 continue;
619 }
620
621 auto pv = prop.readOnGadget(rhs.constData());
622 if ((QMetaType(pv.userType()).flags() & QMetaType::IsGadget) && QMetaType(pv.userType()).metaObject()) {
623 pv = apply(prop.readOnGadget(lhs.constData()), pv);
624 }
625 if (!JsonLd::valueIsNull(pv)) {
626 prop.writeOnGadget(res.data(), pv);
627 }
628 }
629
630 return res;
631}
632
633void JsonLdDocument::registerType(const char *typeName, const QMetaObject *mo, int metaTypeId)
634{
635 auto &registry = typeResgistry();
636 const auto it = std::lower_bound(registry.begin(), registry.end(), typeName, [](const auto &lhs, const auto *rhs) {
637 return std::strcmp(lhs.name, rhs) < 0;
638 });
639 if (it != registry.end() && std::strcmp((*it).name, typeName) == 0) {
640 qCWarning(Log) << "Type already registered:" << typeName;
641 } else {
642 registry.insert(it, { typeName, mo, metaTypeId });
643 }
644}
static void writeProperty(QVariant &obj, const char *name, const QVariant &value)
Set property name on object obj to value value.
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 QVariant readProperty(const QVariant &obj, const char *name)
Read property name on object obj.
static QJsonArray toJson(const QList< QVariant > &data)
Serialize instantiated data types to JSON-LD.
static void registerType()
Register a custom type for deserialization.
static void removeProperty(QVariant &obj, const char *name)
Removes property name on object obj.
static QList< QVariant > fromJson(const QJsonArray &array)
Convert JSON-LD array into instantiated data types.
static QVariant fromJsonSingular(const QJsonObject &obj)
Convert a single JSON-LD object into an instantiated data type.
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.
bool valueIsNull(const QVariant &v)
Checks whether v holds a null-like value.
Classes for reservation/travel data models, data extraction and data augmentation.
Definition berelement.h:17
KGuiItem add()
KGuiItem remove()
const char * constData() const const
QDate fromString(QStringView string, QStringView format, QCalendar cal)
bool isNull() const const
QString toString(QStringView format, QCalendar cal) const const
QDateTime fromString(QStringView string, QStringView format, QCalendar cal)
bool isNull() const const
bool isValid() const const
void setTimeZone(const QTimeZone &toZone)
Qt::TimeSpec timeSpec() const const
QTimeZone timeZone() const const
QString toString(QStringView format, QCalendar cal) const const
QDateTime toTimeZone(const QTimeZone &timeZone) const const
const_iterator constEnd() const const
const_iterator constFind(const Key &key) const const
iterator insert(const Key &key, const T &value)
void push_back(const QJsonValue &value)
qsizetype size() const const
iterator begin()
iterator end()
iterator insert(QLatin1StringView key, const QJsonValue &value)
qsizetype size() const const
QJsonValue value(QLatin1StringView key) const const
bool isArray() const const
bool isDouble() const const
bool isObject() const const
bool isString() const const
QJsonArray toArray() const const
double toDouble(double defaultValue) const const
QJsonObject toObject() const const
QString toString() const const
void append(QList< T > &&value)
bool isEmpty() const const
void push_back(parameter_type value)
void reserve(qsizetype size)
int keyToValue(const char *key, bool *ok) const const
const char * valueToKey(int value) const const
const char * className() const const
QMetaEnum enumerator(int index) const const
int indexOfEnumerator(const char *name) const const
int indexOfProperty(const char *name) const const
QMetaProperty property(int index) const const
int propertyCount() const const
QMetaEnum enumerator() const const
bool isEnumType() const const
bool isStored() const const
QMetaType metaType() const const
const char * name() const const
QVariant readOnGadget(const void *gadget) const const
const char * typeName() const const
int userType() const const
bool writeOnGadget(void *gadget, QVariant &&value) const const
void * create(int type, const void *copy)
TypeFlags flags() const const
const QMetaObject * metaObject() const const
QString fromLatin1(QByteArrayView str)
QString fromUtf8(QByteArrayView str)
bool isNull() const const
QString number(double n, char format, int precision)
double toDouble(bool *ok) const const
int toInt(bool *ok, int base) const const
QByteArray toUtf8() const const
LocalTime
QTime fromString(QStringView string, QStringView format)
QString toString(QStringView format) const const
QByteArray id() const const
QTimeZone utc()
bool isValid() const const
QString toString(FormattingOptions options) const const
bool canConvert() const const
const void * constData() const const
void * data()
QVariant fromValue(T &&value)
bool isNull() const const
bool toBool() const const
QDate toDate() const const
QDateTime toDateTime() const const
double toDouble(bool *ok) const const
float toFloat(bool *ok) const const
int toInt(bool *ok) const const
QList< QVariant > toList() const const
QString toString() const const
QTime toTime() const const
QUrl toUrl() 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 Nov 22 2024 12:00:34 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.