1/*
2 This file is part of the syndication library
3 SPDX-FileCopyrightText: 2005 Frank Osterfeld <osterfeld@kde.org>
4
5 SPDX-License-Identifier: LGPL-2.0-or-later
6*/
7
8#include <constants.h>
9#include <rss2/category.h>
10#include <rss2/enclosure.h>
11#include <rss2/item.h>
12#include <rss2/source.h>
13#include <rss2/tools_p.h>
14#include <specificitem.h>
15#include <specificitemvisitor.h>
16#include <tools.h>
17
18#include <QDomElement>
19#include <QList>
20#include <QString>
21
22#include <vector>
23
24namespace Syndication
25{
26namespace RSS2
27{
28class SYNDICATION_NO_EXPORT Item::ItemPrivate
29{
30public:
31 QSharedPointer<Document> doc;
32};
33
34Item::Item(QSharedPointer<Document> doc)
35 : ElementWrapper()
36 , d(new ItemPrivate)
37{
38 d->doc = doc;
39}
40
41Item::Item(const QDomElement &element, QSharedPointer<Document> doc)
42 : ElementWrapper(element)
43 , d(new ItemPrivate)
44{
45 d->doc = doc;
46}
47
48Item::~Item()
49{
50}
51
52Item::Item(const Item &other)
53 : ElementWrapper(other)
54 , SpecificItem(other)
55{
56 d = other.d;
57}
58
59Item &Item::operator=(const Item &other)
60{
61 ElementWrapper::operator=(other);
62 SpecificItem::operator=(other);
63 d = other.d;
64 return *this;
65}
66
67QString Item::title() const
68{
69 if (!d->doc) {
70 return originalTitle();
71 }
72
73 bool isCDATA = false;
74 bool containsMarkup = false;
75 d->doc->getItemTitleFormatInfo(isCDATA: &isCDATA, containsMarkup: &containsMarkup);
76
77 return normalize(str: originalTitle(), isCDATA, containsMarkup);
78}
79
80QString Item::originalDescription() const
81{
82 return extractElementTextNS(namespaceURI: QString(), QStringLiteral("description"));
83}
84
85QString Item::originalTitle() const
86{
87 return extractElementTextNS(namespaceURI: QString(), QStringLiteral("title"));
88}
89
90QString Item::link() const
91{
92 QString url = extractElementTextNS(namespaceURI: QString(), QStringLiteral("link"));
93 if (url.startsWith(s: QLatin1String("http://")) || url.startsWith(s: QLatin1String("https://"))) {
94 return url;
95 }
96 if (url.isEmpty()) {
97 return QString();
98 }
99 if (d->doc->link().isEmpty()) {
100 return url;
101 }
102 // link does not look like a complete url, assume the feed author expects
103 // the doc link to provide the base of the url.
104 QString baseUrl = d->doc->link();
105 if (url.startsWith(c: QLatin1Char('/')) || baseUrl.endsWith(c: QLatin1Char('/'))) {
106 return baseUrl + url;
107 } else {
108 return baseUrl + QLatin1Char('/') + url;
109 }
110}
111
112QString Item::description() const
113{
114 if (!d->doc) {
115 return originalDescription();
116 }
117
118 bool isCDATA = false;
119 bool containsMarkup = false;
120 d->doc->getItemDescriptionFormatInfo(isCDATA: &isCDATA, containsMarkup: &containsMarkup);
121
122 return normalize(str: originalDescription(), isCDATA, containsMarkup);
123}
124
125QString Item::content() const
126{
127 // parse encoded stuff from content:encoded, xhtml:body and friends into content
128 return extractContent(parent: *this);
129}
130
131QList<Category> Item::categories() const
132{
133 const QList<QDomElement> cats = elementsByTagNameNS(nsURI: QString(), QStringLiteral("category"));
134
135 QList<Category> categories;
136 categories.reserve(asize: cats.count());
137
138 std::transform(first: cats.cbegin(), last: cats.cend(), result: std::back_inserter(x&: categories), unary_op: [](const QDomElement &element) {
139 return Category(element);
140 });
141
142 return categories;
143}
144
145QString Item::comments() const
146{
147 return extractElementTextNS(namespaceURI: QString(), QStringLiteral("comments"));
148}
149
150QString Item::author() const
151{
152 QString a = extractElementTextNS(namespaceURI: QString(), QStringLiteral("author"));
153 if (!a.isNull()) {
154 return a;
155 } else {
156 // if author is not available, fall back to dc:creator
157 return extractElementTextNS(namespaceURI: dublinCoreNamespace(), QStringLiteral("creator"));
158 }
159}
160
161QList<Enclosure> Item::enclosures() const
162{
163 const QList<QDomElement> encs = elementsByTagNameNS(nsURI: QString(), QStringLiteral("enclosure"));
164
165 QList<Enclosure> enclosures;
166 enclosures.reserve(asize: encs.count());
167
168 std::transform(first: encs.cbegin(), last: encs.cend(), result: std::back_inserter(x&: enclosures), unary_op: [](const QDomElement &element) {
169 return Enclosure(element);
170 });
171
172 return enclosures;
173}
174
175QString Item::guid() const
176{
177 return extractElementTextNS(namespaceURI: QString(), QStringLiteral("guid"));
178}
179
180bool Item::guidIsPermaLink() const
181{
182 bool guidIsPermaLink = true; // true is default
183
184 QDomElement guidNode = firstElementByTagNameNS(nsURI: QString(), QStringLiteral("guid"));
185 if (!guidNode.isNull()) {
186 if (guidNode.attribute(QStringLiteral("isPermaLink")) == QLatin1String("false")) {
187 guidIsPermaLink = false;
188 }
189 }
190
191 return guidIsPermaLink;
192}
193
194time_t Item::pubDate() const
195{
196 QString str = extractElementTextNS(namespaceURI: QString(), QStringLiteral("pubDate"));
197
198 if (!str.isNull()) {
199 return parseDate(str, hint: RFCDate);
200 }
201
202 // if there is no pubDate, check for dc:date
203 str = extractElementTextNS(namespaceURI: dublinCoreNamespace(), QStringLiteral("date"));
204 return parseDate(str, hint: ISODate);
205}
206
207time_t Item::expirationDate() const
208{
209 QString str = extractElementTextNS(namespaceURI: QString(), QStringLiteral("expirationDate"));
210 return parseDate(str, hint: RFCDate);
211}
212
213Source Item::source() const
214{
215 return Source(firstElementByTagNameNS(nsURI: QString(), QStringLiteral("source")));
216}
217
218QString Item::rating() const
219{
220 return extractElementTextNS(namespaceURI: QString(), QStringLiteral("rating"));
221}
222
223QString Item::debugInfo() const
224{
225 QString info = QLatin1String("### Item: ###################\n");
226 if (!title().isNull()) {
227 info += QLatin1String("title: #") + title() + QLatin1String("#\n");
228 }
229 if (!link().isNull()) {
230 info += QLatin1String("link: #") + link() + QLatin1String("#\n");
231 }
232 if (!description().isNull()) {
233 info += QLatin1String("description: #") + description() + QLatin1String("#\n");
234 }
235 if (!content().isNull()) {
236 info += QLatin1String("content: #") + content() + QLatin1String("#\n");
237 }
238 if (!author().isNull()) {
239 info += QLatin1String("author: #") + author() + QLatin1String("#\n");
240 }
241 if (!comments().isNull()) {
242 info += QLatin1String("comments: #") + comments() + QLatin1String("#\n");
243 }
244 QString dpubdate = dateTimeToString(date: pubDate());
245 if (!dpubdate.isNull()) {
246 info += QLatin1String("pubDate: #") + dpubdate + QLatin1String("#\n");
247 }
248 if (!guid().isNull()) {
249 info += QLatin1String("guid: #") + guid() + QLatin1String("#\n");
250 }
251 if (guidIsPermaLink()) {
252 info += QLatin1String("guid is PL: #true#\n");
253 }
254 if (!source().isNull()) {
255 info += source().debugInfo();
256 }
257
258 const QList<Category> cats = categories();
259 for (const auto &c : cats) {
260 info += c.debugInfo();
261 }
262
263 const QList<Enclosure> encs = enclosures();
264 for (const auto &e : encs) {
265 info += e.debugInfo();
266 }
267
268 info += QLatin1String("### Item end ################\n");
269 return info;
270}
271
272QList<QDomElement> Item::unhandledElements() const
273{
274 // TODO: do not hardcode this list here
275 static std::vector<ElementType> handled; // QVector would require a default ctor, and ElementType is too big for QList
276 if (handled.empty()) {
277 handled.reserve(n: 11);
278 handled.push_back(x: ElementType(QStringLiteral("title")));
279 handled.push_back(x: ElementType(QStringLiteral("link")));
280 handled.push_back(x: ElementType(QStringLiteral("description")));
281 handled.push_back(x: ElementType(QStringLiteral("pubDate")));
282 handled.push_back(x: ElementType(QStringLiteral("expirationDate")));
283 handled.push_back(x: ElementType(QStringLiteral("rating")));
284 handled.push_back(x: ElementType(QStringLiteral("source")));
285 handled.push_back(x: ElementType(QStringLiteral("guid")));
286 handled.push_back(x: ElementType(QStringLiteral("comments")));
287 handled.push_back(x: ElementType(QStringLiteral("author")));
288 handled.push_back(x: ElementType(QStringLiteral("date"), dublinCoreNamespace()));
289 }
290
291 QList<QDomElement> notHandled;
292
293 QDomNodeList children = element().childNodes();
294 const int numChildren = children.size();
295 for (int i = 0; i < numChildren; ++i) {
296 QDomElement el = children.at(index: i).toElement();
297 if (!el.isNull() //
298 && std::find(first: handled.cbegin(), last: handled.cend(), val: ElementType(el.localName(), el.namespaceURI())) == handled.cend()) {
299 notHandled.append(t: el);
300 }
301 }
302
303 return notHandled;
304}
305
306bool Item::accept(SpecificItemVisitor *visitor)
307{
308 return visitor->visitRSS2Item(item: this);
309}
310
311} // namespace RSS2
312} // namespace Syndication
313

source code of syndication/src/rss2/item.cpp