1 /*
2     This file is part of KNewStuff2.
3     SPDX-FileCopyrightText: 2002 Cornelius Schumacher <schumacher@kde.org>
4     SPDX-FileCopyrightText: 2003-2007 Josef Spillner <spillner@kde.org>
5     SPDX-FileCopyrightText: 2009 Frederik Gladhorn <gladhorn@kde.org>
6 
7     SPDX-License-Identifier: LGPL-2.1-or-later
8 */
9 
10 #include "entryinternal.h"
11 
12 #include <QStringList>
13 #include <QXmlStreamReader>
14 #include <knewstuffcore_debug.h>
15 
16 #include "../entry_p.h" // For Entry::Status ONLY!
17 #include "xmlloader.h"
18 
19 using namespace KNSCore;
20 
21 class EntryInternal::Private : public QSharedData
22 {
23 public:
Private()24     Private()
25         : mReleaseDate(QDate::currentDate())
26         , mRating(0)
27         , mNumberOfComments(0)
28         , mDownloadCount(0)
29         , mNumberFans(0)
30         , mNumberKnowledgebaseEntries(0)
31         , mStatus(KNS3::Entry::Invalid)
32         , mSource(EntryInternal::Online)
33         , mEntryType(EntryInternal::CatalogEntry)
34     {
35         qRegisterMetaType<KNSCore::EntryInternal::List>();
36     }
37 
operator ==(const Private & other) const38     bool operator==(const Private &other) const
39     {
40         return mUniqueId == other.mUniqueId && mProviderId == other.mProviderId;
41     }
42 
43     QString mUniqueId;
44     QString mName;
45     QUrl mHomepage;
46     QString mCategory;
47     QString mLicense;
48     QString mVersion;
49     QDate mReleaseDate;
50 
51     // Version and date if a newer version is found (updateable)
52     QString mUpdateVersion;
53     QDate mUpdateReleaseDate;
54 
55     Author mAuthor;
56     int mRating;
57     int mNumberOfComments;
58     int mDownloadCount;
59     int mNumberFans;
60     int mNumberKnowledgebaseEntries;
61     QString mKnowledgebaseLink;
62     QString mSummary;
63     QString mShortSummary;
64     QString mChangelog;
65     QString mPayload;
66     QStringList mInstalledFiles;
67     QString mProviderId;
68     QStringList mUnInstalledFiles;
69     QString mDonationLink;
70     QStringList mTags;
71 
72     QString mChecksum;
73     QString mSignature;
74     KNS3::Entry::Status mStatus;
75     EntryInternal::Source mSource;
76     EntryInternal::EntryType mEntryType;
77 
78     QString mPreviewUrl[6];
79     QImage mPreviewImage[6];
80 
81     QList<EntryInternal::DownloadLinkInformation> mDownloadLinkInformationList;
82 };
83 
EntryInternal()84 EntryInternal::EntryInternal()
85     : d(new Private)
86 {
87 }
88 
EntryInternal(const EntryInternal & other)89 EntryInternal::EntryInternal(const EntryInternal &other)
90     : d(other.d)
91 {
92 }
93 
operator =(const EntryInternal & other)94 EntryInternal &EntryInternal::operator=(const EntryInternal &other)
95 {
96     d = other.d;
97     return *this;
98 }
99 
operator <(const KNSCore::EntryInternal & other) const100 bool EntryInternal::operator<(const KNSCore::EntryInternal &other) const
101 {
102     return d->mUniqueId < other.d->mUniqueId;
103 }
104 
operator ==(const KNSCore::EntryInternal & other) const105 bool EntryInternal::operator==(const KNSCore::EntryInternal &other) const
106 {
107     return d->mUniqueId == other.d->mUniqueId && d->mProviderId == other.d->mProviderId;
108 }
109 
~EntryInternal()110 EntryInternal::~EntryInternal()
111 {
112 }
113 
isValid() const114 bool EntryInternal::isValid() const
115 {
116     return !d->mUniqueId.isEmpty();
117 }
118 
name() const119 QString EntryInternal::name() const
120 {
121     return d->mName;
122 }
123 
setName(const QString & name)124 void EntryInternal::setName(const QString &name)
125 {
126     d->mName = name;
127 }
128 
uniqueId() const129 QString EntryInternal::uniqueId() const
130 {
131     return d->mUniqueId;
132 }
133 
setUniqueId(const QString & id)134 void EntryInternal::setUniqueId(const QString &id)
135 {
136     d->mUniqueId = id;
137 }
138 
providerId() const139 QString EntryInternal::providerId() const
140 {
141     return d->mProviderId;
142 }
143 
setProviderId(const QString & id)144 void EntryInternal::setProviderId(const QString &id)
145 {
146     d->mProviderId = id;
147 }
148 
tags() const149 QStringList KNSCore::EntryInternal::tags() const
150 {
151     return d->mTags;
152 }
153 
setTags(const QStringList & tags)154 void KNSCore::EntryInternal::setTags(const QStringList &tags)
155 {
156     d->mTags = tags;
157 }
158 
category() const159 QString EntryInternal::category() const
160 {
161     return d->mCategory;
162 }
163 
setCategory(const QString & category)164 void EntryInternal::setCategory(const QString &category)
165 {
166     d->mCategory = category;
167 }
168 
homepage() const169 QUrl EntryInternal::homepage() const
170 {
171     return d->mHomepage;
172 }
173 
setHomepage(const QUrl & page)174 void EntryInternal::setHomepage(const QUrl &page)
175 {
176     d->mHomepage = page;
177 }
178 
author() const179 Author EntryInternal::author() const
180 {
181     return d->mAuthor;
182 }
183 
setAuthor(const KNSCore::Author & author)184 void EntryInternal::setAuthor(const KNSCore::Author &author)
185 {
186     d->mAuthor = author;
187 }
188 
license() const189 QString EntryInternal::license() const
190 {
191     return d->mLicense;
192 }
193 
setLicense(const QString & license)194 void EntryInternal::setLicense(const QString &license)
195 {
196     d->mLicense = license;
197 }
198 
summary() const199 QString EntryInternal::summary() const
200 {
201     return d->mSummary;
202 }
203 
setSummary(const QString & summary)204 void EntryInternal::setSummary(const QString &summary)
205 {
206     d->mSummary = summary;
207 }
208 
shortSummary() const209 QString EntryInternal::shortSummary() const
210 {
211     return d->mShortSummary;
212 }
213 
setShortSummary(const QString & summary)214 void EntryInternal::setShortSummary(const QString &summary)
215 {
216     d->mShortSummary = summary;
217 }
218 
setChangelog(const QString & changelog)219 void EntryInternal::setChangelog(const QString &changelog)
220 {
221     d->mChangelog = changelog;
222 }
223 
changelog() const224 QString EntryInternal::changelog() const
225 {
226     return d->mChangelog;
227 }
228 
version() const229 QString EntryInternal::version() const
230 {
231     return d->mVersion;
232 }
233 
setVersion(const QString & version)234 void EntryInternal::setVersion(const QString &version)
235 {
236     d->mVersion = version;
237 }
238 
releaseDate() const239 QDate EntryInternal::releaseDate() const
240 {
241     return d->mReleaseDate;
242 }
243 
setReleaseDate(const QDate & releasedate)244 void EntryInternal::setReleaseDate(const QDate &releasedate)
245 {
246     d->mReleaseDate = releasedate;
247 }
248 
payload() const249 QString EntryInternal::payload() const
250 {
251     return d->mPayload;
252 }
253 
setPayload(const QString & url)254 void EntryInternal::setPayload(const QString &url)
255 {
256     d->mPayload = url;
257 }
258 
updateReleaseDate() const259 QDate EntryInternal::updateReleaseDate() const
260 {
261     return d->mUpdateReleaseDate;
262 }
263 
setUpdateReleaseDate(const QDate & releasedate)264 void EntryInternal::setUpdateReleaseDate(const QDate &releasedate)
265 {
266     d->mUpdateReleaseDate = releasedate;
267 }
268 
updateVersion() const269 QString EntryInternal::updateVersion() const
270 {
271     return d->mUpdateVersion;
272 }
273 
setUpdateVersion(const QString & version)274 void EntryInternal::setUpdateVersion(const QString &version)
275 {
276     d->mUpdateVersion = version;
277 }
278 
previewUrl(PreviewType type) const279 QString EntryInternal::previewUrl(PreviewType type) const
280 {
281     return d->mPreviewUrl[type];
282 }
283 
setPreviewUrl(const QString & url,PreviewType type)284 void EntryInternal::setPreviewUrl(const QString &url, PreviewType type)
285 {
286     d->mPreviewUrl[type] = url;
287 }
288 
previewImage(PreviewType type) const289 QImage EntryInternal::previewImage(PreviewType type) const
290 {
291     return d->mPreviewImage[type];
292 }
293 
setPreviewImage(const QImage & image,PreviewType type)294 void EntryInternal::setPreviewImage(const QImage &image, PreviewType type)
295 {
296     d->mPreviewImage[type] = image;
297 }
298 
rating() const299 int EntryInternal::rating() const
300 {
301     return d->mRating;
302 }
303 
setRating(int rating)304 void EntryInternal::setRating(int rating)
305 {
306     d->mRating = rating;
307 }
308 
numberOfComments() const309 int EntryInternal::numberOfComments() const
310 {
311     return d->mNumberOfComments;
312 }
313 
setNumberOfComments(int comments)314 void EntryInternal::setNumberOfComments(int comments)
315 {
316     d->mNumberOfComments = comments;
317 }
318 
downloadCount() const319 int EntryInternal::downloadCount() const
320 {
321     return d->mDownloadCount;
322 }
323 
setDownloadCount(int downloads)324 void EntryInternal::setDownloadCount(int downloads)
325 {
326     d->mDownloadCount = downloads;
327 }
328 
numberFans() const329 int EntryInternal::numberFans() const
330 {
331     return d->mNumberFans;
332 }
333 
setNumberFans(int fans)334 void EntryInternal::setNumberFans(int fans)
335 {
336     d->mNumberFans = fans;
337 }
338 
donationLink() const339 QString EntryInternal::donationLink() const
340 {
341     return d->mDonationLink;
342 }
343 
setDonationLink(const QString & link)344 void EntryInternal::setDonationLink(const QString &link)
345 {
346     d->mDonationLink = link;
347 }
348 
numberKnowledgebaseEntries() const349 int EntryInternal::numberKnowledgebaseEntries() const
350 {
351     return d->mNumberKnowledgebaseEntries;
352 }
setNumberKnowledgebaseEntries(int num)353 void EntryInternal::setNumberKnowledgebaseEntries(int num)
354 {
355     d->mNumberKnowledgebaseEntries = num;
356 }
357 
knowledgebaseLink() const358 QString EntryInternal::knowledgebaseLink() const
359 {
360     return d->mKnowledgebaseLink;
361 }
setKnowledgebaseLink(const QString & link)362 void EntryInternal::setKnowledgebaseLink(const QString &link)
363 {
364     d->mKnowledgebaseLink = link;
365 }
366 
source() const367 EntryInternal::Source EntryInternal::source() const
368 {
369     return d->mSource;
370 }
371 
setEntryType(EntryInternal::EntryType type)372 void EntryInternal::setEntryType(EntryInternal::EntryType type)
373 {
374     d->mEntryType = type;
375 }
376 
entryType() const377 EntryInternal::EntryType EntryInternal::entryType() const
378 {
379     return d->mEntryType;
380 }
381 
setSource(Source source)382 void EntryInternal::setSource(Source source)
383 {
384     d->mSource = source;
385 }
386 
status() const387 KNS3::Entry::Status EntryInternal::status() const
388 {
389     return d->mStatus;
390 }
391 
setStatus(KNS3::Entry::Status status)392 void EntryInternal::setStatus(KNS3::Entry::Status status)
393 {
394     d->mStatus = status;
395 }
396 
setInstalledFiles(const QStringList & files)397 void KNSCore::EntryInternal::setInstalledFiles(const QStringList &files)
398 {
399     d->mInstalledFiles = files;
400 }
401 
installedFiles() const402 QStringList KNSCore::EntryInternal::installedFiles() const
403 {
404     return d->mInstalledFiles;
405 }
406 
setUnInstalledFiles(const QStringList & files)407 void KNSCore::EntryInternal::setUnInstalledFiles(const QStringList &files)
408 {
409     d->mUnInstalledFiles = files;
410 }
411 
uninstalledFiles() const412 QStringList KNSCore::EntryInternal::uninstalledFiles() const
413 {
414     return d->mUnInstalledFiles;
415 }
416 
downloadLinkCount() const417 int KNSCore::EntryInternal::downloadLinkCount() const
418 {
419     return d->mDownloadLinkInformationList.size();
420 }
421 
downloadLinkInformationList() const422 QList<KNSCore::EntryInternal::DownloadLinkInformation> KNSCore::EntryInternal::downloadLinkInformationList() const
423 {
424     return d->mDownloadLinkInformationList;
425 }
426 
appendDownloadLinkInformation(const KNSCore::EntryInternal::DownloadLinkInformation & info)427 void KNSCore::EntryInternal::appendDownloadLinkInformation(const KNSCore::EntryInternal::DownloadLinkInformation &info)
428 {
429     d->mDownloadLinkInformationList.append(info);
430 }
431 
clearDownloadLinkInformation()432 void EntryInternal::clearDownloadLinkInformation()
433 {
434     d->mDownloadLinkInformationList.clear();
435 }
436 
readNextSkipComments(QXmlStreamReader * xml)437 static QXmlStreamReader::TokenType readNextSkipComments(QXmlStreamReader *xml)
438 {
439     do {
440         xml->readNext();
441     } while (xml->tokenType() == QXmlStreamReader::Comment || (xml->tokenType() == QXmlStreamReader::Characters && xml->text().trimmed().isEmpty()));
442     return xml->tokenType();
443 }
444 
readText(QXmlStreamReader * xml)445 static QString readText(QXmlStreamReader *xml)
446 {
447     Q_ASSERT(xml->tokenType() == QXmlStreamReader::StartElement);
448     QString ret;
449     const auto token = readNextSkipComments(xml);
450     if (token == QXmlStreamReader::Characters) {
451         ret = xml->text().toString();
452     }
453     return ret;
454 }
455 
readStringTrimmed(QXmlStreamReader * xml)456 static QString readStringTrimmed(QXmlStreamReader *xml)
457 {
458     Q_ASSERT(xml->tokenType() == QXmlStreamReader::StartElement);
459     QString ret = readText(xml).trimmed();
460 
461     if (xml->tokenType() == QXmlStreamReader::Characters) {
462         readNextSkipComments(xml);
463     }
464     Q_ASSERT(xml->tokenType() == QXmlStreamReader::EndElement);
465     return ret;
466 }
467 
readInt(QXmlStreamReader * xml)468 static int readInt(QXmlStreamReader *xml)
469 {
470     Q_ASSERT(xml->tokenType() == QXmlStreamReader::StartElement);
471     int ret = readText(xml).toInt();
472 
473     xml->readNext();
474     Q_ASSERT(xml->tokenType() == QXmlStreamReader::EndElement);
475     return ret;
476 }
477 
setEntryXML(QXmlStreamReader & reader)478 bool KNSCore::EntryInternal::setEntryXML(QXmlStreamReader &reader)
479 {
480     if (reader.name() != QLatin1String("stuff")) {
481         qCWarning(KNEWSTUFFCORE) << "Parsing Entry from invalid XML. Reader tag name was expected to be \"stuff\", but was found as:" << reader.name();
482         return false;
483     }
484 
485     d->mCategory = reader.attributes().value(QStringLiteral("category")).toString();
486 
487     while (!reader.atEnd()) {
488         const auto token = readNextSkipComments(&reader);
489         if (token == QXmlStreamReader::EndElement) {
490             break;
491         } else if (token != QXmlStreamReader::StartElement) {
492             continue;
493         }
494 
495         if (reader.name() == QLatin1String("name")) {
496             // TODO maybe do something with the language attribute? QString lang = e.attribute("lang");
497             d->mName = reader.readElementText(QXmlStreamReader::SkipChildElements);
498         } else if (reader.name() == QLatin1String("author")) {
499             const auto email = reader.attributes().value(QStringLiteral("email"));
500             const auto jabber = reader.attributes().value(QStringLiteral("im"));
501             const auto homepage = reader.attributes().value(QStringLiteral("homepage"));
502             d->mAuthor.setName(readStringTrimmed(&reader));
503             d->mAuthor.setEmail(email.toString());
504             d->mAuthor.setJabber(jabber.toString());
505             d->mAuthor.setHomepage(homepage.toString());
506         } else if (reader.name() == QLatin1String("providerid")) {
507             d->mProviderId = reader.readElementText(QXmlStreamReader::SkipChildElements);
508         } else if (reader.name() == QLatin1String("homepage")) {
509             d->mHomepage = QUrl(reader.readElementText(QXmlStreamReader::SkipChildElements));
510         } else if (reader.name() == QLatin1String("licence")) { // krazy:exclude=spelling
511             d->mLicense = readStringTrimmed(&reader);
512         } else if (reader.name() == QLatin1String("summary")) {
513             d->mSummary = reader.readElementText(QXmlStreamReader::SkipChildElements);
514         } else if (reader.name() == QLatin1String("changelog")) {
515             d->mChangelog = reader.readElementText(QXmlStreamReader::SkipChildElements);
516         } else if (reader.name() == QLatin1String("version")) {
517             d->mVersion = readStringTrimmed(&reader);
518         } else if (reader.name() == QLatin1String("releasedate")) {
519             d->mReleaseDate = QDate::fromString(readStringTrimmed(&reader), Qt::ISODate);
520         } else if (reader.name() == QLatin1String("preview")) {
521             // TODO support for all 6 image links
522             d->mPreviewUrl[PreviewSmall1] = readStringTrimmed(&reader);
523         } else if (reader.name() == QLatin1String("previewBig")) {
524             d->mPreviewUrl[PreviewBig1] = readStringTrimmed(&reader);
525         } else if (reader.name() == QLatin1String("payload")) {
526             d->mPayload = readStringTrimmed(&reader);
527         } else if (reader.name() == QLatin1String("rating")) {
528             d->mRating = readInt(&reader);
529         } else if (reader.name() == QLatin1String("downloads")) {
530             d->mDownloadCount = readInt(&reader);
531         } else if (reader.name() == QLatin1String("category")) {
532             d->mCategory = reader.readElementText(QXmlStreamReader::SkipChildElements);
533         } else if (reader.name() == QLatin1String("signature")) {
534             d->mSignature = reader.readElementText(QXmlStreamReader::SkipChildElements);
535         } else if (reader.name() == QLatin1String("checksum")) {
536             d->mChecksum = reader.readElementText(QXmlStreamReader::SkipChildElements);
537         } else if (reader.name() == QLatin1String("installedfile")) {
538             d->mInstalledFiles.append(reader.readElementText(QXmlStreamReader::SkipChildElements));
539         } else if (reader.name() == QLatin1String("id")) {
540             d->mUniqueId = reader.readElementText(QXmlStreamReader::SkipChildElements);
541         } else if (reader.name() == QLatin1String("tags")) {
542             d->mTags = reader.readElementText(QXmlStreamReader::SkipChildElements).split(QLatin1Char(','));
543         } else if (reader.name() == QLatin1String("status")) {
544             const auto statusText = readText(&reader);
545             if (statusText == QLatin1String("installed")) {
546                 qCDebug(KNEWSTUFFCORE) << "Found an installed entry in registry";
547                 d->mStatus = KNS3::Entry::Installed;
548             } else if (statusText == QLatin1String("updateable")) {
549                 d->mStatus = KNS3::Entry::Updateable;
550             }
551             if (reader.tokenType() == QXmlStreamReader::Characters) {
552                 readNextSkipComments(&reader);
553             }
554         }
555         Q_ASSERT_X(reader.tokenType() == QXmlStreamReader::EndElement,
556                    Q_FUNC_INFO,
557                    QStringLiteral("token name was %1 and the type was %2").arg(reader.name().toString(), reader.tokenString()).toLocal8Bit().data());
558     }
559 
560     // Validation
561     if (d->mName.isEmpty()) {
562         qWarning() << "Entry: no name given";
563         return false;
564     }
565 
566     if (d->mUniqueId.isEmpty()) {
567         if (!d->mPayload.isEmpty()) {
568             d->mUniqueId = d->mPayload;
569         } else {
570             d->mUniqueId = d->mName;
571         }
572     }
573 
574     if (d->mPayload.isEmpty()) {
575         qWarning() << "Entry: no payload URL given for: " << d->mName << " - " << d->mUniqueId;
576         return false;
577     }
578     return true;
579 }
580 
setEntryXML(const QDomElement & xmldata)581 bool KNSCore::EntryInternal::setEntryXML(const QDomElement &xmldata)
582 {
583     if (xmldata.tagName() != QLatin1String("stuff")) {
584         qWarning() << "Parsing Entry from invalid XML";
585         return false;
586     }
587 
588     d->mCategory = xmldata.attribute(QStringLiteral("category"));
589 
590     QDomNode n;
591     for (n = xmldata.firstChild(); !n.isNull(); n = n.nextSibling()) {
592         QDomElement e = n.toElement();
593         if (e.tagName() == QLatin1String("name")) {
594             // TODO maybe do something with the language attribute? QString lang = e.attribute("lang");
595             d->mName = e.text().trimmed();
596         } else if (e.tagName() == QLatin1String("author")) {
597             QString email = e.attribute(QStringLiteral("email"));
598             QString jabber = e.attribute(QStringLiteral("im"));
599             QString homepage = e.attribute(QStringLiteral("homepage"));
600             d->mAuthor.setName(e.text().trimmed());
601             d->mAuthor.setEmail(email);
602             d->mAuthor.setJabber(jabber);
603             d->mAuthor.setHomepage(homepage);
604         } else if (e.tagName() == QLatin1String("providerid")) {
605             d->mProviderId = e.text();
606         } else if (e.tagName() == QLatin1String("homepage")) {
607             d->mHomepage = QUrl(e.text());
608         } else if (e.tagName() == QLatin1String("licence")) { // krazy:exclude=spelling
609             d->mLicense = e.text().trimmed();
610         } else if (e.tagName() == QLatin1String("summary")) {
611             d->mSummary = e.text();
612         } else if (e.tagName() == QLatin1String("changelog")) {
613             d->mChangelog = e.text();
614         } else if (e.tagName() == QLatin1String("version")) {
615             d->mVersion = e.text().trimmed();
616         } else if (e.tagName() == QLatin1String("releasedate")) {
617             d->mReleaseDate = QDate::fromString(e.text().trimmed(), Qt::ISODate);
618         } else if (e.tagName() == QLatin1String("preview")) {
619             // TODO support for all 6 image links
620             d->mPreviewUrl[PreviewSmall1] = e.text().trimmed();
621         } else if (e.tagName() == QLatin1String("previewBig")) {
622             d->mPreviewUrl[PreviewBig1] = e.text().trimmed();
623         } else if (e.tagName() == QLatin1String("payload")) {
624             d->mPayload = e.text().trimmed();
625         } else if (e.tagName() == QLatin1String("rating")) {
626             d->mRating = e.text().toInt();
627         } else if (e.tagName() == QLatin1String("downloads")) {
628             d->mDownloadCount = e.text().toInt();
629         } else if (e.tagName() == QLatin1String("category")) {
630             d->mCategory = e.text();
631         } else if (e.tagName() == QLatin1String("signature")) {
632             d->mSignature = e.text();
633         } else if (e.tagName() == QLatin1String("checksum")) {
634             d->mChecksum = e.text();
635         } else if (e.tagName() == QLatin1String("installedfile")) {
636             // TODO KF6 Add a "installeddirectory" entry to avoid
637             // all the issues with the "/*" notation which is currently used as a workaround
638             d->mInstalledFiles.append(e.text());
639         } else if (e.tagName() == QLatin1String("id")) {
640             d->mUniqueId = e.text();
641         } else if (e.tagName() == QLatin1String("tags")) {
642             d->mTags = e.text().split(QLatin1Char(','));
643         } else if (e.tagName() == QLatin1String("status")) {
644             QString statusText = e.text();
645             if (statusText == QLatin1String("installed")) {
646                 qCDebug(KNEWSTUFFCORE) << "Found an installed entry in registry";
647                 d->mStatus = KNS3::Entry::Installed;
648             } else if (statusText == QLatin1String("updateable")) {
649                 d->mStatus = KNS3::Entry::Updateable;
650             }
651         }
652     }
653 
654     // Validation
655     if (d->mName.isEmpty()) {
656         qWarning() << "Entry: no name given";
657         return false;
658     }
659 
660     if (d->mUniqueId.isEmpty()) {
661         if (!d->mPayload.isEmpty()) {
662             d->mUniqueId = d->mPayload;
663         } else {
664             d->mUniqueId = d->mName;
665         }
666     }
667 
668     if (d->mPayload.isEmpty()) {
669         qWarning() << "Entry: no payload URL given for: " << d->mName << " - " << d->mUniqueId;
670         return false;
671     }
672     return true;
673 }
674 
675 /**
676  * get the xml string for the entry
677  */
entryXML() const678 QDomElement KNSCore::EntryInternal::entryXML() const
679 {
680     Q_ASSERT(!d->mUniqueId.isEmpty());
681     Q_ASSERT(!d->mProviderId.isEmpty());
682 
683     QDomDocument doc;
684 
685     QDomElement el = doc.createElement(QStringLiteral("stuff"));
686     el.setAttribute(QStringLiteral("category"), d->mCategory);
687 
688     QString name = d->mName;
689 
690     QDomElement e;
691     e = addElement(doc, el, QStringLiteral("name"), name);
692     // todo: add language attribute
693     (void)addElement(doc, el, QStringLiteral("providerid"), d->mProviderId);
694 
695     QDomElement author = addElement(doc, el, QStringLiteral("author"), d->mAuthor.name());
696     if (!d->mAuthor.email().isEmpty()) {
697         author.setAttribute(QStringLiteral("email"), d->mAuthor.email());
698     }
699     if (!d->mAuthor.homepage().isEmpty()) {
700         author.setAttribute(QStringLiteral("homepage"), d->mAuthor.homepage());
701     }
702     if (!d->mAuthor.jabber().isEmpty()) {
703         author.setAttribute(QStringLiteral("im"), d->mAuthor.jabber());
704     }
705     // FIXME: 'jabber' or 'im'? consult with kopete guys...
706     addElement(doc, el, QStringLiteral("homepage"), d->mHomepage.url());
707     (void)addElement(doc, el, QStringLiteral("licence"), d->mLicense); // krazy:exclude=spelling
708     (void)addElement(doc, el, QStringLiteral("version"), d->mVersion);
709     if ((d->mRating > 0) || (d->mDownloadCount > 0)) {
710         (void)addElement(doc, el, QStringLiteral("rating"), QString::number(d->mRating));
711         (void)addElement(doc, el, QStringLiteral("downloads"), QString::number(d->mDownloadCount));
712     }
713     if (!d->mSignature.isEmpty()) {
714         (void)addElement(doc, el, QStringLiteral("signature"), d->mSignature);
715     }
716     if (!d->mChecksum.isEmpty()) {
717         (void)addElement(doc, el, QStringLiteral("checksum"), d->mChecksum);
718     }
719     for (const QString &file : std::as_const(d->mInstalledFiles)) {
720         (void)addElement(doc, el, QStringLiteral("installedfile"), file);
721     }
722     if (!d->mUniqueId.isEmpty()) {
723         addElement(doc, el, QStringLiteral("id"), d->mUniqueId);
724     }
725 
726     (void)addElement(doc, el, QStringLiteral("releasedate"), d->mReleaseDate.toString(Qt::ISODate));
727 
728     e = addElement(doc, el, QStringLiteral("summary"), d->mSummary);
729     e = addElement(doc, el, QStringLiteral("changelog"), d->mChangelog);
730     e = addElement(doc, el, QStringLiteral("preview"), d->mPreviewUrl[PreviewSmall1]);
731     e = addElement(doc, el, QStringLiteral("previewBig"), d->mPreviewUrl[PreviewBig1]);
732     e = addElement(doc, el, QStringLiteral("payload"), d->mPayload);
733     e = addElement(doc, el, QStringLiteral("tags"), d->mTags.join(QLatin1Char(',')));
734 
735     if (d->mStatus == KNS3::Entry::Installed) {
736         (void)addElement(doc, el, QStringLiteral("status"), QStringLiteral("installed"));
737     }
738     if (d->mStatus == KNS3::Entry::Updateable) {
739         (void)addElement(doc, el, QStringLiteral("status"), QStringLiteral("updateable"));
740     }
741 
742     return el;
743 }
744 
fromEntry(const KNS3::Entry & entry)745 KNSCore::EntryInternal EntryInternal::fromEntry(const KNS3::Entry &entry)
746 {
747     return entry.d->e;
748 }
749 
replaceBBCode(const QString & unformattedText)750 QString KNSCore::replaceBBCode(const QString &unformattedText)
751 {
752     QString text(unformattedText);
753     text.replace(QLatin1String("[b]"), QLatin1String("<b>"));
754     text.replace(QLatin1String("[/b]"), QLatin1String("</b>"));
755     text.replace(QLatin1String("[i]"), QLatin1String("<i>"));
756     text.replace(QLatin1String("[/i]"), QLatin1String("</i>"));
757     text.replace(QLatin1String("[u]"), QLatin1String("<i>"));
758     text.replace(QLatin1String("[/u]"), QLatin1String("</i>"));
759     text.replace(QLatin1String("\\\""), QLatin1String("\""));
760     text.replace(QLatin1String("\\\'"), QLatin1String("\'"));
761     text.replace(QLatin1String("[li]"), QLatin1String("* ")); // TODO: better replacement for list elements?
762     text.remove(QStringLiteral("[/li]"));
763     text.remove(QStringLiteral("[url]"));
764     text.remove(QStringLiteral("[/url]"));
765     return text;
766 }
767