File indexing completed on 2024-04-14 03:58:28

0001 /*
0002     This file is part of the syndication library
0003     SPDX-FileCopyrightText: 2006 Frank Osterfeld <osterfeld@kde.org>
0004 
0005     SPDX-License-Identifier: LGPL-2.0-or-later
0006 */
0007 
0008 #include "parser.h"
0009 #include "document.h"
0010 #include "model.h"
0011 #include "modelmaker.h"
0012 #include "property.h"
0013 #include "rdfvocab.h"
0014 #include "resource.h"
0015 #include "rssvocab.h"
0016 #include "statement.h"
0017 
0018 #include <documentsource.h>
0019 
0020 #include <QDomDocument>
0021 #include <QDomNodeList>
0022 #include <QHash>
0023 #include <QList>
0024 #include <QMap>
0025 #include <QString>
0026 #include <QStringList>
0027 
0028 namespace Syndication
0029 {
0030 namespace RDF
0031 {
0032 class SYNDICATION_NO_EXPORT Parser::ParserPrivate
0033 {
0034 public:
0035     QDomDocument addEnumeration(const QDomDocument &doc);
0036     void map09to10(Model model);
0037     void addSequenceFor09(Model model);
0038 
0039     QString strInternalNs;
0040     QString strItemIndex;
0041 };
0042 
0043 bool Parser::accept(const DocumentSource &source) const
0044 {
0045     QDomDocument doc = source.asDomDocument();
0046 
0047     if (doc.isNull()) {
0048         return false;
0049     }
0050     QDomElement root = doc.documentElement();
0051 
0052     if (!root.isElement()) {
0053         return false;
0054     }
0055 
0056     return root.namespaceURI() == RDFVocab::self()->namespaceURI();
0057 }
0058 
0059 SpecificDocumentPtr Parser::parse(const DocumentSource &source) const
0060 {
0061     QDomDocument doc = source.asDomDocument();
0062 
0063     if (doc.isNull()) {
0064         return Syndication::SpecificDocumentPtr(new Document());
0065     }
0066 
0067     doc = d->addEnumeration(doc);
0068 
0069     ModelMaker maker;
0070     Model model = maker.createFromXML(doc);
0071 
0072     bool is09 = !model.resourcesWithType(RSS09Vocab::self()->channel()).isEmpty();
0073 
0074     if (is09) {
0075         d->map09to10(model);
0076         d->addSequenceFor09(model);
0077     }
0078 
0079     QList<ResourcePtr> channels = model.resourcesWithType(RSSVocab::self()->channel());
0080 
0081     if (channels.isEmpty()) {
0082         return Syndication::SpecificDocumentPtr(new Document());
0083     }
0084 
0085     return DocumentPtr(new Document(*(channels.begin())));
0086 }
0087 
0088 QDomDocument Parser::ParserPrivate::addEnumeration(const QDomDocument &docp)
0089 {
0090     QDomDocument doc(docp);
0091 
0092     const QDomNodeList list = doc.elementsByTagNameNS(RSS09Vocab::self()->namespaceURI(), QStringLiteral("item"));
0093 
0094     for (int i = 0; i < list.size(); ++i) {
0095         QDomElement item = list.item(i).toElement();
0096         if (!item.isNull()) {
0097             QDomElement ie = doc.createElementNS(strInternalNs, strItemIndex);
0098             item.appendChild(ie);
0099             ie.appendChild(doc.createTextNode(QString::number(i)));
0100         }
0101     }
0102 
0103     return doc;
0104 }
0105 
0106 void Parser::ParserPrivate::map09to10(Model model)
0107 {
0108     QHash<QString, PropertyPtr> hash;
0109 
0110     hash.insert(RSS09Vocab::self()->title()->uri(), RSSVocab::self()->title());
0111     hash.insert(RSS09Vocab::self()->description()->uri(), RSSVocab::self()->description());
0112     hash.insert(RSS09Vocab::self()->link()->uri(), RSSVocab::self()->link());
0113     hash.insert(RSS09Vocab::self()->name()->uri(), RSSVocab::self()->name());
0114     hash.insert(RSS09Vocab::self()->url()->uri(), RSSVocab::self()->url());
0115     hash.insert(RSS09Vocab::self()->image()->uri(), RSSVocab::self()->image());
0116     hash.insert(RSS09Vocab::self()->textinput()->uri(), RSSVocab::self()->textinput());
0117 
0118     QStringList uris09 = RSS09Vocab::self()->properties();
0119 
0120     // map statement predicates to RSS 1.0
0121 
0122     const QList<StatementPtr> &statements = model.statements();
0123 
0124     for (const auto &stmt : statements) {
0125         const QString predUri = stmt->predicate()->uri();
0126         if (uris09.contains(predUri)) {
0127             model.addStatement(stmt->subject(), hash[predUri], stmt->object());
0128         }
0129     }
0130     // map channel type
0131     QList<ResourcePtr> channels = model.resourcesWithType(RSS09Vocab::self()->channel());
0132 
0133     ResourcePtr channel;
0134 
0135     if (!channels.isEmpty()) {
0136         channel = *(channels.begin());
0137 
0138         model.removeStatement(channel, RDFVocab::self()->type(), RSS09Vocab::self()->channel());
0139         model.addStatement(channel, RDFVocab::self()->type(), RSSVocab::self()->channel());
0140     }
0141 }
0142 
0143 void Parser::ParserPrivate::addSequenceFor09(Model model)
0144 {
0145     // RDF 0.9 doesn't contain an item sequence, and the items don't have rdf:about, so add both
0146 
0147     const QList<ResourcePtr> items = model.resourcesWithType(RSS09Vocab::self()->item());
0148 
0149     if (items.isEmpty()) {
0150         return;
0151     }
0152 
0153     const QList<ResourcePtr> channels = model.resourcesWithType(RSSVocab::self()->channel());
0154 
0155     if (channels.isEmpty()) {
0156         return;
0157     }
0158 
0159     PropertyPtr itemIndex = model.createProperty(strInternalNs + strItemIndex);
0160 
0161     // use QMap here, not QHash. as we need the sorting functionality
0162     QMap<uint, ResourcePtr> sorted;
0163 
0164     for (const ResourcePtr &i : items) {
0165         QString numstr = i->property(itemIndex)->asString();
0166         bool ok = false;
0167         uint num = numstr.toUInt(&ok);
0168         if (ok) {
0169             sorted[num] = i;
0170         }
0171     }
0172 
0173     SequencePtr seq = model.createSequence();
0174     model.addStatement(channels.first(), RSSVocab::self()->items(), seq);
0175 
0176     for (const ResourcePtr &i : std::as_const(sorted)) {
0177         seq->append(i);
0178         // add rdf:about (type)
0179         model.addStatement(i, RDFVocab::self()->type(), RSSVocab::self()->item());
0180 
0181         // add to items sequence
0182         model.addStatement(seq, RDFVocab::self()->li(), i);
0183     }
0184 }
0185 
0186 Parser::Parser()
0187     : d(new ParserPrivate)
0188 {
0189     d->strInternalNs = QStringLiteral("http://akregator.sf.net/libsyndication/internal#");
0190     d->strItemIndex = QStringLiteral("itemIndex");
0191 }
0192 
0193 Parser::~Parser() = default;
0194 
0195 Parser::Parser(const Parser &other)
0196     : AbstractParser(other)
0197     , d(nullptr)
0198 {
0199 }
0200 Parser &Parser::operator=(const Parser & /*other*/)
0201 {
0202     return *this;
0203 }
0204 
0205 QString Parser::format() const
0206 {
0207     return QStringLiteral("rdf");
0208 }
0209 
0210 } // namespace RDF
0211 } // namespace Syndication