KHealthCertificate

jsonld.cpp
1 /*
2  * SPDX-FileCopyrightText: 2021 Volker Krause <[email protected]>
3  * SPDX-License-Identifier: LGPL-2.0-or-later
4  */
5 
6 #include "jsonld_p.h"
7 #include "rdf_p.h"
8 
9 #include <QJsonArray>
10 #include <QJsonDocument>
11 #include <QJsonObject>
12 
13 static bool readCurie(const QJsonObject::const_iterator it, JsonLdCurieMap &curieMap)
14 {
15  const auto prefix = it.value().toString();
16  if (!prefix.startsWith(QLatin1String("http"))) {
17  return false;
18  }
19  constexpr const char allowdTrailingChars[] = ":/?#[]@";
20  if (std::count(std::begin(allowdTrailingChars), std::end(allowdTrailingChars), prefix.back().toLatin1()) == 0) {
21  return false;
22  }
23  curieMap.insert(it.key(), prefix);
24  return true;
25 }
26 
27 static void resolveCurie(JsonLdProperty &prop, const JsonLdCurieMap &curieMap);
28 
29 static void resolveCurie(QString &str, const JsonLdCurieMap &curieMap)
30 {
31  const auto idx = str.indexOf(QLatin1Char(':'));
32  if (idx < 0) {
33  return;
34  }
35  const auto prefix = QStringView(str).left(idx);
36  const auto fullPrefix = curieMap.value(prefix.toString());
37  if (fullPrefix.isEmpty()) {
38  return;
39  }
40  str = fullPrefix + QStringView(str).mid(idx + 1);
41 }
42 
43 static void resolveCurie(JsonLdMetaType &mt, const JsonLdCurieMap &curieMap)
44 {
45  resolveCurie(mt.qualifiedName, curieMap);
46  for (auto &prop : mt.properties) {
47  resolveCurie(prop, curieMap);
48  }
49 }
50 
51 static void resolveCurie(JsonLdProperty &prop, const JsonLdCurieMap &curieMap)
52 {
53  resolveCurie(prop.qualifiedName, curieMap);
54  resolveCurie(prop.type, curieMap);
55  resolveCurie(prop.metaType, curieMap);
56  resolveCurie(prop.prefix, curieMap);
57 }
58 
59 JsonLdProperty JsonLdProperty::fromJson(const QString &name, const QJsonValue &value)
60 {
61  JsonLdProperty prop;
62  if (name == QLatin1String("id")) {
63  return prop;
64  }
65  prop.name = name;
66  if (value.isString()) {
67  prop.qualifiedName = value.toString();
68  } else {
69  const auto obj = value.toObject();
70  prop.qualifiedName = obj.value(QLatin1String("@id")).toString();
71  prop.type = obj.value(QLatin1String("@type")).toString();
72  if (prop.type == QLatin1String("@vocab")) {
73  prop.prefix = prop.qualifiedName.left(prop.qualifiedName.indexOf(QLatin1Char(':')) + 1);
74  }
75  }
76  return prop;
77 }
78 
79 
80 void JsonLdMetaType::load(const QJsonObject &obj)
81 {
82  qualifiedName = obj.value(QLatin1String("@id")).toString();
83  const auto context = obj.value(QLatin1String("@context")).toObject();
84  JsonLdCurieMap curieMap;
85  for (auto it = context.begin(); it != context.end(); ++it) {
86  if (it.value().isObject()) {
87  const auto propObj = it.value().toObject();
88  auto prop = JsonLdProperty::fromJson(it.key(), it.value());
89  if (propObj.contains(QLatin1String("@context"))) {
90  prop.metaType.load(propObj);
91  }
92  addProperty(std::move(prop));
93  }
94  else if (it.value().isString()) {
95  if (readCurie(it, curieMap)) {
96  continue;
97  }
98  addProperty(JsonLdProperty::fromJson(it.key(), it.value()));
99  }
100  }
101 
102  resolveCurie(*this, curieMap);
103 }
104 
105 void JsonLdMetaType::addProperty(JsonLdProperty &&property)
106 {
107  if (property.name.isEmpty()) {
108  return;
109  }
110 
111  auto it = std::lower_bound(properties.begin(), properties.end(), property, [](const auto &lhs, const auto &rhs) { return lhs.name < rhs.name; });
112  if (it == properties.end() || (*it).name != property.name) {
113  properties.insert(it, std::move(property));
114  }
115 }
116 
117 
118 void JsonLdContext::load(const QByteArray &contextData, const JsonLdDocumentLoader &loader)
119 {
120  const auto doc = QJsonDocument::fromJson(contextData);
121  const auto context = doc.object().value(QLatin1String("@context"));
122  if (context.isObject()) {
123  load(context.toObject());
124  } else if (context.isArray()) {
125  for (const auto &c : context.toArray()) {
126  if (c.isObject()) {
127  load(c.toObject());
128  } else if (c.isString()) {
129  load(loader(c.toString()), loader);
130  }
131  }
132  }
133 }
134 
135 void JsonLdContext::load(const QJsonObject &context)
136 {
137  for (auto it = context.begin(); it != context.end(); ++it) {
138  if (it.value().isObject()) {
139  const auto subObj = it.value().toObject();
140  if (it.key().front().isUpper() || subObj.contains(QLatin1String("@context"))) {
141  JsonLdMetaType metaType;
142  metaType.name = it.key();
143  metaType.load(subObj);
144  metaTypes.push_back(std::move(metaType));
145  } else {
146  globalProperties.push_back(JsonLdProperty::fromJson(it.key(), it.value()));
147  }
148  }
149  else if (it.value().isString()) {
150  if (readCurie(it, curieMap)) {
151  continue;
152  }
153  if (it.key().front().isUpper()) {
154  JsonLdMetaType metaType;
155  metaType.name = it.key();
156  metaType.qualifiedName = it.value().toString();
157  metaTypes.push_back(std::move(metaType));
158  } else {
159  globalProperties.push_back(JsonLdProperty::fromJson(it.key(), it.value()));
160  }
161  }
162  }
163 }
164 
165 void JsonLdContext::resolve()
166 {
167  for (auto &mt : metaTypes) {
168  for (auto prop : globalProperties) { // copy is intentional!
169  mt.addProperty(std::move(prop));
170  }
171  }
172 
173  for (auto &mt : metaTypes) {
174  resolveCurie(mt, curieMap);
175  }
176 
177  std::sort(metaTypes.begin(), metaTypes.end(), [](const auto &lhs, const auto &rhs) {
178  return lhs.name < rhs.name;
179  });
180 }
181 
182 JsonLdMetaType JsonLdContext::metaType(const QString &type) const
183 {
184  const auto it = std::lower_bound(metaTypes.begin(), metaTypes.end(), type, [](const auto &lhs, const auto &rhs) {
185  return lhs.name < rhs;
186  });
187  if (it != metaTypes.end() && (*it).name == type) {
188  return *it;
189  }
190  return {};
191 }
192 
193 void JsonLd::setDocumentLoader(const JsonLdDocumentLoader &loader)
194 {
195  m_documentLoader = loader;
196 }
197 
198 std::vector<Rdf::Quad> JsonLd::toRdf(const QJsonObject &obj) const
199 {
200  std::vector<Rdf::Quad> quads;
201 
202  // determine context
203  const auto contextVal = obj.value(QLatin1String("@context"));
204  JsonLdContext context;
205  if (contextVal.isArray()) {
206  for (const auto &contextV : contextVal.toArray()) {
207  context.load(m_documentLoader(contextV.toString()), m_documentLoader);
208  }
209  } else if (contextVal.isString()) {
210  context.load(m_documentLoader(contextVal.toString()), m_documentLoader);
211  }
212  context.resolve();
213 
214  toRdfRecursive(context, obj, quads);
215  return quads;
216 }
217 
218 Rdf::Term JsonLd::toRdfRecursive(const JsonLdContext &context, const QJsonObject &obj, std::vector<Rdf::Quad> &quads) const
219 {
220  const auto id = idForObject(obj);
221 
222  // find meta type for this object
223  const auto typeVal = obj.value(QLatin1String("type"));
224  if (typeVal.isArray()) {
225  for (const auto &typeV : typeVal.toArray()) {
226  toRdfRecursive(context, context.metaType(typeV.toString()), id, obj, quads);
227  }
228  } else if (typeVal.isString()) {
229  toRdfRecursive(context, context.metaType(typeVal.toString()), id, obj, quads);
230  }
231 
232  return id;
233 }
234 
235 void JsonLd::toRdfRecursive(const JsonLdContext &context, const JsonLdMetaType &mt, const Rdf::Term &id, const QJsonObject &obj, std::vector<Rdf::Quad> &quads) const
236 {
237  if (mt.name.isEmpty() && mt.properties.empty()) { // meta type not found
238  return;
239  }
240 
241  for (const auto &property : mt.properties) {
242  const auto val = obj.value(property.name);
243  if (val.isUndefined()) {
244  continue;
245  }
246 
247  const auto createQuad = [&](const QJsonValue &value) {
248  Rdf::Quad quad;
249  quad.subject = id;
250  quad.predicate.value = property.qualifiedName;
251  quad.predicate.type = Rdf::Term::IRI;
252  if (value.isString()) {
253  quad.object.value = value.toString();
254  if (property.type == QLatin1String("@id")) {
255  quad.object.type = Rdf::Term::IRI;
256  } else if (property.qualifiedName == QLatin1String("@type")) {
257  quad.predicate.value = QStringLiteral("http://www.w3.org/1999/02/22-rdf-syntax-ns#type");
258  quad.object.type = Rdf::Term::IRI;
259  if (!property.metaType.qualifiedName.isEmpty()) {
260  quad.object.value = property.metaType.qualifiedName;
261  } else if (!mt.qualifiedName.isEmpty()) {
262  quad.object.value = mt.qualifiedName;
263  }
264  } else if (property.type == QLatin1String("@vocab")) {
265  quad.object.type = Rdf::Term::IRI;
266  quad.object.value = property.prefix + quad.object.value;
267  } else {
268  quad.object.type = Rdf::Term::Literal;
269  quad.object.literalType = property.type;
270  }
271  } else if (value.isObject()) {
272  if (property.metaType.properties.empty()) {
273  quad.object = toRdfRecursive(context, value.toObject(), quads);
274  } else {
275  const auto subId = idForObject(value.toObject());
276  toRdfRecursive(context, property.metaType, subId, value.toObject(), quads);
277  quad.object = subId;
278  }
279  } else if (value.isDouble()) {
280  quad.object.value = QString::number(value.toInt());
281  quad.object.type = Rdf::Term::Literal;
282  quad.object.literalType = QStringLiteral("http://www.w3.org/2001/XMLSchema#integer");
283  }
284  quads.push_back(std::move(quad));
285  };
286  if (val.isArray()) {
287  if (property.name == QLatin1String("type")) {
288  // we are already iterating over that one!
289  createQuad(mt.name);
290  } else {
291  for (const auto &i : val.toArray()) {
292  createQuad(i);
293  }
294  }
295  } else {
296  createQuad(val);
297  }
298  }
299 }
300 
301 Rdf::Term JsonLd::idForObject(const QJsonObject &obj) const
302 {
303  Rdf::Term id;
304  id.value = obj.value(QLatin1String("id")).toString();
305  if (id.value.isEmpty()) {
306  id.type = Rdf::Term::BlankNode;
307  id.value = QString::number(m_blankNodeCounter++);
308  } else {
309  id.type = Rdf::Term::IRI;
310  }
311  return id;
312 }
QJsonDocument fromJson(const QByteArray &json, QJsonParseError *error)
int indexOf(QChar ch, int from, Qt::CaseSensitivity cs) const const
bool isDouble() const const
bool isObject() const const
QJsonObject::iterator end()
QJsonObject::iterator begin()
int toInt(int defaultValue) const const
QAction * load(const QObject *recvr, const char *slot, QObject *parent)
QString number(int n, int base)
QString toString() const const
KGuiItem properties()
QJsonObject toObject() const const
bool isString() const const
QString left(int n) const const
QJsonValue value(const QString &key) const const
QJsonValue::Type type() const const
QStringView left(qsizetype length) const const
QStringView mid(qsizetype start) const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2021 The KDE developers.
Generated on Mon Oct 18 2021 23:21:56 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.