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 | |
24 | namespace Syndication |
25 | { |
26 | namespace RSS2 |
27 | { |
28 | class SYNDICATION_NO_EXPORT Item:: |
29 | { |
30 | public: |
31 | QSharedPointer<Document> ; |
32 | }; |
33 | |
34 | Item::(QSharedPointer<Document> doc) |
35 | : ElementWrapper() |
36 | , d(new ItemPrivate) |
37 | { |
38 | d->doc = doc; |
39 | } |
40 | |
41 | Item::(const QDomElement &element, QSharedPointer<Document> doc) |
42 | : ElementWrapper(element) |
43 | , d(new ItemPrivate) |
44 | { |
45 | d->doc = doc; |
46 | } |
47 | |
48 | Item::() |
49 | { |
50 | } |
51 | |
52 | Item::(const Item &other) |
53 | : ElementWrapper(other) |
54 | , SpecificItem(other) |
55 | { |
56 | d = other.d; |
57 | } |
58 | |
59 | Item &Item::(const Item &other) |
60 | { |
61 | ElementWrapper::operator=(other); |
62 | SpecificItem::operator=(other); |
63 | d = other.d; |
64 | return *this; |
65 | } |
66 | |
67 | QString Item::() 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 | |
80 | QString Item::() const |
81 | { |
82 | return extractElementTextNS(namespaceURI: QString(), QStringLiteral("description" )); |
83 | } |
84 | |
85 | QString Item::() const |
86 | { |
87 | return extractElementTextNS(namespaceURI: QString(), QStringLiteral("title" )); |
88 | } |
89 | |
90 | QString Item::() 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 | |
112 | QString Item::() 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 | |
125 | QString Item::() const |
126 | { |
127 | // parse encoded stuff from content:encoded, xhtml:body and friends into content |
128 | return extractContent(parent: *this); |
129 | } |
130 | |
131 | QList<Category> Item::() 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 | |
145 | QString Item::() const |
146 | { |
147 | return extractElementTextNS(namespaceURI: QString(), QStringLiteral("comments" )); |
148 | } |
149 | |
150 | QString Item::() 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 | |
161 | QList<Enclosure> Item::() 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 | |
175 | QString Item::() const |
176 | { |
177 | return extractElementTextNS(namespaceURI: QString(), QStringLiteral("guid" )); |
178 | } |
179 | |
180 | bool Item::() 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 | |
194 | time_t Item::() 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 | |
207 | time_t Item::() const |
208 | { |
209 | QString str = extractElementTextNS(namespaceURI: QString(), QStringLiteral("expirationDate" )); |
210 | return parseDate(str, hint: RFCDate); |
211 | } |
212 | |
213 | Source Item::() const |
214 | { |
215 | return Source(firstElementByTagNameNS(nsURI: QString(), QStringLiteral("source" ))); |
216 | } |
217 | |
218 | QString Item::() const |
219 | { |
220 | return extractElementTextNS(namespaceURI: QString(), QStringLiteral("rating" )); |
221 | } |
222 | |
223 | QString Item::() 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 | |
272 | QList<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 | |
306 | bool Item::(SpecificItemVisitor *visitor) |
307 | { |
308 | return visitor->visitRSS2Item(item: this); |
309 | } |
310 | |
311 | } // namespace RSS2 |
312 | } // namespace Syndication |
313 | |