File indexing completed on 2024-04-28 15:34:24

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