File indexing completed on 2024-10-06 12:27:16
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