File indexing completed on 2025-02-16 04:50:53
0001 /* 0002 * SPDX-FileCopyrightText: 2021 Volker Krause <vkrause@kde.org> 0003 * SPDX-License-Identifier: LGPL-2.0-or-later 0004 */ 0005 0006 #include "jsonld_p.h" 0007 #include "rdf_p.h" 0008 0009 #include <QJsonArray> 0010 #include <QJsonDocument> 0011 #include <QJsonObject> 0012 0013 static bool readCurie(const QJsonObject::const_iterator it, JsonLdCurieMap &curieMap) 0014 { 0015 const auto prefix = it.value().toString(); 0016 if (!prefix.startsWith(QLatin1String("http"))) { 0017 return false; 0018 } 0019 constexpr const char allowdTrailingChars[] = ":/?#[]@"; 0020 if (std::count(std::begin(allowdTrailingChars), std::end(allowdTrailingChars), prefix.back().toLatin1()) == 0) { 0021 return false; 0022 } 0023 curieMap.insert(it.key(), prefix); 0024 return true; 0025 } 0026 0027 static void resolveCurie(JsonLdProperty &prop, const JsonLdCurieMap &curieMap); 0028 0029 static void resolveCurie(QString &str, const JsonLdCurieMap &curieMap) 0030 { 0031 const auto idx = str.indexOf(QLatin1Char(':')); 0032 if (idx < 0) { 0033 return; 0034 } 0035 const auto prefix = QStringView(str).left(idx); 0036 const auto fullPrefix = curieMap.value(prefix.toString()); 0037 if (fullPrefix.isEmpty()) { 0038 return; 0039 } 0040 str = fullPrefix + QStringView(str).mid(idx + 1); 0041 } 0042 0043 static void resolveCurie(JsonLdMetaType &mt, const JsonLdCurieMap &curieMap) 0044 { 0045 resolveCurie(mt.qualifiedName, curieMap); 0046 for (auto &prop : mt.properties) { 0047 resolveCurie(prop, curieMap); 0048 } 0049 } 0050 0051 static void resolveCurie(JsonLdProperty &prop, const JsonLdCurieMap &curieMap) 0052 { 0053 resolveCurie(prop.qualifiedName, curieMap); 0054 resolveCurie(prop.type, curieMap); 0055 resolveCurie(prop.metaType, curieMap); 0056 resolveCurie(prop.prefix, curieMap); 0057 } 0058 0059 JsonLdProperty JsonLdProperty::fromJson(const QString &name, const QJsonValue &value) 0060 { 0061 JsonLdProperty prop; 0062 if (name == QLatin1String("id")) { 0063 return prop; 0064 } 0065 prop.name = name; 0066 if (value.isString()) { 0067 prop.qualifiedName = value.toString(); 0068 } else { 0069 const auto obj = value.toObject(); 0070 prop.qualifiedName = obj.value(QLatin1String("@id")).toString(); 0071 prop.type = obj.value(QLatin1String("@type")).toString(); 0072 if (prop.type == QLatin1String("@vocab")) { 0073 prop.prefix = prop.qualifiedName.left(prop.qualifiedName.indexOf(QLatin1Char(':')) + 1); 0074 } 0075 } 0076 return prop; 0077 } 0078 0079 0080 void JsonLdMetaType::load(const QJsonObject &obj) 0081 { 0082 qualifiedName = obj.value(QLatin1String("@id")).toString(); 0083 const auto context = obj.value(QLatin1String("@context")).toObject(); 0084 JsonLdCurieMap curieMap; 0085 for (auto it = context.begin(); it != context.end(); ++it) { 0086 if (it.value().isObject()) { 0087 const auto propObj = it.value().toObject(); 0088 auto prop = JsonLdProperty::fromJson(it.key(), it.value()); 0089 if (propObj.contains(QLatin1String("@context"))) { 0090 prop.metaType.load(propObj); 0091 } 0092 addProperty(std::move(prop)); 0093 } 0094 else if (it.value().isString()) { 0095 if (readCurie(it, curieMap)) { 0096 continue; 0097 } 0098 addProperty(JsonLdProperty::fromJson(it.key(), it.value())); 0099 } 0100 } 0101 0102 resolveCurie(*this, curieMap); 0103 } 0104 0105 void JsonLdMetaType::addProperty(JsonLdProperty &&property) 0106 { 0107 if (property.name.isEmpty()) { 0108 return; 0109 } 0110 0111 auto it = std::lower_bound(properties.begin(), properties.end(), property, [](const auto &lhs, const auto &rhs) { return lhs.name < rhs.name; }); 0112 if (it == properties.end() || (*it).name != property.name) { 0113 properties.insert(it, std::move(property)); 0114 } 0115 } 0116 0117 0118 void JsonLdContext::load(const QByteArray &contextData, const JsonLdDocumentLoader &loader) 0119 { 0120 const auto doc = QJsonDocument::fromJson(contextData); 0121 const auto context = doc.object().value(QLatin1String("@context")); 0122 if (context.isObject()) { 0123 load(context.toObject()); 0124 } else if (context.isArray()) { 0125 for (const auto &c : context.toArray()) { 0126 if (c.isObject()) { 0127 load(c.toObject()); 0128 } else if (c.isString()) { 0129 load(loader(c.toString()), loader); 0130 } 0131 } 0132 } 0133 } 0134 0135 void JsonLdContext::load(const QJsonObject &context) 0136 { 0137 for (auto it = context.begin(); it != context.end(); ++it) { 0138 if (it.value().isObject()) { 0139 const auto subObj = it.value().toObject(); 0140 if (it.key().front().isUpper() || subObj.contains(QLatin1String("@context"))) { 0141 JsonLdMetaType metaType; 0142 metaType.name = it.key(); 0143 metaType.load(subObj); 0144 metaTypes.push_back(std::move(metaType)); 0145 } else { 0146 globalProperties.push_back(JsonLdProperty::fromJson(it.key(), it.value())); 0147 } 0148 } 0149 else if (it.value().isString()) { 0150 if (readCurie(it, curieMap)) { 0151 continue; 0152 } 0153 if (it.key().front().isUpper()) { 0154 JsonLdMetaType metaType; 0155 metaType.name = it.key(); 0156 metaType.qualifiedName = it.value().toString(); 0157 metaTypes.push_back(std::move(metaType)); 0158 } else { 0159 globalProperties.push_back(JsonLdProperty::fromJson(it.key(), it.value())); 0160 } 0161 } 0162 } 0163 } 0164 0165 void JsonLdContext::resolve() 0166 { 0167 for (auto &mt : metaTypes) { 0168 for (auto prop : globalProperties) { // copy is intentional! 0169 mt.addProperty(std::move(prop)); 0170 } 0171 } 0172 0173 for (auto &mt : metaTypes) { 0174 resolveCurie(mt, curieMap); 0175 } 0176 0177 std::sort(metaTypes.begin(), metaTypes.end(), [](const auto &lhs, const auto &rhs) { 0178 return lhs.name < rhs.name; 0179 }); 0180 } 0181 0182 JsonLdMetaType JsonLdContext::metaType(const QString &type) const 0183 { 0184 const auto it = std::lower_bound(metaTypes.begin(), metaTypes.end(), type, [](const auto &lhs, const auto &rhs) { 0185 return lhs.name < rhs; 0186 }); 0187 if (it != metaTypes.end() && (*it).name == type) { 0188 return *it; 0189 } 0190 return {}; 0191 } 0192 0193 void JsonLd::setDocumentLoader(const JsonLdDocumentLoader &loader) 0194 { 0195 m_documentLoader = loader; 0196 } 0197 0198 std::vector<Rdf::Quad> JsonLd::toRdf(const QJsonObject &obj) const 0199 { 0200 std::vector<Rdf::Quad> quads; 0201 0202 // determine context 0203 const auto contextVal = obj.value(QLatin1String("@context")); 0204 JsonLdContext context; 0205 if (contextVal.isArray()) { 0206 for (const auto &contextV : contextVal.toArray()) { 0207 context.load(m_documentLoader(contextV.toString()), m_documentLoader); 0208 } 0209 } else if (contextVal.isString()) { 0210 context.load(m_documentLoader(contextVal.toString()), m_documentLoader); 0211 } 0212 context.resolve(); 0213 0214 toRdfRecursive(context, obj, quads); 0215 return quads; 0216 } 0217 0218 Rdf::Term JsonLd::toRdfRecursive(const JsonLdContext &context, const QJsonObject &obj, std::vector<Rdf::Quad> &quads) const 0219 { 0220 const auto id = idForObject(obj); 0221 0222 // find meta type for this object 0223 const auto typeVal = obj.value(QLatin1String("type")); 0224 if (typeVal.isArray()) { 0225 for (const auto &typeV : typeVal.toArray()) { 0226 toRdfRecursive(context, context.metaType(typeV.toString()), id, obj, quads); 0227 } 0228 } else if (typeVal.isString()) { 0229 toRdfRecursive(context, context.metaType(typeVal.toString()), id, obj, quads); 0230 } 0231 0232 return id; 0233 } 0234 0235 void JsonLd::toRdfRecursive(const JsonLdContext &context, const JsonLdMetaType &mt, const Rdf::Term &id, const QJsonObject &obj, std::vector<Rdf::Quad> &quads) const 0236 { 0237 if (mt.name.isEmpty() && mt.properties.empty()) { // meta type not found 0238 return; 0239 } 0240 0241 for (const auto &property : mt.properties) { 0242 const auto val = obj.value(property.name); 0243 if (val.isUndefined()) { 0244 continue; 0245 } 0246 0247 const auto createQuad = [&](const QJsonValue &value) { 0248 Rdf::Quad quad; 0249 quad.subject = id; 0250 quad.predicate.value = property.qualifiedName; 0251 quad.predicate.type = Rdf::Term::IRI; 0252 if (value.isString()) { 0253 quad.object.value = value.toString(); 0254 if (property.type == QLatin1String("@id")) { 0255 quad.object.type = Rdf::Term::IRI; 0256 } else if (property.qualifiedName == QLatin1String("@type")) { 0257 quad.predicate.value = QStringLiteral("http://www.w3.org/1999/02/22-rdf-syntax-ns#type"); 0258 quad.object.type = Rdf::Term::IRI; 0259 if (!property.metaType.qualifiedName.isEmpty()) { 0260 quad.object.value = property.metaType.qualifiedName; 0261 } else if (!mt.qualifiedName.isEmpty()) { 0262 quad.object.value = mt.qualifiedName; 0263 } 0264 } else if (property.type == QLatin1String("@vocab")) { 0265 quad.object.type = Rdf::Term::IRI; 0266 quad.object.value = property.prefix + quad.object.value; 0267 } else { 0268 quad.object.type = Rdf::Term::Literal; 0269 quad.object.literalType = property.type; 0270 } 0271 } else if (value.isObject()) { 0272 if (property.metaType.properties.empty()) { 0273 quad.object = toRdfRecursive(context, value.toObject(), quads); 0274 } else { 0275 const auto subId = idForObject(value.toObject()); 0276 toRdfRecursive(context, property.metaType, subId, value.toObject(), quads); 0277 quad.object = subId; 0278 } 0279 } else if (value.isDouble()) { 0280 quad.object.value = QString::number(value.toInt()); 0281 quad.object.type = Rdf::Term::Literal; 0282 quad.object.literalType = QStringLiteral("http://www.w3.org/2001/XMLSchema#integer"); 0283 } 0284 quads.push_back(std::move(quad)); 0285 }; 0286 if (val.isArray()) { 0287 if (property.name == QLatin1String("type")) { 0288 // we are already iterating over that one! 0289 createQuad(mt.name); 0290 } else { 0291 for (const auto &i : val.toArray()) { 0292 createQuad(i); 0293 } 0294 } 0295 } else { 0296 createQuad(val); 0297 } 0298 } 0299 } 0300 0301 Rdf::Term JsonLd::idForObject(const QJsonObject &obj) const 0302 { 0303 Rdf::Term id; 0304 id.value = obj.value(QLatin1String("id")).toString(); 0305 if (id.value.isEmpty()) { 0306 id.type = Rdf::Term::BlankNode; 0307 id.value = QString::number(m_blankNodeCounter++); 0308 } else { 0309 id.type = Rdf::Term::IRI; 0310 } 0311 return id; 0312 }