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