File indexing completed on 2024-10-06 06:46:09

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 "document.h"
0009 #include "dublincore.h"
0010 #include "image.h"
0011 #include "item.h"
0012 #include "model.h"
0013 #include "model_p.h"
0014 #include "resource.h"
0015 #include "rssvocab.h"
0016 #include "sequence.h"
0017 #include "statement.h"
0018 #include "syndicationinfo.h"
0019 #include "textinput.h"
0020 
0021 #include <documentvisitor.h>
0022 #include <tools.h>
0023 
0024 #include <QList>
0025 #include <QString>
0026 #include <QStringList>
0027 #include <QVector>
0028 
0029 #include <algorithm>
0030 
0031 namespace Syndication
0032 {
0033 namespace RDF
0034 {
0035 class SYNDICATION_NO_EXPORT Document::Private
0036 {
0037 public:
0038     Private()
0039         : itemTitleContainsMarkup(false)
0040         , itemTitlesGuessed(false)
0041         , itemDescriptionContainsMarkup(false)
0042         , itemDescGuessed(false)
0043     {
0044     }
0045     mutable bool itemTitleContainsMarkup;
0046     mutable bool itemTitlesGuessed;
0047     mutable bool itemDescriptionContainsMarkup;
0048     mutable bool itemDescGuessed;
0049     QSharedPointer<Model::ModelPrivate> modelPrivate;
0050 };
0051 
0052 Document::Document()
0053     : Syndication::SpecificDocument()
0054     , ResourceWrapper()
0055     , d(new Private)
0056 {
0057     d->modelPrivate = resource()->model().d;
0058 }
0059 
0060 Document::Document(ResourcePtr resource)
0061     : Syndication::SpecificDocument()
0062     , ResourceWrapper(resource)
0063     , d(new Private)
0064 {
0065     d->modelPrivate = resource->model().d;
0066 }
0067 
0068 Document::Document(const Document &other)
0069     : SpecificDocument(other)
0070     , ResourceWrapper(other)
0071     , d(new Private)
0072 {
0073     *d = *(other.d);
0074 }
0075 
0076 Document::~Document() = default;
0077 
0078 bool Document::operator==(const Document &other) const
0079 {
0080     return ResourceWrapper::operator==(other);
0081 }
0082 
0083 Document &Document::operator=(const Document &other)
0084 {
0085     ResourceWrapper::operator=(other);
0086     *d = *(other.d);
0087 
0088     return *this;
0089 }
0090 
0091 bool Document::accept(DocumentVisitor *visitor)
0092 {
0093     return visitor->visitRDFDocument(this);
0094 }
0095 
0096 bool Document::isValid() const
0097 {
0098     return !isNull();
0099 }
0100 
0101 QString Document::title() const
0102 {
0103     const QString str = resource()->property(RSSVocab::self()->title())->asString();
0104     return normalize(str);
0105 }
0106 
0107 QString Document::description() const
0108 {
0109     const QString str = resource()->property(RSSVocab::self()->description())->asString();
0110     return normalize(str);
0111 }
0112 
0113 QString Document::link() const
0114 {
0115     return resource()->property(RSSVocab::self()->link())->asString();
0116 }
0117 
0118 DublinCore Document::dc() const
0119 {
0120     return DublinCore(resource());
0121 }
0122 
0123 SyndicationInfo Document::syn() const
0124 {
0125     return SyndicationInfo(resource());
0126 }
0127 
0128 struct SortItem {
0129     Item item;
0130     int index;
0131 };
0132 
0133 struct LessThanByIndex {
0134     bool operator()(const SortItem &lhs, const SortItem &rhs) const
0135     {
0136         return lhs.index < rhs.index;
0137     }
0138 };
0139 
0140 static QList<Item> sortListToMatchSequence(QList<Item> items, const QStringList &uriSequence)
0141 {
0142     QVector<SortItem> toSort;
0143     toSort.reserve(items.size());
0144     for (const Item &i : items) {
0145         SortItem item;
0146         item.item = i;
0147         item.index = uriSequence.indexOf(i.resource()->uri());
0148         toSort.append(item);
0149     }
0150     std::sort(toSort.begin(), toSort.end(), LessThanByIndex());
0151 
0152     int i = 0;
0153     for (const SortItem &sortItem : std::as_const(toSort)) {
0154         items[i] = sortItem.item;
0155         i++;
0156     }
0157 
0158     return items;
0159 }
0160 
0161 struct UriLessThan {
0162     bool operator()(const RDF::ResourcePtr &lhs, const RDF::ResourcePtr &rhs) const
0163     {
0164         return lhs->uri() < rhs->uri();
0165     }
0166 };
0167 
0168 QList<Item> Document::items() const
0169 {
0170     QList<ResourcePtr> items = resource()->model().resourcesWithType(RSSVocab::self()->item());
0171     // if there is no sequence, ensure sorting by URI to have a defined and deterministic order
0172     // important for unit tests
0173     std::sort(items.begin(), items.end(), UriLessThan());
0174 
0175     DocumentPtr doccpy(new Document(*this));
0176 
0177     QList<Item> list;
0178     list.reserve(items.count());
0179 
0180     for (const ResourcePtr &i : std::as_const(items)) {
0181         list.append(Item(i, doccpy));
0182     }
0183 
0184     if (resource()->hasProperty(RSSVocab::self()->items())) {
0185         NodePtr n = resource()->property(RSSVocab::self()->items())->object();
0186         if (n->isSequence()) {
0187             const SequencePtr seq = n.staticCast<Sequence>();
0188 
0189             const QList<NodePtr> seqItems = seq->items();
0190 
0191             QStringList uriSequence;
0192             uriSequence.reserve(seqItems.size());
0193 
0194             for (const NodePtr &i : seqItems) {
0195                 if (i->isResource()) {
0196                     uriSequence.append(i.staticCast<Resource>()->uri());
0197                 }
0198             }
0199             list = sortListToMatchSequence(list, uriSequence);
0200         }
0201     }
0202 
0203     return list;
0204 }
0205 
0206 Image Document::image() const
0207 {
0208     ResourcePtr img = resource()->property(RSSVocab::self()->image())->asResource();
0209 
0210     return img ? Image(img) : Image();
0211 }
0212 
0213 TextInput Document::textInput() const
0214 {
0215     ResourcePtr ti = resource()->property(RSSVocab::self()->textinput())->asResource();
0216 
0217     return ti ? TextInput(ti) : TextInput();
0218 }
0219 
0220 void Document::getItemTitleFormatInfo(bool *containsMarkup) const
0221 {
0222     if (!d->itemTitlesGuessed) {
0223         QString titles;
0224         QList<Item> litems = items();
0225 
0226         if (litems.isEmpty()) {
0227             d->itemTitlesGuessed = true;
0228             return;
0229         }
0230 
0231         const int nmax = std::min<int>(litems.size(), 10); // we check a maximum of 10 items
0232         int i = 0;
0233 
0234         for (const auto &item : litems) {
0235             if (i++ >= nmax) {
0236                 break;
0237             }
0238             titles += item.originalTitle();
0239         }
0240 
0241         d->itemTitleContainsMarkup = stringContainsMarkup(titles);
0242         d->itemTitlesGuessed = true;
0243     }
0244     if (containsMarkup != nullptr) {
0245         *containsMarkup = d->itemTitleContainsMarkup;
0246     }
0247 }
0248 
0249 void Document::getItemDescriptionFormatInfo(bool *containsMarkup) const
0250 {
0251     if (!d->itemDescGuessed) {
0252         QString desc;
0253         QList<Item> litems = items();
0254 
0255         if (litems.isEmpty()) {
0256             d->itemDescGuessed = true;
0257             return;
0258         }
0259 
0260         const int nmax = std::min<int>(litems.size(), 10); // we check a maximum of 10 items
0261         int i = 0;
0262 
0263         for (const auto &item : litems) {
0264             if (i++ >= nmax) {
0265                 break;
0266             }
0267             desc += item.originalDescription();
0268         }
0269 
0270         d->itemDescriptionContainsMarkup = stringContainsMarkup(desc);
0271         d->itemDescGuessed = true;
0272     }
0273 
0274     if (containsMarkup != nullptr) {
0275         *containsMarkup = d->itemDescriptionContainsMarkup;
0276     }
0277 }
0278 
0279 QString Document::debugInfo() const
0280 {
0281     QString info;
0282     info += QLatin1String("### Document: ###################\n");
0283     info += QLatin1String("title: #") + title() + QLatin1String("#\n");
0284     info += QLatin1String("link: #") + link() + QLatin1String("#\n");
0285     info += QLatin1String("description: #") + description() + QLatin1String("#\n");
0286     info += dc().debugInfo();
0287     info += syn().debugInfo();
0288     Image img = image();
0289     if (!img.resource() == 0L) {
0290         info += img.debugInfo();
0291     }
0292     TextInput input = textInput();
0293     if (!input.isNull()) {
0294         info += input.debugInfo();
0295     }
0296 
0297     const QList<Item> itlist = items();
0298     for (const auto &item : itlist) {
0299         info += item.debugInfo();
0300     }
0301 
0302     info += QLatin1String("### Document end ################\n");
0303     return info;
0304 }
0305 
0306 } // namespace RDF
0307 } // namespace Syndication