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 }